1/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1.  Redistributions of source code must retain the above copyright
8 *     notice, this list of conditions and the following disclaimer.
9 * 2.  Redistributions in binary form must reproduce the above copyright
10 *     notice, this list of conditions and the following disclaimer in the
11 *     documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26
27#include "core/page/scrolling/ScrollingCoordinator.h"
28
29#include "core/page/Page.h"
30#include "core/rendering/RenderView.h"
31#include "core/rendering/RenderWidget.h"
32#include "core/rendering/compositing/CompositedLayerMapping.h"
33#include "core/rendering/compositing/RenderLayerCompositor.h"
34#include "core/testing/URLTestHelpers.h"
35#include "platform/graphics/GraphicsLayer.h"
36#include "public/platform/Platform.h"
37#include "public/platform/WebLayer.h"
38#include "public/platform/WebLayerPositionConstraint.h"
39#include "public/platform/WebLayerTreeView.h"
40#include "public/platform/WebUnitTestSupport.h"
41#include "public/web/WebSettings.h"
42#include "public/web/WebViewClient.h"
43#include "web/WebLocalFrameImpl.h"
44#include "web/WebViewImpl.h"
45#include "web/tests/FrameTestHelpers.h"
46#include <gtest/gtest.h>
47
48using namespace blink;
49
50namespace {
51
52class ScrollingCoordinatorChromiumTest : public testing::Test {
53public:
54    ScrollingCoordinatorChromiumTest()
55        : m_baseURL("http://www.test.com/")
56    {
57        m_helper.initialize(true, 0, &m_mockWebViewClient, &configureSettings);
58        webViewImpl()->resize(IntSize(320, 240));
59    }
60
61    virtual ~ScrollingCoordinatorChromiumTest()
62    {
63        Platform::current()->unitTestSupport()->unregisterAllMockedURLs();
64    }
65
66    void navigateTo(const std::string& url)
67    {
68        FrameTestHelpers::loadFrame(webViewImpl()->mainFrame(), url);
69    }
70
71    void forceFullCompositingUpdate()
72    {
73        webViewImpl()->layout();
74    }
75
76    void registerMockedHttpURLLoad(const std::string& fileName)
77    {
78        URLTestHelpers::registerMockedURLFromBaseURL(WebString::fromUTF8(m_baseURL.c_str()), WebString::fromUTF8(fileName.c_str()));
79    }
80
81    WebLayer* getRootScrollLayer()
82    {
83        RenderLayerCompositor* compositor = frame()->contentRenderer()->compositor();
84        ASSERT(compositor);
85        ASSERT(compositor->scrollLayer());
86
87        WebLayer* webScrollLayer = compositor->scrollLayer()->platformLayer();
88        return webScrollLayer;
89    }
90
91    WebViewImpl* webViewImpl() const { return m_helper.webViewImpl(); }
92    LocalFrame* frame() const { return m_helper.webViewImpl()->mainFrameImpl()->frame(); }
93
94protected:
95    std::string m_baseURL;
96    FrameTestHelpers::TestWebViewClient m_mockWebViewClient;
97
98private:
99    static void configureSettings(WebSettings* settings)
100    {
101        settings->setJavaScriptEnabled(true);
102        settings->setAcceleratedCompositingEnabled(true);
103        settings->setPreferCompositingToLCDTextEnabled(true);
104    }
105
106    FrameTestHelpers::WebViewHelper m_helper;
107};
108
109TEST_F(ScrollingCoordinatorChromiumTest, fastScrollingByDefault)
110{
111    navigateTo("about:blank");
112    forceFullCompositingUpdate();
113
114    // Make sure the scrolling coordinator is active.
115    FrameView* frameView = frame()->view();
116    Page* page = frame()->page();
117    ASSERT_TRUE(page->scrollingCoordinator());
118    ASSERT_TRUE(page->scrollingCoordinator()->coordinatesScrollingForFrameView(frameView));
119
120    // Fast scrolling should be enabled by default.
121    WebLayer* rootScrollLayer = getRootScrollLayer();
122    ASSERT_TRUE(rootScrollLayer->scrollable());
123    ASSERT_FALSE(rootScrollLayer->shouldScrollOnMainThread());
124    ASSERT_FALSE(rootScrollLayer->haveWheelEventHandlers());
125}
126
127TEST_F(ScrollingCoordinatorChromiumTest, fastScrollingCanBeDisabledWithSetting)
128{
129    navigateTo("about:blank");
130    webViewImpl()->settings()->setThreadedScrollingEnabled(false);
131    forceFullCompositingUpdate();
132
133    // Make sure the scrolling coordinator is active.
134    FrameView* frameView = frame()->view();
135    Page* page = frame()->page();
136    ASSERT_TRUE(page->scrollingCoordinator());
137    ASSERT_TRUE(page->scrollingCoordinator()->coordinatesScrollingForFrameView(frameView));
138
139    // Main scrolling should be enabled with the setting override.
140    WebLayer* rootScrollLayer = getRootScrollLayer();
141    ASSERT_TRUE(rootScrollLayer->scrollable());
142    ASSERT_TRUE(rootScrollLayer->shouldScrollOnMainThread());
143}
144
145static WebLayer* webLayerFromElement(Element* element)
146{
147    if (!element)
148        return 0;
149    RenderObject* renderer = element->renderer();
150    if (!renderer || !renderer->isBoxModelObject())
151        return 0;
152    RenderLayer* layer = toRenderBoxModelObject(renderer)->layer();
153    if (!layer)
154        return 0;
155    if (!layer->hasCompositedLayerMapping())
156        return 0;
157    CompositedLayerMapping* compositedLayerMapping = layer->compositedLayerMapping();
158    GraphicsLayer* graphicsLayer = compositedLayerMapping->mainGraphicsLayer();
159    if (!graphicsLayer)
160        return 0;
161    return graphicsLayer->platformLayer();
162}
163
164TEST_F(ScrollingCoordinatorChromiumTest, fastScrollingForFixedPosition)
165{
166    registerMockedHttpURLLoad("fixed-position.html");
167    navigateTo(m_baseURL + "fixed-position.html");
168    forceFullCompositingUpdate();
169
170    // Fixed position should not fall back to main thread scrolling.
171    WebLayer* rootScrollLayer = getRootScrollLayer();
172    ASSERT_FALSE(rootScrollLayer->shouldScrollOnMainThread());
173
174    Document* document = frame()->document();
175    {
176        Element* element = document->getElementById("div-tl");
177        ASSERT_TRUE(element);
178        WebLayer* layer = webLayerFromElement(element);
179        ASSERT_TRUE(layer);
180        WebLayerPositionConstraint constraint = layer->positionConstraint();
181        ASSERT_TRUE(constraint.isFixedPosition);
182        ASSERT_TRUE(!constraint.isFixedToRightEdge && !constraint.isFixedToBottomEdge);
183    }
184    {
185        Element* element = document->getElementById("div-tr");
186        ASSERT_TRUE(element);
187        WebLayer* layer = webLayerFromElement(element);
188        ASSERT_TRUE(layer);
189        WebLayerPositionConstraint constraint = layer->positionConstraint();
190        ASSERT_TRUE(constraint.isFixedPosition);
191        ASSERT_TRUE(constraint.isFixedToRightEdge && !constraint.isFixedToBottomEdge);
192    }
193    {
194        Element* element = document->getElementById("div-bl");
195        ASSERT_TRUE(element);
196        WebLayer* layer = webLayerFromElement(element);
197        ASSERT_TRUE(layer);
198        WebLayerPositionConstraint constraint = layer->positionConstraint();
199        ASSERT_TRUE(constraint.isFixedPosition);
200        ASSERT_TRUE(!constraint.isFixedToRightEdge && constraint.isFixedToBottomEdge);
201    }
202    {
203        Element* element = document->getElementById("div-br");
204        ASSERT_TRUE(element);
205        WebLayer* layer = webLayerFromElement(element);
206        ASSERT_TRUE(layer);
207        WebLayerPositionConstraint constraint = layer->positionConstraint();
208        ASSERT_TRUE(constraint.isFixedPosition);
209        ASSERT_TRUE(constraint.isFixedToRightEdge && constraint.isFixedToBottomEdge);
210    }
211    {
212        Element* element = document->getElementById("span-tl");
213        ASSERT_TRUE(element);
214        WebLayer* layer = webLayerFromElement(element);
215        ASSERT_TRUE(layer);
216        WebLayerPositionConstraint constraint = layer->positionConstraint();
217        ASSERT_TRUE(constraint.isFixedPosition);
218        ASSERT_TRUE(!constraint.isFixedToRightEdge && !constraint.isFixedToBottomEdge);
219    }
220    {
221        Element* element = document->getElementById("span-tr");
222        ASSERT_TRUE(element);
223        WebLayer* layer = webLayerFromElement(element);
224        ASSERT_TRUE(layer);
225        WebLayerPositionConstraint constraint = layer->positionConstraint();
226        ASSERT_TRUE(constraint.isFixedPosition);
227        ASSERT_TRUE(constraint.isFixedToRightEdge && !constraint.isFixedToBottomEdge);
228    }
229    {
230        Element* element = document->getElementById("span-bl");
231        ASSERT_TRUE(element);
232        WebLayer* layer = webLayerFromElement(element);
233        ASSERT_TRUE(layer);
234        WebLayerPositionConstraint constraint = layer->positionConstraint();
235        ASSERT_TRUE(constraint.isFixedPosition);
236        ASSERT_TRUE(!constraint.isFixedToRightEdge && constraint.isFixedToBottomEdge);
237    }
238    {
239        Element* element = document->getElementById("span-br");
240        ASSERT_TRUE(element);
241        WebLayer* layer = webLayerFromElement(element);
242        ASSERT_TRUE(layer);
243        WebLayerPositionConstraint constraint = layer->positionConstraint();
244        ASSERT_TRUE(constraint.isFixedPosition);
245        ASSERT_TRUE(constraint.isFixedToRightEdge && constraint.isFixedToBottomEdge);
246    }
247}
248
249TEST_F(ScrollingCoordinatorChromiumTest, wheelEventHandler)
250{
251    registerMockedHttpURLLoad("wheel-event-handler.html");
252    navigateTo(m_baseURL + "wheel-event-handler.html");
253    forceFullCompositingUpdate();
254
255    WebLayer* rootScrollLayer = getRootScrollLayer();
256    ASSERT_TRUE(rootScrollLayer->haveWheelEventHandlers());
257}
258
259TEST_F(ScrollingCoordinatorChromiumTest, scrollEventHandler)
260{
261    registerMockedHttpURLLoad("scroll-event-handler.html");
262    navigateTo(m_baseURL + "scroll-event-handler.html");
263    forceFullCompositingUpdate();
264
265    WebLayer* rootScrollLayer = getRootScrollLayer();
266    ASSERT_TRUE(rootScrollLayer->haveScrollEventHandlers());
267}
268
269TEST_F(ScrollingCoordinatorChromiumTest, updateEventHandlersDuringTeardown)
270{
271    registerMockedHttpURLLoad("scroll-event-handler-window.html");
272    navigateTo(m_baseURL + "scroll-event-handler-window.html");
273    forceFullCompositingUpdate();
274
275    // Simulate detaching the document from its DOM window. This should not
276    // cause a crash when the WebViewImpl is closed by the test runner.
277    frame()->document()->prepareForDestruction();
278}
279
280TEST_F(ScrollingCoordinatorChromiumTest, clippedBodyTest)
281{
282    registerMockedHttpURLLoad("clipped-body.html");
283    navigateTo(m_baseURL + "clipped-body.html");
284    forceFullCompositingUpdate();
285
286    WebLayer* rootScrollLayer = getRootScrollLayer();
287    ASSERT_EQ(0u, rootScrollLayer->nonFastScrollableRegion().size());
288}
289
290TEST_F(ScrollingCoordinatorChromiumTest, overflowScrolling)
291{
292    registerMockedHttpURLLoad("overflow-scrolling.html");
293    navigateTo(m_baseURL + "overflow-scrolling.html");
294    forceFullCompositingUpdate();
295
296    // Verify the properties of the accelerated scrolling element starting from the RenderObject
297    // all the way to the WebLayer.
298    Element* scrollableElement = frame()->document()->getElementById("scrollable");
299    ASSERT(scrollableElement);
300
301    RenderObject* renderer = scrollableElement->renderer();
302    ASSERT_TRUE(renderer->isBox());
303    ASSERT_TRUE(renderer->hasLayer());
304
305    RenderBox* box = toRenderBox(renderer);
306    ASSERT_TRUE(box->usesCompositedScrolling());
307    ASSERT_EQ(PaintsIntoOwnBacking, box->layer()->compositingState());
308
309    CompositedLayerMapping* compositedLayerMapping = box->layer()->compositedLayerMapping();
310    ASSERT_TRUE(compositedLayerMapping->hasScrollingLayer());
311    ASSERT(compositedLayerMapping->scrollingContentsLayer());
312
313    GraphicsLayer* graphicsLayer = compositedLayerMapping->scrollingContentsLayer();
314    ASSERT_EQ(box->layer()->scrollableArea(), graphicsLayer->scrollableArea());
315
316    WebLayer* webScrollLayer = compositedLayerMapping->scrollingContentsLayer()->platformLayer();
317    ASSERT_TRUE(webScrollLayer->scrollable());
318    ASSERT_TRUE(webScrollLayer->userScrollableHorizontal());
319    ASSERT_TRUE(webScrollLayer->userScrollableVertical());
320
321#if OS(ANDROID)
322    // Now verify we've attached impl-side scrollbars onto the scrollbar layers
323    ASSERT_TRUE(compositedLayerMapping->layerForHorizontalScrollbar());
324    ASSERT_TRUE(compositedLayerMapping->layerForHorizontalScrollbar()->hasContentsLayer());
325    ASSERT_TRUE(compositedLayerMapping->layerForVerticalScrollbar());
326    ASSERT_TRUE(compositedLayerMapping->layerForVerticalScrollbar()->hasContentsLayer());
327#endif
328}
329
330TEST_F(ScrollingCoordinatorChromiumTest, overflowHidden)
331{
332    registerMockedHttpURLLoad("overflow-hidden.html");
333    navigateTo(m_baseURL + "overflow-hidden.html");
334    forceFullCompositingUpdate();
335
336    // Verify the properties of the accelerated scrolling element starting from the RenderObject
337    // all the way to the WebLayer.
338    Element* overflowElement = frame()->document()->getElementById("unscrollable-y");
339    ASSERT(overflowElement);
340
341    RenderObject* renderer = overflowElement->renderer();
342    ASSERT_TRUE(renderer->isBox());
343    ASSERT_TRUE(renderer->hasLayer());
344
345    RenderBox* box = toRenderBox(renderer);
346    ASSERT_TRUE(box->usesCompositedScrolling());
347    ASSERT_EQ(PaintsIntoOwnBacking, box->layer()->compositingState());
348
349    CompositedLayerMapping* compositedLayerMapping = box->layer()->compositedLayerMapping();
350    ASSERT_TRUE(compositedLayerMapping->hasScrollingLayer());
351    ASSERT(compositedLayerMapping->scrollingContentsLayer());
352
353    GraphicsLayer* graphicsLayer = compositedLayerMapping->scrollingContentsLayer();
354    ASSERT_EQ(box->layer()->scrollableArea(), graphicsLayer->scrollableArea());
355
356    WebLayer* webScrollLayer = compositedLayerMapping->scrollingContentsLayer()->platformLayer();
357    ASSERT_TRUE(webScrollLayer->scrollable());
358    ASSERT_TRUE(webScrollLayer->userScrollableHorizontal());
359    ASSERT_FALSE(webScrollLayer->userScrollableVertical());
360
361    overflowElement = frame()->document()->getElementById("unscrollable-x");
362    ASSERT(overflowElement);
363
364    renderer = overflowElement->renderer();
365    ASSERT_TRUE(renderer->isBox());
366    ASSERT_TRUE(renderer->hasLayer());
367
368    box = toRenderBox(renderer);
369    ASSERT_TRUE(box->scrollableArea()->usesCompositedScrolling());
370    ASSERT_EQ(PaintsIntoOwnBacking, box->layer()->compositingState());
371
372    compositedLayerMapping = box->layer()->compositedLayerMapping();
373    ASSERT_TRUE(compositedLayerMapping->hasScrollingLayer());
374    ASSERT(compositedLayerMapping->scrollingContentsLayer());
375
376    graphicsLayer = compositedLayerMapping->scrollingContentsLayer();
377    ASSERT_EQ(box->layer()->scrollableArea(), graphicsLayer->scrollableArea());
378
379    webScrollLayer = compositedLayerMapping->scrollingContentsLayer()->platformLayer();
380    ASSERT_TRUE(webScrollLayer->scrollable());
381    ASSERT_FALSE(webScrollLayer->userScrollableHorizontal());
382    ASSERT_TRUE(webScrollLayer->userScrollableVertical());
383}
384
385TEST_F(ScrollingCoordinatorChromiumTest, iframeScrolling)
386{
387    registerMockedHttpURLLoad("iframe-scrolling.html");
388    registerMockedHttpURLLoad("iframe-scrolling-inner.html");
389    navigateTo(m_baseURL + "iframe-scrolling.html");
390    forceFullCompositingUpdate();
391
392    // Verify the properties of the accelerated scrolling element starting from the RenderObject
393    // all the way to the WebLayer.
394    Element* scrollableFrame = frame()->document()->getElementById("scrollable");
395    ASSERT_TRUE(scrollableFrame);
396
397    RenderObject* renderer = scrollableFrame->renderer();
398    ASSERT_TRUE(renderer);
399    ASSERT_TRUE(renderer->isWidget());
400
401    RenderWidget* renderWidget = toRenderWidget(renderer);
402    ASSERT_TRUE(renderWidget);
403    ASSERT_TRUE(renderWidget->widget());
404    ASSERT_TRUE(renderWidget->widget()->isFrameView());
405
406    FrameView* innerFrameView = toFrameView(renderWidget->widget());
407    RenderView* innerRenderView = innerFrameView->renderView();
408    ASSERT_TRUE(innerRenderView);
409
410    RenderLayerCompositor* innerCompositor = innerRenderView->compositor();
411    ASSERT_TRUE(innerCompositor->inCompositingMode());
412    ASSERT_TRUE(innerCompositor->scrollLayer());
413
414    GraphicsLayer* scrollLayer = innerCompositor->scrollLayer();
415    ASSERT_EQ(innerFrameView, scrollLayer->scrollableArea());
416
417    WebLayer* webScrollLayer = scrollLayer->platformLayer();
418    ASSERT_TRUE(webScrollLayer->scrollable());
419
420#if OS(ANDROID)
421    // Now verify we've attached impl-side scrollbars onto the scrollbar layers
422    ASSERT_TRUE(innerCompositor->layerForHorizontalScrollbar());
423    ASSERT_TRUE(innerCompositor->layerForHorizontalScrollbar()->hasContentsLayer());
424    ASSERT_TRUE(innerCompositor->layerForVerticalScrollbar());
425    ASSERT_TRUE(innerCompositor->layerForVerticalScrollbar()->hasContentsLayer());
426#endif
427}
428
429TEST_F(ScrollingCoordinatorChromiumTest, rtlIframe)
430{
431    registerMockedHttpURLLoad("rtl-iframe.html");
432    registerMockedHttpURLLoad("rtl-iframe-inner.html");
433    navigateTo(m_baseURL + "rtl-iframe.html");
434    forceFullCompositingUpdate();
435
436    // Verify the properties of the accelerated scrolling element starting from the RenderObject
437    // all the way to the WebLayer.
438    Element* scrollableFrame = frame()->document()->getElementById("scrollable");
439    ASSERT_TRUE(scrollableFrame);
440
441    RenderObject* renderer = scrollableFrame->renderer();
442    ASSERT_TRUE(renderer);
443    ASSERT_TRUE(renderer->isWidget());
444
445    RenderWidget* renderWidget = toRenderWidget(renderer);
446    ASSERT_TRUE(renderWidget);
447    ASSERT_TRUE(renderWidget->widget());
448    ASSERT_TRUE(renderWidget->widget()->isFrameView());
449
450    FrameView* innerFrameView = toFrameView(renderWidget->widget());
451    RenderView* innerRenderView = innerFrameView->renderView();
452    ASSERT_TRUE(innerRenderView);
453
454    RenderLayerCompositor* innerCompositor = innerRenderView->compositor();
455    ASSERT_TRUE(innerCompositor->inCompositingMode());
456    ASSERT_TRUE(innerCompositor->scrollLayer());
457
458    GraphicsLayer* scrollLayer = innerCompositor->scrollLayer();
459    ASSERT_EQ(innerFrameView, scrollLayer->scrollableArea());
460
461    WebLayer* webScrollLayer = scrollLayer->platformLayer();
462    ASSERT_TRUE(webScrollLayer->scrollable());
463
464    int expectedScrollPosition = 958 + (innerFrameView->verticalScrollbar()->isOverlayScrollbar() ? 0 : 15);
465    ASSERT_EQ(expectedScrollPosition, webScrollLayer->scrollPosition().x);
466}
467
468TEST_F(ScrollingCoordinatorChromiumTest, setupScrollbarLayerShouldNotCrash)
469{
470    registerMockedHttpURLLoad("setup_scrollbar_layer_crash.html");
471    navigateTo(m_baseURL + "setup_scrollbar_layer_crash.html");
472    forceFullCompositingUpdate();
473    // This test document setup an iframe with scrollbars, then switch to
474    // an empty document by javascript.
475}
476
477} // namespace
478