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