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 android.app.ActivityManager;
     20 import android.graphics.Bitmap;
     21 import android.os.Build;
     22 import android.os.Message;
     23 import android.platform.test.annotations.AppModeFull;
     24 import android.test.ActivityInstrumentationTestCase2;
     25 import android.view.KeyEvent;
     26 import android.view.ViewGroup;
     27 import android.webkit.HttpAuthHandler;
     28 import android.webkit.RenderProcessGoneDetail;
     29 import android.webkit.SafeBrowsingResponse;
     30 import android.webkit.ValueCallback;
     31 import android.webkit.WebChromeClient;
     32 import android.webkit.WebResourceError;
     33 import android.webkit.WebResourceRequest;
     34 import android.webkit.WebResourceResponse;
     35 import android.webkit.WebSettings;
     36 import android.webkit.WebView;
     37 import android.webkit.WebViewClient;
     38 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
     39 import android.util.Pair;
     40 
     41 import com.android.compatibility.common.util.NullWebViewUtils;
     42 import com.android.compatibility.common.util.PollingCheck;
     43 import com.google.common.util.concurrent.SettableFuture;
     44 
     45 import java.io.ByteArrayInputStream;
     46 import java.nio.charset.StandardCharsets;
     47 import java.util.HashMap;
     48 import java.util.Map;
     49 import java.util.List;
     50 import java.util.ArrayList;
     51 
     52 @AppModeFull
     53 public class WebViewClientTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
     54     private static final long TEST_TIMEOUT = 5000;
     55     private static final String TEST_URL = "http://www.example.com/";
     56 
     57     private WebViewOnUiThread mOnUiThread;
     58     private CtsTestServer mWebServer;
     59 
     60     private static final String TEST_SAFE_BROWSING_URL_PREFIX =
     61             "chrome://safe-browsing/match?type=";
     62     private static final String TEST_SAFE_BROWSING_MALWARE_URL =
     63             TEST_SAFE_BROWSING_URL_PREFIX + "malware";
     64     private static final String TEST_SAFE_BROWSING_PHISHING_URL =
     65             TEST_SAFE_BROWSING_URL_PREFIX + "phishing";
     66     private static final String TEST_SAFE_BROWSING_UNWANTED_SOFTWARE_URL =
     67             TEST_SAFE_BROWSING_URL_PREFIX + "unwanted";
     68     private static final String TEST_SAFE_BROWSING_BILLING_URL =
     69             TEST_SAFE_BROWSING_URL_PREFIX + "billing";
     70 
     71     public WebViewClientTest() {
     72         super("android.webkit.cts", WebViewCtsActivity.class);
     73     }
     74 
     75     @Override
     76     protected void setUp() throws Exception {
     77         super.setUp();
     78         final WebViewCtsActivity activity = getActivity();
     79         WebView webview = activity.getWebView();
     80         if (webview != null) {
     81             new PollingCheck(TEST_TIMEOUT) {
     82                 @Override
     83                     protected boolean check() {
     84                     return activity.hasWindowFocus();
     85                 }
     86             }.run();
     87 
     88             mOnUiThread = new WebViewOnUiThread(webview);
     89         }
     90     }
     91 
     92     @Override
     93     protected void tearDown() throws Exception {
     94         if (mOnUiThread != null) {
     95             mOnUiThread.cleanUp();
     96         }
     97         if (mWebServer != null) {
     98             mWebServer.shutdown();
     99         }
    100         super.tearDown();
    101     }
    102 
    103     /**
    104      * This should remain functionally equivalent to
    105      * androidx.webkit.WebViewClientCompatTest#testShouldOverrideUrlLoadingDefault. Modifications
    106      * to this test should be reflected in that test as necessary. See
    107      * http://go/modifying-webview-cts.
    108      */
    109     // Verify that the shouldoverrideurlloading is false by default
    110     public void testShouldOverrideUrlLoadingDefault() {
    111         if (!NullWebViewUtils.isWebViewAvailable()) {
    112             return;
    113         }
    114         final WebViewClient webViewClient = new WebViewClient();
    115         assertFalse(webViewClient.shouldOverrideUrlLoading(mOnUiThread.getWebView(), new String()));
    116     }
    117 
    118     /**
    119      * This should remain functionally equivalent to
    120      * androidx.webkit.WebViewClientCompatTest#testShouldOverrideUrlLoading. Modifications to this
    121      * test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
    122      */
    123     // Verify shouldoverrideurlloading called on top level navigation
    124     public void testShouldOverrideUrlLoading() {
    125         if (!NullWebViewUtils.isWebViewAvailable()) {
    126             return;
    127         }
    128         final MockWebViewClient webViewClient = new MockWebViewClient();
    129         mOnUiThread.setWebViewClient(webViewClient);
    130         mOnUiThread.getSettings().setJavaScriptEnabled(true);
    131         String data = "<html><body>" +
    132                 "<a href=\"" + TEST_URL + "\" id=\"link\">new page</a>" +
    133                 "</body></html>";
    134         mOnUiThread.loadDataAndWaitForCompletion(data, "text/html", null);
    135         clickOnLinkUsingJs("link", mOnUiThread);
    136         assertEquals(TEST_URL, webViewClient.getLastShouldOverrideUrl());
    137         assertNotNull(webViewClient.getLastShouldOverrideResourceRequest());
    138         assertTrue(webViewClient.getLastShouldOverrideResourceRequest().isForMainFrame());
    139         assertFalse(webViewClient.getLastShouldOverrideResourceRequest().isRedirect());
    140         assertFalse(webViewClient.getLastShouldOverrideResourceRequest().hasGesture());
    141     }
    142 
    143     // Verify shouldoverrideurlloading called on webview called via onCreateWindow
    144     // TODO(sgurun) upstream this test to Aw.
    145     public void testShouldOverrideUrlLoadingOnCreateWindow() throws Exception {
    146         if (!NullWebViewUtils.isWebViewAvailable()) {
    147             return;
    148         }
    149         mWebServer = new CtsTestServer(getActivity());
    150         // WebViewClient for main window
    151         final MockWebViewClient mainWebViewClient = new MockWebViewClient();
    152         // WebViewClient for child window
    153         final MockWebViewClient childWebViewClient = new MockWebViewClient();
    154         mOnUiThread.setWebViewClient(mainWebViewClient);
    155         mOnUiThread.getSettings().setJavaScriptEnabled(true);
    156         mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
    157         mOnUiThread.getSettings().setSupportMultipleWindows(true);
    158 
    159         final WebView childWebView = mOnUiThread.createWebView();
    160 
    161         WebViewOnUiThread childWebViewOnUiThread = new WebViewOnUiThread(childWebView);
    162         mOnUiThread.setWebChromeClient(new WebChromeClient() {
    163             @Override
    164             public boolean onCreateWindow(
    165                 WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
    166                 WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
    167                 childWebView.setWebViewClient(childWebViewClient);
    168                 childWebView.getSettings().setJavaScriptEnabled(true);
    169                 transport.setWebView(childWebView);
    170                 getActivity().addContentView(childWebView, new ViewGroup.LayoutParams(
    171                             ViewGroup.LayoutParams.FILL_PARENT,
    172                             ViewGroup.LayoutParams.WRAP_CONTENT));
    173                 resultMsg.sendToTarget();
    174                 return true;
    175             }
    176         });
    177         {
    178           final int childCallCount = childWebViewClient.getShouldOverrideUrlLoadingCallCount();
    179           mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.BLANK_TAG_URL));
    180 
    181           new PollingCheck(TEST_TIMEOUT) {
    182               @Override
    183               protected boolean check() {
    184                   return childWebViewClient.hasOnPageFinishedCalled();
    185               }
    186           }.run();
    187           new PollingCheck(TEST_TIMEOUT) {
    188               @Override
    189               protected boolean check() {
    190                   return childWebViewClient.getShouldOverrideUrlLoadingCallCount() > childCallCount;
    191               }
    192           }.run();
    193           assertEquals(mWebServer.getAssetUrl(TestHtmlConstants.PAGE_WITH_LINK_URL),
    194                   childWebViewClient.getLastShouldOverrideUrl());
    195         }
    196 
    197         final int childCallCount = childWebViewClient.getShouldOverrideUrlLoadingCallCount();
    198         final int mainCallCount = mainWebViewClient.getShouldOverrideUrlLoadingCallCount();
    199         clickOnLinkUsingJs("link", childWebViewOnUiThread);
    200         new PollingCheck(TEST_TIMEOUT) {
    201             @Override
    202             protected boolean check() {
    203                 return childWebViewClient.getShouldOverrideUrlLoadingCallCount() > childCallCount;
    204             }
    205         }.run();
    206         assertEquals(mainCallCount, mainWebViewClient.getShouldOverrideUrlLoadingCallCount());
    207         assertEquals(
    208             TestHtmlConstants.URL_IN_PAGE_WITH_LINK, childWebViewClient.getLastShouldOverrideUrl());
    209     }
    210 
    211     private void clickOnLinkUsingJs(final String linkId, WebViewOnUiThread webViewOnUiThread) {
    212         assertEquals("null", webViewOnUiThread.evaluateJavascriptSync(
    213                         "document.getElementById('" + linkId + "').click();" +
    214                         "console.log('element with id [" + linkId + "] clicked');"));
    215     }
    216 
    217     public void testLoadPage() throws Exception {
    218         if (!NullWebViewUtils.isWebViewAvailable()) {
    219             return;
    220         }
    221         final MockWebViewClient webViewClient = new MockWebViewClient();
    222         mOnUiThread.setWebViewClient(webViewClient);
    223         mWebServer = new CtsTestServer(getActivity());
    224         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
    225 
    226         assertFalse(webViewClient.hasOnPageStartedCalled());
    227         assertFalse(webViewClient.hasOnLoadResourceCalled());
    228         assertFalse(webViewClient.hasOnPageFinishedCalled());
    229         mOnUiThread.loadUrlAndWaitForCompletion(url);
    230 
    231         new PollingCheck(TEST_TIMEOUT) {
    232             @Override
    233             protected boolean check() {
    234                 return webViewClient.hasOnPageStartedCalled();
    235             }
    236         }.run();
    237 
    238         new PollingCheck(TEST_TIMEOUT) {
    239             @Override
    240             protected boolean check() {
    241                 return webViewClient.hasOnLoadResourceCalled();
    242             }
    243         }.run();
    244 
    245         new PollingCheck(TEST_TIMEOUT) {
    246             @Override
    247             protected boolean check() {
    248                 return webViewClient.hasOnPageFinishedCalled();
    249             }
    250         }.run();
    251     }
    252 
    253     public void testOnReceivedLoginRequest() throws Exception {
    254         if (!NullWebViewUtils.isWebViewAvailable()) {
    255             return;
    256         }
    257         final MockWebViewClient webViewClient = new MockWebViewClient();
    258         mOnUiThread.setWebViewClient(webViewClient);
    259         TestWebServer testServer = null;
    260         //set the url and html
    261         final String path = "/main";
    262         final String page = "<head></head><body>test onReceivedLoginRequest</body>";
    263         final String headerName = "x-auto-login";
    264         final String headerValue = "realm=com.google&account=foo%40bar.com&args=random_string";
    265         List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
    266         headers.add(Pair.create(headerName, headerValue));
    267 
    268         try {
    269             testServer = new TestWebServer(false);
    270             String url = testServer.setResponse(path, page, headers);
    271             assertFalse(webViewClient.hasOnReceivedLoginRequest());
    272             mOnUiThread.loadUrlAndWaitForCompletion(url);
    273             assertTrue(webViewClient.hasOnReceivedLoginRequest());
    274             new PollingCheck(TEST_TIMEOUT) {
    275                 @Override
    276                 protected boolean check() {
    277                     return webViewClient.hasOnReceivedLoginRequest();
    278                 }
    279             }.run();
    280            assertEquals("com.google", webViewClient.getLoginRequestRealm());
    281            assertEquals("foo (at) bar.com", webViewClient.getLoginRequestAccount());
    282            assertEquals("random_string", webViewClient.getLoginRequestArgs());
    283         } finally {
    284             testServer.shutdown();
    285         }
    286     }
    287     /**
    288      * This should remain functionally equivalent to
    289      * androidx.webkit.WebViewClientCompatTest#testOnReceivedError. Modifications to this test
    290      * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
    291      */
    292     public void testOnReceivedError() throws Exception {
    293         if (!NullWebViewUtils.isWebViewAvailable()) {
    294             return;
    295         }
    296         final MockWebViewClient webViewClient = new MockWebViewClient();
    297         mOnUiThread.setWebViewClient(webViewClient);
    298 
    299         String wrongUri = "invalidscheme://some/resource";
    300         assertEquals(0, webViewClient.hasOnReceivedErrorCode());
    301         mOnUiThread.loadUrlAndWaitForCompletion(wrongUri);
    302         assertEquals(WebViewClient.ERROR_UNSUPPORTED_SCHEME,
    303                 webViewClient.hasOnReceivedErrorCode());
    304     }
    305 
    306     /**
    307      * This should remain functionally equivalent to
    308      * androidx.webkit.WebViewClientCompatTest#testOnReceivedErrorForSubresource. Modifications to
    309      * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
    310      */
    311     public void testOnReceivedErrorForSubresource() throws Exception {
    312         if (!NullWebViewUtils.isWebViewAvailable()) {
    313             return;
    314         }
    315         final MockWebViewClient webViewClient = new MockWebViewClient();
    316         mOnUiThread.setWebViewClient(webViewClient);
    317         mWebServer = new CtsTestServer(getActivity());
    318 
    319         assertNull(webViewClient.hasOnReceivedResourceError());
    320         String url = mWebServer.getAssetUrl(TestHtmlConstants.BAD_IMAGE_PAGE_URL);
    321         mOnUiThread.loadUrlAndWaitForCompletion(url);
    322         assertTrue(webViewClient.hasOnReceivedResourceError() != null);
    323         assertEquals(WebViewClient.ERROR_UNSUPPORTED_SCHEME,
    324                 webViewClient.hasOnReceivedResourceError().getErrorCode());
    325     }
    326 
    327     public void testOnReceivedHttpError() throws Exception {
    328         if (!NullWebViewUtils.isWebViewAvailable()) {
    329             return;
    330         }
    331         final MockWebViewClient webViewClient = new MockWebViewClient();
    332         mOnUiThread.setWebViewClient(webViewClient);
    333         mWebServer = new CtsTestServer(getActivity());
    334 
    335         assertNull(webViewClient.hasOnReceivedHttpError());
    336         String url = mWebServer.getAssetUrl(TestHtmlConstants.NON_EXISTENT_PAGE_URL);
    337         mOnUiThread.loadUrlAndWaitForCompletion(url);
    338         assertTrue(webViewClient.hasOnReceivedHttpError() != null);
    339         assertEquals(404, webViewClient.hasOnReceivedHttpError().getStatusCode());
    340     }
    341 
    342     public void testOnFormResubmission() throws Exception {
    343         if (!NullWebViewUtils.isWebViewAvailable()) {
    344             return;
    345         }
    346         final MockWebViewClient webViewClient = new MockWebViewClient();
    347         mOnUiThread.setWebViewClient(webViewClient);
    348         final WebSettings settings = mOnUiThread.getSettings();
    349         settings.setJavaScriptEnabled(true);
    350         mWebServer = new CtsTestServer(getActivity());
    351 
    352         assertFalse(webViewClient.hasOnFormResubmissionCalled());
    353         String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_FORM_URL);
    354         // this loads a form, which automatically posts itself
    355         mOnUiThread.loadUrlAndWaitForCompletion(url);
    356         // wait for JavaScript to post the form
    357         mOnUiThread.waitForLoadCompletion();
    358         assertFalse("The URL should have changed when the form was posted",
    359                 url.equals(mOnUiThread.getUrl()));
    360         // reloading the current URL should trigger the callback
    361         mOnUiThread.reload();
    362         new PollingCheck(TEST_TIMEOUT) {
    363             @Override
    364             protected boolean check() {
    365                 return webViewClient.hasOnFormResubmissionCalled();
    366             }
    367         }.run();
    368     }
    369 
    370     public void testDoUpdateVisitedHistory() throws Exception {
    371         if (!NullWebViewUtils.isWebViewAvailable()) {
    372             return;
    373         }
    374         final MockWebViewClient webViewClient = new MockWebViewClient();
    375         mOnUiThread.setWebViewClient(webViewClient);
    376         mWebServer = new CtsTestServer(getActivity());
    377 
    378         assertFalse(webViewClient.hasDoUpdateVisitedHistoryCalled());
    379         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
    380         String url2 = mWebServer.getAssetUrl(TestHtmlConstants.BR_TAG_URL);
    381         mOnUiThread.loadUrlAndWaitForCompletion(url1);
    382         mOnUiThread.loadUrlAndWaitForCompletion(url2);
    383         new PollingCheck(TEST_TIMEOUT) {
    384             @Override
    385             protected boolean check() {
    386                 return webViewClient.hasDoUpdateVisitedHistoryCalled();
    387             }
    388         }.run();
    389     }
    390 
    391     public void testOnReceivedHttpAuthRequest() throws Exception {
    392         if (!NullWebViewUtils.isWebViewAvailable()) {
    393             return;
    394         }
    395         final MockWebViewClient webViewClient = new MockWebViewClient();
    396         mOnUiThread.setWebViewClient(webViewClient);
    397         mWebServer = new CtsTestServer(getActivity());
    398 
    399         assertFalse(webViewClient.hasOnReceivedHttpAuthRequestCalled());
    400         String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.EMBEDDED_IMG_URL);
    401         mOnUiThread.loadUrlAndWaitForCompletion(url);
    402         assertTrue(webViewClient.hasOnReceivedHttpAuthRequestCalled());
    403     }
    404 
    405     public void testShouldOverrideKeyEvent() {
    406         if (!NullWebViewUtils.isWebViewAvailable()) {
    407             return;
    408         }
    409         final MockWebViewClient webViewClient = new MockWebViewClient();
    410         mOnUiThread.setWebViewClient(webViewClient);
    411 
    412         assertFalse(webViewClient.shouldOverrideKeyEvent(mOnUiThread.getWebView(), null));
    413     }
    414 
    415     public void testOnUnhandledKeyEvent() throws Throwable {
    416         if (!NullWebViewUtils.isWebViewAvailable()) {
    417             return;
    418         }
    419         requireLoadedPage();
    420         final MockWebViewClient webViewClient = new MockWebViewClient();
    421         mOnUiThread.setWebViewClient(webViewClient);
    422 
    423         mOnUiThread.requestFocus();
    424         getInstrumentation().waitForIdleSync();
    425 
    426         assertFalse(webViewClient.hasOnUnhandledKeyEventCalled());
    427         sendKeys(KeyEvent.KEYCODE_1);
    428 
    429         new PollingCheck(TEST_TIMEOUT) {
    430             @Override
    431             protected boolean check() {
    432                 return webViewClient.hasOnUnhandledKeyEventCalled();
    433             }
    434         }.run();
    435     }
    436 
    437     public void testOnScaleChanged() throws Throwable {
    438         if (!NullWebViewUtils.isWebViewAvailable()) {
    439             return;
    440         }
    441         final MockWebViewClient webViewClient = new MockWebViewClient();
    442         mOnUiThread.setWebViewClient(webViewClient);
    443         mWebServer = new CtsTestServer(getActivity());
    444 
    445         assertFalse(webViewClient.hasOnScaleChangedCalled());
    446         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
    447         mOnUiThread.loadUrlAndWaitForCompletion(url1);
    448 
    449         new PollingCheck(TEST_TIMEOUT) {
    450             @Override
    451             protected boolean check() {
    452                 return mOnUiThread.canZoomIn();
    453             }
    454         }.run();
    455 
    456         assertTrue(mOnUiThread.zoomIn());
    457         new PollingCheck(TEST_TIMEOUT) {
    458             @Override
    459             protected boolean check() {
    460                 return webViewClient.hasOnScaleChangedCalled();
    461             }
    462         }.run();
    463     }
    464 
    465     // Test that shouldInterceptRequest is called with the correct parameters
    466     public void testShouldInterceptRequestParams() throws Throwable {
    467         if (!NullWebViewUtils.isWebViewAvailable()) {
    468             return;
    469         }
    470 
    471         final String mainPath = "/main";
    472         final String mainPage = "<head></head><body>test page</body>";
    473         final String headerName = "x-test-header-name";
    474         final String headerValue = "testheadervalue";
    475         HashMap<String, String> headers = new HashMap<String, String>(1);
    476         headers.put(headerName, headerValue);
    477 
    478         // A client which saves the WebResourceRequest as interceptRequest
    479         final class TestClient extends WaitForLoadedClient {
    480             public TestClient() {
    481                 super(mOnUiThread);
    482             }
    483 
    484             @Override
    485             public WebResourceResponse shouldInterceptRequest(WebView view,
    486                     WebResourceRequest request) {
    487                 assertNotNull(view);
    488                 assertNotNull(request);
    489 
    490                 assertEquals(view, mOnUiThread.getWebView());
    491 
    492                 // Save the main page request; discard any other requests (e.g. for favicon.ico)
    493                 if (request.getUrl().getPath().equals(mainPath)) {
    494                     assertNull(interceptRequest);
    495                     interceptRequest = request;
    496                 }
    497 
    498                 return null;
    499             }
    500 
    501             public volatile WebResourceRequest interceptRequest;
    502         }
    503 
    504         TestClient client = new TestClient();
    505         mOnUiThread.setWebViewClient(client);
    506 
    507         TestWebServer server = new TestWebServer(false);
    508         try {
    509             String mainUrl = server.setResponse(mainPath, mainPage, null);
    510 
    511             mOnUiThread.loadUrlAndWaitForCompletion(mainUrl, headers);
    512 
    513             // Inspect the fields of the saved WebResourceRequest
    514             assertNotNull(client.interceptRequest);
    515             assertEquals(mainUrl, client.interceptRequest.getUrl().toString());
    516             assertTrue(client.interceptRequest.isForMainFrame());
    517             assertEquals(server.getLastRequest(mainPath).getRequestLine().getMethod(),
    518                 client.interceptRequest.getMethod());
    519 
    520             // Web request headers are case-insensitive. We provided lower-case headerName and
    521             // headerValue. This will pass implementations which either do not mangle case,
    522             // convert to lowercase, or convert to uppercase but return a case-insensitive map.
    523             Map<String, String> interceptHeaders = client.interceptRequest.getRequestHeaders();
    524             assertTrue(interceptHeaders.containsKey(headerName));
    525             assertEquals(headerValue, interceptHeaders.get(headerName));
    526         } finally {
    527             server.shutdown();
    528         }
    529     }
    530 
    531     // Test that the WebResourceResponse returned by shouldInterceptRequest is handled correctly
    532     public void testShouldInterceptRequestResponse() throws Throwable {
    533         if (!NullWebViewUtils.isWebViewAvailable()) {
    534             return;
    535         }
    536 
    537         final String mainPath = "/main";
    538         final String mainPage = "<head></head><body>test page</body>";
    539         final String interceptPath = "/intercept_me";
    540 
    541         // A client which responds to requests for interceptPath with a saved interceptResponse
    542         final class TestClient extends WaitForLoadedClient {
    543             public TestClient() {
    544                 super(mOnUiThread);
    545             }
    546 
    547             @Override
    548             public WebResourceResponse shouldInterceptRequest(WebView view,
    549                     WebResourceRequest request) {
    550                 if (request.getUrl().toString().contains(interceptPath)) {
    551                     assertNotNull(interceptResponse);
    552                     return interceptResponse;
    553                 }
    554 
    555                 return null;
    556             }
    557 
    558             volatile public WebResourceResponse interceptResponse;
    559         }
    560 
    561         mOnUiThread.getSettings().setJavaScriptEnabled(true);
    562 
    563         TestClient client = new TestClient();
    564         mOnUiThread.setWebViewClient(client);
    565 
    566         TestWebServer server = new TestWebServer(false);
    567         try {
    568             String interceptUrl = server.getResponseUrl(interceptPath);
    569 
    570             // JavaScript which makes a synchronous AJAX request and logs and returns the status
    571             String js =
    572                 "(function() {" +
    573                 "  var xhr = new XMLHttpRequest();" +
    574                 "  xhr.open('GET', '" + interceptUrl + "', false);" +
    575                 "  xhr.send(null);" +
    576                 "  console.info('xhr.status = ' + xhr.status);" +
    577                 "  console.info('xhr.statusText = ' + xhr.statusText);" +
    578                 "  return '[' + xhr.status + '][' + xhr.statusText + ']';" +
    579                 "})();";
    580 
    581             String mainUrl = server.setResponse(mainPath, mainPage, null);
    582             mOnUiThread.loadUrlAndWaitForCompletion(mainUrl, null);
    583 
    584             // Test a nonexistent page
    585             client.interceptResponse = new WebResourceResponse("text/html", "UTF-8", null);
    586             assertEquals("\"[404][Not Found]\"", mOnUiThread.evaluateJavascriptSync(js));
    587 
    588             // Test an empty page
    589             client.interceptResponse = new WebResourceResponse("text/html", "UTF-8",
    590                 new ByteArrayInputStream(new byte[0]));
    591             assertEquals("\"[200][OK]\"", mOnUiThread.evaluateJavascriptSync(js));
    592 
    593             // Test a nonempty page with unusual response code/text
    594             client.interceptResponse =
    595                 new WebResourceResponse("text/html", "UTF-8", 123, "unusual", null,
    596                     new ByteArrayInputStream("nonempty page".getBytes(StandardCharsets.UTF_8)));
    597             assertEquals("\"[123][unusual]\"", mOnUiThread.evaluateJavascriptSync(js));
    598         } finally {
    599             server.shutdown();
    600         }
    601     }
    602 
    603     // Verify that OnRenderProcessGone returns false by default
    604     public void testOnRenderProcessGoneDefault() throws Throwable {
    605         if (!NullWebViewUtils.isWebViewAvailable()) {
    606             return;
    607         }
    608         final WebViewClient webViewClient = new WebViewClient();
    609         assertFalse(webViewClient.onRenderProcessGone(mOnUiThread.getWebView(), null));
    610     }
    611 
    612     public void testOnRenderProcessGone() throws Throwable {
    613         if (!NullWebViewUtils.isWebViewAvailable()) {
    614             return;
    615         }
    616         if (!getActivity().isMultiprocessMode()) {
    617             return;
    618         }
    619         final MockWebViewClient webViewClient = new MockWebViewClient();
    620         mOnUiThread.setWebViewClient(webViewClient);
    621         mOnUiThread.loadUrl("chrome://kill");
    622         new PollingCheck(TEST_TIMEOUT * 5) {
    623             @Override
    624             protected boolean check() {
    625                 return webViewClient.hasRenderProcessGoneCalled();
    626             }
    627         }.run();
    628         assertFalse(webViewClient.didRenderProcessCrash());
    629     }
    630 
    631     /**
    632      * This should remain functionally equivalent to
    633      * androidx.webkit.WebViewClientCompatTest#testOnSafeBrowsingHitBackToSafety.
    634      * Modifications to this test should be reflected in that test as necessary. See
    635      * http://go/modifying-webview-cts.
    636      */
    637     public void testOnSafeBrowsingHitBackToSafety() throws Throwable {
    638         if (!NullWebViewUtils.isWebViewAvailable()) {
    639             return;
    640         }
    641         mWebServer = new CtsTestServer(getActivity());
    642         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
    643         mOnUiThread.loadUrlAndWaitForCompletion(url);
    644         final String ORIGINAL_URL = mOnUiThread.getUrl();
    645 
    646         final SafeBrowsingBackToSafetyClient backToSafetyWebViewClient =
    647                 new SafeBrowsingBackToSafetyClient();
    648         mOnUiThread.setWebViewClient(backToSafetyWebViewClient);
    649         mOnUiThread.getSettings().setSafeBrowsingEnabled(true);
    650 
    651         // Note: Safe Browsing depends on user opt-in as well, so we can't assume it's actually
    652         // enabled. #getSafeBrowsingEnabled will tell us the true state of whether Safe Browsing is
    653         // enabled.
    654         if (mOnUiThread.getSettings().getSafeBrowsingEnabled()) {
    655             assertEquals(0, backToSafetyWebViewClient.hasOnReceivedErrorCode());
    656             mOnUiThread.loadUrlAndWaitForCompletion(TEST_SAFE_BROWSING_MALWARE_URL);
    657 
    658             assertEquals(TEST_SAFE_BROWSING_MALWARE_URL,
    659                     backToSafetyWebViewClient.getOnSafeBrowsingHitRequest().getUrl().toString());
    660             assertTrue(backToSafetyWebViewClient.getOnSafeBrowsingHitRequest().isForMainFrame());
    661 
    662             assertEquals("Back to safety should produce a network error",
    663                     WebViewClient.ERROR_UNSAFE_RESOURCE,
    664                     backToSafetyWebViewClient.hasOnReceivedErrorCode());
    665 
    666             assertEquals("Back to safety should navigate backward", ORIGINAL_URL,
    667                     mOnUiThread.getUrl());
    668         }
    669     }
    670 
    671     /**
    672      * This should remain functionally equivalent to
    673      * androidx.webkit.WebViewClientCompatTest#testOnSafeBrowsingHitProceed.
    674      * Modifications to this test should be reflected in that test as necessary. See
    675      * http://go/modifying-webview-cts.
    676      */
    677     public void testOnSafeBrowsingHitProceed() throws Throwable {
    678         if (!NullWebViewUtils.isWebViewAvailable()) {
    679             return;
    680         }
    681         mWebServer = new CtsTestServer(getActivity());
    682         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
    683         mOnUiThread.loadUrlAndWaitForCompletion(url);
    684         final String ORIGINAL_URL = mOnUiThread.getUrl();
    685 
    686         final SafeBrowsingProceedClient proceedWebViewClient =
    687                 new SafeBrowsingProceedClient();
    688         mOnUiThread.setWebViewClient(proceedWebViewClient);
    689         mOnUiThread.getSettings().setSafeBrowsingEnabled(true);
    690 
    691         // Note: Safe Browsing depends on user opt-in as well, so we can't assume it's actually
    692         // enabled. #getSafeBrowsingEnabled will tell us the true state of whether Safe Browsing is
    693         // enabled.
    694         if (mOnUiThread.getSettings().getSafeBrowsingEnabled()) {
    695             assertEquals(0, proceedWebViewClient.hasOnReceivedErrorCode());
    696             mOnUiThread.loadUrlAndWaitForCompletion(TEST_SAFE_BROWSING_MALWARE_URL);
    697 
    698             assertEquals(TEST_SAFE_BROWSING_MALWARE_URL,
    699                     proceedWebViewClient.getOnSafeBrowsingHitRequest().getUrl().toString());
    700             assertTrue(proceedWebViewClient.getOnSafeBrowsingHitRequest().isForMainFrame());
    701 
    702             assertEquals("Proceed button should navigate to the page",
    703                     TEST_SAFE_BROWSING_MALWARE_URL, mOnUiThread.getUrl());
    704         }
    705     }
    706 
    707     private void testOnSafeBrowsingCode(String expectedUrl, int expectedThreatType)
    708             throws Throwable {
    709         if (!NullWebViewUtils.isWebViewAvailable()) {
    710             return;
    711         }
    712         mWebServer = new CtsTestServer(getActivity());
    713         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
    714         mOnUiThread.loadUrlAndWaitForCompletion(url);
    715         final String ORIGINAL_URL = mOnUiThread.getUrl();
    716 
    717         final SafeBrowsingBackToSafetyClient backToSafetyWebViewClient =
    718                 new SafeBrowsingBackToSafetyClient();
    719         mOnUiThread.setWebViewClient(backToSafetyWebViewClient);
    720         mOnUiThread.getSettings().setSafeBrowsingEnabled(true);
    721 
    722         // Note: Safe Browsing depends on user opt-in as well, so we can't assume it's actually
    723         // enabled. #getSafeBrowsingEnabled will tell us the true state of whether Safe Browsing is
    724         // enabled.
    725         if (mOnUiThread.getSettings().getSafeBrowsingEnabled()) {
    726             mOnUiThread.loadUrlAndWaitForCompletion(expectedUrl);
    727 
    728             assertEquals("Safe Browsing hit is for unexpected URL",
    729                     expectedUrl,
    730                     backToSafetyWebViewClient.getOnSafeBrowsingHitRequest().getUrl().toString());
    731 
    732             assertEquals("Safe Browsing hit has unexpected threat type",
    733                     expectedThreatType,
    734                     backToSafetyWebViewClient.getOnSafeBrowsingHitThreatType());
    735         }
    736     }
    737 
    738     public void testOnSafeBrowsingMalwareCode() throws Throwable {
    739         testOnSafeBrowsingCode(TEST_SAFE_BROWSING_MALWARE_URL,
    740                 WebViewClient.SAFE_BROWSING_THREAT_MALWARE);
    741     }
    742 
    743     public void testOnSafeBrowsingPhishingCode() throws Throwable {
    744         testOnSafeBrowsingCode(TEST_SAFE_BROWSING_PHISHING_URL,
    745                 WebViewClient.SAFE_BROWSING_THREAT_PHISHING);
    746     }
    747 
    748     public void testOnSafeBrowsingUnwantedSoftwareCode() throws Throwable {
    749         testOnSafeBrowsingCode(TEST_SAFE_BROWSING_UNWANTED_SOFTWARE_URL,
    750                 WebViewClient.SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE);
    751     }
    752 
    753     public void testOnSafeBrowsingBillingCode() throws Throwable {
    754         testOnSafeBrowsingCode(TEST_SAFE_BROWSING_BILLING_URL,
    755                 WebViewClient.SAFE_BROWSING_THREAT_BILLING);
    756     }
    757 
    758     private void requireLoadedPage() throws Throwable {
    759         if (!NullWebViewUtils.isWebViewAvailable()) {
    760             return;
    761         }
    762         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
    763     }
    764 
    765     /**
    766      * This should remain functionally equivalent to
    767      * androidx.webkit.WebViewClientCompatTest#testOnPageCommitVisibleCalled.
    768      * Modifications to this test should be reflected in that test as necessary. See
    769      * http://go/modifying-webview-cts.
    770      */
    771     public void testOnPageCommitVisibleCalled() throws Exception {
    772         // Check that the onPageCommitVisible callback is called
    773         // correctly.
    774         if (!NullWebViewUtils.isWebViewAvailable()) {
    775             return;
    776         }
    777 
    778         final SettableFuture<String> pageCommitVisibleFuture = SettableFuture.create();
    779         mOnUiThread.setWebViewClient(new WebViewClient() {
    780             public void onPageCommitVisible(WebView view, String url) {
    781                 pageCommitVisibleFuture.set(url);
    782             }
    783         });
    784 
    785         final String url = "about:blank";
    786         mOnUiThread.loadUrl(url);
    787         assertEquals(url, WebkitUtils.waitForFuture(pageCommitVisibleFuture));
    788     }
    789 
    790     private class MockWebViewClient extends WaitForLoadedClient {
    791         private boolean mOnPageStartedCalled;
    792         private boolean mOnPageFinishedCalled;
    793         private boolean mOnLoadResourceCalled;
    794         private int mOnReceivedErrorCode;
    795         private WebResourceError mOnReceivedResourceError;
    796         private WebResourceResponse mOnReceivedHttpError;
    797         private boolean mOnFormResubmissionCalled;
    798         private boolean mDoUpdateVisitedHistoryCalled;
    799         private boolean mOnReceivedHttpAuthRequestCalled;
    800         private boolean mOnReceivedLoginRequest;
    801         private String mOnReceivedLoginAccount;
    802         private String mOnReceivedLoginArgs;
    803         private String mOnReceivedLoginRealm;
    804         private boolean mOnUnhandledKeyEventCalled;
    805         private boolean mOnScaleChangedCalled;
    806         private int mShouldOverrideUrlLoadingCallCount;
    807         private String mLastShouldOverrideUrl;
    808         private WebResourceRequest mLastShouldOverrideResourceRequest;
    809         private boolean mOnRenderProcessGoneCalled;
    810         private boolean mRenderProcessCrashed;
    811 
    812         public MockWebViewClient() {
    813             super(mOnUiThread);
    814         }
    815 
    816         public boolean hasOnPageStartedCalled() {
    817             return mOnPageStartedCalled;
    818         }
    819 
    820         public boolean hasOnPageFinishedCalled() {
    821             return mOnPageFinishedCalled;
    822         }
    823 
    824         public boolean hasOnLoadResourceCalled() {
    825             return mOnLoadResourceCalled;
    826         }
    827 
    828         public int hasOnReceivedErrorCode() {
    829             return mOnReceivedErrorCode;
    830         }
    831 
    832         public boolean hasOnReceivedLoginRequest() {
    833             return mOnReceivedLoginRequest;
    834         }
    835 
    836         public WebResourceError hasOnReceivedResourceError() {
    837             return mOnReceivedResourceError;
    838         }
    839 
    840         public WebResourceResponse hasOnReceivedHttpError() {
    841             return mOnReceivedHttpError;
    842         }
    843 
    844         public boolean hasOnFormResubmissionCalled() {
    845             return mOnFormResubmissionCalled;
    846         }
    847 
    848         public boolean hasDoUpdateVisitedHistoryCalled() {
    849             return mDoUpdateVisitedHistoryCalled;
    850         }
    851 
    852         public boolean hasOnReceivedHttpAuthRequestCalled() {
    853             return mOnReceivedHttpAuthRequestCalled;
    854         }
    855 
    856         public boolean hasOnUnhandledKeyEventCalled() {
    857             return mOnUnhandledKeyEventCalled;
    858         }
    859 
    860         public boolean hasOnScaleChangedCalled() {
    861             return mOnScaleChangedCalled;
    862         }
    863 
    864         public int getShouldOverrideUrlLoadingCallCount() {
    865             return mShouldOverrideUrlLoadingCallCount;
    866         }
    867 
    868         public String getLastShouldOverrideUrl() {
    869             return mLastShouldOverrideUrl;
    870         }
    871 
    872         public WebResourceRequest getLastShouldOverrideResourceRequest() {
    873             return mLastShouldOverrideResourceRequest;
    874         }
    875 
    876         public String getLoginRequestRealm() {
    877             return mOnReceivedLoginRealm;
    878         }
    879 
    880         public String getLoginRequestAccount() {
    881             return mOnReceivedLoginAccount;
    882         }
    883 
    884         public String getLoginRequestArgs() {
    885             return mOnReceivedLoginArgs;
    886         }
    887 
    888         public boolean hasRenderProcessGoneCalled() {
    889             return mOnRenderProcessGoneCalled;
    890         }
    891 
    892         public boolean didRenderProcessCrash() {
    893             return mRenderProcessCrashed;
    894         }
    895 
    896         @Override
    897         public void onPageStarted(WebView view, String url, Bitmap favicon) {
    898             super.onPageStarted(view, url, favicon);
    899             mOnPageStartedCalled = true;
    900         }
    901 
    902         @Override
    903         public void onPageFinished(WebView view, String url) {
    904             super.onPageFinished(view, url);
    905             assertTrue(mOnPageStartedCalled);
    906             assertTrue(mOnLoadResourceCalled);
    907             mOnPageFinishedCalled = true;
    908         }
    909 
    910         @Override
    911         public void onLoadResource(WebView view, String url) {
    912             super.onLoadResource(view, url);
    913             mOnLoadResourceCalled = true;
    914         }
    915 
    916         @Override
    917         public void onReceivedError(WebView view, int errorCode,
    918                 String description, String failingUrl) {
    919             super.onReceivedError(view, errorCode, description, failingUrl);
    920             mOnReceivedErrorCode = errorCode;
    921         }
    922 
    923         @Override
    924         public void onReceivedError(WebView view, WebResourceRequest request,
    925                 WebResourceError error) {
    926             super.onReceivedError(view, request, error);
    927             mOnReceivedResourceError = error;
    928         }
    929 
    930         @Override
    931         public void onReceivedHttpError(WebView view,  WebResourceRequest request,
    932                 WebResourceResponse errorResponse) {
    933             super.onReceivedHttpError(view, request, errorResponse);
    934             mOnReceivedHttpError = errorResponse;
    935         }
    936 
    937         @Override
    938         public void onReceivedLoginRequest(WebView view, String realm, String account,
    939                 String args) {
    940             super.onReceivedLoginRequest(view, realm, account, args);
    941             mOnReceivedLoginRequest = true;
    942             mOnReceivedLoginRealm = realm;
    943             mOnReceivedLoginAccount = account;
    944             mOnReceivedLoginArgs = args;
    945        }
    946 
    947         @Override
    948         public void onFormResubmission(WebView view, Message dontResend, Message resend) {
    949             mOnFormResubmissionCalled = true;
    950             dontResend.sendToTarget();
    951         }
    952 
    953         @Override
    954         public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
    955             super.doUpdateVisitedHistory(view, url, isReload);
    956             mDoUpdateVisitedHistoryCalled = true;
    957         }
    958 
    959         @Override
    960         public void onReceivedHttpAuthRequest(WebView view,
    961                 HttpAuthHandler handler, String host, String realm) {
    962             super.onReceivedHttpAuthRequest(view, handler, host, realm);
    963             mOnReceivedHttpAuthRequestCalled = true;
    964         }
    965 
    966         @Override
    967         public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
    968             super.onUnhandledKeyEvent(view, event);
    969             mOnUnhandledKeyEventCalled = true;
    970         }
    971 
    972         @Override
    973         public void onScaleChanged(WebView view, float oldScale, float newScale) {
    974             super.onScaleChanged(view, oldScale, newScale);
    975             mOnScaleChangedCalled = true;
    976         }
    977 
    978         @Override
    979         public boolean shouldOverrideUrlLoading(WebView view, String url) {
    980             mLastShouldOverrideUrl = url;
    981             mLastShouldOverrideResourceRequest = null;
    982             mShouldOverrideUrlLoadingCallCount++;
    983             return false;
    984         }
    985 
    986         @Override
    987         public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    988             mLastShouldOverrideUrl = request.getUrl().toString();
    989             mLastShouldOverrideResourceRequest = request;
    990             mShouldOverrideUrlLoadingCallCount++;
    991             return false;
    992         }
    993 
    994         @Override
    995         public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail  detail) {
    996             mOnRenderProcessGoneCalled = true;
    997             mRenderProcessCrashed = detail.didCrash();
    998             return true;
    999         }
   1000     }
   1001 
   1002     private class SafeBrowsingBackToSafetyClient extends MockWebViewClient {
   1003         private WebResourceRequest mOnSafeBrowsingHitRequest;
   1004         private int mOnSafeBrowsingHitThreatType;
   1005 
   1006         public WebResourceRequest getOnSafeBrowsingHitRequest() {
   1007             return mOnSafeBrowsingHitRequest;
   1008         }
   1009 
   1010         public int getOnSafeBrowsingHitThreatType() {
   1011             return mOnSafeBrowsingHitThreatType;
   1012         }
   1013 
   1014         @Override
   1015         public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
   1016                 int threatType, SafeBrowsingResponse response) {
   1017             // Immediately go back to safety to return the network error code
   1018             mOnSafeBrowsingHitRequest = request;
   1019             mOnSafeBrowsingHitThreatType = threatType;
   1020             response.backToSafety(/* report */ true);
   1021         }
   1022     }
   1023 
   1024     private class SafeBrowsingProceedClient extends MockWebViewClient {
   1025         private WebResourceRequest mOnSafeBrowsingHitRequest;
   1026         private int mOnSafeBrowsingHitThreatType;
   1027 
   1028         public WebResourceRequest getOnSafeBrowsingHitRequest() {
   1029             return mOnSafeBrowsingHitRequest;
   1030         }
   1031 
   1032         public int getOnSafeBrowsingHitThreatType() {
   1033             return mOnSafeBrowsingHitThreatType;
   1034         }
   1035 
   1036         @Override
   1037         public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
   1038                 int threatType, SafeBrowsingResponse response) {
   1039             // Proceed through Safe Browsing warnings
   1040             mOnSafeBrowsingHitRequest = request;
   1041             mOnSafeBrowsingHitThreatType = threatType;
   1042             response.proceed(/* report */ true);
   1043         }
   1044     }
   1045 }
   1046