OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "core/page/scrolling/RootScroller.h" |
| 6 |
| 7 #include "core/frame/FrameHost.h" |
| 8 #include "core/frame/FrameView.h" |
| 9 #include "core/frame/TopControls.h" |
| 10 #include "core/html/HTMLFrameOwnerElement.h" |
| 11 #include "core/page/Page.h" |
| 12 #include "platform/testing/URLTestHelpers.h" |
| 13 #include "platform/testing/UnitTestHelpers.h" |
| 14 #include "public/platform/Platform.h" |
| 15 #include "public/platform/WebURLLoaderMockFactory.h" |
| 16 #include "public/web/WebCache.h" |
| 17 #include "public/web/WebConsoleMessage.h" |
| 18 #include "public/web/WebScriptSource.h" |
| 19 #include "public/web/WebSettings.h" |
| 20 #include "testing/gtest/include/gtest/gtest.h" |
| 21 #include "web/WebLocalFrameImpl.h" |
| 22 #include "web/tests/FrameTestHelpers.h" |
| 23 #include "wtf/Vector.h" |
| 24 |
| 25 using blink::testing::runPendingTasks; |
| 26 using testing::Mock; |
| 27 |
| 28 namespace blink { |
| 29 |
| 30 namespace { |
| 31 |
| 32 class RootScrollerTestWebViewClient : public FrameTestHelpers::TestWebViewClient
{ |
| 33 public: |
| 34 MOCK_METHOD4(didOverscroll, void(const WebFloatSize&, const WebFloatSize&, c
onst WebFloatPoint&, const WebFloatSize&)); |
| 35 }; |
| 36 |
| 37 class RootScrollerTest : public ::testing::Test { |
| 38 public: |
| 39 RootScrollerTest() |
| 40 : m_baseURL("http://www.test.com/") |
| 41 { |
| 42 registerMockedHttpURLLoad("overflow-scrolling.html"); |
| 43 registerMockedHttpURLLoad("root-scroller.html"); |
| 44 registerMockedHttpURLLoad("root-scroller-iframe.html"); |
| 45 registerMockedHttpURLLoad("root-scroller-child.html"); |
| 46 } |
| 47 |
| 48 ~RootScrollerTest() override |
| 49 { |
| 50 m_featuresBackup.restore(); |
| 51 Platform::current()->getURLLoaderMockFactory()->unregisterAllURLs(); |
| 52 WebCache::clear(); |
| 53 } |
| 54 |
| 55 WebViewImpl* initialize(const std::string& pageName) |
| 56 { |
| 57 RuntimeEnabledFeatures::setSetRootScrollerEnabled(true); |
| 58 |
| 59 // Load a page with large body and set viewport size to 400x400 to |
| 60 // ensure main frame is scrollable. |
| 61 m_helper.initializeAndLoad( |
| 62 m_baseURL + pageName, true, 0, &m_client, &configureSettings); |
| 63 |
| 64 // Initialize top controls to be shown. |
| 65 webViewImpl()->resizeWithTopControls(IntSize(400, 400), 50, true); |
| 66 webViewImpl()->topControls().setShownRatio(1); |
| 67 |
| 68 mainFrameView()->updateAllLifecyclePhases(); |
| 69 |
| 70 return webViewImpl(); |
| 71 } |
| 72 |
| 73 static void configureSettings(WebSettings* settings) |
| 74 { |
| 75 settings->setJavaScriptEnabled(true); |
| 76 settings->setAcceleratedCompositingEnabled(true); |
| 77 settings->setPreferCompositingToLCDTextEnabled(true); |
| 78 // Android settings. |
| 79 settings->setViewportEnabled(true); |
| 80 settings->setViewportMetaEnabled(true); |
| 81 settings->setShrinksViewportContentToFit(true); |
| 82 settings->setMainFrameResizesAreOrientationChanges(true); |
| 83 } |
| 84 |
| 85 void registerMockedHttpURLLoad(const std::string& fileName) |
| 86 { |
| 87 URLTestHelpers::registerMockedURLFromBaseURL( |
| 88 WebString::fromUTF8(m_baseURL.c_str()), |
| 89 WebString::fromUTF8(fileName.c_str())); |
| 90 } |
| 91 |
| 92 void executeScript(const WebString& code) |
| 93 { |
| 94 mainWebFrame()->executeScript(WebScriptSource(code)); |
| 95 mainWebFrame()->view()->updateAllLifecyclePhases(); |
| 96 runPendingTasks(); |
| 97 } |
| 98 |
| 99 WebGestureEvent generateEvent( |
| 100 WebInputEvent::Type type, int deltaX = 0, int deltaY = 0) |
| 101 { |
| 102 WebGestureEvent event; |
| 103 event.type = type; |
| 104 event.sourceDevice = WebGestureDeviceTouchscreen; |
| 105 event.x = 100; |
| 106 event.y = 100; |
| 107 if (type == WebInputEvent::GestureScrollUpdate) { |
| 108 event.data.scrollUpdate.deltaX = deltaX; |
| 109 event.data.scrollUpdate.deltaY = deltaY; |
| 110 } |
| 111 return event; |
| 112 } |
| 113 |
| 114 void verticalScroll(float deltaY) |
| 115 { |
| 116 webViewImpl()->handleInputEvent( |
| 117 generateEvent(WebInputEvent::GestureScrollBegin)); |
| 118 webViewImpl()->handleInputEvent( |
| 119 generateEvent(WebInputEvent::GestureScrollUpdate, 0, -deltaY)); |
| 120 webViewImpl()->handleInputEvent( |
| 121 generateEvent(WebInputEvent::GestureScrollEnd)); |
| 122 } |
| 123 |
| 124 WebViewImpl* webViewImpl() const |
| 125 { |
| 126 return m_helper.webViewImpl(); |
| 127 } |
| 128 |
| 129 FrameHost& frameHost() const |
| 130 { |
| 131 return m_helper.webViewImpl()->page()->frameHost(); |
| 132 } |
| 133 |
| 134 LocalFrame* mainFrame() const |
| 135 { |
| 136 return toWebLocalFrameImpl(webViewImpl()->mainFrame())->frame(); |
| 137 } |
| 138 |
| 139 WebLocalFrame* mainWebFrame() const |
| 140 { |
| 141 return toWebLocalFrameImpl(webViewImpl()->mainFrame()); |
| 142 } |
| 143 |
| 144 FrameView* mainFrameView() const |
| 145 { |
| 146 return webViewImpl()->mainFrameImpl()->frame()->view(); |
| 147 } |
| 148 |
| 149 VisualViewport& visualViewport() const |
| 150 { |
| 151 return frameHost().visualViewport(); |
| 152 } |
| 153 |
| 154 RootScroller& rootScroller() const |
| 155 { |
| 156 return *frameHost().rootScroller(); |
| 157 } |
| 158 |
| 159 TopControls& topControls() const |
| 160 { |
| 161 return frameHost().topControls(); |
| 162 } |
| 163 |
| 164 protected: |
| 165 std::string m_baseURL; |
| 166 RootScrollerTestWebViewClient m_client; |
| 167 FrameTestHelpers::WebViewHelper m_helper; |
| 168 RuntimeEnabledFeatures::Backup m_featuresBackup; |
| 169 }; |
| 170 |
| 171 // Test that a default root scroller element is set if setRootScroller isn't |
| 172 // called on any elements. |
| 173 TEST_F(RootScrollerTest, TestDefaultRootScroller) |
| 174 { |
| 175 initialize("overflow-scrolling.html"); |
| 176 |
| 177 EXPECT_EQ( |
| 178 mainFrame()->document()->documentElement(), |
| 179 rootScroller().get()); |
| 180 } |
| 181 |
| 182 // Tests that setting an element as the root scroller causes it to control url |
| 183 // bar hiding and overscroll. |
| 184 TEST_F(RootScrollerTest, TestSetRootScroller) |
| 185 { |
| 186 initialize("root-scroller.html"); |
| 187 |
| 188 Element* container = mainFrame()->document()->getElementById("container"); |
| 189 TrackExceptionState exceptionState; |
| 190 mainFrame()->document()->setRootScroller(container, exceptionState); |
| 191 ASSERT_EQ(container, mainFrame()->document()->rootScroller()); |
| 192 |
| 193 // Content is 1000x1000, WebView size is 400x400 so max scroll is 600px. |
| 194 double maximumScroll = 600; |
| 195 |
| 196 webViewImpl()->handleInputEvent( |
| 197 generateEvent(WebInputEvent::GestureScrollBegin)); |
| 198 |
| 199 { |
| 200 // Scrolling over the #container DIV should cause the top controls to |
| 201 // hide. |
| 202 ASSERT_FLOAT_EQ(1, topControls().shownRatio()); |
| 203 webViewImpl()->handleInputEvent(generateEvent( |
| 204 WebInputEvent::GestureScrollUpdate, 0, -topControls().height())); |
| 205 ASSERT_FLOAT_EQ(0, topControls().shownRatio()); |
| 206 } |
| 207 |
| 208 { |
| 209 // Make sure we're actually scrolling the DIV and not the FrameView. |
| 210 webViewImpl()->handleInputEvent( |
| 211 generateEvent(WebInputEvent::GestureScrollUpdate, 0, -100)); |
| 212 ASSERT_FLOAT_EQ(100, container->scrollTop()); |
| 213 ASSERT_FLOAT_EQ(0, mainFrameView()->scrollPositionDouble().y()); |
| 214 } |
| 215 |
| 216 { |
| 217 // Scroll 50 pixels past the end. Ensure we report the 50 pixels as |
| 218 // overscroll. |
| 219 EXPECT_CALL(m_client, |
| 220 didOverscroll( |
| 221 WebFloatSize(0, 50), |
| 222 WebFloatSize(0, 50), |
| 223 WebFloatPoint(100, 100), |
| 224 WebFloatSize())); |
| 225 webViewImpl()->handleInputEvent( |
| 226 generateEvent(WebInputEvent::GestureScrollUpdate, 0, -550)); |
| 227 ASSERT_FLOAT_EQ(maximumScroll, container->scrollTop()); |
| 228 ASSERT_FLOAT_EQ(0, mainFrameView()->scrollPositionDouble().y()); |
| 229 Mock::VerifyAndClearExpectations(&m_client); |
| 230 } |
| 231 |
| 232 { |
| 233 // Continue the gesture overscroll. |
| 234 EXPECT_CALL(m_client, |
| 235 didOverscroll( |
| 236 WebFloatSize(0, 20), |
| 237 WebFloatSize(0, 70), |
| 238 WebFloatPoint(100, 100), |
| 239 WebFloatSize())); |
| 240 webViewImpl()->handleInputEvent( |
| 241 generateEvent(WebInputEvent::GestureScrollUpdate, 0, -20)); |
| 242 ASSERT_FLOAT_EQ(maximumScroll, container->scrollTop()); |
| 243 ASSERT_FLOAT_EQ(0, mainFrameView()->scrollPositionDouble().y()); |
| 244 Mock::VerifyAndClearExpectations(&m_client); |
| 245 } |
| 246 |
| 247 webViewImpl()->handleInputEvent( |
| 248 generateEvent(WebInputEvent::GestureScrollEnd)); |
| 249 |
| 250 { |
| 251 // Make sure a new gesture scroll still won't scroll the frameview and |
| 252 // overscrolls. |
| 253 webViewImpl()->handleInputEvent( |
| 254 generateEvent(WebInputEvent::GestureScrollBegin)); |
| 255 |
| 256 EXPECT_CALL(m_client, |
| 257 didOverscroll( |
| 258 WebFloatSize(0, 30), |
| 259 WebFloatSize(0, 30), |
| 260 WebFloatPoint(100, 100), |
| 261 WebFloatSize())); |
| 262 webViewImpl()->handleInputEvent( |
| 263 generateEvent(WebInputEvent::GestureScrollUpdate, 0, -30)); |
| 264 ASSERT_FLOAT_EQ(maximumScroll, container->scrollTop()); |
| 265 ASSERT_FLOAT_EQ(0, mainFrameView()->scrollPositionDouble().y()); |
| 266 Mock::VerifyAndClearExpectations(&m_client); |
| 267 |
| 268 webViewImpl()->handleInputEvent( |
| 269 generateEvent(WebInputEvent::GestureScrollEnd)); |
| 270 } |
| 271 |
| 272 { |
| 273 // Scrolling up should show the top controls. |
| 274 webViewImpl()->handleInputEvent( |
| 275 generateEvent(WebInputEvent::GestureScrollBegin)); |
| 276 |
| 277 ASSERT_FLOAT_EQ(0, topControls().shownRatio()); |
| 278 webViewImpl()->handleInputEvent( |
| 279 generateEvent(WebInputEvent::GestureScrollUpdate, 0, 30)); |
| 280 ASSERT_FLOAT_EQ(0.6, topControls().shownRatio()); |
| 281 |
| 282 webViewImpl()->handleInputEvent( |
| 283 generateEvent(WebInputEvent::GestureScrollEnd)); |
| 284 } |
| 285 } |
| 286 |
| 287 // Tests that removing the element that is the root scroller from the DOM tree |
| 288 // resets the default element to be the root scroller. |
| 289 TEST_F(RootScrollerTest, TestRemoveRootScrollerFromDom) |
| 290 { |
| 291 initialize("root-scroller.html"); |
| 292 |
| 293 ASSERT_EQ( |
| 294 mainFrame()->document()->documentElement(), |
| 295 rootScroller().get()); |
| 296 |
| 297 Element* container = mainFrame()->document()->getElementById("container"); |
| 298 TrackExceptionState exceptionState; |
| 299 mainFrame()->document()->setRootScroller(container, exceptionState); |
| 300 |
| 301 ASSERT_EQ(container, mainFrame()->document()->rootScroller()); |
| 302 |
| 303 mainFrame()->document()->body()->removeChild(container); |
| 304 |
| 305 ASSERT_EQ( |
| 306 mainFrame()->document()->documentElement(), |
| 307 mainFrame()->document()->rootScroller()); |
| 308 } |
| 309 |
| 310 // Tests that setting an element that isn't a valid scroller as the root |
| 311 // scroller fails and doesn't change the current root scroller. |
| 312 TEST_F(RootScrollerTest, TestSetRootScrollerOnInvalidElement) |
| 313 { |
| 314 initialize("root-scroller.html"); |
| 315 |
| 316 { |
| 317 // Set to a non-block element. Should be rejected and a console message |
| 318 // logged. |
| 319 Element* element = mainFrame()->document()->getElementById("nonBlock"); |
| 320 TrackExceptionState exceptionState; |
| 321 mainFrame()->document()->setRootScroller(element, exceptionState); |
| 322 ASSERT_EQ( |
| 323 mainFrame()->document()->documentElement(), |
| 324 mainFrame()->document()->rootScroller()); |
| 325 EXPECT_TRUE(exceptionState.hadException()); |
| 326 } |
| 327 |
| 328 { |
| 329 // Set to an element with no size. |
| 330 Element* element = mainFrame()->document()->getElementById("empty"); |
| 331 TrackExceptionState exceptionState; |
| 332 mainFrame()->document()->setRootScroller(element, exceptionState); |
| 333 ASSERT_EQ( |
| 334 mainFrame()->document()->documentElement(), |
| 335 mainFrame()->document()->rootScroller()); |
| 336 EXPECT_TRUE(exceptionState.hadException()); |
| 337 } |
| 338 } |
| 339 |
| 340 // Test that the root scroller resets to the default element when the current |
| 341 // root scroller element becomes invalid as a scroller. |
| 342 TEST_F(RootScrollerTest, TestRootScrollerBecomesInvalid) |
| 343 { |
| 344 initialize("root-scroller.html"); |
| 345 |
| 346 ASSERT_EQ( |
| 347 mainFrame()->document()->documentElement(), |
| 348 rootScroller().get()); |
| 349 |
| 350 Element* container = mainFrame()->document()->getElementById("container"); |
| 351 TrackExceptionState exceptionState; |
| 352 mainFrame()->document()->setRootScroller(container, exceptionState); |
| 353 |
| 354 ASSERT_EQ(container, mainFrame()->document()->rootScroller()); |
| 355 |
| 356 executeScript( |
| 357 "document.querySelector('#container').style.display = 'inline'"); |
| 358 |
| 359 ASSERT_EQ( |
| 360 mainFrame()->document()->documentElement(), |
| 361 mainFrame()->document()->rootScroller()); |
| 362 } |
| 363 |
| 364 // Tests that setting the root scroller of the top codument to an element that |
| 365 // belongs to a nested document fails. |
| 366 TEST_F(RootScrollerTest, TestSetRootScrollerOnElementInIframe) |
| 367 { |
| 368 initialize("root-scroller-iframe.html"); |
| 369 |
| 370 ASSERT_EQ( |
| 371 mainFrame()->document()->documentElement(), |
| 372 rootScroller().get()); |
| 373 |
| 374 { |
| 375 // Trying to set an element from a nested document should fail. |
| 376 HTMLFrameOwnerElement* iframe = toHTMLFrameOwnerElement( |
| 377 mainFrame()->document()->getElementById("iframe")); |
| 378 Element* innerContainer = |
| 379 iframe->contentDocument()->getElementById("container"); |
| 380 |
| 381 TrackExceptionState exceptionState; |
| 382 mainFrame()->document()->setRootScroller( |
| 383 innerContainer, |
| 384 exceptionState); |
| 385 EXPECT_TRUE(exceptionState.hadException()); |
| 386 |
| 387 ASSERT_EQ( |
| 388 mainFrame()->document()->documentElement(), |
| 389 rootScroller().get()); |
| 390 } |
| 391 |
| 392 { |
| 393 // Setting the iframe itself, however, should work. |
| 394 HTMLFrameOwnerElement* iframe = toHTMLFrameOwnerElement( |
| 395 mainFrame()->document()->getElementById("iframe")); |
| 396 |
| 397 TrackExceptionState exceptionState; |
| 398 mainFrame()->document()->setRootScroller(iframe, exceptionState); |
| 399 EXPECT_FALSE(exceptionState.hadException()); |
| 400 |
| 401 ASSERT_EQ(iframe, rootScroller().get()); |
| 402 } |
| 403 } |
| 404 |
| 405 // Tests that setting an otherwise valid element as the root scroller on a |
| 406 // document within an iframe fails and getting the root scroller in the nested |
| 407 // document returns the default element. |
| 408 TEST_F(RootScrollerTest, TestRootScrollerWithinIframe) |
| 409 { |
| 410 initialize("root-scroller-iframe.html"); |
| 411 |
| 412 ASSERT_EQ( |
| 413 mainFrame()->document()->documentElement(), |
| 414 rootScroller().get()); |
| 415 |
| 416 { |
| 417 // Trying to set an element within nested document should fail. |
| 418 // rootScroller() should always return its documentElement. |
| 419 HTMLFrameOwnerElement* iframe = toHTMLFrameOwnerElement( |
| 420 mainFrame()->document()->getElementById("iframe")); |
| 421 |
| 422 ASSERT_EQ( |
| 423 iframe->contentDocument()->documentElement(), |
| 424 iframe->contentDocument()->rootScroller()); |
| 425 |
| 426 Element* innerContainer = |
| 427 iframe->contentDocument()->getElementById("container"); |
| 428 TrackExceptionState exceptionState; |
| 429 iframe->contentDocument()->setRootScroller( |
| 430 innerContainer, |
| 431 exceptionState); |
| 432 EXPECT_TRUE(exceptionState.hadException()); |
| 433 |
| 434 ASSERT_EQ( |
| 435 iframe->contentDocument()->documentElement(), |
| 436 iframe->contentDocument()->rootScroller()); |
| 437 } |
| 438 } |
| 439 |
| 440 // Tests that trying to set an element as the root scroller of a document inside |
| 441 // an iframe fails when that element belongs to the parent document. |
| 442 TEST_F(RootScrollerTest, TestSetRootScrollerOnElementFromOutsideIframe) |
| 443 { |
| 444 initialize("root-scroller-iframe.html"); |
| 445 |
| 446 ASSERT_EQ( |
| 447 mainFrame()->document()->documentElement(), |
| 448 rootScroller().get()); |
| 449 { |
| 450 // Try to set the the root scroller of the child document to be the |
| 451 // <iframe> element in the parent document. |
| 452 HTMLFrameOwnerElement* iframe = toHTMLFrameOwnerElement( |
| 453 mainFrame()->document()->getElementById("iframe")); |
| 454 NonThrowableExceptionState nonThrow; |
| 455 Element* body = |
| 456 mainFrame()->document()->querySelector("body", nonThrow); |
| 457 |
| 458 ASSERT_EQ( |
| 459 iframe->contentDocument()->documentElement(), |
| 460 iframe->contentDocument()->rootScroller()); |
| 461 |
| 462 TrackExceptionState exceptionState; |
| 463 iframe->contentDocument()->setRootScroller( |
| 464 iframe, |
| 465 exceptionState); |
| 466 EXPECT_TRUE(exceptionState.hadException()); |
| 467 |
| 468 ASSERT_EQ( |
| 469 iframe->contentDocument()->documentElement(), |
| 470 iframe->contentDocument()->rootScroller()); |
| 471 |
| 472 exceptionState.clearException(); |
| 473 |
| 474 // Try to set the root scroller of the child document to be the |
| 475 // <body> element of the parent document. |
| 476 iframe->contentDocument()->setRootScroller( |
| 477 body, |
| 478 exceptionState); |
| 479 EXPECT_TRUE(exceptionState.hadException()); |
| 480 |
| 481 ASSERT_EQ( |
| 482 iframe->contentDocument()->documentElement(), |
| 483 iframe->contentDocument()->rootScroller()); |
| 484 } |
| 485 } |
| 486 |
| 487 } // namespace |
| 488 |
| 489 } // namespace blink |
OLD | NEW |