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