• 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.graphics.Bitmap;
27 import android.graphics.Bitmap.Config;
28 import android.graphics.Canvas;
29 import android.graphics.Color;
30 import android.graphics.Picture;
31 import android.graphics.Rect;
32 import android.graphics.pdf.PdfRenderer;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.CancellationSignal;
36 import android.os.Handler;
37 import android.os.LocaleList;
38 import android.os.Looper;
39 import android.os.Message;
40 import android.os.ParcelFileDescriptor;
41 import android.os.StrictMode;
42 import android.os.StrictMode.ThreadPolicy;
43 import android.os.SystemClock;
44 import android.platform.test.annotations.AppModeFull;
45 import android.platform.test.annotations.Presubmit;
46 import android.print.PageRange;
47 import android.print.PrintAttributes;
48 import android.print.PrintDocumentAdapter;
49 import android.print.PrintDocumentAdapter.LayoutResultCallback;
50 import android.print.PrintDocumentAdapter.WriteResultCallback;
51 import android.print.PrintDocumentInfo;
52 import android.test.ActivityInstrumentationTestCase2;
53 import android.test.UiThreadTest;
54 import android.util.AttributeSet;
55 import android.util.DisplayMetrics;
56 import android.view.KeyEvent;
57 import android.view.MotionEvent;
58 import android.view.View;
59 import android.view.ViewGroup;
60 import android.view.textclassifier.TextClassification;
61 import android.view.textclassifier.TextClassifier;
62 import android.view.textclassifier.TextSelection;
63 import android.webkit.ConsoleMessage;
64 import android.webkit.CookieSyncManager;
65 import android.webkit.DownloadListener;
66 import android.webkit.JavascriptInterface;
67 import android.webkit.SafeBrowsingResponse;
68 import android.webkit.ValueCallback;
69 import android.webkit.WebBackForwardList;
70 import android.webkit.WebChromeClient;
71 import android.webkit.WebIconDatabase;
72 import android.webkit.WebResourceRequest;
73 import android.webkit.WebSettings;
74 import android.webkit.WebView;
75 import android.webkit.WebView.HitTestResult;
76 import android.webkit.WebView.PictureListener;
77 import android.webkit.WebView.VisualStateCallback;
78 import android.webkit.WebViewClient;
79 import android.webkit.WebViewDatabase;
80 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
81 import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
82 import android.widget.LinearLayout;
83 
84 import androidx.test.filters.FlakyTest;
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     private static final int INITIAL_PROGRESS = 100;
126     private static final String X_REQUESTED_WITH = "X-Requested-With";
127     private static final String PRINTER_TEST_FILE = "print.pdf";
128     private static final String PDF_PREAMBLE = "%PDF-1";
129     // Snippet of HTML that will prevent favicon requests to the test server.
130     private static final String HTML_HEADER =
131             "<html><head><link rel=\"shortcut icon\" href=\"%23\" /></head>";
132     private static final String SIMPLE_HTML = "<html><body>simple html</body></html>";
133 
134     /**
135      * This is the minimum number of milliseconds to wait for scrolling to
136      * start. If no scrolling has started before this timeout then it is
137      * assumed that no scrolling will happen.
138      */
139     private static final long MIN_SCROLL_WAIT_MS = 1000;
140 
141     /**
142      * This is the minimum number of milliseconds to wait for findAll to
143      * find all the matches. If matches are not found, the Listener would
144      * call findAll again until it times out.
145      */
146     private static final long MIN_FIND_WAIT_MS = 3000;
147 
148     /**
149      * Once scrolling has started, this is the interval that scrolling
150      * is checked to see if there is a change. If no scrolling change
151      * has happened in the given time then it is assumed that scrolling
152      * has stopped.
153      */
154     private static final long SCROLL_WAIT_INTERVAL_MS = 200;
155 
156     private WebView mWebView;
157     private CtsTestServer mWebServer;
158     private WebViewOnUiThread mOnUiThread;
159     private WebIconDatabase mIconDb;
160 
WebViewTest()161     public WebViewTest() {
162         super("com.android.cts.webkit", WebViewCtsActivity.class);
163     }
164 
165     @Override
setUp()166     protected void setUp() throws Exception {
167         super.setUp();
168         final WebViewCtsActivity activity = getActivity();
169         mWebView = activity.getWebView();
170         if (mWebView != null) {
171             new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
172                 @Override
173                     protected boolean check() {
174                         return activity.hasWindowFocus();
175                 }
176             }.run();
177             File f = activity.getFileStreamPath("snapshot");
178             if (f.exists()) {
179                 f.delete();
180             }
181 
182             mOnUiThread = new WebViewOnUiThread(mWebView);
183         }
184     }
185 
186     @Override
tearDown()187     protected void tearDown() throws Exception {
188         if (mOnUiThread != null) {
189             mOnUiThread.cleanUp();
190         }
191         if (mWebServer != null) {
192             stopWebServer();
193         }
194         if (mIconDb != null) {
195             mIconDb.removeAllIcons();
196             mIconDb.close();
197             mIconDb = null;
198         }
199         super.tearDown();
200     }
201 
startWebServer(boolean secure)202     private void startWebServer(boolean secure) throws Exception {
203         assertNull(mWebServer);
204         mWebServer = new CtsTestServer(getActivity(), secure);
205     }
206 
stopWebServer()207     private void stopWebServer() throws Exception {
208         assertNotNull(mWebServer);
209         ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
210         ThreadPolicy tmpPolicy = new ThreadPolicy.Builder(oldPolicy)
211                 .permitNetwork()
212                 .build();
213         StrictMode.setThreadPolicy(tmpPolicy);
214         mWebServer.shutdown();
215         mWebServer = null;
216         StrictMode.setThreadPolicy(oldPolicy);
217     }
218 
219     @UiThreadTest
testConstructor()220     public void testConstructor() {
221         if (!NullWebViewUtils.isWebViewAvailable()) {
222             return;
223         }
224 
225         WebView webView = new WebView(getActivity());
226         webView.destroy();
227         webView = new WebView(getActivity(), null);
228         webView.destroy();
229         webView = new WebView(getActivity(), null, 0);
230         webView.destroy();
231     }
232 
233     @UiThreadTest
testCreatingWebViewWithDeviceEncrpytionFails()234     public void testCreatingWebViewWithDeviceEncrpytionFails() {
235         if (!NullWebViewUtils.isWebViewAvailable()) {
236             return;
237         }
238 
239         Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext();
240         try {
241             new WebView(deviceEncryptedContext);
242             fail("WebView should have thrown exception when creating with a device " +
243                 "protected storage context");
244         } catch (IllegalArgumentException e) {}
245     }
246 
247     @UiThreadTest
testCreatingWebViewWithMultipleEncryptionContext()248     public void testCreatingWebViewWithMultipleEncryptionContext() {
249         if (!NullWebViewUtils.isWebViewAvailable()) {
250             return;
251         }
252 
253         // Credential encrpytion is the default. Create one here for the sake of clarity.
254         Context credentialEncryptedContext = getActivity().createCredentialProtectedStorageContext();
255         Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext();
256 
257         // No exception should be thrown with credential encryption context.
258         WebView webView = new WebView(credentialEncryptedContext);
259         webView.destroy();
260 
261         try {
262             new WebView(deviceEncryptedContext);
263             fail("WebView should have thrown exception when creating with a device " +
264                 "protected storage context");
265         } catch (IllegalArgumentException e) {}
266     }
267 
268     @UiThreadTest
testCreatingWebViewCreatesCookieSyncManager()269     public void testCreatingWebViewCreatesCookieSyncManager() throws Exception {
270         if (!NullWebViewUtils.isWebViewAvailable()) {
271             return;
272         }
273         WebView webView = new WebView(getActivity());
274         assertNotNull(CookieSyncManager.getInstance());
275         webView.destroy();
276     }
277 
278     // Static methods should be safe to call on non-UI threads
testFindAddress()279     public void testFindAddress() {
280         if (!NullWebViewUtils.isWebViewAvailable()) {
281             return;
282         }
283 
284         /*
285          * Info about USPS
286          * http://en.wikipedia.org/wiki/Postal_address#United_States
287          * http://www.usps.com/
288          */
289         // full address
290         assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826",
291                 WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826"));
292         // Zipcode is optional.
293         assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA",
294                 WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA"));
295         // not an address
296         assertNull(WebView.findAddress("This is not an address: no town, no state, no zip."));
297 
298         // would be an address, except for numbers that are not ASCII
299         assertNull(WebView.findAddress(
300                 "80\uD835\uDFEF \uD835\uDFEF\uD835\uDFEFth Avenue Sunnyvale, CA 94089"));
301     }
302 
303     @UiThreadTest
testScrollBarOverlay()304     public void testScrollBarOverlay() throws Throwable {
305         if (!NullWebViewUtils.isWebViewAvailable()) {
306             return;
307         }
308 
309         // These functions have no effect; just verify they don't crash
310         mWebView.setHorizontalScrollbarOverlay(true);
311         mWebView.setVerticalScrollbarOverlay(false);
312 
313         assertTrue(mWebView.overlayHorizontalScrollbar());
314         assertFalse(mWebView.overlayVerticalScrollbar());
315     }
316 
317     @Presubmit
318     @UiThreadTest
testLoadUrl()319     public void testLoadUrl() throws Exception {
320         if (!NullWebViewUtils.isWebViewAvailable()) {
321             return;
322         }
323 
324         assertNull(mWebView.getUrl());
325         assertNull(mWebView.getOriginalUrl());
326         assertEquals(INITIAL_PROGRESS, mWebView.getProgress());
327 
328         startWebServer(false);
329         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
330         mOnUiThread.loadUrlAndWaitForCompletion(url);
331         assertEquals(100, mWebView.getProgress());
332         assertEquals(url, mWebView.getUrl());
333         assertEquals(url, mWebView.getOriginalUrl());
334         assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle());
335 
336         // verify that the request also includes X-Requested-With header
337         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
338         Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH);
339         assertEquals(1, matchingHeaders.length);
340 
341         Header header = matchingHeaders[0];
342         assertEquals(mWebView.getContext().getApplicationInfo().packageName, header.getValue());
343     }
344 
345     @UiThreadTest
testPostUrlWithNonNetworkUrl()346     public void testPostUrlWithNonNetworkUrl() throws Exception {
347         if (!NullWebViewUtils.isWebViewAvailable()) {
348             return;
349         }
350         final String nonNetworkUrl = "file:///android_asset/" + TestHtmlConstants.HELLO_WORLD_URL;
351 
352         mOnUiThread.postUrlAndWaitForCompletion(nonNetworkUrl, new byte[1]);
353 
354         assertEquals("Non-network URL should have loaded", TestHtmlConstants.HELLO_WORLD_TITLE,
355                 mWebView.getTitle());
356     }
357 
358     @UiThreadTest
testPostUrlWithNetworkUrl()359     public void testPostUrlWithNetworkUrl() throws Exception {
360         if (!NullWebViewUtils.isWebViewAvailable()) {
361             return;
362         }
363         startWebServer(false);
364         final String networkUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
365         final String postDataString = "username=my_username&password=my_password";
366         final byte[] postData = EncodingUtils.getBytes(postDataString, "BASE64");
367 
368         mOnUiThread.postUrlAndWaitForCompletion(networkUrl, postData);
369 
370         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
371         assertEquals("The last request should be POST", request.getRequestLine().getMethod(),
372                 "POST");
373 
374         assertTrue("The last request should have a request body",
375                 request instanceof HttpEntityEnclosingRequest);
376         HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
377         String entityString = EntityUtils.toString(entity);
378         assertEquals(entityString, postDataString);
379     }
380 
381     @UiThreadTest
testLoadUrlDoesNotStripParamsWhenLoadingContentUrls()382     public void testLoadUrlDoesNotStripParamsWhenLoadingContentUrls() throws Exception {
383         if (!NullWebViewUtils.isWebViewAvailable()) {
384             return;
385         }
386 
387         Uri.Builder uriBuilder = new Uri.Builder().scheme(
388                 ContentResolver.SCHEME_CONTENT).authority(MockContentProvider.AUTHORITY);
389         uriBuilder.appendPath("foo.html").appendQueryParameter("param","bar");
390         String url = uriBuilder.build().toString();
391         mOnUiThread.loadUrlAndWaitForCompletion(url);
392         // verify the parameter is not stripped.
393         Uri uri = Uri.parse(mWebView.getTitle());
394         assertEquals("bar", uri.getQueryParameter("param"));
395     }
396 
397     @UiThreadTest
testAppInjectedXRequestedWithHeaderIsNotOverwritten()398     public void testAppInjectedXRequestedWithHeaderIsNotOverwritten() throws Exception {
399         if (!NullWebViewUtils.isWebViewAvailable()) {
400             return;
401         }
402 
403         startWebServer(false);
404         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
405         HashMap<String, String> map = new HashMap<String, String>();
406         final String requester = "foo";
407         map.put(X_REQUESTED_WITH, requester);
408         mOnUiThread.loadUrlAndWaitForCompletion(url, map);
409 
410         // verify that the request also includes X-Requested-With header
411         // but is not overwritten by the webview
412         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
413         Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH);
414         assertEquals(1, matchingHeaders.length);
415 
416         Header header = matchingHeaders[0];
417         assertEquals(requester, header.getValue());
418     }
419 
420     @UiThreadTest
testAppCanInjectHeadersViaImmutableMap()421     public void testAppCanInjectHeadersViaImmutableMap() throws Exception {
422         if (!NullWebViewUtils.isWebViewAvailable()) {
423             return;
424         }
425 
426         startWebServer(false);
427         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
428         HashMap<String, String> map = new HashMap<String, String>();
429         final String requester = "foo";
430         map.put(X_REQUESTED_WITH, requester);
431         mOnUiThread.loadUrlAndWaitForCompletion(url, Collections.unmodifiableMap(map));
432 
433         // verify that the request also includes X-Requested-With header
434         // but is not overwritten by the webview
435         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
436         Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH);
437         assertEquals(1, matchingHeaders.length);
438 
439         Header header = matchingHeaders[0];
440         assertEquals(requester, header.getValue());
441     }
442 
testCanInjectHeaders()443     public void testCanInjectHeaders() throws Exception {
444         if (!NullWebViewUtils.isWebViewAvailable()) {
445             return;
446         }
447 
448         final String X_FOO = "X-foo";
449         final String X_FOO_VALUE = "test";
450 
451         final String X_REFERER = "Referer";
452         final String X_REFERER_VALUE = "http://www.example.com/";
453         startWebServer(false);
454         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
455         HashMap<String, String> map = new HashMap<String, String>();
456         map.put(X_FOO, X_FOO_VALUE);
457         map.put(X_REFERER, X_REFERER_VALUE);
458         mOnUiThread.loadUrlAndWaitForCompletion(url, map);
459 
460         HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
461         for (Map.Entry<String,String> value : map.entrySet()) {
462             String header = value.getKey();
463             Header[] matchingHeaders = request.getHeaders(header);
464             assertEquals("header " + header + " not found", 1, matchingHeaders.length);
465             assertEquals(value.getValue(), matchingHeaders[0].getValue());
466         }
467     }
468 
469     @SuppressWarnings("deprecation")
470     @UiThreadTest
testGetVisibleTitleHeight()471     public void testGetVisibleTitleHeight() throws Exception {
472         if (!NullWebViewUtils.isWebViewAvailable()) {
473             return;
474         }
475 
476         startWebServer(false);
477         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
478         mOnUiThread.loadUrlAndWaitForCompletion(url);
479         assertEquals(0, mWebView.getVisibleTitleHeight());
480     }
481 
482     @UiThreadTest
testGetOriginalUrl()483     public void testGetOriginalUrl() throws Throwable {
484         if (!NullWebViewUtils.isWebViewAvailable()) {
485             return;
486         }
487 
488         startWebServer(false);
489         final String finalUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
490         final String redirectUrl =
491                 mWebServer.getRedirectingAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
492 
493         assertNull(mWebView.getUrl());
494         assertNull(mWebView.getOriginalUrl());
495 
496         // By default, WebView sends an intent to ask the system to
497         // handle loading a new URL. We set a WebViewClient as
498         // WebViewClient.shouldOverrideUrlLoading() returns false, so
499         // the WebView will load the new URL.
500         mWebView.setWebViewClient(new WaitForLoadedClient(mOnUiThread));
501         mOnUiThread.loadUrlAndWaitForCompletion(redirectUrl);
502 
503         assertEquals(finalUrl, mWebView.getUrl());
504         assertEquals(redirectUrl, mWebView.getOriginalUrl());
505     }
506 
testStopLoading()507     public void testStopLoading() throws Exception {
508         if (!NullWebViewUtils.isWebViewAvailable()) {
509             return;
510         }
511 
512         assertEquals(INITIAL_PROGRESS, mOnUiThread.getProgress());
513 
514         startWebServer(false);
515         String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.STOP_LOADING_URL);
516 
517         class JsInterface {
518             private boolean mPageLoaded;
519 
520             @JavascriptInterface
521             public synchronized void pageLoaded() {
522                 mPageLoaded = true;
523                 notify();
524             }
525             public synchronized boolean getPageLoaded() {
526                 return mPageLoaded;
527             }
528         }
529 
530         JsInterface jsInterface = new JsInterface();
531 
532         mOnUiThread.getSettings().setJavaScriptEnabled(true);
533         mOnUiThread.addJavascriptInterface(jsInterface, "javabridge");
534         mOnUiThread.loadUrl(url);
535         mOnUiThread.stopLoading();
536 
537         // We wait to see that the onload callback in the HTML is not fired.
538         synchronized (jsInterface) {
539             jsInterface.wait(3000);
540         }
541 
542         assertFalse(jsInterface.getPageLoaded());
543     }
544 
545     @UiThreadTest
testGoBackAndForward()546     public void testGoBackAndForward() throws Exception {
547         if (!NullWebViewUtils.isWebViewAvailable()) {
548             return;
549         }
550 
551         assertGoBackOrForwardBySteps(false, -1);
552         assertGoBackOrForwardBySteps(false, 1);
553 
554         startWebServer(false);
555         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
556         String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
557         String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);
558 
559         mOnUiThread.loadUrlAndWaitForCompletion(url1);
560         pollingCheckWebBackForwardList(url1, 0, 1);
561         assertGoBackOrForwardBySteps(false, -1);
562         assertGoBackOrForwardBySteps(false, 1);
563 
564         mOnUiThread.loadUrlAndWaitForCompletion(url2);
565         pollingCheckWebBackForwardList(url2, 1, 2);
566         assertGoBackOrForwardBySteps(true, -1);
567         assertGoBackOrForwardBySteps(false, 1);
568 
569         mOnUiThread.loadUrlAndWaitForCompletion(url3);
570         pollingCheckWebBackForwardList(url3, 2, 3);
571         assertGoBackOrForwardBySteps(true, -2);
572         assertGoBackOrForwardBySteps(false, 1);
573 
574         mWebView.goBack();
575         pollingCheckWebBackForwardList(url2, 1, 3);
576         assertGoBackOrForwardBySteps(true, -1);
577         assertGoBackOrForwardBySteps(true, 1);
578 
579         mWebView.goForward();
580         pollingCheckWebBackForwardList(url3, 2, 3);
581         assertGoBackOrForwardBySteps(true, -2);
582         assertGoBackOrForwardBySteps(false, 1);
583 
584         mWebView.goBackOrForward(-2);
585         pollingCheckWebBackForwardList(url1, 0, 3);
586         assertGoBackOrForwardBySteps(false, -1);
587         assertGoBackOrForwardBySteps(true, 2);
588 
589         mWebView.goBackOrForward(2);
590         pollingCheckWebBackForwardList(url3, 2, 3);
591         assertGoBackOrForwardBySteps(true, -2);
592         assertGoBackOrForwardBySteps(false, 1);
593     }
594 
testAddJavascriptInterface()595     public void testAddJavascriptInterface() throws Exception {
596         if (!NullWebViewUtils.isWebViewAvailable()) {
597             return;
598         }
599 
600         mOnUiThread.getSettings().setJavaScriptEnabled(true);
601         mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
602 
603         final class TestJavaScriptInterface {
604             private boolean mWasProvideResultCalled;
605             private String mResult;
606 
607             private synchronized String waitForResult() {
608                 while (!mWasProvideResultCalled) {
609                     try {
610                         wait(WebkitUtils.TEST_TIMEOUT_MS);
611                     } catch (InterruptedException e) {
612                         continue;
613                     }
614                     if (!mWasProvideResultCalled) {
615                         fail("Unexpected timeout");
616                     }
617                 }
618                 return mResult;
619             }
620 
621             public synchronized boolean wasProvideResultCalled() {
622                 return mWasProvideResultCalled;
623             }
624 
625             @JavascriptInterface
626             public synchronized void provideResult(String result) {
627                 mWasProvideResultCalled = true;
628                 mResult = result;
629                 notify();
630             }
631         }
632 
633         final TestJavaScriptInterface obj = new TestJavaScriptInterface();
634         mOnUiThread.addJavascriptInterface(obj, "interface");
635         assertFalse(obj.wasProvideResultCalled());
636 
637         startWebServer(false);
638         String url = mWebServer.getAssetUrl(TestHtmlConstants.ADD_JAVA_SCRIPT_INTERFACE_URL);
639         mOnUiThread.loadUrlAndWaitForCompletion(url);
640         assertEquals("Original title", obj.waitForResult());
641 
642         // Verify that only methods annotated with @JavascriptInterface are exposed
643         // on the JavaScript interface object.
644         assertEquals("\"function\"",
645                 mOnUiThread.evaluateJavascriptSync("typeof interface.provideResult"));
646 
647         assertEquals("\"undefined\"",
648                 mOnUiThread.evaluateJavascriptSync("typeof interface.wasProvideResultCalled"));
649 
650         assertEquals("\"undefined\"",
651                 mOnUiThread.evaluateJavascriptSync("typeof interface.getClass"));
652     }
653 
testAddJavascriptInterfaceNullObject()654     public void testAddJavascriptInterfaceNullObject() throws Exception {
655         if (!NullWebViewUtils.isWebViewAvailable()) {
656             return;
657         }
658 
659         mOnUiThread.getSettings().setJavaScriptEnabled(true);
660         String setTitleToPropertyTypeHtml = "<html><head></head>" +
661                 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>";
662 
663         // Test that the property is initially undefined.
664         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
665                 "text/html", null);
666         assertEquals("undefined", mOnUiThread.getTitle());
667 
668         // Test that adding a null object has no effect.
669         mOnUiThread.addJavascriptInterface(null, "injectedObject");
670         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
671                 "text/html", null);
672         assertEquals("undefined", mOnUiThread.getTitle());
673 
674         // Test that adding an object gives an object type.
675         final Object obj = new Object();
676         mOnUiThread.addJavascriptInterface(obj, "injectedObject");
677         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
678                 "text/html", null);
679         assertEquals("object", mOnUiThread.getTitle());
680 
681         // Test that trying to replace with a null object has no effect.
682         mOnUiThread.addJavascriptInterface(null, "injectedObject");
683         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
684                 "text/html", null);
685         assertEquals("object", mOnUiThread.getTitle());
686     }
687 
testRemoveJavascriptInterface()688     public void testRemoveJavascriptInterface() throws Exception {
689         if (!NullWebViewUtils.isWebViewAvailable()) {
690             return;
691         }
692 
693         mOnUiThread.getSettings().setJavaScriptEnabled(true);
694         String setTitleToPropertyTypeHtml = "<html><head></head>" +
695                 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>";
696 
697         // Test that adding an object gives an object type.
698         mOnUiThread.addJavascriptInterface(new Object(), "injectedObject");
699         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
700                 "text/html", null);
701         assertEquals("object", mOnUiThread.getTitle());
702 
703         // Test that reloading the page after removing the object leaves the property undefined.
704         mOnUiThread.removeJavascriptInterface("injectedObject");
705         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
706                 "text/html", null);
707         assertEquals("undefined", mOnUiThread.getTitle());
708     }
709 
testUseRemovedJavascriptInterface()710     public void testUseRemovedJavascriptInterface() throws Throwable {
711         if (!NullWebViewUtils.isWebViewAvailable()) {
712             return;
713         }
714 
715         class RemovedObject {
716             @Override
717             @JavascriptInterface
718             public String toString() {
719                 return "removedObject";
720             }
721 
722             @JavascriptInterface
723             public void remove() throws Throwable {
724                 mOnUiThread.removeJavascriptInterface("removedObject");
725                 System.gc();
726             }
727         }
728         class ResultObject {
729             private String mResult;
730             private boolean mIsResultAvailable;
731 
732             @JavascriptInterface
733             public synchronized void setResult(String result) {
734                 mResult = result;
735                 mIsResultAvailable = true;
736                 notify();
737             }
738             public synchronized String getResult() {
739                 while (!mIsResultAvailable) {
740                     try {
741                         wait();
742                     } catch (InterruptedException e) {
743                     }
744                 }
745                 return mResult;
746             }
747         }
748         final ResultObject resultObject = new ResultObject();
749 
750         // Test that an object is still usable if removed while the page is in use, even if we have
751         // no external references to it.
752         mOnUiThread.getSettings().setJavaScriptEnabled(true);
753         mOnUiThread.addJavascriptInterface(new RemovedObject(), "removedObject");
754         mOnUiThread.addJavascriptInterface(resultObject, "resultObject");
755         mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" +
756                 "<body onload=\"window.removedObject.remove();" +
757                 "resultObject.setResult(removedObject.toString());\"></body></html>",
758                 "text/html", null);
759         assertEquals("removedObject", resultObject.getResult());
760     }
761 
testAddJavascriptInterfaceExceptions()762     public void testAddJavascriptInterfaceExceptions() throws Exception {
763         if (!NullWebViewUtils.isWebViewAvailable()) {
764             return;
765         }
766         WebSettings settings = mOnUiThread.getSettings();
767         settings.setJavaScriptEnabled(true);
768         settings.setJavaScriptCanOpenWindowsAutomatically(true);
769 
770         final AtomicBoolean mJsInterfaceWasCalled = new AtomicBoolean(false) {
771             @JavascriptInterface
772             public synchronized void call() {
773                 set(true);
774                 // The main purpose of this test is to ensure an exception here does not
775                 // crash the implementation.
776                 throw new RuntimeException("Javascript Interface exception");
777             }
778         };
779 
780         mOnUiThread.addJavascriptInterface(mJsInterfaceWasCalled, "interface");
781 
782         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
783 
784         assertFalse(mJsInterfaceWasCalled.get());
785 
786         assertEquals("\"pass\"", mOnUiThread.evaluateJavascriptSync(
787                 "try {interface.call(); 'fail'; } catch (exception) { 'pass'; } "));
788         assertTrue(mJsInterfaceWasCalled.get());
789     }
790 
testJavascriptInterfaceCustomPropertiesClearedOnReload()791     public void testJavascriptInterfaceCustomPropertiesClearedOnReload() throws Exception {
792         if (!NullWebViewUtils.isWebViewAvailable()) {
793             return;
794         }
795 
796         mOnUiThread.getSettings().setJavaScriptEnabled(true);
797 
798         mOnUiThread.addJavascriptInterface(new Object(), "interface");
799         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
800 
801         assertEquals("42", mOnUiThread.evaluateJavascriptSync("interface.custom_property = 42"));
802 
803         assertEquals("true", mOnUiThread.evaluateJavascriptSync("'custom_property' in interface"));
804 
805         mOnUiThread.reloadAndWaitForCompletion();
806 
807         assertEquals("false", mOnUiThread.evaluateJavascriptSync("'custom_property' in interface"));
808     }
809 
810     @FlakyTest(bugId = 171702662)
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 TestJavaScriptInterface {
821             @JavascriptInterface
822             public int test() {
823                 return 42;
824             }
825         }
826         final TestJavaScriptInterface obj = new TestJavaScriptInterface();
827 
828         final WebView childWebView = mOnUiThread.createWebView();
829         WebViewOnUiThread childOnUiThread = new WebViewOnUiThread(childWebView);
830         childOnUiThread.getSettings().setJavaScriptEnabled(true);
831         childOnUiThread.addJavascriptInterface(obj, "interface");
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("'interface' in window"));
857 
858         assertEquals("The injected object should be functional", "42",
859                 childOnUiThread.evaluateJavascriptSync("interface.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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS, 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         assertEquals(mOnUiThread.getScrollY(), previousScrollY);
1447 
1448         mOnUiThread.findNext(true);
1449         waitForScrollingComplete(previousScrollY);
1450         assertEquals(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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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(10000, 10000);
1698 
1699         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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(WebkitUtils.TEST_TIMEOUT_MS) {
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 
1798         startWebServer(false);
1799         final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.LARGE_IMG_URL);
1800         mOnUiThread.loadDataAndWaitForCompletion(
1801                 "<html><head><title>Title</title><style type='text/css'>"
1802                 + "%23imgElement { -webkit-transform: translate3d(0,0,1); }"
1803                 + "%23imgElement.finish { -webkit-transform: translate3d(0,0,0);"
1804                 + " -webkit-transition-duration: 1ms; }</style>"
1805                 + "<script type='text/javascript'>function imgLoad() {"
1806                 + "imgElement = document.getElementById('imgElement');"
1807                 + "imgElement.addEventListener('webkitTransitionEnd',"
1808                 + "function(e) { imageLoaded.loaded(); });"
1809                 + "imgElement.className = 'finish';}</script>"
1810                 + "</head><body><img id='imgElement' src='" + imgUrl
1811                 + "' width='100%' height='100%' onLoad='imgLoad()'/>"
1812                 + "</body></html>", "text/html", null);
1813         WebkitUtils.waitForFuture(imageLoaded.future());
1814         getInstrumentation().waitForIdleSync();
1815 
1816         final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper());
1817         final Message msg = new Message();
1818         msg.setTarget(handler);
1819 
1820         // touch the image
1821         handler.reset();
1822         int[] location = mOnUiThread.getLocationOnScreen();
1823         int middleX = location[0] + mOnUiThread.getWebView().getWidth() / 2;
1824         int middleY = location[1] + mOnUiThread.getWebView().getHeight() / 2;
1825 
1826         long time = SystemClock.uptimeMillis();
1827         getInstrumentation().sendPointerSync(
1828                 MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN,
1829                         middleX, middleY, 0));
1830         getInstrumentation().waitForIdleSync();
1831         mOnUiThread.requestImageRef(msg);
1832         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
1833             @Override
1834             protected boolean check() {
1835                 boolean done = false;
1836                 if (handler.hasCalledHandleMessage()) {
1837                     if (handler.mResultUrl != null) {
1838                         done = true;
1839                     } else {
1840                         handler.reset();
1841                         Message newMsg = new Message();
1842                         newMsg.setTarget(handler);
1843                         mOnUiThread.requestImageRef(newMsg);
1844                     }
1845                 }
1846                 return done;
1847             }
1848         }.run();
1849         assertEquals(imgUrl, handler.mResultUrl);
1850     }
1851 
1852     @UiThreadTest
testDebugDump()1853     public void testDebugDump() {
1854         if (!NullWebViewUtils.isWebViewAvailable()) {
1855             return;
1856         }
1857         mWebView.debugDump();
1858     }
1859 
testGetHitTestResult()1860     public void testGetHitTestResult() throws Throwable {
1861         if (!NullWebViewUtils.isWebViewAvailable()) {
1862             return;
1863         }
1864         final String anchor = "<p><a href=\"" + TestHtmlConstants.EXT_WEB_URL1
1865                 + "\">normal anchor</a></p>";
1866         final String blankAnchor = "<p><a href=\"\">blank anchor</a></p>";
1867         final String form = "<p><form><input type=\"text\" name=\"Test\"><br>"
1868                 + "<input type=\"submit\" value=\"Submit\"></form></p>";
1869         String phoneNo = "3106984000";
1870         final String tel = "<p><a href=\"tel:" + phoneNo + "\">Phone</a></p>";
1871         String email = "test@gmail.com";
1872         final String mailto = "<p><a href=\"mailto:" + email + "\">Email</a></p>";
1873         String location = "shanghai";
1874         final String geo = "<p><a href=\"geo:0,0?q=" + location + "\">Location</a></p>";
1875 
1876         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("fake://home",
1877                 "<html><body>" + anchor + blankAnchor + form + tel + mailto +
1878                 geo + "</body></html>", "text/html", "UTF-8", null);
1879         getInstrumentation().waitForIdleSync();
1880 
1881         // anchor
1882         moveFocusDown();
1883         HitTestResult hitTestResult = mOnUiThread.getHitTestResult();
1884         assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType());
1885         assertEquals(TestHtmlConstants.EXT_WEB_URL1, hitTestResult.getExtra());
1886 
1887         // blank anchor
1888         moveFocusDown();
1889         hitTestResult = mOnUiThread.getHitTestResult();
1890         assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType());
1891         assertEquals("fake://home", hitTestResult.getExtra());
1892 
1893         // text field
1894         moveFocusDown();
1895         hitTestResult = mOnUiThread.getHitTestResult();
1896         assertEquals(HitTestResult.EDIT_TEXT_TYPE, hitTestResult.getType());
1897         assertNull(hitTestResult.getExtra());
1898 
1899         // submit button
1900         moveFocusDown();
1901         hitTestResult = mOnUiThread.getHitTestResult();
1902         assertEquals(HitTestResult.UNKNOWN_TYPE, hitTestResult.getType());
1903         assertNull(hitTestResult.getExtra());
1904 
1905         // phone number
1906         moveFocusDown();
1907         hitTestResult = mOnUiThread.getHitTestResult();
1908         assertEquals(HitTestResult.PHONE_TYPE, hitTestResult.getType());
1909         assertEquals(phoneNo, hitTestResult.getExtra());
1910 
1911         // email
1912         moveFocusDown();
1913         hitTestResult = mOnUiThread.getHitTestResult();
1914         assertEquals(HitTestResult.EMAIL_TYPE, hitTestResult.getType());
1915         assertEquals(email, hitTestResult.getExtra());
1916 
1917         // geo address
1918         moveFocusDown();
1919         hitTestResult = mOnUiThread.getHitTestResult();
1920         assertEquals(HitTestResult.GEO_TYPE, hitTestResult.getType());
1921         assertEquals(location, hitTestResult.getExtra());
1922     }
1923 
testSetInitialScale()1924     public void testSetInitialScale() throws Throwable {
1925         if (!NullWebViewUtils.isWebViewAvailable()) {
1926             return;
1927         }
1928         final String p = "<p style=\"height:1000px;width:1000px\">Test setInitialScale.</p>";
1929         final float defaultScale =
1930             getActivity().getResources().getDisplayMetrics().density;
1931 
1932         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1933                 + "</body></html>", "text/html", null);
1934 
1935         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
1936             @Override
1937             protected boolean check() {
1938                 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
1939             }
1940         }.run();
1941 
1942         mOnUiThread.setInitialScale(0);
1943         // modify content to fool WebKit into re-loading
1944         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1945                 + "2" + "</body></html>", "text/html", null);
1946 
1947         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
1948             @Override
1949             protected boolean check() {
1950                 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
1951             }
1952         }.run();
1953 
1954         mOnUiThread.setInitialScale(50);
1955         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1956                 + "3" + "</body></html>", "text/html", null);
1957 
1958         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
1959             @Override
1960             protected boolean check() {
1961                 return Math.abs(0.5 - mOnUiThread.getScale()) < .01f;
1962             }
1963         }.run();
1964 
1965         mOnUiThread.setInitialScale(0);
1966         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
1967                 + "4" + "</body></html>", "text/html", null);
1968 
1969         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
1970             @Override
1971             protected boolean check() {
1972                 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
1973             }
1974         }.run();
1975     }
1976 
1977     @UiThreadTest
testClearHistory()1978     public void testClearHistory() throws Exception {
1979         if (!NullWebViewUtils.isWebViewAvailable()) {
1980             return;
1981         }
1982         startWebServer(false);
1983         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
1984         String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
1985         String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);
1986 
1987         mOnUiThread.loadUrlAndWaitForCompletion(url1);
1988         pollingCheckWebBackForwardList(url1, 0, 1);
1989 
1990         mOnUiThread.loadUrlAndWaitForCompletion(url2);
1991         pollingCheckWebBackForwardList(url2, 1, 2);
1992 
1993         mOnUiThread.loadUrlAndWaitForCompletion(url3);
1994         pollingCheckWebBackForwardList(url3, 2, 3);
1995 
1996         mWebView.clearHistory();
1997 
1998         // only current URL is left after clearing
1999         pollingCheckWebBackForwardList(url3, 0, 1);
2000     }
2001 
2002     @UiThreadTest
testSaveAndRestoreState()2003     public void testSaveAndRestoreState() throws Throwable {
2004         if (!NullWebViewUtils.isWebViewAvailable()) {
2005             return;
2006         }
2007         assertNull("Should return null when there's nothing to save",
2008                 mWebView.saveState(new Bundle()));
2009 
2010         startWebServer(false);
2011         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
2012         String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
2013         String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);
2014 
2015         // make a history list
2016         mOnUiThread.loadUrlAndWaitForCompletion(url1);
2017         pollingCheckWebBackForwardList(url1, 0, 1);
2018         mOnUiThread.loadUrlAndWaitForCompletion(url2);
2019         pollingCheckWebBackForwardList(url2, 1, 2);
2020         mOnUiThread.loadUrlAndWaitForCompletion(url3);
2021         pollingCheckWebBackForwardList(url3, 2, 3);
2022 
2023         // save the list
2024         Bundle bundle = new Bundle();
2025         WebBackForwardList saveList = mWebView.saveState(bundle);
2026         assertNotNull(saveList);
2027         assertEquals(3, saveList.getSize());
2028         assertEquals(2, saveList.getCurrentIndex());
2029         assertEquals(url1, saveList.getItemAtIndex(0).getUrl());
2030         assertEquals(url2, saveList.getItemAtIndex(1).getUrl());
2031         assertEquals(url3, saveList.getItemAtIndex(2).getUrl());
2032 
2033         // change the content to a new "blank" web view without history
2034         final WebView newWebView = new WebView(getActivity());
2035 
2036         WebBackForwardList copyListBeforeRestore = newWebView.copyBackForwardList();
2037         assertNotNull(copyListBeforeRestore);
2038         assertEquals(0, copyListBeforeRestore.getSize());
2039 
2040         // restore the list
2041         final WebBackForwardList restoreList = newWebView.restoreState(bundle);
2042         assertNotNull(restoreList);
2043         assertEquals(3, restoreList.getSize());
2044         assertEquals(2, saveList.getCurrentIndex());
2045 
2046         // wait for the list items to get inflated
2047         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
2048             @Override
2049             protected boolean check() {
2050                 return restoreList.getItemAtIndex(0).getUrl() != null &&
2051                        restoreList.getItemAtIndex(1).getUrl() != null &&
2052                        restoreList.getItemAtIndex(2).getUrl() != null;
2053             }
2054         }.run();
2055         assertEquals(url1, restoreList.getItemAtIndex(0).getUrl());
2056         assertEquals(url2, restoreList.getItemAtIndex(1).getUrl());
2057         assertEquals(url3, restoreList.getItemAtIndex(2).getUrl());
2058 
2059         WebBackForwardList copyListAfterRestore = newWebView.copyBackForwardList();
2060         assertNotNull(copyListAfterRestore);
2061         assertEquals(3, copyListAfterRestore.getSize());
2062         assertEquals(2, copyListAfterRestore.getCurrentIndex());
2063         assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl());
2064         assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl());
2065         assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl());
2066 
2067         newWebView.destroy();
2068     }
2069 
testRequestChildRectangleOnScreen()2070     public void testRequestChildRectangleOnScreen() throws Throwable {
2071         if (!NullWebViewUtils.isWebViewAvailable()) {
2072             return;
2073         }
2074 
2075         // It is needed to make test pass on some devices.
2076         mOnUiThread.setLayoutToMatchParent();
2077 
2078         DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
2079         final int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);
2080         String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\">&nbsp;</p>";
2081         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
2082                 + "</body></html>", "text/html", null);
2083         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
2084             @Override
2085             protected boolean check() {
2086                 return mOnUiThread.getContentHeight() >= dimension;
2087             }
2088         }.run();
2089 
2090         int origX = mOnUiThread.getScrollX();
2091         int origY = mOnUiThread.getScrollY();
2092 
2093         int half = dimension / 2;
2094         Rect rect = new Rect(half, half, half + 1, half + 1);
2095         assertTrue(mOnUiThread.requestChildRectangleOnScreen(mWebView, rect, true));
2096         assertThat(mOnUiThread.getScrollX(), greaterThan(origX));
2097         assertThat(mOnUiThread.getScrollY(), greaterThan(origY));
2098     }
2099 
testSetDownloadListener()2100     public void testSetDownloadListener() throws Throwable {
2101         if (!NullWebViewUtils.isWebViewAvailable()) {
2102             return;
2103         }
2104 
2105         final SettableFuture<Void> downloadStartFuture = SettableFuture.create();
2106         final class MyDownloadListener implements DownloadListener {
2107             public String url;
2108             public String mimeType;
2109             public long contentLength;
2110             public String contentDisposition;
2111 
2112             @Override
2113             public void onDownloadStart(String url, String userAgent, String contentDisposition,
2114                     String mimetype, long contentLength) {
2115                 this.url = url;
2116                 this.mimeType = mimetype;
2117                 this.contentLength = contentLength;
2118                 this.contentDisposition = contentDisposition;
2119                 downloadStartFuture.set(null);
2120             }
2121         }
2122 
2123         final String mimeType = "application/octet-stream";
2124         final int length = 100;
2125         final MyDownloadListener listener = new MyDownloadListener();
2126 
2127         startWebServer(false);
2128         final String url = mWebServer.getBinaryUrl(mimeType, length);
2129 
2130         // By default, WebView sends an intent to ask the system to
2131         // handle loading a new URL. We set WebViewClient as
2132         // WebViewClient.shouldOverrideUrlLoading() returns false, so
2133         // the WebView will load the new URL.
2134         mOnUiThread.setDownloadListener(listener);
2135         mOnUiThread.getSettings().setJavaScriptEnabled(true);
2136         mOnUiThread.loadDataAndWaitForCompletion(
2137                 "<html><body onload=\"window.location = \'" + url + "\'\"></body></html>",
2138                 "text/html", null);
2139         // Wait for layout to complete before setting focus.
2140         getInstrumentation().waitForIdleSync();
2141 
2142         WebkitUtils.waitForFuture(downloadStartFuture);
2143         assertEquals(url, listener.url);
2144         assertTrue(listener.contentDisposition.contains("test.bin"));
2145         assertEquals(length, listener.contentLength);
2146         assertEquals(mimeType, listener.mimeType);
2147     }
2148 
2149     @UiThreadTest
testSetLayoutParams()2150     public void testSetLayoutParams() {
2151         if (!NullWebViewUtils.isWebViewAvailable()) {
2152             return;
2153         }
2154         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(600, 800);
2155         mWebView.setLayoutParams(params);
2156         assertSame(params, mWebView.getLayoutParams());
2157     }
2158 
2159     @UiThreadTest
testSetMapTrackballToArrowKeys()2160     public void testSetMapTrackballToArrowKeys() {
2161         if (!NullWebViewUtils.isWebViewAvailable()) {
2162             return;
2163         }
2164         mWebView.setMapTrackballToArrowKeys(true);
2165     }
2166 
testSetNetworkAvailable()2167     public void testSetNetworkAvailable() throws Exception {
2168         if (!NullWebViewUtils.isWebViewAvailable()) {
2169             return;
2170         }
2171         WebSettings settings = mOnUiThread.getSettings();
2172         settings.setJavaScriptEnabled(true);
2173         startWebServer(false);
2174 
2175         String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL);
2176         mOnUiThread.loadUrlAndWaitForCompletion(url);
2177         assertEquals("ONLINE", mOnUiThread.getTitle());
2178 
2179         mOnUiThread.setNetworkAvailable(false);
2180 
2181         // Wait for the DOM to receive notification of the network state change.
2182         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
2183             @Override
2184             protected boolean check() {
2185                 return mOnUiThread.getTitle().equals("OFFLINE");
2186             }
2187         }.run();
2188 
2189         mOnUiThread.setNetworkAvailable(true);
2190 
2191         // Wait for the DOM to receive notification of the network state change.
2192         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
2193             @Override
2194             protected boolean check() {
2195                 return mOnUiThread.getTitle().equals("ONLINE");
2196             }
2197         }.run();
2198     }
2199 
testSetWebChromeClient()2200     public void testSetWebChromeClient() throws Throwable {
2201         if (!NullWebViewUtils.isWebViewAvailable()) {
2202             return;
2203         }
2204 
2205         final SettableFuture<Void> future = SettableFuture.create();
2206         mOnUiThread.setWebChromeClient(new WaitForProgressClient(mOnUiThread) {
2207             @Override
2208             public void onProgressChanged(WebView view, int newProgress) {
2209                 super.onProgressChanged(view, newProgress);
2210                 future.set(null);
2211             }
2212         });
2213         getInstrumentation().waitForIdleSync();
2214         assertFalse(future.isDone());
2215 
2216         startWebServer(false);
2217         final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
2218         mOnUiThread.loadUrlAndWaitForCompletion(url);
2219         getInstrumentation().waitForIdleSync();
2220 
2221         WebkitUtils.waitForFuture(future);
2222     }
2223 
testPauseResumeTimers()2224     public void testPauseResumeTimers() throws Throwable {
2225         if (!NullWebViewUtils.isWebViewAvailable()) {
2226             return;
2227         }
2228         class Monitor {
2229             private boolean mIsUpdated;
2230 
2231             @JavascriptInterface
2232             public synchronized void update() {
2233                 mIsUpdated  = true;
2234                 notify();
2235             }
2236             public synchronized boolean waitForUpdate() {
2237                 while (!mIsUpdated) {
2238                     try {
2239                         // This is slightly flaky, as we can't guarantee that
2240                         // this is a sufficient time limit, but there's no way
2241                         // around this.
2242                         wait(1000);
2243                         if (!mIsUpdated) {
2244                             return false;
2245                         }
2246                     } catch (InterruptedException e) {
2247                     }
2248                 }
2249                 mIsUpdated = false;
2250                 return true;
2251             }
2252         };
2253         final Monitor monitor = new Monitor();
2254         final String updateMonitorHtml = "<html>" +
2255                 "<body onload=\"monitor.update();\"></body></html>";
2256 
2257         // Test that JavaScript is executed even with timers paused.
2258         WebkitUtils.onMainThreadSync(() -> {
2259             mWebView.getSettings().setJavaScriptEnabled(true);
2260             mWebView.addJavascriptInterface(monitor, "monitor");
2261             mWebView.pauseTimers();
2262             mOnUiThread.loadDataAndWaitForCompletion(updateMonitorHtml,
2263                     "text/html", null);
2264         });
2265         assertTrue(monitor.waitForUpdate());
2266 
2267         // Start a timer and test that it does not fire.
2268         mOnUiThread.loadDataAndWaitForCompletion(
2269                 "<html><body onload='setTimeout(function(){monitor.update();},100)'>" +
2270                 "</body></html>", "text/html", null);
2271         assertFalse(monitor.waitForUpdate());
2272 
2273         // Resume timers and test that the timer fires.
2274         mOnUiThread.resumeTimers();
2275         assertTrue(monitor.waitForUpdate());
2276     }
2277 
2278     // verify query parameters can be passed correctly to android asset files
testAndroidAssetQueryParam()2279     public void testAndroidAssetQueryParam() {
2280         if (!NullWebViewUtils.isWebViewAvailable()) {
2281             return;
2282         }
2283 
2284         WebSettings settings = mOnUiThread.getSettings();
2285         settings.setJavaScriptEnabled(true);
2286         // test passing a parameter
2287         String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.PARAM_ASSET_URL+"?val=SUCCESS");
2288         mOnUiThread.loadUrlAndWaitForCompletion(fileUrl);
2289         assertEquals("SUCCESS", mOnUiThread.getTitle());
2290     }
2291 
2292     // verify anchors work correctly for android asset files
testAndroidAssetAnchor()2293     public void testAndroidAssetAnchor() {
2294         if (!NullWebViewUtils.isWebViewAvailable()) {
2295             return;
2296         }
2297 
2298         WebSettings settings = mOnUiThread.getSettings();
2299         settings.setJavaScriptEnabled(true);
2300         // test using an anchor
2301         String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.ANCHOR_ASSET_URL+"#anchor");
2302         mOnUiThread.loadUrlAndWaitForCompletion(fileUrl);
2303         assertEquals("anchor", mOnUiThread.getTitle());
2304     }
2305 
testEvaluateJavascript()2306     public void testEvaluateJavascript() {
2307         if (!NullWebViewUtils.isWebViewAvailable()) {
2308             return;
2309         }
2310         mOnUiThread.getSettings().setJavaScriptEnabled(true);
2311         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
2312 
2313         assertEquals("2", mOnUiThread.evaluateJavascriptSync("1+1"));
2314 
2315         assertEquals("9", mOnUiThread.evaluateJavascriptSync("1+1; 4+5"));
2316 
2317         final String EXPECTED_TITLE = "test";
2318         mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null);
2319         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
2320             @Override
2321             protected boolean check() {
2322                 return mOnUiThread.getTitle().equals(EXPECTED_TITLE);
2323             }
2324         }.run();
2325     }
2326 
2327     // Verify Print feature can create a PDF file with a correct preamble.
testPrinting()2328     public void testPrinting() throws Throwable {
2329         if (!NullWebViewUtils.isWebViewAvailable()) {
2330             return;
2331         }
2332         mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" +
2333                 "<body>foo</body></html>",
2334                 "text/html", null);
2335         final PrintDocumentAdapter adapter =  mOnUiThread.createPrintDocumentAdapter();
2336         printDocumentStart(adapter);
2337         PrintAttributes attributes = new PrintAttributes.Builder()
2338                 .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
2339                 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300))
2340                 .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
2341                 .build();
2342         final WebViewCtsActivity activity = getActivity();
2343         final File file = activity.getFileStreamPath(PRINTER_TEST_FILE);
2344         final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file,
2345                 ParcelFileDescriptor.parseMode("w"));
2346         final SettableFuture<Void> result = SettableFuture.create();
2347         printDocumentLayout(adapter, null, attributes,
2348                 new LayoutResultCallback() {
2349                     // Called on UI thread
2350                     @Override
2351                     public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
2352                         PageRange[] pageRanges = new PageRange[] {PageRange.ALL_PAGES};
2353                         savePrintedPage(adapter, descriptor, pageRanges, result);
2354                     }
2355                 });
2356         try {
2357             WebkitUtils.waitForFuture(result);
2358             assertThat(file.length(), greaterThan(0L));
2359             FileInputStream in = new FileInputStream(file);
2360             byte[] b = new byte[PDF_PREAMBLE.length()];
2361             in.read(b);
2362             String preamble = new String(b);
2363             assertEquals(PDF_PREAMBLE, preamble);
2364         } finally {
2365             // close the descriptor, if not closed already.
2366             descriptor.close();
2367             file.delete();
2368         }
2369     }
2370 
2371     // Verify Print feature can create a PDF file with correct number of pages.
testPrintingPagesCount()2372     public void testPrintingPagesCount() throws Throwable {
2373         if (!NullWebViewUtils.isWebViewAvailable()) {
2374             return;
2375         }
2376         String content = "<html><head></head><body>";
2377         for (int i = 0; i < 500; ++i) {
2378             content += "<br />abcdefghijk<br />";
2379         }
2380         content += "</body></html>";
2381         mOnUiThread.loadDataAndWaitForCompletion(content, "text/html", null);
2382         final PrintDocumentAdapter adapter =  mOnUiThread.createPrintDocumentAdapter();
2383         printDocumentStart(adapter);
2384         PrintAttributes attributes = new PrintAttributes.Builder()
2385                 .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
2386                 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300))
2387                 .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
2388                 .build();
2389         final WebViewCtsActivity activity = getActivity();
2390         final File file = activity.getFileStreamPath(PRINTER_TEST_FILE);
2391         final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file,
2392                 ParcelFileDescriptor.parseMode("w"));
2393         final SettableFuture<Void> result = SettableFuture.create();
2394         printDocumentLayout(adapter, null, attributes,
2395                 new LayoutResultCallback() {
2396                     // Called on UI thread
2397                     @Override
2398                     public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
2399                         PageRange[] pageRanges = new PageRange[] {
2400                             new PageRange(1, 1), new PageRange(4, 7)
2401                         };
2402                         savePrintedPage(adapter, descriptor, pageRanges, result);
2403                     }
2404                 });
2405         try {
2406             WebkitUtils.waitForFuture(result);
2407             assertThat(file.length(), greaterThan(0L));
2408             PdfRenderer renderer = new PdfRenderer(
2409                 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
2410             assertEquals(5, renderer.getPageCount());
2411         } finally {
2412             descriptor.close();
2413             file.delete();
2414         }
2415     }
2416 
2417     /**
2418      * This should remain functionally equivalent to
2419      * androidx.webkit.WebViewCompatTest#testVisualStateCallbackCalled. Modifications to this test
2420      * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
2421      */
testVisualStateCallbackCalled()2422     public void testVisualStateCallbackCalled() throws Exception {
2423         // Check that the visual state callback is called correctly.
2424         if (!NullWebViewUtils.isWebViewAvailable()) {
2425             return;
2426         }
2427 
2428         final long kRequest = 100;
2429 
2430         mOnUiThread.loadUrl("about:blank");
2431 
2432         final SettableFuture<Long> visualStateFuture = SettableFuture.create();
2433         mOnUiThread.postVisualStateCallback(kRequest, new VisualStateCallback() {
2434             public void onComplete(long requestId) {
2435                 visualStateFuture.set(requestId);
2436             }
2437         });
2438 
2439         assertEquals(kRequest, (long) WebkitUtils.waitForFuture(visualStateFuture));
2440     }
2441 
setSafeBrowsingAllowlistSync(List<String> allowlist)2442     private static boolean setSafeBrowsingAllowlistSync(List<String> allowlist) {
2443         final SettableFuture<Boolean> safeBrowsingAllowlistFuture = SettableFuture.create();
2444         WebView.setSafeBrowsingWhitelist(allowlist, new ValueCallback<Boolean>() {
2445             @Override
2446             public void onReceiveValue(Boolean success) {
2447                 safeBrowsingAllowlistFuture.set(success);
2448             }
2449         });
2450         return WebkitUtils.waitForFuture(safeBrowsingAllowlistFuture);
2451     }
2452 
2453     /**
2454      * This should remain functionally equivalent to
2455      * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingAllowlistWithMalformedList.
2456      * Modifications to this test should be reflected in that test as necessary. See
2457      * http://go/modifying-webview-cts.
2458      */
testSetSafeBrowsingAllowlistWithMalformedList()2459     public void testSetSafeBrowsingAllowlistWithMalformedList() throws Exception {
2460         if (!NullWebViewUtils.isWebViewAvailable()) {
2461             return;
2462         }
2463 
2464         List allowlist = new ArrayList<String>();
2465         // Protocols are not supported in the allowlist
2466         allowlist.add("http://google.com");
2467         assertFalse("Malformed list entry should fail", setSafeBrowsingAllowlistSync(allowlist));
2468     }
2469 
2470     /**
2471      * This should remain functionally equivalent to
2472      * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingAllowlistWithValidList. Modifications
2473      * to this test should be reflected in that test as necessary. See
2474      * http://go/modifying-webview-cts.
2475      */
testSetSafeBrowsingAllowlistWithValidList()2476     public void testSetSafeBrowsingAllowlistWithValidList() throws Exception {
2477         if (!NullWebViewUtils.isWebViewAvailable()) {
2478             return;
2479         }
2480 
2481         List allowlist = new ArrayList<String>();
2482         allowlist.add("safe-browsing");
2483         assertTrue("Valid allowlist should be successful", setSafeBrowsingAllowlistSync(allowlist));
2484 
2485         final SettableFuture<Void> pageFinishedFuture = SettableFuture.create();
2486         mOnUiThread.setWebViewClient(new WebViewClient() {
2487             @Override
2488             public void onPageFinished(WebView view, String url) {
2489                 pageFinishedFuture.set(null);
2490             }
2491 
2492             @Override
2493             public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType,
2494                     SafeBrowsingResponse callback) {
2495                 pageFinishedFuture.setException(new IllegalStateException(
2496                         "Should not invoke onSafeBrowsingHit for " + request.getUrl()));
2497             }
2498         });
2499 
2500         mOnUiThread.loadUrl("chrome://safe-browsing/match?type=malware");
2501 
2502         // Wait until page load has completed
2503         WebkitUtils.waitForFuture(pageFinishedFuture);
2504     }
2505 
2506     /**
2507      * This should remain functionally equivalent to
2508      * androidx.webkit.WebViewCompatTest#testGetWebViewClient. Modifications to this test should be
2509      * reflected in that test as necessary. See http://go/modifying-webview-cts.
2510      */
2511     @UiThreadTest
testGetWebViewClient()2512     public void testGetWebViewClient() throws Exception {
2513         if (!NullWebViewUtils.isWebViewAvailable()) {
2514             return;
2515         }
2516 
2517         // getWebViewClient should return a default WebViewClient if it hasn't been set yet
2518         WebView webView = new WebView(getActivity());
2519         WebViewClient client = webView.getWebViewClient();
2520         assertNotNull(client);
2521         assertTrue(client instanceof WebViewClient);
2522 
2523         // getWebViewClient should return the client after it has been set
2524         WebViewClient client2 = new WebViewClient();
2525         assertNotSame(client, client2);
2526         webView.setWebViewClient(client2);
2527         assertSame(client2, webView.getWebViewClient());
2528         webView.destroy();
2529     }
2530 
2531     /**
2532      * This should remain functionally equivalent to
2533      * androidx.webkit.WebViewCompatTest#testGetWebChromeClient. Modifications to this test should
2534      * be reflected in that test as necessary. See http://go/modifying-webview-cts.
2535      */
2536     @UiThreadTest
testGetWebChromeClient()2537     public void testGetWebChromeClient() throws Exception {
2538         if (!NullWebViewUtils.isWebViewAvailable()) {
2539             return;
2540         }
2541 
2542         // getWebChromeClient should return null if the client hasn't been set yet
2543         WebView webView = new WebView(getActivity());
2544         WebChromeClient client = webView.getWebChromeClient();
2545         assertNull(client);
2546 
2547         // getWebChromeClient should return the client after it has been set
2548         WebChromeClient client2 = new WebChromeClient();
2549         assertNotSame(client, client2);
2550         webView.setWebChromeClient(client2);
2551         assertSame(client2, webView.getWebChromeClient());
2552         webView.destroy();
2553     }
2554 
2555     @UiThreadTest
testSetCustomTextClassifier()2556     public void testSetCustomTextClassifier() throws Exception {
2557         if (!NullWebViewUtils.isWebViewAvailable()) {
2558             return;
2559         }
2560 
2561         class CustomTextClassifier implements TextClassifier {
2562             @Override
2563             public TextSelection suggestSelection(
2564                 CharSequence text,
2565                 int startIndex,
2566                 int endIndex,
2567                 LocaleList defaultLocales) {
2568                 return new TextSelection.Builder(0, 1).build();
2569             }
2570 
2571             @Override
2572             public TextClassification classifyText(
2573                 CharSequence text,
2574                 int startIndex,
2575                 int endIndex,
2576                 LocaleList defaultLocales) {
2577                 return new TextClassification.Builder().build();
2578             }
2579         };
2580 
2581         TextClassifier classifier = new CustomTextClassifier();
2582         WebView webView = new WebView(getActivity());
2583         webView.setTextClassifier(classifier);
2584         assertSame(webView.getTextClassifier(), classifier);
2585         webView.destroy();
2586     }
2587 
2588     private static class MockContext extends ContextWrapper {
2589         private boolean mGetApplicationContextWasCalled;
2590 
MockContext(Context context)2591         public MockContext(Context context) {
2592             super(context);
2593         }
2594 
getApplicationContext()2595         public Context getApplicationContext() {
2596             mGetApplicationContextWasCalled = true;
2597             return super.getApplicationContext();
2598         }
2599 
wasGetApplicationContextCalled()2600         public boolean wasGetApplicationContextCalled() {
2601             return mGetApplicationContextWasCalled;
2602         }
2603     }
2604 
2605     /**
2606      * This should remain functionally equivalent to
2607      * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingUseApplicationContext. Modifications to
2608      * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
2609      */
testStartSafeBrowsingUseApplicationContext()2610     public void testStartSafeBrowsingUseApplicationContext() throws Exception {
2611         if (!NullWebViewUtils.isWebViewAvailable()) {
2612             return;
2613         }
2614 
2615         final MockContext ctx = new MockContext(getActivity());
2616         final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create();
2617         WebView.startSafeBrowsing(ctx, new ValueCallback<Boolean>() {
2618             @Override
2619             public void onReceiveValue(Boolean value) {
2620                 startSafeBrowsingFuture.set(ctx.wasGetApplicationContextCalled());
2621                 return;
2622             }
2623         });
2624         assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture));
2625     }
2626 
2627     /**
2628      * This should remain functionally equivalent to
2629      * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingWithNullCallbackDoesntCrash.
2630      * Modifications to this test should be reflected in that test as necessary. See
2631      * http://go/modifying-webview-cts.
2632      */
testStartSafeBrowsingWithNullCallbackDoesntCrash()2633     public void testStartSafeBrowsingWithNullCallbackDoesntCrash() throws Exception {
2634         if (!NullWebViewUtils.isWebViewAvailable()) {
2635             return;
2636         }
2637 
2638         WebView.startSafeBrowsing(getActivity().getApplicationContext(), null);
2639     }
2640 
2641     /**
2642      * This should remain functionally equivalent to
2643      * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingInvokesCallback. Modifications to
2644      * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
2645      */
testStartSafeBrowsingInvokesCallback()2646     public void testStartSafeBrowsingInvokesCallback() throws Exception {
2647         if (!NullWebViewUtils.isWebViewAvailable()) {
2648             return;
2649         }
2650 
2651         final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create();
2652         WebView.startSafeBrowsing(getActivity().getApplicationContext(),
2653                 new ValueCallback<Boolean>() {
2654             @Override
2655             public void onReceiveValue(Boolean value) {
2656                 startSafeBrowsingFuture.set(Looper.getMainLooper().isCurrentThread());
2657                 return;
2658             }
2659         });
2660         assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture));
2661     }
2662 
savePrintedPage(final PrintDocumentAdapter adapter, final ParcelFileDescriptor descriptor, final PageRange[] pageRanges, final SettableFuture<Void> result)2663     private void savePrintedPage(final PrintDocumentAdapter adapter,
2664             final ParcelFileDescriptor descriptor, final PageRange[] pageRanges,
2665             final SettableFuture<Void> result) {
2666         adapter.onWrite(pageRanges, descriptor,
2667                 new CancellationSignal(),
2668                 new WriteResultCallback() {
2669                     @Override
2670                     public void onWriteFinished(PageRange[] pages) {
2671                         try {
2672                             descriptor.close();
2673                             result.set(null);
2674                         } catch (IOException ex) {
2675                             result.setException(ex);
2676                         }
2677                     }
2678                 });
2679     }
2680 
printDocumentStart(final PrintDocumentAdapter adapter)2681     private void printDocumentStart(final PrintDocumentAdapter adapter) {
2682         WebkitUtils.onMainThreadSync(() -> {
2683             adapter.onStart();
2684         });
2685     }
2686 
printDocumentLayout(final PrintDocumentAdapter adapter, final PrintAttributes oldAttributes, final PrintAttributes newAttributes, final LayoutResultCallback layoutResultCallback)2687     private void printDocumentLayout(final PrintDocumentAdapter adapter,
2688             final PrintAttributes oldAttributes, final PrintAttributes newAttributes,
2689             final LayoutResultCallback layoutResultCallback) {
2690         WebkitUtils.onMainThreadSync(() -> {
2691             adapter.onLayout(oldAttributes, newAttributes, new CancellationSignal(),
2692                     layoutResultCallback, null);
2693         });
2694     }
2695 
2696     private static class HrefCheckHandler extends Handler {
2697         private boolean mHadRecieved;
2698 
2699         private String mResultUrl;
2700 
HrefCheckHandler(Looper looper)2701         public HrefCheckHandler(Looper looper) {
2702             super(looper);
2703         }
2704 
hasCalledHandleMessage()2705         public boolean hasCalledHandleMessage() {
2706             return mHadRecieved;
2707         }
2708 
getResultUrl()2709         public String getResultUrl() {
2710             return mResultUrl;
2711         }
2712 
reset()2713         public void reset(){
2714             mResultUrl = null;
2715             mHadRecieved = false;
2716         }
2717 
2718         @Override
handleMessage(Message msg)2719         public void handleMessage(Message msg) {
2720             mResultUrl = msg.getData().getString("url");
2721             mHadRecieved = true;
2722         }
2723     }
2724 
moveFocusDown()2725     private void moveFocusDown() throws Throwable {
2726         // send down key and wait for idle
2727         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
2728         // waiting for idle isn't always sufficient for the key to be fully processed
2729         Thread.sleep(500);
2730     }
2731 
pollingCheckWebBackForwardList(final String currUrl, final int currIndex, final int size)2732     private void pollingCheckWebBackForwardList(final String currUrl, final int currIndex,
2733             final int size) {
2734         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
2735             @Override
2736             protected boolean check() {
2737                 WebBackForwardList list = mWebView.copyBackForwardList();
2738                 return checkWebBackForwardList(list, currUrl, currIndex, size);
2739             }
2740         }.run();
2741     }
2742 
checkWebBackForwardList(WebBackForwardList list, String currUrl, int currIndex, int size)2743     private boolean checkWebBackForwardList(WebBackForwardList list, String currUrl,
2744             int currIndex, int size) {
2745         return (list != null)
2746                 && (list.getSize() == size)
2747                 && (list.getCurrentIndex() == currIndex)
2748                 && list.getItemAtIndex(currIndex).getUrl().equals(currUrl);
2749     }
2750 
assertGoBackOrForwardBySteps(boolean expected, int steps)2751     private void assertGoBackOrForwardBySteps(boolean expected, int steps) {
2752         // skip if steps equals to 0
2753         if (steps == 0)
2754             return;
2755 
2756         int start = steps > 0 ? 1 : steps;
2757         int end = steps > 0 ? steps : -1;
2758 
2759         // check all the steps in the history
2760         for (int i = start; i <= end; i++) {
2761             assertEquals(expected, mWebView.canGoBackOrForward(i));
2762 
2763             // shortcut methods for one step
2764             if (i == 1) {
2765                 assertEquals(expected, mWebView.canGoForward());
2766             } else if (i == -1) {
2767                 assertEquals(expected, mWebView.canGoBack());
2768             }
2769         }
2770     }
2771 
isPictureFilledWithColor(Picture picture, int color)2772     private boolean isPictureFilledWithColor(Picture picture, int color) {
2773         if (picture.getWidth() == 0 || picture.getHeight() == 0)
2774             return false;
2775 
2776         Bitmap bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(),
2777                 Config.ARGB_8888);
2778         picture.draw(new Canvas(bitmap));
2779 
2780         for (int i = 0; i < bitmap.getWidth(); i ++) {
2781             for (int j = 0; j < bitmap.getHeight(); j ++) {
2782                 if (color != bitmap.getPixel(i, j)) {
2783                     return false;
2784                 }
2785             }
2786         }
2787         return true;
2788     }
2789 
2790     /**
2791      * Waits at least MIN_SCROLL_WAIT_MS for scrolling to start. Once started,
2792      * scrolling is checked every SCROLL_WAIT_INTERVAL_MS for changes. Once
2793      * changes have stopped, the function exits. If no scrolling has happened
2794      * then the function exits after MIN_SCROLL_WAIT milliseconds.
2795      * @param previousScrollY The Y scroll position prior to waiting.
2796      */
waitForScrollingComplete(int previousScrollY)2797     private void waitForScrollingComplete(int previousScrollY)
2798             throws InterruptedException {
2799         int scrollY = previousScrollY;
2800         // wait at least MIN_SCROLL_WAIT for something to happen.
2801         long noChangeMinWait = SystemClock.uptimeMillis() + MIN_SCROLL_WAIT_MS;
2802         boolean scrollChanging = false;
2803         boolean scrollChanged = false;
2804         boolean minWaitExpired = false;
2805         while (scrollChanging || (!scrollChanged && !minWaitExpired)) {
2806             Thread.sleep(SCROLL_WAIT_INTERVAL_MS);
2807             int oldScrollY = scrollY;
2808             scrollY = mOnUiThread.getScrollY();
2809             scrollChanging = (scrollY != oldScrollY);
2810             scrollChanged = (scrollY != previousScrollY);
2811             minWaitExpired = (SystemClock.uptimeMillis() > noChangeMinWait);
2812         }
2813     }
2814 
2815     /**
2816      * This should remain functionally equivalent to
2817      * androidx.webkit.WebViewCompatTest#testGetSafeBrowsingPrivacyPolicyUrl. Modifications to this
2818      * test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
2819      */
testGetSafeBrowsingPrivacyPolicyUrl()2820     public void testGetSafeBrowsingPrivacyPolicyUrl() throws Exception {
2821         if (!NullWebViewUtils.isWebViewAvailable()) {
2822             return;
2823         }
2824 
2825         assertNotNull(WebView.getSafeBrowsingPrivacyPolicyUrl());
2826         try {
2827             new URL(WebView.getSafeBrowsingPrivacyPolicyUrl().toString());
2828         } catch (MalformedURLException e) {
2829             fail("The privacy policy URL should be a well-formed URL");
2830         }
2831     }
2832 
testWebViewClassLoaderReturnsNonNull()2833     public void testWebViewClassLoaderReturnsNonNull() {
2834         if (!NullWebViewUtils.isWebViewAvailable()) {
2835             return;
2836         }
2837 
2838         assertNotNull(WebView.getWebViewClassLoader());
2839     }
2840 }
2841