| // 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 "core/frame/VisualViewport.h" |
| |
| #include "core/dom/Document.h" |
| #include "core/frame/FrameHost.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/TopControls.h" |
| #include "core/html/HTMLBodyElement.h" |
| #include "core/html/HTMLElement.h" |
| #include "core/input/EventHandler.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/layout/api/LayoutViewItem.h" |
| #include "core/layout/compositing/PaintLayerCompositor.h" |
| #include "core/page/Page.h" |
| #include "platform/PlatformGestureEvent.h" |
| #include "platform/geometry/DoublePoint.h" |
| #include "platform/geometry/DoubleRect.h" |
| #include "platform/testing/RuntimeEnabledFeaturesTestHelpers.h" |
| #include "platform/testing/URLTestHelpers.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebCachePolicy.h" |
| #include "public/platform/WebLayerTreeView.h" |
| #include "public/platform/WebURLLoaderMockFactory.h" |
| #include "public/web/WebCache.h" |
| #include "public/web/WebContextMenuData.h" |
| #include "public/web/WebDocument.h" |
| #include "public/web/WebFrameClient.h" |
| #include "public/web/WebInputEvent.h" |
| #include "public/web/WebScriptSource.h" |
| #include "public/web/WebSettings.h" |
| #include "public/web/WebViewClient.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "web/WebLocalFrameImpl.h" |
| #include "web/tests/FrameTestHelpers.h" |
| |
| #include <string> |
| |
| #define ASSERT_POINT_EQ(expected, actual) \ |
| do { \ |
| ASSERT_EQ((expected).x(), (actual).x()); \ |
| ASSERT_EQ((expected).y(), (actual).y()); \ |
| } while (false) |
| |
| #define EXPECT_POINT_EQ(expected, actual) \ |
| do { \ |
| EXPECT_EQ((expected).x(), (actual).x()); \ |
| EXPECT_EQ((expected).y(), (actual).y()); \ |
| } while (false) |
| |
| #define EXPECT_FLOAT_POINT_EQ(expected, actual) \ |
| do { \ |
| EXPECT_FLOAT_EQ((expected).x(), (actual).x()); \ |
| EXPECT_FLOAT_EQ((expected).y(), (actual).y()); \ |
| } while (false) |
| |
| #define EXPECT_POINT_EQ(expected, actual) \ |
| do { \ |
| EXPECT_EQ((expected).x(), (actual).x()); \ |
| EXPECT_EQ((expected).y(), (actual).y()); \ |
| } while (false) |
| |
| #define EXPECT_SIZE_EQ(expected, actual) \ |
| do { \ |
| EXPECT_EQ((expected).width(), (actual).width()); \ |
| EXPECT_EQ((expected).height(), (actual).height()); \ |
| } while (false) |
| |
| #define EXPECT_FLOAT_SIZE_EQ(expected, actual) \ |
| do { \ |
| EXPECT_FLOAT_EQ((expected).width(), (actual).width()); \ |
| EXPECT_FLOAT_EQ((expected).height(), (actual).height()); \ |
| } while (false) |
| |
| #define EXPECT_FLOAT_RECT_EQ(expected, actual) \ |
| do { \ |
| EXPECT_FLOAT_EQ((expected).x(), (actual).x()); \ |
| EXPECT_FLOAT_EQ((expected).y(), (actual).y()); \ |
| EXPECT_FLOAT_EQ((expected).width(), (actual).width()); \ |
| EXPECT_FLOAT_EQ((expected).height(), (actual).height()); \ |
| } while (false) |
| |
| using namespace blink; |
| |
| using ::testing::_; |
| using ::testing::PrintToString; |
| using ::testing::Mock; |
| |
| namespace blink { |
| ::std::ostream& operator<<(::std::ostream& os, const WebContextMenuData& data) { |
| return os << "Context menu location: [" << data.mousePosition.x << ", " |
| << data.mousePosition.y << "]"; |
| } |
| } |
| |
| namespace { |
| |
| class VisualViewportTest : public testing::Test { |
| public: |
| VisualViewportTest() : m_baseURL("http://www.test.com/") {} |
| |
| void initializeWithDesktopSettings( |
| void (*overrideSettingsFunc)(WebSettings*) = 0) { |
| if (!overrideSettingsFunc) |
| overrideSettingsFunc = &configureSettings; |
| m_helper.initialize(true, nullptr, &m_mockWebViewClient, nullptr, |
| overrideSettingsFunc); |
| webViewImpl()->setDefaultPageScaleLimits(1, 4); |
| } |
| |
| void initializeWithAndroidSettings( |
| void (*overrideSettingsFunc)(WebSettings*) = 0) { |
| if (!overrideSettingsFunc) |
| overrideSettingsFunc = &configureAndroidSettings; |
| m_helper.initialize(true, nullptr, &m_mockWebViewClient, nullptr, |
| overrideSettingsFunc); |
| webViewImpl()->setDefaultPageScaleLimits(0.25f, 5); |
| } |
| |
| ~VisualViewportTest() override { |
| Platform::current()->getURLLoaderMockFactory()->unregisterAllURLs(); |
| WebCache::clear(); |
| } |
| |
| void navigateTo(const std::string& url) { |
| FrameTestHelpers::loadFrame(webViewImpl()->mainFrame(), url); |
| } |
| |
| void forceFullCompositingUpdate() { |
| webViewImpl()->updateAllLifecyclePhases(); |
| } |
| |
| void registerMockedHttpURLLoad(const std::string& fileName) { |
| URLTestHelpers::registerMockedURLFromBaseURL( |
| WebString::fromUTF8(m_baseURL.c_str()), |
| WebString::fromUTF8(fileName.c_str())); |
| } |
| |
| WebLayer* getRootScrollLayer() { |
| PaintLayerCompositor* compositor = |
| frame()->contentLayoutItem().compositor(); |
| DCHECK(compositor); |
| DCHECK(compositor->scrollLayer()); |
| |
| WebLayer* webScrollLayer = compositor->scrollLayer()->platformLayer(); |
| return webScrollLayer; |
| } |
| |
| WebViewImpl* webViewImpl() const { return m_helper.webView(); } |
| LocalFrame* frame() const { |
| return m_helper.webView()->mainFrameImpl()->frame(); |
| } |
| |
| 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); |
| } |
| |
| protected: |
| std::string m_baseURL; |
| FrameTestHelpers::TestWebViewClient m_mockWebViewClient; |
| |
| private: |
| FrameTestHelpers::WebViewHelper m_helper; |
| }; |
| |
| typedef bool TestParamRootLayerScrolling; |
| class ParameterizedVisualViewportTest |
| : public testing::WithParamInterface<TestParamRootLayerScrolling>, |
| private ScopedRootLayerScrollingForTest, |
| public VisualViewportTest { |
| public: |
| ParameterizedVisualViewportTest() |
| : ScopedRootLayerScrollingForTest(GetParam()) {} |
| }; |
| |
| INSTANTIATE_TEST_CASE_P(All, |
| ParameterizedVisualViewportTest, |
| ::testing::Bool()); |
| |
| // Test that resizing the VisualViewport works as expected and that resizing the |
| // WebView resizes the VisualViewport. |
| TEST_P(ParameterizedVisualViewportTest, TestResize) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(320, 240)); |
| |
| navigateTo("about:blank"); |
| forceFullCompositingUpdate(); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| |
| IntSize webViewSize = webViewImpl()->size(); |
| |
| // Make sure the visual viewport was initialized. |
| EXPECT_SIZE_EQ(webViewSize, visualViewport.size()); |
| |
| // Resizing the WebView should change the VisualViewport. |
| webViewSize = IntSize(640, 480); |
| webViewImpl()->resize(webViewSize); |
| EXPECT_SIZE_EQ(webViewSize, IntSize(webViewImpl()->size())); |
| EXPECT_SIZE_EQ(webViewSize, visualViewport.size()); |
| |
| // Resizing the visual viewport shouldn't affect the WebView. |
| IntSize newViewportSize = IntSize(320, 200); |
| visualViewport.setSize(newViewportSize); |
| EXPECT_SIZE_EQ(webViewSize, IntSize(webViewImpl()->size())); |
| EXPECT_SIZE_EQ(newViewportSize, visualViewport.size()); |
| } |
| |
| // 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 FrameView will clamp |
| // the VisualViewport so we need to counter scroll the FrameView to make it |
| // appear to stay still). This caused bugs like crbug.com/453859. |
| TEST_P(ParameterizedVisualViewportTest, |
| TestResizeAtFullyScrolledPreservesViewportLocation) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(800, 600)); |
| |
| registerMockedHttpURLLoad("content-width-1000.html"); |
| navigateTo(m_baseURL + "content-width-1000.html"); |
| |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| |
| visualViewport.setScale(2); |
| |
| // Fully scroll both viewports. |
| frameView.layoutViewportScrollableArea()->setScrollPosition( |
| DoublePoint(10000, 10000), ProgrammaticScroll); |
| visualViewport.move(FloatSize(10000, 10000)); |
| |
| // Sanity check. |
| ASSERT_POINT_EQ(FloatPoint(400, 300), visualViewport.location()); |
| ASSERT_POINT_EQ( |
| DoublePoint(200, 1400), |
| frameView.layoutViewportScrollableArea()->scrollPositionDouble()); |
| |
| DoublePoint expectedLocation = |
| frameView.getScrollableArea()->visibleContentRectDouble().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. |
| webViewImpl()->resize(IntSize(700, 550)); |
| |
| EXPECT_POINT_EQ( |
| expectedLocation, |
| frameView.getScrollableArea()->visibleContentRectDouble().location()); |
| |
| webViewImpl()->resize(IntSize(800, 600)); |
| |
| EXPECT_POINT_EQ( |
| expectedLocation, |
| frameView.getScrollableArea()->visibleContentRectDouble().location()); |
| } |
| |
| // Test that the VisualViewport works as expected in case of a scaled |
| // and scrolled viewport - scroll down. |
| TEST_P(ParameterizedVisualViewportTest, TestResizeAfterVerticalScroll) { |
| /* |
| 200 200 |
| | | | | |
| | | | | |
| | | 800 | | 800 |
| |-------------------| | | |
| | | | | |
| | | | | |
| | | | | |
| | | --------> | | |
| | 300 | | | |
| | | | | |
| | 400 | | | |
| | | |-------------------| |
| | | | 75 | |
| | 50 | | 50 100| |
| o----- | o---- | |
| | | | | | 25 | |
| | |100 | |-------------------| |
| | | | | | |
| | | | | | |
| -------------------- -------------------- |
| |
| */ |
| |
| // Disable the test on Mac OSX until futher investigation. |
| // Local build on Mac is OK but thes bot fails. |
| #if OS(MACOSX) |
| return; |
| #endif |
| |
| initializeWithAndroidSettings(); |
| |
| registerMockedHttpURLLoad("200-by-800-viewport.html"); |
| navigateTo(m_baseURL + "200-by-800-viewport.html"); |
| |
| webViewImpl()->resize(IntSize(100, 200)); |
| |
| // Scroll main frame to the bottom of the document |
| webViewImpl()->mainFrame()->setScrollOffset(WebSize(0, 400)); |
| EXPECT_POINT_EQ( |
| IntPoint(0, 400), |
| frame()->view()->layoutViewportScrollableArea()->scrollPosition()); |
| |
| webViewImpl()->setPageScaleFactor(2.0); |
| |
| // Scroll visual viewport to the bottom of the main frame |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| visualViewport.setLocation(FloatPoint(0, 300)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 300), visualViewport.location()); |
| |
| // Verify the initial size of the visual viewport in the CSS pixels |
| EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 100), visualViewport.visibleRect().size()); |
| |
| // Perform the resizing |
| webViewImpl()->resize(IntSize(200, 100)); |
| |
| // After resizing the scale changes 2.0 -> 4.0 |
| EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 25), visualViewport.visibleRect().size()); |
| |
| EXPECT_POINT_EQ( |
| IntPoint(0, 625), |
| frame()->view()->layoutViewportScrollableArea()->scrollPosition()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 75), visualViewport.location()); |
| } |
| |
| // Test that the VisualViewport works as expected in case if a scaled |
| // and scrolled viewport - scroll right. |
| TEST_P(ParameterizedVisualViewportTest, TestResizeAfterHorizontalScroll) { |
| /* |
| 200 200 |
| ---------------o----- ---------------o----- |
| | | | | 25| | |
| | | | | -----| |
| | 100| | |100 50 | |
| | | | | | |
| | ---- | |-------------------| |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| |400 | ---------> | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| |-------------------| | | |
| | | | | |
| |
| */ |
| |
| // Disable the test on Mac OSX until futher investigation. |
| // Local build on Mac is OK but thes bot fails. |
| #if OS(MACOSX) |
| return; |
| #endif |
| |
| initializeWithAndroidSettings(); |
| |
| registerMockedHttpURLLoad("200-by-800-viewport.html"); |
| navigateTo(m_baseURL + "200-by-800-viewport.html"); |
| |
| webViewImpl()->resize(IntSize(100, 200)); |
| |
| // Outer viewport takes the whole width of the document. |
| |
| webViewImpl()->setPageScaleFactor(2.0); |
| |
| // Scroll visual viewport to the right edge of the frame |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| visualViewport.setLocation(FloatPoint(150, 0)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(150, 0), visualViewport.location()); |
| |
| // Verify the initial size of the visual viewport in the CSS pixels |
| EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 100), visualViewport.visibleRect().size()); |
| |
| webViewImpl()->resize(IntSize(200, 100)); |
| |
| // After resizing the scale changes 2.0 -> 4.0 |
| EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 25), visualViewport.visibleRect().size()); |
| |
| EXPECT_POINT_EQ(IntPoint(0, 0), frame()->view()->scrollPosition()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(150, 0), visualViewport.location()); |
| } |
| |
| // Test that the container layer gets sized properly if the WebView is resized |
| // prior to the VisualViewport being attached to the layer tree. |
| TEST_P(ParameterizedVisualViewportTest, TestWebViewResizedBeforeAttachment) { |
| initializeWithDesktopSettings(); |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| GraphicsLayer* rootGraphicsLayer = |
| frameView.layoutViewItem().compositor()->rootGraphicsLayer(); |
| |
| // Make sure that a resize that comes in while there's no root layer is |
| // honoured when we attach to the layer tree. |
| WebFrameWidgetBase* mainFrameWidget = |
| webViewImpl()->mainFrameImpl()->frameWidget(); |
| mainFrameWidget->setRootGraphicsLayer(nullptr); |
| webViewImpl()->resize(IntSize(320, 240)); |
| mainFrameWidget->setRootGraphicsLayer(rootGraphicsLayer); |
| |
| navigateTo("about:blank"); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| EXPECT_FLOAT_SIZE_EQ(FloatSize(320, 240), |
| visualViewport.containerLayer()->size()); |
| } |
| |
| // Make sure that the visibleRect method acurately reflects the scale and scroll |
| // location of the viewport. |
| TEST_P(ParameterizedVisualViewportTest, TestVisibleRect) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(320, 240)); |
| |
| navigateTo("about:blank"); |
| forceFullCompositingUpdate(); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| |
| // Initial visible rect should be the whole frame. |
| EXPECT_SIZE_EQ(IntSize(webViewImpl()->size()), visualViewport.size()); |
| |
| // Viewport is whole frame. |
| IntSize size = IntSize(400, 200); |
| webViewImpl()->resize(size); |
| webViewImpl()->updateAllLifecyclePhases(); |
| visualViewport.setSize(size); |
| |
| // Scale the viewport to 2X; size should not change. |
| FloatRect expectedRect(FloatPoint(0, 0), FloatSize(size)); |
| expectedRect.scale(0.5); |
| visualViewport.setScale(2); |
| EXPECT_EQ(2, visualViewport.scale()); |
| EXPECT_SIZE_EQ(size, visualViewport.size()); |
| EXPECT_FLOAT_RECT_EQ(expectedRect, visualViewport.visibleRect()); |
| |
| // Move the viewport. |
| expectedRect.setLocation(FloatPoint(5, 7)); |
| visualViewport.setLocation(expectedRect.location()); |
| EXPECT_FLOAT_RECT_EQ(expectedRect, visualViewport.visibleRect()); |
| |
| expectedRect.setLocation(FloatPoint(200, 100)); |
| visualViewport.setLocation(expectedRect.location()); |
| EXPECT_FLOAT_RECT_EQ(expectedRect, visualViewport.visibleRect()); |
| |
| // Scale the viewport to 3X to introduce some non-int values. |
| FloatPoint oldLocation = expectedRect.location(); |
| expectedRect = FloatRect(FloatPoint(), FloatSize(size)); |
| expectedRect.scale(1 / 3.0f); |
| expectedRect.setLocation(oldLocation); |
| visualViewport.setScale(3); |
| EXPECT_FLOAT_RECT_EQ(expectedRect, visualViewport.visibleRect()); |
| |
| expectedRect.setLocation(FloatPoint(0.25f, 0.333f)); |
| visualViewport.setLocation(expectedRect.location()); |
| EXPECT_FLOAT_RECT_EQ(expectedRect, visualViewport.visibleRect()); |
| } |
| |
| // Make sure that the visibleRectInDocument method acurately reflects the scale |
| // and scroll location of the viewport relative to the document. |
| TEST_P(ParameterizedVisualViewportTest, TestVisibleRectInDocument) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(100, 400)); |
| |
| registerMockedHttpURLLoad("200-by-800-viewport.html"); |
| navigateTo(m_baseURL + "200-by-800-viewport.html"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| |
| // Scale the viewport to 2X and move it. |
| visualViewport.setScale(2); |
| visualViewport.setLocation(FloatPoint(10, 15)); |
| EXPECT_FLOAT_RECT_EQ(FloatRect(10, 15, 50, 200), |
| visualViewport.visibleRectInDocument()); |
| |
| // Scroll the layout viewport. Ensure its offset is reflected in |
| // visibleRectInDocument(). |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| frameView.layoutViewportScrollableArea()->setScrollPosition( |
| DoublePoint(40, 100), ProgrammaticScroll); |
| EXPECT_FLOAT_RECT_EQ(FloatRect(50, 115, 50, 200), |
| visualViewport.visibleRectInDocument()); |
| } |
| |
| TEST_P(ParameterizedVisualViewportTest, |
| TestFractionalScrollOffsetIsNotOverwritten) { |
| bool origFractionalOffsetsEnabled = |
| RuntimeEnabledFeatures::fractionalScrollOffsetsEnabled(); |
| RuntimeEnabledFeatures::setFractionalScrollOffsetsEnabled(true); |
| |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(200, 250)); |
| |
| registerMockedHttpURLLoad("200-by-800-viewport.html"); |
| navigateTo(m_baseURL + "200-by-800-viewport.html"); |
| |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| frameView.layoutViewportScrollableArea()->setScrollPosition( |
| DoublePoint(0, 10.5), ProgrammaticScroll); |
| frameView.layoutViewportScrollableArea()->ScrollableArea::setScrollPosition( |
| DoublePoint(10, 30.5), CompositorScroll); |
| |
| EXPECT_EQ( |
| 30.5, |
| frameView.layoutViewportScrollableArea()->scrollPositionDouble().y()); |
| |
| RuntimeEnabledFeatures::setFractionalScrollOffsetsEnabled( |
| origFractionalOffsetsEnabled); |
| } |
| |
| // 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(ParameterizedVisualViewportTest, TestOffsetClamping) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(320, 240)); |
| |
| navigateTo("about:blank"); |
| forceFullCompositingUpdate(); |
| |
| // Visual viewport should be initialized to same size as frame so no scrolling |
| // possible. |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| |
| visualViewport.setLocation(FloatPoint(-1, -2)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| |
| visualViewport.setLocation(FloatPoint(100, 200)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| |
| visualViewport.setLocation(FloatPoint(-5, 10)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| |
| // Scale by 2x. The viewport's visible rect should now have a size of 160x120. |
| visualViewport.setScale(2); |
| FloatPoint location(10, 50); |
| visualViewport.setLocation(location); |
| EXPECT_FLOAT_POINT_EQ(location, visualViewport.visibleRect().location()); |
| |
| visualViewport.setLocation(FloatPoint(1000, 2000)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(160, 120), |
| visualViewport.visibleRect().location()); |
| |
| visualViewport.setLocation(FloatPoint(-1000, -2000)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| |
| // Make sure offset gets clamped on scale out. Scale to 1.25 so the viewport |
| // is 256x192. |
| visualViewport.setLocation(FloatPoint(160, 120)); |
| visualViewport.setScale(1.25); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(64, 48), |
| visualViewport.visibleRect().location()); |
| |
| // Scale out smaller than 1. |
| visualViewport.setScale(0.25); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.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(ParameterizedVisualViewportTest, TestOffsetClampingWithResize) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(320, 240)); |
| |
| navigateTo("about:blank"); |
| forceFullCompositingUpdate(); |
| |
| // Visual viewport should be initialized to same size as frame so no scrolling |
| // possible. |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| |
| // Shrink the viewport vertically. The resize shouldn't affect the location, |
| // but it should allow vertical scrolling. |
| visualViewport.setSize(IntSize(320, 200)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(10, 20)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 20), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(0, 100)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 40), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(0, 10)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 10), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(0, -100)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| |
| // Repeat the above but for horizontal dimension. |
| visualViewport.setSize(IntSize(280, 240)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(10, 20)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 0), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(100, 0)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(40, 0), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(10, 0)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 0), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(-100, 0)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| |
| // Now with both dimensions. |
| visualViewport.setSize(IntSize(280, 200)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(10, 20)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 20), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(100, 100)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(40, 40), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(10, 3)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 3), |
| visualViewport.visibleRect().location()); |
| visualViewport.setLocation(FloatPoint(-10, -4)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| } |
| |
| // Test that the viewport is scrollable but bounded appropriately within the |
| // main frame when we apply both scaling and resizes. |
| TEST_P(ParameterizedVisualViewportTest, TestOffsetClampingWithResizeAndScale) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(320, 240)); |
| |
| navigateTo("about:blank"); |
| forceFullCompositingUpdate(); |
| |
| // Visual viewport should be initialized to same size as WebView so no |
| // scrolling possible. |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| visualViewport.visibleRect().location()); |
| |
| // Zoom in to 2X so we can scroll the viewport to 160x120. |
| visualViewport.setScale(2); |
| visualViewport.setLocation(FloatPoint(200, 200)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(160, 120), |
| visualViewport.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. |
| visualViewport.setSize(IntSize(310, 230)); |
| visualViewport.setLocation(FloatPoint(200, 200)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(165, 125), |
| visualViewport.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. |
| visualViewport.setSize(IntSize(330, 250)); |
| EXPECT_SIZE_EQ(IntSize(330, 250), visualViewport.size()); |
| |
| // Resize both the viewport and the frame to be larger. |
| webViewImpl()->resize(IntSize(640, 480)); |
| webViewImpl()->updateAllLifecyclePhases(); |
| EXPECT_SIZE_EQ(IntSize(webViewImpl()->size()), visualViewport.size()); |
| EXPECT_SIZE_EQ(IntSize(webViewImpl()->size()), |
| frame()->view()->frameRect().size()); |
| visualViewport.setLocation(FloatPoint(1000, 1000)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(320, 240), |
| visualViewport.visibleRect().location()); |
| |
| // Make sure resizing the viewport doesn't change its offset if the resize |
| // doesn't make the viewport go out of bounds. |
| visualViewport.setLocation(FloatPoint(200, 200)); |
| visualViewport.setSize(IntSize(880, 560)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(200, 200), |
| visualViewport.visibleRect().location()); |
| } |
| |
| // The main FrameView'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 |
| // FrameView should be set to the content width and height derived by the aspect |
| // ratio. |
| TEST_P(ParameterizedVisualViewportTest, TestFrameViewSizedToContent) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(320, 240)); |
| |
| registerMockedHttpURLLoad("200-by-300-viewport.html"); |
| navigateTo(m_baseURL + "200-by-300-viewport.html"); |
| |
| webViewImpl()->resize(IntSize(600, 800)); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| // Note: the size is ceiled and should match the behavior in CC's |
| // LayerImpl::bounds(). |
| EXPECT_SIZE_EQ( |
| IntSize(200, 267), |
| webViewImpl()->mainFrameImpl()->frameView()->frameRect().size()); |
| } |
| |
| // The main FrameView'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 FrameView is sized to the viewport. |
| TEST_P(ParameterizedVisualViewportTest, TestFrameViewSizedToMinimumScale) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(320, 240)); |
| |
| registerMockedHttpURLLoad("200-by-300.html"); |
| navigateTo(m_baseURL + "200-by-300.html"); |
| |
| webViewImpl()->resize(IntSize(100, 160)); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| EXPECT_SIZE_EQ( |
| IntSize(100, 160), |
| webViewImpl()->mainFrameImpl()->frameView()->frameRect().size()); |
| } |
| |
| // Test that attaching a new frame view resets the size of the inner viewport |
| // scroll layer. crbug.com/423189. |
| TEST_P(ParameterizedVisualViewportTest, |
| TestAttachingNewFrameSetsInnerScrollLayerSize) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(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(m_baseURL + "content-width-1000.html"); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| visualViewport.setScale(2); |
| visualViewport.move(FloatPoint(50, 60)); |
| |
| // Move and scale the viewport to make sure it gets reset in the navigation. |
| EXPECT_POINT_EQ(FloatPoint(50, 60), visualViewport.location()); |
| EXPECT_EQ(2, visualViewport.scale()); |
| |
| // Navigate again, this time the FrameView should be smaller. |
| registerMockedHttpURLLoad("viewport-device-width.html"); |
| navigateTo(m_baseURL + "viewport-device-width.html"); |
| |
| // Ensure the scroll layer matches the frame view's size. |
| EXPECT_SIZE_EQ(FloatSize(320, 240), visualViewport.scrollLayer()->size()); |
| |
| // Ensure the location and scale were reset. |
| EXPECT_POINT_EQ(FloatPoint(), visualViewport.location()); |
| EXPECT_EQ(1, visualViewport.scale()); |
| } |
| |
| // The main FrameView's size should be set such that its the size of the visual |
| // viewport at minimum scale. Test that the FrameView is appropriately sized in |
| // the presence of a viewport <meta> tag. |
| TEST_P(ParameterizedVisualViewportTest, |
| TestFrameViewSizedToViewportMetaMinimumScale) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(320, 240)); |
| |
| registerMockedHttpURLLoad("200-by-300-min-scale-2.html"); |
| navigateTo(m_baseURL + "200-by-300-min-scale-2.html"); |
| |
| webViewImpl()->resize(IntSize(100, 160)); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| EXPECT_SIZE_EQ( |
| IntSize(50, 80), |
| webViewImpl()->mainFrameImpl()->frameView()->frameRect().size()); |
| } |
| |
| // Test that the visual viewport still gets sized in AutoSize/AutoResize mode. |
| TEST_P(ParameterizedVisualViewportTest, |
| TestVisualViewportGetsSizeInAutoSizeMode) { |
| initializeWithDesktopSettings(); |
| |
| EXPECT_SIZE_EQ(IntSize(0, 0), IntSize(webViewImpl()->size())); |
| EXPECT_SIZE_EQ(IntSize(0, 0), |
| frame()->page()->frameHost().visualViewport().size()); |
| |
| webViewImpl()->enableAutoResizeMode(WebSize(10, 10), WebSize(1000, 1000)); |
| |
| registerMockedHttpURLLoad("200-by-300.html"); |
| navigateTo(m_baseURL + "200-by-300.html"); |
| |
| EXPECT_SIZE_EQ(IntSize(200, 300), |
| frame()->page()->frameHost().visualViewport().size()); |
| } |
| |
| // Test that the text selection handle's position accounts for the visual |
| // viewport. |
| TEST_P(ParameterizedVisualViewportTest, TestTextSelectionHandles) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(500, 800)); |
| |
| registerMockedHttpURLLoad("pinch-viewport-input-field.html"); |
| navigateTo(m_baseURL + "pinch-viewport-input-field.html"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| webViewImpl()->setInitialFocus(false); |
| |
| WebRect originalAnchor; |
| WebRect originalFocus; |
| webViewImpl()->selectionBounds(originalAnchor, originalFocus); |
| |
| webViewImpl()->setPageScaleFactor(2); |
| visualViewport.setLocation(FloatPoint(100, 400)); |
| |
| WebRect anchor; |
| WebRect focus; |
| webViewImpl()->selectionBounds(anchor, focus); |
| |
| IntPoint expected(IntRect(originalAnchor).location()); |
| expected.moveBy(-flooredIntPoint(visualViewport.visibleRect().location())); |
| expected.scale(visualViewport.scale(), visualViewport.scale()); |
| |
| EXPECT_POINT_EQ(expected, IntRect(anchor).location()); |
| EXPECT_POINT_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(ParameterizedVisualViewportTest, TestSavedToHistoryItem) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(200, 300)); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| registerMockedHttpURLLoad("200-by-300.html"); |
| navigateTo(m_baseURL + "200-by-300.html"); |
| |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), |
| toLocalFrame(webViewImpl()->page()->mainFrame()) |
| ->loader() |
| .currentItem() |
| ->visualViewportScrollPoint()); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| visualViewport.setScale(2); |
| |
| EXPECT_EQ(2, toLocalFrame(webViewImpl()->page()->mainFrame()) |
| ->loader() |
| .currentItem() |
| ->pageScaleFactor()); |
| |
| visualViewport.setLocation(FloatPoint(10, 20)); |
| |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 20), |
| toLocalFrame(webViewImpl()->page()->mainFrame()) |
| ->loader() |
| .currentItem() |
| ->visualViewportScrollPoint()); |
| } |
| |
| // Test restoring a HistoryItem properly restores the visual viewport's state. |
| TEST_P(ParameterizedVisualViewportTest, TestRestoredFromHistoryItem) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(200, 300)); |
| |
| registerMockedHttpURLLoad("200-by-300.html"); |
| |
| WebHistoryItem item; |
| item.initialize(); |
| WebURL destinationURL(URLTestHelpers::toKURL(m_baseURL + "200-by-300.html")); |
| item.setURLString(destinationURL.string()); |
| item.setVisualViewportScrollOffset(WebFloatPoint(100, 120)); |
| item.setPageScaleFactor(2); |
| |
| FrameTestHelpers::loadHistoryItem(webViewImpl()->mainFrame(), item, |
| WebHistoryDifferentDocumentLoad, |
| WebCachePolicy::UseProtocolCachePolicy); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| EXPECT_EQ(2, visualViewport.scale()); |
| |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(100, 120), |
| visualViewport.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_F(VisualViewportTest, TestRestoredFromLegacyHistoryItem) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(100, 150)); |
| |
| registerMockedHttpURLLoad("200-by-300-viewport.html"); |
| |
| WebHistoryItem item; |
| item.initialize(); |
| WebURL destinationURL( |
| URLTestHelpers::toKURL(m_baseURL + "200-by-300-viewport.html")); |
| item.setURLString(destinationURL.string()); |
| // (-1, -1) will be used if the HistoryItem is an older version prior to |
| // having visual viewport scroll offset. |
| item.setVisualViewportScrollOffset(WebFloatPoint(-1, -1)); |
| item.setScrollOffset(WebPoint(120, 180)); |
| item.setPageScaleFactor(2); |
| |
| FrameTestHelpers::loadHistoryItem(webViewImpl()->mainFrame(), item, |
| WebHistoryDifferentDocumentLoad, |
| WebCachePolicy::UseProtocolCachePolicy); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| EXPECT_EQ(2, visualViewport.scale()); |
| EXPECT_POINT_EQ(IntPoint(100, 150), frame()->view()->scrollPosition()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(20, 30), |
| visualViewport.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(ParameterizedVisualViewportTest, |
| TestNavigateToSmallerFrameViewHistoryItemClobberBug) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(400, 400)); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| registerMockedHttpURLLoad("content-width-1000.html"); |
| navigateTo(m_baseURL + "content-width-1000.html"); |
| |
| FrameView* frameView = webViewImpl()->mainFrameImpl()->frameView(); |
| frameView->layoutViewportScrollableArea()->setScrollPosition( |
| IntPoint(0, 1000), ProgrammaticScroll); |
| |
| EXPECT_SIZE_EQ(IntSize(1000, 1000), frameView->frameRect().size()); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| visualViewport.setScale(2); |
| visualViewport.setLocation(FloatPoint(350, 350)); |
| |
| Persistent<HistoryItem> firstItem = |
| webViewImpl()->mainFrameImpl()->frame()->loader().currentItem(); |
| EXPECT_POINT_EQ(IntPoint(0, 1000), firstItem->scrollPoint()); |
| |
| // Now navigate to a page which causes a smaller frameView. Make sure that |
| // navigating doesn't cause the history item to set a new scroll offset |
| // before the item was replaced. |
| navigateTo("about:blank"); |
| frameView = webViewImpl()->mainFrameImpl()->frameView(); |
| |
| EXPECT_NE(firstItem, |
| webViewImpl()->mainFrameImpl()->frame()->loader().currentItem()); |
| EXPECT_LT(frameView->frameRect().size().width(), 1000); |
| EXPECT_POINT_EQ(IntPoint(0, 1000), firstItem->scrollPoint()); |
| } |
| |
| // Test that the coordinates sent into moveRangeSelection are offset by the |
| // visual viewport's location. |
| TEST_P(ParameterizedVisualViewportTest, |
| DISABLED_TestWebFrameRangeAccountsForVisualViewportScroll) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->settings()->setDefaultFontSize(12); |
| webViewImpl()->resize(WebSize(640, 480)); |
| registerMockedHttpURLLoad("move_range.html"); |
| navigateTo(m_baseURL + "move_range.html"); |
| |
| WebRect baseRect; |
| WebRect extentRect; |
| |
| webViewImpl()->setPageScaleFactor(2); |
| WebFrame* mainFrame = webViewImpl()->mainFrame(); |
| |
| // 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 |
| mainFrame->executeScript(WebScriptSource("selectRange();")); |
| EXPECT_EQ("ir", mainFrame->toWebLocalFrame()->selectionAsText().utf8()); |
| |
| webViewImpl()->selectionBounds(baseRect, extentRect); |
| WebPoint initialPoint(baseRect.x, baseRect.y); |
| WebPoint endPoint(extentRect.x, extentRect.y); |
| |
| // 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& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| visualViewport.move(FloatPoint(60, 25)); |
| mainFrame->toWebLocalFrame()->moveRangeSelection(initialPoint, endPoint); |
| EXPECT_EQ("t ", mainFrame->toWebLocalFrame()->selectionAsText().utf8()); |
| } |
| |
| // Test that the scrollFocusedEditableElementIntoRect method works with the |
| // visual viewport. |
| TEST_P(ParameterizedVisualViewportTest, |
| DISABLED_TestScrollFocusedEditableElementIntoRect) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(500, 300)); |
| |
| registerMockedHttpURLLoad("pinch-viewport-input-field.html"); |
| navigateTo(m_baseURL + "pinch-viewport-input-field.html"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| webViewImpl()->resizeVisualViewport(IntSize(200, 100)); |
| webViewImpl()->setInitialFocus(false); |
| visualViewport.setLocation(FloatPoint()); |
| webViewImpl()->scrollFocusedEditableElementIntoRect(IntRect(0, 0, 500, 200)); |
| |
| EXPECT_POINT_EQ(IntPoint(0, frame()->view()->maximumScrollPosition().y()), |
| frame()->view()->scrollPosition()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(150, 200), |
| visualViewport.visibleRect().location()); |
| |
| // Try it again but with the page zoomed in |
| frame()->view()->setScrollPosition(IntPoint(0, 0), ProgrammaticScroll); |
| webViewImpl()->resizeVisualViewport(IntSize(500, 300)); |
| visualViewport.setLocation(FloatPoint(0, 0)); |
| |
| webViewImpl()->setPageScaleFactor(2); |
| webViewImpl()->scrollFocusedEditableElementIntoRect(IntRect(0, 0, 500, 200)); |
| EXPECT_POINT_EQ(IntPoint(0, frame()->view()->maximumScrollPosition().y()), |
| frame()->view()->scrollPosition()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(125, 150), |
| visualViewport.visibleRect().location()); |
| |
| // Once more but make sure that we don't move the visual viewport unless |
| // necessary. |
| registerMockedHttpURLLoad("pinch-viewport-input-field-long-and-wide.html"); |
| navigateTo(m_baseURL + "pinch-viewport-input-field-long-and-wide.html"); |
| webViewImpl()->setInitialFocus(false); |
| visualViewport.setLocation(FloatPoint()); |
| frame()->view()->setScrollPosition(IntPoint(0, 0), ProgrammaticScroll); |
| webViewImpl()->resizeVisualViewport(IntSize(500, 300)); |
| visualViewport.setLocation(FloatPoint(30, 50)); |
| |
| webViewImpl()->setPageScaleFactor(2); |
| webViewImpl()->scrollFocusedEditableElementIntoRect(IntRect(0, 0, 500, 200)); |
| EXPECT_POINT_EQ(IntPoint(200 - 30 - 75, 600 - 50 - 65), |
| frame()->view()->scrollPosition()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(30, 50), |
| visualViewport.visibleRect().location()); |
| } |
| |
| // Test that resizing the WebView causes ViewportConstrained objects to |
| // relayout. |
| TEST_P(ParameterizedVisualViewportTest, |
| TestWebViewResizeCausesViewportConstrainedLayout) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(500, 300)); |
| |
| registerMockedHttpURLLoad("pinch-viewport-fixed-pos.html"); |
| navigateTo(m_baseURL + "pinch-viewport-fixed-pos.html"); |
| |
| LayoutObject* navbar = |
| frame()->document()->getElementById("navbar")->layoutObject(); |
| |
| EXPECT_FALSE(navbar->needsLayout()); |
| |
| frame()->view()->resize(IntSize(500, 200)); |
| |
| EXPECT_TRUE(navbar->needsLayout()); |
| } |
| |
| class MockWebFrameClient : public FrameTestHelpers::TestWebFrameClient { |
| public: |
| MOCK_METHOD1(showContextMenu, void(const WebContextMenuData&)); |
| MOCK_METHOD1(didChangeScrollOffset, void(WebLocalFrame*)); |
| }; |
| |
| MATCHER_P2(ContextMenuAtLocation, |
| x, |
| y, |
| std::string(negation ? "is" : "isn't") + " at expected location [" + |
| PrintToString(x) + |
| ", " + |
| PrintToString(y) + |
| "]") { |
| return arg.mousePosition.x == x && arg.mousePosition.y == y; |
| } |
| |
| // Test that the context menu's location is correct in the presence of visual |
| // viewport offset. |
| TEST_P(ParameterizedVisualViewportTest, TestContextMenuShownInCorrectLocation) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(200, 300)); |
| |
| registerMockedHttpURLLoad("200-by-300.html"); |
| navigateTo(m_baseURL + "200-by-300.html"); |
| |
| WebMouseEvent mouseDownEvent; |
| mouseDownEvent.type = WebInputEvent::MouseDown; |
| mouseDownEvent.x = 10; |
| mouseDownEvent.y = 10; |
| mouseDownEvent.windowX = 10; |
| mouseDownEvent.windowY = 10; |
| mouseDownEvent.globalX = 110; |
| mouseDownEvent.globalY = 210; |
| mouseDownEvent.clickCount = 1; |
| mouseDownEvent.button = WebMouseEvent::Button::Right; |
| |
| // Corresponding release event (Windows shows context menu on release). |
| WebMouseEvent mouseUpEvent(mouseDownEvent); |
| mouseUpEvent.type = WebInputEvent::MouseUp; |
| |
| WebFrameClient* oldClient = webViewImpl()->mainFrameImpl()->client(); |
| MockWebFrameClient mockWebFrameClient; |
| EXPECT_CALL(mockWebFrameClient, showContextMenu(ContextMenuAtLocation( |
| mouseDownEvent.x, mouseDownEvent.y))); |
| |
| // Do a sanity check with no scale applied. |
| webViewImpl()->mainFrameImpl()->setClient(&mockWebFrameClient); |
| webViewImpl()->handleInputEvent(mouseDownEvent); |
| webViewImpl()->handleInputEvent(mouseUpEvent); |
| |
| Mock::VerifyAndClearExpectations(&mockWebFrameClient); |
| mouseDownEvent.button = WebMouseEvent::Button::Left; |
| webViewImpl()->handleInputEvent(mouseDownEvent); |
| |
| // 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& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| webViewImpl()->setPageScaleFactor(2); |
| visualViewport.setLocation(FloatPoint(60, 80)); |
| EXPECT_CALL(mockWebFrameClient, showContextMenu(ContextMenuAtLocation( |
| mouseDownEvent.x, mouseDownEvent.y))); |
| |
| mouseDownEvent.button = WebMouseEvent::Button::Right; |
| webViewImpl()->handleInputEvent(mouseDownEvent); |
| webViewImpl()->handleInputEvent(mouseUpEvent); |
| |
| // Reset the old client so destruction can occur naturally. |
| webViewImpl()->mainFrameImpl()->setClient(oldClient); |
| } |
| |
| // Test that the client is notified if page scroll events. |
| TEST_P(ParameterizedVisualViewportTest, TestClientNotifiedOfScrollEvents) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(200, 300)); |
| |
| registerMockedHttpURLLoad("200-by-300.html"); |
| navigateTo(m_baseURL + "200-by-300.html"); |
| |
| WebFrameClient* oldClient = webViewImpl()->mainFrameImpl()->client(); |
| MockWebFrameClient mockWebFrameClient; |
| webViewImpl()->mainFrameImpl()->setClient(&mockWebFrameClient); |
| |
| webViewImpl()->setPageScaleFactor(2); |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| |
| EXPECT_CALL(mockWebFrameClient, didChangeScrollOffset(_)); |
| visualViewport.setLocation(FloatPoint(60, 80)); |
| Mock::VerifyAndClearExpectations(&mockWebFrameClient); |
| |
| // Scroll vertically. |
| EXPECT_CALL(mockWebFrameClient, didChangeScrollOffset(_)); |
| visualViewport.setLocation(FloatPoint(60, 90)); |
| Mock::VerifyAndClearExpectations(&mockWebFrameClient); |
| |
| // Scroll horizontally. |
| EXPECT_CALL(mockWebFrameClient, didChangeScrollOffset(_)); |
| visualViewport.setLocation(FloatPoint(70, 90)); |
| |
| // Reset the old client so destruction can occur naturally. |
| webViewImpl()->mainFrameImpl()->setClient(oldClient); |
| } |
| |
| // 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(ParameterizedVisualViewportTest, ScrollIntoViewFractionalOffset) { |
| initializeWithAndroidSettings(); |
| |
| webViewImpl()->resize(IntSize(1000, 1000)); |
| |
| registerMockedHttpURLLoad("scroll-into-view.html"); |
| navigateTo(m_baseURL + "scroll-into-view.html"); |
| |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| ScrollableArea* layoutViewportScrollableArea = |
| frameView.layoutViewportScrollableArea(); |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| Element* inputBox = frame()->document()->getElementById("box"); |
| |
| webViewImpl()->setPageScaleFactor(2); |
| |
| // The element is already in the view so the scrollIntoView shouldn't move |
| // the viewport at all. |
| webViewImpl()->setVisualViewportOffset(WebFloatPoint(250.25f, 100.25f)); |
| layoutViewportScrollableArea->setScrollPosition(DoublePoint(0, 900.75), |
| ProgrammaticScroll); |
| inputBox->scrollIntoViewIfNeeded(false); |
| |
| EXPECT_POINT_EQ(DoublePoint(0, 900), |
| layoutViewportScrollableArea->scrollPositionDouble()); |
| EXPECT_POINT_EQ(FloatPoint(250.25f, 100.25f), visualViewport.location()); |
| |
| // Change the fractional part of the frameview to one that would round down. |
| layoutViewportScrollableArea->setScrollPosition(DoublePoint(0, 900.125), |
| ProgrammaticScroll); |
| inputBox->scrollIntoViewIfNeeded(false); |
| |
| EXPECT_POINT_EQ(DoublePoint(0, 900), |
| layoutViewportScrollableArea->scrollPositionDouble()); |
| EXPECT_POINT_EQ(FloatPoint(250.25f, 100.25f), visualViewport.location()); |
| |
| // Repeat both tests above with the visual viewport at a high fractional. |
| webViewImpl()->setVisualViewportOffset(WebFloatPoint(250.875f, 100.875f)); |
| layoutViewportScrollableArea->setScrollPosition(DoublePoint(0, 900.75), |
| ProgrammaticScroll); |
| inputBox->scrollIntoViewIfNeeded(false); |
| |
| EXPECT_POINT_EQ(DoublePoint(0, 900), |
| layoutViewportScrollableArea->scrollPositionDouble()); |
| EXPECT_POINT_EQ(FloatPoint(250.875f, 100.875f), visualViewport.location()); |
| |
| // Change the fractional part of the frameview to one that would round down. |
| layoutViewportScrollableArea->setScrollPosition(DoublePoint(0, 900.125), |
| ProgrammaticScroll); |
| inputBox->scrollIntoViewIfNeeded(false); |
| |
| EXPECT_POINT_EQ(DoublePoint(0, 900), |
| layoutViewportScrollableArea->scrollPositionDouble()); |
| EXPECT_POINT_EQ(FloatPoint(250.875f, 100.875f), visualViewport.location()); |
| |
| // Both viewports with a 0.5 fraction. |
| webViewImpl()->setVisualViewportOffset(WebFloatPoint(250.5f, 100.5f)); |
| layoutViewportScrollableArea->setScrollPosition(DoublePoint(0, 900.5), |
| ProgrammaticScroll); |
| inputBox->scrollIntoViewIfNeeded(false); |
| |
| EXPECT_POINT_EQ(DoublePoint(0, 900), |
| layoutViewportScrollableArea->scrollPositionDouble()); |
| EXPECT_POINT_EQ(FloatPoint(250.5f, 100.5f), visualViewport.location()); |
| } |
| |
| static IntPoint expectedMaxFrameViewScrollOffset(VisualViewport& visualViewport, |
| FrameView& frameView) { |
| float aspectRatio = visualViewport.visibleRect().width() / |
| visualViewport.visibleRect().height(); |
| float newHeight = frameView.frameRect().width() / aspectRatio; |
| return IntPoint( |
| frameView.contentsSize().width() - frameView.frameRect().width(), |
| frameView.contentsSize().height() - newHeight); |
| } |
| |
| TEST_F(VisualViewportTest, TestTopControlsAdjustment) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resizeWithTopControls(IntSize(500, 450), 20, false); |
| |
| registerMockedHttpURLLoad("content-width-1000.html"); |
| navigateTo(m_baseURL + "content-width-1000.html"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| |
| visualViewport.setScale(1); |
| EXPECT_SIZE_EQ(IntSize(500, 450), visualViewport.visibleRect().size()); |
| EXPECT_SIZE_EQ(IntSize(1000, 900), frameView.frameRect().size()); |
| |
| // Simulate bringing down the top controls by 20px. |
| webViewImpl()->applyViewportDeltas(WebFloatSize(), WebFloatSize(), |
| WebFloatSize(), 1, 1); |
| EXPECT_SIZE_EQ(IntSize(500, 430), visualViewport.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. |
| visualViewport.move(FloatPoint(10000, 10000)); |
| EXPECT_POINT_EQ(FloatPoint(500, 860 - 430), visualViewport.location()); |
| |
| // The outer viewport (FrameView) should be affected as well. |
| frameView.scrollBy(IntSize(10000, 10000), UserScroll); |
| EXPECT_POINT_EQ(expectedMaxFrameViewScrollOffset(visualViewport, frameView), |
| frameView.scrollPosition()); |
| |
| // Simulate bringing up the top controls by 10.5px. |
| webViewImpl()->applyViewportDeltas(WebFloatSize(), WebFloatSize(), |
| WebFloatSize(), 1, -10.5f / 20); |
| EXPECT_FLOAT_SIZE_EQ(FloatSize(500, 440.5f), |
| visualViewport.visibleRect().size()); |
| |
| // maximumScrollPosition |ceil|s the top controls adjustment. |
| visualViewport.move(FloatPoint(10000, 10000)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(500, 881 - 441), visualViewport.location()); |
| |
| // The outer viewport (FrameView) should be affected as well. |
| frameView.scrollBy(IntSize(10000, 10000), UserScroll); |
| EXPECT_POINT_EQ(expectedMaxFrameViewScrollOffset(visualViewport, frameView), |
| frameView.scrollPosition()); |
| } |
| |
| TEST_F(VisualViewportTest, TestTopControlsAdjustmentWithScale) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resizeWithTopControls(IntSize(500, 450), 20, false); |
| |
| registerMockedHttpURLLoad("content-width-1000.html"); |
| navigateTo(m_baseURL + "content-width-1000.html"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| |
| visualViewport.setScale(2); |
| EXPECT_SIZE_EQ(IntSize(250, 225), visualViewport.visibleRect().size()); |
| EXPECT_SIZE_EQ(IntSize(1000, 900), frameView.frameRect().size()); |
| |
| // Simulate bringing down the top controls by 20px. Since we're zoomed in, the |
| // top controls take up half as much space (in document-space) than they do at |
| // an unzoomed level. |
| webViewImpl()->applyViewportDeltas(WebFloatSize(), WebFloatSize(), |
| WebFloatSize(), 1, 1); |
| EXPECT_SIZE_EQ(IntSize(250, 215), visualViewport.visibleRect().size()); |
| |
| // Test that the scroll bounds are adjusted appropriately. |
| visualViewport.move(FloatPoint(10000, 10000)); |
| EXPECT_POINT_EQ(FloatPoint(750, 860 - 215), visualViewport.location()); |
| |
| // The outer viewport (FrameView) should be affected as well. |
| frameView.scrollBy(IntSize(10000, 10000), UserScroll); |
| IntPoint expected = |
| expectedMaxFrameViewScrollOffset(visualViewport, frameView); |
| EXPECT_POINT_EQ(expected, frameView.scrollPosition()); |
| |
| // Scale back out, FrameView max scroll shouldn't have changed. Visual |
| // viewport should be moved up to accomodate larger view. |
| webViewImpl()->applyViewportDeltas(WebFloatSize(), WebFloatSize(), |
| WebFloatSize(), 0.5f, 0); |
| EXPECT_EQ(1, visualViewport.scale()); |
| EXPECT_POINT_EQ(expected, frameView.scrollPosition()); |
| frameView.scrollBy(IntSize(10000, 10000), UserScroll); |
| EXPECT_POINT_EQ(expected, frameView.scrollPosition()); |
| |
| EXPECT_POINT_EQ(FloatPoint(500, 860 - 430), visualViewport.location()); |
| visualViewport.move(FloatPoint(10000, 10000)); |
| EXPECT_POINT_EQ(FloatPoint(500, 860 - 430), visualViewport.location()); |
| |
| // Scale out, use a scale that causes fractional rects. |
| webViewImpl()->applyViewportDeltas(WebFloatSize(), WebFloatSize(), |
| WebFloatSize(), 0.8f, -1); |
| EXPECT_SIZE_EQ(FloatSize(625, 562.5), visualViewport.visibleRect().size()); |
| |
| // Bring out the top controls by 11 |
| webViewImpl()->applyViewportDeltas(WebFloatSize(), WebFloatSize(), |
| WebFloatSize(), 1, 11 / 20.f); |
| EXPECT_SIZE_EQ(FloatSize(625, 548.75), visualViewport.visibleRect().size()); |
| |
| // Ensure max scroll offsets are updated properly. |
| visualViewport.move(FloatPoint(10000, 10000)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(375, 877.5 - 548.75), |
| visualViewport.location()); |
| |
| frameView.scrollBy(IntSize(10000, 10000), UserScroll); |
| EXPECT_POINT_EQ(expectedMaxFrameViewScrollOffset(visualViewport, frameView), |
| frameView.scrollPosition()); |
| } |
| |
| // Tests that a scroll all the way to the bottom of the page, while hiding the |
| // top controls doesn't cause a clamp in the viewport scroll offset when the top |
| // controls initiated resize occurs. |
| TEST_F(VisualViewportTest, TestTopControlsAdjustmentAndResize) { |
| int topControlsHeight = 20; |
| int visualViewportHeight = 450; |
| int layoutViewportHeight = 900; |
| float pageScale = 2; |
| float minPageScale = 0.5; |
| |
| initializeWithAndroidSettings(); |
| |
| // Initialize with top controls showing and shrinking the Blink size. |
| webViewImpl()->resizeWithTopControls( |
| WebSize(500, visualViewportHeight - topControlsHeight), 20, true); |
| webViewImpl()->topControls().setShownRatio(1); |
| |
| registerMockedHttpURLLoad("content-width-1000.html"); |
| navigateTo(m_baseURL + "content-width-1000.html"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| |
| visualViewport.setScale(pageScale); |
| EXPECT_SIZE_EQ( |
| IntSize(250, (visualViewportHeight - topControlsHeight) / pageScale), |
| visualViewport.visibleRect().size()); |
| EXPECT_SIZE_EQ( |
| IntSize(1000, layoutViewportHeight - topControlsHeight / minPageScale), |
| frameView.frameRect().size()); |
| EXPECT_SIZE_EQ(IntSize(500, visualViewportHeight - topControlsHeight), |
| visualViewport.size()); |
| |
| // Scroll all the way to the bottom, hiding the top controls in the process. |
| visualViewport.move(FloatPoint(10000, 10000)); |
| frameView.scrollBy(IntSize(10000, 10000), UserScroll); |
| webViewImpl()->topControls().setShownRatio(0); |
| |
| EXPECT_SIZE_EQ(IntSize(250, visualViewportHeight / pageScale), |
| visualViewport.visibleRect().size()); |
| |
| IntPoint frameViewExpected = |
| expectedMaxFrameViewScrollOffset(visualViewport, frameView); |
| FloatPoint visualViewportExpected = |
| FloatPoint(750, layoutViewportHeight - visualViewportHeight / pageScale); |
| |
| EXPECT_POINT_EQ(visualViewportExpected, visualViewport.location()); |
| EXPECT_POINT_EQ(frameViewExpected, frameView.scrollPosition()); |
| |
| FloatPoint totalExpected = visualViewportExpected + frameViewExpected; |
| |
| // Resize the widget to match the top controls adjustment. Ensure that the |
| // total offset (i.e. what the user sees) doesn't change because of clamping |
| // the offsets to valid values. |
| webViewImpl()->resizeWithTopControls(WebSize(500, visualViewportHeight), 20, |
| false); |
| |
| EXPECT_SIZE_EQ(IntSize(500, visualViewportHeight), visualViewport.size()); |
| EXPECT_SIZE_EQ(IntSize(250, visualViewportHeight / pageScale), |
| visualViewport.visibleRect().size()); |
| EXPECT_SIZE_EQ(IntSize(1000, layoutViewportHeight), |
| frameView.frameRect().size()); |
| EXPECT_POINT_EQ(totalExpected, |
| frameView.scrollPosition() + visualViewport.location()); |
| } |
| |
| // Tests that a scroll all the way to the bottom while showing the top controls |
| // doesn't cause a clamp to the viewport scroll offset when the top controls |
| // initiated resize occurs. |
| TEST_F(VisualViewportTest, TestTopControlsShrinkAdjustmentAndResize) { |
| int topControlsHeight = 20; |
| int visualViewportHeight = 500; |
| int layoutViewportHeight = 1000; |
| int contentHeight = 2000; |
| float pageScale = 2; |
| float minPageScale = 0.5; |
| |
| initializeWithAndroidSettings(); |
| |
| // Initialize with top controls hidden and not shrinking the Blink size. |
| webViewImpl()->resizeWithTopControls(IntSize(500, visualViewportHeight), 20, |
| false); |
| webViewImpl()->topControls().setShownRatio(0); |
| |
| registerMockedHttpURLLoad("content-width-1000.html"); |
| navigateTo(m_baseURL + "content-width-1000.html"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| |
| visualViewport.setScale(pageScale); |
| EXPECT_SIZE_EQ(IntSize(250, visualViewportHeight / pageScale), |
| visualViewport.visibleRect().size()); |
| EXPECT_SIZE_EQ(IntSize(1000, layoutViewportHeight), |
| frameView.frameRect().size()); |
| EXPECT_SIZE_EQ(IntSize(500, visualViewportHeight), visualViewport.size()); |
| |
| // Scroll all the way to the bottom, showing the the top controls in the |
| // process. (This could happen via window.scrollTo during a scroll, for |
| // example). |
| webViewImpl()->topControls().setShownRatio(1); |
| visualViewport.move(FloatPoint(10000, 10000)); |
| frameView.scrollBy(IntSize(10000, 10000), UserScroll); |
| |
| EXPECT_SIZE_EQ( |
| IntSize(250, (visualViewportHeight - topControlsHeight) / pageScale), |
| visualViewport.visibleRect().size()); |
| |
| IntPoint frameViewExpected = IntPoint( |
| 0, contentHeight - |
| (layoutViewportHeight - topControlsHeight / minPageScale)); |
| FloatPoint visualViewportExpected = |
| FloatPoint(750, (layoutViewportHeight - topControlsHeight / minPageScale - |
| visualViewport.visibleRect().height())); |
| |
| EXPECT_POINT_EQ(visualViewportExpected, visualViewport.location()); |
| EXPECT_POINT_EQ(frameViewExpected, frameView.scrollPosition()); |
| |
| FloatPoint totalExpected = visualViewportExpected + frameViewExpected; |
| |
| // Resize the widget to match the top controls adjustment. Ensure that the |
| // total offset (i.e. what the user sees) doesn't change because of clamping |
| // the offsets to valid values. |
| webViewImpl()->resizeWithTopControls( |
| WebSize(500, visualViewportHeight - topControlsHeight), 20, true); |
| |
| EXPECT_SIZE_EQ(IntSize(500, visualViewportHeight - topControlsHeight), |
| visualViewport.size()); |
| EXPECT_SIZE_EQ( |
| IntSize(250, (visualViewportHeight - topControlsHeight) / pageScale), |
| visualViewport.visibleRect().size()); |
| EXPECT_SIZE_EQ( |
| IntSize(1000, layoutViewportHeight - topControlsHeight / minPageScale), |
| frameView.frameRect().size()); |
| EXPECT_POINT_EQ(totalExpected, |
| frameView.scrollPosition() + visualViewport.location()); |
| } |
| |
| // Tests that a resize due to top controls hiding doesn't incorrectly clamp the |
| // main frame's scroll offset. crbug.com/428193. |
| TEST_F(VisualViewportTest, TestTopControlHidingResizeDoesntClampMainFrame) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resizeWithTopControls(webViewImpl()->size(), 500, false); |
| webViewImpl()->applyViewportDeltas(WebFloatSize(), WebFloatSize(), |
| WebFloatSize(), 1, 1); |
| webViewImpl()->resizeWithTopControls(WebSize(1000, 1000), 500, true); |
| |
| registerMockedHttpURLLoad("content-width-1000.html"); |
| navigateTo(m_baseURL + "content-width-1000.html"); |
| |
| // Scroll the FrameView to the bottom of the page but "hide" the top controls |
| // on the compositor side so the max scroll position should account for the |
| // full viewport height. |
| webViewImpl()->applyViewportDeltas(WebFloatSize(), WebFloatSize(), |
| WebFloatSize(), 1, -1); |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| frameView.setScrollPosition(IntPoint(0, 10000), ProgrammaticScroll); |
| EXPECT_EQ(500, frameView.scrollPositionDouble().y()); |
| |
| // Now send the resize, make sure the scroll offset doesn't change. |
| webViewImpl()->resizeWithTopControls(WebSize(1000, 1500), 500, false); |
| EXPECT_EQ(500, frameView.scrollPositionDouble().y()); |
| } |
| |
| 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_F(VisualViewportTest, |
| TestScrollbarsNotAttachedWhenHideScrollbarsSettingIsTrue) { |
| initializeWithAndroidSettings(configureHiddenScrollbarsSettings); |
| webViewImpl()->resize(IntSize(100, 150)); |
| navigateTo("about:blank"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| EXPECT_FALSE(visualViewport.layerForHorizontalScrollbar()->parent()); |
| EXPECT_FALSE(visualViewport.layerForVerticalScrollbar()->parent()); |
| } |
| |
| // Tests that scrollbar layers are attached to the inner viewport container |
| // layer when hideScrollbars WebSetting is false. |
| TEST_F(VisualViewportTest, |
| TestScrollbarsAttachedWhenHideScrollbarsSettingIsFalse) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(100, 150)); |
| navigateTo("about:blank"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| EXPECT_TRUE(visualViewport.layerForHorizontalScrollbar()->parent()); |
| EXPECT_TRUE(visualViewport.layerForVerticalScrollbar()->parent()); |
| } |
| |
| // Tests that the layout viewport's scroll layer bounds are updated in a |
| // compositing change update. crbug.com/423188. |
| TEST_P(ParameterizedVisualViewportTest, |
| TestChangingContentSizeAffectsScrollBounds) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(100, 150)); |
| |
| registerMockedHttpURLLoad("content-width-1000.html"); |
| navigateTo(m_baseURL + "content-width-1000.html"); |
| |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| WebLayer* scrollLayer = frameView.layerForScrolling()->platformLayer(); |
| |
| webViewImpl()->mainFrame()->executeScript( |
| WebScriptSource("var content = document.getElementById(\"content\");" |
| "content.style.width = \"1500px\";" |
| "content.style.height = \"2400px\";")); |
| frameView.updateAllLifecyclePhases(); |
| |
| EXPECT_SIZE_EQ(IntSize(1500, 2400), IntSize(scrollLayer->bounds())); |
| } |
| |
| // Tests that resizing the visual viepwort keeps its bounds within the outer |
| // viewport. |
| TEST_P(ParameterizedVisualViewportTest, |
| ResizeVisualViewportStaysWithinOuterViewport) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(100, 200)); |
| |
| navigateTo("about:blank"); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| webViewImpl()->resizeVisualViewport(IntSize(100, 100)); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| visualViewport.move(FloatPoint(0, 100)); |
| |
| EXPECT_EQ(100, visualViewport.location().y()); |
| |
| webViewImpl()->resizeVisualViewport(IntSize(100, 200)); |
| |
| EXPECT_EQ(0, visualViewport.location().y()); |
| } |
| |
| TEST_P(ParameterizedVisualViewportTest, |
| ElementBoundsInViewportSpaceAccountsForViewport) { |
| initializeWithAndroidSettings(); |
| |
| webViewImpl()->resize(IntSize(500, 800)); |
| |
| registerMockedHttpURLLoad("pinch-viewport-input-field.html"); |
| navigateTo(m_baseURL + "pinch-viewport-input-field.html"); |
| |
| webViewImpl()->setInitialFocus(false); |
| Element* inputElement = webViewImpl()->focusedElement(); |
| |
| IntRect bounds = inputElement->layoutObject()->absoluteBoundingBoxRect(); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| IntPoint scrollDelta(250, 400); |
| visualViewport.setScale(2); |
| visualViewport.setLocation(scrollDelta); |
| |
| const IntRect boundsInViewport = inputElement->boundsInViewport(); |
| IntRect expectedBounds = bounds; |
| expectedBounds.scale(2.f); |
| IntPoint expectedScrollDelta = scrollDelta; |
| expectedScrollDelta.scale(2.f, 2.f); |
| |
| EXPECT_POINT_EQ(IntPoint(expectedBounds.location() - expectedScrollDelta), |
| boundsInViewport.location()); |
| EXPECT_SIZE_EQ(expectedBounds.size(), boundsInViewport.size()); |
| } |
| |
| TEST_P(ParameterizedVisualViewportTest, ElementVisibleBoundsInVisualViewport) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(640, 1080)); |
| registerMockedHttpURLLoad("viewport-select.html"); |
| navigateTo(m_baseURL + "viewport-select.html"); |
| |
| ASSERT_EQ(2.0f, webViewImpl()->pageScaleFactor()); |
| webViewImpl()->setInitialFocus(false); |
| Element* element = webViewImpl()->focusedElement(); |
| EXPECT_FALSE(element->visibleBoundsInVisualViewport().isEmpty()); |
| |
| webViewImpl()->setPageScaleFactor(4.0); |
| EXPECT_TRUE(element->visibleBoundsInVisualViewport().isEmpty()); |
| } |
| |
| // Test that the various window.scroll and document.body.scroll properties and |
| // methods work unchanged from the pre-virtual viewport mode. |
| TEST_P(ParameterizedVisualViewportTest, |
| bodyAndWindowScrollPropertiesAccountForViewport) { |
| initializeWithAndroidSettings(); |
| |
| webViewImpl()->resize(IntSize(200, 300)); |
| |
| // Load page with no main frame scrolling. |
| registerMockedHttpURLLoad("200-by-300-viewport.html"); |
| navigateTo(m_baseURL + "200-by-300-viewport.html"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| visualViewport.setScale(2); |
| |
| // Chrome's quirky behavior regarding viewport scrolling means we treat the |
| // body element as the viewport and don't apply scrolling to the HTML element. |
| RuntimeEnabledFeatures::setScrollTopLeftInteropEnabled(false); |
| |
| LocalDOMWindow* window = |
| webViewImpl()->mainFrameImpl()->frame()->localDOMWindow(); |
| window->scrollTo(100, 150); |
| EXPECT_EQ(100, window->scrollX()); |
| EXPECT_EQ(150, window->scrollY()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(100, 150), visualViewport.location()); |
| |
| HTMLElement* body = toHTMLBodyElement(window->document()->body()); |
| body->setScrollLeft(50); |
| body->setScrollTop(130); |
| EXPECT_EQ(50, body->scrollLeft()); |
| EXPECT_EQ(130, body->scrollTop()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(50, 130), visualViewport.location()); |
| |
| HTMLElement* documentElement = |
| toHTMLElement(window->document()->documentElement()); |
| documentElement->setScrollLeft(40); |
| documentElement->setScrollTop(50); |
| EXPECT_EQ(0, documentElement->scrollLeft()); |
| EXPECT_EQ(0, documentElement->scrollTop()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(50, 130), visualViewport.location()); |
| |
| visualViewport.setLocation(FloatPoint(10, 20)); |
| EXPECT_EQ(10, body->scrollLeft()); |
| EXPECT_EQ(20, body->scrollTop()); |
| EXPECT_EQ(0, documentElement->scrollLeft()); |
| EXPECT_EQ(0, documentElement->scrollTop()); |
| EXPECT_EQ(10, window->scrollX()); |
| EXPECT_EQ(20, window->scrollY()); |
| |
| // Turning on the standards-compliant viewport scrolling impl should make the |
| // document element the viewport and not body. |
| RuntimeEnabledFeatures::setScrollTopLeftInteropEnabled(true); |
| |
| window->scrollTo(100, 150); |
| EXPECT_EQ(100, window->scrollX()); |
| EXPECT_EQ(150, window->scrollY()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(100, 150), visualViewport.location()); |
| |
| body->setScrollLeft(50); |
| body->setScrollTop(130); |
| EXPECT_EQ(0, body->scrollLeft()); |
| EXPECT_EQ(0, body->scrollTop()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(100, 150), visualViewport.location()); |
| |
| documentElement->setScrollLeft(40); |
| documentElement->setScrollTop(50); |
| EXPECT_EQ(40, documentElement->scrollLeft()); |
| EXPECT_EQ(50, documentElement->scrollTop()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(40, 50), visualViewport.location()); |
| |
| visualViewport.setLocation(FloatPoint(10, 20)); |
| EXPECT_EQ(0, body->scrollLeft()); |
| EXPECT_EQ(0, body->scrollTop()); |
| EXPECT_EQ(10, documentElement->scrollLeft()); |
| EXPECT_EQ(20, documentElement->scrollTop()); |
| EXPECT_EQ(10, window->scrollX()); |
| EXPECT_EQ(20, window->scrollY()); |
| } |
| |
| // 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(ParameterizedVisualViewportTest, TestMainFrameInitializationSizing) { |
| initializeWithAndroidSettings(); |
| |
| webViewImpl()->resize(IntSize(100, 200)); |
| |
| registerMockedHttpURLLoad("content-width-1000-min-scale.html"); |
| navigateTo(m_baseURL + "content-width-1000-min-scale.html"); |
| |
| WebLocalFrameImpl* localFrame = webViewImpl()->mainFrameImpl(); |
| // The shutdown() calls are a hack to prevent this test from violating |
| // invariants about frame state during navigation/detach. |
| localFrame->frame()->document()->shutdown(); |
| localFrame->createFrameView(); |
| |
| FrameView& frameView = *localFrame->frameView(); |
| EXPECT_SIZE_EQ(IntSize(200, 400), frameView.frameRect().size()); |
| frameView.dispose(); |
| } |
| |
| // Tests that the maximum scroll offset of the viewport can be fractional. |
| TEST_P(ParameterizedVisualViewportTest, FractionalMaxScrollOffset) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(101, 201)); |
| navigateTo("about:blank"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| ScrollableArea* scrollableArea = &visualViewport; |
| |
| webViewImpl()->setPageScaleFactor(1.0); |
| EXPECT_FLOAT_POINT_EQ(DoublePoint(), |
| scrollableArea->maximumScrollPositionDouble()); |
| |
| webViewImpl()->setPageScaleFactor(2); |
| EXPECT_FLOAT_POINT_EQ(DoublePoint(101. / 2., 201. / 2.), |
| scrollableArea->maximumScrollPositionDouble()); |
| } |
| |
| // 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(ParameterizedVisualViewportTest, SlowScrollAfterImplScroll) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(800, 600)); |
| navigateTo("about:blank"); |
| |
| VisualViewport& visualViewport = |
| frame()->page()->frameHost().visualViewport(); |
| |
| // Apply some scroll and scale from the impl-side. |
| webViewImpl()->applyViewportDeltas(WebFloatSize(300, 200), WebFloatSize(0, 0), |
| WebFloatSize(0, 0), 2, 0); |
| |
| EXPECT_POINT_EQ(FloatPoint(300, 200), visualViewport.location()); |
| |
| // Send a scroll event on the main thread path. |
| PlatformGestureEvent gsu(PlatformEvent::GestureScrollUpdate, IntPoint(0, 0), |
| IntPoint(0, 0), IntSize(5, 5), 0, |
| PlatformEvent::NoModifiers, |
| PlatformGestureSourceTouchpad); |
| gsu.setScrollGestureData(-50, -60, ScrollByPrecisePixel, 1, 1, |
| ScrollInertialPhaseUnknown, false, |
| -1 /* null plugin id */); |
| |
| frame()->eventHandler().handleGestureEvent(gsu); |
| |
| // The scroll sent from the impl-side must not be overwritten. |
| EXPECT_POINT_EQ(FloatPoint(350, 260), visualViewport.location()); |
| } |
| |
| static void accessibilitySettings(WebSettings* settings) { |
| VisualViewportTest::configureSettings(settings); |
| settings->setAccessibilityEnabled(true); |
| } |
| |
| TEST_P(ParameterizedVisualViewportTest, AccessibilityHitTestWhileZoomedIn) { |
| initializeWithDesktopSettings(accessibilitySettings); |
| |
| registerMockedHttpURLLoad("hit-test.html"); |
| navigateTo(m_baseURL + "hit-test.html"); |
| |
| webViewImpl()->resize(IntSize(500, 500)); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| WebDocument webDoc = webViewImpl()->mainFrame()->document(); |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| |
| webViewImpl()->setPageScaleFactor(2); |
| webViewImpl()->setVisualViewportOffset(WebFloatPoint(200, 230)); |
| frameView.layoutViewportScrollableArea()->setScrollPosition( |
| DoublePoint(400, 1100), ProgrammaticScroll); |
| |
| // 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 = |
| webDoc.accessibilityObject().hitTest(WebPoint(154, 165)); |
| WebAXNameFrom nameFrom; |
| WebVector<WebAXObject> nameObjects; |
| EXPECT_EQ(std::string("Target4"), hitNode.name(nameFrom, nameObjects).utf8()); |
| } |
| |
| // Tests that the maximum scroll offset of the viewport can be fractional. |
| TEST_P(ParameterizedVisualViewportTest, TestCoordinateTransforms) { |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(800, 600)); |
| registerMockedHttpURLLoad("content-width-1000.html"); |
| navigateTo(m_baseURL + "content-width-1000.html"); |
| |
| VisualViewport& visualViewport = |
| webViewImpl()->page()->frameHost().visualViewport(); |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| |
| // At scale = 1 the transform should be a no-op. |
| visualViewport.setScale(1); |
| EXPECT_FLOAT_POINT_EQ( |
| FloatPoint(314, 273), |
| visualViewport.viewportToRootFrame(FloatPoint(314, 273))); |
| EXPECT_FLOAT_POINT_EQ( |
| FloatPoint(314, 273), |
| visualViewport.rootFrameToViewport(FloatPoint(314, 273))); |
| |
| // At scale = 2. |
| visualViewport.setScale(2); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(55, 75), visualViewport.viewportToRootFrame( |
| FloatPoint(110, 150))); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(110, 150), |
| visualViewport.rootFrameToViewport(FloatPoint(55, 75))); |
| |
| // At scale = 2 and with the visual viewport offset. |
| visualViewport.setLocation(FloatPoint(10, 12)); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(50, 62), visualViewport.viewportToRootFrame( |
| FloatPoint(80, 100))); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(80, 100), |
| visualViewport.rootFrameToViewport(FloatPoint(50, 62))); |
| |
| // Test points that will cause non-integer values. |
| EXPECT_FLOAT_POINT_EQ( |
| FloatPoint(50.5, 62.4), |
| visualViewport.viewportToRootFrame(FloatPoint(81, 100.8))); |
| EXPECT_FLOAT_POINT_EQ( |
| FloatPoint(81, 100.8), |
| visualViewport.rootFrameToViewport(FloatPoint(50.5, 62.4))); |
| |
| // Scrolling the main frame should have no effect. |
| frameView.layoutViewportScrollableArea()->setScrollPosition( |
| DoublePoint(100, 120), ProgrammaticScroll); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(50, 62), visualViewport.viewportToRootFrame( |
| FloatPoint(80, 100))); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(80, 100), |
| visualViewport.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(ParameterizedVisualViewportTest, WindowDimensionsOnLoad) { |
| initializeWithAndroidSettings(); |
| registerMockedHttpURLLoad("window_dimensions.html"); |
| webViewImpl()->resize(IntSize(800, 600)); |
| navigateTo(m_baseURL + "window_dimensions.html"); |
| |
| Element* output = frame()->document()->getElementById("output"); |
| DCHECK(output); |
| EXPECT_EQ(std::string("1600x1200"), |
| std::string(output->innerHTML().ascii().data())); |
| } |
| |
| // 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(ParameterizedVisualViewportTest, WindowDimensionsOnLoadWideContent) { |
| initializeWithAndroidSettings(); |
| registerMockedHttpURLLoad("window_dimensions_wide_div.html"); |
| webViewImpl()->resize(IntSize(800, 600)); |
| navigateTo(m_baseURL + "window_dimensions_wide_div.html"); |
| |
| Element* output = frame()->document()->getElementById("output"); |
| DCHECK(output); |
| EXPECT_EQ(std::string("2000x1500"), |
| std::string(output->innerHTML().ascii().data())); |
| } |
| |
| TEST_P(ParameterizedVisualViewportTest, |
| PinchZoomGestureScrollsVisualViewportOnly) { |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(100, 100)); |
| |
| registerMockedHttpURLLoad("200-by-800-viewport.html"); |
| navigateTo(m_baseURL + "200-by-800-viewport.html"); |
| |
| WebGestureEvent pinchUpdate; |
| pinchUpdate.type = WebInputEvent::GesturePinchUpdate; |
| pinchUpdate.sourceDevice = WebGestureDeviceTouchpad; |
| pinchUpdate.x = 100; |
| pinchUpdate.y = 100; |
| pinchUpdate.data.pinchUpdate.scale = 2; |
| pinchUpdate.data.pinchUpdate.zoomDisabled = false; |
| |
| webViewImpl()->handleInputEvent(pinchUpdate); |
| |
| VisualViewport& visualViewport = |
| webViewImpl()->page()->frameHost().visualViewport(); |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(50, 50), visualViewport.location()); |
| EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), frameView.scrollPositionDouble()); |
| } |
| |
| TEST_P(ParameterizedVisualViewportTest, ResizeWithScrollAnchoring) { |
| bool wasScrollAnchoringEnabled = |
| RuntimeEnabledFeatures::scrollAnchoringEnabled(); |
| RuntimeEnabledFeatures::setScrollAnchoringEnabled(true); |
| |
| initializeWithDesktopSettings(); |
| webViewImpl()->resize(IntSize(800, 600)); |
| |
| registerMockedHttpURLLoad("icb-relative-content.html"); |
| navigateTo(m_baseURL + "icb-relative-content.html"); |
| |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| frameView.layoutViewportScrollableArea()->setScrollPosition( |
| DoublePoint(700, 500), ProgrammaticScroll); |
| |
| webViewImpl()->resize(IntSize(800, 300)); |
| EXPECT_POINT_EQ( |
| DoublePoint(700, 200), |
| frameView.layoutViewportScrollableArea()->scrollPositionDouble()); |
| |
| RuntimeEnabledFeatures::setScrollAnchoringEnabled(wasScrollAnchoringEnabled); |
| } |
| |
| // Ensure that resize anchoring as happens when top controls hide/show affects |
| // the scrollable area that's currently set as the root scroller. |
| TEST_P(ParameterizedVisualViewportTest, ResizeAnchoringWithRootScroller) { |
| bool wasRootScrollerEnabled = |
| RuntimeEnabledFeatures::setRootScrollerEnabled(); |
| RuntimeEnabledFeatures::setSetRootScrollerEnabled(true); |
| |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(800, 600)); |
| |
| registerMockedHttpURLLoad("root-scroller-div.html"); |
| navigateTo(m_baseURL + "root-scroller-div.html"); |
| |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| |
| Element* scroller = frame()->document()->getElementById("rootScroller"); |
| NonThrowableExceptionState nonThrow; |
| frame()->document()->setRootScroller(scroller, nonThrow); |
| |
| webViewImpl()->setPageScaleFactor(3.f); |
| frameView.getScrollableArea()->setScrollPosition(DoublePoint(0, 400), |
| ProgrammaticScroll); |
| |
| VisualViewport& visualViewport = |
| webViewImpl()->page()->frameHost().visualViewport(); |
| visualViewport.setScrollPosition(DoublePoint(0, 400), ProgrammaticScroll); |
| |
| webViewImpl()->resize(IntSize(800, 500)); |
| |
| EXPECT_POINT_EQ( |
| DoublePoint(), |
| frameView.layoutViewportScrollableArea()->scrollPositionDouble()); |
| |
| RuntimeEnabledFeatures::setSetRootScrollerEnabled(wasRootScrollerEnabled); |
| } |
| |
| // Ensure that resize anchoring as happens when the device is rotated affects |
| // the scrollable area that's currently set as the root scroller. |
| TEST_P(ParameterizedVisualViewportTest, RotationAnchoringWithRootScroller) { |
| bool wasRootScrollerEnabled = |
| RuntimeEnabledFeatures::setRootScrollerEnabled(); |
| RuntimeEnabledFeatures::setSetRootScrollerEnabled(true); |
| |
| initializeWithAndroidSettings(); |
| webViewImpl()->resize(IntSize(800, 600)); |
| |
| registerMockedHttpURLLoad("root-scroller-div.html"); |
| navigateTo(m_baseURL + "root-scroller-div.html"); |
| |
| FrameView& frameView = *webViewImpl()->mainFrameImpl()->frameView(); |
| |
| Element* scroller = frame()->document()->getElementById("rootScroller"); |
| NonThrowableExceptionState nonThrow; |
| frame()->document()->setRootScroller(scroller, nonThrow); |
| webViewImpl()->updateAllLifecyclePhases(); |
| |
| scroller->setScrollTop(800); |
| |
| webViewImpl()->resize(IntSize(600, 800)); |
| |
| EXPECT_POINT_EQ( |
| DoublePoint(), |
| frameView.layoutViewportScrollableArea()->scrollPositionDouble()); |
| EXPECT_EQ(600, scroller->scrollTop()); |
| |
| RuntimeEnabledFeatures::setSetRootScrollerEnabled(wasRootScrollerEnabled); |
| } |
| |
| } // namespace |