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