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