Index: third_party/WebKit/Source/web/tests/RootScrollerTest.cpp |
diff --git a/third_party/WebKit/Source/web/tests/RootScrollerTest.cpp b/third_party/WebKit/Source/web/tests/RootScrollerTest.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..70f4f792caa064a522635e0a4ffb992ba30ee37b |
--- /dev/null |
+++ b/third_party/WebKit/Source/web/tests/RootScrollerTest.cpp |
@@ -0,0 +1,489 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "core/page/scrolling/RootScroller.h" |
+ |
+#include "core/frame/FrameHost.h" |
+#include "core/frame/FrameView.h" |
+#include "core/frame/TopControls.h" |
+#include "core/html/HTMLFrameOwnerElement.h" |
+#include "core/page/Page.h" |
+#include "platform/testing/URLTestHelpers.h" |
+#include "platform/testing/UnitTestHelpers.h" |
+#include "public/platform/Platform.h" |
+#include "public/platform/WebURLLoaderMockFactory.h" |
+#include "public/web/WebCache.h" |
+#include "public/web/WebConsoleMessage.h" |
+#include "public/web/WebScriptSource.h" |
+#include "public/web/WebSettings.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "web/WebLocalFrameImpl.h" |
+#include "web/tests/FrameTestHelpers.h" |
+#include "wtf/Vector.h" |
+ |
+using blink::testing::runPendingTasks; |
+using testing::Mock; |
+ |
+namespace blink { |
+ |
+namespace { |
+ |
+class RootScrollerTestWebViewClient : public FrameTestHelpers::TestWebViewClient { |
+public: |
+ MOCK_METHOD4(didOverscroll, void(const WebFloatSize&, const WebFloatSize&, const WebFloatPoint&, const WebFloatSize&)); |
+}; |
+ |
+class RootScrollerTest : public ::testing::Test { |
+public: |
+ RootScrollerTest() |
+ : m_baseURL("http://www.test.com/") |
+ { |
+ registerMockedHttpURLLoad("overflow-scrolling.html"); |
+ registerMockedHttpURLLoad("root-scroller.html"); |
+ registerMockedHttpURLLoad("root-scroller-iframe.html"); |
+ registerMockedHttpURLLoad("root-scroller-child.html"); |
+ } |
+ |
+ ~RootScrollerTest() override |
+ { |
+ m_featuresBackup.restore(); |
+ Platform::current()->getURLLoaderMockFactory()->unregisterAllURLs(); |
+ WebCache::clear(); |
+ } |
+ |
+ WebViewImpl* initialize(const std::string& pageName) |
+ { |
+ RuntimeEnabledFeatures::setSetRootScrollerEnabled(true); |
+ |
+ // Load a page with large body and set viewport size to 400x400 to |
+ // ensure main frame is scrollable. |
+ m_helper.initializeAndLoad( |
+ m_baseURL + pageName, true, 0, &m_client, &configureSettings); |
+ |
+ // Initialize top controls to be shown. |
+ webViewImpl()->resizeWithTopControls(IntSize(400, 400), 50, true); |
+ webViewImpl()->topControls().setShownRatio(1); |
+ |
+ mainFrameView()->updateAllLifecyclePhases(); |
+ |
+ return webViewImpl(); |
+ } |
+ |
+ static void configureSettings(WebSettings* settings) |
+ { |
+ settings->setJavaScriptEnabled(true); |
+ settings->setAcceleratedCompositingEnabled(true); |
+ settings->setPreferCompositingToLCDTextEnabled(true); |
+ // Android settings. |
+ settings->setViewportEnabled(true); |
+ settings->setViewportMetaEnabled(true); |
+ settings->setShrinksViewportContentToFit(true); |
+ settings->setMainFrameResizesAreOrientationChanges(true); |
+ } |
+ |
+ void registerMockedHttpURLLoad(const std::string& fileName) |
+ { |
+ URLTestHelpers::registerMockedURLFromBaseURL( |
+ WebString::fromUTF8(m_baseURL.c_str()), |
+ WebString::fromUTF8(fileName.c_str())); |
+ } |
+ |
+ void executeScript(const WebString& code) |
+ { |
+ mainWebFrame()->executeScript(WebScriptSource(code)); |
+ mainWebFrame()->view()->updateAllLifecyclePhases(); |
+ runPendingTasks(); |
+ } |
+ |
+ WebGestureEvent generateEvent( |
+ WebInputEvent::Type type, int deltaX = 0, int deltaY = 0) |
+ { |
+ WebGestureEvent event; |
+ event.type = type; |
+ event.sourceDevice = WebGestureDeviceTouchscreen; |
+ event.x = 100; |
+ event.y = 100; |
+ if (type == WebInputEvent::GestureScrollUpdate) { |
+ event.data.scrollUpdate.deltaX = deltaX; |
+ event.data.scrollUpdate.deltaY = deltaY; |
+ } |
+ return event; |
+ } |
+ |
+ void verticalScroll(float deltaY) |
+ { |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollBegin)); |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollUpdate, 0, -deltaY)); |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollEnd)); |
+ } |
+ |
+ WebViewImpl* webViewImpl() const |
+ { |
+ return m_helper.webViewImpl(); |
+ } |
+ |
+ FrameHost& frameHost() const |
+ { |
+ return m_helper.webViewImpl()->page()->frameHost(); |
+ } |
+ |
+ LocalFrame* mainFrame() const |
+ { |
+ return toWebLocalFrameImpl(webViewImpl()->mainFrame())->frame(); |
+ } |
+ |
+ WebLocalFrame* mainWebFrame() const |
+ { |
+ return toWebLocalFrameImpl(webViewImpl()->mainFrame()); |
+ } |
+ |
+ FrameView* mainFrameView() const |
+ { |
+ return webViewImpl()->mainFrameImpl()->frame()->view(); |
+ } |
+ |
+ VisualViewport& visualViewport() const |
+ { |
+ return frameHost().visualViewport(); |
+ } |
+ |
+ RootScroller& rootScroller() const |
+ { |
+ return *frameHost().rootScroller(); |
+ } |
+ |
+ TopControls& topControls() const |
+ { |
+ return frameHost().topControls(); |
+ } |
+ |
+protected: |
+ std::string m_baseURL; |
+ RootScrollerTestWebViewClient m_client; |
+ FrameTestHelpers::WebViewHelper m_helper; |
+ RuntimeEnabledFeatures::Backup m_featuresBackup; |
+}; |
+ |
+// Test that a default root scroller element is set if setRootScroller isn't |
+// called on any elements. |
+TEST_F(RootScrollerTest, TestDefaultRootScroller) |
+{ |
+ initialize("overflow-scrolling.html"); |
+ |
+ EXPECT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().get()); |
+} |
+ |
+// Tests that setting an element as the root scroller causes it to control url |
+// bar hiding and overscroll. |
+TEST_F(RootScrollerTest, TestSetRootScroller) |
+{ |
+ initialize("root-scroller.html"); |
+ |
+ Element* container = mainFrame()->document()->getElementById("container"); |
+ TrackExceptionState exceptionState; |
+ mainFrame()->document()->setRootScroller(container, exceptionState); |
+ ASSERT_EQ(container, mainFrame()->document()->rootScroller()); |
+ |
+ // Content is 1000x1000, WebView size is 400x400 so max scroll is 600px. |
+ double maximumScroll = 600; |
+ |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollBegin)); |
+ |
+ { |
+ // Scrolling over the #container DIV should cause the top controls to |
+ // hide. |
+ ASSERT_FLOAT_EQ(1, topControls().shownRatio()); |
+ webViewImpl()->handleInputEvent(generateEvent( |
+ WebInputEvent::GestureScrollUpdate, 0, -topControls().height())); |
+ ASSERT_FLOAT_EQ(0, topControls().shownRatio()); |
+ } |
+ |
+ { |
+ // Make sure we're actually scrolling the DIV and not the FrameView. |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollUpdate, 0, -100)); |
+ ASSERT_FLOAT_EQ(100, container->scrollTop()); |
+ ASSERT_FLOAT_EQ(0, mainFrameView()->scrollPositionDouble().y()); |
+ } |
+ |
+ { |
+ // Scroll 50 pixels past the end. Ensure we report the 50 pixels as |
+ // overscroll. |
+ EXPECT_CALL(m_client, |
+ didOverscroll( |
+ WebFloatSize(0, 50), |
+ WebFloatSize(0, 50), |
+ WebFloatPoint(100, 100), |
+ WebFloatSize())); |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollUpdate, 0, -550)); |
+ ASSERT_FLOAT_EQ(maximumScroll, container->scrollTop()); |
+ ASSERT_FLOAT_EQ(0, mainFrameView()->scrollPositionDouble().y()); |
+ Mock::VerifyAndClearExpectations(&m_client); |
+ } |
+ |
+ { |
+ // Continue the gesture overscroll. |
+ EXPECT_CALL(m_client, |
+ didOverscroll( |
+ WebFloatSize(0, 20), |
+ WebFloatSize(0, 70), |
+ WebFloatPoint(100, 100), |
+ WebFloatSize())); |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollUpdate, 0, -20)); |
+ ASSERT_FLOAT_EQ(maximumScroll, container->scrollTop()); |
+ ASSERT_FLOAT_EQ(0, mainFrameView()->scrollPositionDouble().y()); |
+ Mock::VerifyAndClearExpectations(&m_client); |
+ } |
+ |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollEnd)); |
+ |
+ { |
+ // Make sure a new gesture scroll still won't scroll the frameview and |
+ // overscrolls. |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollBegin)); |
+ |
+ EXPECT_CALL(m_client, |
+ didOverscroll( |
+ WebFloatSize(0, 30), |
+ WebFloatSize(0, 30), |
+ WebFloatPoint(100, 100), |
+ WebFloatSize())); |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollUpdate, 0, -30)); |
+ ASSERT_FLOAT_EQ(maximumScroll, container->scrollTop()); |
+ ASSERT_FLOAT_EQ(0, mainFrameView()->scrollPositionDouble().y()); |
+ Mock::VerifyAndClearExpectations(&m_client); |
+ |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollEnd)); |
+ } |
+ |
+ { |
+ // Scrolling up should show the top controls. |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollBegin)); |
+ |
+ ASSERT_FLOAT_EQ(0, topControls().shownRatio()); |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollUpdate, 0, 30)); |
+ ASSERT_FLOAT_EQ(0.6, topControls().shownRatio()); |
+ |
+ webViewImpl()->handleInputEvent( |
+ generateEvent(WebInputEvent::GestureScrollEnd)); |
+ } |
+} |
+ |
+// Tests that removing the element that is the root scroller from the DOM tree |
+// resets the default element to be the root scroller. |
+TEST_F(RootScrollerTest, TestRemoveRootScrollerFromDom) |
+{ |
+ initialize("root-scroller.html"); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().get()); |
+ |
+ Element* container = mainFrame()->document()->getElementById("container"); |
+ TrackExceptionState exceptionState; |
+ mainFrame()->document()->setRootScroller(container, exceptionState); |
+ |
+ ASSERT_EQ(container, mainFrame()->document()->rootScroller()); |
+ |
+ mainFrame()->document()->body()->removeChild(container); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ mainFrame()->document()->rootScroller()); |
+} |
+ |
+// Tests that setting an element that isn't a valid scroller as the root |
+// scroller fails and doesn't change the current root scroller. |
+TEST_F(RootScrollerTest, TestSetRootScrollerOnInvalidElement) |
+{ |
+ initialize("root-scroller.html"); |
+ |
+ { |
+ // Set to a non-block element. Should be rejected and a console message |
+ // logged. |
+ Element* element = mainFrame()->document()->getElementById("nonBlock"); |
+ TrackExceptionState exceptionState; |
+ mainFrame()->document()->setRootScroller(element, exceptionState); |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ mainFrame()->document()->rootScroller()); |
+ EXPECT_TRUE(exceptionState.hadException()); |
+ } |
+ |
+ { |
+ // Set to an element with no size. |
+ Element* element = mainFrame()->document()->getElementById("empty"); |
+ TrackExceptionState exceptionState; |
+ mainFrame()->document()->setRootScroller(element, exceptionState); |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ mainFrame()->document()->rootScroller()); |
+ EXPECT_TRUE(exceptionState.hadException()); |
+ } |
+} |
+ |
+// Test that the root scroller resets to the default element when the current |
+// root scroller element becomes invalid as a scroller. |
+TEST_F(RootScrollerTest, TestRootScrollerBecomesInvalid) |
+{ |
+ initialize("root-scroller.html"); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().get()); |
+ |
+ Element* container = mainFrame()->document()->getElementById("container"); |
+ TrackExceptionState exceptionState; |
+ mainFrame()->document()->setRootScroller(container, exceptionState); |
+ |
+ ASSERT_EQ(container, mainFrame()->document()->rootScroller()); |
+ |
+ executeScript( |
+ "document.querySelector('#container').style.display = 'inline'"); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ mainFrame()->document()->rootScroller()); |
+} |
+ |
+// Tests that setting the root scroller of the top codument to an element that |
+// belongs to a nested document fails. |
+TEST_F(RootScrollerTest, TestSetRootScrollerOnElementInIframe) |
+{ |
+ initialize("root-scroller-iframe.html"); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().get()); |
+ |
+ { |
+ // Trying to set an element from a nested document should fail. |
+ HTMLFrameOwnerElement* iframe = toHTMLFrameOwnerElement( |
+ mainFrame()->document()->getElementById("iframe")); |
+ Element* innerContainer = |
+ iframe->contentDocument()->getElementById("container"); |
+ |
+ TrackExceptionState exceptionState; |
+ mainFrame()->document()->setRootScroller( |
+ innerContainer, |
+ exceptionState); |
+ EXPECT_TRUE(exceptionState.hadException()); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().get()); |
+ } |
+ |
+ { |
+ // Setting the iframe itself, however, should work. |
+ HTMLFrameOwnerElement* iframe = toHTMLFrameOwnerElement( |
+ mainFrame()->document()->getElementById("iframe")); |
+ |
+ TrackExceptionState exceptionState; |
+ mainFrame()->document()->setRootScroller(iframe, exceptionState); |
+ EXPECT_FALSE(exceptionState.hadException()); |
+ |
+ ASSERT_EQ(iframe, rootScroller().get()); |
+ } |
+} |
+ |
+// Tests that setting an otherwise valid element as the root scroller on a |
+// document within an iframe fails and getting the root scroller in the nested |
+// document returns the default element. |
+TEST_F(RootScrollerTest, TestRootScrollerWithinIframe) |
+{ |
+ initialize("root-scroller-iframe.html"); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().get()); |
+ |
+ { |
+ // Trying to set an element within nested document should fail. |
+ // rootScroller() should always return its documentElement. |
+ HTMLFrameOwnerElement* iframe = toHTMLFrameOwnerElement( |
+ mainFrame()->document()->getElementById("iframe")); |
+ |
+ ASSERT_EQ( |
+ iframe->contentDocument()->documentElement(), |
+ iframe->contentDocument()->rootScroller()); |
+ |
+ Element* innerContainer = |
+ iframe->contentDocument()->getElementById("container"); |
+ TrackExceptionState exceptionState; |
+ iframe->contentDocument()->setRootScroller( |
+ innerContainer, |
+ exceptionState); |
+ EXPECT_TRUE(exceptionState.hadException()); |
+ |
+ ASSERT_EQ( |
+ iframe->contentDocument()->documentElement(), |
+ iframe->contentDocument()->rootScroller()); |
+ } |
+} |
+ |
+// Tests that trying to set an element as the root scroller of a document inside |
+// an iframe fails when that element belongs to the parent document. |
+TEST_F(RootScrollerTest, TestSetRootScrollerOnElementFromOutsideIframe) |
+{ |
+ initialize("root-scroller-iframe.html"); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().get()); |
+ { |
+ // Try to set the the root scroller of the child document to be the |
+ // <iframe> element in the parent document. |
+ HTMLFrameOwnerElement* iframe = toHTMLFrameOwnerElement( |
+ mainFrame()->document()->getElementById("iframe")); |
+ NonThrowableExceptionState nonThrow; |
+ Element* body = |
+ mainFrame()->document()->querySelector("body", nonThrow); |
+ |
+ ASSERT_EQ( |
+ iframe->contentDocument()->documentElement(), |
+ iframe->contentDocument()->rootScroller()); |
+ |
+ TrackExceptionState exceptionState; |
+ iframe->contentDocument()->setRootScroller( |
+ iframe, |
+ exceptionState); |
+ EXPECT_TRUE(exceptionState.hadException()); |
+ |
+ ASSERT_EQ( |
+ iframe->contentDocument()->documentElement(), |
+ iframe->contentDocument()->rootScroller()); |
+ |
+ exceptionState.clearException(); |
+ |
+ // Try to set the root scroller of the child document to be the |
+ // <body> element of the parent document. |
+ iframe->contentDocument()->setRootScroller( |
+ body, |
+ exceptionState); |
+ EXPECT_TRUE(exceptionState.hadException()); |
+ |
+ ASSERT_EQ( |
+ iframe->contentDocument()->documentElement(), |
+ iframe->contentDocument()->rootScroller()); |
+ } |
+} |
+ |
+} // namespace |
+ |
+} // namespace blink |