// Copyright 2014 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 "third_party/blink/renderer/core/frame/visual_viewport.h" #include #include "cc/layers/picture_layer.h" #include "cc/layers/scrollbar_layer_base.h" #include "cc/trees/property_tree.h" #include "cc/trees/scroll_node.h" #include "cc/trees/transform_node.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/context_menu_data/context_menu_data.h" #include "third_party/blink/public/common/input/web_coalesced_input_event.h" #include "third_party/blink/public/common/input/web_input_event.h" #include "third_party/blink/public/common/widget/device_emulation_params.h" #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h" #include "third_party/blink/public/platform/web_url_loader_mock_factory.h" #include "third_party/blink/public/web/web_ax_context.h" #include "third_party/blink/public/web/web_document.h" #include "third_party/blink/public/web/web_local_frame_client.h" #include "third_party/blink/public/web/web_script_source.h" #include "third_party/blink/public/web/web_settings.h" #include "third_party/blink/public/web/web_view_client.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/frame/browser_controls.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.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h" #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" #include "third_party/blink/renderer/core/html/html_body_element.h" #include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/html/html_html_element.h" #include "third_party/blink/renderer/core/input/event_handler.h" #include "third_party/blink/renderer/core/layout/layout_object.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/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/scroll/scrollbar_theme_overlay_mobile.h" #include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h" #include "third_party/blink/renderer/core/testing/color_scheme_helper.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/geometry/double_point.h" #include "third_party/blink/renderer/platform/geometry/double_rect.h" #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h" #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h" #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" #include "third_party/blink/renderer/platform/testing/paint_property_test_helpers.h" #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h" #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" #include "third_party/blink/renderer/platform/testing/url_test_helpers.h" #include using testing::_; using testing::PrintToString; using testing::Mock; using testing::UnorderedElementsAre; using blink::url_test_helpers::ToKURL; namespace blink { ::std::ostream& operator<<(::std::ostream& os, const ContextMenuData& data) { return os << "Context menu location: [" << data.mouse_position.x() << ", " << data.mouse_position.y() << "]"; } namespace { void ConfigureAndroidCompositing(WebSettings* settings) { settings->SetPreferCompositingToLCDTextEnabled(true); settings->SetViewportMetaEnabled(true); settings->SetViewportEnabled(true); settings->SetMainFrameResizesAreOrientationChanges(true); settings->SetShrinksViewportContentToFit(true); } const cc::EffectNode* GetEffectNode(const cc::Layer* layer) { return layer->layer_tree_host()->property_trees()->effect_tree.Node( layer->effect_tree_index()); } class VisualViewportTest : public testing::Test, public PaintTestConfigurations { public: VisualViewportTest() : base_url_("http://www.test.com/") {} void InitializeWithDesktopSettings( void (*override_settings_func)(WebSettings*) = nullptr) { if (!override_settings_func) override_settings_func = &ConfigureSettings; helper_.Initialize(nullptr, nullptr, override_settings_func); WebView()->SetDefaultPageScaleLimits(1, 4); } void InitializeWithAndroidSettings( void (*override_settings_func)(WebSettings*) = nullptr) { if (!override_settings_func) override_settings_func = &ConfigureAndroidSettings; helper_.Initialize(nullptr, nullptr, override_settings_func); WebView()->SetDefaultPageScaleLimits(0.25f, 5); } ~VisualViewportTest() override { url_test_helpers::UnregisterAllURLsAndClearMemoryCache(); } void NavigateTo(const std::string& url) { frame_test_helpers::LoadFrame(WebView()->MainFrameImpl(), url); } void UpdateAllLifecyclePhases() { WebView()->MainFrameViewWidget()->UpdateAllLifecyclePhases( DocumentUpdateReason::kTest); } void UpdateAllLifecyclePhasesExceptPaint() { WebView()->MainFrameViewWidget()->UpdateLifecycle( WebLifecycleUpdate::kPrePaint, DocumentUpdateReason::kTest); } PaintArtifactCompositor* paint_artifact_compositor() { LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); return frame_view.GetPaintArtifactCompositor(); } void ForceFullCompositingUpdate() { UpdateAllLifecyclePhases(); } void RegisterMockedHttpURLLoad(const std::string& fileName) { // TODO(crbug.com/751425): We should use the mock functionality // via |helper_|. url_test_helpers::RegisterMockedURLLoadFromBase( WebString::FromUTF8(base_url_), blink::test::CoreTestDataPath(), WebString::FromUTF8(fileName)); } void RegisterMockedHttpURLLoad(const std::string& url, const std::string& fileName) { // TODO(crbug.com/751425): We should use the mock functionality // via |helper_|. url_test_helpers::RegisterMockedURLLoad( ToKURL(url), blink::test::CoreTestDataPath(WebString::FromUTF8(fileName))); } WebViewImpl* WebView() const { return helper_.GetWebView(); } LocalFrame* GetFrame() const { return helper_.LocalMainFrame()->GetFrame(); } static void ConfigureSettings(WebSettings* settings) { settings->SetJavaScriptEnabled(true); settings->SetPreferCompositingToLCDTextEnabled(true); } static void ConfigureAndroidSettings(WebSettings* settings) { ConfigureSettings(settings); settings->SetViewportEnabled(true); settings->SetViewportMetaEnabled(true); settings->SetShrinksViewportContentToFit(true); settings->SetMainFrameResizesAreOrientationChanges(true); } const GraphicsLayer* MainGraphicsLayer(const Document* document) { const auto* layer = document->GetLayoutView()->Layer(); return layer->GetCompositedLayerMapping() ? layer->GetCompositedLayerMapping()->MainGraphicsLayer() : nullptr; } const GraphicsLayer* ScrollingContentsLayer(const Document* document) { const auto* layer = document->GetLayoutView()->Layer(); return layer->GetCompositedLayerMapping() ? layer->GetCompositedLayerMapping()->ScrollingContentsLayer() : nullptr; } const DisplayItemClient& ScrollingBackgroundClient(const Document* document) { return document->GetLayoutView() ->GetScrollableArea() ->GetScrollingBackgroundDisplayItemClient(); } const RasterInvalidationTracking* MainGraphicsLayerRasterInvalidationTracking( const Document* document) { const auto* layer = MainGraphicsLayer(document); return layer ? layer->GetRasterInvalidationTracking() : nullptr; } const RasterInvalidationTracking* ScrollingContentsLayerRasterInvalidationTracking(const Document* document) { const auto* layer = ScrollingContentsLayer(document); return layer ? layer->GetRasterInvalidationTracking() : nullptr; } bool MainGraphicsLayerHasRasterInvalidations(const Document* document) { const auto* tracking = MainGraphicsLayerRasterInvalidationTracking(document); return tracking && tracking->HasInvalidations(); } bool ScrollingContentsLayerHasRasterInvalidations(const Document* document) { const auto* tracking = ScrollingContentsLayerRasterInvalidationTracking(document); return tracking && tracking->HasInvalidations(); } const Vector& MainGraphicsLayerRasterInvalidations( const Document* document) { DCHECK(MainGraphicsLayerHasRasterInvalidations(document)); return MainGraphicsLayerRasterInvalidationTracking(document) ->Invalidations(); } const Vector& ScrollingContentsLayerRasterInvalidations(const Document* document) { DCHECK(ScrollingContentsLayerHasRasterInvalidations(document)); return ScrollingContentsLayerRasterInvalidationTracking(document) ->Invalidations(); } protected: std::string base_url_; frame_test_helpers::WebViewHelper helper_; }; INSTANTIATE_PAINT_TEST_SUITE_P(VisualViewportTest); // Test that resizing the VisualViewport works as expected and that resizing the // WebView resizes the VisualViewport. TEST_P(VisualViewportTest, TestResize) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240)); WebView()->ResizeWithBrowserControls( gfx::Size(320, 240), gfx::Size(320, 240), WebView()->GetBrowserControls().Params()); UpdateAllLifecyclePhases(); NavigateTo("about:blank"); ForceFullCompositingUpdate(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); gfx::Size web_view_size = WebView()->MainFrameViewWidget()->Size(); // Make sure the visual viewport was initialized. EXPECT_EQ(web_view_size, gfx::Size(visual_viewport.Size())); // Resizing the WebView should change the VisualViewport. web_view_size = gfx::Size(640, 480); WebView()->MainFrameViewWidget()->Resize(web_view_size); WebView()->ResizeWithBrowserControls( web_view_size, web_view_size, WebView()->GetBrowserControls().Params()); UpdateAllLifecyclePhases(); EXPECT_EQ(web_view_size, WebView()->MainFrameViewWidget()->Size()); EXPECT_EQ(web_view_size, gfx::Size(visual_viewport.Size())); // Resizing the visual viewport shouldn't affect the WebView. IntSize new_viewport_size = IntSize(320, 200); visual_viewport.SetSize(new_viewport_size); EXPECT_EQ(web_view_size, WebView()->MainFrameViewWidget()->Size()); EXPECT_EQ(new_viewport_size, visual_viewport.Size()); } // Make sure that the visibleContentRect method acurately reflects the scale and // scroll location of the viewport with and without scrollbars. TEST_P(VisualViewportTest, TestVisibleContentRect) { USE_NON_OVERLAY_SCROLLBARS(); InitializeWithDesktopSettings(); RegisterMockedHttpURLLoad("200-by-300.html"); NavigateTo(base_url_ + "200-by-300.html"); IntSize size = IntSize(150, 100); // Vertical scrollbar width and horizontal scrollbar height. IntSize scrollbar_size = IntSize(15, 15); WebView()->ResizeWithBrowserControls( gfx::Size(size), gfx::Size(size), WebView()->GetBrowserControls().Params()); UpdateAllLifecyclePhases(); // Scroll layout viewport and verify visibleContentRect. WebView()->MainFrameImpl()->SetScrollOffset(gfx::ScrollOffset(0, 50)); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_EQ(IntRect(IntPoint(0, 0), size - scrollbar_size), visual_viewport.VisibleContentRect(kExcludeScrollbars)); EXPECT_EQ(IntRect(IntPoint(0, 0), size), visual_viewport.VisibleContentRect(kIncludeScrollbars)); WebView()->SetPageScaleFactor(2.0); // Scroll visual viewport and verify visibleContentRect. size.Scale(0.5); scrollbar_size.Scale(0.5); visual_viewport.SetLocation(FloatPoint(10, 10)); EXPECT_EQ(IntRect(IntPoint(10, 10), size - scrollbar_size), visual_viewport.VisibleContentRect(kExcludeScrollbars)); EXPECT_EQ(IntRect(IntPoint(10, 10), size), visual_viewport.VisibleContentRect(kIncludeScrollbars)); } // This tests that shrinking the WebView while the page is fully scrolled // doesn't move the viewport up/left, it should keep the visible viewport // unchanged from the user's perspective (shrinking the LocalFrameView will // clamp the VisualViewport so we need to counter scroll the LocalFrameView to // make it appear to stay still). This caused bugs like crbug.com/453859. TEST_P(VisualViewportTest, TestResizeAtFullyScrolledPreservesViewportLocation) { InitializeWithDesktopSettings(); WebView()->ResizeWithBrowserControls( gfx::Size(800, 600), gfx::Size(800, 600), WebView()->GetBrowserControls().Params()); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.SetScale(2); // Fully scroll both viewports. frame_view.LayoutViewport()->SetScrollOffset( ScrollOffset(10000, 10000), mojom::blink::ScrollType::kProgrammatic); visual_viewport.Move(FloatSize(10000, 10000)); // Sanity check. ASSERT_EQ(FloatSize(400, 300), visual_viewport.GetScrollOffset()); ASSERT_EQ(ScrollOffset(200, 1400), frame_view.LayoutViewport()->GetScrollOffset()); IntPoint expected_location = frame_view.GetScrollableArea()->VisibleContentRect().Location(); // Shrink the WebView, this should cause both viewports to shrink and // WebView should do whatever it needs to do to preserve the visible // location. WebView()->ResizeWithBrowserControls( gfx::Size(700, 550), gfx::Size(800, 600), WebView()->GetBrowserControls().Params()); UpdateAllLifecyclePhases(); EXPECT_EQ(expected_location, frame_view.GetScrollableArea()->VisibleContentRect().Location()); WebView()->ResizeWithBrowserControls( gfx::Size(800, 600), gfx::Size(800, 600), WebView()->GetBrowserControls().Params()); UpdateAllLifecyclePhases(); EXPECT_EQ(expected_location, frame_view.GetScrollableArea()->VisibleContentRect().Location()); } // Test that the VisualViewport works as expected in case of a scaled // and scrolled viewport - scroll down. TEST_P(VisualViewportTest, TestResizeAfterVerticalScroll) { /* 200 200 | | | | | | | | | | 800 | | 800 |-------------------| | | | | | | | | | | | | | | | | --------> | | | 300 | | | | | | | | 400 | | | | | |-------------------| | | | 75 | | 50 | | 50 100| o----- | o---- | | | | | | 25 | | |100 | |-------------------| | | | | | | | | | | -------------------- -------------------- */ InitializeWithAndroidSettings(); RegisterMockedHttpURLLoad("200-by-800-viewport.html"); NavigateTo(base_url_ + "200-by-800-viewport.html"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 200)); // Scroll main frame to the bottom of the document WebView()->MainFrameImpl()->SetScrollOffset(gfx::ScrollOffset(0, 400)); EXPECT_EQ(ScrollOffset(0, 400), GetFrame()->View()->LayoutViewport()->GetScrollOffset()); WebView()->SetPageScaleFactor(2.0); // Scroll visual viewport to the bottom of the main frame VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.SetLocation(FloatPoint(0, 300)); EXPECT_FLOAT_SIZE_EQ(FloatSize(0, 300), visual_viewport.GetScrollOffset()); // Verify the initial size of the visual viewport in the CSS pixels EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 100), visual_viewport.VisibleRect().Size()); // Verify the paint property nodes and GeometryMapper cache. { UpdateAllLifecyclePhases(); EXPECT_EQ(TransformationMatrix().Scale(2), visual_viewport.GetPageScaleNode()->Matrix()); EXPECT_EQ(FloatSize(0, -300), visual_viewport.GetScrollTranslationNode()->Translation2D()); EXPECT_EQ(TransformationMatrix().Scale(2).Translate(0, -300), GeometryMapper::SourceToDestinationProjection( *visual_viewport.GetScrollTranslationNode(), TransformPaintPropertyNode::Root()) .Matrix()); } // Perform the resizing WebView()->MainFrameViewWidget()->Resize(gfx::Size(200, 100)); // After resizing the scale changes 2.0 -> 4.0 EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 25), visual_viewport.VisibleRect().Size()); EXPECT_EQ(ScrollOffset(0, 625), GetFrame()->View()->LayoutViewport()->GetScrollOffset()); EXPECT_FLOAT_SIZE_EQ(FloatSize(0, 75), visual_viewport.GetScrollOffset()); // Verify the paint property nodes and GeometryMapper cache. { UpdateAllLifecyclePhases(); EXPECT_EQ(TransformationMatrix().Scale(4), visual_viewport.GetPageScaleNode()->Matrix()); EXPECT_EQ(FloatSize(0, -75), visual_viewport.GetScrollTranslationNode()->Translation2D()); EXPECT_EQ(TransformationMatrix().Scale(4).Translate(0, -75), GeometryMapper::SourceToDestinationProjection( *visual_viewport.GetScrollTranslationNode(), TransformPaintPropertyNode::Root()) .Matrix()); } } // Test that the VisualViewport works as expected in case if a scaled // and scrolled viewport - scroll right. TEST_P(VisualViewportTest, TestResizeAfterHorizontalScroll) { /* 200 200 ---------------o----- ---------------o----- | | | | 25| | | | | | -----| | 100| | |100 50 | | | | | | | ---- | |-------------------| | | | | | | | | | | | | | | | | | | | | |400 | ---------> | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |-------------------| | | | | | | */ InitializeWithAndroidSettings(); RegisterMockedHttpURLLoad("200-by-800-viewport.html"); NavigateTo(base_url_ + "200-by-800-viewport.html"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 200)); // Outer viewport takes the whole width of the document. WebView()->SetPageScaleFactor(2.0); // Scroll visual viewport to the right edge of the frame VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.SetLocation(FloatPoint(150, 0)); EXPECT_FLOAT_SIZE_EQ(FloatSize(150, 0), visual_viewport.GetScrollOffset()); // Verify the initial size of the visual viewport in the CSS pixels EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 100), visual_viewport.VisibleRect().Size()); // Verify the paint property nodes and GeometryMapper cache. { UpdateAllLifecyclePhases(); EXPECT_EQ(TransformationMatrix().Scale(2), visual_viewport.GetPageScaleNode()->Matrix()); EXPECT_EQ(FloatSize(-150, 0), visual_viewport.GetScrollTranslationNode()->Translation2D()); EXPECT_EQ(TransformationMatrix().Scale(2).Translate(-150, 0), GeometryMapper::SourceToDestinationProjection( *visual_viewport.GetScrollTranslationNode(), TransformPaintPropertyNode::Root()) .Matrix()); } WebView()->MainFrameViewWidget()->Resize(gfx::Size(200, 100)); // After resizing the scale changes 2.0 -> 4.0 EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 25), visual_viewport.VisibleRect().Size()); EXPECT_EQ(ScrollOffset(0, 0), GetFrame()->View()->LayoutViewport()->GetScrollOffset()); EXPECT_FLOAT_SIZE_EQ(FloatSize(150, 0), visual_viewport.GetScrollOffset()); // Verify the paint property nodes and GeometryMapper cache. { UpdateAllLifecyclePhases(); EXPECT_EQ(TransformationMatrix().Scale(4), visual_viewport.GetPageScaleNode()->Matrix()); EXPECT_EQ(FloatSize(-150, 0), visual_viewport.GetScrollTranslationNode()->Translation2D()); EXPECT_EQ(TransformationMatrix().Scale(4).Translate(-150, 0), GeometryMapper::SourceToDestinationProjection( *visual_viewport.GetScrollTranslationNode(), TransformPaintPropertyNode::Root()) .Matrix()); } } // Make sure that the visibleRect method acurately reflects the scale and scroll // location of the viewport. TEST_P(VisualViewportTest, TestVisibleRect) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240)); NavigateTo("about:blank"); ForceFullCompositingUpdate(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); // Initial visible rect should be the whole frame. EXPECT_EQ(IntSize(WebView()->MainFrameViewWidget()->Size()), visual_viewport.Size()); // Viewport is whole frame. IntSize size = IntSize(400, 200); WebView()->MainFrameViewWidget()->Resize(gfx::Size(size)); UpdateAllLifecyclePhases(); visual_viewport.SetSize(size); // Scale the viewport to 2X; size should not change. FloatRect expected_rect(FloatPoint(0, 0), FloatSize(size)); expected_rect.Scale(0.5); visual_viewport.SetScale(2); EXPECT_EQ(2, visual_viewport.Scale()); EXPECT_EQ(size, visual_viewport.Size()); EXPECT_FLOAT_RECT_EQ(expected_rect, visual_viewport.VisibleRect()); // Move the viewport. expected_rect.SetLocation(FloatPoint(5, 7)); visual_viewport.SetLocation(expected_rect.Location()); EXPECT_FLOAT_RECT_EQ(expected_rect, visual_viewport.VisibleRect()); expected_rect.SetLocation(FloatPoint(200, 100)); visual_viewport.SetLocation(expected_rect.Location()); EXPECT_FLOAT_RECT_EQ(expected_rect, visual_viewport.VisibleRect()); // Scale the viewport to 3X to introduce some non-int values. FloatPoint oldLocation = expected_rect.Location(); expected_rect = FloatRect(FloatPoint(), FloatSize(size)); expected_rect.Scale(1 / 3.0f); expected_rect.SetLocation(oldLocation); visual_viewport.SetScale(3); EXPECT_FLOAT_RECT_EQ(expected_rect, visual_viewport.VisibleRect()); expected_rect.SetLocation(FloatPoint(0.25f, 0.333f)); visual_viewport.SetLocation(expected_rect.Location()); EXPECT_FLOAT_RECT_EQ(expected_rect, visual_viewport.VisibleRect()); } TEST_P(VisualViewportTest, TestFractionalScrollOffsetIsNotOverwritten) { ScopedFractionalScrollOffsetsForTest fractional_scroll_offsets(true); InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(200, 250)); RegisterMockedHttpURLLoad("200-by-800-viewport.html"); NavigateTo(base_url_ + "200-by-800-viewport.html"); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); frame_view.LayoutViewport()->SetScrollOffset( ScrollOffset(0, 10.5), mojom::blink::ScrollType::kProgrammatic); frame_view.LayoutViewport()->ScrollableArea::SetScrollOffset( ScrollOffset(10, 30.5), mojom::blink::ScrollType::kCompositor); EXPECT_EQ(30.5, frame_view.LayoutViewport()->GetScrollOffset().Height()); } // Test that the viewport's scroll offset is always appropriately bounded such // that the visual viewport always stays within the bounds of the main frame. TEST_P(VisualViewportTest, TestOffsetClamping) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240)); WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); frame_test_helpers::LoadHTMLString( WebView()->MainFrameImpl(), "" "", base_url); ForceFullCompositingUpdate(); // Visual viewport should be initialized to same size as frame so no scrolling // possible. At minimum scale, the viewport is 1280x960. VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); ASSERT_EQ(0.25, visual_viewport.Scale()); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(-1, -2)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(100, 200)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(-5, 10)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); // Scale to 2x. The viewport's visible rect should now have a size of 160x120. visual_viewport.SetScale(2); FloatPoint location(10, 50); visual_viewport.SetLocation(location); EXPECT_FLOAT_POINT_EQ(location, visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(10000, 10000)); EXPECT_FLOAT_POINT_EQ(FloatPoint(1120, 840), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(-2000, -2000)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); // Make sure offset gets clamped on scale out. Scale to 1.25 so the viewport // is 256x192. visual_viewport.SetLocation(FloatPoint(1120, 840)); visual_viewport.SetScale(1.25); EXPECT_FLOAT_POINT_EQ(FloatPoint(1024, 768), visual_viewport.VisibleRect().Location()); // Scale out smaller than 1. visual_viewport.SetScale(0.25); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); } // Test that the viewport can be scrolled around only within the main frame in // the presence of viewport resizes, as would be the case if the on screen // keyboard came up. TEST_P(VisualViewportTest, TestOffsetClampingWithResize) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240)); NavigateTo("about:blank"); ForceFullCompositingUpdate(); // Visual viewport should be initialized to same size as frame so no scrolling // possible. VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); // Shrink the viewport vertically. The resize shouldn't affect the location, // but it should allow vertical scrolling. visual_viewport.SetSize(IntSize(320, 200)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(10, 20)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 20), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(0, 100)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 40), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(0, 10)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 10), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(0, -100)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); // Repeat the above but for horizontal dimension. visual_viewport.SetSize(IntSize(280, 240)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(10, 20)); EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 0), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(100, 0)); EXPECT_FLOAT_POINT_EQ(FloatPoint(40, 0), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(10, 0)); EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 0), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(-100, 0)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); // Now with both dimensions. visual_viewport.SetSize(IntSize(280, 200)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(10, 20)); EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 20), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(100, 100)); EXPECT_FLOAT_POINT_EQ(FloatPoint(40, 40), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(10, 3)); EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 3), visual_viewport.VisibleRect().Location()); visual_viewport.SetLocation(FloatPoint(-10, -4)); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); } // Test that the viewport is scrollable but bounded appropriately within the // main frame when we apply both scaling and resizes. TEST_P(VisualViewportTest, TestOffsetClampingWithResizeAndScale) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240)); NavigateTo("about:blank"); ForceFullCompositingUpdate(); // Visual viewport should be initialized to same size as WebView so no // scrolling possible. VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visual_viewport.VisibleRect().Location()); // Zoom in to 2X so we can scroll the viewport to 160x120. visual_viewport.SetScale(2); visual_viewport.SetLocation(FloatPoint(200, 200)); EXPECT_FLOAT_POINT_EQ(FloatPoint(160, 120), visual_viewport.VisibleRect().Location()); // Now resize the viewport to make it 10px smaller. Since we're zoomed in by // 2X it should allow us to scroll by 5px more. visual_viewport.SetSize(IntSize(310, 230)); visual_viewport.SetLocation(FloatPoint(200, 200)); EXPECT_FLOAT_POINT_EQ(FloatPoint(165, 125), visual_viewport.VisibleRect().Location()); // The viewport can be larger than the main frame (currently 320, 240) though // typically the scale will be clamped to prevent it from actually being // larger. visual_viewport.SetSize(IntSize(330, 250)); EXPECT_EQ(IntSize(330, 250), visual_viewport.Size()); // Resize both the viewport and the frame to be larger. WebView()->MainFrameViewWidget()->Resize(gfx::Size(640, 480)); UpdateAllLifecyclePhases(); EXPECT_EQ(IntSize(WebView()->MainFrameViewWidget()->Size()), visual_viewport.Size()); EXPECT_EQ(IntSize(WebView()->MainFrameViewWidget()->Size()), GetFrame()->View()->FrameRect().Size()); visual_viewport.SetLocation(FloatPoint(1000, 1000)); EXPECT_FLOAT_POINT_EQ(FloatPoint(320, 240), visual_viewport.VisibleRect().Location()); // Make sure resizing the viewport doesn't change its offset if the resize // doesn't make the viewport go out of bounds. visual_viewport.SetLocation(FloatPoint(200, 200)); visual_viewport.SetSize(IntSize(880, 560)); EXPECT_FLOAT_POINT_EQ(FloatPoint(200, 200), visual_viewport.VisibleRect().Location()); } // The main LocalFrameView's size should be set such that its the size of the // visual viewport at minimum scale. If there's no explicit minimum scale set, // the LocalFrameView should be set to the content width and height derived by // the aspect ratio. TEST_P(VisualViewportTest, TestFrameViewSizedToContent) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240)); RegisterMockedHttpURLLoad("200-by-300-viewport.html"); NavigateTo(base_url_ + "200-by-300-viewport.html"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(600, 800)); UpdateAllLifecyclePhases(); // Note: the size is ceiled and should match the behavior in CC's // LayerImpl::bounds(). EXPECT_EQ(IntSize(200, 267), WebView()->MainFrameImpl()->GetFrameView()->FrameRect().Size()); } // The main LocalFrameView's size should be set such that its the size of the // visual viewport at minimum scale. On Desktop, the minimum scale is set at 1 // so make sure the LocalFrameView is sized to the viewport. TEST_P(VisualViewportTest, TestFrameViewSizedToMinimumScale) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240)); RegisterMockedHttpURLLoad("200-by-300.html"); NavigateTo(base_url_ + "200-by-300.html"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 160)); UpdateAllLifecyclePhases(); EXPECT_EQ(IntSize(100, 160), WebView()->MainFrameImpl()->GetFrameView()->FrameRect().Size()); } // Test that attaching a new frame view resets the size of the inner viewport // scroll layer. crbug.com/423189. TEST_P(VisualViewportTest, TestAttachingNewFrameSetsInnerScrollLayerSize) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240)); // Load a wider page first, the navigation should resize the scroll layer to // the smaller size on the second navigation. RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.SetScale(2); visual_viewport.Move(ScrollOffset(50, 60)); // Move and scale the viewport to make sure it gets reset in the navigation. EXPECT_EQ(FloatSize(50, 60), visual_viewport.GetScrollOffset()); EXPECT_EQ(2, visual_viewport.Scale()); // Navigate again, this time the LocalFrameView should be smaller. RegisterMockedHttpURLLoad("viewport-device-width.html"); NavigateTo(base_url_ + "viewport-device-width.html"); UpdateAllLifecyclePhases(); // Ensure the scroll contents size matches the frame view's size. EXPECT_EQ(gfx::Size(320, 240), visual_viewport.LayerForScrolling()->bounds()); EXPECT_EQ(IntSize(320, 240), visual_viewport.GetScrollNode()->ContentsSize()); // Ensure the location and scale were reset. EXPECT_EQ(FloatSize(), visual_viewport.GetScrollOffset()); EXPECT_EQ(1, visual_viewport.Scale()); } // The main LocalFrameView's size should be set such that its the size of the // visual viewport at minimum scale. Test that the LocalFrameView is // appropriately sized in the presence of a viewport tag. TEST_P(VisualViewportTest, TestFrameViewSizedToViewportMetaMinimumScale) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240)); RegisterMockedHttpURLLoad("200-by-300-min-scale-2.html"); NavigateTo(base_url_ + "200-by-300-min-scale-2.html"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 160)); UpdateAllLifecyclePhases(); EXPECT_EQ(IntSize(50, 80), WebView()->MainFrameImpl()->GetFrameView()->FrameRect().Size()); } // Test that the visual viewport still gets sized in AutoSize/AutoResize mode. TEST_P(VisualViewportTest, TestVisualViewportGetsSizeInAutoSizeMode) { InitializeWithDesktopSettings(); EXPECT_EQ(IntSize(0, 0), IntSize(WebView()->MainFrameViewWidget()->Size())); EXPECT_EQ(IntSize(0, 0), GetFrame()->GetPage()->GetVisualViewport().Size()); WebView()->EnableAutoResizeMode(gfx::Size(10, 10), gfx::Size(1000, 1000)); RegisterMockedHttpURLLoad("200-by-300.html"); NavigateTo(base_url_ + "200-by-300.html"); EXPECT_EQ(IntSize(200, 300), GetFrame()->GetPage()->GetVisualViewport().Size()); } // Test that the text selection handle's position accounts for the visual // viewport. TEST_P(VisualViewportTest, TestTextSelectionHandles) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(500, 800)); RegisterMockedHttpURLLoad("pinch-viewport-input-field.html"); NavigateTo(base_url_ + "pinch-viewport-input-field.html"); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); To(WebView()->GetPage()->MainFrame())->SetInitialFocus(false); gfx::Rect original_anchor; gfx::Rect original_focus; WebView()->MainFrameViewWidget()->CalculateSelectionBounds(original_anchor, original_focus); WebView()->SetPageScaleFactor(2); visual_viewport.SetLocation(FloatPoint(100, 400)); gfx::Rect anchor; gfx::Rect focus; WebView()->MainFrameViewWidget()->CalculateSelectionBounds(anchor, focus); IntPoint expected(IntRect(original_anchor).Location()); expected.MoveBy(-FlooredIntPoint(visual_viewport.VisibleRect().Location())); expected.Scale(visual_viewport.Scale(), visual_viewport.Scale()); EXPECT_EQ(expected, IntRect(anchor).Location()); EXPECT_EQ(expected, IntRect(focus).Location()); // FIXME(bokan) - http://crbug.com/364154 - Figure out how to test text // selection as well rather than just carret. } // Test that the HistoryItem for the page stores the visual viewport's offset // and scale. TEST_P(VisualViewportTest, TestSavedToHistoryItem) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(200, 300)); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("200-by-300.html"); NavigateTo(base_url_ + "200-by-300.html"); EXPECT_FALSE(To(WebView()->GetPage()->MainFrame()) ->Loader() .GetDocumentLoader() ->GetHistoryItem() ->GetViewState() .has_value()); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.SetScale(2); EXPECT_EQ(2, To(WebView()->GetPage()->MainFrame()) ->Loader() .GetDocumentLoader() ->GetHistoryItem() ->GetViewState() ->page_scale_factor_); visual_viewport.SetLocation(FloatPoint(10, 20)); EXPECT_EQ(ScrollOffset(10, 20), To(WebView()->GetPage()->MainFrame()) ->Loader() .GetDocumentLoader() ->GetHistoryItem() ->GetViewState() ->visual_viewport_scroll_offset_); } // Test restoring a HistoryItem properly restores the visual viewport's state. TEST_P(VisualViewportTest, TestRestoredFromHistoryItem) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(200, 300)); RegisterMockedHttpURLLoad("200-by-300.html"); WebHistoryItem item; item.Initialize(); WebURL destination_url( url_test_helpers::ToKURL(base_url_ + "200-by-300.html")); item.SetURLString(destination_url.GetString()); item.SetVisualViewportScrollOffset(gfx::PointF(100, 120)); item.SetPageScaleFactor(2); frame_test_helpers::LoadHistoryItem(WebView()->MainFrameImpl(), item, mojom::FetchCacheMode::kDefault); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_EQ(2, visual_viewport.Scale()); EXPECT_FLOAT_POINT_EQ(FloatPoint(100, 120), visual_viewport.VisibleRect().Location()); } // Test restoring a HistoryItem without the visual viewport offset falls back to // distributing the scroll offset between the main frame and the visual // viewport. TEST_P(VisualViewportTest, TestRestoredFromLegacyHistoryItem) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 150)); RegisterMockedHttpURLLoad("200-by-300-viewport.html"); WebHistoryItem item; item.Initialize(); WebURL destination_url( url_test_helpers::ToKURL(base_url_ + "200-by-300-viewport.html")); item.SetURLString(destination_url.GetString()); // (-1, -1) will be used if the HistoryItem is an older version prior to // having visual viewport scroll offset. item.SetVisualViewportScrollOffset(gfx::PointF(-1, -1)); item.SetScrollOffset(gfx::Point(120, 180)); item.SetPageScaleFactor(2); frame_test_helpers::LoadHistoryItem(WebView()->MainFrameImpl(), item, mojom::FetchCacheMode::kDefault); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_EQ(2, visual_viewport.Scale()); EXPECT_EQ(ScrollOffset(100, 150), GetFrame()->View()->LayoutViewport()->GetScrollOffset()); EXPECT_FLOAT_POINT_EQ(FloatPoint(20, 30), visual_viewport.VisibleRect().Location()); } // Test that navigation to a new page with a different sized main frame doesn't // clobber the history item's main frame scroll offset. crbug.com/371867 TEST_P(VisualViewportTest, TestNavigateToSmallerFrameViewHistoryItemClobberBug) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(400, 400)); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); LocalFrameView* frame_view = WebView()->MainFrameImpl()->GetFrameView(); frame_view->LayoutViewport()->SetScrollOffset( ScrollOffset(0, 1000), mojom::blink::ScrollType::kProgrammatic); EXPECT_EQ(IntSize(1000, 1000), frame_view->FrameRect().Size()); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.SetScale(2); visual_viewport.SetLocation(FloatPoint(350, 350)); Persistent firstItem = WebView() ->MainFrameImpl() ->GetFrame() ->Loader() .GetDocumentLoader() ->GetHistoryItem(); EXPECT_EQ(ScrollOffset(0, 1000), firstItem->GetViewState()->scroll_offset_); // Now navigate to a page which causes a smaller frame_view. Make sure that // navigating doesn't cause the history item to set a new scroll offset // before the item was replaced. NavigateTo("about:blank"); frame_view = WebView()->MainFrameImpl()->GetFrameView(); EXPECT_NE(firstItem, WebView() ->MainFrameImpl() ->GetFrame() ->Loader() .GetDocumentLoader() ->GetHistoryItem()); EXPECT_LT(frame_view->FrameRect().Size().Width(), 1000); EXPECT_EQ(ScrollOffset(0, 1000), firstItem->GetViewState()->scroll_offset_); } // Test that the coordinates sent into moveRangeSelection are offset by the // visual viewport's location. TEST_P(VisualViewportTest, DISABLED_TestWebFrameRangeAccountsForVisualViewportScroll) { InitializeWithDesktopSettings(); WebView()->GetSettings()->SetDefaultFontSize(12); WebView()->MainFrameViewWidget()->Resize(gfx::Size(640, 480)); RegisterMockedHttpURLLoad("move_range.html"); NavigateTo(base_url_ + "move_range.html"); gfx::Rect base_rect; gfx::Rect extent_rect; WebView()->SetPageScaleFactor(2); WebLocalFrame* main_frame = WebView()->MainFrameImpl(); // Select some text and get the base and extent rects (that's the start of // the range and its end). Do a sanity check that the expected text is // selected main_frame->ExecuteScript(WebScriptSource("selectRange();")); EXPECT_EQ("ir", main_frame->SelectionAsText().Utf8()); WebView()->MainFrameViewWidget()->CalculateSelectionBounds(base_rect, extent_rect); gfx::Point initial_point = base_rect.origin(); gfx::Point end_point = extent_rect.origin(); // Move the visual viewport over and make the selection in the same // screen-space location. The selection should change to two characters to the // right and down one line. VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.Move(ScrollOffset(60, 25)); main_frame->MoveRangeSelection(initial_point, end_point); EXPECT_EQ("t ", main_frame->SelectionAsText().Utf8()); } // Test that resizing the WebView causes ViewportConstrained objects to // relayout. TEST_P(VisualViewportTest, TestWebViewResizeCausesViewportConstrainedLayout) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(500, 300)); RegisterMockedHttpURLLoad("pinch-viewport-fixed-pos.html"); NavigateTo(base_url_ + "pinch-viewport-fixed-pos.html"); LayoutObject* navbar = GetFrame()->GetDocument()->getElementById("navbar")->GetLayoutObject(); EXPECT_FALSE(navbar->NeedsLayout()); GetFrame()->View()->Resize(IntSize(500, 200)); EXPECT_TRUE(navbar->NeedsLayout()); } class VisualViewportMockWebFrameClient : public frame_test_helpers::TestWebFrameClient { public: MOCK_METHOD2(UpdateContextMenuDataForTesting, void(const ContextMenuData&, const absl::optional&)); MOCK_METHOD0(DidChangeScrollOffset, void()); }; MATCHER_P2(ContextMenuAtLocation, x, y, std::string(negation ? "is" : "isn't") + " at expected location [" + PrintToString(x) + ", " + PrintToString(y) + "]") { return arg.mouse_position.x() == x && arg.mouse_position.y() == y; } // Test that the context menu's location is correct in the presence of visual // viewport offset. TEST_P(VisualViewportTest, TestContextMenuShownInCorrectLocation) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(200, 300)); RegisterMockedHttpURLLoad("200-by-300.html"); NavigateTo(base_url_ + "200-by-300.html"); WebMouseEvent mouse_down_event(WebInputEvent::Type::kMouseDown, WebInputEvent::kNoModifiers, WebInputEvent::GetStaticTimeStampForTests()); mouse_down_event.SetPositionInWidget(10, 10); mouse_down_event.SetPositionInScreen(110, 210); mouse_down_event.click_count = 1; mouse_down_event.button = WebMouseEvent::Button::kRight; // Corresponding release event (Windows shows context menu on release). WebMouseEvent mouse_up_event(mouse_down_event); mouse_up_event.SetType(WebInputEvent::Type::kMouseUp); WebLocalFrameClient* old_client = WebView()->MainFrameImpl()->Client(); VisualViewportMockWebFrameClient mock_web_frame_client; EXPECT_CALL( mock_web_frame_client, UpdateContextMenuDataForTesting( ContextMenuAtLocation(mouse_down_event.PositionInWidget().x(), mouse_down_event.PositionInWidget().y()), _)); // Do a sanity check with no scale applied. WebView()->MainFrameImpl()->SetClient(&mock_web_frame_client); WebView()->MainFrameViewWidget()->HandleInputEvent( WebCoalescedInputEvent(mouse_down_event, ui::LatencyInfo())); WebView()->MainFrameViewWidget()->HandleInputEvent( WebCoalescedInputEvent(mouse_up_event, ui::LatencyInfo())); Mock::VerifyAndClearExpectations(&mock_web_frame_client); mouse_down_event.button = WebMouseEvent::Button::kLeft; WebView()->MainFrameViewWidget()->HandleInputEvent( WebCoalescedInputEvent(mouse_down_event, ui::LatencyInfo())); // Now pinch zoom into the page and move the visual viewport. The context menu // should still appear at the location of the event, relative to the WebView. VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); WebView()->SetPageScaleFactor(2); EXPECT_CALL(mock_web_frame_client, DidChangeScrollOffset()); visual_viewport.SetLocation(FloatPoint(60, 80)); EXPECT_CALL( mock_web_frame_client, UpdateContextMenuDataForTesting( ContextMenuAtLocation(mouse_down_event.PositionInWidget().x(), mouse_down_event.PositionInWidget().y()), _)); mouse_down_event.button = WebMouseEvent::Button::kRight; WebView()->MainFrameViewWidget()->HandleInputEvent( WebCoalescedInputEvent(mouse_down_event, ui::LatencyInfo())); WebView()->MainFrameViewWidget()->HandleInputEvent( WebCoalescedInputEvent(mouse_up_event, ui::LatencyInfo())); // Reset the old client so destruction can occur naturally. WebView()->MainFrameImpl()->SetClient(old_client); } // Test that the client is notified if page scroll events. TEST_P(VisualViewportTest, TestClientNotifiedOfScrollEvents) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(200, 300)); RegisterMockedHttpURLLoad("200-by-300.html"); NavigateTo(base_url_ + "200-by-300.html"); WebLocalFrameClient* old_client = WebView()->MainFrameImpl()->Client(); VisualViewportMockWebFrameClient mock_web_frame_client; WebView()->MainFrameImpl()->SetClient(&mock_web_frame_client); WebView()->SetPageScaleFactor(2); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_CALL(mock_web_frame_client, DidChangeScrollOffset()); visual_viewport.SetLocation(FloatPoint(60, 80)); Mock::VerifyAndClearExpectations(&mock_web_frame_client); // Scroll vertically. EXPECT_CALL(mock_web_frame_client, DidChangeScrollOffset()); visual_viewport.SetLocation(FloatPoint(60, 90)); Mock::VerifyAndClearExpectations(&mock_web_frame_client); // Scroll horizontally. EXPECT_CALL(mock_web_frame_client, DidChangeScrollOffset()); visual_viewport.SetLocation(FloatPoint(70, 90)); // Reset the old client so destruction can occur naturally. WebView()->MainFrameImpl()->SetClient(old_client); } // Tests that calling scroll into view on a visible element doesn't cause // a scroll due to a fractional offset. Bug crbug.com/463356. TEST_P(VisualViewportTest, ScrollIntoViewFractionalOffset) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(1000, 1000)); RegisterMockedHttpURLLoad("scroll-into-view.html"); NavigateTo(base_url_ + "scroll-into-view.html"); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); ScrollableArea* layout_viewport_scrollable_area = frame_view.LayoutViewport(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); Element* inputBox = GetFrame()->GetDocument()->getElementById("box"); WebView()->SetPageScaleFactor(2); // The element is already in the view so the scrollIntoView shouldn't move // the viewport at all. WebView()->SetVisualViewportOffset(gfx::PointF(250.25f, 100.25f)); layout_viewport_scrollable_area->SetScrollOffset( ScrollOffset(0, 900.75), mojom::blink::ScrollType::kProgrammatic); inputBox->scrollIntoViewIfNeeded(false); if (RuntimeEnabledFeatures::FractionalScrollOffsetsEnabled()) { EXPECT_EQ(ScrollOffset(0, 900.75), layout_viewport_scrollable_area->GetScrollOffset()); } else { EXPECT_EQ(ScrollOffset(0, 900), layout_viewport_scrollable_area->GetScrollOffset()); } EXPECT_EQ(FloatSize(250.25f, 100.25f), visual_viewport.GetScrollOffset()); // Change the fractional part of the frameview to one that would round down. layout_viewport_scrollable_area->SetScrollOffset( ScrollOffset(0, 900.125), mojom::blink::ScrollType::kProgrammatic); inputBox->scrollIntoViewIfNeeded(false); if (RuntimeEnabledFeatures::FractionalScrollOffsetsEnabled()) { EXPECT_EQ(ScrollOffset(0, 900.125), layout_viewport_scrollable_area->GetScrollOffset()); } else { EXPECT_EQ(ScrollOffset(0, 900), layout_viewport_scrollable_area->GetScrollOffset()); } EXPECT_EQ(FloatSize(250.25f, 100.25f), visual_viewport.GetScrollOffset()); // Repeat both tests above with the visual viewport at a high fractional. WebView()->SetVisualViewportOffset(gfx::PointF(250.875f, 100.875f)); layout_viewport_scrollable_area->SetScrollOffset( ScrollOffset(0, 900.75), mojom::blink::ScrollType::kProgrammatic); inputBox->scrollIntoViewIfNeeded(false); if (RuntimeEnabledFeatures::FractionalScrollOffsetsEnabled()) { EXPECT_EQ(ScrollOffset(0, 900.75), layout_viewport_scrollable_area->GetScrollOffset()); } else { EXPECT_EQ(ScrollOffset(0, 900), layout_viewport_scrollable_area->GetScrollOffset()); } EXPECT_EQ(FloatSize(250.875f, 100.875f), visual_viewport.GetScrollOffset()); // Change the fractional part of the frameview to one that would round down. layout_viewport_scrollable_area->SetScrollOffset( ScrollOffset(0, 900.125), mojom::blink::ScrollType::kProgrammatic); inputBox->scrollIntoViewIfNeeded(false); if (RuntimeEnabledFeatures::FractionalScrollOffsetsEnabled()) { EXPECT_EQ(ScrollOffset(0, 900.125), layout_viewport_scrollable_area->GetScrollOffset()); } else { EXPECT_EQ(ScrollOffset(0, 900), layout_viewport_scrollable_area->GetScrollOffset()); } EXPECT_EQ(FloatSize(250.875f, 100.875f), visual_viewport.GetScrollOffset()); // Both viewports with a 0.5 fraction. WebView()->SetVisualViewportOffset(gfx::PointF(250.5f, 100.5f)); layout_viewport_scrollable_area->SetScrollOffset( ScrollOffset(0, 900.5), mojom::blink::ScrollType::kProgrammatic); inputBox->scrollIntoViewIfNeeded(false); if (RuntimeEnabledFeatures::FractionalScrollOffsetsEnabled()) { EXPECT_EQ(ScrollOffset(0, 900.5), layout_viewport_scrollable_area->GetScrollOffset()); } else { EXPECT_EQ(ScrollOffset(0, 900), layout_viewport_scrollable_area->GetScrollOffset()); } EXPECT_EQ(FloatSize(250.5f, 100.5f), visual_viewport.GetScrollOffset()); } static ScrollOffset expectedMaxLayoutViewportScrollOffset( VisualViewport& visual_viewport, LocalFrameView& frame_view) { float aspect_ratio = visual_viewport.VisibleRect().Width() / visual_viewport.VisibleRect().Height(); float new_height = frame_view.FrameRect().Width() / aspect_ratio; IntSize contents_size = frame_view.LayoutViewport()->ContentsSize(); return ScrollOffset(contents_size.Width() - frame_view.FrameRect().Width(), contents_size.Height() - new_height); } TEST_P(VisualViewportTest, TestBrowserControlsAdjustment) { InitializeWithAndroidSettings(); WebView()->ResizeWithBrowserControls(gfx::Size(500, 450), 20, 0, false); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); visual_viewport.SetScale(1); EXPECT_EQ(FloatSize(500, 450), visual_viewport.VisibleRect().Size()); EXPECT_EQ(IntSize(1000, 900), frame_view.FrameRect().Size()); // Simulate bringing down the browser controls by 20px. WebView()->MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(), gfx::Vector2dF(), 1, false, 1, 0, cc::BrowserControlsState::kBoth}); EXPECT_EQ(FloatSize(500, 430), visual_viewport.VisibleRect().Size()); // Test that the scroll bounds are adjusted appropriately: the visual viewport // should be shrunk by 20px to 430px. The outer viewport was shrunk to // maintain the // aspect ratio so it's height is 860px. visual_viewport.Move(ScrollOffset(10000, 10000)); EXPECT_EQ(FloatSize(500, 860 - 430), visual_viewport.GetScrollOffset()); // The outer viewport (LocalFrameView) should be affected as well. frame_view.LayoutViewport()->ScrollBy(ScrollOffset(10000, 10000), mojom::blink::ScrollType::kUser); EXPECT_EQ(expectedMaxLayoutViewportScrollOffset(visual_viewport, frame_view), frame_view.LayoutViewport()->GetScrollOffset()); // Simulate bringing up the browser controls by 10.5px. WebView()->MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(), gfx::Vector2dF(), 1, false, -10.5f / 20, 0, cc::BrowserControlsState::kBoth}); EXPECT_FLOAT_SIZE_EQ(FloatSize(500, 440.5f), visual_viewport.VisibleRect().Size()); // maximumScrollPosition |ceil|s the browser controls adjustment. visual_viewport.Move(ScrollOffset(10000, 10000)); EXPECT_FLOAT_SIZE_EQ(FloatSize(500, 881 - 441), visual_viewport.GetScrollOffset()); // The outer viewport (LocalFrameView) should be affected as well. frame_view.LayoutViewport()->ScrollBy(ScrollOffset(10000, 10000), mojom::blink::ScrollType::kUser); EXPECT_EQ(expectedMaxLayoutViewportScrollOffset(visual_viewport, frame_view), frame_view.LayoutViewport()->GetScrollOffset()); } TEST_P(VisualViewportTest, TestBrowserControlsAdjustmentWithScale) { InitializeWithAndroidSettings(); WebView()->ResizeWithBrowserControls(gfx::Size(500, 450), 20, 0, false); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); visual_viewport.SetScale(2); EXPECT_EQ(FloatSize(250, 225), visual_viewport.VisibleRect().Size()); EXPECT_EQ(IntSize(1000, 900), frame_view.FrameRect().Size()); // Simulate bringing down the browser controls by 20px. Since we're zoomed in, // the browser controls take up half as much space (in document-space) than // they do at an unzoomed level. WebView()->MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(), gfx::Vector2dF(), 1, false, 1, 0, cc::BrowserControlsState::kBoth}); EXPECT_EQ(FloatSize(250, 215), visual_viewport.VisibleRect().Size()); // Test that the scroll bounds are adjusted appropriately. visual_viewport.Move(ScrollOffset(10000, 10000)); EXPECT_EQ(FloatSize(750, 860 - 215), visual_viewport.GetScrollOffset()); // The outer viewport (LocalFrameView) should be affected as well. frame_view.LayoutViewport()->ScrollBy(ScrollOffset(10000, 10000), mojom::blink::ScrollType::kUser); ScrollOffset expected = expectedMaxLayoutViewportScrollOffset(visual_viewport, frame_view); EXPECT_EQ(expected, frame_view.LayoutViewport()->GetScrollOffset()); // Scale back out, LocalFrameView max scroll shouldn't have changed. Visual // viewport should be moved up to accommodate larger view. WebView()->MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(), gfx::Vector2dF(), 0.5f, false, 0, 0, cc::BrowserControlsState::kBoth}); EXPECT_EQ(1, visual_viewport.Scale()); EXPECT_EQ(expected, frame_view.LayoutViewport()->GetScrollOffset()); frame_view.LayoutViewport()->ScrollBy(ScrollOffset(10000, 10000), mojom::blink::ScrollType::kUser); EXPECT_EQ(expected, frame_view.LayoutViewport()->GetScrollOffset()); EXPECT_EQ(FloatSize(500, 860 - 430), visual_viewport.GetScrollOffset()); visual_viewport.Move(ScrollOffset(10000, 10000)); EXPECT_EQ(FloatSize(500, 860 - 430), visual_viewport.GetScrollOffset()); // Scale out, use a scale that causes fractional rects. WebView()->MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(), gfx::Vector2dF(), 0.8f, false, -1, 0, cc::BrowserControlsState::kBoth}); EXPECT_EQ(FloatSize(625, 562.5), visual_viewport.VisibleRect().Size()); // Bring out the browser controls by 11 WebView()->MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(), gfx::Vector2dF(), 1, false, 11 / 20.f, 0, cc::BrowserControlsState::kBoth}); EXPECT_EQ(FloatSize(625, 548.75), visual_viewport.VisibleRect().Size()); // Ensure max scroll offsets are updated properly. visual_viewport.Move(ScrollOffset(10000, 10000)); EXPECT_FLOAT_SIZE_EQ(FloatSize(375, 877.5 - 548.75), visual_viewport.GetScrollOffset()); frame_view.LayoutViewport()->ScrollBy(ScrollOffset(10000, 10000), mojom::blink::ScrollType::kUser); EXPECT_EQ(expectedMaxLayoutViewportScrollOffset(visual_viewport, frame_view), frame_view.LayoutViewport()->GetScrollOffset()); } // Tests that a scroll all the way to the bottom of the page, while hiding the // browser controls doesn't cause a clamp in the viewport scroll offset when the // top controls initiated resize occurs. TEST_P(VisualViewportTest, TestBrowserControlsAdjustmentAndResize) { int browser_controls_height = 20; int visual_viewport_height = 450; int layout_viewport_height = 900; float page_scale = 2; float min_page_scale = 0.5; InitializeWithAndroidSettings(); // Initialize with browser controls showing and shrinking the Blink size. cc::BrowserControlsParams controls; controls.top_controls_height = browser_controls_height; controls.browser_controls_shrink_blink_size = true; // TODO(danakj): The browser (RenderWidgetHostImpl) doesn't shrink the widget // size by the browser controls, only the visible_viewport_size, but this test // shrinks and grows both. WebView()->ResizeWithBrowserControls( gfx::Size(500, visual_viewport_height - browser_controls_height), gfx::Size(500, visual_viewport_height - browser_controls_height), controls); UpdateAllLifecyclePhases(); WebView()->GetBrowserControls().SetShownRatio(1, 0); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); visual_viewport.SetScale(page_scale); EXPECT_EQ(FloatSize(250, (visual_viewport_height - browser_controls_height) / page_scale), visual_viewport.VisibleRect().Size()); EXPECT_EQ(IntSize(1000, layout_viewport_height - browser_controls_height / min_page_scale), frame_view.FrameRect().Size()); EXPECT_EQ(IntSize(500, visual_viewport_height - browser_controls_height), visual_viewport.Size()); // Scroll all the way to the bottom, hiding the browser controls in the // process. visual_viewport.Move(ScrollOffset(10000, 10000)); frame_view.LayoutViewport()->ScrollBy(ScrollOffset(10000, 10000), mojom::blink::ScrollType::kUser); WebView()->GetBrowserControls().SetShownRatio(0, 0); EXPECT_EQ(FloatSize(250, visual_viewport_height / page_scale), visual_viewport.VisibleRect().Size()); ScrollOffset frame_view_expected = expectedMaxLayoutViewportScrollOffset(visual_viewport, frame_view); ScrollOffset visual_viewport_expected = ScrollOffset( 750, layout_viewport_height - visual_viewport_height / page_scale); EXPECT_EQ(visual_viewport_expected, visual_viewport.GetScrollOffset()); EXPECT_EQ(frame_view_expected, frame_view.LayoutViewport()->GetScrollOffset()); ScrollOffset total_expected = visual_viewport_expected + frame_view_expected; // Resize the widget and visible viewport to match the browser controls // adjustment. Ensure that the total offset (i.e. what the user sees) doesn't // change because of clamping the offsets to valid values. controls.browser_controls_shrink_blink_size = false; WebView()->ResizeWithBrowserControls(gfx::Size(500, visual_viewport_height), gfx::Size(500, visual_viewport_height), controls); UpdateAllLifecyclePhases(); EXPECT_EQ(IntSize(500, visual_viewport_height), visual_viewport.Size()); EXPECT_EQ(FloatSize(250, visual_viewport_height / page_scale), visual_viewport.VisibleRect().Size()); EXPECT_EQ(IntSize(1000, layout_viewport_height), frame_view.FrameRect().Size()); EXPECT_EQ(total_expected, visual_viewport.GetScrollOffset() + frame_view.LayoutViewport()->GetScrollOffset()); EXPECT_EQ(visual_viewport_expected, visual_viewport.GetScrollOffset()); EXPECT_EQ(frame_view_expected, frame_view.LayoutViewport()->GetScrollOffset()); } // Tests that a scroll all the way to the bottom while showing the browser // controls doesn't cause a clamp to the viewport scroll offset when the browser // controls initiated resize occurs. TEST_P(VisualViewportTest, TestBrowserControlsShrinkAdjustmentAndResize) { int browser_controls_height = 20; int visual_viewport_height = 500; int layout_viewport_height = 1000; int content_height = 2000; float page_scale = 2; float min_page_scale = 0.5; InitializeWithAndroidSettings(); // Initialize with browser controls hidden and not shrinking the Blink size. WebView()->ResizeWithBrowserControls(gfx::Size(500, visual_viewport_height), 20, 0, false); UpdateAllLifecyclePhases(); WebView()->GetBrowserControls().SetShownRatio(0, 0); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); visual_viewport.SetScale(page_scale); EXPECT_EQ(FloatSize(250, visual_viewport_height / page_scale), visual_viewport.VisibleRect().Size()); EXPECT_EQ(IntSize(1000, layout_viewport_height), frame_view.FrameRect().Size()); EXPECT_EQ(IntSize(500, visual_viewport_height), visual_viewport.Size()); // Scroll all the way to the bottom, showing the the browser controls in the // process. (This could happen via window.scrollTo during a scroll, for // example). WebView()->GetBrowserControls().SetShownRatio(1, 0); visual_viewport.Move(ScrollOffset(10000, 10000)); frame_view.LayoutViewport()->ScrollBy(ScrollOffset(10000, 10000), mojom::blink::ScrollType::kUser); EXPECT_EQ(FloatSize(250, (visual_viewport_height - browser_controls_height) / page_scale), visual_viewport.VisibleRect().Size()); ScrollOffset frame_view_expected( 0, content_height - (layout_viewport_height - browser_controls_height / min_page_scale)); ScrollOffset visual_viewport_expected = ScrollOffset( 750, (layout_viewport_height - browser_controls_height / min_page_scale - visual_viewport.VisibleRect().Height())); EXPECT_EQ(visual_viewport_expected, visual_viewport.GetScrollOffset()); EXPECT_EQ(frame_view_expected, frame_view.LayoutViewport()->GetScrollOffset()); ScrollOffset total_expected = visual_viewport_expected + frame_view_expected; // Resize the widget to match the browser controls adjustment. Ensure that the // total offset (i.e. what the user sees) doesn't change because of clamping // the offsets to valid values. WebView()->ResizeWithBrowserControls( gfx::Size(500, visual_viewport_height - browser_controls_height), 20, 0, true); UpdateAllLifecyclePhases(); EXPECT_EQ(IntSize(500, visual_viewport_height - browser_controls_height), visual_viewport.Size()); EXPECT_EQ(FloatSize(250, (visual_viewport_height - browser_controls_height) / page_scale), visual_viewport.VisibleRect().Size()); EXPECT_EQ(IntSize(1000, layout_viewport_height - browser_controls_height / min_page_scale), frame_view.FrameRect().Size()); EXPECT_EQ(total_expected, visual_viewport.GetScrollOffset() + frame_view.LayoutViewport()->GetScrollOffset()); } // Tests that a resize due to browser controls hiding doesn't incorrectly clamp // the main frame's scroll offset. crbug.com/428193. TEST_P(VisualViewportTest, TestTopControlHidingResizeDoesntClampMainFrame) { InitializeWithAndroidSettings(); WebView()->ResizeWithBrowserControls( gfx::Size(WebView()->MainFrameViewWidget()->Size()), 500, 0, false); UpdateAllLifecyclePhases(); WebView()->MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(), gfx::Vector2dF(), 1, false, 1, 0, cc::BrowserControlsState::kBoth}); WebView()->ResizeWithBrowserControls(gfx::Size(1000, 1000), 500, 0, true); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); UpdateAllLifecyclePhases(); // Scroll the LocalFrameView to the bottom of the page but "hide" the browser // controls on the compositor side so the max scroll position should account // for the full viewport height. WebView()->MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(), gfx::Vector2dF(), 1, false, -1, 0, cc::BrowserControlsState::kBoth}); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); frame_view.LayoutViewport()->SetScrollOffset( ScrollOffset(0, 10000), mojom::blink::ScrollType::kProgrammatic); EXPECT_EQ(500, frame_view.LayoutViewport()->GetScrollOffset().Height()); // Now send the resize, make sure the scroll offset doesn't change. WebView()->ResizeWithBrowserControls(gfx::Size(1000, 1500), 500, 0, false); UpdateAllLifecyclePhases(); EXPECT_EQ(500, frame_view.LayoutViewport()->GetScrollOffset().Height()); } static void configureHiddenScrollbarsSettings(WebSettings* settings) { VisualViewportTest::ConfigureAndroidSettings(settings); settings->SetHideScrollbars(true); } // Tests that scrollbar layers are not attached to the inner viewport container // layer when hideScrollbars WebSetting is true. TEST_P(VisualViewportTest, TestScrollbarsNotAttachedWhenHideScrollbarsSettingIsTrue) { InitializeWithAndroidSettings(configureHiddenScrollbarsSettings); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 150)); NavigateTo("about:blank"); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_FALSE(visual_viewport.LayerForHorizontalScrollbar()); EXPECT_FALSE(visual_viewport.LayerForVerticalScrollbar()); } // Tests that scrollbar layers are attached to the inner viewport container // layer when hideScrollbars WebSetting is false. TEST_P(VisualViewportTest, TestScrollbarsAttachedWhenHideScrollbarsSettingIsFalse) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 150)); UpdateAllLifecyclePhases(); NavigateTo("about:blank"); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_TRUE(visual_viewport.LayerForHorizontalScrollbar()); EXPECT_TRUE(visual_viewport.LayerForVerticalScrollbar()); } // Tests that the layout viewport's scroll node bounds are updated. // crbug.com/423188. TEST_P(VisualViewportTest, TestChangingContentSizeAffectsScrollBounds) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 150)); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); WebView()->MainFrameImpl()->ExecuteScript( WebScriptSource("var content = document.getElementById(\"content\");" "content.style.width = \"1500px\";" "content.style.height = \"2400px\";")); UpdateAllLifecyclePhases(); const auto* scroll_node = frame_view.GetLayoutView()->FirstFragment().PaintProperties()->Scroll(); float scale = GetFrame()->GetPage()->GetVisualViewport().Scale(); EXPECT_EQ(IntSize(100 / scale, 150 / scale), scroll_node->ContainerRect().Size()); EXPECT_EQ(IntSize(1500, 2400), scroll_node->ContentsSize()); } // Tests that resizing the visual viepwort keeps its bounds within the outer // viewport. TEST_P(VisualViewportTest, ResizeVisualViewportStaysWithinOuterViewport) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 200)); NavigateTo("about:blank"); UpdateAllLifecyclePhases(); WebView()->ResizeVisualViewport(gfx::Size(100, 100)); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.Move(ScrollOffset(0, 100)); EXPECT_EQ(100, visual_viewport.GetScrollOffset().Height()); WebView()->ResizeVisualViewport(gfx::Size(100, 200)); EXPECT_EQ(0, visual_viewport.GetScrollOffset().Height()); } TEST_P(VisualViewportTest, ElementBoundsInViewportSpaceAccountsForViewport) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(500, 800)); RegisterMockedHttpURLLoad("pinch-viewport-input-field.html"); NavigateTo(base_url_ + "pinch-viewport-input-field.html"); To(WebView()->GetPage()->MainFrame())->SetInitialFocus(false); Element* input_element = WebView()->FocusedElement(); IntRect bounds = input_element->GetLayoutObject()->AbsoluteBoundingBoxRect(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); FloatPoint scrollDelta(250, 400); visual_viewport.SetScale(2); visual_viewport.SetLocation(scrollDelta); const IntRect bounds_in_viewport = input_element->BoundsInViewport(); IntRect expectedBounds = bounds; expectedBounds.Scale(2.f); FloatPoint expectedScrollDelta = scrollDelta; expectedScrollDelta.Scale(2.f, 2.f); EXPECT_EQ(RoundedIntPoint(FloatPoint(FloatPoint(expectedBounds.Location()) - expectedScrollDelta)), bounds_in_viewport.Location()); EXPECT_EQ(expectedBounds.Size(), bounds_in_viewport.Size()); } TEST_P(VisualViewportTest, ElementVisibleBoundsInVisualViewport) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(640, 1080)); RegisterMockedHttpURLLoad("viewport-select.html"); NavigateTo(base_url_ + "viewport-select.html"); ASSERT_EQ(2.0f, WebView()->PageScaleFactor()); To(WebView()->GetPage()->MainFrame())->SetInitialFocus(false); Element* element = WebView()->FocusedElement(); EXPECT_FALSE(element->VisibleBoundsInVisualViewport().IsEmpty()); WebView()->SetPageScaleFactor(4.0); EXPECT_TRUE(element->VisibleBoundsInVisualViewport().IsEmpty()); } // Test that the various window.scroll and document.body.scroll properties and // methods don't change with the visual viewport. TEST_P(VisualViewportTest, visualViewportIsInert) { WebViewImpl* web_view_impl = helper_.InitializeWithSettings(&ConfigureAndroidCompositing); web_view_impl->MainFrameViewWidget()->Resize(gfx::Size(200, 300)); WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); frame_test_helpers::LoadHTMLString( web_view_impl->MainFrameImpl(), "" "" "", base_url); UpdateAllLifecyclePhases(); LocalDOMWindow* window = web_view_impl->MainFrameImpl()->GetFrame()->DomWindow(); auto* html = To(window->document()->documentElement()); ASSERT_EQ(200, window->innerWidth()); ASSERT_EQ(300, window->innerHeight()); ASSERT_EQ(200, html->clientWidth()); ASSERT_EQ(300, html->clientHeight()); VisualViewport& visual_viewport = web_view_impl->MainFrameImpl() ->GetFrame() ->GetPage() ->GetVisualViewport(); visual_viewport.SetScale(2); ASSERT_EQ(100, visual_viewport.VisibleRect().Width()); ASSERT_EQ(150, visual_viewport.VisibleRect().Height()); EXPECT_EQ(200, window->innerWidth()); EXPECT_EQ(300, window->innerHeight()); EXPECT_EQ(200, html->clientWidth()); EXPECT_EQ(300, html->clientHeight()); visual_viewport.SetScrollOffset( ScrollOffset(10, 15), mojom::blink::ScrollType::kProgrammatic, mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback()); ASSERT_EQ(10, visual_viewport.GetScrollOffset().Width()); ASSERT_EQ(15, visual_viewport.GetScrollOffset().Height()); EXPECT_EQ(0, window->scrollX()); EXPECT_EQ(0, window->scrollY()); html->setScrollLeft(5); html->setScrollTop(30); EXPECT_EQ(5, html->scrollLeft()); EXPECT_EQ(30, html->scrollTop()); EXPECT_EQ(10, visual_viewport.GetScrollOffset().Width()); EXPECT_EQ(15, visual_viewport.GetScrollOffset().Height()); html->setScrollLeft(5000); html->setScrollTop(5000); EXPECT_EQ(600, html->scrollLeft()); EXPECT_EQ(500, html->scrollTop()); EXPECT_EQ(10, visual_viewport.GetScrollOffset().Width()); EXPECT_EQ(15, visual_viewport.GetScrollOffset().Height()); html->setScrollLeft(0); html->setScrollTop(0); EXPECT_EQ(0, html->scrollLeft()); EXPECT_EQ(0, html->scrollTop()); EXPECT_EQ(10, visual_viewport.GetScrollOffset().Width()); EXPECT_EQ(15, visual_viewport.GetScrollOffset().Height()); window->scrollTo(5000, 5000); EXPECT_EQ(600, html->scrollLeft()); EXPECT_EQ(500, html->scrollTop()); EXPECT_EQ(10, visual_viewport.GetScrollOffset().Width()); EXPECT_EQ(15, visual_viewport.GetScrollOffset().Height()); } // Tests that when a new frame is created, it is created with the intended size // (i.e. viewport at minimum scale, 100x200 / 0.5). TEST_P(VisualViewportTest, TestMainFrameInitializationSizing) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 200)); RegisterMockedHttpURLLoad("content-width-1000-min-scale.html"); NavigateTo(base_url_ + "content-width-1000-min-scale.html"); WebLocalFrameImpl* local_frame = WebView()->MainFrameImpl(); // The shutdown() calls are a hack to prevent this test from violating // invariants about frame state during navigation/detach. local_frame->GetFrame()->GetDocument()->Shutdown(); local_frame->CreateFrameView(); LocalFrameView& frame_view = *local_frame->GetFrameView(); EXPECT_EQ(IntSize(200, 400), frame_view.FrameRect().Size()); frame_view.Dispose(); } // Tests that the maximum scroll offset of the viewport can be fractional. TEST_P(VisualViewportTest, FractionalMaxScrollOffset) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(101, 201)); NavigateTo("about:blank"); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); ScrollableArea* scrollable_area = &visual_viewport; WebView()->SetPageScaleFactor(1.0); EXPECT_EQ(ScrollOffset(), scrollable_area->MaximumScrollOffset()); WebView()->SetPageScaleFactor(2); EXPECT_EQ(ScrollOffset(101. / 2., 201. / 2.), scrollable_area->MaximumScrollOffset()); } // Tests that the slow scrolling after an impl scroll on the visual viewport is // continuous. crbug.com/453460 was caused by the impl-path not updating the // ScrollAnimatorBase class. TEST_P(VisualViewportTest, SlowScrollAfterImplScroll) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(800, 600)); NavigateTo("about:blank"); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); // Apply some scroll and scale from the impl-side. WebView()->MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(300, 200), gfx::Vector2dF(0, 0), 2, false, 0, 0, cc::BrowserControlsState::kBoth}); EXPECT_EQ(FloatSize(300, 200), visual_viewport.GetScrollOffset()); // Send a scroll event on the main thread path. WebGestureEvent gsb( WebInputEvent::Type::kGestureScrollBegin, WebInputEvent::kNoModifiers, WebInputEvent::GetStaticTimeStampForTests(), WebGestureDevice::kTouchpad); gsb.SetFrameScale(1); gsb.data.scroll_begin.delta_x_hint = -50; gsb.data.scroll_begin.delta_x_hint = -60; gsb.data.scroll_begin.delta_hint_units = ScrollGranularity::kScrollByPrecisePixel; GetFrame()->GetEventHandler().HandleGestureEvent(gsb); WebGestureEvent gsu( WebInputEvent::Type::kGestureScrollUpdate, WebInputEvent::kNoModifiers, WebInputEvent::GetStaticTimeStampForTests(), WebGestureDevice::kTouchpad); gsu.SetFrameScale(1); gsu.data.scroll_update.delta_x = -50; gsu.data.scroll_update.delta_y = -60; gsu.data.scroll_update.delta_units = ScrollGranularity::kScrollByPrecisePixel; gsu.data.scroll_update.velocity_x = 1; gsu.data.scroll_update.velocity_y = 1; GetFrame()->GetEventHandler().HandleGestureEvent(gsu); // The scroll sent from the impl-side must not be overwritten. EXPECT_EQ(FloatSize(350, 260), visual_viewport.GetScrollOffset()); } TEST_P(VisualViewportTest, AccessibilityHitTestWhileZoomedIn) { InitializeWithDesktopSettings(); RegisterMockedHttpURLLoad("hit-test.html"); NavigateTo(base_url_ + "hit-test.html"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(500, 500)); UpdateAllLifecyclePhases(); WebDocument web_doc = WebView()->MainFrameImpl()->GetDocument(); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); WebAXContext ax_context(web_doc); WebView()->SetPageScaleFactor(2); WebView()->SetVisualViewportOffset(gfx::PointF(200, 230)); frame_view.LayoutViewport()->SetScrollOffset( ScrollOffset(400, 1100), mojom::blink::ScrollType::kProgrammatic); // FIXME(504057): PaintLayerScrollableArea dirties the compositing state. ForceFullCompositingUpdate(); // Because of where the visual viewport is located, this should hit the bottom // right target (target 4). WebAXObject hitNode = WebAXObject::FromWebDocument(web_doc).HitTest(gfx::Point(154, 165)); ax::mojom::NameFrom name_from; WebVector name_objects; EXPECT_EQ(std::string("Target4"), hitNode.GetName(name_from, name_objects).Utf8()); } // Tests that the maximum scroll offset of the viewport can be fractional. TEST_P(VisualViewportTest, TestCoordinateTransforms) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(800, 600)); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); VisualViewport& visual_viewport = WebView()->GetPage()->GetVisualViewport(); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); // At scale = 1 the transform should be a no-op. visual_viewport.SetScale(1); EXPECT_FLOAT_POINT_EQ( FloatPoint(314, 273), visual_viewport.ViewportToRootFrame(FloatPoint(314, 273))); EXPECT_FLOAT_POINT_EQ( FloatPoint(314, 273), visual_viewport.RootFrameToViewport(FloatPoint(314, 273))); // At scale = 2. visual_viewport.SetScale(2); EXPECT_FLOAT_POINT_EQ(FloatPoint(55, 75), visual_viewport.ViewportToRootFrame( FloatPoint(110, 150))); EXPECT_FLOAT_POINT_EQ( FloatPoint(110, 150), visual_viewport.RootFrameToViewport(FloatPoint(55, 75))); // At scale = 2 and with the visual viewport offset. visual_viewport.SetLocation(FloatPoint(10, 12)); EXPECT_FLOAT_POINT_EQ(FloatPoint(50, 62), visual_viewport.ViewportToRootFrame( FloatPoint(80, 100))); EXPECT_FLOAT_POINT_EQ( FloatPoint(80, 100), visual_viewport.RootFrameToViewport(FloatPoint(50, 62))); // Test points that will cause non-integer values. EXPECT_FLOAT_POINT_EQ( FloatPoint(50.5, 62.4), visual_viewport.ViewportToRootFrame(FloatPoint(81, 100.8))); EXPECT_FLOAT_POINT_EQ( FloatPoint(81, 100.8), visual_viewport.RootFrameToViewport(FloatPoint(50.5, 62.4))); // Scrolling the main frame should have no effect. frame_view.LayoutViewport()->SetScrollOffset( ScrollOffset(100, 120), mojom::blink::ScrollType::kProgrammatic); EXPECT_FLOAT_POINT_EQ(FloatPoint(50, 62), visual_viewport.ViewportToRootFrame( FloatPoint(80, 100))); EXPECT_FLOAT_POINT_EQ( FloatPoint(80, 100), visual_viewport.RootFrameToViewport(FloatPoint(50, 62))); } // Tests that the window dimensions are available before a full layout occurs. // More specifically, it checks that the innerWidth and innerHeight window // properties will trigger a layout which will cause an update to viewport // constraints and a refreshed initial scale. crbug.com/466718 TEST_P(VisualViewportTest, WindowDimensionsOnLoad) { InitializeWithAndroidSettings(); RegisterMockedHttpURLLoad("window_dimensions.html"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(800, 600)); NavigateTo(base_url_ + "window_dimensions.html"); Element* output = GetFrame()->GetDocument()->getElementById("output"); DCHECK(output); EXPECT_EQ("1600x1200", output->innerHTML()); } // Similar to above but make sure the initial scale is updated with the content // width for a very wide page. That is, make that innerWidth/Height actually // trigger a layout of the content, and not just an update of the viepwort. // crbug.com/466718 TEST_P(VisualViewportTest, WindowDimensionsOnLoadWideContent) { InitializeWithAndroidSettings(); RegisterMockedHttpURLLoad("window_dimensions_wide_div.html"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(800, 600)); NavigateTo(base_url_ + "window_dimensions_wide_div.html"); Element* output = GetFrame()->GetDocument()->getElementById("output"); DCHECK(output); EXPECT_EQ("2000x1500", output->innerHTML()); } TEST_P(VisualViewportTest, ResizeWithScrollAnchoring) { InitializeWithDesktopSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(800, 600)); RegisterMockedHttpURLLoad("icb-relative-content.html"); NavigateTo(base_url_ + "icb-relative-content.html"); LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView(); frame_view.LayoutViewport()->SetScrollOffset( ScrollOffset(700, 500), mojom::blink::ScrollType::kProgrammatic); WebView()->MainFrameViewWidget()->Resize(gfx::Size(800, 300)); UpdateAllLifecyclePhases(); EXPECT_EQ(ScrollOffset(700, 200), frame_view.LayoutViewport()->GetScrollOffset()); } // Make sure a composited background-attachment:fixed background gets resized // by browser controls. TEST_P(VisualViewportTest, ResizeCompositedAndFixedBackground) { if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) return; WebViewImpl* web_view_impl = helper_.InitializeWithSettings(&ConfigureAndroidCompositing); int page_width = 640; int page_height = 480; float browser_controls_height = 50.0f; int smallest_height = page_height - browser_controls_height; web_view_impl->ResizeWithBrowserControls(gfx::Size(page_width, page_height), browser_controls_height, 0, false); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("http://example.com/foo.png", "white-1x1.png"); WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); frame_test_helpers::LoadHTMLString(web_view_impl->MainFrameImpl(), "" "" "
", base_url); UpdateAllLifecyclePhases(); Document* document = To(web_view_impl->GetPage()->MainFrame())->GetDocument(); GraphicsLayer* background_layer = document->GetLayoutView() ->Layer() ->GetCompositedLayerMapping() ->MainGraphicsLayer(); ASSERT_TRUE(background_layer); ASSERT_EQ(page_width, background_layer->Size().width()); ASSERT_EQ(page_height, background_layer->Size().height()); ASSERT_EQ(page_width, document->View()->GetLayoutSize().Width()); ASSERT_EQ(smallest_height, document->View()->GetLayoutSize().Height()); web_view_impl->ResizeWithBrowserControls( gfx::Size(page_width, smallest_height), browser_controls_height, 0, true); UpdateAllLifecyclePhases(); // The layout size should not have changed. ASSERT_EQ(page_width, document->View()->GetLayoutSize().Width()); ASSERT_EQ(smallest_height, document->View()->GetLayoutSize().Height()); // The background layer's size should have changed though. EXPECT_EQ(page_width, background_layer->Size().width()); EXPECT_EQ(smallest_height, background_layer->Size().height()); web_view_impl->ResizeWithBrowserControls(gfx::Size(page_width, page_height), browser_controls_height, 0, true); UpdateAllLifecyclePhases(); // The background layer's size should change again. EXPECT_EQ(page_width, background_layer->Size().width()); EXPECT_EQ(page_height, background_layer->Size().height()); } static void ConfigureAndroidNonCompositing(WebSettings* settings) { settings->SetPreferCompositingToLCDTextEnabled(false); settings->SetViewportMetaEnabled(true); settings->SetViewportEnabled(true); settings->SetMainFrameResizesAreOrientationChanges(true); settings->SetShrinksViewportContentToFit(true); } // Make sure a non-composited background-attachment:fixed background gets // resized by browser controls. TEST_P(VisualViewportTest, ResizeNonCompositedAndFixedBackground) { if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) return; WebViewImpl* web_view_impl = helper_.InitializeWithSettings(&ConfigureAndroidNonCompositing); int page_width = 640; int page_height = 480; float browser_controls_height = 50.0f; int smallest_height = page_height - browser_controls_height; web_view_impl->ResizeWithBrowserControls(gfx::Size(page_width, page_height), browser_controls_height, 0, false); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("http://example.com/foo.png", "white-1x1.png"); WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); frame_test_helpers::LoadHTMLString(web_view_impl->MainFrameImpl(), "" "" "
", base_url); UpdateAllLifecyclePhases(); Document* document = To(web_view_impl->GetPage()->MainFrame())->GetDocument(); document->View()->SetTracksRasterInvalidations(true); web_view_impl->ResizeWithBrowserControls( gfx::Size(page_width, smallest_height), browser_controls_height, 0, true); UpdateAllLifecyclePhases(); // The layout size should not have changed. ASSERT_EQ(page_width, document->View()->GetLayoutSize().Width()); ASSERT_EQ(smallest_height, document->View()->GetLayoutSize().Height()); // Fixed-attachment background is affected by viewport size. EXPECT_FALSE(MainGraphicsLayerHasRasterInvalidations(document)); ASSERT_TRUE(ScrollingContentsLayerHasRasterInvalidations(document)); EXPECT_THAT( ScrollingContentsLayerRasterInvalidations(document), UnorderedElementsAre(RasterInvalidationInfo{ &ScrollingBackgroundClient(document), ScrollingBackgroundClient(document).DebugName(), IntRect(0, 0, 640, 1000), PaintInvalidationReason::kBackground})); document->View()->SetTracksRasterInvalidations(false); document->View()->SetTracksRasterInvalidations(true); web_view_impl->ResizeWithBrowserControls(gfx::Size(page_width, page_height), browser_controls_height, 0, true); UpdateAllLifecyclePhases(); // Fixed-attachment background is affected by viewport size. EXPECT_FALSE(MainGraphicsLayerHasRasterInvalidations(document)); ASSERT_TRUE(ScrollingContentsLayerHasRasterInvalidations(document)); EXPECT_THAT( ScrollingContentsLayerRasterInvalidations(document), UnorderedElementsAre(RasterInvalidationInfo{ &ScrollingBackgroundClient(document), ScrollingBackgroundClient(document).DebugName(), IntRect(0, 0, 640, 1000), PaintInvalidationReason::kBackground})); document->View()->SetTracksRasterInvalidations(false); } // Make sure a browser control resize with background-attachment:not-fixed // background doesn't cause invalidation or layout. TEST_P(VisualViewportTest, ResizeNonFixedBackgroundNoLayoutOrInvalidation) { WebViewImpl* web_view_impl = helper_.InitializeWithSettings(&ConfigureAndroidCompositing); int page_width = 640; int page_height = 480; float browser_controls_height = 50.0f; int smallest_height = page_height - browser_controls_height; web_view_impl->ResizeWithBrowserControls(gfx::Size(page_width, page_height), browser_controls_height, 0, false); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("http://example.com/foo.png", "white-1x1.png"); WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); // This time the background is the default attachment. frame_test_helpers::LoadHTMLString(web_view_impl->MainFrameImpl(), "" "" "
", base_url); UpdateAllLifecyclePhases(); Document* document = To(web_view_impl->GetPage()->MainFrame())->GetDocument(); // A resize will do a layout synchronously so manually check that we don't // setNeedsLayout from viewportSizeChanged. document->View()->ViewportSizeChanged(false, true); unsigned needs_layout_objects = 0; unsigned total_objects = 0; bool is_subtree = false; EXPECT_FALSE(document->View()->NeedsLayout()); document->View()->CountObjectsNeedingLayout(needs_layout_objects, total_objects, is_subtree); EXPECT_EQ(0u, needs_layout_objects); UpdateAllLifecyclePhases(); // Do a real resize to check for invalidations. document->View()->SetTracksRasterInvalidations(true); web_view_impl->ResizeWithBrowserControls( gfx::Size(page_width, smallest_height), browser_controls_height, 0, true); UpdateAllLifecyclePhases(); // The layout size should not have changed. ASSERT_EQ(page_width, document->View()->GetLayoutSize().Width()); ASSERT_EQ(smallest_height, document->View()->GetLayoutSize().Height()); if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { // No invalidations should have occurred on either main layer or scrolling // contents layer. EXPECT_FALSE(MainGraphicsLayerHasRasterInvalidations(document)); EXPECT_FALSE(ScrollingContentsLayerHasRasterInvalidations(document)); } document->View()->SetTracksRasterInvalidations(false); } TEST_P(VisualViewportTest, InvalidateLayoutViewWhenDocumentSmallerThanView) { WebViewImpl* web_view_impl = helper_.InitializeWithSettings(&ConfigureAndroidCompositing); int page_width = 320; int page_height = 590; float browser_controls_height = 50.0f; int largest_height = page_height + browser_controls_height; web_view_impl->ResizeWithBrowserControls(gfx::Size(page_width, page_height), browser_controls_height, 0, true); UpdateAllLifecyclePhases(); frame_test_helpers::LoadFrame(web_view_impl->MainFrameImpl(), "about:blank"); UpdateAllLifecyclePhases(); Document* document = To(web_view_impl->GetPage()->MainFrame())->GetDocument(); // Do a resize to check for invalidations. document->View()->SetTracksRasterInvalidations(true); web_view_impl->ResizeWithBrowserControls( gfx::Size(page_width, largest_height), browser_controls_height, 0, false); UpdateAllLifecyclePhases(); // The layout size should not have changed. ASSERT_EQ(page_width, document->View()->GetLayoutSize().Width()); ASSERT_EQ(page_height, document->View()->GetLayoutSize().Height()); if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { // Incremental raster invalidation is needed because the resize exposes // unpainted area of background. EXPECT_FALSE(MainGraphicsLayerHasRasterInvalidations(document)); ASSERT_TRUE(ScrollingContentsLayerHasRasterInvalidations(document)); EXPECT_THAT( ScrollingContentsLayerRasterInvalidations(document), UnorderedElementsAre(RasterInvalidationInfo{ &ScrollingBackgroundClient(document), ScrollingBackgroundClient(document).DebugName(), IntRect(0, 590, 320, 50), PaintInvalidationReason::kIncremental})); } document->View()->SetTracksRasterInvalidations(false); // Resize back to the original size. document->View()->SetTracksRasterInvalidations(true); web_view_impl->ResizeWithBrowserControls(gfx::Size(page_width, page_height), browser_controls_height, 0, false); UpdateAllLifecyclePhases(); if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { // No raster invalidation is needed because of no change within the root // scrolling layer. EXPECT_FALSE(MainGraphicsLayerHasRasterInvalidations(document)); EXPECT_FALSE(ScrollingContentsLayerHasRasterInvalidations(document)); } document->View()->SetTracksRasterInvalidations(false); } // Ensure we create transform node for overscroll elasticity properly. TEST_P(VisualViewportTest, EnsureOverscrollElasticityTransformNode) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(400, 400)); NavigateTo("about:blank"); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_TRUE(visual_viewport.GetOverscrollElasticityTransformNode()); } // Ensure we create effect node for scrollbar properly. TEST_P(VisualViewportTest, EnsureEffectNodeForScrollbars) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(400, 400)); NavigateTo("about:blank"); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); auto* vertical_scrollbar = visual_viewport.LayerForVerticalScrollbar(); auto* horizontal_scrollbar = visual_viewport.LayerForHorizontalScrollbar(); ASSERT_TRUE(vertical_scrollbar); ASSERT_TRUE(horizontal_scrollbar); auto& theme = ScrollbarThemeOverlayMobile::GetInstance(); int scrollbar_thickness = theme.ScrollbarThickness( visual_viewport.ScaleFromDIP(), EScrollbarWidth::kAuto); EXPECT_EQ(vertical_scrollbar->effect_tree_index(), vertical_scrollbar->layer_tree_host() ->property_trees() ->element_id_to_effect_node_index [visual_viewport.GetScrollbarElementId( ScrollbarOrientation::kVerticalScrollbar)]); EXPECT_EQ(vertical_scrollbar->offset_to_transform_parent(), gfx::Vector2dF(400 - scrollbar_thickness, 0)); EXPECT_EQ(horizontal_scrollbar->effect_tree_index(), horizontal_scrollbar->layer_tree_host() ->property_trees() ->element_id_to_effect_node_index [visual_viewport.GetScrollbarElementId( ScrollbarOrientation::kHorizontalScrollbar)]); EXPECT_EQ(horizontal_scrollbar->offset_to_transform_parent(), gfx::Vector2dF(0, 400 - scrollbar_thickness)); EXPECT_EQ(GetEffectNode(vertical_scrollbar)->parent_id, GetEffectNode(horizontal_scrollbar)->parent_id); } // Make sure we don't crash when the visual viewport's height is 0. This can // happen transiently in autoresize mode and cause a crash. This test passes if // it doesn't crash. TEST_P(VisualViewportTest, AutoResizeNoHeightUsesMinimumHeight) { InitializeWithDesktopSettings(); WebView()->ResizeWithBrowserControls(gfx::Size(0, 0), 0, 0, false); UpdateAllLifecyclePhases(); WebView()->EnableAutoResizeMode(gfx::Size(25, 25), gfx::Size(100, 100)); WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); frame_test_helpers::LoadHTMLString(WebView()->MainFrameImpl(), "" "" "
", base_url); } // When a provisional frame is committed, it will get swapped in. At that // point, the VisualViewport will be reset but the Document is in a detached // state with no domWindow(). Ensure we correctly reset the viewport properties // but don't crash trying to enqueue resize and scroll events in the document. // https://crbug.com/1175916. TEST_P(VisualViewportTest, SwapMainFrame) { InitializeWithDesktopSettings(); WebView()->SetPageScaleFactor(2.0f); WebView()->SetVisualViewportOffset(gfx::PointF(10, 20)); WebLocalFrame* local_frame = helper_.CreateProvisional(*helper_.LocalMainFrame()); // Commit the provisional frame so it gets swapped in. RegisterMockedHttpURLLoad("200-by-300.html"); frame_test_helpers::LoadFrame(local_frame, base_url_ + "200-by-300.html"); EXPECT_EQ(WebView()->PageScaleFactor(), 1.0f); EXPECT_EQ(WebView()->VisualViewportOffset().x(), 0.0f); EXPECT_EQ(WebView()->VisualViewportOffset().y(), 0.0f); } // Similar to above but checks the case where a page is loaded such that it // will zoom out as a result of loading and layout (i.e. loading a desktop page // on Android). TEST_P(VisualViewportTest, SwapMainFrameLoadZoomedOut) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 150)); WebLocalFrame* local_frame = helper_.CreateProvisional(*helper_.LocalMainFrame()); // Commit the provisional frame so it gets swapped in. RegisterMockedHttpURLLoad("200-by-300.html"); frame_test_helpers::LoadFrame(local_frame, base_url_ + "200-by-300.html"); EXPECT_EQ(WebView()->PageScaleFactor(), 0.5f); EXPECT_EQ(WebView()->VisualViewportOffset().x(), 0.0f); EXPECT_EQ(WebView()->VisualViewportOffset().y(), 0.0f); } class VisualViewportSimTest : public SimTest { public: VisualViewportSimTest() {} void SetUp() override { SimTest::SetUp(); // Use settings that resemble the Android configuration. WebView().GetSettings()->SetViewportEnabled(true); WebView().GetSettings()->SetPreferCompositingToLCDTextEnabled(true); WebView().GetSettings()->SetViewportMetaEnabled(true); WebView().GetSettings()->SetViewportEnabled(true); WebView().GetSettings()->SetMainFrameResizesAreOrientationChanges(true); WebView().GetSettings()->SetShrinksViewportContentToFit(true); WebView().SetDefaultPageScaleLimits(0.25f, 5); } }; // Test that we correctly size the visual viewport's scrolling contents layer // when the layout viewport is smaller. TEST_F(VisualViewportSimTest, ScrollingContentsSmallerThanContainer) { WebView().MainFrameViewWidget()->Resize(gfx::Size(400, 600)); SimRequest request("https://example.com/test.html", "text/html"); LoadURL("https://example.com/test.html"); request.Complete(R"HTML( )HTML"); Compositor().BeginFrame(); ASSERT_EQ(1.25f, WebView().MinimumPageScaleFactor()); VisualViewport& visual_viewport = WebView().GetPage()->GetVisualViewport(); EXPECT_EQ(gfx::Size(320, 480), visual_viewport.LayerForScrolling()->bounds()); EXPECT_EQ(IntSize(400, 600), visual_viewport.GetScrollNode()->ContainerRect().Size()); EXPECT_EQ(IntSize(320, 480), visual_viewport.GetScrollNode()->ContentsSize()); WebView().MainFrameViewWidget()->ApplyViewportChangesForTesting( {gfx::ScrollOffset(1, 1), gfx::Vector2dF(), 2, false, 1, 0, cc::BrowserControlsState::kBoth}); EXPECT_EQ(gfx::Size(320, 480), visual_viewport.LayerForScrolling()->bounds()); EXPECT_EQ(IntSize(400, 600), visual_viewport.GetScrollNode()->ContainerRect().Size()); EXPECT_EQ(IntSize(320, 480), visual_viewport.GetScrollNode()->ContentsSize()); } class VisualViewportScrollIntoViewTest : public VisualViewportSimTest { public: VisualViewportScrollIntoViewTest() {} void SetUp() override { VisualViewportSimTest::SetUp(); // Setup a fixed-position element that's outside of an inset visual // viewport. WebView().MainFrameViewWidget()->Resize(gfx::Size(400, 600)); SimRequest request("https://example.com/test.html", "text/html"); LoadURL("https://example.com/test.html"); request.Complete(R"HTML(
Layout bottom
)HTML"); Compositor().BeginFrame(); // Shrink the height such that the fixed element is now off screen. WebView().ResizeVisualViewport(gfx::Size(400, 600 - 100)); } // Scrolls an element by the given name into view in the |visual_viewport| // using params that optionally apply to a scroll sequence. void ScrollIntoView(const WebString& element_name, bool is_for_scroll_sequence) { WebDocument web_doc = WebView().MainFrameImpl()->GetDocument(); Element* bottom_element = web_doc.GetElementById(element_name); auto scroll_params = ScrollAlignment::CreateScrollIntoViewParams( ScrollAlignment::ToEdgeIfNeeded(), ScrollAlignment::ToEdgeIfNeeded(), mojom::blink::ScrollType::kProgrammatic, /*make_visible_in_visual_viewport=*/true, mojom::blink::ScrollBehavior::kInstant, is_for_scroll_sequence); WebView().GetPage()->GetVisualViewport().ScrollIntoView( bottom_element->BoundingBox(), scroll_params); } }; TEST_F(VisualViewportScrollIntoViewTest, ScrollingToFixedWithScrollSequenceAnimationShort) { VisualViewport& visual_viewport = WebView().GetPage()->GetVisualViewport(); EXPECT_EQ(0.f, visual_viewport.GetScrollOffset().Height()); ScrollIntoView("bottom", true); visual_viewport.GetSmoothScrollSequencer()->RunQueuedAnimations(); EXPECT_EQ(100.f, visual_viewport.GetScrollOffset().Height()); } TEST_F(VisualViewportScrollIntoViewTest, ScrollingToFixedWithoutScrollSequenceAnimationShort) { VisualViewport& visual_viewport = WebView().GetPage()->GetVisualViewport(); EXPECT_EQ(0.f, visual_viewport.GetScrollOffset().Height()); ScrollIntoView("bottom", false); EXPECT_EQ(100.f, visual_viewport.GetScrollOffset().Height()); } TEST_F(VisualViewportScrollIntoViewTest, ScrollingToFixedFromJavascript) { VisualViewport& visual_viewport = WebView().GetPage()->GetVisualViewport(); EXPECT_EQ(0.f, visual_viewport.GetScrollOffset().Height()); GetDocument().getElementById("bottom")->scrollIntoView(); EXPECT_EQ(100.f, visual_viewport.GetScrollOffset().Height()); } TEST_P(VisualViewportTest, DeviceEmulation) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(400, 400)); NavigateTo("about:blank"); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_FALSE(visual_viewport.GetDeviceEmulationTransformNode()); EXPECT_FALSE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); DeviceEmulationParams params; params.viewport_offset = gfx::PointF(); params.viewport_scale = 1.f; WebView()->EnableDeviceEmulation(params); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_FALSE(visual_viewport.GetDeviceEmulationTransformNode()); EXPECT_FALSE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); UpdateAllLifecyclePhases(); EXPECT_FALSE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); // Set device mulation with viewport offset should repaint visual viewport. params.viewport_offset = gfx::PointF(314, 159); WebView()->EnableDeviceEmulation(params); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_TRUE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); ASSERT_TRUE(visual_viewport.GetDeviceEmulationTransformNode()); EXPECT_EQ(TransformationMatrix().Translate(-params.viewport_offset.x(), -params.viewport_offset.y()), visual_viewport.GetDeviceEmulationTransformNode()->Matrix()); UpdateAllLifecyclePhases(); EXPECT_FALSE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); // Change device emulation with scale should not repaint visual viewport. params.viewport_offset = gfx::PointF(); params.viewport_scale = 1.5f; WebView()->EnableDeviceEmulation(params); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_FALSE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); ASSERT_TRUE(visual_viewport.GetDeviceEmulationTransformNode()); EXPECT_EQ(TransformationMatrix().Scale(1.5f), visual_viewport.GetDeviceEmulationTransformNode()->Matrix()); UpdateAllLifecyclePhases(); EXPECT_FALSE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); // Set an identity device emulation transform and ensure the transform // paint property node is cleared and repaint visual viewport. WebView()->EnableDeviceEmulation(DeviceEmulationParams()); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_TRUE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); EXPECT_FALSE(visual_viewport.GetDeviceEmulationTransformNode()); UpdateAllLifecyclePhases(); EXPECT_FALSE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); } TEST_P(VisualViewportTest, PaintScrollbar) { InitializeWithAndroidSettings(); WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(400, 400)); frame_test_helpers::LoadHTMLString(WebView()->MainFrameImpl(), R"HTML( " )HTML", base_url); UpdateAllLifecyclePhases(); auto check_scrollbar = [](const cc::Layer* scrollbar, float scale) { EXPECT_TRUE(scrollbar->DrawsContent()); EXPECT_FALSE(scrollbar->HitTestable()); EXPECT_TRUE(scrollbar->IsScrollbarLayerForTesting()); EXPECT_EQ( cc::ScrollbarOrientation::VERTICAL, static_cast(scrollbar)->orientation()); EXPECT_EQ(gfx::Size(7, 393), scrollbar->bounds()); EXPECT_EQ(gfx::Vector2dF(393, 0), scrollbar->offset_to_transform_parent()); // ScreenSpaceTransform is in the device emulation transform space, so it's // not affected by device emulation scale. gfx::Transform screen_space_transform; screen_space_transform.Translate(393, 0); EXPECT_EQ(screen_space_transform, scrollbar->ScreenSpaceTransform()); gfx::Transform transform; transform.Scale(scale, scale); EXPECT_EQ(transform, scrollbar->layer_tree_host() ->property_trees() ->transform_tree.Node(scrollbar->transform_tree_index()) ->local); }; // The last layer should be the vertical scrollbar. const cc::Layer* scrollbar = GetFrame()->View()->RootCcLayer()->children().back().get(); check_scrollbar(scrollbar, 1.f); // Apply device emulation scale. DeviceEmulationParams params; params.viewport_offset = gfx::PointF(); params.viewport_scale = 1.5f; WebView()->EnableDeviceEmulation(params); UpdateAllLifecyclePhases(); ASSERT_EQ(scrollbar, GetFrame()->View()->RootCcLayer()->children().back().get()); check_scrollbar(scrollbar, 1.5f); params.viewport_scale = 1.f; WebView()->EnableDeviceEmulation(params); UpdateAllLifecyclePhases(); ASSERT_EQ(scrollbar, GetFrame()->View()->RootCcLayer()->children().back().get()); check_scrollbar(scrollbar, 1.f); params.viewport_scale = 0.75f; WebView()->EnableDeviceEmulation(params); UpdateAllLifecyclePhases(); ASSERT_EQ(scrollbar, GetFrame()->View()->RootCcLayer()->children().back().get()); check_scrollbar(scrollbar, 0.75f); } // When a pinch-zoom occurs, the viewport scale and translation nodes can be // directly updated without a PaintArtifactCompositor update. TEST_P(VisualViewportTest, DirectPinchZoomPropertyUpdate) { InitializeWithAndroidSettings(); RegisterMockedHttpURLLoad("200-by-800-viewport.html"); NavigateTo(base_url_ + "200-by-800-viewport.html"); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 200)); // Scroll visual viewport to the right edge of the frame VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.SetScaleAndLocation(2.f, true, FloatPoint(150, 10)); EXPECT_FLOAT_SIZE_EQ(FloatSize(150, 10), visual_viewport.GetScrollOffset()); EXPECT_EQ(2.f, visual_viewport.Scale()); UpdateAllLifecyclePhases(); EXPECT_FALSE(paint_artifact_compositor()->NeedsUpdate()); // Update the scale and location and ensure that a PaintArtifactCompositor // update is not required. visual_viewport.SetScaleAndLocation(3.f, true, FloatPoint(120, 10)); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_FALSE(paint_artifact_compositor()->NeedsUpdate()); EXPECT_FLOAT_SIZE_EQ(FloatSize(120, 10), visual_viewport.GetScrollOffset()); EXPECT_EQ(3.f, visual_viewport.Scale()); } // |TransformPaintPropertyNode::in_subtree_of_page_scale| should be false for // the page scale transform node and all ancestors, and should be true for // descendants of the page scale transform node. TEST_P(VisualViewportTest, InSubtreeOfPageScale) { InitializeWithAndroidSettings(); RegisterMockedHttpURLLoad("200-by-800-viewport.html"); NavigateTo(base_url_ + "200-by-800-viewport.html"); UpdateAllLifecyclePhases(); VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); const auto* page_scale = visual_viewport.GetPageScaleNode(); // The page scale is not in its own subtree. EXPECT_FALSE(page_scale->IsInSubtreeOfPageScale()); // Ancestors of the page scale are not in the page scale's subtree. for (const auto* ancestor = page_scale->UnaliasedParent(); ancestor; ancestor = ancestor->UnaliasedParent()) { EXPECT_FALSE(ancestor->IsInSubtreeOfPageScale()); } const auto* view = GetFrame()->View()->GetLayoutView(); const auto& view_contents_transform = view->FirstFragment().ContentsProperties().Transform(); // Descendants of the page scale node should have |IsInSubtreeOfPageScale|. EXPECT_TRUE(ToUnaliased(view_contents_transform).IsInSubtreeOfPageScale()); for (const auto* ancestor = view_contents_transform.UnaliasedParent(); ancestor != page_scale; ancestor = ancestor->UnaliasedParent()) { EXPECT_TRUE(ancestor->IsInSubtreeOfPageScale()); } } TEST_F(VisualViewportSimTest, UsedColorSchemeFromRootElement) { ScopedCSSColorSchemeUARenderingForTest color_scheme_ua_enabled(true); ColorSchemeHelper color_scheme_helper(*(WebView().GetPage())); color_scheme_helper.SetPreferredColorScheme( mojom::blink::PreferredColorScheme::kDark); WebView().MainFrameViewWidget()->Resize(gfx::Size(400, 600)); const VisualViewport& visual_viewport = WebView().GetPage()->GetVisualViewport(); EXPECT_EQ(mojom::blink::ColorScheme::kLight, visual_viewport.UsedColorScheme()); SimRequest request("https://example.com/test.html", "text/html"); LoadURL("https://example.com/test.html"); request.Complete(R"HTML( )HTML"); Compositor().BeginFrame(); EXPECT_EQ(mojom::blink::ColorScheme::kDark, visual_viewport.UsedColorScheme()); } TEST_P(VisualViewportTest, SetLocationBeforePrePaint) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 100)); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); // Simulate that the visual viewport is just created and FrameLoader is // restoring the previously saved scale and scroll state. VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); visual_viewport.DisposeImpl(); ASSERT_FALSE(visual_viewport.LayerForScrolling()); visual_viewport.SetScaleAndLocation(1.75, false, FloatPoint(12, 34)); EXPECT_EQ(FloatPoint(12, 34), visual_viewport.ScrollPosition()); UpdateAllLifecyclePhases(); EXPECT_EQ(FloatPoint(12, 34), visual_viewport.ScrollPosition()); // When we create the scrolling layer, we should update its scroll offset. ASSERT_TRUE(visual_viewport.LayerForScrolling()); auto* layer_tree_host = GetFrame()->View()->RootCcLayer()->layer_tree_host(); EXPECT_EQ( gfx::ScrollOffset(12, 34), layer_tree_host->property_trees()->scroll_tree.current_scroll_offset( visual_viewport.GetScrollElementId())); } TEST_P(VisualViewportTest, ScrollbarGeometryOnSizeChange) { InitializeWithAndroidSettings(); WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 100)); UpdateAllLifecyclePhases(); RegisterMockedHttpURLLoad("content-width-1000.html"); NavigateTo(base_url_ + "content-width-1000.html"); auto& visual_viewport = GetFrame()->GetPage()->GetVisualViewport(); EXPECT_EQ(IntSize(100, 100), visual_viewport.Size()); auto* horizontal_scrollbar = visual_viewport.LayerForHorizontalScrollbar(); auto* vertical_scrollbar = visual_viewport.LayerForVerticalScrollbar(); ASSERT_TRUE(horizontal_scrollbar); ASSERT_TRUE(vertical_scrollbar); EXPECT_EQ(gfx::Vector2dF(0, 93), horizontal_scrollbar->offset_to_transform_parent()); EXPECT_EQ(gfx::Vector2dF(93, 0), vertical_scrollbar->offset_to_transform_parent()); EXPECT_EQ(gfx::Size(93, 7), horizontal_scrollbar->bounds()); EXPECT_EQ(gfx::Size(7, 93), vertical_scrollbar->bounds()); // Simulate hiding of the top controls. WebView()->MainFrameViewWidget()->Resize(gfx::Size(100, 120)); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_TRUE( GetFrame()->View()->VisualViewportOrOverlayNeedsRepaintForTesting()); UpdateAllLifecyclePhases(); EXPECT_EQ(IntSize(100, 120), visual_viewport.Size()); ASSERT_EQ(horizontal_scrollbar, visual_viewport.LayerForHorizontalScrollbar()); ASSERT_EQ(vertical_scrollbar, visual_viewport.LayerForVerticalScrollbar()); EXPECT_EQ(gfx::Vector2dF(0, 113), horizontal_scrollbar->offset_to_transform_parent()); EXPECT_EQ(gfx::Vector2dF(93, 0), vertical_scrollbar->offset_to_transform_parent()); EXPECT_EQ(gfx::Size(93, 7), horizontal_scrollbar->bounds()); EXPECT_EQ(gfx::Size(7, 113), vertical_scrollbar->bounds()); } } // namespace } // namespace blink