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 package android.webkit.cts; 17 18 import static org.hamcrest.MatcherAssert.assertThat; 19 import static org.hamcrest.Matchers.greaterThan; 20 import static org.hamcrest.Matchers.lessThan; 21 22 import android.content.Context; 23 import android.graphics.Bitmap; 24 import android.graphics.Color; 25 import android.net.http.SslError; 26 import android.os.Build; 27 import android.os.Message; 28 import android.platform.test.annotations.AppModeFull; 29 import android.test.ActivityInstrumentationTestCase2; 30 import android.util.Base64; 31 import android.util.Log; 32 import android.view.ViewGroup; 33 import android.webkit.ConsoleMessage; 34 import android.webkit.SslErrorHandler; 35 import android.webkit.WebChromeClient; 36 import android.webkit.WebIconDatabase; 37 import android.webkit.WebResourceResponse; 38 import android.webkit.WebResourceRequest; 39 import android.webkit.WebSettings; 40 import android.webkit.WebSettings.TextSize; 41 import android.webkit.WebStorage; 42 import android.webkit.WebView; 43 import android.webkit.WebViewClient; 44 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient; 45 import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient; 46 47 import com.android.compatibility.common.util.NullWebViewUtils; 48 import com.android.compatibility.common.util.PollingCheck; 49 import com.google.common.util.concurrent.SettableFuture; 50 51 import java.io.ByteArrayInputStream; 52 import java.io.FileOutputStream; 53 import java.nio.charset.StandardCharsets; 54 import java.util.HashMap; 55 import java.util.Locale; 56 import java.util.Map; 57 import java.util.concurrent.CountDownLatch; 58 import java.util.regex.Matcher; 59 import java.util.regex.Pattern; 60 61 import java.util.concurrent.BlockingQueue; 62 import java.util.concurrent.LinkedBlockingQueue; 63 64 /** 65 * Tests for {@link android.webkit.WebSettings} 66 */ 67 @AppModeFull 68 public class WebSettingsTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> { 69 70 private static final int WEBVIEW_TIMEOUT = 5000; 71 private static final String LOG_TAG = "WebSettingsTest"; 72 73 private final String EMPTY_IMAGE_HEIGHT = "0"; 74 private final String NETWORK_IMAGE_HEIGHT = "48"; // See getNetworkImageHtml() 75 private final String DATA_URL_IMAGE_HTML = "<html>" + 76 "<head><script>function updateTitle(){" + 77 "document.title=document.getElementById('img').naturalHeight;}</script></head>" + 78 "<body onload='updateTitle()'>" + 79 "<img id='img' onload='updateTitle()' src='data:image/png;base64,iVBORw0KGgoAAA" + 80 "ANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAAAXNSR0IArs4c6QAAAA1JREFUCB0BAgD9/wAAAAIAAc3j" + 81 "0SsAAAAASUVORK5CYII=" + 82 "'></body></html>"; 83 private final String DATA_URL_IMAGE_HEIGHT = "1"; 84 85 private WebSettings mSettings; 86 private CtsTestServer mWebServer; 87 private WebViewOnUiThread mOnUiThread; 88 private Context mContext; 89 90 public WebSettingsTest() { 91 super("android.webkit.cts", WebViewCtsActivity.class); 92 } 93 94 @Override 95 protected void setUp() throws Exception { 96 super.setUp(); 97 WebView webview = getActivity().getWebView(); 98 if (webview != null) { 99 mOnUiThread = new WebViewOnUiThread(webview); 100 mSettings = mOnUiThread.getSettings(); 101 } 102 mContext = getInstrumentation().getTargetContext(); 103 } 104 105 @Override 106 protected void tearDown() throws Exception { 107 if (mWebServer != null) { 108 mWebServer.shutdown(); 109 } 110 if (mOnUiThread != null) { 111 mOnUiThread.cleanUp(); 112 } 113 super.tearDown(); 114 } 115 116 /** 117 * Verifies that the default user agent string follows the format defined in Android 118 * compatibility definition (tokens in angle brackets are variables, tokens in square 119 * brackets are optional): 120 * <p/> 121 * Mozilla/5.0 (Linux; Android <version>; [<devicemodel>] [Build/<buildID>]; wv) 122 * AppleWebKit/<major>.<minor> (KHTML, like Gecko) Version/<major>.<minor> 123 * Chrome/<major>.<minor>.<branch>.<build>[ Mobile] Safari/<major>.<minor> 124 */ 125 public void testUserAgentString_default() { 126 if (!NullWebViewUtils.isWebViewAvailable()) { 127 return; 128 } 129 checkUserAgentStringHelper(mSettings.getUserAgentString(), true); 130 } 131 132 /** 133 * Verifies that the useragent testing regex is actually correct, because it's very complex. 134 */ 135 public void testUserAgentStringTest() { 136 // All test UAs share the same prefix and suffix; only the middle part varies. 137 final String prefix = "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; "; 138 final String suffix = "wv) AppleWebKit/0.0 (KHTML, like Gecko) Version/4.0 Chrome/0.0.0.0 Safari/0.0"; 139 140 // Valid cases: 141 // Both model and build present 142 checkUserAgentStringHelper(prefix + Build.MODEL + " Build/" + Build.ID + "; " + suffix, true); 143 // Just model 144 checkUserAgentStringHelper(prefix + Build.MODEL + "; " + suffix, true); 145 // Just build 146 checkUserAgentStringHelper(prefix + "Build/" + Build.ID + "; " + suffix, true); 147 // Neither 148 checkUserAgentStringHelper(prefix + suffix, true); 149 150 // Invalid cases: 151 // No space between model and build 152 checkUserAgentStringHelper(prefix + Build.MODEL + "Build/" + Build.ID + "; " + suffix, false); 153 // No semicolon after model and/or build 154 checkUserAgentStringHelper(prefix + Build.MODEL + " Build/" + Build.ID + suffix, false); 155 checkUserAgentStringHelper(prefix + Build.MODEL + suffix, false); 156 checkUserAgentStringHelper(prefix + "Build/" + Build.ID + suffix, false); 157 // Double semicolon when both omitted 158 checkUserAgentStringHelper(prefix + "; " + suffix, false); 159 } 160 161 /** 162 * Helper function to validate that a given useragent string is or is not valid. 163 */ 164 private void checkUserAgentStringHelper(final String useragent, boolean shouldMatch) { 165 String expectedRelease; 166 if ("REL".equals(Build.VERSION.CODENAME)) { 167 expectedRelease = Pattern.quote(Build.VERSION.RELEASE); 168 } else { 169 // Non-release builds don't include real release version, be lenient. 170 expectedRelease = "[^;]+"; 171 } 172 173 // Build expected regex inserting the appropriate variables, as this is easier to 174 // understand and get right than matching any possible useragent and comparing the 175 // variables afterward. 176 final String patternString = 177 // Release version always has a semicolon after it: 178 Pattern.quote("Mozilla/5.0 (Linux; Android ") + expectedRelease + ";" + 179 // Model is optional, but if present must have a space first: 180 "( " + Pattern.quote(Build.MODEL) + ")?" + 181 // Build is optional, but if present must have a space first: 182 "( Build/" + Pattern.quote(Build.ID) + ")?" + 183 // We want a semicolon before the wv token, but we don't want to have two in a row 184 // if both model and build are omitted. Lookbehind assertions ensure either: 185 // - the previous character is a semicolon 186 // - or the previous character is NOT a semicolon AND a semicolon is added here. 187 "((?<=;)|(?<!;);)" + 188 // After that we can just check for " wv)" to finish the platform section: 189 Pattern.quote(" wv) ") + 190 // The rest of the expression is browser tokens and is fairly simple: 191 "AppleWebKit/\\d+\\.\\d+ " + 192 Pattern.quote("(KHTML, like Gecko) Version/4.0 ") + 193 "Chrome/\\d+\\.\\d+\\.\\d+\\.\\d+ " + 194 "(Mobile )?Safari/\\d+\\.\\d+"; 195 final Pattern userAgentExpr = Pattern.compile(patternString); 196 Matcher patternMatcher = userAgentExpr.matcher(useragent); 197 if (shouldMatch) { 198 assertTrue(String.format("CDD(3.4.1/C-1-3) User agent string did not match expected pattern. \n" + 199 "Expected pattern:\n%s\nActual:\n%s", patternString, useragent), 200 patternMatcher.find()); 201 } else { 202 assertFalse(String.format("Known-bad user agent string incorrectly matched. \n" + 203 "Expected pattern:\n%s\nActual:\n%s", patternString, useragent), 204 patternMatcher.find()); 205 } 206 } 207 208 public void testAccessUserAgentString() throws Exception { 209 if (!NullWebViewUtils.isWebViewAvailable()) { 210 return; 211 } 212 startWebServer(); 213 String url = mWebServer.getUserAgentUrl(); 214 215 String defaultUserAgent = mSettings.getUserAgentString(); 216 assertNotNull(defaultUserAgent); 217 mOnUiThread.loadUrlAndWaitForCompletion(url); 218 assertEquals(defaultUserAgent, mOnUiThread.getTitle()); 219 220 // attempting to set a null string has no effect 221 mSettings.setUserAgentString(null); 222 assertEquals(defaultUserAgent, mSettings.getUserAgentString()); 223 mOnUiThread.loadUrlAndWaitForCompletion(url); 224 assertEquals(defaultUserAgent, mOnUiThread.getTitle()); 225 226 // attempting to set an empty string has no effect 227 mSettings.setUserAgentString(""); 228 assertEquals(defaultUserAgent, mSettings.getUserAgentString()); 229 mOnUiThread.loadUrlAndWaitForCompletion(url); 230 assertEquals(defaultUserAgent, mOnUiThread.getTitle()); 231 232 String customUserAgent = "Cts/test"; 233 mSettings.setUserAgentString(customUserAgent); 234 assertEquals(customUserAgent, mSettings.getUserAgentString()); 235 mOnUiThread.loadUrlAndWaitForCompletion(url); 236 assertEquals(customUserAgent, mOnUiThread.getTitle()); 237 } 238 239 public void testAccessAllowFileAccess() { 240 if (!NullWebViewUtils.isWebViewAvailable()) { 241 return; 242 } 243 // This test is not compatible with 4.0.3 244 if ("4.0.3".equals(Build.VERSION.RELEASE)) { 245 return; 246 } 247 248 assertTrue(mSettings.getAllowFileAccess()); 249 250 String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.HELLO_WORLD_URL); 251 mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); 252 assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle()); 253 254 fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.BR_TAG_URL); 255 mSettings.setAllowFileAccess(false); 256 assertFalse(mSettings.getAllowFileAccess()); 257 mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); 258 // android_asset URLs should still be loaded when even with file access 259 // disabled. 260 assertEquals(TestHtmlConstants.BR_TAG_TITLE, mOnUiThread.getTitle()); 261 262 // Files on the file system should not be loaded. 263 mOnUiThread.loadUrlAndWaitForCompletion(TestHtmlConstants.LOCAL_FILESYSTEM_URL); 264 assertEquals(TestHtmlConstants.WEBPAGE_NOT_AVAILABLE_TITLE, mOnUiThread.getTitle()); 265 } 266 267 public void testAccessCacheMode_defaultValue() throws Throwable { 268 if (!NullWebViewUtils.isWebViewAvailable()) { 269 return; 270 } 271 assertEquals(WebSettings.LOAD_DEFAULT, mSettings.getCacheMode()); 272 } 273 274 private void openIconDatabase() throws InterruptedException { 275 WebkitUtils.onMainThreadSync(() -> { 276 // getInstance must run on the UI thread 277 WebIconDatabase iconDb = WebIconDatabase.getInstance(); 278 String dbPath = getActivity().getFilesDir().toString() + "/icons"; 279 iconDb.open(dbPath); 280 }); 281 getInstrumentation().waitForIdleSync(); 282 Thread.sleep(100); // Wait for open to be received on the icon db thread. 283 } 284 285 public void testAccessCacheMode_cacheElseNetwork() throws Throwable { 286 if (!NullWebViewUtils.isWebViewAvailable()) { 287 return; 288 } 289 openIconDatabase(); 290 final IconListenerClient iconListener = new IconListenerClient(); 291 mOnUiThread.setWebChromeClient(iconListener); 292 startWebServer(); 293 294 mSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 295 assertEquals(WebSettings.LOAD_CACHE_ELSE_NETWORK, mSettings.getCacheMode()); 296 int initialRequestCount = mWebServer.getRequestCount(); 297 loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 298 iconListener.waitForNextIcon(); 299 int requestCountAfterFirstLoad = mWebServer.getRequestCount(); 300 assertTrue("Must fetch non-cached resource from server", 301 requestCountAfterFirstLoad > initialRequestCount); 302 iconListener.drainIconQueue(); 303 loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 304 iconListener.waitForNextIcon(); 305 int requestCountAfterSecondLoad = mWebServer.getRequestCount(); 306 assertEquals("Expected to use cache instead of re-fetching resource", 307 requestCountAfterSecondLoad, requestCountAfterFirstLoad); 308 } 309 310 public void testAccessCacheMode_noCache() throws Throwable { 311 if (!NullWebViewUtils.isWebViewAvailable()) { 312 return; 313 } 314 openIconDatabase(); 315 final IconListenerClient iconListener = new IconListenerClient(); 316 mOnUiThread.setWebChromeClient(iconListener); 317 startWebServer(); 318 319 mSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); 320 assertEquals(WebSettings.LOAD_NO_CACHE, mSettings.getCacheMode()); 321 int initialRequestCount = mWebServer.getRequestCount(); 322 loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 323 iconListener.waitForNextIcon(); 324 int requestCountAfterFirstLoad = mWebServer.getRequestCount(); 325 assertTrue("Must fetch non-cached resource from server", 326 requestCountAfterFirstLoad > initialRequestCount); 327 iconListener.drainIconQueue(); 328 loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 329 iconListener.waitForNextIcon(); 330 int requestCountAfterSecondLoad = mWebServer.getRequestCount(); 331 assertTrue("Expected to re-fetch resource instead of caching", 332 requestCountAfterSecondLoad > requestCountAfterFirstLoad); 333 } 334 335 public void testAccessCacheMode_cacheOnly() throws Throwable { 336 if (!NullWebViewUtils.isWebViewAvailable()) { 337 return; 338 } 339 openIconDatabase(); 340 final IconListenerClient iconListener = new IconListenerClient(); 341 mOnUiThread.setWebChromeClient(iconListener); 342 startWebServer(); 343 344 // As a precondition, get the icon in the cache. 345 mSettings.setCacheMode(WebSettings.LOAD_DEFAULT); 346 loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 347 iconListener.waitForNextIcon(); 348 349 mSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY); 350 assertEquals(WebSettings.LOAD_CACHE_ONLY, mSettings.getCacheMode()); 351 iconListener.drainIconQueue(); 352 int initialRequestCount = mWebServer.getRequestCount(); 353 loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 354 iconListener.waitForNextIcon(); 355 int requestCountAfterFirstLoad = mWebServer.getRequestCount(); 356 assertEquals("Expected to use cache instead of fetching resource", 357 requestCountAfterFirstLoad, initialRequestCount); 358 } 359 360 public void testAccessCursiveFontFamily() throws Exception { 361 if (!NullWebViewUtils.isWebViewAvailable()) { 362 return; 363 } 364 assertNotNull(mSettings.getCursiveFontFamily()); 365 366 String newCusiveFamily = "Apple Chancery"; 367 mSettings.setCursiveFontFamily(newCusiveFamily); 368 assertEquals(newCusiveFamily, mSettings.getCursiveFontFamily()); 369 } 370 371 public void testAccessFantasyFontFamily() { 372 if (!NullWebViewUtils.isWebViewAvailable()) { 373 return; 374 } 375 assertNotNull(mSettings.getFantasyFontFamily()); 376 377 String newFantasyFamily = "Papyrus"; 378 mSettings.setFantasyFontFamily(newFantasyFamily); 379 assertEquals(newFantasyFamily, mSettings.getFantasyFontFamily()); 380 } 381 382 public void testAccessFixedFontFamily() { 383 if (!NullWebViewUtils.isWebViewAvailable()) { 384 return; 385 } 386 assertNotNull(mSettings.getFixedFontFamily()); 387 388 String newFixedFamily = "Courier"; 389 mSettings.setFixedFontFamily(newFixedFamily); 390 assertEquals(newFixedFamily, mSettings.getFixedFontFamily()); 391 } 392 393 public void testAccessSansSerifFontFamily() { 394 if (!NullWebViewUtils.isWebViewAvailable()) { 395 return; 396 } 397 assertNotNull(mSettings.getSansSerifFontFamily()); 398 399 String newFixedFamily = "Verdana"; 400 mSettings.setSansSerifFontFamily(newFixedFamily); 401 assertEquals(newFixedFamily, mSettings.getSansSerifFontFamily()); 402 } 403 404 public void testAccessSerifFontFamily() { 405 if (!NullWebViewUtils.isWebViewAvailable()) { 406 return; 407 } 408 assertNotNull(mSettings.getSerifFontFamily()); 409 410 String newSerifFamily = "Times"; 411 mSettings.setSerifFontFamily(newSerifFamily); 412 assertEquals(newSerifFamily, mSettings.getSerifFontFamily()); 413 } 414 415 public void testAccessStandardFontFamily() { 416 if (!NullWebViewUtils.isWebViewAvailable()) { 417 return; 418 } 419 assertNotNull(mSettings.getStandardFontFamily()); 420 421 String newStandardFamily = "Times"; 422 mSettings.setStandardFontFamily(newStandardFamily); 423 assertEquals(newStandardFamily, mSettings.getStandardFontFamily()); 424 } 425 426 public void testAccessDefaultFontSize() { 427 if (!NullWebViewUtils.isWebViewAvailable()) { 428 return; 429 } 430 int defaultSize = mSettings.getDefaultFontSize(); 431 assertThat(defaultSize, greaterThan(0)); 432 433 mSettings.setDefaultFontSize(1000); 434 int maxSize = mSettings.getDefaultFontSize(); 435 // cannot check exact size set, since the implementation caps it at an arbitrary limit 436 assertThat("max size should be greater than default size", 437 maxSize, 438 greaterThan(defaultSize)); 439 440 mSettings.setDefaultFontSize(-10); 441 int minSize = mSettings.getDefaultFontSize(); 442 assertThat(minSize, greaterThan(0)); 443 assertThat(minSize, lessThan(maxSize)); 444 445 mSettings.setDefaultFontSize(10); 446 assertEquals(10, mSettings.getDefaultFontSize()); 447 } 448 449 public void testAccessDefaultFixedFontSize() { 450 if (!NullWebViewUtils.isWebViewAvailable()) { 451 return; 452 } 453 int defaultSize = mSettings.getDefaultFixedFontSize(); 454 assertThat(defaultSize, greaterThan(0)); 455 456 mSettings.setDefaultFixedFontSize(1000); 457 int maxSize = mSettings.getDefaultFixedFontSize(); 458 // cannot check exact size set, since the implementation caps it at an arbitrary limit 459 assertThat("max size should be greater than default size", 460 maxSize, 461 greaterThan(defaultSize)); 462 463 mSettings.setDefaultFixedFontSize(-10); 464 int minSize = mSettings.getDefaultFixedFontSize(); 465 assertThat(minSize, greaterThan(0)); 466 assertThat(minSize, lessThan(maxSize)); 467 468 mSettings.setDefaultFixedFontSize(10); 469 assertEquals(10, mSettings.getDefaultFixedFontSize()); 470 } 471 472 public void testAccessDefaultTextEncodingName() { 473 if (!NullWebViewUtils.isWebViewAvailable()) { 474 return; 475 } 476 assertNotNull(mSettings.getDefaultTextEncodingName()); 477 478 String newEncodingName = "iso-8859-1"; 479 mSettings.setDefaultTextEncodingName(newEncodingName); 480 assertEquals(newEncodingName, mSettings.getDefaultTextEncodingName()); 481 } 482 483 public void testAccessJavaScriptCanOpenWindowsAutomatically() throws Exception { 484 if (!NullWebViewUtils.isWebViewAvailable()) { 485 return; 486 } 487 mSettings.setJavaScriptEnabled(true); 488 mSettings.setSupportMultipleWindows(true); 489 startWebServer(); 490 491 final WebView childWebView = mOnUiThread.createWebView(); 492 final SettableFuture<Void> createWindowFuture = SettableFuture.create(); 493 mOnUiThread.setWebChromeClient(new WebChromeClient() { 494 @Override 495 public boolean onCreateWindow( 496 WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { 497 WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; 498 transport.setWebView(childWebView); 499 resultMsg.sendToTarget(); 500 createWindowFuture.set(null); 501 return true; 502 } 503 }); 504 505 mSettings.setJavaScriptCanOpenWindowsAutomatically(false); 506 assertFalse(mSettings.getJavaScriptCanOpenWindowsAutomatically()); 507 mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.POPUP_URL)); 508 new PollingCheck(WEBVIEW_TIMEOUT) { 509 @Override 510 protected boolean check() { 511 return "Popup blocked".equals(mOnUiThread.getTitle()); 512 } 513 }.run(); 514 assertFalse("onCreateWindow should not have been called yet", createWindowFuture.isDone()); 515 516 mSettings.setJavaScriptCanOpenWindowsAutomatically(true); 517 assertTrue(mSettings.getJavaScriptCanOpenWindowsAutomatically()); 518 mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.POPUP_URL)); 519 WebkitUtils.waitForFuture(createWindowFuture); 520 } 521 522 public void testAccessJavaScriptEnabled() throws Exception { 523 if (!NullWebViewUtils.isWebViewAvailable()) { 524 return; 525 } 526 mSettings.setJavaScriptEnabled(true); 527 assertTrue(mSettings.getJavaScriptEnabled()); 528 loadAssetUrl(TestHtmlConstants.JAVASCRIPT_URL); 529 new PollingCheck(WEBVIEW_TIMEOUT) { 530 @Override 531 protected boolean check() { 532 return "javascript on".equals(mOnUiThread.getTitle()); 533 } 534 }.run(); 535 536 mSettings.setJavaScriptEnabled(false); 537 assertFalse(mSettings.getJavaScriptEnabled()); 538 loadAssetUrl(TestHtmlConstants.JAVASCRIPT_URL); 539 new PollingCheck(WEBVIEW_TIMEOUT) { 540 @Override 541 protected boolean check() { 542 return "javascript off".equals(mOnUiThread.getTitle()); 543 } 544 }.run(); 545 546 } 547 548 public void testAccessLayoutAlgorithm() { 549 if (!NullWebViewUtils.isWebViewAvailable()) { 550 return; 551 } 552 assertEquals(WebSettings.LayoutAlgorithm.NARROW_COLUMNS, mSettings.getLayoutAlgorithm()); 553 554 mSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); 555 assertEquals(WebSettings.LayoutAlgorithm.NORMAL, mSettings.getLayoutAlgorithm()); 556 557 mSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); 558 assertEquals(WebSettings.LayoutAlgorithm.SINGLE_COLUMN, mSettings.getLayoutAlgorithm()); 559 } 560 561 public void testAccessMinimumFontSize() { 562 if (!NullWebViewUtils.isWebViewAvailable()) { 563 return; 564 } 565 assertEquals(8, mSettings.getMinimumFontSize()); 566 567 mSettings.setMinimumFontSize(100); 568 assertEquals(72, mSettings.getMinimumFontSize()); 569 570 mSettings.setMinimumFontSize(-10); 571 assertEquals(1, mSettings.getMinimumFontSize()); 572 573 mSettings.setMinimumFontSize(10); 574 assertEquals(10, mSettings.getMinimumFontSize()); 575 } 576 577 public void testAccessMinimumLogicalFontSize() { 578 if (!NullWebViewUtils.isWebViewAvailable()) { 579 return; 580 } 581 assertEquals(8, mSettings.getMinimumLogicalFontSize()); 582 583 mSettings.setMinimumLogicalFontSize(100); 584 assertEquals(72, mSettings.getMinimumLogicalFontSize()); 585 586 mSettings.setMinimumLogicalFontSize(-10); 587 assertEquals(1, mSettings.getMinimumLogicalFontSize()); 588 589 mSettings.setMinimumLogicalFontSize(10); 590 assertEquals(10, mSettings.getMinimumLogicalFontSize()); 591 } 592 593 public void testAccessPluginsEnabled() { 594 if (!NullWebViewUtils.isWebViewAvailable()) { 595 return; 596 } 597 assertFalse(mSettings.getPluginsEnabled()); 598 599 mSettings.setPluginsEnabled(true); 600 assertTrue(mSettings.getPluginsEnabled()); 601 } 602 603 /** 604 * This should remain functionally equivalent to 605 * androidx.webkit.WebSettingsCompatTest#testOffscreenPreRaster. Modifications to this test 606 * should be reflected in that test as necessary. See http://go/modifying-webview-cts. 607 */ 608 public void testOffscreenPreRaster() { 609 if (!NullWebViewUtils.isWebViewAvailable()) { 610 return; 611 } 612 assertFalse(mSettings.getOffscreenPreRaster()); 613 614 mSettings.setOffscreenPreRaster(true); 615 assertTrue(mSettings.getOffscreenPreRaster()); 616 } 617 618 public void testAccessPluginsPath() { 619 if (!NullWebViewUtils.isWebViewAvailable()) { 620 return; 621 } 622 assertNotNull(mSettings.getPluginsPath()); 623 624 String pluginPath = "pluginPath"; 625 mSettings.setPluginsPath(pluginPath); 626 assertEquals("Plugin path always empty", "", mSettings.getPluginsPath()); 627 } 628 629 public void testAccessTextSize() { 630 if (!NullWebViewUtils.isWebViewAvailable()) { 631 return; 632 } 633 mSettings.setTextSize(TextSize.NORMAL); 634 assertEquals(TextSize.NORMAL, mSettings.getTextSize()); 635 636 mSettings.setTextSize(TextSize.LARGER); 637 assertEquals(TextSize.LARGER, mSettings.getTextSize()); 638 639 mSettings.setTextSize(TextSize.LARGEST); 640 assertEquals(TextSize.LARGEST, mSettings.getTextSize()); 641 642 mSettings.setTextSize(TextSize.SMALLER); 643 assertEquals(TextSize.SMALLER, mSettings.getTextSize()); 644 645 mSettings.setTextSize(TextSize.SMALLEST); 646 assertEquals(TextSize.SMALLEST, mSettings.getTextSize()); 647 } 648 649 public void testAccessUseDoubleTree() { 650 if (!NullWebViewUtils.isWebViewAvailable()) { 651 return; 652 } 653 assertFalse(mSettings.getUseDoubleTree()); 654 655 mSettings.setUseDoubleTree(true); 656 assertFalse("setUseDoubleTree should be a no-op", mSettings.getUseDoubleTree()); 657 } 658 659 public void testAccessUseWideViewPort() { 660 if (!NullWebViewUtils.isWebViewAvailable()) { 661 return; 662 } 663 assertFalse(mSettings.getUseWideViewPort()); 664 665 mSettings.setUseWideViewPort(true); 666 assertTrue(mSettings.getUseWideViewPort()); 667 } 668 669 public void testSetNeedInitialFocus() { 670 if (!NullWebViewUtils.isWebViewAvailable()) { 671 return; 672 } 673 mSettings.setNeedInitialFocus(false); 674 675 mSettings.setNeedInitialFocus(true); 676 } 677 678 public void testSetRenderPriority() { 679 if (!NullWebViewUtils.isWebViewAvailable()) { 680 return; 681 } 682 mSettings.setRenderPriority(WebSettings.RenderPriority.HIGH); 683 684 mSettings.setRenderPriority(WebSettings.RenderPriority.LOW); 685 686 mSettings.setRenderPriority(WebSettings.RenderPriority.NORMAL); 687 } 688 689 public void testAccessSupportMultipleWindows() { 690 if (!NullWebViewUtils.isWebViewAvailable()) { 691 return; 692 } 693 assertFalse(mSettings.supportMultipleWindows()); 694 695 mSettings.setSupportMultipleWindows(true); 696 assertTrue(mSettings.supportMultipleWindows()); 697 } 698 699 public void testAccessSupportZoom() throws Throwable { 700 if (!NullWebViewUtils.isWebViewAvailable()) { 701 return; 702 } 703 assertTrue(mSettings.supportZoom()); 704 705 mSettings.setSupportZoom(false); 706 assertFalse(mSettings.supportZoom()); 707 } 708 709 public void testAccessBuiltInZoomControls() throws Throwable { 710 if (!NullWebViewUtils.isWebViewAvailable()) { 711 return; 712 } 713 assertFalse(mSettings.getBuiltInZoomControls()); 714 715 mSettings.setBuiltInZoomControls(true); 716 assertTrue(mSettings.getBuiltInZoomControls()); 717 } 718 719 public void testAppCacheDisabled() throws Throwable { 720 if (!NullWebViewUtils.isWebViewAvailable()) { 721 return; 722 } 723 // Test that when AppCache is disabled, we don't get any AppCache 724 // callbacks. 725 startWebServer(); 726 final String url = mWebServer.getAppCacheUrl(); 727 mSettings.setJavaScriptEnabled(true); 728 729 mOnUiThread.loadUrlAndWaitForCompletion(url); 730 new PollingCheck(WEBVIEW_TIMEOUT) { 731 protected boolean check() { 732 return "Loaded".equals(mOnUiThread.getTitle()); 733 } 734 }.run(); 735 // The page is now loaded. Wait for a further 1s to check no AppCache 736 // callbacks occur. 737 Thread.sleep(1000); 738 assertEquals("Loaded", mOnUiThread.getTitle()); 739 } 740 741 public void testAppCacheEnabled() throws Throwable { 742 if (!NullWebViewUtils.isWebViewAvailable()) { 743 return; 744 } 745 // Note that the AppCache path can only be set once. This limits the 746 // amount of testing we can do, and means that we must test all aspects 747 // of setting the AppCache path in a single test to guarantee ordering. 748 749 // Test that when AppCache is enabled but no valid path is provided, 750 // we don't get any AppCache callbacks. 751 startWebServer(); 752 final String url = mWebServer.getAppCacheUrl(); 753 mSettings.setAppCacheEnabled(true); 754 mSettings.setJavaScriptEnabled(true); 755 756 mOnUiThread.loadUrlAndWaitForCompletion(url); 757 new PollingCheck(WEBVIEW_TIMEOUT) { 758 @Override 759 protected boolean check() { 760 return "Loaded".equals(mOnUiThread.getTitle()); 761 } 762 }.run(); 763 // The page is now loaded. Wait for a further 1s to check no AppCache 764 // callbacks occur. 765 Thread.sleep(1000); 766 assertEquals("Loaded", mOnUiThread.getTitle()); 767 768 // Test that when AppCache is enabled and a valid path is provided, we 769 // get an AppCache callback of some kind. 770 mSettings.setAppCachePath(getActivity().getDir("appcache", 0).getPath()); 771 mOnUiThread.loadUrlAndWaitForCompletion(url); 772 new PollingCheck(WEBVIEW_TIMEOUT) { 773 @Override 774 protected boolean check() { 775 return mOnUiThread.getTitle() != null 776 && mOnUiThread.getTitle().endsWith("Callback"); 777 } 778 }.run(); 779 } 780 781 // Ideally, we need a test case for the enabled case. However, it seems that 782 // enabling the database should happen prior to navigating the first url due to 783 // some internal limitations of webview. For this reason, we only provide a 784 // test case for "disabled" behavior. 785 // Also loading as data rather than using URL should work, but it causes a 786 // security exception in JS, most likely due to cross domain access. So we load 787 // using a URL. Finally, it looks like enabling database requires creating a 788 // webChromeClient and listening to Quota callbacks, which is not documented. 789 public void testDatabaseDisabled() throws Throwable { 790 if (!NullWebViewUtils.isWebViewAvailable()) { 791 return; 792 } 793 // Verify that websql database does not work when disabled. 794 startWebServer(); 795 796 mOnUiThread.setWebChromeClient(new WebViewSyncLoader.WaitForProgressClient(mOnUiThread) { 797 @Override 798 public void onExceededDatabaseQuota(String url, String databaseId, long quota, 799 long estimatedSize, long total, WebStorage.QuotaUpdater updater) { 800 updater.updateQuota(estimatedSize); 801 } 802 }); 803 mSettings.setJavaScriptEnabled(true); 804 mSettings.setDatabaseEnabled(false); 805 final String url = mWebServer.getAssetUrl(TestHtmlConstants.DATABASE_ACCESS_URL); 806 mOnUiThread.loadUrlAndWaitForCompletion(url); 807 assertEquals("No database", mOnUiThread.getTitle()); 808 } 809 810 /** 811 * This should remain functionally equivalent to 812 * androidx.webkit.WebSettingsCompatTest#testDisabledActionModeMenuItems. Modifications to this 813 * test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 814 */ 815 public void testDisabledActionModeMenuItems() throws Throwable { 816 if (!NullWebViewUtils.isWebViewAvailable()) { 817 return; 818 } 819 820 assertEquals(WebSettings.MENU_ITEM_NONE, mSettings.getDisabledActionModeMenuItems()); 821 822 int allDisabledFlags = WebSettings.MENU_ITEM_NONE | WebSettings.MENU_ITEM_SHARE | 823 WebSettings.MENU_ITEM_WEB_SEARCH | WebSettings.MENU_ITEM_PROCESS_TEXT; 824 for (int i = WebSettings.MENU_ITEM_NONE; i <= allDisabledFlags; i++) { 825 mSettings.setDisabledActionModeMenuItems(i); 826 assertEquals(i, mSettings.getDisabledActionModeMenuItems()); 827 } 828 } 829 830 public void testLoadsImagesAutomatically_default() throws Throwable { 831 if (!NullWebViewUtils.isWebViewAvailable()) { 832 return; 833 } 834 assertTrue(mSettings.getLoadsImagesAutomatically()); 835 } 836 837 public void testLoadsImagesAutomatically_httpImagesLoaded() throws Throwable { 838 if (!NullWebViewUtils.isWebViewAvailable()) { 839 return; 840 } 841 startWebServer(); 842 mSettings.setJavaScriptEnabled(true); 843 mSettings.setLoadsImagesAutomatically(true); 844 845 mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null); 846 assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle()); 847 } 848 849 public void testLoadsImagesAutomatically_dataUriImagesLoaded() throws Throwable { 850 if (!NullWebViewUtils.isWebViewAvailable()) { 851 return; 852 } 853 startWebServer(); 854 mSettings.setJavaScriptEnabled(true); 855 mSettings.setLoadsImagesAutomatically(true); 856 857 mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null); 858 assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle()); 859 } 860 861 public void testLoadsImagesAutomatically_blockLoadingImages() throws Throwable { 862 if (!NullWebViewUtils.isWebViewAvailable()) { 863 return; 864 } 865 startWebServer(); 866 mSettings.setJavaScriptEnabled(true); 867 mSettings.setLoadsImagesAutomatically(false); 868 869 mOnUiThread.clearCache(true); // in case of side-effects from other tests 870 mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null); 871 assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle()); 872 873 mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null); 874 assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle()); 875 } 876 877 public void testLoadsImagesAutomatically_loadImagesWithoutReload() throws Throwable { 878 if (!NullWebViewUtils.isWebViewAvailable()) { 879 return; 880 } 881 startWebServer(); 882 mSettings.setJavaScriptEnabled(true); 883 mSettings.setLoadsImagesAutomatically(false); 884 885 mOnUiThread.clearCache(true); // in case of side-effects from other tests 886 mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null); 887 assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle()); 888 mSettings.setLoadsImagesAutomatically(true); // load images, without calling #reload() 889 waitForNonEmptyImage(); 890 assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle()); 891 892 mSettings.setLoadsImagesAutomatically(false); 893 mOnUiThread.clearCache(true); 894 mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null); 895 assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle()); 896 mSettings.setLoadsImagesAutomatically(true); // load images, without calling #reload() 897 waitForNonEmptyImage(); 898 assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle()); 899 } 900 901 public void testBlockNetworkImage() throws Throwable { 902 if (!NullWebViewUtils.isWebViewAvailable()) { 903 return; 904 } 905 assertFalse(mSettings.getBlockNetworkImage()); 906 907 startWebServer(); 908 mSettings.setJavaScriptEnabled(true); 909 910 // Check that by default network and data url images are loaded. 911 mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null); 912 assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle()); 913 mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null); 914 assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle()); 915 916 // Check that only network images are blocked, data url images are still loaded. 917 // Also check that network images are loaded automatically once we disable the setting, 918 // without reloading the page. 919 mSettings.setBlockNetworkImage(true); 920 mOnUiThread.clearCache(true); 921 mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null); 922 assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle()); 923 mSettings.setBlockNetworkImage(false); 924 waitForNonEmptyImage(); 925 assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle()); 926 927 mSettings.setBlockNetworkImage(true); 928 mOnUiThread.clearCache(true); 929 mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null); 930 assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle()); 931 } 932 933 public void testBlockNetworkLoads() throws Throwable { 934 if (!NullWebViewUtils.isWebViewAvailable()) { 935 return; 936 } 937 assertFalse(mSettings.getBlockNetworkLoads()); 938 939 startWebServer(); 940 mSettings.setJavaScriptEnabled(true); 941 942 // Check that by default network resources and data url images are loaded. 943 mOnUiThread.loadUrlAndWaitForCompletion( 944 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); 945 assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle()); 946 mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null); 947 assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle()); 948 mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null); 949 assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle()); 950 951 // Check that only network resources are blocked, data url images are still loaded. 952 mSettings.setBlockNetworkLoads(true); 953 mOnUiThread.clearCache(true); 954 mOnUiThread.loadUrlAndWaitForCompletion( 955 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); 956 assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle())); 957 mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null); 958 assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle()); 959 mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null); 960 assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle()); 961 962 // Check that network resources are loaded once we disable the setting and reload the page. 963 mSettings.setBlockNetworkLoads(false); 964 mOnUiThread.loadUrlAndWaitForCompletion( 965 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); 966 assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle()); 967 mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null); 968 assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle()); 969 } 970 971 // Verify that an image in local file system can be loaded by an asset 972 public void testLocalImageLoads() throws Throwable { 973 if (!NullWebViewUtils.isWebViewAvailable()) { 974 return; 975 } 976 977 mSettings.setJavaScriptEnabled(true); 978 // Check that local images are loaded without issues regardless of domain checkings 979 mSettings.setAllowUniversalAccessFromFileURLs(false); 980 mSettings.setAllowFileAccessFromFileURLs(false); 981 String url = TestHtmlConstants.getFileUrl(TestHtmlConstants.IMAGE_ACCESS_URL); 982 mOnUiThread.loadUrlAndWaitForCompletion(url); 983 waitForNonEmptyImage(); 984 assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle()); 985 } 986 987 // Verify that javascript cross-domain request permissions matches file domain settings 988 // for iframes 989 public void testIframesWhenAccessFromFileURLsEnabled() throws Throwable { 990 if (!NullWebViewUtils.isWebViewAvailable()) { 991 return; 992 } 993 994 mSettings.setJavaScriptEnabled(true); 995 // disable universal access from files 996 mSettings.setAllowUniversalAccessFromFileURLs(false); 997 mSettings.setAllowFileAccessFromFileURLs(true); 998 999 // when cross file scripting is enabled, make sure cross domain requests succeed 1000 String url = TestHtmlConstants.getFileUrl(TestHtmlConstants.IFRAME_ACCESS_URL); 1001 mOnUiThread.loadUrlAndWaitForCompletion(url); 1002 String iframeUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.HELLO_WORLD_URL); 1003 assertEquals(iframeUrl, mOnUiThread.getTitle()); 1004 } 1005 1006 // Verify that javascript cross-domain request permissions matches file domain settings 1007 // for iframes 1008 public void testIframesWhenAccessFromFileURLsDisabled() throws Throwable { 1009 if (!NullWebViewUtils.isWebViewAvailable()) { 1010 return; 1011 } 1012 1013 mSettings.setJavaScriptEnabled(true); 1014 // disable universal access from files 1015 mSettings.setAllowUniversalAccessFromFileURLs(false); 1016 mSettings.setAllowFileAccessFromFileURLs(false); 1017 1018 // when cross file scripting is disabled, make sure cross domain requests fail 1019 String url = TestHtmlConstants.getFileUrl(TestHtmlConstants.IFRAME_ACCESS_URL); 1020 mOnUiThread.loadUrlAndWaitForCompletion(url); 1021 String iframeUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.HELLO_WORLD_URL); 1022 assertFalse("Title should not have changed, because file URL access is disabled", 1023 iframeUrl.equals(mOnUiThread.getTitle())); 1024 } 1025 1026 // Verify that enabling file access from file URLs enable XmlHttpRequest (XHR) across files 1027 public void testXHRWhenAccessFromFileURLsEnabled() throws Throwable { 1028 if (!NullWebViewUtils.isWebViewAvailable()) { 1029 return; 1030 } 1031 verifyFileXHR(true); 1032 } 1033 1034 // Verify that disabling file access from file URLs disable XmlHttpRequest (XHR) accross files 1035 public void testXHRWhenAccessFromFileURLsDisabled() throws Throwable { 1036 if (!NullWebViewUtils.isWebViewAvailable()) { 1037 return; 1038 } 1039 1040 verifyFileXHR(false); 1041 } 1042 1043 // verify XHR across files matches the allowFileAccessFromFileURLs setting 1044 private void verifyFileXHR(boolean enableXHR) throws Throwable { 1045 // target file content 1046 String target ="<html><body>target</body><html>"; 1047 1048 String targetPath = mContext.getFileStreamPath("target.html").getAbsolutePath(); 1049 // local file content that use XHR to read the target file 1050 String local ="" + 1051 "<html><body><script>" + 1052 "var client = new XMLHttpRequest();" + 1053 "client.open('GET', 'file://" + targetPath + "',false);" + 1054 "client.send();" + 1055 "document.title = client.responseText;" + 1056 "</script></body></html>"; 1057 1058 // create files in internal storage 1059 writeFile("local.html", local); 1060 writeFile("target.html", target); 1061 1062 mSettings.setJavaScriptEnabled(true); 1063 // disable universal access from files 1064 mSettings.setAllowUniversalAccessFromFileURLs(false); 1065 mSettings.setAllowFileAccessFromFileURLs(enableXHR); 1066 String localPath = mContext.getFileStreamPath("local.html").getAbsolutePath(); 1067 // when cross file scripting is enabled, make sure cross domain requests succeed 1068 mOnUiThread.loadUrlAndWaitForCompletion("file://" + localPath); 1069 if (enableXHR) { 1070 assertEquals("Expected title to change, because XHR should be enabled", target, 1071 mOnUiThread.getTitle()); 1072 } else { 1073 assertFalse("Title should not have changed, because XHR should be disabled", 1074 target.equals(mOnUiThread.getTitle())); 1075 } 1076 } 1077 1078 // Create a private file on internal storage from the given string 1079 private void writeFile(String filename, String content) throws Exception { 1080 1081 FileOutputStream fos = mContext.openFileOutput(filename, Context.MODE_PRIVATE); 1082 fos.write(content.getBytes()); 1083 fos.close(); 1084 } 1085 1086 public void testAllowMixedMode() throws Throwable { 1087 if (!NullWebViewUtils.isWebViewAvailable()) { 1088 return; 1089 } 1090 1091 final String INSECURE_BASE_URL = "http://www.example.com/"; 1092 final String INSECURE_JS_URL = INSECURE_BASE_URL + "insecure.js"; 1093 final String INSECURE_IMG_URL = INSECURE_BASE_URL + "insecure.png"; 1094 final String SECURE_URL = "/secure.html"; 1095 final String JS_HTML = "<script src=\"" + INSECURE_JS_URL + "\"></script>"; 1096 final String IMG_HTML = "<img src=\"" + INSECURE_IMG_URL + "\" />"; 1097 final String SECURE_HTML = "<body>" + IMG_HTML + " " + JS_HTML + "</body>"; 1098 final String JS_CONTENT = "window.loaded_js = 42;"; 1099 final String IMG_CONTENT = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; 1100 1101 final class InterceptClient extends WaitForLoadedClient { 1102 public int mInsecureJsCounter; 1103 public int mInsecureImgCounter; 1104 1105 public InterceptClient() { 1106 super(mOnUiThread); 1107 } 1108 1109 @Override 1110 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 1111 handler.proceed(); 1112 } 1113 1114 @Override 1115 public WebResourceResponse shouldInterceptRequest( 1116 WebView view, WebResourceRequest request) { 1117 if (request.getUrl().toString().equals(INSECURE_JS_URL)) { 1118 mInsecureJsCounter++; 1119 return new WebResourceResponse("text/javascript", "utf-8", 1120 new ByteArrayInputStream(JS_CONTENT.getBytes(StandardCharsets.UTF_8))); 1121 } else if (request.getUrl().toString().equals(INSECURE_IMG_URL)) { 1122 mInsecureImgCounter++; 1123 return new WebResourceResponse("image/gif", "utf-8", 1124 new ByteArrayInputStream(Base64.decode(IMG_CONTENT, Base64.DEFAULT))); 1125 } 1126 1127 if (request.getUrl().toString().startsWith(INSECURE_BASE_URL)) { 1128 return new WebResourceResponse("text/html", "UTF-8", null); 1129 } 1130 return null; 1131 } 1132 } 1133 1134 InterceptClient interceptClient = new InterceptClient(); 1135 mOnUiThread.setWebViewClient(interceptClient); 1136 mSettings.setJavaScriptEnabled(true); 1137 TestWebServer httpsServer = null; 1138 try { 1139 httpsServer = new TestWebServer(true); 1140 String secureUrl = httpsServer.setResponse(SECURE_URL, SECURE_HTML, null); 1141 mOnUiThread.clearSslPreferences(); 1142 1143 mSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); 1144 mOnUiThread.loadUrlAndWaitForCompletion(secureUrl); 1145 assertEquals(1, httpsServer.getRequestCount(SECURE_URL)); 1146 assertEquals(0, interceptClient.mInsecureJsCounter); 1147 assertEquals(0, interceptClient.mInsecureImgCounter); 1148 1149 mSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); 1150 mOnUiThread.loadUrlAndWaitForCompletion(secureUrl); 1151 assertEquals(2, httpsServer.getRequestCount(SECURE_URL)); 1152 assertEquals(1, interceptClient.mInsecureJsCounter); 1153 assertEquals(1, interceptClient.mInsecureImgCounter); 1154 1155 mSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); 1156 mOnUiThread.loadUrlAndWaitForCompletion(secureUrl); 1157 assertEquals(3, httpsServer.getRequestCount(SECURE_URL)); 1158 assertEquals(1, interceptClient.mInsecureJsCounter); 1159 assertEquals(2, interceptClient.mInsecureImgCounter); 1160 } finally { 1161 if (httpsServer != null) { 1162 httpsServer.shutdown(); 1163 } 1164 } 1165 } 1166 1167 /** 1168 * This should remain functionally equivalent to 1169 * androidx.webkit.WebSettingsCompatTest#testEnableSafeBrowsing. Modifications to this test 1170 * should be reflected in that test as necessary. See http://go/modifying-webview-cts. 1171 */ 1172 public void testEnableSafeBrowsing() throws Throwable { 1173 if (!NullWebViewUtils.isWebViewAvailable()) { 1174 return; 1175 } 1176 mSettings.setSafeBrowsingEnabled(false); 1177 assertFalse(mSettings.getSafeBrowsingEnabled()); 1178 } 1179 1180 private int[] getBitmapPixels(Bitmap bitmap, int x, int y, int width, int height) { 1181 int[] pixels = new int[width * height]; 1182 bitmap.getPixels(pixels, 0, width, x, y, width, height); 1183 return pixels; 1184 } 1185 1186 private Map<Integer,Integer> getBitmapHistogram(Bitmap bitmap, int x, int y, int width, int height) { 1187 HashMap<Integer, Integer> histogram = new HashMap(); 1188 for (int pixel : getBitmapPixels(bitmap, x, y, width, height)) { 1189 histogram.put(pixel, histogram.getOrDefault(pixel, 0) + 1); 1190 } 1191 return histogram; 1192 } 1193 1194 public void testForceDark_default() throws Throwable { 1195 if (!NullWebViewUtils.isWebViewAvailable()) { 1196 return; 1197 } 1198 1199 assertEquals("The default force dark state should be AUTO", 1200 mSettings.getForceDark(), WebSettings.FORCE_DARK_AUTO); 1201 } 1202 1203 private void setWebViewSize(int width, int height) { 1204 // Set the webview size to 64x64 1205 WebkitUtils.onMainThreadSync(() -> { 1206 WebView webView = mOnUiThread.getWebView(); 1207 ViewGroup.LayoutParams params = webView.getLayoutParams(); 1208 params.height = height; 1209 params.width = width; 1210 webView.setLayoutParams(params); 1211 }); 1212 1213 } 1214 1215 public void testForceDark_rendersDark() throws Throwable { 1216 if (!NullWebViewUtils.isWebViewAvailable()) { 1217 return; 1218 } 1219 1220 setWebViewSize(64, 64); 1221 1222 Map<Integer, Integer> histogram; 1223 Integer[] colourValues; 1224 1225 // Loading about:blank into a force-dark-on webview should result in a dark background 1226 mSettings.setForceDark(WebSettings.FORCE_DARK_ON); 1227 assertEquals("Force dark should have been set to ON", 1228 mSettings.getForceDark(), WebSettings.FORCE_DARK_ON); 1229 1230 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 1231 histogram = getBitmapHistogram(mOnUiThread.captureBitmap(), 0, 0, 64, 64); 1232 assertEquals("Bitmap should have a single colour", histogram.size(), 1); 1233 colourValues = histogram.keySet().toArray(new Integer[0]); 1234 assertThat("Bitmap colour should be dark", 1235 Color.luminance(colourValues[0]), lessThan(0.5f)); 1236 1237 // Loading about:blank into a force-dark-off webview should result in a light background 1238 mSettings.setForceDark(WebSettings.FORCE_DARK_OFF); 1239 assertEquals("Force dark should have been set to OFF", 1240 mSettings.getForceDark(), WebSettings.FORCE_DARK_OFF); 1241 1242 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 1243 histogram = getBitmapHistogram(mOnUiThread.captureBitmap(), 0, 0, 64, 64); 1244 assertEquals("Bitmap should have a single colour", histogram.size(), 1); 1245 colourValues = histogram.keySet().toArray(new Integer[0]); 1246 assertThat("Bitmap colour should be light", 1247 Color.luminance(colourValues[0]), greaterThan(0.5f)); 1248 } 1249 1250 /** 1251 * Starts the internal web server. The server will be shut down automatically 1252 * during tearDown(). 1253 * 1254 * @throws Exception 1255 */ 1256 private void startWebServer() throws Exception { 1257 assertNull(mWebServer); 1258 mWebServer = new CtsTestServer(getActivity(), false); 1259 } 1260 1261 /** 1262 * Load the given asset from the internal web server. Starts the server if 1263 * it is not already running. 1264 * 1265 * @param asset The name of the asset to load. 1266 * @throws Exception 1267 */ 1268 private void loadAssetUrl(String asset) throws Exception { 1269 if (mWebServer == null) { 1270 startWebServer(); 1271 } 1272 String url = mWebServer.getAssetUrl(asset); 1273 mOnUiThread.loadUrlAndWaitForCompletion(url); 1274 } 1275 1276 private String getNetworkImageHtml() { 1277 return "<html>" + 1278 "<head><script>function updateTitle(){" + 1279 "document.title=document.getElementById('img').naturalHeight;}</script></head>" + 1280 "<body onload='updateTitle()'>" + 1281 "<img id='img' onload='updateTitle()' src='" + 1282 mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL) + 1283 "'></body></html>"; 1284 } 1285 1286 private void waitForNonEmptyImage() { 1287 new PollingCheck(WEBVIEW_TIMEOUT) { 1288 @Override 1289 protected boolean check() { 1290 return !EMPTY_IMAGE_HEIGHT.equals(mOnUiThread.getTitle()); 1291 } 1292 }.run(); 1293 } 1294 1295 private class IconListenerClient extends WaitForProgressClient { 1296 private final BlockingQueue<Bitmap> mReceivedIconQueue = new LinkedBlockingQueue<>(); 1297 1298 public IconListenerClient() { 1299 super(mOnUiThread); 1300 } 1301 1302 @Override 1303 public void onReceivedIcon(WebView view, Bitmap icon) { 1304 mReceivedIconQueue.add(icon); 1305 } 1306 1307 /** 1308 * Exposed as a precaution, in case for some reason we get multiple calls to 1309 * {@link #onReceivedIcon}. 1310 */ 1311 public void drainIconQueue() { 1312 while (mReceivedIconQueue.poll() != null) {} 1313 } 1314 1315 public void waitForNextIcon() { 1316 // TODO(ntfschr): consider exposing the Bitmap, if we want to make assertions. 1317 WebkitUtils.waitForNextQueueElement(mReceivedIconQueue); 1318 } 1319 } 1320 } 1321