Chromium Code Reviews| 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 |