Java tutorial
/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.webkit.cts; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetManager; import android.cts.util.EvaluateJsResultPollingCheck; import android.cts.util.NullWebViewUtils; import android.cts.util.PollingCheck; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Picture; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.StrictMode; import android.os.StrictMode.ThreadPolicy; import android.os.SystemClock; import android.print.PageRange; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintDocumentAdapter.LayoutResultCallback; import android.print.PrintDocumentAdapter.WriteResultCallback; import android.print.PrintDocumentInfo; import android.test.ActivityInstrumentationTestCase2; import android.test.UiThreadTest; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.webkit.ConsoleMessage; import android.webkit.CookieSyncManager; import android.webkit.DownloadListener; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; import android.webkit.WebBackForwardList; import android.webkit.WebChromeClient; import android.webkit.WebIconDatabase; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebView.HitTestResult; import android.webkit.WebView.PictureListener; import android.webkit.WebViewClient; import android.webkit.WebViewDatabase; import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient; import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient; import android.widget.LinearLayout; import junit.framework.Assert; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Date; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.FutureTask; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.HashMap; import java.util.Map; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpRequest; import org.apache.http.util.EncodingUtils; import org.apache.http.util.EntityUtils; public class WebViewTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> { public static final long TEST_TIMEOUT = 20000L; private static final int INITIAL_PROGRESS = 100; private static final String X_REQUESTED_WITH = "X-Requested-With"; private static final String PRINTER_TEST_FILE = "print.pdf"; private static final String PDF_PREAMBLE = "%PDF-1"; /** * This is the minimum number of milliseconds to wait for scrolling to * start. If no scrolling has started before this timeout then it is * assumed that no scrolling will happen. */ private static final long MIN_SCROLL_WAIT_MS = 1000; /** * Once scrolling has started, this is the interval that scrolling * is checked to see if there is a change. If no scrolling change * has happened in the given time then it is assumed that scrolling * has stopped. */ private static final long SCROLL_WAIT_INTERVAL_MS = 200; private WebView mWebView; private CtsTestServer mWebServer; private WebViewOnUiThread mOnUiThread; private WebIconDatabase mIconDb; public WebViewTest() { super("com.android.cts.webkit", WebViewCtsActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); final WebViewCtsActivity activity = getActivity(); mWebView = activity.getWebView(); if (mWebView != null) { new PollingCheck() { @Override protected boolean check() { return activity.hasWindowFocus(); } }.run(); File f = activity.getFileStreamPath("snapshot"); if (f.exists()) { f.delete(); } mOnUiThread = new WebViewOnUiThread(this, mWebView); } } @Override protected void tearDown() throws Exception { if (mOnUiThread != null) { mOnUiThread.cleanUp(); } if (mWebServer != null) { stopWebServer(); } if (mIconDb != null) { mIconDb.removeAllIcons(); mIconDb.close(); mIconDb = null; } super.tearDown(); } private void startWebServer(boolean secure) throws Exception { assertNull(mWebServer); mWebServer = new CtsTestServer(getActivity(), secure); } private void stopWebServer() throws Exception { assertNotNull(mWebServer); ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); ThreadPolicy tmpPolicy = new ThreadPolicy.Builder(oldPolicy).permitNetwork().build(); StrictMode.setThreadPolicy(tmpPolicy); mWebServer.shutdown(); mWebServer = null; StrictMode.setThreadPolicy(oldPolicy); } @UiThreadTest public void testConstructor() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } new WebView(getActivity()); new WebView(getActivity(), null); new WebView(getActivity(), null, 0); } @UiThreadTest public void testCreatingWebViewCreatesCookieSyncManager() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } new WebView(getActivity()); assertNotNull(CookieSyncManager.getInstance()); } @UiThreadTest public void testFindAddress() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } /* * Info about USPS * http://en.wikipedia.org/wiki/Postal_address#United_States * http://www.usps.com/ */ // full address assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826", WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826")); // not an address assertNull(WebView.findAddress("This is not an address: no town, no state, no zip.")); } @SuppressWarnings("deprecation") @UiThreadTest public void testGetZoomControls() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } WebSettings settings = mWebView.getSettings(); assertTrue(settings.supportZoom()); View zoomControls = mWebView.getZoomControls(); assertNotNull(zoomControls); // disable zoom support settings.setSupportZoom(false); assertFalse(settings.supportZoom()); assertNull(mWebView.getZoomControls()); } @UiThreadTest public void testInvokeZoomPicker() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } WebSettings settings = mWebView.getSettings(); assertTrue(settings.supportZoom()); startWebServer(false); String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); mOnUiThread.loadUrlAndWaitForCompletion(url); mWebView.invokeZoomPicker(); } public void testZoom() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } // Pinch zoom is not supported in wrap_content layouts. mOnUiThread.setLayoutHeightToMatchParent(); final ScaleChangedWebViewClient webViewClient = new ScaleChangedWebViewClient(); mOnUiThread.setWebViewClient(webViewClient); mWebServer = new CtsTestServer(getActivity()); mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); pollingCheckForCanZoomIn(); WebSettings settings = mOnUiThread.getSettings(); settings.setSupportZoom(false); assertFalse(settings.supportZoom()); float currScale = mOnUiThread.getScale(); float previousScale = currScale; // can zoom in or out although zoom support is disabled in web settings assertTrue(mOnUiThread.zoomIn()); webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale > previousScale); assertTrue(mOnUiThread.zoomOut()); previousScale = currScale; webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale < previousScale); mOnUiThread.zoomBy(1.25f); // zoom in previousScale = currScale; webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale > previousScale); mOnUiThread.zoomBy(0.8f); // zoom out previousScale = currScale; webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale < previousScale); // enable zoom support settings.setSupportZoom(true); assertTrue(settings.supportZoom()); previousScale = mOnUiThread.getScale(); assertTrue(mOnUiThread.zoomIn()); webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale > previousScale); // zoom in until it reaches maximum scale while (mOnUiThread.zoomIn()) { previousScale = currScale; webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale > previousScale); } previousScale = currScale; // can not zoom in further assertFalse(mOnUiThread.zoomIn()); // We sleep to assert to the best of our ability // that a scale change does *not* happen. Thread.sleep(500); currScale = mOnUiThread.getScale(); assertEquals(currScale, previousScale); assertTrue(mOnUiThread.zoomOut()); previousScale = currScale; webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale < previousScale); // zoom out until it reaches minimum scale while (mOnUiThread.zoomOut()) { previousScale = currScale; webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale < previousScale); } previousScale = currScale; assertFalse(mOnUiThread.zoomOut()); // We sleep to assert to the best of our ability // that a scale change does *not* happen. Thread.sleep(500); currScale = mOnUiThread.getScale(); assertEquals(currScale, previousScale); mOnUiThread.zoomBy(1.25f); previousScale = currScale; webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale > previousScale); // zoom in until it reaches maximum scale while (mOnUiThread.canZoomIn()) { previousScale = currScale; mOnUiThread.zoomBy(1.25f); webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale > previousScale); } previousScale = currScale; // We sleep to assert to the best of our ability // that a scale change does *not* happen. Thread.sleep(500); currScale = mOnUiThread.getScale(); assertEquals(currScale, previousScale); mOnUiThread.zoomBy(0.8f); previousScale = currScale; webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale < previousScale); // zoom out until it reaches minimum scale while (mOnUiThread.canZoomOut()) { previousScale = currScale; mOnUiThread.zoomBy(0.8f); webViewClient.waitForScaleChanged(); currScale = mOnUiThread.getScale(); assertTrue(currScale < previousScale); } previousScale = currScale; // We sleep to assert to the best of our ability // that a scale change does *not* happen. Thread.sleep(500); currScale = mOnUiThread.getScale(); assertEquals(currScale, previousScale); } @UiThreadTest public void testSetScrollBarStyle() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); assertFalse(mWebView.overlayHorizontalScrollbar()); assertFalse(mWebView.overlayVerticalScrollbar()); mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); assertTrue(mWebView.overlayHorizontalScrollbar()); assertTrue(mWebView.overlayVerticalScrollbar()); mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET); assertFalse(mWebView.overlayHorizontalScrollbar()); assertFalse(mWebView.overlayVerticalScrollbar()); mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); assertTrue(mWebView.overlayHorizontalScrollbar()); assertTrue(mWebView.overlayVerticalScrollbar()); } @UiThreadTest public void testScrollBarOverlay() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } mWebView.setHorizontalScrollbarOverlay(true); mWebView.setVerticalScrollbarOverlay(false); assertTrue(mWebView.overlayHorizontalScrollbar()); assertFalse(mWebView.overlayVerticalScrollbar()); mWebView.setHorizontalScrollbarOverlay(false); mWebView.setVerticalScrollbarOverlay(true); assertFalse(mWebView.overlayHorizontalScrollbar()); assertTrue(mWebView.overlayVerticalScrollbar()); } @UiThreadTest public void testLoadUrl() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } assertNull(mWebView.getUrl()); assertNull(mWebView.getOriginalUrl()); assertEquals(INITIAL_PROGRESS, mWebView.getProgress()); startWebServer(false); String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); mOnUiThread.loadUrlAndWaitForCompletion(url); assertEquals(100, mWebView.getProgress()); assertEquals(url, mWebView.getUrl()); assertEquals(url, mWebView.getOriginalUrl()); assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle()); // verify that the request also includes X-Requested-With header HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH); assertEquals(1, matchingHeaders.length); Header header = matchingHeaders[0]; assertEquals(mWebView.getContext().getApplicationInfo().packageName, header.getValue()); } @UiThreadTest public void testPostUrlWithNonNetworkUrl() throws Exception { final String nonNetworkUrl = "file:///android_asset/" + TestHtmlConstants.HELLO_WORLD_URL; mOnUiThread.postUrlAndWaitForCompletion(nonNetworkUrl, new byte[1]); // Test if the nonNetworkUrl is loaded assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle()); } @UiThreadTest public void testPostUrlWithNetworkUrl() throws Exception { startWebServer(false); final String networkUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); final String postDataString = "username=my_username&password=my_password"; final byte[] postData = EncodingUtils.getBytes(postDataString, "BASE64"); mOnUiThread.postUrlAndWaitForCompletion(networkUrl, postData); HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); // The last request should be POST assertEquals(request.getRequestLine().getMethod(), "POST"); // The last request should have a request body assertTrue(request instanceof HttpEntityEnclosingRequest); HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); String entityString = EntityUtils.toString(entity); assertEquals(entityString, postDataString); } @UiThreadTest public void testLoadUrlDoesNotStripParamsWhenLoadingContentUrls() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } Uri.Builder uriBuilder = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(MockContentProvider.AUTHORITY); uriBuilder.appendPath("foo.html").appendQueryParameter("param", "bar"); String url = uriBuilder.build().toString(); mOnUiThread.loadUrlAndWaitForCompletion(url); // verify the parameter is not stripped. Uri uri = Uri.parse(mWebView.getTitle()); assertEquals("bar", uri.getQueryParameter("param")); } @UiThreadTest public void testAppInjectedXRequestedWithHeaderIsNotOverwritten() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } startWebServer(false); String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); HashMap<String, String> map = new HashMap<String, String>(); final String requester = "foo"; map.put(X_REQUESTED_WITH, requester); mOnUiThread.loadUrlAndWaitForCompletion(url, map); // verify that the request also includes X-Requested-With header // but is not overwritten by the webview HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH); assertEquals(1, matchingHeaders.length); Header header = matchingHeaders[0]; assertEquals(requester, header.getValue()); } @UiThreadTest public void testAppCanInjectHeadersViaImmutableMap() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } startWebServer(false); String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); HashMap<String, String> map = new HashMap<String, String>(); final String requester = "foo"; map.put(X_REQUESTED_WITH, requester); mOnUiThread.loadUrlAndWaitForCompletion(url, Collections.unmodifiableMap(map)); // verify that the request also includes X-Requested-With header // but is not overwritten by the webview HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH); assertEquals(1, matchingHeaders.length); Header header = matchingHeaders[0]; assertEquals(requester, header.getValue()); } public void testCanInjectHeaders() throws Exception { final String X_FOO = "X-foo"; final String X_FOO_VALUE = "test"; final String X_REFERER = "Referer"; final String X_REFERER_VALUE = "http://www.example.com/"; startWebServer(false); String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); HashMap<String, String> map = new HashMap<String, String>(); map.put(X_FOO, X_FOO_VALUE); map.put(X_REFERER, X_REFERER_VALUE); mOnUiThread.loadUrlAndWaitForCompletion(url, map); HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); for (Map.Entry<String, String> value : map.entrySet()) { String header = value.getKey(); Header[] matchingHeaders = request.getHeaders(header); assertEquals("header " + header + " not found", 1, matchingHeaders.length); assertEquals(value.getValue(), matchingHeaders[0].getValue()); } } @SuppressWarnings("deprecation") @UiThreadTest public void testGetVisibleTitleHeight() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } startWebServer(false); String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); mOnUiThread.loadUrlAndWaitForCompletion(url); assertEquals(0, mWebView.getVisibleTitleHeight()); } @UiThreadTest public void testGetOriginalUrl() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } startWebServer(false); final String finalUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); final String redirectUrl = mWebServer.getRedirectingAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); assertNull(mWebView.getUrl()); assertNull(mWebView.getOriginalUrl()); // By default, WebView sends an intent to ask the system to // handle loading a new URL. We set a WebViewClient as // WebViewClient.shouldOverrideUrlLoading() returns false, so // the WebView will load the new URL. mOnUiThread.setWebViewClient(new WaitForLoadedClient(mOnUiThread)); mOnUiThread.loadUrlAndWaitForCompletion(redirectUrl); assertEquals(finalUrl, mWebView.getUrl()); assertEquals(redirectUrl, mWebView.getOriginalUrl()); } public void testStopLoading() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } assertEquals(INITIAL_PROGRESS, mOnUiThread.getProgress()); startWebServer(false); String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.STOP_LOADING_URL); class JsInterface { private boolean mPageLoaded; @JavascriptInterface public synchronized void pageLoaded() { mPageLoaded = true; notify(); } public synchronized boolean getPageLoaded() { return mPageLoaded; } } JsInterface jsInterface = new JsInterface(); mOnUiThread.getSettings().setJavaScriptEnabled(true); mOnUiThread.addJavascriptInterface(jsInterface, "javabridge"); mOnUiThread.loadUrl(url); mOnUiThread.stopLoading(); // We wait to see that the onload callback in the HTML is not fired. synchronized (jsInterface) { jsInterface.wait(3000); } assertFalse(jsInterface.getPageLoaded()); } @UiThreadTest public void testGoBackAndForward() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } assertGoBackOrForwardBySteps(false, -1); assertGoBackOrForwardBySteps(false, 1); startWebServer(false); String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); mOnUiThread.loadUrlAndWaitForCompletion(url1); pollingCheckWebBackForwardList(url1, 0, 1); assertGoBackOrForwardBySteps(false, -1); assertGoBackOrForwardBySteps(false, 1); mOnUiThread.loadUrlAndWaitForCompletion(url2); pollingCheckWebBackForwardList(url2, 1, 2); assertGoBackOrForwardBySteps(true, -1); assertGoBackOrForwardBySteps(false, 1); mOnUiThread.loadUrlAndWaitForCompletion(url3); pollingCheckWebBackForwardList(url3, 2, 3); assertGoBackOrForwardBySteps(true, -2); assertGoBackOrForwardBySteps(false, 1); mWebView.goBack(); pollingCheckWebBackForwardList(url2, 1, 3); assertGoBackOrForwardBySteps(true, -1); assertGoBackOrForwardBySteps(true, 1); mWebView.goForward(); pollingCheckWebBackForwardList(url3, 2, 3); assertGoBackOrForwardBySteps(true, -2); assertGoBackOrForwardBySteps(false, 1); mWebView.goBackOrForward(-2); pollingCheckWebBackForwardList(url1, 0, 3); assertGoBackOrForwardBySteps(false, -1); assertGoBackOrForwardBySteps(true, 2); mWebView.goBackOrForward(2); pollingCheckWebBackForwardList(url3, 2, 3); assertGoBackOrForwardBySteps(true, -2); assertGoBackOrForwardBySteps(false, 1); } @UiThreadTest public void testAddJavascriptInterface() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } WebSettings settings = mWebView.getSettings(); settings.setJavaScriptEnabled(true); settings.setJavaScriptCanOpenWindowsAutomatically(true); final class DummyJavaScriptInterface { private boolean mWasProvideResultCalled; private String mResult; private synchronized String waitForResult() { while (!mWasProvideResultCalled) { try { wait(TEST_TIMEOUT); } catch (InterruptedException e) { continue; } if (!mWasProvideResultCalled) { Assert.fail("Unexpected timeout"); } } return mResult; } public synchronized boolean wasProvideResultCalled() { return mWasProvideResultCalled; } @JavascriptInterface public synchronized void provideResult(String result) { mWasProvideResultCalled = true; mResult = result; notify(); } } final DummyJavaScriptInterface obj = new DummyJavaScriptInterface(); mWebView.addJavascriptInterface(obj, "dummy"); assertFalse(obj.wasProvideResultCalled()); startWebServer(false); String url = mWebServer.getAssetUrl(TestHtmlConstants.ADD_JAVA_SCRIPT_INTERFACE_URL); mOnUiThread.loadUrlAndWaitForCompletion(url); assertEquals("Original title", obj.waitForResult()); // Verify that only methods annotated with @JavascriptInterface are exposed // on the JavaScript interface object. mOnUiThread.evaluateJavascript("typeof dummy.provideResult", new ValueCallback<String>() { @Override public void onReceiveValue(String result) { assertEquals("\"function\"", result); } }); mOnUiThread.evaluateJavascript("typeof dummy.wasProvideResultCalled", new ValueCallback<String>() { @Override public void onReceiveValue(String result) { assertEquals("\"undefined\"", result); } }); mOnUiThread.evaluateJavascript("typeof dummy.getClass", new ValueCallback<String>() { @Override public void onReceiveValue(String result) { assertEquals("\"undefined\"", result); } }); } @UiThreadTest public void testAddJavascriptInterfaceNullObject() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } WebSettings settings = mWebView.getSettings(); settings.setJavaScriptEnabled(true); String setTitleToPropertyTypeHtml = "<html><head></head>" + "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>"; // Test that the property is initially undefined. mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, "text/html", null); assertEquals("undefined", mWebView.getTitle()); // Test that adding a null object has no effect. mWebView.addJavascriptInterface(null, "injectedObject"); mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, "text/html", null); assertEquals("undefined", mWebView.getTitle()); // Test that adding an object gives an object type. final Object obj = new Object(); mWebView.addJavascriptInterface(obj, "injectedObject"); mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, "text/html", null); assertEquals("object", mWebView.getTitle()); // Test that trying to replace with a null object has no effect. mWebView.addJavascriptInterface(null, "injectedObject"); mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, "text/html", null); assertEquals("object", mWebView.getTitle()); } @UiThreadTest public void testRemoveJavascriptInterface() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } WebSettings settings = mWebView.getSettings(); settings.setJavaScriptEnabled(true); String setTitleToPropertyTypeHtml = "<html><head></head>" + "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>"; // Test that adding an object gives an object type. mWebView.addJavascriptInterface(new Object(), "injectedObject"); mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, "text/html", null); assertEquals("object", mWebView.getTitle()); // Test that reloading the page after removing the object leaves the property undefined. mWebView.removeJavascriptInterface("injectedObject"); mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, "text/html", null); assertEquals("undefined", mWebView.getTitle()); } public void testUseRemovedJavascriptInterface() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } class RemovedObject { @Override @JavascriptInterface public String toString() { return "removedObject"; } @JavascriptInterface public void remove() throws Throwable { mOnUiThread.removeJavascriptInterface("removedObject"); System.gc(); } } class ResultObject { private String mResult; private boolean mIsResultAvailable; @JavascriptInterface public synchronized void setResult(String result) { mResult = result; mIsResultAvailable = true; notify(); } public synchronized String getResult() { while (!mIsResultAvailable) { try { wait(); } catch (InterruptedException e) { } } return mResult; } } final ResultObject resultObject = new ResultObject(); // Test that an object is still usable if removed while the page is in use, even if we have // no external references to it. mOnUiThread.getSettings().setJavaScriptEnabled(true); mOnUiThread.addJavascriptInterface(new RemovedObject(), "removedObject"); mOnUiThread.addJavascriptInterface(resultObject, "resultObject"); mOnUiThread .loadDataAndWaitForCompletion( "<html><head></head>" + "<body onload=\"window.removedObject.remove();" + "resultObject.setResult(removedObject.toString());\"></body></html>", "text/html", null); assertEquals("removedObject", resultObject.getResult()); } public void testAddJavascriptInterfaceExceptions() throws Exception { WebSettings settings = mOnUiThread.getSettings(); settings.setJavaScriptEnabled(true); settings.setJavaScriptCanOpenWindowsAutomatically(true); final AtomicBoolean mJsInterfaceWasCalled = new AtomicBoolean(false) { @JavascriptInterface public synchronized void call() { set(true); // The main purpose of this test is to ensure an exception here does not // crash the implementation. throw new RuntimeException("Javascript Interface exception"); } }; mOnUiThread.addJavascriptInterface(mJsInterfaceWasCalled, "dummy"); mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); assertFalse(mJsInterfaceWasCalled.get()); final CountDownLatch resultLatch = new CountDownLatch(1); mOnUiThread.evaluateJavascript("try {dummy.call(); 'fail'; } catch (exception) { 'pass'; } ", new ValueCallback<String>() { @Override public void onReceiveValue(String result) { assertEquals("\"pass\"", result); resultLatch.countDown(); } }); assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); assertTrue(mJsInterfaceWasCalled.get()); } public void testJavascriptInterfaceCustomPropertiesClearedOnReload() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } mOnUiThread.getSettings().setJavaScriptEnabled(true); class DummyJavaScriptInterface { } final DummyJavaScriptInterface obj = new DummyJavaScriptInterface(); mOnUiThread.addJavascriptInterface(obj, "dummy"); mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); EvaluateJsResultPollingCheck jsResult; jsResult = new EvaluateJsResultPollingCheck("42"); mOnUiThread.evaluateJavascript("dummy.custom_property = 42", jsResult); jsResult.run(); jsResult = new EvaluateJsResultPollingCheck("true"); mOnUiThread.evaluateJavascript("'custom_property' in dummy", jsResult); jsResult.run(); mOnUiThread.reload(); jsResult = new EvaluateJsResultPollingCheck("false"); mOnUiThread.evaluateJavascript("'custom_property' in dummy", jsResult); jsResult.run(); } public void testJavascriptInterfaceForClientPopup() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } mOnUiThread.getSettings().setJavaScriptEnabled(true); mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); mOnUiThread.getSettings().setSupportMultipleWindows(true); class DummyJavaScriptInterface { @JavascriptInterface public int test() { return 42; } } final DummyJavaScriptInterface obj = new DummyJavaScriptInterface(); final WebView childWebView = mOnUiThread.createWebView(); WebViewOnUiThread childOnUiThread = new WebViewOnUiThread(this, childWebView); childOnUiThread.getSettings().setJavaScriptEnabled(true); childOnUiThread.addJavascriptInterface(obj, "dummy"); final boolean[] hadOnCreateWindow = new boolean[1]; hadOnCreateWindow[0] = false; mOnUiThread.setWebChromeClient(new WebViewOnUiThread.WaitForProgressClient(mOnUiThread) { @Override public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { getActivity().addContentView(childWebView, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; transport.setWebView(childWebView); resultMsg.sendToTarget(); hadOnCreateWindow[0] = true; return true; } }); startWebServer(false); mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.getAssetUrl(TestHtmlConstants.POPUP_URL)); new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return hadOnCreateWindow[0]; } }.run(); childOnUiThread.loadUrlAndWaitForCompletion("about:blank"); EvaluateJsResultPollingCheck jsResult; jsResult = new EvaluateJsResultPollingCheck("true"); childOnUiThread.evaluateJavascript("'dummy' in window", jsResult); jsResult.run(); // Verify that the injected object is functional. jsResult = new EvaluateJsResultPollingCheck("42"); childOnUiThread.evaluateJavascript("dummy.test()", jsResult); jsResult.run(); } private final class TestPictureListener implements PictureListener { public int callCount; @Override public void onNewPicture(WebView view, Picture picture) { // Need to inform the listener tracking new picture // for the "page loaded" knowledge since it has been replaced. mOnUiThread.onNewPicture(); this.callCount += 1; } } private Picture waitForPictureToHaveColor(int color, final TestPictureListener listener) throws Throwable { final int MAX_ON_NEW_PICTURE_ITERATIONS = 5; final AtomicReference<Picture> pictureRef = new AtomicReference<Picture>(); for (int i = 0; i < MAX_ON_NEW_PICTURE_ITERATIONS; i++) { final int oldCallCount = listener.callCount; runTestOnUiThread(new Runnable() { @Override public void run() { pictureRef.set(mWebView.capturePicture()); } }); if (isPictureFilledWithColor(pictureRef.get(), color)) break; new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return listener.callCount > oldCallCount; } }.run(); } return pictureRef.get(); } public void testCapturePicture() throws Exception, Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final TestPictureListener listener = new TestPictureListener(); startWebServer(false); final String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL); mOnUiThread.setPictureListener(listener); // Showing the blank page will fill the picture with the background color. mOnUiThread.loadUrlAndWaitForCompletion(url); // The default background color is white. Picture oldPicture = waitForPictureToHaveColor(Color.WHITE, listener); runTestOnUiThread(new Runnable() { @Override public void run() { mWebView.setBackgroundColor(Color.CYAN); } }); mOnUiThread.reloadAndWaitForCompletion(); waitForPictureToHaveColor(Color.CYAN, listener); // The content of the previously captured picture will not be updated automatically. assertTrue(isPictureFilledWithColor(oldPicture, Color.WHITE)); } public void testSetPictureListener() throws Exception, Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final class MyPictureListener implements PictureListener { public int callCount; public WebView webView; public Picture picture; @Override public void onNewPicture(WebView view, Picture picture) { // Need to inform the listener tracking new picture // for the "page loaded" knowledge since it has been replaced. mOnUiThread.onNewPicture(); this.callCount += 1; this.webView = view; this.picture = picture; } } final MyPictureListener listener = new MyPictureListener(); startWebServer(false); final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); mOnUiThread.setPictureListener(listener); mOnUiThread.loadUrlAndWaitForCompletion(url); new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return listener.callCount > 0; } }.run(); assertEquals(mWebView, listener.webView); assertNull(listener.picture); final int oldCallCount = listener.callCount; final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL); mOnUiThread.loadUrlAndWaitForCompletion(newUrl); new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return listener.callCount > oldCallCount; } }.run(); } @UiThreadTest public void testAccessHttpAuthUsernamePassword() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } try { WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword(); String host = "http://localhost:8080"; String realm = "testrealm"; String userName = "user"; String password = "password"; String[] result = mWebView.getHttpAuthUsernamePassword(host, realm); assertNull(result); mWebView.setHttpAuthUsernamePassword(host, realm, userName, password); result = mWebView.getHttpAuthUsernamePassword(host, realm); assertNotNull(result); assertEquals(userName, result[0]); assertEquals(password, result[1]); String newPassword = "newpassword"; mWebView.setHttpAuthUsernamePassword(host, realm, userName, newPassword); result = mWebView.getHttpAuthUsernamePassword(host, realm); assertNotNull(result); assertEquals(userName, result[0]); assertEquals(newPassword, result[1]); String newUserName = "newuser"; mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); result = mWebView.getHttpAuthUsernamePassword(host, realm); assertNotNull(result); assertEquals(newUserName, result[0]); assertEquals(newPassword, result[1]); // the user is set to null, can not change any thing in the future mWebView.setHttpAuthUsernamePassword(host, realm, null, password); result = mWebView.getHttpAuthUsernamePassword(host, realm); assertNotNull(result); assertNull(result[0]); assertEquals(password, result[1]); mWebView.setHttpAuthUsernamePassword(host, realm, userName, null); result = mWebView.getHttpAuthUsernamePassword(host, realm); assertNotNull(result); assertEquals(userName, result[0]); assertEquals(null, result[1]); mWebView.setHttpAuthUsernamePassword(host, realm, null, null); result = mWebView.getHttpAuthUsernamePassword(host, realm); assertNotNull(result); assertNull(result[0]); assertNull(result[1]); mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); result = mWebView.getHttpAuthUsernamePassword(host, realm); assertNotNull(result); assertEquals(newUserName, result[0]); assertEquals(newPassword, result[1]); } finally { WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword(); } } public void testLoadData() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final String HTML_CONTENT = "<html><head><title>Hello,World!</title></head><body></body>" + "</html>"; mOnUiThread.loadDataAndWaitForCompletion(HTML_CONTENT, "text/html", null); assertEquals("Hello,World!", mOnUiThread.getTitle()); startWebServer(false); final ChromeClient webChromeClient = new ChromeClient(mOnUiThread); final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); runTestOnUiThread(new Runnable() { @Override public void run() { mWebView.getSettings().setJavaScriptEnabled(true); mOnUiThread.setWebChromeClient(webChromeClient); mOnUiThread.loadDataAndWaitForCompletion( "<html><head></head><body onload=\"" + "document.title = " + "document.getElementById('frame').contentWindow.location.href;" + "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>", "text/html", null); } }); assertEquals(ConsoleMessage.MessageLevel.ERROR, webChromeClient.getMessageLevel(10000)); } @UiThreadTest public void testLoadDataWithBaseUrl() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } assertNull(mWebView.getUrl()); String imgUrl = TestHtmlConstants.SMALL_IMG_URL; // relative // Snippet of HTML that will prevent favicon requests to the test server. final String HTML_HEADER = "<html><head><link rel=\"shortcut icon\" href=\"#\" /></head>"; // Trying to resolve a relative URL against a data URL without a base URL // will fail and we won't make a request to the test web server. // By using the test web server as the base URL we expect to see a request // for the relative URL in the test server. startWebServer(false); String baseUrl = mWebServer.getAssetUrl("foo.html"); String historyUrl = "http://www.example.com/"; mWebServer.resetRequestState(); mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, HTML_HEADER + "<body><img src=\"" + imgUrl + "\"/></body></html>", "text/html", "UTF-8", historyUrl); // Verify that the resource request makes it to the server. assertTrue(mWebServer.wasResourceRequested(imgUrl)); assertEquals(historyUrl, mWebView.getUrl()); // Check that reported URL is "about:blank" when supplied history URL // is null. imgUrl = TestHtmlConstants.LARGE_IMG_URL; mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, HTML_HEADER + "<body><img src=\"" + imgUrl + "\"/></body></html>", "text/html", "UTF-8", null); assertTrue(mWebServer.wasResourceRequested(imgUrl)); assertEquals("about:blank", mWebView.getUrl()); // Test that JavaScript can access content from the same origin as the base URL. mWebView.getSettings().setJavaScriptEnabled(true); final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, HTML_HEADER + "<body onload=\"" + "document.title = document.getElementById('frame').contentWindow.location.href;" + "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>", "text/html", "UTF-8", null); assertEquals(crossOriginUrl, mWebView.getTitle()); // Check that when the base URL uses the 'data' scheme, a 'data' scheme URL is used and the // history URL is ignored. mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo", HTML_HEADER + "<body>bar</body></html>", "text/html", "UTF-8", historyUrl); assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("data:text/html") == 0); assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("bar") > 0); // Check that when a non-data: base URL is used, we treat the String to load as // a raw string and just dump it into the WebView, i.e. not decoding any URL entities. mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("http://www.foo.com", HTML_HEADER + "<title>Hello World%21</title><body>bar</body></html>", "text/html", "UTF-8", null); assertEquals("Hello World%21", mOnUiThread.getTitle()); // Check that when a data: base URL is used, we treat the String to load as a data: URL // and run load steps such as decoding URL entities (i.e., contrary to the test case // above.) mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo", HTML_HEADER + "<title>Hello World%21</title></html>", "text/html", "UTF-8", null); assertEquals("Hello World!", mOnUiThread.getTitle()); // Check the method is null input safe. mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(null, null, null, null, null); assertEquals("about:blank", mOnUiThread.getUrl()); } private void deleteIfExists(File file) throws IOException { if (file.exists()) { file.delete(); } } private String readTextFile(File file, Charset encoding) throws FileNotFoundException, IOException { FileInputStream stream = new FileInputStream(file); byte[] bytes = new byte[(int) file.length()]; stream.read(bytes); stream.close(); return new String(bytes, encoding); } private void doSaveWebArchive(String baseName, boolean autoName, final String expectName) throws Throwable { final Semaphore saving = new Semaphore(0); ValueCallback<String> callback = new ValueCallback<String>() { @Override public void onReceiveValue(String savedName) { assertEquals(expectName, savedName); saving.release(); } }; mOnUiThread.saveWebArchive(baseName, autoName, callback); assertTrue(saving.tryAcquire(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); } public void testSaveWebArchive() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final String testPage = "testSaveWebArchive test page"; File dir = getActivity().getFilesDir(); String dirStr = dir.toString(); File test = new File(dir, "test.mht"); deleteIfExists(test); String testStr = test.getAbsolutePath(); File index = new File(dir, "index.mht"); deleteIfExists(index); String indexStr = index.getAbsolutePath(); File index1 = new File(dir, "index-1.mht"); deleteIfExists(index1); String index1Str = index1.getAbsolutePath(); mOnUiThread.loadDataAndWaitForCompletion(testPage, "text/html", "UTF-8"); try { // Save test.mht doSaveWebArchive(testStr, false, testStr); // Check the contents of test.mht String testMhtml = readTextFile(test, StandardCharsets.UTF_8); assertTrue(testMhtml.contains(testPage)); // Save index.mht doSaveWebArchive(dirStr + "/", true, indexStr); // Check the contents of index.mht String indexMhtml = readTextFile(index, StandardCharsets.UTF_8); assertTrue(indexMhtml.contains(testPage)); // Save index-1.mht since index.mht already exists doSaveWebArchive(dirStr + "/", true, index1Str); // Check the contents of index-1.mht String index1Mhtml = readTextFile(index1, StandardCharsets.UTF_8); assertTrue(index1Mhtml.contains(testPage)); // Try a file in a bogus directory doSaveWebArchive("/bogus/path/test.mht", false, null); // Try a bogus directory doSaveWebArchive("/bogus/path/", true, null); } finally { deleteIfExists(test); deleteIfExists(index); deleteIfExists(index1); } } private static class WaitForFindResultsListener extends FutureTask<Integer> implements WebView.FindListener { public WaitForFindResultsListener() { super(new Runnable() { @Override public void run() { } }, null); } @Override public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) { if (isDoneCounting) { set(numberOfMatches); } } } public void testFindAll() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } // Make the page scrollable, so we can detect the scrolling to make sure the // content fully loaded. mOnUiThread.setInitialScale(100); DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); int dimension = Math.max(metrics.widthPixels, metrics.heightPixels); // create a paragraph high enough to take up the entire screen String p = "<p style=\"height:" + dimension + "px;\">" + "Find all instances of find on the page and highlight them.</p>"; mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + "</body></html>", "text/html", null); WaitForFindResultsListener l = new WaitForFindResultsListener(); int previousScrollY = mOnUiThread.getScrollY(); mOnUiThread.pageDown(true); // Wait for content fully loaded. waitForScrollingComplete(previousScrollY); mOnUiThread.setFindListener(l); mOnUiThread.findAll("find"); assertEquals(2, l.get().intValue()); } public void testFindNext() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } // Reset the scaling so that finding the next "all" text will require scrolling. mOnUiThread.setInitialScale(100); DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); int dimension = Math.max(metrics.widthPixels, metrics.heightPixels); // create a paragraph high enough to take up the entire screen String p = "<p style=\"height:" + dimension + "px;\">" + "Find all instances of a word on the page and highlight them.</p>"; mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + p + "</body></html>", "text/html", null); WaitForFindResultsListener l = new WaitForFindResultsListener(); mOnUiThread.setFindListener(l); // highlight all the strings found mOnUiThread.findAll("all"); // make sure the findAll action is completed before findNext l.get(); mOnUiThread.setFindListener(null); int previousScrollY = mOnUiThread.getScrollY(); // Focus "all" in the second page and assert that the view scrolls. mOnUiThread.findNext(true); waitForScrollingComplete(previousScrollY); assertTrue(mOnUiThread.getScrollY() > previousScrollY); previousScrollY = mOnUiThread.getScrollY(); // Focus "all" in the first page and assert that the view scrolls. mOnUiThread.findNext(true); waitForScrollingComplete(previousScrollY); assertTrue(mOnUiThread.getScrollY() < previousScrollY); previousScrollY = mOnUiThread.getScrollY(); // Focus "all" in the second page and assert that the view scrolls. mOnUiThread.findNext(false); waitForScrollingComplete(previousScrollY); assertTrue(mOnUiThread.getScrollY() > previousScrollY); previousScrollY = mOnUiThread.getScrollY(); // Focus "all" in the first page and assert that the view scrolls. mOnUiThread.findNext(false); waitForScrollingComplete(previousScrollY); assertTrue(mOnUiThread.getScrollY() < previousScrollY); previousScrollY = mOnUiThread.getScrollY(); // clear the result mOnUiThread.clearMatches(); getInstrumentation().waitForIdleSync(); // can not scroll any more mOnUiThread.findNext(false); waitForScrollingComplete(previousScrollY); assertTrue(mOnUiThread.getScrollY() == previousScrollY); mOnUiThread.findNext(true); waitForScrollingComplete(previousScrollY); assertTrue(mOnUiThread.getScrollY() == previousScrollY); } public void testDocumentHasImages() throws Exception, Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final class DocumentHasImageCheckHandler extends Handler { private boolean mReceived; private int mMsgArg1; public DocumentHasImageCheckHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { synchronized (this) { mReceived = true; mMsgArg1 = msg.arg1; } } public synchronized boolean hasCalledHandleMessage() { return mReceived; } public synchronized int getMsgArg1() { return mMsgArg1; } } startWebServer(false); final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL); // Create a handler on the UI thread. final DocumentHasImageCheckHandler handler = new DocumentHasImageCheckHandler( mWebView.getHandler().getLooper()); runTestOnUiThread(new Runnable() { @Override public void run() { mOnUiThread.loadDataAndWaitForCompletion("<html><body><img src=\"" + imgUrl + "\"/></body></html>", "text/html", null); Message response = new Message(); response.setTarget(handler); assertFalse(handler.hasCalledHandleMessage()); mWebView.documentHasImages(response); } }); new PollingCheck() { @Override protected boolean check() { return handler.hasCalledHandleMessage(); } }.run(); assertEquals(1, handler.getMsgArg1()); } public void testPageScroll() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels); String p = "<p style=\"height:" + dimension + "px;\">" + "Scroll by half the size of the page.</p>"; mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + p + "</body></html>", "text/html", null); // Wait for UI thread to settle and receive page dimentions from renderer // such that we can invoke page down. new PollingCheck() { @Override protected boolean check() { return mOnUiThread.pageDown(false); } }.run(); do { getInstrumentation().waitForIdleSync(); } while (mOnUiThread.pageDown(false)); getInstrumentation().waitForIdleSync(); int bottomScrollY = mOnUiThread.getScrollY(); assertTrue(mOnUiThread.pageUp(false)); do { getInstrumentation().waitForIdleSync(); } while (mOnUiThread.pageUp(false)); getInstrumentation().waitForIdleSync(); int topScrollY = mOnUiThread.getScrollY(); // jump to the bottom assertTrue(mOnUiThread.pageDown(true)); getInstrumentation().waitForIdleSync(); assertEquals(bottomScrollY, mOnUiThread.getScrollY()); // jump to the top assertTrue(mOnUiThread.pageUp(true)); getInstrumentation().waitForIdleSync(); assertEquals(topScrollY, mOnUiThread.getScrollY()); } public void testGetContentHeight() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } mOnUiThread.loadDataAndWaitForCompletion("<html><body></body></html>", "text/html", null); new PollingCheck() { @Override protected boolean check() { return mOnUiThread.getScale() != 0 && mOnUiThread.getContentHeight() != 0 && mOnUiThread.getHeight() != 0; } }.run(); assertEquals(mOnUiThread.getHeight(), mOnUiThread.getContentHeight() * mOnUiThread.getScale(), 2f); final int pageHeight = 600; // set the margin to 0 final String p = "<p style=\"height:" + pageHeight + "px;margin:0px auto;\">Get the height of HTML content.</p>"; mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + "</body></html>", "text/html", null); new PollingCheck() { @Override protected boolean check() { return mOnUiThread.getContentHeight() > pageHeight; } }.run(); final int extraSpace = mOnUiThread.getContentHeight() - pageHeight; mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + p + "</body></html>", "text/html", null); new PollingCheck() { @Override protected boolean check() { return pageHeight + pageHeight + extraSpace == mOnUiThread.getContentHeight(); } }.run(); } @UiThreadTest public void testPlatformNotifications() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } WebView.enablePlatformNotifications(); WebView.disablePlatformNotifications(); } @UiThreadTest public void testAccessPluginList() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } assertNotNull(WebView.getPluginList()); // can not find a way to install plugins mWebView.refreshPlugins(false); } @UiThreadTest public void testDestroy() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } // Create a new WebView, since we cannot call destroy() on a view in the hierarchy WebView localWebView = new WebView(getActivity()); localWebView.destroy(); } public void testFlingScroll() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); final int dimension = 10 * Math.max(metrics.widthPixels, metrics.heightPixels); String p = "<p style=\"height:" + dimension + "px;" + "width:" + dimension + "px\">Test fling scroll.</p>"; mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + "</body></html>", "text/html", null); new PollingCheck() { @Override protected boolean check() { return mOnUiThread.getContentHeight() >= dimension; } }.run(); getInstrumentation().waitForIdleSync(); final int previousScrollX = mOnUiThread.getScrollX(); final int previousScrollY = mOnUiThread.getScrollY(); mOnUiThread.flingScroll(100, 100); new PollingCheck() { @Override protected boolean check() { return mOnUiThread.getScrollX() > previousScrollX && mOnUiThread.getScrollY() > previousScrollY; } }.run(); } public void testRequestFocusNodeHref() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } startWebServer(false); String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); final String links = "<DL><p><DT><A HREF=\"" + url1 + "\">HTML_URL1</A><DT><A HREF=\"" + url2 + "\">HTML_URL2</A></DL><p>"; mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + links + "</body></html>", "text/html", null); getInstrumentation().waitForIdleSync(); final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper()); final Message hrefMsg = new Message(); hrefMsg.setTarget(handler); // focus on first link handler.reset(); getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); mOnUiThread.requestFocusNodeHref(hrefMsg); new PollingCheck() { @Override protected boolean check() { boolean done = false; if (handler.hasCalledHandleMessage()) { if (handler.mResultUrl != null) { done = true; } else { handler.reset(); Message newMsg = new Message(); newMsg.setTarget(handler); mOnUiThread.requestFocusNodeHref(newMsg); } } return done; } }.run(); assertEquals(url1, handler.getResultUrl()); // focus on second link handler.reset(); final Message hrefMsg2 = new Message(); hrefMsg2.setTarget(handler); getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); mOnUiThread.requestFocusNodeHref(hrefMsg2); new PollingCheck() { @Override protected boolean check() { boolean done = false; final String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); if (handler.hasCalledHandleMessage()) { if (handler.mResultUrl != null && handler.mResultUrl.equals(url2)) { done = true; } else { handler.reset(); Message newMsg = new Message(); newMsg.setTarget(handler); mOnUiThread.requestFocusNodeHref(newMsg); } } return done; } }.run(); assertEquals(url2, handler.getResultUrl()); mOnUiThread.requestFocusNodeHref(null); } public void testRequestImageRef() throws Exception, Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final class ImageLoaded { public boolean mImageLoaded; @JavascriptInterface public void loaded() { mImageLoaded = true; } } final ImageLoaded imageLoaded = new ImageLoaded(); runTestOnUiThread(new Runnable() { public void run() { mOnUiThread.getSettings().setJavaScriptEnabled(true); } }); mOnUiThread.addJavascriptInterface(imageLoaded, "imageLoaded"); AssetManager assets = getActivity().getAssets(); Bitmap bitmap = BitmapFactory.decodeStream(assets.open(TestHtmlConstants.LARGE_IMG_URL)); int imgWidth = bitmap.getWidth(); int imgHeight = bitmap.getHeight(); startWebServer(false); final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.LARGE_IMG_URL); mOnUiThread.loadDataAndWaitForCompletion( "<html><head><title>Title</title><style type=\"text/css\">" + "#imgElement { -webkit-transform: translate3d(0,0,1); }" + "#imgElement.finish { -webkit-transform: translate3d(0,0,0);" + " -webkit-transition-duration: 1ms; }</style>" + "<script type=\"text/javascript\">function imgLoad() {" + "imgElement = document.getElementById('imgElement');" + "imgElement.addEventListener('webkitTransitionEnd'," + "function(e) { imageLoaded.loaded(); });" + "imgElement.className = 'finish';}</script>" + "</head><body><img id=\"imgElement\" src=\"" + imgUrl + "\" width=\"" + imgWidth + "\" height=\"" + imgHeight + "\" onLoad=\"imgLoad()\"/></body></html>", "text/html", null); new PollingCheck() { @Override protected boolean check() { return imageLoaded.mImageLoaded; } }.run(); getInstrumentation().waitForIdleSync(); final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper()); final Message msg = new Message(); msg.setTarget(handler); // touch the image handler.reset(); int[] location = mOnUiThread.getLocationOnScreen(); long time = SystemClock.uptimeMillis(); getInstrumentation().sendPointerSync(MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, location[0] + imgWidth / 2, location[1] + imgHeight / 2, 0)); getInstrumentation().waitForIdleSync(); mOnUiThread.requestImageRef(msg); new PollingCheck() { @Override protected boolean check() { boolean done = false; if (handler.hasCalledHandleMessage()) { if (handler.mResultUrl != null) { done = true; } else { handler.reset(); Message newMsg = new Message(); newMsg.setTarget(handler); mOnUiThread.requestImageRef(newMsg); } } return done; } }.run(); assertEquals(imgUrl, handler.mResultUrl); } @UiThreadTest public void testDebugDump() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } mWebView.debugDump(); } public void testGetHitTestResult() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final String anchor = "<p><a href=\"" + TestHtmlConstants.EXT_WEB_URL1 + "\">normal anchor</a></p>"; final String blankAnchor = "<p><a href=\"\">blank anchor</a></p>"; final String form = "<p><form><input type=\"text\" name=\"Test\"><br>" + "<input type=\"submit\" value=\"Submit\"></form></p>"; String phoneNo = "3106984000"; final String tel = "<p><a href=\"tel:" + phoneNo + "\">Phone</a></p>"; String email = "test@gmail.com"; final String mailto = "<p><a href=\"mailto:" + email + "\">Email</a></p>"; String location = "shanghai"; final String geo = "<p><a href=\"geo:0,0?q=" + location + "\">Location</a></p>"; mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("fake://home", "<html><body>" + anchor + blankAnchor + form + tel + mailto + geo + "</body></html>", "text/html", "UTF-8", null); getInstrumentation().waitForIdleSync(); // anchor moveFocusDown(); HitTestResult hitTestResult = mOnUiThread.getHitTestResult(); assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType()); assertEquals(TestHtmlConstants.EXT_WEB_URL1, hitTestResult.getExtra()); // blank anchor moveFocusDown(); hitTestResult = mOnUiThread.getHitTestResult(); assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType()); assertEquals("fake://home", hitTestResult.getExtra()); // text field moveFocusDown(); hitTestResult = mOnUiThread.getHitTestResult(); assertEquals(HitTestResult.EDIT_TEXT_TYPE, hitTestResult.getType()); assertNull(hitTestResult.getExtra()); // submit button moveFocusDown(); hitTestResult = mOnUiThread.getHitTestResult(); assertEquals(HitTestResult.UNKNOWN_TYPE, hitTestResult.getType()); assertNull(hitTestResult.getExtra()); // phone number moveFocusDown(); hitTestResult = mOnUiThread.getHitTestResult(); assertEquals(HitTestResult.PHONE_TYPE, hitTestResult.getType()); assertEquals(phoneNo, hitTestResult.getExtra()); // email moveFocusDown(); hitTestResult = mOnUiThread.getHitTestResult(); assertEquals(HitTestResult.EMAIL_TYPE, hitTestResult.getType()); assertEquals(email, hitTestResult.getExtra()); // geo address moveFocusDown(); hitTestResult = mOnUiThread.getHitTestResult(); assertEquals(HitTestResult.GEO_TYPE, hitTestResult.getType()); assertEquals(location, hitTestResult.getExtra()); } public void testSetInitialScale() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final String p = "<p style=\"height:1000px;width:1000px\">Test setInitialScale.</p>"; final float defaultScale = getInstrumentation().getTargetContext().getResources() .getDisplayMetrics().density; mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + "</body></html>", "text/html", null); new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; } }.run(); mOnUiThread.setInitialScale(0); // modify content to fool WebKit into re-loading mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + "2" + "</body></html>", "text/html", null); new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; } }.run(); mOnUiThread.setInitialScale(50); mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + "3" + "</body></html>", "text/html", null); new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return Math.abs(0.5 - mOnUiThread.getScale()) < .01f; } }.run(); mOnUiThread.setInitialScale(0); mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + "4" + "</body></html>", "text/html", null); new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; } }.run(); } @UiThreadTest public void testClearHistory() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } startWebServer(false); String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); mOnUiThread.loadUrlAndWaitForCompletion(url1); pollingCheckWebBackForwardList(url1, 0, 1); mOnUiThread.loadUrlAndWaitForCompletion(url2); pollingCheckWebBackForwardList(url2, 1, 2); mOnUiThread.loadUrlAndWaitForCompletion(url3); pollingCheckWebBackForwardList(url3, 2, 3); mWebView.clearHistory(); // only current URL is left after clearing pollingCheckWebBackForwardList(url3, 0, 1); } @UiThreadTest public void testSaveAndRestoreState() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } // nothing to save assertNull(mWebView.saveState(new Bundle())); startWebServer(false); String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); // make a history list mOnUiThread.loadUrlAndWaitForCompletion(url1); pollingCheckWebBackForwardList(url1, 0, 1); mOnUiThread.loadUrlAndWaitForCompletion(url2); pollingCheckWebBackForwardList(url2, 1, 2); mOnUiThread.loadUrlAndWaitForCompletion(url3); pollingCheckWebBackForwardList(url3, 2, 3); // save the list Bundle bundle = new Bundle(); WebBackForwardList saveList = mWebView.saveState(bundle); assertNotNull(saveList); assertEquals(3, saveList.getSize()); assertEquals(2, saveList.getCurrentIndex()); assertEquals(url1, saveList.getItemAtIndex(0).getUrl()); assertEquals(url2, saveList.getItemAtIndex(1).getUrl()); assertEquals(url3, saveList.getItemAtIndex(2).getUrl()); // change the content to a new "blank" web view without history final WebView newWebView = new WebView(getActivity()); WebBackForwardList copyListBeforeRestore = newWebView.copyBackForwardList(); assertNotNull(copyListBeforeRestore); assertEquals(0, copyListBeforeRestore.getSize()); // restore the list final WebBackForwardList restoreList = newWebView.restoreState(bundle); assertNotNull(restoreList); assertEquals(3, restoreList.getSize()); assertEquals(2, saveList.getCurrentIndex()); // wait for the list items to get inflated new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return restoreList.getItemAtIndex(0).getUrl() != null && restoreList.getItemAtIndex(1).getUrl() != null && restoreList.getItemAtIndex(2).getUrl() != null; } }.run(); assertEquals(url1, restoreList.getItemAtIndex(0).getUrl()); assertEquals(url2, restoreList.getItemAtIndex(1).getUrl()); assertEquals(url3, restoreList.getItemAtIndex(2).getUrl()); WebBackForwardList copyListAfterRestore = newWebView.copyBackForwardList(); assertNotNull(copyListAfterRestore); assertEquals(3, copyListAfterRestore.getSize()); assertEquals(2, copyListAfterRestore.getCurrentIndex()); assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl()); assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl()); assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl()); } public void testSetWebViewClient() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final ScaleChangedWebViewClient webViewClient = new ScaleChangedWebViewClient(); mOnUiThread.setWebViewClient(webViewClient); startWebServer(false); assertFalse(webViewClient.onScaleChangedCalled()); String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); mOnUiThread.loadUrlAndWaitForCompletion(url1); pollingCheckForCanZoomIn(); assertTrue(mOnUiThread.zoomIn()); webViewClient.waitForScaleChanged(); } public void testRequestChildRectangleOnScreen() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); final int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels); String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\"> </p>"; mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + "</body></html>", "text/html", null); new PollingCheck() { @Override protected boolean check() { return mOnUiThread.getContentHeight() >= dimension; } }.run(); int origX = mOnUiThread.getScrollX(); int origY = mOnUiThread.getScrollY(); int half = dimension / 2; Rect rect = new Rect(half, half, half + 1, half + 1); assertTrue(mOnUiThread.requestChildRectangleOnScreen(mWebView, rect, true)); assertTrue(mOnUiThread.getScrollX() > origX); assertTrue(mOnUiThread.getScrollY() > origY); } public void testSetDownloadListener() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final CountDownLatch resultLatch = new CountDownLatch(1); final class MyDownloadListener implements DownloadListener { public String url; public String mimeType; public long contentLength; public String contentDisposition; @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { this.url = url; this.mimeType = mimetype; this.contentLength = contentLength; this.contentDisposition = contentDisposition; resultLatch.countDown(); } } final String mimeType = "application/octet-stream"; final int length = 100; final MyDownloadListener listener = new MyDownloadListener(); startWebServer(false); final String url = mWebServer.getBinaryUrl(mimeType, length); // By default, WebView sends an intent to ask the system to // handle loading a new URL. We set WebViewClient as // WebViewClient.shouldOverrideUrlLoading() returns false, so // the WebView will load the new URL. mOnUiThread.setDownloadListener(listener); mOnUiThread.getSettings().setJavaScriptEnabled(true); mOnUiThread.loadDataAndWaitForCompletion( "<html><body onload=\"window.location = \'" + url + "\'\"></body></html>", "text/html", null); // Wait for layout to complete before setting focus. getInstrumentation().waitForIdleSync(); assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); assertEquals(url, listener.url); assertTrue(listener.contentDisposition.contains("test.bin")); assertEquals(length, listener.contentLength); assertEquals(mimeType, listener.mimeType); } @UiThreadTest public void testSetLayoutParams() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(600, 800); mWebView.setLayoutParams(params); assertSame(params, mWebView.getLayoutParams()); } @UiThreadTest public void testSetMapTrackballToArrowKeys() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } mWebView.setMapTrackballToArrowKeys(true); } public void testSetNetworkAvailable() throws Exception { if (!NullWebViewUtils.isWebViewAvailable()) { return; } WebSettings settings = mOnUiThread.getSettings(); settings.setJavaScriptEnabled(true); startWebServer(false); String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL); mOnUiThread.loadUrlAndWaitForCompletion(url); assertEquals("ONLINE", mOnUiThread.getTitle()); mOnUiThread.setNetworkAvailable(false); // Wait for the DOM to receive notification of the network state change. new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return mOnUiThread.getTitle().equals("OFFLINE"); } }.run(); mOnUiThread.setNetworkAvailable(true); // Wait for the DOM to receive notification of the network state change. new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return mOnUiThread.getTitle().equals("ONLINE"); } }.run(); } public void testSetWebChromeClient() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } final class MockWebChromeClient extends WaitForProgressClient { private boolean mOnProgressChanged = false; public MockWebChromeClient() { super(mOnUiThread); } @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); mOnProgressChanged = true; } public boolean onProgressChangedCalled() { return mOnProgressChanged; } } final MockWebChromeClient webChromeClient = new MockWebChromeClient(); mOnUiThread.setWebChromeClient(webChromeClient); getInstrumentation().waitForIdleSync(); assertFalse(webChromeClient.onProgressChangedCalled()); startWebServer(false); final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); mOnUiThread.loadUrlAndWaitForCompletion(url); getInstrumentation().waitForIdleSync(); new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return webChromeClient.onProgressChangedCalled(); } }.run(); } public void testPauseResumeTimers() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } class Monitor { private boolean mIsUpdated; @JavascriptInterface public synchronized void update() { mIsUpdated = true; notify(); } public synchronized boolean waitForUpdate() { while (!mIsUpdated) { try { // This is slightly flaky, as we can't guarantee that // this is a sufficient time limit, but there's no way // around this. wait(1000); if (!mIsUpdated) { return false; } } catch (InterruptedException e) { } } mIsUpdated = false; return true; } } ; final Monitor monitor = new Monitor(); final String updateMonitorHtml = "<html>" + "<body onload=\"monitor.update();\"></body></html>"; // Test that JavaScript is executed even with timers paused. runTestOnUiThread(new Runnable() { @Override public void run() { mWebView.getSettings().setJavaScriptEnabled(true); mWebView.addJavascriptInterface(monitor, "monitor"); mWebView.pauseTimers(); mOnUiThread.loadDataAndWaitForCompletion(updateMonitorHtml, "text/html", null); } }); assertTrue(monitor.waitForUpdate()); // Start a timer and test that it does not fire. mOnUiThread.loadDataAndWaitForCompletion( "<html><body onload='setTimeout(function(){monitor.update();},100)'>" + "</body></html>", "text/html", null); assertFalse(monitor.waitForUpdate()); // Resume timers and test that the timer fires. mOnUiThread.resumeTimers(); assertTrue(monitor.waitForUpdate()); } // verify query parameters can be passed correctly to android asset files public void testAndroidAssetQueryParam() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } WebSettings settings = mOnUiThread.getSettings(); settings.setJavaScriptEnabled(true); // test passing a parameter String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.PARAM_ASSET_URL + "?val=SUCCESS"); mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); assertEquals("SUCCESS", mOnUiThread.getTitle()); } // verify anchors work correctly for android asset files public void testAndroidAssetAnchor() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } WebSettings settings = mOnUiThread.getSettings(); settings.setJavaScriptEnabled(true); // test using an anchor String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.ANCHOR_ASSET_URL + "#anchor"); mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); assertEquals("anchor", mOnUiThread.getTitle()); } public void testEvaluateJavascript() { if (!NullWebViewUtils.isWebViewAvailable()) { return; } mOnUiThread.getSettings().setJavaScriptEnabled(true); mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); EvaluateJsResultPollingCheck jsResult = new EvaluateJsResultPollingCheck("2"); mOnUiThread.evaluateJavascript("1+1", jsResult); jsResult.run(); jsResult = new EvaluateJsResultPollingCheck("9"); mOnUiThread.evaluateJavascript("1+1; 4+5", jsResult); jsResult.run(); final String EXPECTED_TITLE = "test"; mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null); new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return mOnUiThread.getTitle().equals(EXPECTED_TITLE); } }.run(); } // Verify Print feature can create a PDF file with a correct preamble. public void testPrinting() throws Throwable { if (!NullWebViewUtils.isWebViewAvailable()) { return; } mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" + "<body>foo</body></html>", "text/html", null); final PrintDocumentAdapter adapter = mOnUiThread.createPrintDocumentAdapter(); printDocumentStart(adapter); PrintAttributes attributes = new PrintAttributes.Builder().setMediaSize(PrintAttributes.MediaSize.ISO_A4) .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300)) .setMinMargins(PrintAttributes.Margins.NO_MARGINS).build(); final WebViewCtsActivity activity = getActivity(); final File file = activity.getFileStreamPath(PRINTER_TEST_FILE); final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode("w")); final FutureTask<Boolean> result = new FutureTask<Boolean>(new Callable<Boolean>() { public Boolean call() { return true; } }); printDocumentLayout(adapter, null, attributes, new LayoutResultCallback() { // Called on UI thread @Override public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { savePrintedPage(adapter, descriptor, result); } }); try { result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS); assertTrue(file.length() > 0); FileInputStream in = new FileInputStream(file); byte[] b = new byte[PDF_PREAMBLE.length()]; in.read(b); String preamble = new String(b); assertEquals(PDF_PREAMBLE, preamble); } finally { // close the descriptor, if not closed already. descriptor.close(); file.delete(); } } private void savePrintedPage(final PrintDocumentAdapter adapter, final ParcelFileDescriptor descriptor, final FutureTask<Boolean> result) { adapter.onWrite(new PageRange[] { PageRange.ALL_PAGES }, descriptor, new CancellationSignal(), new WriteResultCallback() { @Override public void onWriteFinished(PageRange[] pages) { try { descriptor.close(); result.run(); } catch (IOException ex) { fail("Failed file operation: " + ex.toString()); } } }); } private void printDocumentStart(final PrintDocumentAdapter adapter) { mOnUiThread.runOnUiThread(new Runnable() { @Override public void run() { adapter.onStart(); } }); } private void printDocumentLayout(final PrintDocumentAdapter adapter, final PrintAttributes oldAttributes, final PrintAttributes newAttributes, final LayoutResultCallback layoutResultCallback) { mOnUiThread.runOnUiThread(new Runnable() { @Override public void run() { adapter.onLayout(oldAttributes, newAttributes, new CancellationSignal(), layoutResultCallback, null); } }); } private static class HrefCheckHandler extends Handler { private boolean mHadRecieved; private String mResultUrl; public HrefCheckHandler(Looper looper) { super(looper); } public boolean hasCalledHandleMessage() { return mHadRecieved; } public String getResultUrl() { return mResultUrl; } public void reset() { mResultUrl = null; mHadRecieved = false; } @Override public void handleMessage(Message msg) { mResultUrl = msg.getData().getString("url"); mHadRecieved = true; } } private void moveFocusDown() throws Throwable { // send down key and wait for idle getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); // waiting for idle isn't always sufficient for the key to be fully processed Thread.sleep(500); } private void pollingCheckWebBackForwardList(final String currUrl, final int currIndex, final int size) { new PollingCheck() { @Override protected boolean check() { WebBackForwardList list = mWebView.copyBackForwardList(); return checkWebBackForwardList(list, currUrl, currIndex, size); } }.run(); } private boolean checkWebBackForwardList(WebBackForwardList list, String currUrl, int currIndex, int size) { return (list != null) && (list.getSize() == size) && (list.getCurrentIndex() == currIndex) && list.getItemAtIndex(currIndex).getUrl().equals(currUrl); } private void assertGoBackOrForwardBySteps(boolean expected, int steps) { // skip if steps equals to 0 if (steps == 0) return; int start = steps > 0 ? 1 : steps; int end = steps > 0 ? steps : -1; // check all the steps in the history for (int i = start; i <= end; i++) { assertEquals(expected, mWebView.canGoBackOrForward(i)); // shortcut methods for one step if (i == 1) { assertEquals(expected, mWebView.canGoForward()); } else if (i == -1) { assertEquals(expected, mWebView.canGoBack()); } } } private boolean isPictureFilledWithColor(Picture picture, int color) { if (picture.getWidth() == 0 || picture.getHeight() == 0) return false; Bitmap bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(), Config.ARGB_8888); picture.draw(new Canvas(bitmap)); for (int i = 0; i < bitmap.getWidth(); i++) { for (int j = 0; j < bitmap.getHeight(); j++) { if (color != bitmap.getPixel(i, j)) { return false; } } } return true; } /** * Waits at least MIN_SCROLL_WAIT_MS for scrolling to start. Once started, * scrolling is checked every SCROLL_WAIT_INTERVAL_MS for changes. Once * changes have stopped, the function exits. If no scrolling has happened * then the function exits after MIN_SCROLL_WAIT milliseconds. * @param previousScrollY The Y scroll position prior to waiting. */ private void waitForScrollingComplete(int previousScrollY) throws InterruptedException { int scrollY = previousScrollY; // wait at least MIN_SCROLL_WAIT for something to happen. long noChangeMinWait = SystemClock.uptimeMillis() + MIN_SCROLL_WAIT_MS; boolean scrollChanging = false; boolean scrollChanged = false; boolean minWaitExpired = false; while (scrollChanging || (!scrollChanged && !minWaitExpired)) { Thread.sleep(SCROLL_WAIT_INTERVAL_MS); int oldScrollY = scrollY; scrollY = mOnUiThread.getScrollY(); scrollChanging = (scrollY != oldScrollY); scrollChanged = (scrollY != previousScrollY); minWaitExpired = (SystemClock.uptimeMillis() > noChangeMinWait); } } private void pollingCheckForCanZoomIn() { new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return mOnUiThread.canZoomIn(); } }.run(); } final class ScaleChangedWebViewClient extends WaitForLoadedClient { private boolean mOnScaleChangedCalled = false; public ScaleChangedWebViewClient() { super(mOnUiThread); } @Override public void onScaleChanged(WebView view, float oldScale, float newScale) { super.onScaleChanged(view, oldScale, newScale); synchronized (this) { mOnScaleChangedCalled = true; } } public void waitForScaleChanged() { new PollingCheck(TEST_TIMEOUT) { @Override protected boolean check() { return onScaleChangedCalled(); } }.run(); synchronized (this) { mOnScaleChangedCalled = false; } } public synchronized boolean onScaleChangedCalled() { return mOnScaleChangedCalled; } } }