Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1558)

Unified Diff: third_party/WebKit/Source/web/tests/RootScrollerTest.cpp

Issue 1913843004: Implementing document.setRootScroller API for main thread scrolling. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@overscrollController
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..8fbde181d305dc8360f3bc9a9c8c9cccb01c4778
--- /dev/null
+++ b/third_party/WebKit/Source/web/tests/RootScrollerTest.cpp
@@ -0,0 +1,473 @@
+// 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.
+ 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.
+ 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;
+};
+
+// 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().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 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());
tdresser 2016/04/27 15:37:08 Because of scroll latching, we wouldn't scroll the
bokan 2016/04/27 17:53:56 Yah, this comment is misleading so I changed it. W
+ 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().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 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().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 on an element within a iframe fails.
tdresser 2016/04/27 15:37:08 Do you set the root scroller on an element, or do
bokan 2016/04/27 17:53:56 Good point. The way to think about it is that the
+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 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().get());
+
+ {
tdresser 2016/04/27 15:37:08 It might be worth splitting up these two cases int
bokan 2016/04/27 17:53:56 Done.
+ // 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.
tdresser 2016/04/27 15:37:08 I initially misinterpreted this comment, thinking
bokan 2016/04/27 17:53:56 Done.
+ 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;
tdresser 2016/04/27 15:37:08 Is there any reason to not reuse the exceptionStat
bokan 2016/04/27 17:53:56 Didn't think to just clear the exception state. Do
+ 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

Powered by Google App Engine
This is Rietveld 408576698