1// Copyright 2013 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
5package org.chromium.android_webview.test;
6
7import android.test.suitebuilder.annotation.MediumTest;
8
9import org.chromium.android_webview.AwContents;
10import org.chromium.android_webview.AwContentsClient;
11import org.chromium.android_webview.AwSettings;
12import org.chromium.base.test.util.Feature;
13import org.chromium.content.browser.test.util.CallbackHelper;
14import org.chromium.ui.gfx.DeviceDisplayInfo;
15
16import java.util.Locale;
17import java.util.concurrent.Callable;
18
19/**
20 * Tests for legacy quirks (compatibility with WebView Classic).
21 */
22public class AwLegacyQuirksTest extends AwTestBase {
23
24    @MediumTest
25    @Feature({"AndroidWebView"})
26    public void testTargetDensityDpi() throws Throwable {
27        final TestAwContentsClient contentClient = new TestAwContentsClient();
28        final AwTestContainerView testContainerView =
29                createAwTestContainerViewOnMainSyncInQuirksMode(contentClient);
30        final AwContents awContents = testContainerView.getAwContents();
31        AwSettings settings = getAwSettingsOnUiThread(awContents);
32        CallbackHelper onPageFinishedHelper = contentClient.getOnPageFinishedHelper();
33
34        final String pageTemplate = "<html><head>" +
35                "<meta name='viewport' content='width=device-width, target-densityDpi=%s' />" +
36                "</head><body onload='document.title=document.body.clientWidth'></body></html>";
37        final String pageDeviceDpi = String.format((Locale) null, pageTemplate, "device-dpi");
38        final String pageHighDpi = String.format((Locale) null, pageTemplate, "high-dpi");
39        final String pageDpi100 = String.format((Locale) null, pageTemplate, "100");
40
41        settings.setJavaScriptEnabled(true);
42
43        DeviceDisplayInfo deviceInfo =
44                DeviceDisplayInfo.create(getInstrumentation().getTargetContext());
45        loadDataSync(awContents, onPageFinishedHelper, pageDeviceDpi, "text/html", false);
46        int actualWidth = Integer.parseInt(getTitleOnUiThread(awContents));
47        assertEquals(deviceInfo.getDisplayWidth(), actualWidth, 10f);
48
49        float displayWidth = (deviceInfo.getDisplayWidth());
50        float deviceDpi = (float) (160f * deviceInfo.getDIPScale());
51
52        loadDataSync(awContents, onPageFinishedHelper, pageHighDpi, "text/html", false);
53        actualWidth = Integer.parseInt(getTitleOnUiThread(awContents));
54        assertEquals(displayWidth * (240f / deviceDpi), actualWidth, 10f);
55
56        loadDataSync(awContents, onPageFinishedHelper, pageDpi100, "text/html", false);
57        actualWidth = Integer.parseInt(getTitleOnUiThread(awContents));
58        assertEquals(displayWidth * (100f / deviceDpi), actualWidth, 10f);
59    }
60
61    @MediumTest
62    @Feature({"AndroidWebView"})
63    public void testWideViewportInitialScaleDoesNotExpandFixedLayoutWidth() throws Throwable {
64        final TestAwContentsClient contentClient = new TestAwContentsClient();
65        final AwTestContainerView testContainerView =
66                createAwTestContainerViewOnMainSyncInQuirksMode(contentClient);
67        final AwContents awContents = testContainerView.getAwContents();
68        AwSettings settings = getAwSettingsOnUiThread(awContents);
69        CallbackHelper onPageFinishedHelper = contentClient.getOnPageFinishedHelper();
70
71        final String page = "<html><head>" +
72                "<meta name='viewport' content='width=device-width, initial-scale=0.5' />" +
73                "</head><body onload='document.title=document.body.clientWidth'></body></html>";
74
75        settings.setJavaScriptEnabled(true);
76        settings.setUseWideViewPort(true);
77
78        DeviceDisplayInfo deviceInfo =
79                DeviceDisplayInfo.create(getInstrumentation().getTargetContext());
80        loadDataSync(awContents, onPageFinishedHelper, page, "text/html", false);
81        float displayWidth = (float) (deviceInfo.getDisplayWidth() / deviceInfo.getDIPScale());
82        int actualWidth = Integer.parseInt(getTitleOnUiThread(awContents));
83        assertEquals(displayWidth, actualWidth, 10f);
84        assertEquals(1.0f, getScaleOnUiThread(awContents));
85    }
86
87    @MediumTest
88    @Feature({"AndroidWebView"})
89    public void testZeroValuesQuirk() throws Throwable {
90        final TestAwContentsClient contentClient = new TestAwContentsClient();
91        final AwTestContainerView testContainerView =
92                createAwTestContainerViewOnMainSyncInQuirksMode(contentClient);
93        final AwContents awContents = testContainerView.getAwContents();
94        AwSettings settings = getAwSettingsOnUiThread(awContents);
95        CallbackHelper onPageFinishedHelper = contentClient.getOnPageFinishedHelper();
96
97        final String page = "<html><head>" +
98                "<meta name='viewport' content='width=0, height=0, initial-scale=0.0, " +
99                "    minimum-scale=0.0, maximum-scale=0.0' />" +
100                "</head><body onload='document.title=document.body.clientWidth'></body></html>";
101
102        settings.setJavaScriptEnabled(true);
103
104        DeviceDisplayInfo deviceInfo =
105                DeviceDisplayInfo.create(getInstrumentation().getTargetContext());
106        loadDataSync(awContents, onPageFinishedHelper, page, "text/html", false);
107        float displayWidth = (float) (deviceInfo.getDisplayWidth() / deviceInfo.getDIPScale());
108        int actualWidth = Integer.parseInt(getTitleOnUiThread(awContents));
109        assertEquals(displayWidth, actualWidth, 10f);
110        assertEquals(1.0f, getScaleOnUiThread(awContents));
111
112        settings.setUseWideViewPort(true);
113        loadDataSync(awContents, onPageFinishedHelper, page, "text/html", false);
114        actualWidth = Integer.parseInt(getTitleOnUiThread(awContents));
115        assertEquals(displayWidth, actualWidth, 10f);
116        assertEquals(1.0f, getScaleOnUiThread(awContents));
117    }
118
119    @MediumTest
120    @Feature({"AndroidWebView"})
121    public void testScreenSizeInPhysicalPixelsQuirk() throws Throwable {
122        final TestAwContentsClient contentClient = new TestAwContentsClient();
123        final AwTestContainerView testContainerView =
124                createAwTestContainerViewOnMainSyncInQuirksMode(contentClient);
125        final AwContents awContents = testContainerView.getAwContents();
126        AwSettings settings = getAwSettingsOnUiThread(awContents);
127        CallbackHelper onPageFinishedHelper = contentClient.getOnPageFinishedHelper();
128
129        settings.setJavaScriptEnabled(true);
130
131        loadUrlSync(awContents, onPageFinishedHelper, "about:blank");
132
133        DeviceDisplayInfo deviceInfo =
134                DeviceDisplayInfo.create(getInstrumentation().getTargetContext());
135        float dipScale = (float) deviceInfo.getDIPScale();
136        float physicalDisplayWidth = deviceInfo.getPhysicalDisplayWidth() != 0 ?
137                                     deviceInfo.getPhysicalDisplayWidth() :
138                                     deviceInfo.getDisplayWidth();
139        float cssDisplayWidth = physicalDisplayWidth / dipScale;
140        float physicalDisplayHeight = deviceInfo.getPhysicalDisplayHeight() != 0 ?
141                                      deviceInfo.getPhysicalDisplayHeight() :
142                                      deviceInfo.getDisplayHeight();
143        float cssDisplayHeight = physicalDisplayHeight / dipScale;
144
145        float screenWidth = Integer.parseInt(
146                executeJavaScriptAndWaitForResult(awContents, contentClient, "screen.width"));
147        assertEquals(physicalDisplayWidth, screenWidth, 10f);
148        float screenAvailWidth = Integer.parseInt(
149                executeJavaScriptAndWaitForResult(awContents, contentClient, "screen.availWidth"));
150        assertEquals(physicalDisplayWidth, screenAvailWidth, 10f);
151        float outerWidth = Integer.parseInt(
152                executeJavaScriptAndWaitForResult(awContents, contentClient, "outerWidth"));
153        float innerWidth = Integer.parseInt(
154                executeJavaScriptAndWaitForResult(awContents, contentClient, "innerWidth"));
155        assertEquals(innerWidth * dipScale, outerWidth, 10f);
156        String deviceWidthEqualsScreenWidth = executeJavaScriptAndWaitForResult(awContents,
157                contentClient,
158                "matchMedia(\"screen and (device-width:" + (int) screenWidth + "px)\").matches");
159        assertEquals("true", deviceWidthEqualsScreenWidth);
160
161        float screenHeight = Integer.parseInt(
162                executeJavaScriptAndWaitForResult(awContents, contentClient, "screen.height"));
163        assertEquals(physicalDisplayHeight, screenHeight, 10f);
164        float screenAvailHeight = Integer.parseInt(
165                executeJavaScriptAndWaitForResult(awContents, contentClient, "screen.availHeight"));
166        assertEquals(physicalDisplayHeight, screenAvailHeight, 10f);
167        float outerHeight = Integer.parseInt(
168                executeJavaScriptAndWaitForResult(awContents, contentClient, "outerHeight"));
169        float innerHeight = Integer.parseInt(
170                executeJavaScriptAndWaitForResult(awContents, contentClient, "innerHeight"));
171        assertEquals(innerHeight * dipScale, outerHeight, 10f);
172        String deviceHeightEqualsScreenHeight = executeJavaScriptAndWaitForResult(awContents,
173                contentClient,
174                "matchMedia(\"screen and (device-height:" + (int) screenHeight + "px)\").matches");
175        assertEquals("true", deviceHeightEqualsScreenHeight);
176    }
177
178    @MediumTest
179    @Feature({"AndroidWebView"})
180    public void testMetaMergeContentQuirk() throws Throwable {
181        final TestAwContentsClient contentClient = new TestAwContentsClient();
182        final AwTestContainerView testContainerView =
183                createAwTestContainerViewOnMainSyncInQuirksMode(contentClient);
184        final AwContents awContents = testContainerView.getAwContents();
185        AwSettings settings = getAwSettingsOnUiThread(awContents);
186        CallbackHelper onPageFinishedHelper = contentClient.getOnPageFinishedHelper();
187
188        final int pageWidth = 3000;
189        final float pageScale = 1.0f;
190        final String page = String.format((Locale) null, "<html><head>" +
191                "<meta name='viewport' content='width=%d' />" +
192                "<meta name='viewport' content='initial-scale=%.1f' />" +
193                "<meta name='viewport' content='user-scalable=0' />" +
194                "</head><body onload='document.title=document.body.clientWidth'></body></html>",
195                pageWidth, pageScale);
196
197        settings.setJavaScriptEnabled(true);
198        settings.setUseWideViewPort(true);
199        settings.setBuiltInZoomControls(true);
200        settings.setSupportZoom(true);
201
202        loadDataSync(awContents, onPageFinishedHelper, page, "text/html", false);
203        // ContentView must update itself according to the viewport setup.
204        // As we specify 'user-scalable=0', the page must become non-zoomable.
205        poll(new Callable<Boolean>() {
206            @Override
207            public Boolean call() throws Exception {
208                return !canZoomInOnUiThread(awContents) && !canZoomOutOnUiThread(awContents);
209            }
210        });
211        int width = Integer.parseInt(getTitleOnUiThread(awContents));
212        assertEquals(pageWidth, width);
213        assertEquals(pageScale, getScaleOnUiThread(awContents));
214    }
215
216    @MediumTest
217    @Feature({"AndroidWebView"})
218    public void testMetaMergeContentQuirkOverrides() throws Throwable {
219        final TestAwContentsClient contentClient = new TestAwContentsClient();
220        final AwTestContainerView testContainerView =
221                createAwTestContainerViewOnMainSyncInQuirksMode(contentClient);
222        final AwContents awContents = testContainerView.getAwContents();
223        AwSettings settings = getAwSettingsOnUiThread(awContents);
224        CallbackHelper onPageFinishedHelper = contentClient.getOnPageFinishedHelper();
225
226        final int pageWidth = 3000;
227        final String page = String.format((Locale) null, "<html><head>" +
228                "<meta name='viewport' content='width=device-width' />" +
229                "<meta name='viewport' content='width=%d' />" +
230                "</head><body onload='document.title=document.body.clientWidth'></body></html>",
231                pageWidth);
232
233        settings.setJavaScriptEnabled(true);
234        settings.setUseWideViewPort(true);
235
236        loadDataSync(awContents, onPageFinishedHelper, page, "text/html", false);
237        int width = Integer.parseInt(getTitleOnUiThread(awContents));
238        assertEquals(pageWidth, width);
239    }
240
241    @MediumTest
242    @Feature({"AndroidWebView"})
243    public void testInitialScaleClobberQuirk() throws Throwable {
244        final TestAwContentsClient contentClient = new TestAwContentsClient();
245        final AwTestContainerView testContainerView =
246                createAwTestContainerViewOnMainSyncInQuirksMode(contentClient);
247        final AwContents awContents = testContainerView.getAwContents();
248        AwSettings settings = getAwSettingsOnUiThread(awContents);
249        CallbackHelper onPageFinishedHelper = contentClient.getOnPageFinishedHelper();
250
251        final String pageTemplate = "<html><head>" +
252                "<meta name='viewport' content='initial-scale=%d' />" +
253                "</head><body>" +
254                "<div style='width:10000px;height:200px'>A big div</div>" +
255                "</body></html>";
256        final String pageScale4 = String.format((Locale) null, pageTemplate, 4);
257        final String page = String.format((Locale) null, pageTemplate, 1);
258
259        // Page scale updates are asynchronous. There is an issue that we can't
260        // reliably check, whether the scale as NOT changed (i.e. remains to be 1.0).
261        // So we first change the scale to some non-default value, and then wait
262        // until it gets back to 1.0.
263        int onScaleChangedCallCount = contentClient.getOnScaleChangedHelper().getCallCount();
264        loadDataSync(awContents, onPageFinishedHelper, pageScale4, "text/html", false);
265        contentClient.getOnScaleChangedHelper().waitForCallback(onScaleChangedCallCount);
266        assertEquals(4.0f, getScaleOnUiThread(awContents));
267        // The following call to set initial scale will be ignored. However, a temporary
268        // page scale change may occur, and this makes the usual onScaleChanged-based workflow
269        // flaky. So instead, we are just polling the scale until it becomes 1.0.
270        settings.setInitialPageScale(50);
271        loadDataSync(awContents, onPageFinishedHelper, page, "text/html", false);
272        ensureScaleBecomes(1.0f, awContents);
273    }
274
275    @MediumTest
276    @Feature({"AndroidWebView"})
277    public void testNoUserScalableQuirk() throws Throwable {
278        final TestAwContentsClient contentClient = new TestAwContentsClient();
279        final AwTestContainerView testContainerView =
280                createAwTestContainerViewOnMainSyncInQuirksMode(contentClient);
281        final AwContents awContents = testContainerView.getAwContents();
282        CallbackHelper onPageFinishedHelper = contentClient.getOnPageFinishedHelper();
283
284        final String pageScale4 = "<html><head>" +
285                "<meta name='viewport' content='initial-scale=4' />" +
286                "</head><body>" +
287                "<div style='width:10000px;height:200px'>A big div</div>" +
288                "</body></html>";
289        final String page = "<html><head>" +
290                "<meta name='viewport' " +
291                "content='width=device-width,initial-scale=2,user-scalable=no' />" +
292                "</head><body>" +
293                "<div style='width:10000px;height:200px'>A big div</div>" +
294                "</body></html>";
295
296        // Page scale updates are asynchronous. There is an issue that we can't
297        // reliably check, whether the scale as NOT changed (i.e. remains to be 1.0).
298        // So we first change the scale to some non-default value, and then wait
299        // until it gets back to 1.0.
300        int onScaleChangedCallCount = contentClient.getOnScaleChangedHelper().getCallCount();
301        loadDataSync(awContents, onPageFinishedHelper, pageScale4, "text/html", false);
302        contentClient.getOnScaleChangedHelper().waitForCallback(onScaleChangedCallCount);
303        assertEquals(4.0f, getScaleOnUiThread(awContents));
304        onScaleChangedCallCount = contentClient.getOnScaleChangedHelper().getCallCount();
305        loadDataSync(awContents, onPageFinishedHelper, page, "text/html", false);
306        contentClient.getOnScaleChangedHelper().waitForCallback(onScaleChangedCallCount);
307        assertEquals(1.0f, getScaleOnUiThread(awContents));
308    }
309
310    // background shorthand property must not override background-size when
311    // it's already set.
312    @MediumTest
313    @Feature({"AndroidWebView", "Preferences"})
314    public void testUseLegacyBackgroundSizeShorthandBehavior() throws Throwable {
315        final TestAwContentsClient contentClient = new TestAwContentsClient();
316        final AwTestContainerView testContainerView =
317                createAwTestContainerViewOnMainSyncInQuirksMode(contentClient);
318        final AwContents awContents = testContainerView.getAwContents();
319        AwSettings settings = getAwSettingsOnUiThread(awContents);
320        CallbackHelper onPageFinishedHelper = contentClient.getOnPageFinishedHelper();
321        final String expectedBackgroundSize = "cover";
322        final String page = "<html><head>" +
323                "<script>" +
324                "function getBackgroundSize() {" +
325                "  var e = document.getElementById('test'); " +
326                "  e.style.backgroundSize = '" + expectedBackgroundSize + "';" +
327                "  e.style.background = 'center red url(dummy://test.png) no-repeat border-box'; " +
328                "  return e.style.backgroundSize; " +
329                "}" +
330                "</script></head>" +
331                "<body onload='document.title=getBackgroundSize()'>" +
332                "  <div id='test'> </div>" +
333                "</body></html>";
334        settings.setJavaScriptEnabled(true);
335        loadDataSync(awContents, onPageFinishedHelper, page, "text/html", false);
336        String actualBackgroundSize = getTitleOnUiThread(awContents);
337        assertEquals(expectedBackgroundSize, actualBackgroundSize);
338    }
339
340    private AwTestContainerView createAwTestContainerViewOnMainSyncInQuirksMode(
341            final AwContentsClient client) throws Exception {
342        return createAwTestContainerViewOnMainSync(client, true);
343    }
344
345    private void ensureScaleBecomes(final float targetScale, final AwContents awContents)
346            throws Throwable {
347        poll(new Callable<Boolean>() {
348            @Override
349            public Boolean call() throws Exception {
350                return targetScale == getScaleOnUiThread(awContents);
351            }
352        });
353    }
354}
355