1/* 2 * Copyright 2018 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 17package androidx.webkit; 18 19import android.graphics.Bitmap; 20import android.net.Uri; 21import android.support.test.filters.MediumTest; 22import android.support.test.runner.AndroidJUnit4; 23import android.webkit.ValueCallback; 24import android.webkit.WebResourceRequest; 25import android.webkit.WebResourceResponse; 26import android.webkit.WebView; 27import android.webkit.WebViewClient; 28 29import org.junit.Assert; 30import org.junit.Assume; 31import org.junit.Before; 32import org.junit.Test; 33import org.junit.runner.RunWith; 34 35import java.util.HashMap; 36import java.util.Map; 37import java.util.concurrent.CountDownLatch; 38import java.util.concurrent.TimeUnit; 39 40@MediumTest 41@RunWith(AndroidJUnit4.class) 42public class WebViewClientCompatTest { 43 private WebViewOnUiThread mWebViewOnUiThread; 44 45 private static final long TEST_TIMEOUT = 20000L; 46 private static final String TEST_URL = "http://www.example.com/"; 47 private static final String TEST_SAFE_BROWSING_URL = 48 "chrome://safe-browsing/match?type=malware"; 49 50 @Before 51 public void setUp() { 52 mWebViewOnUiThread = new WebViewOnUiThread(); 53 } 54 55 @Test 56 public void testShouldOverrideUrlLoadingDefault() { 57 // This never calls into chromium, so we don't need to do any feature checks. 58 59 final MockWebViewClient webViewClient = new MockWebViewClient(); 60 61 // Create any valid WebResourceRequest, the return values don't matter much. 62 final WebResourceRequest resourceRequest = new WebResourceRequest() { 63 @Override 64 public Uri getUrl() { 65 return Uri.parse(TEST_URL); 66 } 67 68 @Override 69 public boolean isForMainFrame() { 70 return false; 71 } 72 73 @Override 74 public boolean isRedirect() { 75 return false; 76 } 77 78 @Override 79 public boolean hasGesture() { 80 return false; 81 } 82 83 @Override 84 public String getMethod() { 85 return "GET"; 86 } 87 88 @Override 89 public Map<String, String> getRequestHeaders() { 90 return new HashMap<String, String>(); 91 } 92 }; 93 94 Assert.assertFalse(webViewClient.shouldOverrideUrlLoading( 95 mWebViewOnUiThread.getWebViewOnCurrentThread(), resourceRequest)); 96 } 97 98 @Test 99 public void testShouldOverrideUrlLoading() throws InterruptedException { 100 Assume.assumeTrue( 101 WebViewFeature.isFeatureSupported(WebViewFeature.SHOULD_OVERRIDE_WITH_REDIRECTS)); 102 Assume.assumeTrue( 103 WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT)); 104 105 String data = "<html><body>" 106 + "<a href=\"" + TEST_URL + "\" id=\"link\">new page</a>" 107 + "</body></html>"; 108 mWebViewOnUiThread.loadDataAndWaitForCompletion(data, "text/html", null); 109 final CountDownLatch pageFinishedLatch = new CountDownLatch(1); 110 final MockWebViewClient webViewClient = new MockWebViewClient() { 111 @Override 112 public void onPageFinished(WebView view, String url) { 113 super.onPageFinished(view, url); 114 pageFinishedLatch.countDown(); 115 } 116 }; 117 mWebViewOnUiThread.setWebViewClient(webViewClient); 118 mWebViewOnUiThread.getSettings().setJavaScriptEnabled(true); 119 clickOnLinkUsingJs("link", mWebViewOnUiThread); 120 pageFinishedLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS); 121 Assert.assertEquals(TEST_URL, 122 webViewClient.getLastShouldOverrideResourceRequest().getUrl().toString()); 123 124 WebResourceRequest request = webViewClient.getLastShouldOverrideResourceRequest(); 125 Assert.assertNotNull(request); 126 Assert.assertTrue(request.isForMainFrame()); 127 Assert.assertFalse(WebResourceRequestCompat.isRedirect(request)); 128 Assert.assertFalse(request.hasGesture()); 129 } 130 131 private void clickOnLinkUsingJs(final String linkId, WebViewOnUiThread webViewOnUiThread) 132 throws InterruptedException { 133 final CountDownLatch callbackLatch = new CountDownLatch(1); 134 ValueCallback<String> callback = new ValueCallback<String>() { 135 @Override 136 public void onReceiveValue(String value) { 137 callbackLatch.countDown(); 138 } 139 }; 140 webViewOnUiThread.evaluateJavascript( 141 "document.getElementById('" + linkId + "').click();" 142 + "console.log('element with id [" + linkId + "] clicked');", callback); 143 Assert.assertTrue(callbackLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); 144 } 145 146 @Test 147 public void testOnReceivedError() throws Exception { 148 Assume.assumeTrue( 149 WebViewFeature.isFeatureSupported(WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR)); 150 Assume.assumeTrue( 151 WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE)); 152 153 final MockWebViewClient webViewClient = new MockWebViewClient(); 154 mWebViewOnUiThread.setWebViewClient(webViewClient); 155 156 String wrongUri = "invalidscheme://some/resource"; 157 Assert.assertNull(webViewClient.getOnReceivedResourceError()); 158 mWebViewOnUiThread.loadUrlAndWaitForCompletion(wrongUri); 159 Assert.assertNotNull(webViewClient.getOnReceivedResourceError()); 160 Assert.assertEquals(WebViewClient.ERROR_UNSUPPORTED_SCHEME, 161 webViewClient.getOnReceivedResourceError().getErrorCode()); 162 } 163 164 @Test 165 public void testOnReceivedErrorForSubresource() throws Exception { 166 Assume.assumeTrue( 167 WebViewFeature.isFeatureSupported(WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR)); 168 169 final MockWebViewClient webViewClient = new MockWebViewClient(); 170 mWebViewOnUiThread.setWebViewClient(webViewClient); 171 172 Assert.assertNull(webViewClient.getOnReceivedResourceError()); 173 String data = "<html>" 174 + " <body>" 175 + " <img src=\"invalidscheme://some/resource\" />" 176 + " </body>" 177 + "</html>"; 178 179 mWebViewOnUiThread.loadDataAndWaitForCompletion(data, "text/html", null); 180 Assert.assertNotNull(webViewClient.getOnReceivedResourceError()); 181 Assert.assertEquals(WebViewClient.ERROR_UNSUPPORTED_SCHEME, 182 webViewClient.getOnReceivedResourceError().getErrorCode()); 183 } 184 185 @Test 186 public void testOnSafeBrowsingHitBackToSafety() throws Throwable { 187 Assume.assumeTrue(WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_HIT)); 188 Assume.assumeTrue(WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_ENABLE)); 189 Assume.assumeTrue(WebViewFeature.isFeatureSupported( 190 WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)); 191 Assume.assumeTrue( 192 WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE)); 193 194 final SafeBrowsingBackToSafetyClient backToSafetyWebViewClient = 195 new SafeBrowsingBackToSafetyClient(); 196 mWebViewOnUiThread.setWebViewClient(backToSafetyWebViewClient); 197 WebSettingsCompat.setSafeBrowsingEnabled(mWebViewOnUiThread.getSettings(), true); 198 199 // Load any page 200 String data = "<html><body>some safe page</body></html>"; 201 mWebViewOnUiThread.loadDataAndWaitForCompletion(data, "text/html", null); 202 final String originalUrl = mWebViewOnUiThread.getUrl(); 203 204 enableSafeBrowsingAndLoadUnsafePage(backToSafetyWebViewClient); 205 206 // Back to safety should produce a network error 207 Assert.assertNotNull(backToSafetyWebViewClient.getOnReceivedResourceError()); 208 Assert.assertEquals(WebViewClient.ERROR_UNSAFE_RESOURCE, 209 backToSafetyWebViewClient.getOnReceivedResourceError().getErrorCode()); 210 211 // Check that we actually navigated backward 212 Assert.assertEquals(originalUrl, mWebViewOnUiThread.getUrl()); 213 } 214 215 @Test 216 public void testOnSafeBrowsingHitProceed() throws Throwable { 217 Assume.assumeTrue(WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_HIT)); 218 Assume.assumeTrue(WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_ENABLE)); 219 Assume.assumeTrue(WebViewFeature.isFeatureSupported( 220 WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED)); 221 222 final SafeBrowsingProceedClient proceedWebViewClient = new SafeBrowsingProceedClient(); 223 mWebViewOnUiThread.setWebViewClient(proceedWebViewClient); 224 WebSettingsCompat.setSafeBrowsingEnabled(mWebViewOnUiThread.getSettings(), true); 225 226 // Load any page 227 String data = "<html><body>some safe page</body></html>"; 228 mWebViewOnUiThread.loadDataAndWaitForCompletion(data, "text/html", null); 229 230 enableSafeBrowsingAndLoadUnsafePage(proceedWebViewClient); 231 232 // Check that we actually proceeded 233 Assert.assertEquals(TEST_SAFE_BROWSING_URL, mWebViewOnUiThread.getUrl()); 234 } 235 236 private void enableSafeBrowsingAndLoadUnsafePage(SafeBrowsingClient client) throws Throwable { 237 // Note: Safe Browsing depends on user opt-in as well, so we can't assume it's actually 238 // enabled. #getSafeBrowsingEnabled will tell us the true state of whether Safe Browsing is 239 // enabled. 240 boolean deviceSupportsSafeBrowsing = 241 WebSettingsCompat.getSafeBrowsingEnabled(mWebViewOnUiThread.getSettings()); 242 Assume.assumeTrue(deviceSupportsSafeBrowsing); 243 244 Assert.assertNull(client.getOnReceivedResourceError()); 245 mWebViewOnUiThread.loadUrlAndWaitForCompletion(TEST_SAFE_BROWSING_URL); 246 247 Assert.assertEquals(TEST_SAFE_BROWSING_URL, 248 client.getOnSafeBrowsingHitRequest().getUrl().toString()); 249 Assert.assertTrue(client.getOnSafeBrowsingHitRequest().isForMainFrame()); 250 } 251 252 @Test 253 public void testOnPageCommitVisibleCalled() throws Exception { 254 Assume.assumeTrue(WebViewFeature.isFeatureSupported(WebViewFeature.VISUAL_STATE_CALLBACK)); 255 256 final CountDownLatch callbackLatch = new CountDownLatch(1); 257 258 mWebViewOnUiThread.setWebViewClient(new WebViewClientCompat() { 259 @Override 260 public void onPageCommitVisible(WebView view, String url) { 261 Assert.assertEquals(url, "about:blank"); 262 callbackLatch.countDown(); 263 } 264 }); 265 266 mWebViewOnUiThread.loadUrl("about:blank"); 267 Assert.assertTrue(callbackLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); 268 } 269 270 private class MockWebViewClient extends WebViewOnUiThread.WaitForLoadedClient { 271 private boolean mOnPageStartedCalled; 272 private boolean mOnPageFinishedCalled; 273 private boolean mOnLoadResourceCalled; 274 private WebResourceErrorCompat mOnReceivedResourceError; 275 private WebResourceResponse mOnReceivedHttpError; 276 private WebResourceRequest mLastShouldOverrideResourceRequest; 277 278 MockWebViewClient() { 279 super(mWebViewOnUiThread); 280 } 281 282 public WebResourceErrorCompat getOnReceivedResourceError() { 283 return mOnReceivedResourceError; 284 } 285 286 public WebResourceResponse getOnReceivedHttpError() { 287 return mOnReceivedHttpError; 288 } 289 290 public WebResourceRequest getLastShouldOverrideResourceRequest() { 291 return mLastShouldOverrideResourceRequest; 292 } 293 294 @Override 295 public void onPageStarted(WebView view, String url, Bitmap favicon) { 296 super.onPageStarted(view, url, favicon); 297 mOnPageStartedCalled = true; 298 } 299 300 @Override 301 public void onPageFinished(WebView view, String url) { 302 super.onPageFinished(view, url); 303 Assert.assertTrue(mOnPageStartedCalled); 304 Assert.assertTrue(mOnLoadResourceCalled); 305 mOnPageFinishedCalled = true; 306 } 307 308 @Override 309 public void onLoadResource(WebView view, String url) { 310 super.onLoadResource(view, url); 311 Assert.assertTrue(mOnPageStartedCalled); 312 mOnLoadResourceCalled = true; 313 } 314 315 @Override 316 @SuppressWarnings("deprecation") 317 public void onReceivedError(WebView view, int errorCode, 318 String description, String failingUrl) { 319 // This can be called if a test runs for a WebView which does not support the {@link 320 // WebViewFeature#RECEIVE_WEB_RESOURCE_ERROR} feature. 321 } 322 323 @Override 324 public void onReceivedError(WebView view, WebResourceRequest request, 325 WebResourceErrorCompat error) { 326 mOnReceivedResourceError = error; 327 } 328 329 @Override 330 public void onReceivedHttpError(WebView view, WebResourceRequest request, 331 WebResourceResponse errorResponse) { 332 super.onReceivedHttpError(view, request, errorResponse); 333 mOnReceivedHttpError = errorResponse; 334 } 335 336 @Override 337 @SuppressWarnings("deprecation") 338 public boolean shouldOverrideUrlLoading(WebView view, String url) { 339 // This can be called if a test runs for a WebView which does not support the {@link 340 // WebViewFeature#SHOULD_OVERRIDE_WITH_REDIRECTS} feature. 341 return false; 342 } 343 344 @Override 345 public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { 346 mLastShouldOverrideResourceRequest = request; 347 return false; 348 } 349 } 350 351 private class SafeBrowsingClient extends MockWebViewClient { 352 private WebResourceRequest mOnSafeBrowsingHitRequest; 353 private int mOnSafeBrowsingHitThreatType; 354 355 public WebResourceRequest getOnSafeBrowsingHitRequest() { 356 return mOnSafeBrowsingHitRequest; 357 } 358 359 public void setOnSafeBrowsingHitRequest(WebResourceRequest request) { 360 mOnSafeBrowsingHitRequest = request; 361 } 362 363 public int getOnSafeBrowsingHitThreatType() { 364 return mOnSafeBrowsingHitThreatType; 365 } 366 367 public void setOnSafeBrowsingHitThreatType(int type) { 368 mOnSafeBrowsingHitThreatType = type; 369 } 370 } 371 372 private class SafeBrowsingBackToSafetyClient extends SafeBrowsingClient { 373 @Override 374 public void onSafeBrowsingHit(WebView view, WebResourceRequest request, 375 int threatType, SafeBrowsingResponseCompat response) { 376 // Immediately go back to safety to return the network error code 377 setOnSafeBrowsingHitRequest(request); 378 setOnSafeBrowsingHitThreatType(threatType); 379 response.backToSafety(/* report */ true); 380 } 381 } 382 383 private class SafeBrowsingProceedClient extends SafeBrowsingClient { 384 @Override 385 public void onSafeBrowsingHit(WebView view, WebResourceRequest request, 386 int threatType, SafeBrowsingResponseCompat response) { 387 // Proceed through Safe Browsing warnings 388 setOnSafeBrowsingHitRequest(request); 389 setOnSafeBrowsingHitThreatType(threatType); 390 response.proceed(/* report */ true); 391 } 392 } 393} 394