1// Copyright 2014 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 "config.h"
6
7#include "core/frame/PinchViewport.h"
8
9#include "core/frame/FrameHost.h"
10#include "core/frame/LocalFrame.h"
11#include "core/page/Page.h"
12#include "core/rendering/RenderObject.h"
13#include "core/rendering/RenderView.h"
14#include "core/rendering/compositing/CompositedLayerMapping.h"
15#include "core/rendering/compositing/RenderLayerCompositor.h"
16#include "core/testing/URLTestHelpers.h"
17#include "public/platform/Platform.h"
18#include "public/platform/WebLayerTreeView.h"
19#include "public/platform/WebUnitTestSupport.h"
20#include "public/web/WebContextMenuData.h"
21#include "public/web/WebFrameClient.h"
22#include "public/web/WebInputEvent.h"
23#include "public/web/WebScriptSource.h"
24#include "public/web/WebSettings.h"
25#include "public/web/WebViewClient.h"
26#include "web/WebLocalFrameImpl.h"
27#include "web/tests/FrameTestHelpers.h"
28#include <gmock/gmock.h>
29#include <gtest/gtest.h>
30
31#define EXPECT_POINT_EQ(expected, actual) \
32    do { \
33        EXPECT_EQ((expected).x(), (actual).x()); \
34        EXPECT_EQ((expected).y(), (actual).y()); \
35    } while (false)
36
37#define EXPECT_FLOAT_POINT_EQ(expected, actual) \
38    do { \
39        EXPECT_FLOAT_EQ((expected).x(), (actual).x()); \
40        EXPECT_FLOAT_EQ((expected).y(), (actual).y()); \
41    } while (false)
42
43#define EXPECT_POINT_EQ(expected, actual) \
44    do { \
45        EXPECT_EQ((expected).x(), (actual).x()); \
46        EXPECT_EQ((expected).y(), (actual).y()); \
47    } while (false)
48
49#define EXPECT_SIZE_EQ(expected, actual) \
50    do { \
51        EXPECT_EQ((expected).width(), (actual).width()); \
52        EXPECT_EQ((expected).height(), (actual).height()); \
53    } while (false)
54
55#define EXPECT_FLOAT_SIZE_EQ(expected, actual) \
56    do { \
57        EXPECT_FLOAT_EQ((expected).width(), (actual).width()); \
58        EXPECT_FLOAT_EQ((expected).height(), (actual).height()); \
59    } while (false)
60
61#define EXPECT_FLOAT_RECT_EQ(expected, actual) \
62    do { \
63        EXPECT_FLOAT_EQ((expected).x(), (actual).x()); \
64        EXPECT_FLOAT_EQ((expected).y(), (actual).y()); \
65        EXPECT_FLOAT_EQ((expected).width(), (actual).width()); \
66        EXPECT_FLOAT_EQ((expected).height(), (actual).height()); \
67    } while (false)
68
69
70using namespace blink;
71
72using ::testing::_;
73using ::testing::PrintToString;
74using ::testing::Mock;
75
76namespace blink {
77::std::ostream& operator<<(::std::ostream& os, const WebContextMenuData& data)
78{
79    return os << "Context menu location: ["
80        << data.mousePosition.x << ", " << data.mousePosition.y << "]";
81}
82}
83
84
85namespace {
86
87class PinchViewportTest : public testing::Test {
88public:
89    PinchViewportTest()
90        : m_baseURL("http://www.test.com/")
91    {
92    }
93
94    void initializeWithDesktopSettings(void (*overrideSettingsFunc)(WebSettings*) = 0)
95    {
96        if (!overrideSettingsFunc)
97            overrideSettingsFunc = &configureSettings;
98        m_helper.initialize(true, 0, &m_mockWebViewClient, overrideSettingsFunc);
99        webViewImpl()->setPageScaleFactorLimits(1, 4);
100    }
101
102    void initializeWithAndroidSettings(void (*overrideSettingsFunc)(WebSettings*) = 0)
103    {
104        if (!overrideSettingsFunc)
105            overrideSettingsFunc = &configureAndroidSettings;
106        m_helper.initialize(true, 0, &m_mockWebViewClient, overrideSettingsFunc);
107    }
108
109    virtual ~PinchViewportTest()
110    {
111        Platform::current()->unitTestSupport()->unregisterAllMockedURLs();
112    }
113
114    void navigateTo(const std::string& url)
115    {
116        FrameTestHelpers::loadFrame(webViewImpl()->mainFrame(), url);
117    }
118
119    void forceFullCompositingUpdate()
120    {
121        webViewImpl()->layout();
122    }
123
124    void registerMockedHttpURLLoad(const std::string& fileName)
125    {
126        URLTestHelpers::registerMockedURLFromBaseURL(WebString::fromUTF8(m_baseURL.c_str()), WebString::fromUTF8(fileName.c_str()));
127    }
128
129    WebLayer* getRootScrollLayer()
130    {
131        RenderLayerCompositor* compositor = frame()->contentRenderer()->compositor();
132        ASSERT(compositor);
133        ASSERT(compositor->scrollLayer());
134
135        WebLayer* webScrollLayer = compositor->scrollLayer()->platformLayer();
136        return webScrollLayer;
137    }
138
139    WebViewImpl* webViewImpl() const { return m_helper.webViewImpl(); }
140    LocalFrame* frame() const { return m_helper.webViewImpl()->mainFrameImpl()->frame(); }
141
142    static void configureSettings(WebSettings* settings)
143    {
144        settings->setJavaScriptEnabled(true);
145        settings->setAcceleratedCompositingEnabled(true);
146        settings->setPreferCompositingToLCDTextEnabled(true);
147        settings->setPinchVirtualViewportEnabled(true);
148    }
149
150    static void configureAndroidSettings(WebSettings* settings)
151    {
152        configureSettings(settings);
153        settings->setViewportEnabled(true);
154        settings->setViewportMetaEnabled(true);
155        settings->setShrinksViewportContentToFit(true);
156        settings->setMainFrameResizesAreOrientationChanges(true);
157    }
158
159protected:
160    std::string m_baseURL;
161    FrameTestHelpers::TestWebViewClient m_mockWebViewClient;
162
163private:
164    FrameTestHelpers::WebViewHelper m_helper;
165};
166
167// Test that resizing the PinchViewport works as expected and that resizing the
168// WebView resizes the PinchViewport.
169TEST_F(PinchViewportTest, TestResize)
170{
171    initializeWithDesktopSettings();
172    webViewImpl()->resize(IntSize(320, 240));
173
174    navigateTo("about:blank");
175    forceFullCompositingUpdate();
176
177    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
178
179    IntSize webViewSize = webViewImpl()->size();
180
181    // Make sure the pinch viewport was initialized.
182    EXPECT_SIZE_EQ(webViewSize, pinchViewport.size());
183
184    // Resizing the WebView should change the PinchViewport.
185    webViewSize = IntSize(640, 480);
186    webViewImpl()->resize(webViewSize);
187    EXPECT_SIZE_EQ(webViewSize, IntSize(webViewImpl()->size()));
188    EXPECT_SIZE_EQ(webViewSize, pinchViewport.size());
189
190    // Resizing the pinch viewport shouldn't affect the WebView.
191    IntSize newViewportSize = IntSize(320, 200);
192    pinchViewport.setSize(newViewportSize);
193    EXPECT_SIZE_EQ(webViewSize, IntSize(webViewImpl()->size()));
194    EXPECT_SIZE_EQ(newViewportSize, pinchViewport.size());
195}
196
197// Test that the PinchViewport works as expected in case of a scaled
198// and scrolled viewport - scroll down.
199TEST_F(PinchViewportTest, TestResizeAfterVerticalScroll)
200{
201    /*
202                 200                                 200
203        |                   |               |                   |
204        |                   |               |                   |
205        |                   | 800           |                   | 800
206        |-------------------|               |                   |
207        |                   |               |                   |
208        |                   |               |                   |
209        |                   |               |                   |
210        |                   |   -------->   |                   |
211        | 300               |               |                   |
212        |                   |               |                   |
213        |               400 |               |                   |
214        |                   |               |-------------------|
215        |                   |               |      75           |
216        | 50                |               | 50             100|
217        o-----              |               o----               |
218        |    |              |               |   |  25           |
219        |    |100           |               |-------------------|
220        |    |              |               |                   |
221        |    |              |               |                   |
222        --------------------                --------------------
223
224     */
225
226    initializeWithAndroidSettings();
227
228    registerMockedHttpURLLoad("200-by-800-viewport.html");
229    navigateTo(m_baseURL + "200-by-800-viewport.html");
230
231    webViewImpl()->resize(IntSize(100, 200));
232
233    // Scroll main frame to the bottom of the document
234    webViewImpl()->setMainFrameScrollOffset(WebPoint(0, 400));
235    EXPECT_POINT_EQ(IntPoint(0, 400), frame()->view()->scrollPosition());
236
237    webViewImpl()->setPageScaleFactor(2.0);
238
239    // Scroll pinch viewport to the bottom of the main frame
240    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
241    pinchViewport.setLocation(FloatPoint(0, 300));
242    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 300), pinchViewport.location());
243
244    // Verify the initial size of the pinch viewport in the CSS pixels
245    EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 100), pinchViewport.visibleRect().size());
246
247    // Perform the resizing
248    webViewImpl()->resize(IntSize(200, 100));
249
250    // After resizing the scale changes 2.0 -> 4.0
251    EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 25), pinchViewport.visibleRect().size());
252
253    EXPECT_POINT_EQ(IntPoint(0, 625), frame()->view()->scrollPosition());
254    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 75), pinchViewport.location());
255}
256
257// Test that the PinchViewport works as expected in case if a scaled
258// and scrolled viewport - scroll right.
259TEST_F(PinchViewportTest, TestResizeAfterHorizontalScroll)
260{
261    /*
262                 200                                 200
263        ---------------o-----               ---------------o-----
264        |              |    |               |            25|    |
265        |              |    |               |              -----|
266        |           100|    |               |100             50 |
267        |              |    |               |                   |
268        |              ---- |               |-------------------|
269        |                   |               |                   |
270        |                   |               |                   |
271        |                   |               |                   |
272        |                   |               |                   |
273        |                   |               |                   |
274        |400                |   --------->  |                   |
275        |                   |               |                   |
276        |                   |               |                   |
277        |                   |               |                   |
278        |                   |               |                   |
279        |                   |               |                   |
280        |                   |               |                   |
281        |                   |               |                   |
282        |                   |               |                   |
283        |-------------------|               |                   |
284        |                   |               |                   |
285
286     */
287
288    initializeWithAndroidSettings();
289
290    registerMockedHttpURLLoad("200-by-800-viewport.html");
291    navigateTo(m_baseURL + "200-by-800-viewport.html");
292
293    webViewImpl()->resize(IntSize(100, 200));
294
295    // Outer viewport takes the whole width of the document.
296
297    webViewImpl()->setPageScaleFactor(2.0);
298
299    // Scroll pinch viewport to the right edge of the frame
300    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
301    pinchViewport.setLocation(FloatPoint(150, 0));
302    EXPECT_FLOAT_POINT_EQ(FloatPoint(150, 0), pinchViewport.location());
303
304    // Verify the initial size of the pinch viewport in the CSS pixels
305    EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 100), pinchViewport.visibleRect().size());
306
307    webViewImpl()->resize(IntSize(200, 100));
308
309    // After resizing the scale changes 2.0 -> 4.0
310    EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 25), pinchViewport.visibleRect().size());
311
312    EXPECT_POINT_EQ(IntPoint(0, 0), frame()->view()->scrollPosition());
313    EXPECT_FLOAT_POINT_EQ(FloatPoint(150, 0), pinchViewport.location());
314}
315
316static void disableAcceleratedCompositing(WebSettings* settings)
317{
318    PinchViewportTest::configureSettings(settings);
319    // FIXME: This setting is being removed, so this test needs to be rewritten to
320    // do something else. crbug.com/173949
321    settings->setAcceleratedCompositingEnabled(false);
322}
323
324// Test that the container layer gets sized properly if the WebView is resized
325// prior to the PinchViewport being attached to the layer tree.
326TEST_F(PinchViewportTest, TestWebViewResizedBeforeAttachment)
327{
328    initializeWithDesktopSettings(disableAcceleratedCompositing);
329    webViewImpl()->resize(IntSize(320, 240));
330
331    navigateTo("about:blank");
332    forceFullCompositingUpdate();
333    webViewImpl()->settings()->setAcceleratedCompositingEnabled(true);
334    webViewImpl()->layout();
335
336    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
337    EXPECT_FLOAT_SIZE_EQ(FloatSize(320, 240), pinchViewport.containerLayer()->size());
338}
339// Make sure that the visibleRect method acurately reflects the scale and scroll location
340// of the viewport.
341TEST_F(PinchViewportTest, TestVisibleRect)
342{
343    initializeWithDesktopSettings();
344    webViewImpl()->resize(IntSize(320, 240));
345
346    navigateTo("about:blank");
347    forceFullCompositingUpdate();
348
349    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
350
351    // Initial visible rect should be the whole frame.
352    EXPECT_SIZE_EQ(IntSize(webViewImpl()->size()), pinchViewport.size());
353
354    // Viewport is whole frame.
355    IntSize size = IntSize(400, 200);
356    webViewImpl()->resize(size);
357    webViewImpl()->layout();
358    pinchViewport.setSize(size);
359
360    // Scale the viewport to 2X; size should not change.
361    FloatRect expectedRect(FloatPoint(0, 0), size);
362    expectedRect.scale(0.5);
363    pinchViewport.setScale(2);
364    EXPECT_EQ(2, pinchViewport.scale());
365    EXPECT_SIZE_EQ(size, pinchViewport.size());
366    EXPECT_FLOAT_RECT_EQ(expectedRect, pinchViewport.visibleRect());
367
368    // Move the viewport.
369    expectedRect.setLocation(FloatPoint(5, 7));
370    pinchViewport.setLocation(expectedRect.location());
371    EXPECT_FLOAT_RECT_EQ(expectedRect, pinchViewport.visibleRect());
372
373    expectedRect.setLocation(FloatPoint(200, 100));
374    pinchViewport.setLocation(expectedRect.location());
375    EXPECT_FLOAT_RECT_EQ(expectedRect, pinchViewport.visibleRect());
376
377    // Scale the viewport to 3X to introduce some non-int values.
378    FloatPoint oldLocation = expectedRect.location();
379    expectedRect = FloatRect(FloatPoint(), size);
380    expectedRect.scale(1 / 3.0f);
381    expectedRect.setLocation(oldLocation);
382    pinchViewport.setScale(3);
383    EXPECT_FLOAT_RECT_EQ(expectedRect, pinchViewport.visibleRect());
384
385    expectedRect.setLocation(FloatPoint(0.25f, 0.333f));
386    pinchViewport.setLocation(expectedRect.location());
387    EXPECT_FLOAT_RECT_EQ(expectedRect, pinchViewport.visibleRect());
388}
389
390// Test that the viewport's scroll offset is always appropriately bounded such that the
391// pinch viewport always stays within the bounds of the main frame.
392TEST_F(PinchViewportTest, TestOffsetClamping)
393{
394    initializeWithDesktopSettings();
395    webViewImpl()->resize(IntSize(320, 240));
396
397    navigateTo("about:blank");
398    forceFullCompositingUpdate();
399
400    // Pinch viewport should be initialized to same size as frame so no scrolling possible.
401    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
402    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
403
404    pinchViewport.setLocation(FloatPoint(-1, -2));
405    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
406
407    pinchViewport.setLocation(FloatPoint(100, 200));
408    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
409
410    pinchViewport.setLocation(FloatPoint(-5, 10));
411    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
412
413    // Scale by 2x. The viewport's visible rect should now have a size of 160x120.
414    pinchViewport.setScale(2);
415    FloatPoint location(10, 50);
416    pinchViewport.setLocation(location);
417    EXPECT_FLOAT_POINT_EQ(location, pinchViewport.visibleRect().location());
418
419    pinchViewport.setLocation(FloatPoint(1000, 2000));
420    EXPECT_FLOAT_POINT_EQ(FloatPoint(160, 120), pinchViewport.visibleRect().location());
421
422    pinchViewport.setLocation(FloatPoint(-1000, -2000));
423    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
424
425    // Make sure offset gets clamped on scale out. Scale to 1.25 so the viewport is 256x192.
426    pinchViewport.setLocation(FloatPoint(160, 120));
427    pinchViewport.setScale(1.25);
428    EXPECT_FLOAT_POINT_EQ(FloatPoint(64, 48), pinchViewport.visibleRect().location());
429
430    // Scale out smaller than 1.
431    pinchViewport.setScale(0.25);
432    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
433}
434
435// Test that the viewport can be scrolled around only within the main frame in the presence
436// of viewport resizes, as would be the case if the on screen keyboard came up.
437TEST_F(PinchViewportTest, TestOffsetClampingWithResize)
438{
439    initializeWithDesktopSettings();
440    webViewImpl()->resize(IntSize(320, 240));
441
442    navigateTo("about:blank");
443    forceFullCompositingUpdate();
444
445    // Pinch viewport should be initialized to same size as frame so no scrolling possible.
446    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
447    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
448
449    // Shrink the viewport vertically. The resize shouldn't affect the location, but it
450    // should allow vertical scrolling.
451    pinchViewport.setSize(IntSize(320, 200));
452    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
453    pinchViewport.setLocation(FloatPoint(10, 20));
454    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 20), pinchViewport.visibleRect().location());
455    pinchViewport.setLocation(FloatPoint(0, 100));
456    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 40), pinchViewport.visibleRect().location());
457    pinchViewport.setLocation(FloatPoint(0, 10));
458    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 10), pinchViewport.visibleRect().location());
459    pinchViewport.setLocation(FloatPoint(0, -100));
460    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
461
462    // Repeat the above but for horizontal dimension.
463    pinchViewport.setSize(IntSize(280, 240));
464    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
465    pinchViewport.setLocation(FloatPoint(10, 20));
466    EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 0), pinchViewport.visibleRect().location());
467    pinchViewport.setLocation(FloatPoint(100, 0));
468    EXPECT_FLOAT_POINT_EQ(FloatPoint(40, 0), pinchViewport.visibleRect().location());
469    pinchViewport.setLocation(FloatPoint(10, 0));
470    EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 0), pinchViewport.visibleRect().location());
471    pinchViewport.setLocation(FloatPoint(-100, 0));
472    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
473
474    // Now with both dimensions.
475    pinchViewport.setSize(IntSize(280, 200));
476    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
477    pinchViewport.setLocation(FloatPoint(10, 20));
478    EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 20), pinchViewport.visibleRect().location());
479    pinchViewport.setLocation(FloatPoint(100, 100));
480    EXPECT_FLOAT_POINT_EQ(FloatPoint(40, 40), pinchViewport.visibleRect().location());
481    pinchViewport.setLocation(FloatPoint(10, 3));
482    EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 3), pinchViewport.visibleRect().location());
483    pinchViewport.setLocation(FloatPoint(-10, -4));
484    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
485}
486
487// Test that the viewport is scrollable but bounded appropriately within the main frame
488// when we apply both scaling and resizes.
489TEST_F(PinchViewportTest, TestOffsetClampingWithResizeAndScale)
490{
491    initializeWithDesktopSettings();
492    webViewImpl()->resize(IntSize(320, 240));
493
494    navigateTo("about:blank");
495    forceFullCompositingUpdate();
496
497    // Pinch viewport should be initialized to same size as WebView so no scrolling possible.
498    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
499    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), pinchViewport.visibleRect().location());
500
501    // Zoom in to 2X so we can scroll the viewport to 160x120.
502    pinchViewport.setScale(2);
503    pinchViewport.setLocation(FloatPoint(200, 200));
504    EXPECT_FLOAT_POINT_EQ(FloatPoint(160, 120), pinchViewport.visibleRect().location());
505
506    // Now resize the viewport to make it 10px smaller. Since we're zoomed in by 2X it should
507    // allow us to scroll by 5px more.
508    pinchViewport.setSize(IntSize(310, 230));
509    pinchViewport.setLocation(FloatPoint(200, 200));
510    EXPECT_FLOAT_POINT_EQ(FloatPoint(165, 125), pinchViewport.visibleRect().location());
511
512    // The viewport can be larger than the main frame (currently 320, 240) though typically
513    // the scale will be clamped to prevent it from actually being larger. Make sure size
514    // changes clamp the offset so the inner remains within the outer.
515    pinchViewport.setSize(IntSize(330, 250));
516    EXPECT_SIZE_EQ(IntSize(330, 250), pinchViewport.size());
517    EXPECT_FLOAT_POINT_EQ(FloatPoint(155, 115), pinchViewport.visibleRect().location());
518    pinchViewport.setLocation(FloatPoint(200, 200));
519    EXPECT_FLOAT_POINT_EQ(FloatPoint(155, 115), pinchViewport.visibleRect().location());
520
521    // Resize both the viewport and the frame to be larger.
522    webViewImpl()->resize(IntSize(640, 480));
523    webViewImpl()->layout();
524    EXPECT_SIZE_EQ(IntSize(webViewImpl()->size()), pinchViewport.size());
525    EXPECT_SIZE_EQ(IntSize(webViewImpl()->size()), frame()->view()->frameRect().size());
526    pinchViewport.setLocation(FloatPoint(1000, 1000));
527    EXPECT_FLOAT_POINT_EQ(FloatPoint(320, 240), pinchViewport.visibleRect().location());
528
529    // Make sure resizing the viewport doesn't change its offset if the resize doesn't make
530    // the viewport go out of bounds.
531    pinchViewport.setLocation(FloatPoint(200, 200));
532    pinchViewport.setSize(IntSize(880, 560));
533    EXPECT_FLOAT_POINT_EQ(FloatPoint(200, 200), pinchViewport.visibleRect().location());
534
535    // Resizing the viewport such that the viewport is out of bounds should move the
536    // viewport.
537    pinchViewport.setSize(IntSize(920, 640));
538    EXPECT_FLOAT_POINT_EQ(FloatPoint(180, 160), pinchViewport.visibleRect().location());
539}
540
541// The main FrameView's size should be set such that its the size of the pinch viewport
542// at minimum scale. If there's no explicit minimum scale set, the FrameView should be
543// set to the content width and height derived by the aspect ratio.
544TEST_F(PinchViewportTest, TestFrameViewSizedToContent)
545{
546    initializeWithAndroidSettings();
547    webViewImpl()->resize(IntSize(320, 240));
548
549    registerMockedHttpURLLoad("200-by-300-viewport.html");
550    navigateTo(m_baseURL + "200-by-300-viewport.html");
551
552    webViewImpl()->resize(IntSize(600, 800));
553    webViewImpl()->layout();
554
555    EXPECT_SIZE_EQ(IntSize(200, 266),
556        webViewImpl()->mainFrameImpl()->frameView()->frameRect().size());
557}
558
559// The main FrameView's size should be set such that its the size of the pinch viewport
560// at minimum scale. On Desktop, the minimum scale is set at 1 so make sure the FrameView
561// is sized to the viewport.
562TEST_F(PinchViewportTest, TestFrameViewSizedToMinimumScale)
563{
564    initializeWithDesktopSettings();
565    webViewImpl()->resize(IntSize(320, 240));
566
567    registerMockedHttpURLLoad("200-by-300.html");
568    navigateTo(m_baseURL + "200-by-300.html");
569
570    webViewImpl()->resize(IntSize(100, 160));
571    webViewImpl()->layout();
572
573    EXPECT_SIZE_EQ(IntSize(100, 160),
574        webViewImpl()->mainFrameImpl()->frameView()->frameRect().size());
575}
576
577// The main FrameView's size should be set such that its the size of the pinch viewport
578// at minimum scale. Test that the FrameView is appropriately sized in the presence
579// of a viewport <meta> tag.
580TEST_F(PinchViewportTest, TestFrameViewSizedToViewportMetaMinimumScale)
581{
582    initializeWithAndroidSettings();
583    webViewImpl()->resize(IntSize(320, 240));
584
585    registerMockedHttpURLLoad("200-by-300-min-scale-2.html");
586    navigateTo(m_baseURL + "200-by-300-min-scale-2.html");
587
588    webViewImpl()->resize(IntSize(100, 160));
589    webViewImpl()->layout();
590
591    EXPECT_SIZE_EQ(IntSize(50, 80),
592        webViewImpl()->mainFrameImpl()->frameView()->frameRect().size());
593}
594
595// Test that the pinch viewport still gets sized in AutoSize/AutoResize mode.
596TEST_F(PinchViewportTest, TestPinchViewportGetsSizeInAutoSizeMode)
597{
598    initializeWithDesktopSettings();
599
600    EXPECT_SIZE_EQ(IntSize(0, 0), IntSize(webViewImpl()->size()));
601    EXPECT_SIZE_EQ(IntSize(0, 0), frame()->page()->frameHost().pinchViewport().size());
602
603    webViewImpl()->enableAutoResizeMode(WebSize(10, 10), WebSize(1000, 1000));
604
605    registerMockedHttpURLLoad("200-by-300.html");
606    navigateTo(m_baseURL + "200-by-300.html");
607
608    EXPECT_SIZE_EQ(IntSize(200, 300), frame()->page()->frameHost().pinchViewport().size());
609}
610
611// Test that the text selection handle's position accounts for the pinch viewport.
612TEST_F(PinchViewportTest, TestTextSelectionHandles)
613{
614    initializeWithDesktopSettings();
615    webViewImpl()->resize(IntSize(500, 800));
616
617    registerMockedHttpURLLoad("pinch-viewport-input-field.html");
618    navigateTo(m_baseURL + "pinch-viewport-input-field.html");
619
620    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
621    webViewImpl()->setInitialFocus(false);
622
623    WebRect originalAnchor;
624    WebRect originalFocus;
625    webViewImpl()->selectionBounds(originalAnchor, originalFocus);
626
627    webViewImpl()->setPageScaleFactor(2);
628    pinchViewport.setLocation(FloatPoint(100, 400));
629
630    WebRect anchor;
631    WebRect focus;
632    webViewImpl()->selectionBounds(anchor, focus);
633
634    IntPoint expected(IntRect(originalAnchor).location());
635    expected.moveBy(-flooredIntPoint(pinchViewport.visibleRect().location()));
636    expected.scale(pinchViewport.scale(), pinchViewport.scale());
637
638    EXPECT_POINT_EQ(expected, IntRect(anchor).location());
639    EXPECT_POINT_EQ(expected, IntRect(focus).location());
640
641    // FIXME(bokan) - http://crbug.com/364154 - Figure out how to test text selection
642    // as well rather than just carret.
643}
644
645// Test that the HistoryItem for the page stores the pinch viewport's offset and scale.
646TEST_F(PinchViewportTest, TestSavedToHistoryItem)
647{
648    initializeWithDesktopSettings();
649    webViewImpl()->resize(IntSize(200, 300));
650    webViewImpl()->layout();
651
652    registerMockedHttpURLLoad("200-by-300.html");
653    navigateTo(m_baseURL + "200-by-300.html");
654
655    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0),
656        toLocalFrame(webViewImpl()->page()->mainFrame())->loader().currentItem()->pinchViewportScrollPoint());
657
658    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
659    pinchViewport.setScale(2);
660
661    EXPECT_EQ(2, toLocalFrame(webViewImpl()->page()->mainFrame())->loader().currentItem()->pageScaleFactor());
662
663    pinchViewport.setLocation(FloatPoint(10, 20));
664
665    EXPECT_FLOAT_POINT_EQ(FloatPoint(10, 20),
666        toLocalFrame(webViewImpl()->page()->mainFrame())->loader().currentItem()->pinchViewportScrollPoint());
667}
668
669// Test restoring a HistoryItem properly restores the pinch viewport's state.
670TEST_F(PinchViewportTest, TestRestoredFromHistoryItem)
671{
672    initializeWithDesktopSettings();
673    webViewImpl()->resize(IntSize(200, 300));
674
675    registerMockedHttpURLLoad("200-by-300.html");
676
677    WebHistoryItem item;
678    item.initialize();
679    WebURL destinationURL(URLTestHelpers::toKURL(m_baseURL + "200-by-300.html"));
680    item.setURLString(destinationURL.string());
681    item.setPinchViewportScrollOffset(WebFloatPoint(100, 120));
682    item.setPageScaleFactor(2);
683
684    FrameTestHelpers::loadHistoryItem(webViewImpl()->mainFrame(), item, WebHistoryDifferentDocumentLoad, WebURLRequest::UseProtocolCachePolicy);
685
686    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
687    EXPECT_EQ(2, pinchViewport.scale());
688
689    EXPECT_FLOAT_POINT_EQ(FloatPoint(100, 120), pinchViewport.visibleRect().location());
690}
691
692// Test restoring a HistoryItem without the pinch viewport offset falls back to distributing
693// the scroll offset between the main frame and the pinch viewport.
694TEST_F(PinchViewportTest, TestRestoredFromLegacyHistoryItem)
695{
696    initializeWithDesktopSettings();
697    webViewImpl()->resize(IntSize(100, 150));
698
699    registerMockedHttpURLLoad("200-by-300-viewport.html");
700
701    WebHistoryItem item;
702    item.initialize();
703    WebURL destinationURL(URLTestHelpers::toKURL(m_baseURL + "200-by-300-viewport.html"));
704    item.setURLString(destinationURL.string());
705    // (-1, -1) will be used if the HistoryItem is an older version prior to having
706    // pinch viewport scroll offset.
707    item.setPinchViewportScrollOffset(WebFloatPoint(-1, -1));
708    item.setScrollOffset(WebPoint(120, 180));
709    item.setPageScaleFactor(2);
710
711    FrameTestHelpers::loadHistoryItem(webViewImpl()->mainFrame(), item, WebHistoryDifferentDocumentLoad, WebURLRequest::UseProtocolCachePolicy);
712
713    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
714    EXPECT_EQ(2, pinchViewport.scale());
715    EXPECT_POINT_EQ(IntPoint(100, 150), frame()->view()->scrollPosition());
716    EXPECT_FLOAT_POINT_EQ(FloatPoint(20, 30), pinchViewport.visibleRect().location());
717}
718
719// Test that the coordinates sent into moveRangeSelection are offset by the
720// pinch viewport's location.
721TEST_F(PinchViewportTest, TestWebFrameRangeAccountsForPinchViewportScroll)
722{
723    initializeWithDesktopSettings();
724    webViewImpl()->settings()->setDefaultFontSize(12);
725    webViewImpl()->resize(WebSize(640, 480));
726    registerMockedHttpURLLoad("move_range.html");
727    navigateTo(m_baseURL + "move_range.html");
728
729    WebRect baseRect;
730    WebRect extentRect;
731
732    webViewImpl()->setPageScaleFactor(2);
733    WebFrame* mainFrame = webViewImpl()->mainFrame();
734
735    // Select some text and get the base and extent rects (that's the start of
736    // the range and its end). Do a sanity check that the expected text is
737    // selected
738    mainFrame->executeScript(WebScriptSource("selectRange();"));
739    EXPECT_EQ("ir", mainFrame->selectionAsText().utf8());
740
741    webViewImpl()->selectionBounds(baseRect, extentRect);
742    WebPoint initialPoint(baseRect.x, baseRect.y);
743    WebPoint endPoint(extentRect.x, extentRect.y);
744
745    // Move the pinch viewport over and make the selection in the same
746    // screen-space location. The selection should change to two characters to
747    // the right and down one line.
748    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
749    pinchViewport.move(FloatPoint(60, 25));
750    mainFrame->moveRangeSelection(initialPoint, endPoint);
751    EXPECT_EQ("t ", mainFrame->selectionAsText().utf8());
752}
753
754// Test that the scrollFocusedNodeIntoRect method works with the pinch viewport.
755TEST_F(PinchViewportTest, DISABLED_TestScrollFocusedNodeIntoRect)
756{
757    initializeWithDesktopSettings();
758    webViewImpl()->resize(IntSize(500, 300));
759
760    registerMockedHttpURLLoad("pinch-viewport-input-field.html");
761    navigateTo(m_baseURL + "pinch-viewport-input-field.html");
762
763    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
764    webViewImpl()->resizePinchViewport(IntSize(200, 100));
765    webViewImpl()->setInitialFocus(false);
766    pinchViewport.setLocation(FloatPoint());
767    webViewImpl()->scrollFocusedNodeIntoRect(IntRect(0, 0, 500, 200));
768
769    EXPECT_POINT_EQ(IntPoint(0, frame()->view()->maximumScrollPosition().y()),
770        frame()->view()->scrollPosition());
771    EXPECT_FLOAT_POINT_EQ(FloatPoint(150, 200), pinchViewport.visibleRect().location());
772
773    // Try it again but with the page zoomed in
774    frame()->view()->notifyScrollPositionChanged(IntPoint(0, 0));
775    webViewImpl()->resizePinchViewport(IntSize(500, 300));
776    pinchViewport.setLocation(FloatPoint(0, 0));
777
778    webViewImpl()->setPageScaleFactor(2);
779    webViewImpl()->scrollFocusedNodeIntoRect(IntRect(0, 0, 500, 200));
780    EXPECT_POINT_EQ(IntPoint(0, frame()->view()->maximumScrollPosition().y()),
781        frame()->view()->scrollPosition());
782    EXPECT_FLOAT_POINT_EQ(FloatPoint(125, 150), pinchViewport.visibleRect().location());
783
784    // Once more but make sure that we don't move the pinch viewport unless necessary.
785    registerMockedHttpURLLoad("pinch-viewport-input-field-long-and-wide.html");
786    navigateTo(m_baseURL + "pinch-viewport-input-field-long-and-wide.html");
787    webViewImpl()->setInitialFocus(false);
788    pinchViewport.setLocation(FloatPoint());
789    frame()->view()->notifyScrollPositionChanged(IntPoint(0, 0));
790    webViewImpl()->resizePinchViewport(IntSize(500, 300));
791    pinchViewport.setLocation(FloatPoint(30, 50));
792
793    webViewImpl()->setPageScaleFactor(2);
794    webViewImpl()->scrollFocusedNodeIntoRect(IntRect(0, 0, 500, 200));
795    EXPECT_POINT_EQ(IntPoint(200-30-75, 600-50-65), frame()->view()->scrollPosition());
796    EXPECT_FLOAT_POINT_EQ(FloatPoint(30, 50), pinchViewport.visibleRect().location());
797}
798
799// Test that resizing the WebView causes ViewportConstrained objects to relayout.
800TEST_F(PinchViewportTest, TestWebViewResizeCausesViewportConstrainedLayout)
801{
802    initializeWithDesktopSettings();
803    webViewImpl()->resize(IntSize(500, 300));
804
805    registerMockedHttpURLLoad("pinch-viewport-fixed-pos.html");
806    navigateTo(m_baseURL + "pinch-viewport-fixed-pos.html");
807
808    RenderObject* navbar = frame()->document()->getElementById("navbar")->renderer();
809
810    EXPECT_FALSE(navbar->needsLayout());
811
812    frame()->view()->resize(IntSize(500, 200));
813
814    EXPECT_TRUE(navbar->needsLayout());
815}
816
817class MockWebFrameClient : public WebFrameClient {
818public:
819    MOCK_METHOD1(showContextMenu, void(const WebContextMenuData&));
820};
821
822MATCHER_P2(ContextMenuAtLocation, x, y,
823    std::string(negation ? "is" : "isn't")
824    + " at expected location ["
825    + PrintToString(x) + ", " + PrintToString(y) + "]")
826{
827    return arg.mousePosition.x == x && arg.mousePosition.y == y;
828}
829
830// Test that the context menu's location is correct in the presence of pinch
831// viewport offset.
832TEST_F(PinchViewportTest, TestContextMenuShownInCorrectLocation)
833{
834    initializeWithDesktopSettings();
835    webViewImpl()->resize(IntSize(200, 300));
836
837    registerMockedHttpURLLoad("200-by-300.html");
838    navigateTo(m_baseURL + "200-by-300.html");
839
840    WebMouseEvent mouseDownEvent;
841    mouseDownEvent.type = WebInputEvent::MouseDown;
842    mouseDownEvent.x = 10;
843    mouseDownEvent.y = 10;
844    mouseDownEvent.windowX = 10;
845    mouseDownEvent.windowY = 10;
846    mouseDownEvent.globalX = 110;
847    mouseDownEvent.globalY = 210;
848    mouseDownEvent.clickCount = 1;
849    mouseDownEvent.button = WebMouseEvent::ButtonRight;
850
851    // Corresponding release event (Windows shows context menu on release).
852    WebMouseEvent mouseUpEvent(mouseDownEvent);
853    mouseUpEvent.type = WebInputEvent::MouseUp;
854
855    WebFrameClient* oldClient = webViewImpl()->mainFrameImpl()->client();
856    MockWebFrameClient mockWebFrameClient;
857    EXPECT_CALL(mockWebFrameClient, showContextMenu(ContextMenuAtLocation(mouseDownEvent.x, mouseDownEvent.y)));
858
859    // Do a sanity check with no scale applied.
860    webViewImpl()->mainFrameImpl()->setClient(&mockWebFrameClient);
861    webViewImpl()->handleInputEvent(mouseDownEvent);
862    webViewImpl()->handleInputEvent(mouseUpEvent);
863
864    Mock::VerifyAndClearExpectations(&mockWebFrameClient);
865    mouseDownEvent.button = WebMouseEvent::ButtonLeft;
866    webViewImpl()->handleInputEvent(mouseDownEvent);
867
868    // Now pinch zoom into the page and move the pinch viewport. The context
869    // menu should still appear at the location of the event, relative to the
870    // WebView.
871    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
872    webViewImpl()->setPageScaleFactor(2);
873    pinchViewport.setLocation(FloatPoint(60, 80));
874    EXPECT_CALL(mockWebFrameClient, showContextMenu(ContextMenuAtLocation(mouseDownEvent.x, mouseDownEvent.y)));
875
876    mouseDownEvent.button = WebMouseEvent::ButtonRight;
877    webViewImpl()->handleInputEvent(mouseDownEvent);
878    webViewImpl()->handleInputEvent(mouseUpEvent);
879
880    // Reset the old client so destruction can occur naturally.
881    webViewImpl()->mainFrameImpl()->setClient(oldClient);
882}
883
884// Test that the scrollIntoView correctly scrolls the main frame
885// and pinch viewports such that the given rect is centered in the viewport.
886TEST_F(PinchViewportTest, DISABLED_TestScrollingDocumentRegionIntoView)
887{
888    initializeWithDesktopSettings();
889    webViewImpl()->resize(IntSize(100, 150));
890
891    registerMockedHttpURLLoad("200-by-300-viewport.html");
892    navigateTo(m_baseURL + "200-by-300-viewport.html");
893
894    PinchViewport& pinchViewport = frame()->page()->frameHost().pinchViewport();
895
896    // Test that the pinch viewport is scrolled if the viewport has been
897    // resized (as is the case when the ChromeOS keyboard comes up) but not
898    // scaled.
899    webViewImpl()->resizePinchViewport(WebSize(100, 100));
900    pinchViewport.scrollIntoView(FloatRect(100, 250, 50, 50));
901    EXPECT_POINT_EQ(IntPoint(75, 150), frame()->view()->scrollPosition());
902    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 50), pinchViewport.visibleRect().location());
903
904    pinchViewport.scrollIntoView(FloatRect(25, 75, 50, 50));
905    EXPECT_POINT_EQ(IntPoint(0, 0), frame()->view()->scrollPosition());
906    EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 50), pinchViewport.visibleRect().location());
907
908    // Reset the pinch viewport's size, scale the page and repeat the test
909    webViewImpl()->resizePinchViewport(IntSize(100, 150));
910    webViewImpl()->setPageScaleFactor(2);
911    pinchViewport.setLocation(FloatPoint());
912
913    pinchViewport.scrollIntoView(FloatRect(50, 75, 50, 75));
914    EXPECT_POINT_EQ(IntPoint(50, 75), frame()->view()->scrollPosition());
915    EXPECT_FLOAT_POINT_EQ(FloatPoint(), pinchViewport.visibleRect().location());
916
917    pinchViewport.scrollIntoView(FloatRect(190, 290, 10, 10));
918    EXPECT_POINT_EQ(IntPoint(100, 150), frame()->view()->scrollPosition());
919    EXPECT_FLOAT_POINT_EQ(FloatPoint(50, 75), pinchViewport.visibleRect().location());
920}
921
922} // namespace
923