1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.webkit.cts;
18 
19 import static org.hamcrest.MatcherAssert.assertThat;
20 import static org.hamcrest.Matchers.greaterThan;
21 import static org.hamcrest.Matchers.lessThan;
22 
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.ContextWrapper;
26 import android.content.res.AssetManager;
27 import android.graphics.Bitmap;
28 import android.graphics.Bitmap.Config;
29 import android.graphics.BitmapFactory;
30 import android.graphics.Canvas;
31 import android.graphics.Color;
32 import android.graphics.Picture;
33 import android.graphics.Rect;
34 import android.graphics.pdf.PdfRenderer;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.CancellationSignal;
38 import android.os.Handler;
39 import android.os.LocaleList;
40 import android.os.Looper;
41 import android.os.Message;
42 import android.os.ParcelFileDescriptor;
43 import android.os.StrictMode;
44 import android.os.StrictMode.ThreadPolicy;
45 import android.os.SystemClock;
46 import android.platform.test.annotations.AppModeFull;
47 import android.platform.test.annotations.Presubmit;
48 import android.print.PageRange;
49 import android.print.PrintAttributes;
50 import android.print.PrintDocumentAdapter;
51 import android.print.PrintDocumentAdapter.LayoutResultCallback;
52 import android.print.PrintDocumentAdapter.WriteResultCallback;
53 import android.print.PrintDocumentInfo;
54 import android.test.ActivityInstrumentationTestCase2;
55 import android.test.UiThreadTest;
56 import android.util.AttributeSet;
57 import android.util.DisplayMetrics;
58 import android.view.KeyEvent;
59 import android.view.MotionEvent;
60 import android.view.View;
61 import android.view.ViewGroup;
62 import android.view.textclassifier.TextClassification;
63 import android.view.textclassifier.TextClassifier;
64 import android.view.textclassifier.TextSelection;
65 import android.webkit.ConsoleMessage;
66 import android.webkit.CookieSyncManager;
67 import android.webkit.DownloadListener;
68 import android.webkit.JavascriptInterface;
69 import android.webkit.SafeBrowsingResponse;
70 import android.webkit.ValueCallback;
71 import android.webkit.WebBackForwardList;
72 import android.webkit.WebChromeClient;
73 import android.webkit.WebIconDatabase;
74 import android.webkit.WebResourceRequest;
75 import android.webkit.WebSettings;
76 import android.webkit.WebView;
77 import android.webkit.WebView.HitTestResult;
78 import android.webkit.WebView.PictureListener;
79 import android.webkit.WebView.VisualStateCallback;
80 import android.webkit.WebViewClient;
81 import android.webkit.WebViewDatabase;
82 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
83 import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
84 import android.widget.LinearLayout;
85 
86 import com.android.compatibility.common.util.NullWebViewUtils;
87 import com.android.compatibility.common.util.PollingCheck;
88 import com.google.common.util.concurrent.SettableFuture;
89 
90 import java.io.ByteArrayInputStream;
91 import java.io.File;
92 import java.io.FileInputStream;
93 import java.io.FileNotFoundException;
94 import java.io.IOException;
95 
96 import java.net.MalformedURLException;
97 import java.net.URL;
98 
99 import java.nio.charset.Charset;
100 import java.nio.charset.StandardCharsets;
101 
102 import java.util.Collections;
103 import java.util.Date;
104 import java.util.concurrent.atomic.AtomicBoolean;
105 import java.util.concurrent.atomic.AtomicReference;
106 import java.util.concurrent.Callable;
107 import java.util.concurrent.Future;
108 import java.util.concurrent.FutureTask;
109 import java.util.concurrent.Semaphore;
110 import java.util.concurrent.TimeUnit;
111 import java.util.ArrayList;
112 import java.util.HashMap;
113 import java.util.List;
114 import java.util.Map;
115 
116 import org.apache.http.Header;
117 import org.apache.http.HttpEntity;
118 import org.apache.http.HttpEntityEnclosingRequest;
119 import org.apache.http.HttpRequest;
120 import org.apache.http.util.EncodingUtils;
121 import org.apache.http.util.EntityUtils;
122 
123 @AppModeFull
124 public class WebViewTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
125     public static final long TEST_TIMEOUT = 20000L;
126     private static final int INITIAL_PROGRESS = 100;
127     private static final String X_REQUESTED_WITH = "X-Requested-With";
128     private static final String PRINTER_TEST_FILE = "print.pdf";
129     private static final String PDF_PREAMBLE = "%PDF-1";
130     // Snippet of HTML that will prevent favicon requests to the test server.
131     private static final String HTML_HEADER =
132             "<html><head><link rel=\"shortcut icon\" href=\"%23\" /></head>";
133     private static final String SIMPLE_HTML = "<html><body>simple html</body></html>";
134 
135     /**
136      * This is the minimum number of milliseconds to wait for scrolling to
137      * start. If no scrolling has started before this timeout then it is
138      * assumed that no scrolling will happen.
139      */
140     private static final long MIN_SCROLL_WAIT_MS = 1000;
141 
142     /**
143      * This is the minimum number of milliseconds to wait for findAll to
144      * find all the matches. If matches are not found, the Listener would
145      * call findAll again until it times out.
146      */
147     private static final long MIN_FIND_WAIT_MS = 3000;
148 
149     /**
150      * Once scrolling has started, this is the interval that scrolling
151      * is checked to see if there is a change. If no scrolling change
152      * has happened in the given time then it is assumed that scrolling
153      * has stopped.
154      */
155     private static final long SCROLL_WAIT_INTERVAL_MS = 200;
156 
157     private WebView mWebView;
158     private CtsTestServer mWebServer;
159     private WebViewOnUiThread mOnUiThread;
160     private WebIconDatabase mIconDb;
161 
WebViewTest()162     public WebViewTest() {
163         super("com.android.cts.webkit", WebViewCtsActivity.class);
164     }
165 
166     @Override
setUp()167     protected void setUp() throws Exception {
168         super.setUp();
169         final WebViewCtsActivity activity = getActivity();
170         mWebView = activity.getWebView();
171         if (mWebView != null) {
172             new PollingCheck() {
173                 @Override
174                     protected boolean check() {
175                         return activity.hasWindowFocus();
176                 }
177             }.run();
178             File f = activity.getFileStreamPath("snapshot");
179             if (f.exists()) {
180                 f.delete();
181             }
182 
183             mOnUiThread = new WebViewOnUiThread(mWebView);
184         }
185     }
186 
187     @Override
tearDown()188     protected void tearDown() throws Exception {
189         if (mOnUiThread != null) {
190             mOnUiThread.cleanUp();
191         }
192         if (mWebServer != null) {
193             stopWebServer();
194         }
195         if (mIconDb != null) {
196             mIconDb.removeAllIcons();
197             mIconDb.close();
198             mIconDb = null;
199         }
200         super.tearDown();
201     }
202 
startWebServer(boolean secure)203     private void startWebServer(boolean secure) throws Exception {
204         assertNull(mWebServer);
205         mWebServer = new CtsTestServer(getActivity(), secure);
206     }
207 
stopWebServer()208     private void stopWebServer() throws Exception {
209         assertNotNull(mWebServer);
210         ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
211         ThreadPolicy tmpPolicy = new ThreadPolicy.Builder(oldPolicy)
212                 .permitNetwork()
213                 .build();
214         StrictMode.setThreadPolicy(tmpPolicy);
215         mWebServer.shutdown();
216         mWebServer = null;
217         StrictMode.setThreadPolicy(oldPolicy);
218     }
219 
220     @UiThreadTest
testConstructor()221     public void testConstructor() {
222         if (!NullWebViewUtils.isWebViewAvailable()) {
223             return;
224         }
225 
226         new WebView(getActivity());
227         new WebView(getActivity(), null);
228         new WebView(getActivity(), null, 0);
229     }
230 
231     @UiThreadTest
testCreatingWebViewWithDeviceEncrpytionFails()232     public void testCreatingWebViewWithDeviceEncrpytionFails() {
233         if (!NullWebViewUtils.isWebViewAvailable()) {
234             return;
235         }
236 
237         Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext();
238         try {
239             new WebView(deviceEncryptedContext);
240         } catch (IllegalArgumentException e) {
241             return;
242         }
243 
244         fail("WebView should have thrown exception when creating with a device " +
245                 "protected storage context");
246     }
247 
248     @UiThreadTest
testCreatingWebViewWithMultipleEncryptionContext()249     public void testCreatingWebViewWithMultipleEncryptionContext() {
250         if (!NullWebViewUtils.isWebViewAvailable()) {
251             return;
252         }
253 
254         // Credential encrpytion is the default. Create one here for the sake of clarity.
255         Context credentialEncryptedContext = getActivity().createCredentialProtectedStorageContext();
256         Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext();
257 
258         // No exception should be thrown with credential encryption context.
259         new WebView(credentialEncryptedContext);
260 
261         try {
262             new WebView(deviceEncryptedContext);
263         } catch (IllegalArgumentException e) {
264             return;
265         }
266 
267         fail("WebView should have thrown exception when creating with a device " +
268                 "protected storage context");
269     }
270 
271     @UiThreadTest
testCreatingWebViewCreatesCookieSyncManager()272     public void testCreatingWebViewCreatesCookieSyncManager() throws Exception {
273         if (!NullWebViewUtils.isWebViewAvailable()) {
274             return;
275         }
276         new WebView(getActivity());
277         assertNotNull(CookieSyncManager.getInstance());
278     }
279 
280     // Static methods should be safe to call on non-UI threads
testFindAddress()281     public void testFindAddress() {
282         if (!NullWebViewUtils.isWebViewAvailable()) {
283             return;
284         }
285 
286         /*
287          * Info about USPS
288          * http://en.wikipedia.org/wiki/Postal_address#United_States
289          * http://www.usps.com/
290          */
291         // full address
292         assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826",
293                 WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826"));
294         // Zipcode is optional.
295         assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA",
296                 WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA"));
297         // not an address
298         assertNull(WebView.findAddress("This is not an address: no town, no state, no zip."));
299 
300         // would be an address, except for numbers that are not ASCII
301         assertNull(WebView.findAddress(
302                 "80\uD835\uDFEF \uD835\uDFEF\uD835\uDFEFth Avenue Sunnyvale, CA 94089"));
303     }
304 
305     @UiThreadTest
testScrollBarOverlay()306     public void testScrollBarOverlay() throws Throwable {
307         if (!NullWebViewUtils.isWebViewAvailable()) {
308             return;
309         }
310 
311         // These functions have no effect; just verify they don't crash
312         mWebView.setHorizontalScrollbarOverlay(true);
313         mWebView.setVerticalScrollbarOverlay(false);
314 
315         assertTrue(mWebView.overlayHorizontalScrollbar());
316         assertFalse(mWebView.overlayVerticalScrollbar());
317     }
318 
319     @Presubmit
320     @UiThreadTest
testLoadUrl()321     public void testLoadUrl() throws Exception {
322         if (!NullWebViewUtils.isWebViewAvailable()) {
323             return;
324         }
325 
326         assertNull(mWebView.getUrl());
327         assertNull(mWebView.getOriginalUrl());
328         assertEquals(INITIAL_PROGRESS, mWebView.getProgress());
329 
330         startWebServer(false);
331         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
332         mOnUiThread.loadUrlAndWaitForCompletion(url);
333         assertEquals(100, mWebView.getProgress());
334         assertEquals(url, mWebView.getUrl());
335         assertEquals(url, mWebView.getOriginalUrl());
336         assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle());
337 
338         // verify that the request also includes X-Requested-With header
339         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
340         Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH);
341         assertEquals(1, matchingHeaders.length);
342 
343         Header header = matchingHeaders[0];
344         assertEquals(mWebView.getContext().getApplicationInfo().packageName, header.getValue());
345     }
346 
347     @UiThreadTest
testPostUrlWithNonNetworkUrl()348     public void testPostUrlWithNonNetworkUrl() throws Exception {
349         if (!NullWebViewUtils.isWebViewAvailable()) {
350             return;
351         }
352         final String nonNetworkUrl = "file:///android_asset/" + TestHtmlConstants.HELLO_WORLD_URL;
353 
354         mOnUiThread.postUrlAndWaitForCompletion(nonNetworkUrl, new byte[1]);
355 
356         assertEquals("Non-network URL should have loaded", TestHtmlConstants.HELLO_WORLD_TITLE,
357                 mWebView.getTitle());
358     }
359 
360     @UiThreadTest
testPostUrlWithNetworkUrl()361     public void testPostUrlWithNetworkUrl() throws Exception {
362         if (!NullWebViewUtils.isWebViewAvailable()) {
363             return;
364         }
365         startWebServer(false);
366         final String networkUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
367         final String postDataString = "username=my_username&password=my_password";
368         final byte[] postData = EncodingUtils.getBytes(postDataString, "BASE64");
369 
370         mOnUiThread.postUrlAndWaitForCompletion(networkUrl, postData);
371 
372         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
373         assertEquals("The last request should be POST", request.getRequestLine().getMethod(),
374                 "POST");
375 
376         assertTrue("The last request should have a request body",
377                 request instanceof HttpEntityEnclosingRequest);
378         HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
379         String entityString = EntityUtils.toString(entity);
380         assertEquals(entityString, postDataString);
381     }
382 
383     @UiThreadTest
testLoadUrlDoesNotStripParamsWhenLoadingContentUrls()384     public void testLoadUrlDoesNotStripParamsWhenLoadingContentUrls() throws Exception {
385         if (!NullWebViewUtils.isWebViewAvailable()) {
386             return;
387         }
388 
389         Uri.Builder uriBuilder = new Uri.Builder().scheme(
390                 ContentResolver.SCHEME_CONTENT).authority(MockContentProvider.AUTHORITY);
391         uriBuilder.appendPath("foo.html").appendQueryParameter("param","bar");
392         String url = uriBuilder.build().toString();
393         mOnUiThread.loadUrlAndWaitForCompletion(url);
394         // verify the parameter is not stripped.
395         Uri uri = Uri.parse(mWebView.getTitle());
396         assertEquals("bar", uri.getQueryParameter("param"));
397     }
398 
399     @UiThreadTest
testAppInjectedXRequestedWithHeaderIsNotOverwritten()400     public void testAppInjectedXRequestedWithHeaderIsNotOverwritten() throws Exception {
401         if (!NullWebViewUtils.isWebViewAvailable()) {
402             return;
403         }
404 
405         startWebServer(false);
406         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
407         HashMap<String, String> map = new HashMap<String, String>();
408         final String requester = "foo";
409         map.put(X_REQUESTED_WITH, requester);
410         mOnUiThread.loadUrlAndWaitForCompletion(url, map);
411 
412         // verify that the request also includes X-Requested-With header
413         // but is not overwritten by the webview
414         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
415         Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH);
416         assertEquals(1, matchingHeaders.length);
417 
418         Header header = matchingHeaders[0];
419         assertEquals(requester, header.getValue());
420     }
421 
422     @UiThreadTest
testAppCanInjectHeadersViaImmutableMap()423     public void testAppCanInjectHeadersViaImmutableMap() throws Exception {
424         if (!NullWebViewUtils.isWebViewAvailable()) {
425             return;
426         }
427 
428         startWebServer(false);
429         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
430         HashMap<String, String> map = new HashMap<String, String>();
431         final String requester = "foo";
432         map.put(X_REQUESTED_WITH, requester);
433         mOnUiThread.loadUrlAndWaitForCompletion(url, Collections.unmodifiableMap(map));
434 
435         // verify that the request also includes X-Requested-With header
436         // but is not overwritten by the webview
437         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
438         Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH);
439         assertEquals(1, matchingHeaders.length);
440 
441         Header header = matchingHeaders[0];
442         assertEquals(requester, header.getValue());
443     }
444 
testCanInjectHeaders()445     public void testCanInjectHeaders() throws Exception {
446         if (!NullWebViewUtils.isWebViewAvailable()) {
447             return;
448         }
449 
450         final String X_FOO = "X-foo";
451         final String X_FOO_VALUE = "test";
452 
453         final String X_REFERER = "Referer";
454         final String X_REFERER_VALUE = "http://www.example.com/";
455         startWebServer(false);
456         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
457         HashMap<String, String> map = new HashMap<String, String>();
458         map.put(X_FOO, X_FOO_VALUE);
459         map.put(X_REFERER, X_REFERER_VALUE);
460         mOnUiThread.loadUrlAndWaitForCompletion(url, map);
461 
462         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
463         for (Map.Entry<String,String> value : map.entrySet()) {
464             String header = value.getKey();
465             Header[] matchingHeaders = request.getHeaders(header);
466             assertEquals("header " + header + " not found", 1, matchingHeaders.length);
467             assertEquals(value.getValue(), matchingHeaders[0].getValue());
468         }
469     }
470 
471     @SuppressWarnings("deprecation")
472     @UiThreadTest
testGetVisibleTitleHeight()473     public void testGetVisibleTitleHeight() throws Exception {
474         if (!NullWebViewUtils.isWebViewAvailable()) {
475             return;
476         }
477 
478         startWebServer(false);
479         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
480         mOnUiThread.loadUrlAndWaitForCompletion(url);
481         assertEquals(0, mWebView.getVisibleTitleHeight());
482     }
483 
484     @UiThreadTest
testGetOriginalUrl()485     public void testGetOriginalUrl() throws Throwable {
486         if (!NullWebViewUtils.isWebViewAvailable()) {
487             return;
488         }
489 
490         startWebServer(false);
491         final String finalUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
492         final String redirectUrl =
493                 mWebServer.getRedirectingAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
494 
495         assertNull(mWebView.getUrl());
496         assertNull(mWebView.getOriginalUrl());
497 
498         // By default, WebView sends an intent to ask the system to
499         // handle loading a new URL. We set a WebViewClient as
500         // WebViewClient.shouldOverrideUrlLoading() returns false, so
501         // the WebView will load the new URL.
502         mWebView.setWebViewClient(new WaitForLoadedClient(mOnUiThread));
503         mOnUiThread.loadUrlAndWaitForCompletion(redirectUrl);
504 
505         assertEquals(finalUrl, mWebView.getUrl());
506         assertEquals(redirectUrl, mWebView.getOriginalUrl());
507     }
508 
testStopLoading()509     public void testStopLoading() throws Exception {
510         if (!NullWebViewUtils.isWebViewAvailable()) {
511             return;
512         }
513 
514         assertEquals(INITIAL_PROGRESS, mOnUiThread.getProgress());
515 
516         startWebServer(false);
517         String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.STOP_LOADING_URL);
518 
519         class JsInterface {
520             private boolean mPageLoaded;
521 
522             @JavascriptInterface
523             public synchronized void pageLoaded() {
524                 mPageLoaded = true;
525                 notify();
526             }
527             public synchronized boolean getPageLoaded() {
528                 return mPageLoaded;
529             }
530         }
531 
532         JsInterface jsInterface = new JsInterface();
533 
534         mOnUiThread.getSettings().setJavaScriptEnabled(true);
535         mOnUiThread.addJavascriptInterface(jsInterface, "javabridge");
536         mOnUiThread.loadUrl(url);
537         mOnUiThread.stopLoading();
538 
539         // We wait to see that the onload callback in the HTML is not fired.
540         synchronized (jsInterface) {
541             jsInterface.wait(3000);
542         }
543 
544         assertFalse(jsInterface.getPageLoaded());
545     }
546 
547     @UiThreadTest
testGoBackAndForward()548     public void testGoBackAndForward() throws Exception {
549         if (!NullWebViewUtils.isWebViewAvailable()) {
550             return;
551         }
552 
553         assertGoBackOrForwardBySteps(false, -1);
554         assertGoBackOrForwardBySteps(false, 1);
555 
556         startWebServer(false);
557         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
558         String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
559         String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);
560 
561         mOnUiThread.loadUrlAndWaitForCompletion(url1);
562         pollingCheckWebBackForwardList(url1, 0, 1);
563         assertGoBackOrForwardBySteps(false, -1);
564         assertGoBackOrForwardBySteps(false, 1);
565 
566         mOnUiThread.loadUrlAndWaitForCompletion(url2);
567         pollingCheckWebBackForwardList(url2, 1, 2);
568         assertGoBackOrForwardBySteps(true, -1);
569         assertGoBackOrForwardBySteps(false, 1);
570 
571         mOnUiThread.loadUrlAndWaitForCompletion(url3);
572         pollingCheckWebBackForwardList(url3, 2, 3);
573         assertGoBackOrForwardBySteps(true, -2);
574         assertGoBackOrForwardBySteps(false, 1);
575 
576         mWebView.goBack();
577         pollingCheckWebBackForwardList(url2, 1, 3);
578         assertGoBackOrForwardBySteps(true, -1);
579         assertGoBackOrForwardBySteps(true, 1);
580 
581         mWebView.goForward();
582         pollingCheckWebBackForwardList(url3, 2, 3);
583         assertGoBackOrForwardBySteps(true, -2);
584         assertGoBackOrForwardBySteps(false, 1);
585 
586         mWebView.goBackOrForward(-2);
587         pollingCheckWebBackForwardList(url1, 0, 3);
588         assertGoBackOrForwardBySteps(false, -1);
589         assertGoBackOrForwardBySteps(true, 2);
590 
591         mWebView.goBackOrForward(2);
592         pollingCheckWebBackForwardList(url3, 2, 3);
593         assertGoBackOrForwardBySteps(true, -2);
594         assertGoBackOrForwardBySteps(false, 1);
595     }
596 
testAddJavascriptInterface()597     public void testAddJavascriptInterface() throws Exception {
598         if (!NullWebViewUtils.isWebViewAvailable()) {
599             return;
600         }
601 
602         mOnUiThread.getSettings().setJavaScriptEnabled(true);
603         mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
604 
605         final class DummyJavaScriptInterface {
606             private boolean mWasProvideResultCalled;
607             private String mResult;
608 
609             private synchronized String waitForResult() {
610                 while (!mWasProvideResultCalled) {
611                     try {
612                         wait(TEST_TIMEOUT);
613                     } catch (InterruptedException e) {
614                         continue;
615                     }
616                     if (!mWasProvideResultCalled) {
617                         fail("Unexpected timeout");
618                     }
619                 }
620                 return mResult;
621             }
622 
623             public synchronized boolean wasProvideResultCalled() {
624                 return mWasProvideResultCalled;
625             }
626 
627             @JavascriptInterface
628             public synchronized void provideResult(String result) {
629                 mWasProvideResultCalled = true;
630                 mResult = result;
631                 notify();
632             }
633         }
634 
635         final DummyJavaScriptInterface obj = new DummyJavaScriptInterface();
636         mOnUiThread.addJavascriptInterface(obj, "dummy");
637         assertFalse(obj.wasProvideResultCalled());
638 
639         startWebServer(false);
640         String url = mWebServer.getAssetUrl(TestHtmlConstants.ADD_JAVA_SCRIPT_INTERFACE_URL);
641         mOnUiThread.loadUrlAndWaitForCompletion(url);
642         assertEquals("Original title", obj.waitForResult());
643 
644         // Verify that only methods annotated with @JavascriptInterface are exposed
645         // on the JavaScript interface object.
646         assertEquals("\"function\"",
647                 mOnUiThread.evaluateJavascriptSync("typeof dummy.provideResult"));
648 
649         assertEquals("\"undefined\"",
650                 mOnUiThread.evaluateJavascriptSync("typeof dummy.wasProvideResultCalled"));
651 
652         assertEquals("\"undefined\"",
653                 mOnUiThread.evaluateJavascriptSync("typeof dummy.getClass"));
654     }
655 
testAddJavascriptInterfaceNullObject()656     public void testAddJavascriptInterfaceNullObject() throws Exception {
657         if (!NullWebViewUtils.isWebViewAvailable()) {
658             return;
659         }
660 
661         mOnUiThread.getSettings().setJavaScriptEnabled(true);
662         String setTitleToPropertyTypeHtml = "<html><head></head>" +
663                 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>";
664 
665         // Test that the property is initially undefined.
666         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
667                 "text/html", null);
668         assertEquals("undefined", mOnUiThread.getTitle());
669 
670         // Test that adding a null object has no effect.
671         mOnUiThread.addJavascriptInterface(null, "injectedObject");
672         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
673                 "text/html", null);
674         assertEquals("undefined", mOnUiThread.getTitle());
675 
676         // Test that adding an object gives an object type.
677         final Object obj = new Object();
678         mOnUiThread.addJavascriptInterface(obj, "injectedObject");
679         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
680                 "text/html", null);
681         assertEquals("object", mOnUiThread.getTitle());
682 
683         // Test that trying to replace with a null object has no effect.
684         mOnUiThread.addJavascriptInterface(null, "injectedObject");
685         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
686                 "text/html", null);
687         assertEquals("object", mOnUiThread.getTitle());
688     }
689 
testRemoveJavascriptInterface()690     public void testRemoveJavascriptInterface() throws Exception {
691         if (!NullWebViewUtils.isWebViewAvailable()) {
692             return;
693         }
694 
695         mOnUiThread.getSettings().setJavaScriptEnabled(true);
696         String setTitleToPropertyTypeHtml = "<html><head></head>" +
697                 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>";
698 
699         // Test that adding an object gives an object type.
700         mOnUiThread.addJavascriptInterface(new Object(), "injectedObject");
701         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
702                 "text/html", null);
703         assertEquals("object", mOnUiThread.getTitle());
704 
705         // Test that reloading the page after removing the object leaves the property undefined.
706         mOnUiThread.removeJavascriptInterface("injectedObject");
707         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
708                 "text/html", null);
709         assertEquals("undefined", mOnUiThread.getTitle());
710     }
711 
testUseRemovedJavascriptInterface()712     public void testUseRemovedJavascriptInterface() throws Throwable {
713         if (!NullWebViewUtils.isWebViewAvailable()) {
714             return;
715         }
716 
717         class RemovedObject {
718             @Override
719             @JavascriptInterface
720             public String toString() {
721                 return "removedObject";
722             }
723 
724             @JavascriptInterface
725             public void remove() throws Throwable {
726                 mOnUiThread.removeJavascriptInterface("removedObject");
727                 System.gc();
728             }
729         }
730         class ResultObject {
731             private String mResult;
732             private boolean mIsResultAvailable;
733 
734             @JavascriptInterface
735             public synchronized void setResult(String result) {
736                 mResult = result;
737                 mIsResultAvailable = true;
738                 notify();
739             }
740             public synchronized String getResult() {
741                 while (!mIsResultAvailable) {
742                     try {
743                         wait();
744                     } catch (InterruptedException e) {
745                     }
746                 }
747                 return mResult;
748             }
749         }
750         final ResultObject resultObject = new ResultObject();
751 
752         // Test that an object is still usable if removed while the page is in use, even if we have
753         // no external references to it.
754         mOnUiThread.getSettings().setJavaScriptEnabled(true);
755         mOnUiThread.addJavascriptInterface(new RemovedObject(), "removedObject");
756         mOnUiThread.addJavascriptInterface(resultObject, "resultObject");
757         mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" +
758                 "<body onload=\"window.removedObject.remove();" +
759                 "resultObject.setResult(removedObject.toString());\"></body></html>",
760                 "text/html", null);
761         assertEquals("removedObject", resultObject.getResult());
762     }
763 
testAddJavascriptInterfaceExceptions()764     public void testAddJavascriptInterfaceExceptions() throws Exception {
765         if (!NullWebViewUtils.isWebViewAvailable()) {
766             return;
767         }
768         WebSettings settings = mOnUiThread.getSettings();
769         settings.setJavaScriptEnabled(true);
770         settings.setJavaScriptCanOpenWindowsAutomatically(true);
771 
772         final AtomicBoolean mJsInterfaceWasCalled = new AtomicBoolean(false) {
773             @JavascriptInterface
774             public synchronized void call() {
775                 set(true);
776                 // The main purpose of this test is to ensure an exception here does not
777                 // crash the implementation.
778                 throw new RuntimeException("Javascript Interface exception");
779             }
780         };
781 
782         mOnUiThread.addJavascriptInterface(mJsInterfaceWasCalled, "dummy");
783 
784         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
785 
786         assertFalse(mJsInterfaceWasCalled.get());
787 
788         assertEquals("\"pass\"", mOnUiThread.evaluateJavascriptSync(
789                 "try {dummy.call(); 'fail'; } catch (exception) { 'pass'; } "));
790         assertTrue(mJsInterfaceWasCalled.get());
791     }
792 
testJavascriptInterfaceCustomPropertiesClearedOnReload()793     public void testJavascriptInterfaceCustomPropertiesClearedOnReload() throws Exception {
794         if (!NullWebViewUtils.isWebViewAvailable()) {
795             return;
796         }
797 
798         mOnUiThread.getSettings().setJavaScriptEnabled(true);
799 
800         class DummyJavaScriptInterface {
801         }
802         final DummyJavaScriptInterface obj = new DummyJavaScriptInterface();
803         mOnUiThread.addJavascriptInterface(obj, "dummy");
804         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
805 
806         assertEquals("42", mOnUiThread.evaluateJavascriptSync("dummy.custom_property = 42"));
807 
808         assertEquals("true", mOnUiThread.evaluateJavascriptSync("'custom_property' in dummy"));
809 
810         mOnUiThread.reloadAndWaitForCompletion();
811 
812         assertEquals("false", mOnUiThread.evaluateJavascriptSync("'custom_property' in dummy"));
813     }
814 
testJavascriptInterfaceForClientPopup()815     public void testJavascriptInterfaceForClientPopup() throws Exception {
816         if (!NullWebViewUtils.isWebViewAvailable()) {
817             return;
818         }
819 
820         mOnUiThread.getSettings().setJavaScriptEnabled(true);
821         mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
822         mOnUiThread.getSettings().setSupportMultipleWindows(true);
823 
824         class DummyJavaScriptInterface {
825             @JavascriptInterface
826             public int test() {
827                 return 42;
828             }
829         }
830         final DummyJavaScriptInterface obj = new DummyJavaScriptInterface();
831 
832         final WebView childWebView = mOnUiThread.createWebView();
833         WebViewOnUiThread childOnUiThread = new WebViewOnUiThread(childWebView);
834         childOnUiThread.getSettings().setJavaScriptEnabled(true);
835         childOnUiThread.addJavascriptInterface(obj, "dummy");
836 
837         final SettableFuture<Void> onCreateWindowFuture = SettableFuture.create();
838         mOnUiThread.setWebChromeClient(new WebViewSyncLoader.WaitForProgressClient(mOnUiThread) {
839             @Override
840             public boolean onCreateWindow(
841                 WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
842                 getActivity().addContentView(childWebView, new ViewGroup.LayoutParams(
843                             ViewGroup.LayoutParams.FILL_PARENT,
844                             ViewGroup.LayoutParams.WRAP_CONTENT));
845                 WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
846                 transport.setWebView(childWebView);
847                 resultMsg.sendToTarget();
848                 onCreateWindowFuture.set(null);
849                 return true;
850             }
851         });
852 
853         startWebServer(false);
854         mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.
855                 getAssetUrl(TestHtmlConstants.POPUP_URL));
856         WebkitUtils.waitForFuture(onCreateWindowFuture);
857 
858         childOnUiThread.loadUrlAndWaitForCompletion("about:blank");
859 
860         assertEquals("true", childOnUiThread.evaluateJavascriptSync("'dummy' in window"));
861 
862         assertEquals("The injected object should be functional", "42",
863                 childOnUiThread.evaluateJavascriptSync("dummy.test()"));
864     }
865 
866     private final class TestPictureListener implements PictureListener {
867         public int callCount;
868 
869         @Override
onNewPicture(WebView view, Picture picture)870         public void onNewPicture(WebView view, Picture picture) {
871             // Need to inform the listener tracking new picture
872             // for the "page loaded" knowledge since it has been replaced.
873             mOnUiThread.onNewPicture();
874             this.callCount += 1;
875         }
876     }
877 
waitForPictureToHaveColor(int color, final TestPictureListener listener)878     private Picture waitForPictureToHaveColor(int color,
879             final TestPictureListener listener) throws Throwable {
880         final int MAX_ON_NEW_PICTURE_ITERATIONS = 5;
881         final AtomicReference<Picture> pictureRef = new AtomicReference<Picture>();
882         for (int i = 0; i < MAX_ON_NEW_PICTURE_ITERATIONS; i++) {
883             final int oldCallCount = listener.callCount;
884             WebkitUtils.onMainThreadSync(() -> {
885                 pictureRef.set(mWebView.capturePicture());
886             });
887             if (isPictureFilledWithColor(pictureRef.get(), color))
888                 break;
889             new PollingCheck(TEST_TIMEOUT) {
890                 @Override
891                 protected boolean check() {
892                     return listener.callCount > oldCallCount;
893                 }
894             }.run();
895         }
896         return pictureRef.get();
897     }
898 
testCapturePicture()899     public void testCapturePicture() throws Exception, Throwable {
900         if (!NullWebViewUtils.isWebViewAvailable()) {
901             return;
902         }
903         final TestPictureListener listener = new TestPictureListener();
904 
905         startWebServer(false);
906         final String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL);
907         mOnUiThread.setPictureListener(listener);
908         // Showing the blank page will fill the picture with the background color.
909         mOnUiThread.loadUrlAndWaitForCompletion(url);
910         // The default background color is white.
911         Picture oldPicture = waitForPictureToHaveColor(Color.WHITE, listener);
912 
913         WebkitUtils.onMainThread(() -> {
914             mWebView.setBackgroundColor(Color.CYAN);
915         });
916         mOnUiThread.reloadAndWaitForCompletion();
917         waitForPictureToHaveColor(Color.CYAN, listener);
918 
919         assertTrue("The content of the previously captured picture should not update automatically",
920                 isPictureFilledWithColor(oldPicture, Color.WHITE));
921     }
922 
testSetPictureListener()923     public void testSetPictureListener() throws Exception, Throwable {
924         if (!NullWebViewUtils.isWebViewAvailable()) {
925             return;
926         }
927         final class MyPictureListener implements PictureListener {
928             public int callCount;
929             public WebView webView;
930             public Picture picture;
931 
932             @Override
933             public void onNewPicture(WebView view, Picture picture) {
934                 // Need to inform the listener tracking new picture
935                 // for the "page loaded" knowledge since it has been replaced.
936                 mOnUiThread.onNewPicture();
937                 this.callCount += 1;
938                 this.webView = view;
939                 this.picture = picture;
940             }
941         }
942 
943         final MyPictureListener listener = new MyPictureListener();
944         startWebServer(false);
945         final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
946         mOnUiThread.setPictureListener(listener);
947         mOnUiThread.loadUrlAndWaitForCompletion(url);
948         new PollingCheck(TEST_TIMEOUT) {
949             @Override
950             protected boolean check() {
951                 return listener.callCount > 0;
952             }
953         }.run();
954         assertEquals(mWebView, listener.webView);
955         assertNull(listener.picture);
956 
957         final int oldCallCount = listener.callCount;
958         final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
959         mOnUiThread.loadUrlAndWaitForCompletion(newUrl);
960         new PollingCheck(TEST_TIMEOUT) {
961             @Override
962             protected boolean check() {
963                 return listener.callCount > oldCallCount;
964             }
965         }.run();
966     }
967 
968     @UiThreadTest
testAccessHttpAuthUsernamePassword()969     public void testAccessHttpAuthUsernamePassword() {
970         if (!NullWebViewUtils.isWebViewAvailable()) {
971             return;
972         }
973         try {
974             WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword();
975 
976             String host = "http://localhost:8080";
977             String realm = "testrealm";
978             String userName = "user";
979             String password = "password";
980 
981             String[] result = mWebView.getHttpAuthUsernamePassword(host, realm);
982             assertNull(result);
983 
984             mWebView.setHttpAuthUsernamePassword(host, realm, userName, password);
985             result = mWebView.getHttpAuthUsernamePassword(host, realm);
986             assertNotNull(result);
987             assertEquals(userName, result[0]);
988             assertEquals(password, result[1]);
989 
990             String newPassword = "newpassword";
991             mWebView.setHttpAuthUsernamePassword(host, realm, userName, newPassword);
992             result = mWebView.getHttpAuthUsernamePassword(host, realm);
993             assertNotNull(result);
994             assertEquals(userName, result[0]);
995             assertEquals(newPassword, result[1]);
996 
997             String newUserName = "newuser";
998             mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword);
999             result = mWebView.getHttpAuthUsernamePassword(host, realm);
1000             assertNotNull(result);
1001             assertEquals(newUserName, result[0]);
1002             assertEquals(newPassword, result[1]);
1003 
1004             // the user is set to null, can not change any thing in the future
1005             mWebView.setHttpAuthUsernamePassword(host, realm, null, password);
1006             result = mWebView.getHttpAuthUsernamePassword(host, realm);
1007             assertNotNull(result);
1008             assertNull(result[0]);
1009             assertEquals(password, result[1]);
1010 
1011             mWebView.setHttpAuthUsernamePassword(host, realm, userName, null);
1012             result = mWebView.getHttpAuthUsernamePassword(host, realm);
1013             assertNotNull(result);
1014             assertEquals(userName, result[0]);
1015             assertNull(result[1]);
1016 
1017             mWebView.setHttpAuthUsernamePassword(host, realm, null, null);
1018             result = mWebView.getHttpAuthUsernamePassword(host, realm);
1019             assertNotNull(result);
1020             assertNull(result[0]);
1021             assertNull(result[1]);
1022 
1023             mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword);
1024             result = mWebView.getHttpAuthUsernamePassword(host, realm);
1025             assertNotNull(result);
1026             assertEquals(newUserName, result[0]);
1027             assertEquals(newPassword, result[1]);
1028         } finally {
1029             WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword();
1030         }
1031     }
1032 
1033     @UiThreadTest
testWebViewDatabaseAccessHttpAuthUsernamePassword()1034     public void testWebViewDatabaseAccessHttpAuthUsernamePassword() {
1035         if (!NullWebViewUtils.isWebViewAvailable()) {
1036             return;
1037         }
1038         WebViewDatabase webViewDb = WebViewDatabase.getInstance(getActivity());
1039         try {
1040             webViewDb.clearHttpAuthUsernamePassword();
1041 
1042             String host = "http://localhost:8080";
1043             String realm = "testrealm";
1044             String userName = "user";
1045             String password = "password";
1046 
1047             String[] result =
1048                     mWebView.getHttpAuthUsernamePassword(host,
1049                             realm);
1050             assertNull(result);
1051 
1052             webViewDb.setHttpAuthUsernamePassword(host, realm, userName, password);
1053             result = webViewDb.getHttpAuthUsernamePassword(host, realm);
1054             assertNotNull(result);
1055             assertEquals(userName, result[0]);
1056             assertEquals(password, result[1]);
1057 
1058             String newPassword = "newpassword";
1059             webViewDb.setHttpAuthUsernamePassword(host, realm, userName, newPassword);
1060             result = webViewDb.getHttpAuthUsernamePassword(host, realm);
1061             assertNotNull(result);
1062             assertEquals(userName, result[0]);
1063             assertEquals(newPassword, result[1]);
1064 
1065             String newUserName = "newuser";
1066             webViewDb.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword);
1067             result = webViewDb.getHttpAuthUsernamePassword(host, realm);
1068             assertNotNull(result);
1069             assertEquals(newUserName, result[0]);
1070             assertEquals(newPassword, result[1]);
1071 
1072             // the user is set to null, can not change any thing in the future
1073             webViewDb.setHttpAuthUsernamePassword(host, realm, null, password);
1074             result = webViewDb.getHttpAuthUsernamePassword(host, realm);
1075             assertNotNull(result);
1076             assertNull(result[0]);
1077             assertEquals(password, result[1]);
1078 
1079             webViewDb.setHttpAuthUsernamePassword(host, realm, userName, null);
1080             result = webViewDb.getHttpAuthUsernamePassword(host, realm);
1081             assertNotNull(result);
1082             assertEquals(userName, result[0]);
1083             assertNull(result[1]);
1084 
1085             webViewDb.setHttpAuthUsernamePassword(host, realm, null, null);
1086             result = webViewDb.getHttpAuthUsernamePassword(host, realm);
1087             assertNotNull(result);
1088             assertNull(result[0]);
1089             assertNull(result[1]);
1090 
1091             webViewDb.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword);
1092             result = webViewDb.getHttpAuthUsernamePassword(host, realm);
1093             assertNotNull(result);
1094             assertEquals(newUserName, result[0]);
1095             assertEquals(newPassword, result[1]);
1096         } finally {
1097             webViewDb.clearHttpAuthUsernamePassword();
1098         }
1099     }
1100 
testLoadData()1101     public void testLoadData() throws Throwable {
1102         if (!NullWebViewUtils.isWebViewAvailable()) {
1103             return;
1104         }
1105         final String firstTitle = "Hello, World!";
1106         final String HTML_CONTENT =
1107                 "<html><head><title>" + firstTitle + "</title></head><body></body>" +
1108                 "</html>";
1109         mOnUiThread.loadDataAndWaitForCompletion(HTML_CONTENT,
1110                 "text/html", null);
1111         assertEquals(firstTitle, mOnUiThread.getTitle());
1112 
1113         startWebServer(false);
1114         final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
1115         mOnUiThread.getSettings().setJavaScriptEnabled(true);
1116         final String secondTitle = "Foo bar";
1117         mOnUiThread.loadDataAndWaitForCompletion(
1118                 "<html><head><title>" + secondTitle + "</title></head><body onload=\"" +
1119                 "document.title = " +
1120                 "document.getElementById('frame').contentWindow.location.href;" +
1121                 "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>",
1122                 "text/html", null);
1123         assertEquals("Page title should not change, because it should be an error to access a "
1124                 + "cross-site frame's href.",
1125                 secondTitle, mOnUiThread.getTitle());
1126     }
1127 
testLoadDataWithBaseUrl_resolvesRelativeToBaseUrl()1128     public void testLoadDataWithBaseUrl_resolvesRelativeToBaseUrl() throws Throwable {
1129         if (!NullWebViewUtils.isWebViewAvailable()) {
1130             return;
1131         }
1132         assertNull(mOnUiThread.getUrl());
1133         String imgUrl = TestHtmlConstants.SMALL_IMG_URL; // relative
1134 
1135         // Trying to resolve a relative URL against a data URL without a base URL
1136         // will fail and we won't make a request to the test web server.
1137         // By using the test web server as the base URL we expect to see a request
1138         // for the relative URL in the test server.
1139         startWebServer(false);
1140         final String baseUrl = mWebServer.getAssetUrl("foo.html");
1141         mWebServer.resetRequestState();
1142         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
1143                 HTML_HEADER + "<body><img src=\"" + imgUrl + "\"/></body></html>",
1144                 "text/html", "UTF-8", null);
1145         assertTrue("The resource request should make it to the server",
1146                 mWebServer.wasResourceRequested(imgUrl));
1147     }
1148 
testLoadDataWithBaseUrl_historyUrl()1149     public void testLoadDataWithBaseUrl_historyUrl() throws Throwable {
1150         if (!NullWebViewUtils.isWebViewAvailable()) {
1151             return;
1152         }
1153         final String baseUrl = "http://www.baseurl.com/";
1154         final String historyUrl = "http://www.example.com/";
1155         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
1156                 SIMPLE_HTML,
1157                 "text/html", "UTF-8", historyUrl);
1158         assertEquals(historyUrl, mOnUiThread.getUrl());
1159     }
1160 
testLoadDataWithBaseUrl_nullHistoryUrlShowsAsAboutBlank()1161     public void testLoadDataWithBaseUrl_nullHistoryUrlShowsAsAboutBlank() throws Throwable {
1162         if (!NullWebViewUtils.isWebViewAvailable()) {
1163             return;
1164         }
1165         // Check that reported URL is "about:blank" when supplied history URL
1166         // is null.
1167         final String baseUrl = "http://www.baseurl.com/";
1168         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
1169                 SIMPLE_HTML,
1170                 "text/html", "UTF-8", null);
1171         assertEquals("about:blank", mOnUiThread.getUrl());
1172     }
1173 
testLoadDataWithBaseUrl_javascriptCanAccessOrigin()1174     public void testLoadDataWithBaseUrl_javascriptCanAccessOrigin() throws Throwable {
1175         if (!NullWebViewUtils.isWebViewAvailable()) {
1176             return;
1177         }
1178         // Test that JavaScript can access content from the same origin as the base URL.
1179         mOnUiThread.getSettings().setJavaScriptEnabled(true);
1180         startWebServer(false);
1181         final String baseUrl = mWebServer.getAssetUrl("foo.html");
1182         final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
1183         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
1184                 HTML_HEADER + "<body onload=\"" +
1185                 "document.title = document.getElementById('frame').contentWindow.location.href;" +
1186                 "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>",
1187                 "text/html", "UTF-8", null);
1188         assertEquals(crossOriginUrl, mOnUiThread.getTitle());
1189     }
1190 
testLoadDataWithBaseUrl_dataBaseUrlIgnoresHistoryUrl()1191     public void testLoadDataWithBaseUrl_dataBaseUrlIgnoresHistoryUrl() throws Throwable {
1192         if (!NullWebViewUtils.isWebViewAvailable()) {
1193             return;
1194         }
1195         // Check that when the base URL uses the 'data' scheme, a 'data' scheme URL is used and the
1196         // history URL is ignored.
1197         final String baseUrl = "data:foo";
1198         final String historyUrl = "http://www.example.com/";
1199         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
1200                 SIMPLE_HTML,
1201                 "text/html", "UTF-8", historyUrl);
1202 
1203         final String currentUrl = mOnUiThread.getUrl();
1204         assertEquals("Current URL (" + currentUrl + ") should be a data URI", 0,
1205                 mOnUiThread.getUrl().indexOf("data:text/html"));
1206         assertThat("Current URL (" + currentUrl + ") should contain the simple HTML we loaded",
1207                 mOnUiThread.getUrl().indexOf("simple html"), greaterThan(0));
1208     }
1209 
testLoadDataWithBaseUrl_unencodedContentHttpBaseUrl()1210     public void testLoadDataWithBaseUrl_unencodedContentHttpBaseUrl() throws Throwable {
1211         if (!NullWebViewUtils.isWebViewAvailable()) {
1212             return;
1213         }
1214         // Check that when a non-data: base URL is used, we treat the String to load as
1215         // a raw string and just dump it into the WebView, i.e. not decoding any URL entities.
1216         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("http://www.foo.com",
1217                 HTML_HEADER + "<title>Hello World%21</title><body>bar</body></html>",
1218                 "text/html", "UTF-8", null);
1219         assertEquals("Hello World%21", mOnUiThread.getTitle());
1220     }
1221 
testLoadDataWithBaseUrl_urlEncodedContentDataBaseUrl()1222     public void testLoadDataWithBaseUrl_urlEncodedContentDataBaseUrl() throws Throwable {
1223         if (!NullWebViewUtils.isWebViewAvailable()) {
1224             return;
1225         }
1226         // Check that when a data: base URL is used, we treat the String to load as a data: URL
1227         // and run load steps such as decoding URL entities (i.e., contrary to the test case
1228         // above.)
1229         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo",
1230                 HTML_HEADER + "<title>Hello World%21</title></html>", "text/html", "UTF-8", null);
1231         assertEquals("Hello World!", mOnUiThread.getTitle());
1232     }
1233 
testLoadDataWithBaseUrl_nullSafe()1234     public void testLoadDataWithBaseUrl_nullSafe() throws Throwable {
1235         if (!NullWebViewUtils.isWebViewAvailable()) {
1236             return;
1237         }
1238 
1239         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(null, null, null, null, null);
1240         assertEquals("about:blank", mOnUiThread.getUrl());
1241     }
1242 
deleteIfExists(File file)1243     private void deleteIfExists(File file) throws IOException {
1244         if (file.exists()) {
1245             file.delete();
1246         }
1247     }
1248 
readTextFile(File file, Charset encoding)1249     private String readTextFile(File file, Charset encoding)
1250             throws FileNotFoundException, IOException {
1251         FileInputStream stream = new FileInputStream(file);
1252         byte[] bytes = new byte[(int)file.length()];
1253         stream.read(bytes);
1254         stream.close();
1255         return new String(bytes, encoding);
1256     }
1257 
doSaveWebArchive(String baseName, boolean autoName, final String expectName)1258     private void doSaveWebArchive(String baseName, boolean autoName, final String expectName)
1259             throws Throwable {
1260         final Semaphore saving = new Semaphore(0);
1261         ValueCallback<String> callback = new ValueCallback<String>() {
1262             @Override
1263             public void onReceiveValue(String savedName) {
1264                 assertEquals(expectName, savedName);
1265                 saving.release();
1266             }
1267         };
1268 
1269         mOnUiThread.saveWebArchive(baseName, autoName, callback);
1270         assertTrue(saving.tryAcquire(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
1271     }
1272 
testSaveWebArchive()1273     public void testSaveWebArchive() throws Throwable {
1274         if (!NullWebViewUtils.isWebViewAvailable()) {
1275             return;
1276         }
1277 
1278         final String testPage = "testSaveWebArchive test page";
1279 
1280         File dir = getActivity().getFilesDir();
1281         String dirStr = dir.toString();
1282 
1283         File test = new File(dir, "test.mht");
1284         deleteIfExists(test);
1285         String testStr = test.getAbsolutePath();
1286 
1287         File index = new File(dir, "index.mht");
1288         deleteIfExists(index);
1289         String indexStr = index.getAbsolutePath();
1290 
1291         File index1 = new File(dir, "index-1.mht");
1292         deleteIfExists(index1);
1293         String index1Str = index1.getAbsolutePath();
1294 
1295         mOnUiThread.loadDataAndWaitForCompletion(testPage, "text/html", "UTF-8");
1296 
1297         try {
1298             // Save test.mht
1299             doSaveWebArchive(testStr, false, testStr);
1300 
1301             // Check the contents of test.mht
1302             String testMhtml = readTextFile(test, StandardCharsets.UTF_8);
1303             assertTrue(testMhtml.contains(testPage));
1304 
1305             // Save index.mht
1306             doSaveWebArchive(dirStr + "/", true, indexStr);
1307 
1308             // Check the contents of index.mht
1309             String indexMhtml = readTextFile(index, StandardCharsets.UTF_8);
1310             assertTrue(indexMhtml.contains(testPage));
1311 
1312             // Save index-1.mht since index.mht already exists
1313             doSaveWebArchive(dirStr + "/", true, index1Str);
1314 
1315             // Check the contents of index-1.mht
1316             String index1Mhtml = readTextFile(index1, StandardCharsets.UTF_8);
1317             assertTrue(index1Mhtml.contains(testPage));
1318 
1319             // Try a file in a bogus directory
1320             doSaveWebArchive("/bogus/path/test.mht", false, null);
1321 
1322             // Try a bogus directory
1323             doSaveWebArchive("/bogus/path/", true, null);
1324         } finally {
1325             deleteIfExists(test);
1326             deleteIfExists(index);
1327             deleteIfExists(index1);
1328         }
1329     }
1330 
1331     private static class WaitForFindResultsListener
1332             implements WebView.FindListener {
1333         private final SettableFuture<Integer> mFuture;
1334         private final WebView mWebView;
1335         private final int mMatchesWanted;
1336         private final String mStringWanted;
1337         private final boolean mRetry;
1338 
WaitForFindResultsListener( WebView wv, String wanted, int matches, boolean retry)1339         public WaitForFindResultsListener(
1340                 WebView wv, String wanted, int matches, boolean retry) {
1341             mFuture = SettableFuture.create();
1342             mWebView = wv;
1343             mMatchesWanted = matches;
1344             mStringWanted = wanted;
1345             mRetry = retry;
1346         }
1347 
future()1348         public Future<Integer> future() {
1349             return mFuture;
1350         }
1351 
1352         @Override
onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting)1353         public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
1354                 boolean isDoneCounting) {
1355             try {
1356                 assertEquals("WebView.FindListener callbacks should occur on the UI thread",
1357                         Looper.myLooper(), Looper.getMainLooper());
1358             } catch (Throwable t) {
1359                 mFuture.setException(t);
1360             }
1361             if (isDoneCounting) {
1362                 //If mRetry set to true and matches aren't equal, call findAll again
1363                 if (mRetry && numberOfMatches != mMatchesWanted) {
1364                     mWebView.findAll(mStringWanted);
1365                 }
1366                 else {
1367                     mFuture.set(numberOfMatches);
1368                 }
1369             }
1370         }
1371     }
1372 
testFindAll()1373     public void testFindAll()  throws Throwable {
1374         if (!NullWebViewUtils.isWebViewAvailable()) {
1375             return;
1376         }
1377         // Make the page scrollable, so we can detect the scrolling to make sure the
1378         // content fully loaded.
1379         mOnUiThread.setInitialScale(100);
1380         DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
1381         int dimension = Math.max(metrics.widthPixels, metrics.heightPixels);
1382         // create a paragraph high enough to take up the entire screen
1383         String p = "<p style=\"height:" + dimension + "px;\">" +
1384                 "Find all instances of find on the page and highlight them.</p>";
1385 
1386         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1387                 + "</body></html>", "text/html", null);
1388 
1389         WaitForFindResultsListener l = new WaitForFindResultsListener(mWebView, "find", 2, true);
1390         mOnUiThread.setFindListener(l);
1391         mOnUiThread.findAll("find");
1392         assertEquals(2, (int)WebkitUtils.waitForFuture(l.future()));
1393     }
1394 
testFindNext()1395     public void testFindNext() throws Throwable {
1396         if (!NullWebViewUtils.isWebViewAvailable()) {
1397             return;
1398         }
1399         // Reset the scaling so that finding the next "all" text will require scrolling.
1400         mOnUiThread.setInitialScale(100);
1401 
1402         DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
1403         int dimension = Math.max(metrics.widthPixels, metrics.heightPixels);
1404         // create a paragraph high enough to take up the entire screen
1405         String p = "<p style=\"height:" + dimension + "px;\">" +
1406                 "Find all instances of a word on the page and highlight them.</p>";
1407 
1408         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + p + "</body></html>", "text/html", null);
1409         WaitForFindResultsListener l = new WaitForFindResultsListener(mWebView, "all", 2, true);
1410         mOnUiThread.setFindListener(l);
1411 
1412         // highlight all the strings found and wait for all the matches to be found
1413         mOnUiThread.findAll("all");
1414         WebkitUtils.waitForFuture(l.future());
1415         mOnUiThread.setFindListener(null);
1416 
1417         int previousScrollY = mOnUiThread.getScrollY();
1418 
1419         // Focus "all" in the second page and assert that the view scrolls.
1420         mOnUiThread.findNext(true);
1421         waitForScrollingComplete(previousScrollY);
1422         assertThat(mOnUiThread.getScrollY(), greaterThan(previousScrollY));
1423         previousScrollY = mOnUiThread.getScrollY();
1424 
1425         // Focus "all" in the first page and assert that the view scrolls.
1426         mOnUiThread.findNext(true);
1427         waitForScrollingComplete(previousScrollY);
1428         assertThat(mOnUiThread.getScrollY(), lessThan(previousScrollY));
1429         previousScrollY = mOnUiThread.getScrollY();
1430 
1431         // Focus "all" in the second page and assert that the view scrolls.
1432         mOnUiThread.findNext(false);
1433         waitForScrollingComplete(previousScrollY);
1434         assertThat(mOnUiThread.getScrollY(), greaterThan(previousScrollY));
1435         previousScrollY = mOnUiThread.getScrollY();
1436 
1437         // Focus "all" in the first page and assert that the view scrolls.
1438         mOnUiThread.findNext(false);
1439         waitForScrollingComplete(previousScrollY);
1440         assertThat(mOnUiThread.getScrollY(), lessThan(previousScrollY));
1441         previousScrollY = mOnUiThread.getScrollY();
1442 
1443         // clear the result
1444         mOnUiThread.clearMatches();
1445         getInstrumentation().waitForIdleSync();
1446 
1447         // can not scroll any more
1448         mOnUiThread.findNext(false);
1449         waitForScrollingComplete(previousScrollY);
1450         assertTrue(mOnUiThread.getScrollY() == previousScrollY);
1451 
1452         mOnUiThread.findNext(true);
1453         waitForScrollingComplete(previousScrollY);
1454         assertTrue(mOnUiThread.getScrollY() == previousScrollY);
1455     }
1456 
testDocumentHasImages()1457     public void testDocumentHasImages() throws Exception, Throwable {
1458         if (!NullWebViewUtils.isWebViewAvailable()) {
1459             return;
1460         }
1461         final class DocumentHasImageCheckHandler extends Handler {
1462             private SettableFuture<Integer> mFuture;
1463             public DocumentHasImageCheckHandler(Looper looper) {
1464                 super(looper);
1465                 mFuture = SettableFuture.create();
1466             }
1467             @Override
1468             public void handleMessage(Message msg) {
1469                 mFuture.set(msg.arg1);
1470             }
1471             public Future<Integer> future() {
1472                 return mFuture;
1473             }
1474         }
1475 
1476         startWebServer(false);
1477         final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
1478 
1479         // Create a handler on the UI thread.
1480         final DocumentHasImageCheckHandler handler =
1481             new DocumentHasImageCheckHandler(mWebView.getHandler().getLooper());
1482 
1483         WebkitUtils.onMainThreadSync(() -> {
1484             mOnUiThread.loadDataAndWaitForCompletion("<html><body><img src=\""
1485                     + imgUrl + "\"/></body></html>", "text/html", null);
1486             Message response = new Message();
1487             response.setTarget(handler);
1488             assertFalse(handler.future().isDone());
1489             mWebView.documentHasImages(response);
1490         });
1491         assertEquals(1, (int)WebkitUtils.waitForFuture(handler.future()));
1492     }
1493 
waitForFlingDone(WebViewOnUiThread webview)1494     private static void waitForFlingDone(WebViewOnUiThread webview) {
1495         class ScrollDiffPollingCheck extends PollingCheck {
1496             private static final long TIME_SLICE = 50;
1497             WebViewOnUiThread mWebView;
1498             private int mScrollX;
1499             private int mScrollY;
1500 
1501             ScrollDiffPollingCheck(WebViewOnUiThread webview) {
1502                 mWebView = webview;
1503                 mScrollX = mWebView.getScrollX();
1504                 mScrollY = mWebView.getScrollY();
1505             }
1506 
1507             @Override
1508             protected boolean check() {
1509                 try {
1510                     Thread.sleep(TIME_SLICE);
1511                 } catch (InterruptedException e) {
1512                     // Intentionally ignored.
1513                 }
1514                 int newScrollX = mWebView.getScrollX();
1515                 int newScrollY = mWebView.getScrollY();
1516                 boolean flingDone = newScrollX == mScrollX && newScrollY == mScrollY;
1517                 mScrollX = newScrollX;
1518                 mScrollY = newScrollY;
1519                 return flingDone;
1520             }
1521         }
1522         new ScrollDiffPollingCheck(webview).run();
1523     }
1524 
testPageScroll()1525     public void testPageScroll() throws Throwable {
1526         if (!NullWebViewUtils.isWebViewAvailable()) {
1527             return;
1528         }
1529         DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
1530         int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);
1531         String p = "<p style=\"height:" + dimension + "px;\">" +
1532                 "Scroll by half the size of the page.</p>";
1533         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1534                 + p + "</body></html>", "text/html", null);
1535 
1536         // Wait for UI thread to settle and receive page dimentions from renderer
1537         // such that we can invoke page down.
1538         new PollingCheck() {
1539             @Override
1540             protected boolean check() {
1541                  return mOnUiThread.pageDown(false);
1542             }
1543         }.run();
1544 
1545         do {
1546             waitForFlingDone(mOnUiThread);
1547         } while (mOnUiThread.pageDown(false));
1548 
1549         waitForFlingDone(mOnUiThread);
1550         final int bottomScrollY = mOnUiThread.getScrollY();
1551 
1552         assertTrue(mOnUiThread.pageUp(false));
1553 
1554         do {
1555             waitForFlingDone(mOnUiThread);
1556         } while (mOnUiThread.pageUp(false));
1557 
1558         waitForFlingDone(mOnUiThread);
1559         final int topScrollY = mOnUiThread.getScrollY();
1560 
1561         // jump to the bottom
1562         assertTrue(mOnUiThread.pageDown(true));
1563         new PollingCheck() {
1564             @Override
1565             protected boolean check() {
1566                 return bottomScrollY == mOnUiThread.getScrollY();
1567             }
1568         }.run();
1569 
1570         // jump to the top
1571         assertTrue(mOnUiThread.pageUp(true));
1572          new PollingCheck() {
1573             @Override
1574             protected boolean check() {
1575                 return topScrollY == mOnUiThread.getScrollY();
1576             }
1577         }.run();
1578     }
1579 
testGetContentHeight()1580     public void testGetContentHeight() throws Throwable {
1581         if (!NullWebViewUtils.isWebViewAvailable()) {
1582             return;
1583         }
1584         mOnUiThread.loadDataAndWaitForCompletion(
1585                 "<html><body></body></html>", "text/html", null);
1586         new PollingCheck() {
1587             @Override
1588             protected boolean check() {
1589                 return mOnUiThread.getScale() != 0 && mOnUiThread.getContentHeight() != 0
1590                     && mOnUiThread.getHeight() != 0;
1591             }
1592         }.run();
1593 
1594         final int tolerance = 2;
1595         // getHeight() returns physical pixels and it is from web contents' size, getContentHeight()
1596         // returns CSS pixels and it is from compositor. In order to compare these two values, we
1597         // need to scale getContentHeight() by the device scale factor. This also amplifies any
1598         // rounding errors. Internally, getHeight() could also have rounding error first and then
1599         // times device scale factor, so we are comparing two rounded numbers below.
1600         // We allow 2 * getScale() as the delta, because getHeight() and getContentHeight() may
1601         // use different rounding algorithms and the results are from different computation
1602         // sequences. The extreme case is that in CSS pixel we have 2 as differences (0.9999 rounded
1603         // down and 1.0001 rounded up), therefore we ended with 2 * getScale().
1604         assertEquals(
1605                 mOnUiThread.getHeight(),
1606                 mOnUiThread.getContentHeight() * mOnUiThread.getScale(),
1607                 tolerance * Math.max(mOnUiThread.getScale(), 1.0f));
1608 
1609         // Make pageHeight bigger than the larger dimension of the device, so the page is taller
1610         // than viewport. Because when layout_height set to match_parent, getContentHeight() will
1611         // give maximum value between the actual web content height and the viewport height. When
1612         // viewport height is bigger, |extraSpace| below is not the extra space on the web page.
1613         // Note that we are passing physical pixels rather than CSS pixels here, when screen density
1614         // scale is lower than 1.0f, we need to scale it up.
1615         DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
1616         final float scaleFactor = Math.max(1.0f, 1.0f / mOnUiThread.getScale());
1617         final int pageHeight =
1618                 (int)(Math.ceil(Math.max(metrics.widthPixels, metrics.heightPixels)
1619                 * scaleFactor));
1620 
1621         // set the margin to 0
1622         final String p = "<p style=\"height:" + pageHeight
1623                 + "px;margin:0px auto;\">Get the height of HTML content.</p>";
1624         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1625                 + "</body></html>", "text/html", null);
1626         new PollingCheck() {
1627             @Override
1628             protected boolean check() {
1629                 return mOnUiThread.getContentHeight() > pageHeight;
1630             }
1631         }.run();
1632 
1633         final int extraSpace = mOnUiThread.getContentHeight() - pageHeight;
1634         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1635                 + p + "</body></html>", "text/html", null);
1636         new PollingCheck() {
1637             @Override
1638             protected boolean check() {
1639                 // |pageHeight| is accurate, |extraSpace| = getContentheight() - |pageHeight|, so it
1640                 // might have rounding error +-1, also getContentHeight() might have rounding error
1641                 // +-1, so we allow error 2. Note that |pageHeight|, |extraSpace| and
1642                 // getContentHeight() are all CSS pixels.
1643                 final int expectedContentHeight = pageHeight + pageHeight + extraSpace;
1644                 return Math.abs(expectedContentHeight - mOnUiThread.getContentHeight())
1645                         <= tolerance;
1646             }
1647         }.run();
1648     }
1649 
1650     @UiThreadTest
testPlatformNotifications()1651     public void testPlatformNotifications() {
1652         if (!NullWebViewUtils.isWebViewAvailable()) {
1653             return;
1654         }
1655         WebView.enablePlatformNotifications();
1656         WebView.disablePlatformNotifications();
1657     }
1658 
1659     @UiThreadTest
testAccessPluginList()1660     public void testAccessPluginList() {
1661         if (!NullWebViewUtils.isWebViewAvailable()) {
1662             return;
1663         }
1664         assertNotNull(WebView.getPluginList());
1665 
1666         // can not find a way to install plugins
1667         mWebView.refreshPlugins(false);
1668     }
1669 
1670     @UiThreadTest
testDestroy()1671     public void testDestroy() {
1672         if (!NullWebViewUtils.isWebViewAvailable()) {
1673             return;
1674         }
1675         // Create a new WebView, since we cannot call destroy() on a view in the hierarchy
1676         WebView localWebView = new WebView(getActivity());
1677         localWebView.destroy();
1678     }
1679 
testFlingScroll()1680     public void testFlingScroll() throws Throwable {
1681         if (!NullWebViewUtils.isWebViewAvailable()) {
1682             return;
1683         }
1684         DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
1685         final int dimension = 10 * Math.max(metrics.widthPixels, metrics.heightPixels);
1686         String p = "<p style=\"height:" + dimension + "px;" +
1687                 "width:" + dimension + "px\">Test fling scroll.</p>";
1688         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1689                 + "</body></html>", "text/html", null);
1690         new PollingCheck() {
1691             @Override
1692             protected boolean check() {
1693                 return mOnUiThread.getContentHeight() >= dimension;
1694             }
1695         }.run();
1696         getInstrumentation().waitForIdleSync();
1697 
1698         final int previousScrollX = mOnUiThread.getScrollX();
1699         final int previousScrollY = mOnUiThread.getScrollY();
1700 
1701         mOnUiThread.flingScroll(10000, 10000);
1702 
1703         new PollingCheck() {
1704             @Override
1705             protected boolean check() {
1706                 return mOnUiThread.getScrollX() > previousScrollX &&
1707                         mOnUiThread.getScrollY() > previousScrollY;
1708             }
1709         }.run();
1710     }
1711 
testRequestFocusNodeHref()1712     public void testRequestFocusNodeHref() throws Throwable {
1713         if (!NullWebViewUtils.isWebViewAvailable()) {
1714             return;
1715         }
1716         startWebServer(false);
1717         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
1718         String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
1719         final String links = "<DL><p><DT><A HREF=\"" + url1
1720                 + "\">HTML_URL1</A><DT><A HREF=\"" + url2
1721                 + "\">HTML_URL2</A></DL><p>";
1722         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + links + "</body></html>", "text/html", null);
1723         getInstrumentation().waitForIdleSync();
1724 
1725         final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper());
1726         final Message hrefMsg = new Message();
1727         hrefMsg.setTarget(handler);
1728 
1729         // focus on first link
1730         handler.reset();
1731         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
1732         mOnUiThread.requestFocusNodeHref(hrefMsg);
1733         new PollingCheck() {
1734             @Override
1735             protected boolean check() {
1736                 boolean done = false;
1737                 if (handler.hasCalledHandleMessage()) {
1738                     if (handler.mResultUrl != null) {
1739                         done = true;
1740                     } else {
1741                         handler.reset();
1742                         Message newMsg = new Message();
1743                         newMsg.setTarget(handler);
1744                         mOnUiThread.requestFocusNodeHref(newMsg);
1745                     }
1746                 }
1747                 return done;
1748             }
1749         }.run();
1750         assertEquals(url1, handler.getResultUrl());
1751 
1752         // focus on second link
1753         handler.reset();
1754         final Message hrefMsg2 = new Message();
1755         hrefMsg2.setTarget(handler);
1756         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
1757         mOnUiThread.requestFocusNodeHref(hrefMsg2);
1758         new PollingCheck() {
1759             @Override
1760             protected boolean check() {
1761                 boolean done = false;
1762                 final String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
1763                 if (handler.hasCalledHandleMessage()) {
1764                     if (handler.mResultUrl != null &&
1765                             handler.mResultUrl.equals(url2)) {
1766                         done = true;
1767                     } else {
1768                         handler.reset();
1769                         Message newMsg = new Message();
1770                         newMsg.setTarget(handler);
1771                         mOnUiThread.requestFocusNodeHref(newMsg);
1772                     }
1773                 }
1774                 return done;
1775             }
1776         }.run();
1777         assertEquals(url2, handler.getResultUrl());
1778 
1779         mOnUiThread.requestFocusNodeHref(null);
1780     }
1781 
testRequestImageRef()1782     public void testRequestImageRef() throws Exception, Throwable {
1783         if (!NullWebViewUtils.isWebViewAvailable()) {
1784             return;
1785         }
1786         final class ImageLoaded {
1787             public SettableFuture<Void> mImageLoaded = SettableFuture.create();
1788 
1789             @JavascriptInterface
1790             public void loaded() {
1791                 mImageLoaded.set(null);
1792             }
1793 
1794             public Future<Void> future() {
1795                 return mImageLoaded;
1796             }
1797         }
1798         final ImageLoaded imageLoaded = new ImageLoaded();
1799         mOnUiThread.getSettings().setJavaScriptEnabled(true);
1800         mOnUiThread.addJavascriptInterface(imageLoaded, "imageLoaded");
1801         AssetManager assets = getActivity().getAssets();
1802         Bitmap bitmap = BitmapFactory.decodeStream(assets.open(TestHtmlConstants.LARGE_IMG_URL));
1803         int imgWidth = bitmap.getWidth();
1804         int imgHeight = bitmap.getHeight();
1805 
1806         startWebServer(false);
1807         final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.LARGE_IMG_URL);
1808         mOnUiThread.loadDataAndWaitForCompletion(
1809                 "<html><head><title>Title</title><style type=\"text/css\">"
1810                 + "%23imgElement { -webkit-transform: translate3d(0,0,1); }"
1811                 + "%23imgElement.finish { -webkit-transform: translate3d(0,0,0);"
1812                 + " -webkit-transition-duration: 1ms; }</style>"
1813                 + "<script type=\"text/javascript\">function imgLoad() {"
1814                 + "imgElement = document.getElementById('imgElement');"
1815                 + "imgElement.addEventListener('webkitTransitionEnd',"
1816                 + "function(e) { imageLoaded.loaded(); });"
1817                 + "imgElement.className = 'finish';}</script>"
1818                 + "</head><body><img id=\"imgElement\" src=\"" + imgUrl
1819                 + "\" width=\"" + imgWidth + "\" height=\"" + imgHeight
1820                 + "\" onLoad=\"imgLoad()\"/></body></html>", "text/html", null);
1821         WebkitUtils.waitForFuture(imageLoaded.future());
1822         getInstrumentation().waitForIdleSync();
1823 
1824         final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper());
1825         final Message msg = new Message();
1826         msg.setTarget(handler);
1827 
1828         // touch the image
1829         handler.reset();
1830         int[] location = mOnUiThread.getLocationOnScreen();
1831 
1832         long time = SystemClock.uptimeMillis();
1833         getInstrumentation().sendPointerSync(
1834                 MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN,
1835                         location[0] + imgWidth / 2,
1836                         location[1] + imgHeight / 2, 0));
1837         getInstrumentation().waitForIdleSync();
1838         mOnUiThread.requestImageRef(msg);
1839         new PollingCheck() {
1840             @Override
1841             protected boolean check() {
1842                 boolean done = false;
1843                 if (handler.hasCalledHandleMessage()) {
1844                     if (handler.mResultUrl != null) {
1845                         done = true;
1846                     } else {
1847                         handler.reset();
1848                         Message newMsg = new Message();
1849                         newMsg.setTarget(handler);
1850                         mOnUiThread.requestImageRef(newMsg);
1851                     }
1852                 }
1853                 return done;
1854             }
1855         }.run();
1856         assertEquals(imgUrl, handler.mResultUrl);
1857     }
1858 
1859     @UiThreadTest
testDebugDump()1860     public void testDebugDump() {
1861         if (!NullWebViewUtils.isWebViewAvailable()) {
1862             return;
1863         }
1864         mWebView.debugDump();
1865     }
1866 
testGetHitTestResult()1867     public void testGetHitTestResult() throws Throwable {
1868         if (!NullWebViewUtils.isWebViewAvailable()) {
1869             return;
1870         }
1871         final String anchor = "<p><a href=\"" + TestHtmlConstants.EXT_WEB_URL1
1872                 + "\">normal anchor</a></p>";
1873         final String blankAnchor = "<p><a href=\"\">blank anchor</a></p>";
1874         final String form = "<p><form><input type=\"text\" name=\"Test\"><br>"
1875                 + "<input type=\"submit\" value=\"Submit\"></form></p>";
1876         String phoneNo = "3106984000";
1877         final String tel = "<p><a href=\"tel:" + phoneNo + "\">Phone</a></p>";
1878         String email = "test@gmail.com";
1879         final String mailto = "<p><a href=\"mailto:" + email + "\">Email</a></p>";
1880         String location = "shanghai";
1881         final String geo = "<p><a href=\"geo:0,0?q=" + location + "\">Location</a></p>";
1882 
1883         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("fake://home",
1884                 "<html><body>" + anchor + blankAnchor + form + tel + mailto +
1885                 geo + "</body></html>", "text/html", "UTF-8", null);
1886         getInstrumentation().waitForIdleSync();
1887 
1888         // anchor
1889         moveFocusDown();
1890         HitTestResult hitTestResult = mOnUiThread.getHitTestResult();
1891         assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType());
1892         assertEquals(TestHtmlConstants.EXT_WEB_URL1, hitTestResult.getExtra());
1893 
1894         // blank anchor
1895         moveFocusDown();
1896         hitTestResult = mOnUiThread.getHitTestResult();
1897         assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType());
1898         assertEquals("fake://home", hitTestResult.getExtra());
1899 
1900         // text field
1901         moveFocusDown();
1902         hitTestResult = mOnUiThread.getHitTestResult();
1903         assertEquals(HitTestResult.EDIT_TEXT_TYPE, hitTestResult.getType());
1904         assertNull(hitTestResult.getExtra());
1905 
1906         // submit button
1907         moveFocusDown();
1908         hitTestResult = mOnUiThread.getHitTestResult();
1909         assertEquals(HitTestResult.UNKNOWN_TYPE, hitTestResult.getType());
1910         assertNull(hitTestResult.getExtra());
1911 
1912         // phone number
1913         moveFocusDown();
1914         hitTestResult = mOnUiThread.getHitTestResult();
1915         assertEquals(HitTestResult.PHONE_TYPE, hitTestResult.getType());
1916         assertEquals(phoneNo, hitTestResult.getExtra());
1917 
1918         // email
1919         moveFocusDown();
1920         hitTestResult = mOnUiThread.getHitTestResult();
1921         assertEquals(HitTestResult.EMAIL_TYPE, hitTestResult.getType());
1922         assertEquals(email, hitTestResult.getExtra());
1923 
1924         // geo address
1925         moveFocusDown();
1926         hitTestResult = mOnUiThread.getHitTestResult();
1927         assertEquals(HitTestResult.GEO_TYPE, hitTestResult.getType());
1928         assertEquals(location, hitTestResult.getExtra());
1929     }
1930 
testSetInitialScale()1931     public void testSetInitialScale() throws Throwable {
1932         if (!NullWebViewUtils.isWebViewAvailable()) {
1933             return;
1934         }
1935         final String p = "<p style=\"height:1000px;width:1000px\">Test setInitialScale.</p>";
1936         final float defaultScale =
1937             getActivity().getResources().getDisplayMetrics().density;
1938 
1939         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1940                 + "</body></html>", "text/html", null);
1941 
1942         new PollingCheck(TEST_TIMEOUT) {
1943             @Override
1944             protected boolean check() {
1945                 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
1946             }
1947         }.run();
1948 
1949         mOnUiThread.setInitialScale(0);
1950         // modify content to fool WebKit into re-loading
1951         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1952                 + "2" + "</body></html>", "text/html", null);
1953 
1954         new PollingCheck(TEST_TIMEOUT) {
1955             @Override
1956             protected boolean check() {
1957                 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
1958             }
1959         }.run();
1960 
1961         mOnUiThread.setInitialScale(50);
1962         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1963                 + "3" + "</body></html>", "text/html", null);
1964 
1965         new PollingCheck(TEST_TIMEOUT) {
1966             @Override
1967             protected boolean check() {
1968                 return Math.abs(0.5 - mOnUiThread.getScale()) < .01f;
1969             }
1970         }.run();
1971 
1972         mOnUiThread.setInitialScale(0);
1973         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1974                 + "4" + "</body></html>", "text/html", null);
1975 
1976         new PollingCheck(TEST_TIMEOUT) {
1977             @Override
1978             protected boolean check() {
1979                 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
1980             }
1981         }.run();
1982     }
1983 
1984     @UiThreadTest
testClearHistory()1985     public void testClearHistory() throws Exception {
1986         if (!NullWebViewUtils.isWebViewAvailable()) {
1987             return;
1988         }
1989         startWebServer(false);
1990         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
1991         String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
1992         String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);
1993 
1994         mOnUiThread.loadUrlAndWaitForCompletion(url1);
1995         pollingCheckWebBackForwardList(url1, 0, 1);
1996 
1997         mOnUiThread.loadUrlAndWaitForCompletion(url2);
1998         pollingCheckWebBackForwardList(url2, 1, 2);
1999 
2000         mOnUiThread.loadUrlAndWaitForCompletion(url3);
2001         pollingCheckWebBackForwardList(url3, 2, 3);
2002 
2003         mWebView.clearHistory();
2004 
2005         // only current URL is left after clearing
2006         pollingCheckWebBackForwardList(url3, 0, 1);
2007     }
2008 
2009     @UiThreadTest
testSaveAndRestoreState()2010     public void testSaveAndRestoreState() throws Throwable {
2011         if (!NullWebViewUtils.isWebViewAvailable()) {
2012             return;
2013         }
2014         assertNull("Should return null when there's nothing to save",
2015                 mWebView.saveState(new Bundle()));
2016 
2017         startWebServer(false);
2018         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
2019         String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
2020         String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);
2021 
2022         // make a history list
2023         mOnUiThread.loadUrlAndWaitForCompletion(url1);
2024         pollingCheckWebBackForwardList(url1, 0, 1);
2025         mOnUiThread.loadUrlAndWaitForCompletion(url2);
2026         pollingCheckWebBackForwardList(url2, 1, 2);
2027         mOnUiThread.loadUrlAndWaitForCompletion(url3);
2028         pollingCheckWebBackForwardList(url3, 2, 3);
2029 
2030         // save the list
2031         Bundle bundle = new Bundle();
2032         WebBackForwardList saveList = mWebView.saveState(bundle);
2033         assertNotNull(saveList);
2034         assertEquals(3, saveList.getSize());
2035         assertEquals(2, saveList.getCurrentIndex());
2036         assertEquals(url1, saveList.getItemAtIndex(0).getUrl());
2037         assertEquals(url2, saveList.getItemAtIndex(1).getUrl());
2038         assertEquals(url3, saveList.getItemAtIndex(2).getUrl());
2039 
2040         // change the content to a new "blank" web view without history
2041         final WebView newWebView = new WebView(getActivity());
2042 
2043         WebBackForwardList copyListBeforeRestore = newWebView.copyBackForwardList();
2044         assertNotNull(copyListBeforeRestore);
2045         assertEquals(0, copyListBeforeRestore.getSize());
2046 
2047         // restore the list
2048         final WebBackForwardList restoreList = newWebView.restoreState(bundle);
2049         assertNotNull(restoreList);
2050         assertEquals(3, restoreList.getSize());
2051         assertEquals(2, saveList.getCurrentIndex());
2052 
2053         // wait for the list items to get inflated
2054         new PollingCheck(TEST_TIMEOUT) {
2055             @Override
2056             protected boolean check() {
2057                 return restoreList.getItemAtIndex(0).getUrl() != null &&
2058                        restoreList.getItemAtIndex(1).getUrl() != null &&
2059                        restoreList.getItemAtIndex(2).getUrl() != null;
2060             }
2061         }.run();
2062         assertEquals(url1, restoreList.getItemAtIndex(0).getUrl());
2063         assertEquals(url2, restoreList.getItemAtIndex(1).getUrl());
2064         assertEquals(url3, restoreList.getItemAtIndex(2).getUrl());
2065 
2066         WebBackForwardList copyListAfterRestore = newWebView.copyBackForwardList();
2067         assertNotNull(copyListAfterRestore);
2068         assertEquals(3, copyListAfterRestore.getSize());
2069         assertEquals(2, copyListAfterRestore.getCurrentIndex());
2070         assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl());
2071         assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl());
2072         assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl());
2073     }
2074 
testRequestChildRectangleOnScreen()2075     public void testRequestChildRectangleOnScreen() throws Throwable {
2076         if (!NullWebViewUtils.isWebViewAvailable()) {
2077             return;
2078         }
2079 
2080         // It is needed to make test pass on some devices.
2081         mOnUiThread.setLayoutToMatchParent();
2082 
2083         DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
2084         final int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);
2085         String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\">&nbsp;</p>";
2086         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
2087                 + "</body></html>", "text/html", null);
2088         new PollingCheck() {
2089             @Override
2090             protected boolean check() {
2091                 return mOnUiThread.getContentHeight() >= dimension;
2092             }
2093         }.run();
2094 
2095         int origX = mOnUiThread.getScrollX();
2096         int origY = mOnUiThread.getScrollY();
2097 
2098         int half = dimension / 2;
2099         Rect rect = new Rect(half, half, half + 1, half + 1);
2100         assertTrue(mOnUiThread.requestChildRectangleOnScreen(mWebView, rect, true));
2101         assertThat(mOnUiThread.getScrollX(), greaterThan(origX));
2102         assertThat(mOnUiThread.getScrollY(), greaterThan(origY));
2103     }
2104 
testSetDownloadListener()2105     public void testSetDownloadListener() throws Throwable {
2106         if (!NullWebViewUtils.isWebViewAvailable()) {
2107             return;
2108         }
2109 
2110         final SettableFuture<Void> downloadStartFuture = SettableFuture.create();
2111         final class MyDownloadListener implements DownloadListener {
2112             public String url;
2113             public String mimeType;
2114             public long contentLength;
2115             public String contentDisposition;
2116 
2117             @Override
2118             public void onDownloadStart(String url, String userAgent, String contentDisposition,
2119                     String mimetype, long contentLength) {
2120                 this.url = url;
2121                 this.mimeType = mimetype;
2122                 this.contentLength = contentLength;
2123                 this.contentDisposition = contentDisposition;
2124                 downloadStartFuture.set(null);
2125             }
2126         }
2127 
2128         final String mimeType = "application/octet-stream";
2129         final int length = 100;
2130         final MyDownloadListener listener = new MyDownloadListener();
2131 
2132         startWebServer(false);
2133         final String url = mWebServer.getBinaryUrl(mimeType, length);
2134 
2135         // By default, WebView sends an intent to ask the system to
2136         // handle loading a new URL. We set WebViewClient as
2137         // WebViewClient.shouldOverrideUrlLoading() returns false, so
2138         // the WebView will load the new URL.
2139         mOnUiThread.setDownloadListener(listener);
2140         mOnUiThread.getSettings().setJavaScriptEnabled(true);
2141         mOnUiThread.loadDataAndWaitForCompletion(
2142                 "<html><body onload=\"window.location = \'" + url + "\'\"></body></html>",
2143                 "text/html", null);
2144         // Wait for layout to complete before setting focus.
2145         getInstrumentation().waitForIdleSync();
2146 
2147         WebkitUtils.waitForFuture(downloadStartFuture);
2148         assertEquals(url, listener.url);
2149         assertTrue(listener.contentDisposition.contains("test.bin"));
2150         assertEquals(length, listener.contentLength);
2151         assertEquals(mimeType, listener.mimeType);
2152     }
2153 
2154     @UiThreadTest
testSetLayoutParams()2155     public void testSetLayoutParams() {
2156         if (!NullWebViewUtils.isWebViewAvailable()) {
2157             return;
2158         }
2159         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(600, 800);
2160         mWebView.setLayoutParams(params);
2161         assertSame(params, mWebView.getLayoutParams());
2162     }
2163 
2164     @UiThreadTest
testSetMapTrackballToArrowKeys()2165     public void testSetMapTrackballToArrowKeys() {
2166         if (!NullWebViewUtils.isWebViewAvailable()) {
2167             return;
2168         }
2169         mWebView.setMapTrackballToArrowKeys(true);
2170     }
2171 
testSetNetworkAvailable()2172     public void testSetNetworkAvailable() throws Exception {
2173         if (!NullWebViewUtils.isWebViewAvailable()) {
2174             return;
2175         }
2176         WebSettings settings = mOnUiThread.getSettings();
2177         settings.setJavaScriptEnabled(true);
2178         startWebServer(false);
2179 
2180         String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL);
2181         mOnUiThread.loadUrlAndWaitForCompletion(url);
2182         assertEquals("ONLINE", mOnUiThread.getTitle());
2183 
2184         mOnUiThread.setNetworkAvailable(false);
2185 
2186         // Wait for the DOM to receive notification of the network state change.
2187         new PollingCheck(TEST_TIMEOUT) {
2188             @Override
2189             protected boolean check() {
2190                 return mOnUiThread.getTitle().equals("OFFLINE");
2191             }
2192         }.run();
2193 
2194         mOnUiThread.setNetworkAvailable(true);
2195 
2196         // Wait for the DOM to receive notification of the network state change.
2197         new PollingCheck(TEST_TIMEOUT) {
2198             @Override
2199             protected boolean check() {
2200                 return mOnUiThread.getTitle().equals("ONLINE");
2201             }
2202         }.run();
2203     }
2204 
testSetWebChromeClient()2205     public void testSetWebChromeClient() throws Throwable {
2206         if (!NullWebViewUtils.isWebViewAvailable()) {
2207             return;
2208         }
2209 
2210         final SettableFuture<Void> future = SettableFuture.create();
2211         mOnUiThread.setWebChromeClient(new WaitForProgressClient(mOnUiThread) {
2212             @Override
2213             public void onProgressChanged(WebView view, int newProgress) {
2214                 super.onProgressChanged(view, newProgress);
2215                 future.set(null);
2216             }
2217         });
2218         getInstrumentation().waitForIdleSync();
2219         assertFalse(future.isDone());
2220 
2221         startWebServer(false);
2222         final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
2223         mOnUiThread.loadUrlAndWaitForCompletion(url);
2224         getInstrumentation().waitForIdleSync();
2225 
2226         WebkitUtils.waitForFuture(future);
2227     }
2228 
testPauseResumeTimers()2229     public void testPauseResumeTimers() throws Throwable {
2230         if (!NullWebViewUtils.isWebViewAvailable()) {
2231             return;
2232         }
2233         class Monitor {
2234             private boolean mIsUpdated;
2235 
2236             @JavascriptInterface
2237             public synchronized void update() {
2238                 mIsUpdated  = true;
2239                 notify();
2240             }
2241             public synchronized boolean waitForUpdate() {
2242                 while (!mIsUpdated) {
2243                     try {
2244                         // This is slightly flaky, as we can't guarantee that
2245                         // this is a sufficient time limit, but there's no way
2246                         // around this.
2247                         wait(1000);
2248                         if (!mIsUpdated) {
2249                             return false;
2250                         }
2251                     } catch (InterruptedException e) {
2252                     }
2253                 }
2254                 mIsUpdated = false;
2255                 return true;
2256             }
2257         };
2258         final Monitor monitor = new Monitor();
2259         final String updateMonitorHtml = "<html>" +
2260                 "<body onload=\"monitor.update();\"></body></html>";
2261 
2262         // Test that JavaScript is executed even with timers paused.
2263         WebkitUtils.onMainThreadSync(() -> {
2264             mWebView.getSettings().setJavaScriptEnabled(true);
2265             mWebView.addJavascriptInterface(monitor, "monitor");
2266             mWebView.pauseTimers();
2267             mOnUiThread.loadDataAndWaitForCompletion(updateMonitorHtml,
2268                     "text/html", null);
2269         });
2270         assertTrue(monitor.waitForUpdate());
2271 
2272         // Start a timer and test that it does not fire.
2273         mOnUiThread.loadDataAndWaitForCompletion(
2274                 "<html><body onload='setTimeout(function(){monitor.update();},100)'>" +
2275                 "</body></html>", "text/html", null);
2276         assertFalse(monitor.waitForUpdate());
2277 
2278         // Resume timers and test that the timer fires.
2279         mOnUiThread.resumeTimers();
2280         assertTrue(monitor.waitForUpdate());
2281     }
2282 
2283     // verify query parameters can be passed correctly to android asset files
testAndroidAssetQueryParam()2284     public void testAndroidAssetQueryParam() {
2285         if (!NullWebViewUtils.isWebViewAvailable()) {
2286             return;
2287         }
2288 
2289         WebSettings settings = mOnUiThread.getSettings();
2290         settings.setJavaScriptEnabled(true);
2291         // test passing a parameter
2292         String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.PARAM_ASSET_URL+"?val=SUCCESS");
2293         mOnUiThread.loadUrlAndWaitForCompletion(fileUrl);
2294         assertEquals("SUCCESS", mOnUiThread.getTitle());
2295     }
2296 
2297     // verify anchors work correctly for android asset files
testAndroidAssetAnchor()2298     public void testAndroidAssetAnchor() {
2299         if (!NullWebViewUtils.isWebViewAvailable()) {
2300             return;
2301         }
2302 
2303         WebSettings settings = mOnUiThread.getSettings();
2304         settings.setJavaScriptEnabled(true);
2305         // test using an anchor
2306         String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.ANCHOR_ASSET_URL+"#anchor");
2307         mOnUiThread.loadUrlAndWaitForCompletion(fileUrl);
2308         assertEquals("anchor", mOnUiThread.getTitle());
2309     }
2310 
testEvaluateJavascript()2311     public void testEvaluateJavascript() {
2312         if (!NullWebViewUtils.isWebViewAvailable()) {
2313             return;
2314         }
2315         mOnUiThread.getSettings().setJavaScriptEnabled(true);
2316         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
2317 
2318         assertEquals("2", mOnUiThread.evaluateJavascriptSync("1+1"));
2319 
2320         assertEquals("9", mOnUiThread.evaluateJavascriptSync("1+1; 4+5"));
2321 
2322         final String EXPECTED_TITLE = "test";
2323         mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null);
2324         new PollingCheck(TEST_TIMEOUT) {
2325             @Override
2326             protected boolean check() {
2327                 return mOnUiThread.getTitle().equals(EXPECTED_TITLE);
2328             }
2329         }.run();
2330     }
2331 
2332     // Verify Print feature can create a PDF file with a correct preamble.
testPrinting()2333     public void testPrinting() throws Throwable {
2334         if (!NullWebViewUtils.isWebViewAvailable()) {
2335             return;
2336         }
2337         mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" +
2338                 "<body>foo</body></html>",
2339                 "text/html", null);
2340         final PrintDocumentAdapter adapter =  mOnUiThread.createPrintDocumentAdapter();
2341         printDocumentStart(adapter);
2342         PrintAttributes attributes = new PrintAttributes.Builder()
2343                 .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
2344                 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300))
2345                 .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
2346                 .build();
2347         final WebViewCtsActivity activity = getActivity();
2348         final File file = activity.getFileStreamPath(PRINTER_TEST_FILE);
2349         final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file,
2350                 ParcelFileDescriptor.parseMode("w"));
2351         final SettableFuture<Void> result = SettableFuture.create();
2352         printDocumentLayout(adapter, null, attributes,
2353                 new LayoutResultCallback() {
2354                     // Called on UI thread
2355                     @Override
2356                     public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
2357                         PageRange[] pageRanges = new PageRange[] {PageRange.ALL_PAGES};
2358                         savePrintedPage(adapter, descriptor, pageRanges, result);
2359                     }
2360                 });
2361         try {
2362             WebkitUtils.waitForFuture(result);
2363             assertThat(file.length(), greaterThan(0L));
2364             FileInputStream in = new FileInputStream(file);
2365             byte[] b = new byte[PDF_PREAMBLE.length()];
2366             in.read(b);
2367             String preamble = new String(b);
2368             assertEquals(PDF_PREAMBLE, preamble);
2369         } finally {
2370             // close the descriptor, if not closed already.
2371             descriptor.close();
2372             file.delete();
2373         }
2374     }
2375 
2376     // Verify Print feature can create a PDF file with correct number of pages.
testPrintingPagesCount()2377     public void testPrintingPagesCount() throws Throwable {
2378         if (!NullWebViewUtils.isWebViewAvailable()) {
2379             return;
2380         }
2381         String content = "<html><head></head><body>";
2382         for (int i = 0; i < 500; ++i) {
2383             content += "<br />abcdefghijk<br />";
2384         }
2385         content += "</body></html>";
2386         mOnUiThread.loadDataAndWaitForCompletion(content, "text/html", null);
2387         final PrintDocumentAdapter adapter =  mOnUiThread.createPrintDocumentAdapter();
2388         printDocumentStart(adapter);
2389         PrintAttributes attributes = new PrintAttributes.Builder()
2390                 .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
2391                 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300))
2392                 .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
2393                 .build();
2394         final WebViewCtsActivity activity = getActivity();
2395         final File file = activity.getFileStreamPath(PRINTER_TEST_FILE);
2396         final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file,
2397                 ParcelFileDescriptor.parseMode("w"));
2398         final SettableFuture<Void> result = SettableFuture.create();
2399         printDocumentLayout(adapter, null, attributes,
2400                 new LayoutResultCallback() {
2401                     // Called on UI thread
2402                     @Override
2403                     public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
2404                         PageRange[] pageRanges = new PageRange[] {
2405                             new PageRange(1, 1), new PageRange(4, 7)
2406                         };
2407                         savePrintedPage(adapter, descriptor, pageRanges, result);
2408                     }
2409                 });
2410         try {
2411             WebkitUtils.waitForFuture(result);
2412             assertThat(file.length(), greaterThan(0L));
2413             PdfRenderer renderer = new PdfRenderer(
2414                 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
2415             assertEquals(5, renderer.getPageCount());
2416         } finally {
2417             descriptor.close();
2418             file.delete();
2419         }
2420     }
2421 
2422     /**
2423      * This should remain functionally equivalent to
2424      * androidx.webkit.WebViewCompatTest#testVisualStateCallbackCalled. Modifications to this test
2425      * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
2426      */
testVisualStateCallbackCalled()2427     public void testVisualStateCallbackCalled() throws Exception {
2428         // Check that the visual state callback is called correctly.
2429         if (!NullWebViewUtils.isWebViewAvailable()) {
2430             return;
2431         }
2432 
2433         final long kRequest = 100;
2434 
2435         mOnUiThread.loadUrl("about:blank");
2436 
2437         final SettableFuture<Long> visualStateFuture = SettableFuture.create();
2438         mOnUiThread.postVisualStateCallback(kRequest, new VisualStateCallback() {
2439             public void onComplete(long requestId) {
2440                 visualStateFuture.set(requestId);
2441             }
2442         });
2443 
2444         assertEquals(kRequest, (long) WebkitUtils.waitForFuture(visualStateFuture));
2445     }
2446 
2447     /**
2448      * This should remain functionally equivalent to
2449      * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingAllowlistWithMalformedList.
2450      * Modifications to this test should be reflected in that test as necessary. See
2451      * http://go/modifying-webview-cts.
2452      */
testSetSafeBrowsingAllowlistWithMalformedList()2453     public void testSetSafeBrowsingAllowlistWithMalformedList() throws Exception {
2454         if (!NullWebViewUtils.isWebViewAvailable()) {
2455             return;
2456         }
2457 
2458         List allowlist = new ArrayList<String>();
2459         // Protocols are not supported in the allowlist
2460         allowlist.add("http://google.com");
2461         final SettableFuture<Boolean> safeBrowsingAllowlistFuture = SettableFuture.create();
2462         WebView.setSafeBrowsingWhitelist(allowlist, new ValueCallback<Boolean>() {
2463             @Override
2464             public void onReceiveValue(Boolean success) {
2465                 safeBrowsingAllowlistFuture.set(success);
2466             }
2467         });
2468         assertFalse(WebkitUtils.waitForFuture(safeBrowsingAllowlistFuture));
2469     }
2470 
2471     /**
2472      * This should remain functionally equivalent to
2473      * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingAllowlistWithValidList. Modifications
2474      * to this test should be reflected in that test as necessary. See
2475      * http://go/modifying-webview-cts.
2476      */
testSetSafeBrowsingAllowlistWithValidList()2477     public void testSetSafeBrowsingAllowlistWithValidList() throws Exception {
2478         if (!NullWebViewUtils.isWebViewAvailable()) {
2479             return;
2480         }
2481 
2482         List allowlist = new ArrayList<String>();
2483         allowlist.add("safe-browsing");
2484         final SettableFuture<Boolean> safeBrowsingAllowlistFuture = SettableFuture.create();
2485         WebView.setSafeBrowsingWhitelist(allowlist, new ValueCallback<Boolean>() {
2486             @Override
2487             public void onReceiveValue(Boolean success) {
2488                 safeBrowsingAllowlistFuture.set(success);
2489             }
2490         });
2491         assertTrue(WebkitUtils.waitForFuture(safeBrowsingAllowlistFuture));
2492 
2493         final SettableFuture<Void> pageFinishedFuture = SettableFuture.create();
2494         mOnUiThread.setWebViewClient(new WebViewClient() {
2495             @Override
2496             public void onPageFinished(WebView view, String url) {
2497                 pageFinishedFuture.set(null);
2498             }
2499 
2500             @Override
2501             public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType,
2502                     SafeBrowsingResponse callback) {
2503                 pageFinishedFuture.setException(new IllegalStateException(
2504                         "Should not invoke onSafeBrowsingHit"));
2505             }
2506         });
2507 
2508         mOnUiThread.loadUrl("chrome://safe-browsing/match?type=malware");
2509 
2510         // Wait until page load has completed
2511         WebkitUtils.waitForFuture(pageFinishedFuture);
2512     }
2513 
2514     /**
2515      * This should remain functionally equivalent to
2516      * androidx.webkit.WebViewCompatTest#testGetWebViewClient. Modifications to this test should be
2517      * reflected in that test as necessary. See http://go/modifying-webview-cts.
2518      */
2519     @UiThreadTest
testGetWebViewClient()2520     public void testGetWebViewClient() throws Exception {
2521         if (!NullWebViewUtils.isWebViewAvailable()) {
2522             return;
2523         }
2524 
2525         // getWebViewClient should return a default WebViewClient if it hasn't been set yet
2526         WebView webView = new WebView(getActivity());
2527         WebViewClient client = webView.getWebViewClient();
2528         assertNotNull(client);
2529         assertTrue(client instanceof WebViewClient);
2530 
2531         // getWebViewClient should return the client after it has been set
2532         WebViewClient client2 = new WebViewClient();
2533         assertNotSame(client, client2);
2534         webView.setWebViewClient(client2);
2535         assertSame(client2, webView.getWebViewClient());
2536     }
2537 
2538     /**
2539      * This should remain functionally equivalent to
2540      * androidx.webkit.WebViewCompatTest#testGetWebChromeClient. Modifications to this test should
2541      * be reflected in that test as necessary. See http://go/modifying-webview-cts.
2542      */
2543     @UiThreadTest
testGetWebChromeClient()2544     public void testGetWebChromeClient() throws Exception {
2545         if (!NullWebViewUtils.isWebViewAvailable()) {
2546             return;
2547         }
2548 
2549         // getWebChromeClient should return null if the client hasn't been set yet
2550         WebView webView = new WebView(getActivity());
2551         WebChromeClient client = webView.getWebChromeClient();
2552         assertNull(client);
2553 
2554         // getWebChromeClient should return the client after it has been set
2555         WebChromeClient client2 = new WebChromeClient();
2556         assertNotSame(client, client2);
2557         webView.setWebChromeClient(client2);
2558         assertSame(client2, webView.getWebChromeClient());
2559     }
2560 
2561     @UiThreadTest
testSetCustomTextClassifier()2562     public void testSetCustomTextClassifier() throws Exception {
2563         if (!NullWebViewUtils.isWebViewAvailable()) {
2564             return;
2565         }
2566 
2567         class CustomTextClassifier implements TextClassifier {
2568             @Override
2569             public TextSelection suggestSelection(
2570                 CharSequence text,
2571                 int startIndex,
2572                 int endIndex,
2573                 LocaleList defaultLocales) {
2574                 return new TextSelection.Builder(0, 1).build();
2575             }
2576 
2577             @Override
2578             public TextClassification classifyText(
2579                 CharSequence text,
2580                 int startIndex,
2581                 int endIndex,
2582                 LocaleList defaultLocales) {
2583                 return new TextClassification.Builder().build();
2584             }
2585         };
2586 
2587         TextClassifier classifier = new CustomTextClassifier();
2588         WebView webView = new WebView(getActivity());
2589         webView.setTextClassifier(classifier);
2590         assertSame(webView.getTextClassifier(), classifier);
2591     }
2592 
2593     private static class MockContext extends ContextWrapper {
2594         private boolean mGetApplicationContextWasCalled;
2595 
MockContext(Context context)2596         public MockContext(Context context) {
2597             super(context);
2598         }
2599 
getApplicationContext()2600         public Context getApplicationContext() {
2601             mGetApplicationContextWasCalled = true;
2602             return super.getApplicationContext();
2603         }
2604 
wasGetApplicationContextCalled()2605         public boolean wasGetApplicationContextCalled() {
2606             return mGetApplicationContextWasCalled;
2607         }
2608     }
2609 
2610     /**
2611      * This should remain functionally equivalent to
2612      * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingUseApplicationContext. Modifications to
2613      * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
2614      */
testStartSafeBrowsingUseApplicationContext()2615     public void testStartSafeBrowsingUseApplicationContext() throws Exception {
2616         if (!NullWebViewUtils.isWebViewAvailable()) {
2617             return;
2618         }
2619 
2620         final MockContext ctx = new MockContext(getActivity());
2621         final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create();
2622         WebView.startSafeBrowsing(ctx, new ValueCallback<Boolean>() {
2623             @Override
2624             public void onReceiveValue(Boolean value) {
2625                 startSafeBrowsingFuture.set(ctx.wasGetApplicationContextCalled());
2626                 return;
2627             }
2628         });
2629         assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture));
2630     }
2631 
2632     /**
2633      * This should remain functionally equivalent to
2634      * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingWithNullCallbackDoesntCrash.
2635      * Modifications to this test should be reflected in that test as necessary. See
2636      * http://go/modifying-webview-cts.
2637      */
testStartSafeBrowsingWithNullCallbackDoesntCrash()2638     public void testStartSafeBrowsingWithNullCallbackDoesntCrash() throws Exception {
2639         if (!NullWebViewUtils.isWebViewAvailable()) {
2640             return;
2641         }
2642 
2643         WebView.startSafeBrowsing(getActivity().getApplicationContext(), null);
2644     }
2645 
2646     /**
2647      * This should remain functionally equivalent to
2648      * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingInvokesCallback. Modifications to
2649      * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
2650      */
testStartSafeBrowsingInvokesCallback()2651     public void testStartSafeBrowsingInvokesCallback() throws Exception {
2652         if (!NullWebViewUtils.isWebViewAvailable()) {
2653             return;
2654         }
2655 
2656         final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create();
2657         WebView.startSafeBrowsing(getActivity().getApplicationContext(),
2658                 new ValueCallback<Boolean>() {
2659             @Override
2660             public void onReceiveValue(Boolean value) {
2661                 startSafeBrowsingFuture.set(Looper.getMainLooper().isCurrentThread());
2662                 return;
2663             }
2664         });
2665         assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture));
2666     }
2667 
savePrintedPage(final PrintDocumentAdapter adapter, final ParcelFileDescriptor descriptor, final PageRange[] pageRanges, final SettableFuture<Void> result)2668     private void savePrintedPage(final PrintDocumentAdapter adapter,
2669             final ParcelFileDescriptor descriptor, final PageRange[] pageRanges,
2670             final SettableFuture<Void> result) {
2671         adapter.onWrite(pageRanges, descriptor,
2672                 new CancellationSignal(),
2673                 new WriteResultCallback() {
2674                     @Override
2675                     public void onWriteFinished(PageRange[] pages) {
2676                         try {
2677                             descriptor.close();
2678                             result.set(null);
2679                         } catch (IOException ex) {
2680                             result.setException(ex);
2681                         }
2682                     }
2683                 });
2684     }
2685 
printDocumentStart(final PrintDocumentAdapter adapter)2686     private void printDocumentStart(final PrintDocumentAdapter adapter) {
2687         WebkitUtils.onMainThreadSync(() -> {
2688             adapter.onStart();
2689         });
2690     }
2691 
printDocumentLayout(final PrintDocumentAdapter adapter, final PrintAttributes oldAttributes, final PrintAttributes newAttributes, final LayoutResultCallback layoutResultCallback)2692     private void printDocumentLayout(final PrintDocumentAdapter adapter,
2693             final PrintAttributes oldAttributes, final PrintAttributes newAttributes,
2694             final LayoutResultCallback layoutResultCallback) {
2695         WebkitUtils.onMainThreadSync(() -> {
2696             adapter.onLayout(oldAttributes, newAttributes, new CancellationSignal(),
2697                     layoutResultCallback, null);
2698         });
2699     }
2700 
2701     private static class HrefCheckHandler extends Handler {
2702         private boolean mHadRecieved;
2703 
2704         private String mResultUrl;
2705 
HrefCheckHandler(Looper looper)2706         public HrefCheckHandler(Looper looper) {
2707             super(looper);
2708         }
2709 
hasCalledHandleMessage()2710         public boolean hasCalledHandleMessage() {
2711             return mHadRecieved;
2712         }
2713 
getResultUrl()2714         public String getResultUrl() {
2715             return mResultUrl;
2716         }
2717 
reset()2718         public void reset(){
2719             mResultUrl = null;
2720             mHadRecieved = false;
2721         }
2722 
2723         @Override
handleMessage(Message msg)2724         public void handleMessage(Message msg) {
2725             mResultUrl = msg.getData().getString("url");
2726             mHadRecieved = true;
2727         }
2728     }
2729 
moveFocusDown()2730     private void moveFocusDown() throws Throwable {
2731         // send down key and wait for idle
2732         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
2733         // waiting for idle isn't always sufficient for the key to be fully processed
2734         Thread.sleep(500);
2735     }
2736 
pollingCheckWebBackForwardList(final String currUrl, final int currIndex, final int size)2737     private void pollingCheckWebBackForwardList(final String currUrl, final int currIndex,
2738             final int size) {
2739         new PollingCheck() {
2740             @Override
2741             protected boolean check() {
2742                 WebBackForwardList list = mWebView.copyBackForwardList();
2743                 return checkWebBackForwardList(list, currUrl, currIndex, size);
2744             }
2745         }.run();
2746     }
2747 
checkWebBackForwardList(WebBackForwardList list, String currUrl, int currIndex, int size)2748     private boolean checkWebBackForwardList(WebBackForwardList list, String currUrl,
2749             int currIndex, int size) {
2750         return (list != null)
2751                 && (list.getSize() == size)
2752                 && (list.getCurrentIndex() == currIndex)
2753                 && list.getItemAtIndex(currIndex).getUrl().equals(currUrl);
2754     }
2755 
assertGoBackOrForwardBySteps(boolean expected, int steps)2756     private void assertGoBackOrForwardBySteps(boolean expected, int steps) {
2757         // skip if steps equals to 0
2758         if (steps == 0)
2759             return;
2760 
2761         int start = steps > 0 ? 1 : steps;
2762         int end = steps > 0 ? steps : -1;
2763 
2764         // check all the steps in the history
2765         for (int i = start; i <= end; i++) {
2766             assertEquals(expected, mWebView.canGoBackOrForward(i));
2767 
2768             // shortcut methods for one step
2769             if (i == 1) {
2770                 assertEquals(expected, mWebView.canGoForward());
2771             } else if (i == -1) {
2772                 assertEquals(expected, mWebView.canGoBack());
2773             }
2774         }
2775     }
2776 
isPictureFilledWithColor(Picture picture, int color)2777     private boolean isPictureFilledWithColor(Picture picture, int color) {
2778         if (picture.getWidth() == 0 || picture.getHeight() == 0)
2779             return false;
2780 
2781         Bitmap bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(),
2782                 Config.ARGB_8888);
2783         picture.draw(new Canvas(bitmap));
2784 
2785         for (int i = 0; i < bitmap.getWidth(); i ++) {
2786             for (int j = 0; j < bitmap.getHeight(); j ++) {
2787                 if (color != bitmap.getPixel(i, j)) {
2788                     return false;
2789                 }
2790             }
2791         }
2792         return true;
2793     }
2794 
2795     /**
2796      * Waits at least MIN_SCROLL_WAIT_MS for scrolling to start. Once started,
2797      * scrolling is checked every SCROLL_WAIT_INTERVAL_MS for changes. Once
2798      * changes have stopped, the function exits. If no scrolling has happened
2799      * then the function exits after MIN_SCROLL_WAIT milliseconds.
2800      * @param previousScrollY The Y scroll position prior to waiting.
2801      */
waitForScrollingComplete(int previousScrollY)2802     private void waitForScrollingComplete(int previousScrollY)
2803             throws InterruptedException {
2804         int scrollY = previousScrollY;
2805         // wait at least MIN_SCROLL_WAIT for something to happen.
2806         long noChangeMinWait = SystemClock.uptimeMillis() + MIN_SCROLL_WAIT_MS;
2807         boolean scrollChanging = false;
2808         boolean scrollChanged = false;
2809         boolean minWaitExpired = false;
2810         while (scrollChanging || (!scrollChanged && !minWaitExpired)) {
2811             Thread.sleep(SCROLL_WAIT_INTERVAL_MS);
2812             int oldScrollY = scrollY;
2813             scrollY = mOnUiThread.getScrollY();
2814             scrollChanging = (scrollY != oldScrollY);
2815             scrollChanged = (scrollY != previousScrollY);
2816             minWaitExpired = (SystemClock.uptimeMillis() > noChangeMinWait);
2817         }
2818     }
2819 
2820     /**
2821      * This should remain functionally equivalent to
2822      * androidx.webkit.WebViewCompatTest#testGetSafeBrowsingPrivacyPolicyUrl. Modifications to this
2823      * test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
2824      */
testGetSafeBrowsingPrivacyPolicyUrl()2825     public void testGetSafeBrowsingPrivacyPolicyUrl() throws Exception {
2826         if (!NullWebViewUtils.isWebViewAvailable()) {
2827             return;
2828         }
2829 
2830         assertNotNull(WebView.getSafeBrowsingPrivacyPolicyUrl());
2831         try {
2832             new URL(WebView.getSafeBrowsingPrivacyPolicyUrl().toString());
2833         } catch (MalformedURLException e) {
2834             fail("The privacy policy URL should be a well-formed URL");
2835         }
2836     }
2837 
testWebViewClassLoaderReturnsNonNull()2838     public void testWebViewClassLoaderReturnsNonNull() {
2839         if (!NullWebViewUtils.isWebViewAvailable()) {
2840             return;
2841         }
2842 
2843         assertNotNull(WebView.getWebViewClassLoader());
2844     }
2845 }
2846