1 // Copyright 2012 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.
5 package org.chromium.android_webview.test;
7 import android.app.Activity;
8 import android.content.pm.ActivityInfo;
9 import android.test.suitebuilder.annotation.SmallTest;
10 import android.view.View;
11 import android.view.ViewConfiguration;
13 import org.chromium.android_webview.AwContents;
14 import org.chromium.android_webview.AwSettings;
15 import org.chromium.base.ThreadUtils;
16 import org.chromium.base.test.util.Feature;
18 import java.util.Locale;
19 import java.util.concurrent.Callable;
22 * A test suite for zooming-related methods and settings.
24 public class AwZoomTest extends AwTestBase {
25 private TestAwContentsClient mContentsClient;
26 private AwContents mAwContents;
27 private static final float MAXIMUM_SCALE = 2.0f;
30 public void setUp() throws Exception {
32 mContentsClient = new TestAwContentsClient();
33 final AwTestContainerView testContainerView =
34 createAwTestContainerViewOnMainSync(mContentsClient);
35 mAwContents = testContainerView.getAwContents();
38 private String getZoomableHtml(float scale) {
39 final int divWidthPercent = (int) (100.0f / scale);
40 return String.format(Locale.US, "<html><head><meta name=\"viewport\" content=\"" +
41 "width=device-width, minimum-scale=%f, maximum-scale=%f, initial-scale=%f" +
42 "\"/></head><body style='margin:0'>" +
43 "<div style='width:%d%%;height:100px;border:1px solid black'>Zoomable</div>" +
45 scale, MAXIMUM_SCALE, scale, divWidthPercent);
48 private String getNonZoomableHtml() {
49 // This page can't be zoomed because its viewport fully occupies
50 // view area and is explicitly made non user-scalable.
51 return "<html><head>" +
52 "<meta name=\"viewport\" " +
53 "content=\"width=device-width,height=device-height," +
54 "initial-scale=1,maximum-scale=1,user-scalable=no\">" +
55 "</head><body>Non-zoomable</body></html>";
58 private boolean isMultiTouchZoomSupportedOnUiThread() throws Throwable {
59 return runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
61 public Boolean call() throws Exception {
62 return mAwContents.isMultiTouchZoomSupported();
67 private int getVisibilityOnUiThread(final View view) throws Throwable {
68 return runTestOnUiThreadAndGetResult(new Callable<Integer>() {
70 public Integer call() throws Exception {
71 return view.getVisibility();
76 private View getZoomControlsOnUiThread() throws Throwable {
77 return runTestOnUiThreadAndGetResult(new Callable<View>() {
79 public View call() throws Exception {
80 return mAwContents.getZoomControlsForTest();
85 private void invokeZoomPickerOnUiThread() throws Throwable {
86 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
89 mAwContents.invokeZoomPicker();
94 private void zoomInOnUiThreadAndWait() throws Throwable {
95 final float previousScale = getPixelScaleOnUiThread(mAwContents);
96 assertTrue(runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
98 public Boolean call() throws Exception {
99 return mAwContents.zoomIn();
102 // The zoom level is updated asynchronously.
103 waitForScaleChange(previousScale);
106 private void zoomOutOnUiThreadAndWait() throws Throwable {
107 final float previousScale = getPixelScaleOnUiThread(mAwContents);
108 assertTrue(runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
110 public Boolean call() throws Exception {
111 return mAwContents.zoomOut();
114 // The zoom level is updated asynchronously.
115 waitForScaleChange(previousScale);
118 private void zoomByOnUiThreadAndWait(final float delta) throws Throwable {
119 final float previousScale = getPixelScaleOnUiThread(mAwContents);
120 assertTrue(runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
122 public Boolean call() throws Exception {
123 return mAwContents.zoomBy(delta);
126 // The zoom level is updated asynchronously.
127 waitForScaleChange(previousScale);
130 private void waitForScaleChange(final float previousScale) throws Throwable {
131 poll(new Callable<Boolean>() {
133 public Boolean call() throws Exception {
134 return previousScale != getPixelScaleOnUiThread(mAwContents);
139 private void waitForScaleToBecome(final float expectedScale) throws Throwable {
140 poll(new Callable<Boolean>() {
142 public Boolean call() throws Exception {
143 return expectedScale == getScaleOnUiThread(mAwContents);
148 private void waitUntilCanNotZoom() throws Throwable {
149 poll(new Callable<Boolean>() {
151 public Boolean call() throws Exception {
152 return !canZoomInOnUiThread(mAwContents) &&
153 !canZoomOutOnUiThread(mAwContents);
158 private void runMagnificationTest() throws Throwable {
159 getAwSettingsOnUiThread(mAwContents).setUseWideViewPort(true);
160 assertFalse("Should not be able to zoom in", canZoomInOnUiThread(mAwContents));
161 final float pageMinimumScale = 0.5f;
162 loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
163 getZoomableHtml(pageMinimumScale), "text/html", false);
164 waitForScaleToBecome(pageMinimumScale);
165 assertTrue("Should be able to zoom in", canZoomInOnUiThread(mAwContents));
166 assertFalse("Should not be able to zoom out", canZoomOutOnUiThread(mAwContents));
168 while (canZoomInOnUiThread(mAwContents)) {
169 zoomInOnUiThreadAndWait();
171 assertTrue("Should be able to zoom out", canZoomOutOnUiThread(mAwContents));
173 while (canZoomOutOnUiThread(mAwContents)) {
174 zoomOutOnUiThreadAndWait();
176 assertTrue("Should be able to zoom in", canZoomInOnUiThread(mAwContents));
178 zoomByOnUiThreadAndWait(4.0f);
179 waitForScaleToBecome(MAXIMUM_SCALE);
181 zoomByOnUiThreadAndWait(0.5f);
182 waitForScaleToBecome(MAXIMUM_SCALE * 0.5f);
184 zoomByOnUiThreadAndWait(0.01f);
185 waitForScaleToBecome(pageMinimumScale);
189 @Feature({"AndroidWebView"})
190 public void testMagnification() throws Throwable {
191 getAwSettingsOnUiThread(mAwContents).setSupportZoom(true);
192 runMagnificationTest();
195 // According to Android CTS test, zoomIn/Out must work
196 // even if supportZoom is turned off.
198 @Feature({"AndroidWebView"})
199 public void testMagnificationWithZoomSupportOff() throws Throwable {
200 getAwSettingsOnUiThread(mAwContents).setSupportZoom(false);
201 runMagnificationTest();
205 @Feature({"AndroidWebView"})
206 public void testZoomUsingMultiTouch() throws Throwable {
207 AwSettings webSettings = getAwSettingsOnUiThread(mAwContents);
208 loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
209 getZoomableHtml(0.5f), "text/html", false);
211 assertTrue(webSettings.supportZoom());
212 assertFalse(webSettings.getBuiltInZoomControls());
213 assertFalse(isMultiTouchZoomSupportedOnUiThread());
215 webSettings.setBuiltInZoomControls(true);
216 assertTrue(isMultiTouchZoomSupportedOnUiThread());
218 webSettings.setSupportZoom(false);
219 assertFalse(isMultiTouchZoomSupportedOnUiThread());
223 @Feature({"AndroidWebView"})
224 public void testZoomControls() throws Throwable {
225 AwSettings webSettings = getAwSettingsOnUiThread(mAwContents);
226 webSettings.setUseWideViewPort(true);
227 assertFalse("Should not be able to zoom in", canZoomInOnUiThread(mAwContents));
228 final float pageMinimumScale = 0.5f;
229 loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
230 getZoomableHtml(pageMinimumScale), "text/html", false);
231 waitForScaleToBecome(pageMinimumScale);
232 // It must be possible to zoom in (or zoom out) for zoom controls to be shown
233 assertTrue("Should be able to zoom in", canZoomInOnUiThread(mAwContents));
235 assertTrue(webSettings.supportZoom());
236 webSettings.setBuiltInZoomControls(true);
237 webSettings.setDisplayZoomControls(false);
239 // With DisplayZoomControls set to false, attempts to display zoom
240 // controls must be ignored.
241 assertNull(getZoomControlsOnUiThread());
242 invokeZoomPickerOnUiThread();
243 assertNull(getZoomControlsOnUiThread());
245 webSettings.setDisplayZoomControls(true);
246 assertNull(getZoomControlsOnUiThread());
247 invokeZoomPickerOnUiThread();
248 View zoomControls = getZoomControlsOnUiThread();
249 assertEquals(View.VISIBLE, getVisibilityOnUiThread(zoomControls));
253 @Feature({"AndroidWebView"})
254 public void testZoomControlsOnNonZoomableContent() throws Throwable {
255 AwSettings webSettings = getAwSettingsOnUiThread(mAwContents);
256 loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
257 getNonZoomableHtml(), "text/html", false);
259 // ContentView must update itself according to the viewport setup.
260 waitUntilCanNotZoom();
262 assertTrue(webSettings.supportZoom());
263 webSettings.setBuiltInZoomControls(true);
264 webSettings.setDisplayZoomControls(true);
265 assertNull(getZoomControlsOnUiThread());
266 invokeZoomPickerOnUiThread();
267 View zoomControls = getZoomControlsOnUiThread();
268 assertEquals(View.GONE, getVisibilityOnUiThread(zoomControls));
272 @Feature({"AndroidWebView"})
273 public void testZoomControlsOnOrientationChange() throws Throwable {
274 AwSettings webSettings = getAwSettingsOnUiThread(mAwContents);
275 loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
276 getZoomableHtml(0.5f), "text/html", false);
278 assertTrue(webSettings.supportZoom());
279 webSettings.setBuiltInZoomControls(true);
280 webSettings.setDisplayZoomControls(true);
281 invokeZoomPickerOnUiThread();
283 // Now force an orientation change, and try to display the zoom picker
284 // again. Make sure that we don't crash when the ZoomPicker registers
287 Activity activity = getActivity();
288 int orientation = activity.getRequestedOrientation();
289 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
290 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
291 activity.setRequestedOrientation(orientation);
292 invokeZoomPickerOnUiThread();
294 // We may crash shortly (as the zoom picker has a short delay in it before
295 // it tries to register it's BroadcastReceiver), so sleep to verify we don't.
296 // The delay is encoded in ZoomButtonsController#ZOOM_CONTROLS_TIMEOUT,
297 // if that changes we may need to update this test.
298 Thread.sleep(ViewConfiguration.getZoomControlsTimeout());