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..fade772cc52b8f5eb5f61db26df12ce8fee9528b |
--- /dev/null |
+++ b/third_party/WebKit/Source/web/tests/RootScrollerTest.cpp |
@@ -0,0 +1,487 @@ |
+// 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 |
+ { |
+ 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); |
+ |
+ webViewImpl()->resize(IntSize(400, 400)); |
+ |
+ // initialize top controls to be shown. |
tdresser
2016/04/26 20:43:50
initialize -> Initialize
bokan
2016/04/26 23:06:23
Done.
|
+ webViewImpl()->setTopControlsHeight(50.f, 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 |
tdresser
2016/04/26 20:43:50
Add period.
bokan
2016/04/26 23:06:23
Done.
|
+ 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(); |
+ } |
+ |
+ |
tdresser
2016/04/26 20:43:50
Inconsistent spacing.
bokan
2016/04/26 23:06:23
Done.
|
+ 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; |
+}; |
+ |
+#define EXPECT_POINT_EQ(expected, actual) \ |
tdresser
2016/04/26 20:43:50
Is this superior to defining a method which return
bokan
2016/04/26 23:06:23
Actually, these turn out to be unused below so I r
|
+ do { \ |
+ EXPECT_DOUBLE_EQ((expected).x(), (actual).x()); \ |
+ EXPECT_DOUBLE_EQ((expected).y(), (actual).y()); \ |
+ } while (false) |
+ |
+#define ASSERT_POINT_EQ(expected, actual) \ |
+ do { \ |
+ ASSERT_DOUBLE_EQ((expected).x(), (actual).x()); \ |
+ ASSERT_DOUBLE_EQ((expected).y(), (actual).y()); \ |
+ } while (false) |
+ |
+ |
+// Test that a root scroller is set if setRootScroller isn't called on any |
+// elements. |
+TEST_F(RootScrollerTest, TestDefaultRootScroller) |
+{ |
+ initialize("overflow-scrolling.html"); |
+ |
+ EXPECT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().getCurrent()); |
+} |
+ |
+// 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 overscroll rather |
+ // than scrolling the FrameView. |
+ 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 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().getCurrent()); |
+ |
+ 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 the root scroller on an invalid element 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 root scroller resets to the default element when the current root |
+// scroller becomes invalid. |
+TEST_F(RootScrollerTest, TestRootScrollerBecomesInvalid) |
+{ |
+ initialize("root-scroller.html"); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().getCurrent()); |
+ |
+ 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 on an element within a iframe fails. |
+TEST_F(RootScrollerTest, TestSetRootScrollerOnElementInIframe) |
+{ |
+ initialize("root-scroller-iframe.html"); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().getCurrent()); |
+ |
+ { |
+ // 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().getCurrent()); |
+ } |
+ |
+ { |
+ // 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().getCurrent()); |
+ } |
+} |
+ |
+// Tests that setting the root scroller on a document within an iframe fails |
+// and getting the root scroller returns the default element. |
+TEST_F(RootScrollerTest, TestRootScrollerWithinIframe) |
+{ |
+ initialize("root-scroller-iframe.html"); |
+ |
+ ASSERT_EQ( |
+ mainFrame()->document()->documentElement(), |
+ rootScroller().getCurrent()); |
+ |
+ { |
+ // 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()); |
+ } |
+ |
+ { |
+ // Trying to set an element from outside the iframe should fail. |
+ 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 exceptionState1; |
+ iframe->contentDocument()->setRootScroller( |
+ iframe, |
+ exceptionState1); |
+ EXPECT_TRUE(exceptionState1.hadException()); |
+ |
+ ASSERT_EQ( |
+ iframe->contentDocument()->documentElement(), |
+ iframe->contentDocument()->rootScroller()); |
+ |
+ TrackExceptionState exceptionState2; |
+ iframe->contentDocument()->setRootScroller( |
+ body, |
+ exceptionState2); |
+ EXPECT_TRUE(exceptionState2.hadException()); |
+ |
+ ASSERT_EQ( |
+ iframe->contentDocument()->documentElement(), |
+ iframe->contentDocument()->rootScroller()); |
+ } |
+} |
+ |
+} // namespace |
+ |
+} // namespace blink |