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.content.ContentResolver; 20 import android.content.Context; 21 import android.content.res.AssetManager; 22 import android.cts.util.EvaluateJsResultPollingCheck; 23 import android.cts.util.PollingCheck; 24 import android.graphics.Bitmap; 25 import android.graphics.Bitmap.Config; 26 import android.graphics.BitmapFactory; 27 import android.graphics.Canvas; 28 import android.graphics.Color; 29 import android.graphics.Picture; 30 import android.graphics.Rect; 31 import android.net.Uri; 32 import android.net.http.SslCertificate; 33 import android.net.http.SslError; 34 import android.os.Bundle; 35 import android.os.CancellationSignal; 36 import android.os.Handler; 37 import android.os.Looper; 38 import android.os.Message; 39 import android.os.ParcelFileDescriptor; 40 import android.os.StrictMode; 41 import android.os.StrictMode.ThreadPolicy; 42 import android.os.SystemClock; 43 import android.print.PageRange; 44 import android.print.PrintAttributes; 45 import android.print.PrintDocumentAdapter; 46 import android.print.PrintDocumentAdapter.LayoutResultCallback; 47 import android.print.PrintDocumentAdapter.WriteResultCallback; 48 import android.print.PrintDocumentInfo; 49 import android.test.ActivityInstrumentationTestCase2; 50 import android.test.UiThreadTest; 51 import android.util.AttributeSet; 52 import android.util.DisplayMetrics; 53 import android.view.KeyEvent; 54 import android.view.MotionEvent; 55 import android.view.View; 56 import android.view.ViewGroup; 57 import android.webkit.ConsoleMessage; 58 import android.webkit.CookieSyncManager; 59 import android.webkit.DownloadListener; 60 import android.webkit.JavascriptInterface; 61 import android.webkit.SslErrorHandler; 62 import android.webkit.ValueCallback; 63 import android.webkit.WebBackForwardList; 64 import android.webkit.WebChromeClient; 65 import android.webkit.WebIconDatabase; 66 import android.webkit.WebSettings; 67 import android.webkit.WebView; 68 import android.webkit.WebView.HitTestResult; 69 import android.webkit.WebView.PictureListener; 70 import android.webkit.WebViewClient; 71 import android.webkit.WebViewDatabase; 72 import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient; 73 import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient; 74 import android.widget.LinearLayout; 75 76 import junit.framework.Assert; 77 78 import java.io.File; 79 import java.io.FileInputStream; 80 import java.io.IOException; 81 import java.util.Collections; 82 import java.util.Date; 83 import java.util.concurrent.atomic.AtomicReference; 84 import java.util.concurrent.Callable; 85 import java.util.concurrent.FutureTask; 86 import java.util.concurrent.TimeUnit; 87 import java.util.HashMap; 88 89 import org.apache.http.Header; 90 import org.apache.http.HttpRequest; 91 92 public class WebViewTest extends ActivityInstrumentationTestCase2<WebViewStubActivity> { 93 private static final String LOGTAG = "WebViewTest"; 94 private static final int INITIAL_PROGRESS = 100; 95 private static long TEST_TIMEOUT = 20000L; 96 private static final String X_REQUESTED_WITH = "X-Requested-With"; 97 private static final String PRINTER_TEST_FILE = "print.pdf"; 98 private static final String PDF_PREAMBLE = "%PDF-1"; 99 100 /** 101 * This is the minimum number of milliseconds to wait for scrolling to 102 * start. If no scrolling has started before this timeout then it is 103 * assumed that no scrolling will happen. 104 */ 105 private static final long MIN_SCROLL_WAIT_MS = 1000; 106 /** 107 * Once scrolling has started, this is the interval that scrolling 108 * is checked to see if there is a change. If no scrolling change 109 * has happened in the given time then it is assumed that scrolling 110 * has stopped. 111 */ 112 private static final long SCROLL_WAIT_INTERVAL_MS = 200; 113 114 private WebView mWebView; 115 private CtsTestServer mWebServer; 116 private WebViewOnUiThread mOnUiThread; 117 private WebIconDatabase mIconDb; 118 119 public WebViewTest() { 120 super("com.android.cts.stub", WebViewStubActivity.class); 121 } 122 123 @Override 124 protected void setUp() throws Exception { 125 super.setUp(); 126 final WebViewStubActivity activity = getActivity(); 127 new PollingCheck() { 128 @Override 129 protected boolean check() { 130 return activity.hasWindowFocus(); 131 } 132 }.run(); 133 mWebView = activity.getWebView(); 134 File f = activity.getFileStreamPath("snapshot"); 135 if (f.exists()) { 136 f.delete(); 137 } 138 mOnUiThread = new WebViewOnUiThread(this, mWebView); 139 } 140 141 @Override 142 protected void tearDown() throws Exception { 143 mOnUiThread.cleanUp(); 144 if (mWebServer != null) { 145 mWebServer.shutdown(); 146 } 147 if (mIconDb != null) { 148 mIconDb.removeAllIcons(); 149 mIconDb.close(); 150 mIconDb = null; 151 } 152 super.tearDown(); 153 } 154 155 private void startWebServer(boolean secure) throws Exception { 156 assertNull(mWebServer); 157 mWebServer = new CtsTestServer(getActivity(), secure); 158 } 159 160 private void stopWebServer() throws Exception { 161 assertNotNull(mWebServer); 162 ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); 163 ThreadPolicy tmpPolicy = new ThreadPolicy.Builder(oldPolicy) 164 .permitNetwork() 165 .build(); 166 StrictMode.setThreadPolicy(tmpPolicy); 167 mWebServer.shutdown(); 168 mWebServer = null; 169 StrictMode.setThreadPolicy(oldPolicy); 170 } 171 172 @UiThreadTest 173 public void testConstructor() { 174 new WebView(getActivity()); 175 new WebView(getActivity(), null); 176 new WebView(getActivity(), null, 0); 177 } 178 179 @UiThreadTest 180 public void testCreatingWebViewCreatesCookieSyncManager() throws Exception { 181 new WebView(getActivity()); 182 assertNotNull(CookieSyncManager.getInstance()); 183 } 184 185 @UiThreadTest 186 public void testFindAddress() { 187 /* 188 * Info about USPS 189 * http://en.wikipedia.org/wiki/Postal_address#United_States 190 * http://www.usps.com/ 191 */ 192 // full address 193 assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826", 194 WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826")); 195 // not an address 196 assertNull(WebView.findAddress("This is not an address: no town, no state, no zip.")); 197 } 198 199 @SuppressWarnings("deprecation") 200 @UiThreadTest 201 public void testGetZoomControls() { 202 WebSettings settings = mWebView.getSettings(); 203 assertTrue(settings.supportZoom()); 204 View zoomControls = mWebView.getZoomControls(); 205 assertNotNull(zoomControls); 206 207 // disable zoom support 208 settings.setSupportZoom(false); 209 assertFalse(settings.supportZoom()); 210 assertNull(mWebView.getZoomControls()); 211 } 212 213 @UiThreadTest 214 public void testInvokeZoomPicker() throws Exception { 215 WebSettings settings = mWebView.getSettings(); 216 assertTrue(settings.supportZoom()); 217 startWebServer(false); 218 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 219 mOnUiThread.loadUrlAndWaitForCompletion(url); 220 mWebView.invokeZoomPicker(); 221 } 222 223 public void testZoom() throws Throwable { 224 // Pinch zoom is not supported in wrap_content layouts. 225 mOnUiThread.setLayoutHeightToMatchParent(); 226 227 final ScaleChangedWebViewClient webViewClient = new ScaleChangedWebViewClient(); 228 mOnUiThread.setWebViewClient(webViewClient); 229 230 mWebServer = new CtsTestServer(getActivity()); 231 mOnUiThread.loadUrlAndWaitForCompletion( 232 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); 233 234 WebSettings settings = mOnUiThread.getSettings(); 235 settings.setSupportZoom(false); 236 assertFalse(settings.supportZoom()); 237 float currScale = mOnUiThread.getScale(); 238 float previousScale = currScale; 239 240 // can zoom in or out although zoom support is disabled in web settings 241 assertTrue(mOnUiThread.zoomIn()); 242 webViewClient.waitForScaleChanged(); 243 244 currScale = mOnUiThread.getScale(); 245 assertTrue(currScale > previousScale); 246 247 assertTrue(mOnUiThread.zoomOut()); 248 previousScale = currScale; 249 webViewClient.waitForScaleChanged(); 250 251 currScale = mOnUiThread.getScale(); 252 assertTrue(currScale < previousScale); 253 254 // enable zoom support 255 settings.setSupportZoom(true); 256 assertTrue(settings.supportZoom()); 257 previousScale = mOnUiThread.getScale(); 258 259 assertTrue(mOnUiThread.zoomIn()); 260 webViewClient.waitForScaleChanged(); 261 262 currScale = mOnUiThread.getScale(); 263 assertTrue(currScale > previousScale); 264 265 // zoom in until it reaches maximum scale 266 while (mOnUiThread.zoomIn()) { 267 previousScale = currScale; 268 webViewClient.waitForScaleChanged(); 269 currScale = mOnUiThread.getScale(); 270 assertTrue(currScale > previousScale); 271 } 272 273 previousScale = currScale; 274 // can not zoom in further 275 assertFalse(mOnUiThread.zoomIn()); 276 // We sleep to assert to the best of our ability 277 // that a scale change does *not* happen. 278 Thread.sleep(500); 279 currScale = mOnUiThread.getScale(); 280 assertEquals(currScale, previousScale); 281 282 // zoom out 283 assertTrue(mOnUiThread.zoomOut()); 284 previousScale = currScale; 285 webViewClient.waitForScaleChanged(); 286 currScale = mOnUiThread.getScale(); 287 assertTrue(currScale < previousScale); 288 289 // zoom out until it reaches minimum scale 290 while (mOnUiThread.zoomOut()) { 291 previousScale = currScale; 292 webViewClient.waitForScaleChanged(); 293 currScale = mOnUiThread.getScale(); 294 assertTrue(currScale < previousScale); 295 } 296 297 previousScale = currScale; 298 assertFalse(mOnUiThread.zoomOut()); 299 300 // We sleep to assert to the best of our ability 301 // that a scale change does *not* happen. 302 Thread.sleep(500); 303 currScale = mOnUiThread.getScale(); 304 assertEquals(currScale, previousScale); 305 } 306 307 @UiThreadTest 308 public void testSetScrollBarStyle() { 309 mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); 310 assertFalse(mWebView.overlayHorizontalScrollbar()); 311 assertFalse(mWebView.overlayVerticalScrollbar()); 312 313 mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); 314 assertTrue(mWebView.overlayHorizontalScrollbar()); 315 assertTrue(mWebView.overlayVerticalScrollbar()); 316 317 mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET); 318 assertFalse(mWebView.overlayHorizontalScrollbar()); 319 assertFalse(mWebView.overlayVerticalScrollbar()); 320 321 mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); 322 assertTrue(mWebView.overlayHorizontalScrollbar()); 323 assertTrue(mWebView.overlayVerticalScrollbar()); 324 } 325 326 @UiThreadTest 327 public void testScrollBarOverlay() throws Throwable { 328 mWebView.setHorizontalScrollbarOverlay(true); 329 mWebView.setVerticalScrollbarOverlay(false); 330 assertTrue(mWebView.overlayHorizontalScrollbar()); 331 assertFalse(mWebView.overlayVerticalScrollbar()); 332 333 mWebView.setHorizontalScrollbarOverlay(false); 334 mWebView.setVerticalScrollbarOverlay(true); 335 assertFalse(mWebView.overlayHorizontalScrollbar()); 336 assertTrue(mWebView.overlayVerticalScrollbar()); 337 } 338 339 @UiThreadTest 340 public void testLoadUrl() throws Exception { 341 assertNull(mWebView.getUrl()); 342 assertNull(mWebView.getOriginalUrl()); 343 assertEquals(INITIAL_PROGRESS, mWebView.getProgress()); 344 345 startWebServer(false); 346 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 347 mOnUiThread.loadUrlAndWaitForCompletion(url); 348 assertEquals(100, mWebView.getProgress()); 349 assertEquals(url, mWebView.getUrl()); 350 assertEquals(url, mWebView.getOriginalUrl()); 351 assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle()); 352 353 // verify that the request also includes X-Requested-With header 354 HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); 355 Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH); 356 assertEquals(1, matchingHeaders.length); 357 358 Header header = matchingHeaders[0]; 359 assertEquals(mWebView.getContext().getApplicationInfo().packageName, header.getValue()); 360 } 361 362 @UiThreadTest 363 public void testLoadUrlDoesNotStripParamsWhenLoadingContentUrls() throws Exception { 364 Uri.Builder uriBuilder = new Uri.Builder().scheme( 365 ContentResolver.SCHEME_CONTENT).authority(MockContentProvider.AUTHORITY); 366 uriBuilder.appendPath("foo.html").appendQueryParameter("param","bar"); 367 String url = uriBuilder.build().toString(); 368 mOnUiThread.loadUrlAndWaitForCompletion(url); 369 // verify the parameter is not stripped. 370 Uri uri = Uri.parse(mWebView.getTitle()); 371 assertEquals("bar", uri.getQueryParameter("param")); 372 } 373 374 @UiThreadTest 375 public void testAppInjectedXRequestedWithHeaderIsNotOverwritten() throws Exception { 376 startWebServer(false); 377 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 378 HashMap<String, String> map = new HashMap<String, String>(); 379 final String requester = "foo"; 380 map.put(X_REQUESTED_WITH, requester); 381 mOnUiThread.loadUrlAndWaitForCompletion(url, map); 382 383 // verify that the request also includes X-Requested-With header 384 // but is not overwritten by the webview 385 HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); 386 Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH); 387 assertEquals(1, matchingHeaders.length); 388 389 Header header = matchingHeaders[0]; 390 assertEquals(requester, header.getValue()); 391 } 392 393 @UiThreadTest 394 public void testAppCanInjectHeadersViaImmutableMap() throws Exception { 395 startWebServer(false); 396 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 397 HashMap<String, String> map = new HashMap<String, String>(); 398 final String requester = "foo"; 399 map.put(X_REQUESTED_WITH, requester); 400 mOnUiThread.loadUrlAndWaitForCompletion(url, Collections.unmodifiableMap(map)); 401 402 // verify that the request also includes X-Requested-With header 403 // but is not overwritten by the webview 404 HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); 405 Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH); 406 assertEquals(1, matchingHeaders.length); 407 408 Header header = matchingHeaders[0]; 409 assertEquals(requester, header.getValue()); 410 } 411 412 @SuppressWarnings("deprecation") 413 @UiThreadTest 414 public void testGetVisibleTitleHeight() throws Exception { 415 startWebServer(false); 416 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 417 mOnUiThread.loadUrlAndWaitForCompletion(url); 418 assertEquals(0, mWebView.getVisibleTitleHeight()); 419 } 420 421 @UiThreadTest 422 public void testGetOriginalUrl() throws Throwable { 423 startWebServer(false); 424 final String finalUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 425 final String redirectUrl = 426 mWebServer.getRedirectingAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 427 428 assertNull(mWebView.getUrl()); 429 assertNull(mWebView.getOriginalUrl()); 430 431 // By default, WebView sends an intent to ask the system to 432 // handle loading a new URL. We set a WebViewClient as 433 // WebViewClient.shouldOverrideUrlLoading() returns false, so 434 // the WebView will load the new URL. 435 mOnUiThread.setWebViewClient(new WaitForLoadedClient(mOnUiThread)); 436 mOnUiThread.loadUrlAndWaitForCompletion(redirectUrl); 437 438 assertEquals(finalUrl, mWebView.getUrl()); 439 assertEquals(redirectUrl, mWebView.getOriginalUrl()); 440 } 441 442 public void testStopLoading() throws Exception { 443 assertEquals(INITIAL_PROGRESS, mOnUiThread.getProgress()); 444 445 startWebServer(false); 446 String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.STOP_LOADING_URL); 447 448 class JsInterface { 449 private boolean mPageLoaded; 450 451 @JavascriptInterface 452 public synchronized void pageLoaded() { 453 mPageLoaded = true; 454 notify(); 455 } 456 public synchronized boolean getPageLoaded() { 457 return mPageLoaded; 458 } 459 } 460 461 JsInterface jsInterface = new JsInterface(); 462 463 mOnUiThread.getSettings().setJavaScriptEnabled(true); 464 mOnUiThread.addJavascriptInterface(jsInterface, "javabridge"); 465 mOnUiThread.loadUrl(url); 466 mOnUiThread.stopLoading(); 467 468 // We wait to see that the onload callback in the HTML is not fired. 469 synchronized (jsInterface) { 470 jsInterface.wait(3000); 471 } 472 473 assertFalse(jsInterface.getPageLoaded()); 474 } 475 476 @UiThreadTest 477 public void testGoBackAndForward() throws Exception { 478 assertGoBackOrForwardBySteps(false, -1); 479 assertGoBackOrForwardBySteps(false, 1); 480 481 startWebServer(false); 482 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 483 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 484 String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); 485 486 mOnUiThread.loadUrlAndWaitForCompletion(url1); 487 pollingCheckWebBackForwardList(url1, 0, 1); 488 assertGoBackOrForwardBySteps(false, -1); 489 assertGoBackOrForwardBySteps(false, 1); 490 491 mOnUiThread.loadUrlAndWaitForCompletion(url2); 492 pollingCheckWebBackForwardList(url2, 1, 2); 493 assertGoBackOrForwardBySteps(true, -1); 494 assertGoBackOrForwardBySteps(false, 1); 495 496 mOnUiThread.loadUrlAndWaitForCompletion(url3); 497 pollingCheckWebBackForwardList(url3, 2, 3); 498 assertGoBackOrForwardBySteps(true, -2); 499 assertGoBackOrForwardBySteps(false, 1); 500 501 mWebView.goBack(); 502 pollingCheckWebBackForwardList(url2, 1, 3); 503 assertGoBackOrForwardBySteps(true, -1); 504 assertGoBackOrForwardBySteps(true, 1); 505 506 mWebView.goForward(); 507 pollingCheckWebBackForwardList(url3, 2, 3); 508 assertGoBackOrForwardBySteps(true, -2); 509 assertGoBackOrForwardBySteps(false, 1); 510 511 mWebView.goBackOrForward(-2); 512 pollingCheckWebBackForwardList(url1, 0, 3); 513 assertGoBackOrForwardBySteps(false, -1); 514 assertGoBackOrForwardBySteps(true, 2); 515 516 mWebView.goBackOrForward(2); 517 pollingCheckWebBackForwardList(url3, 2, 3); 518 assertGoBackOrForwardBySteps(true, -2); 519 assertGoBackOrForwardBySteps(false, 1); 520 } 521 522 @UiThreadTest 523 public void testAddJavascriptInterface() throws Exception { 524 WebSettings settings = mWebView.getSettings(); 525 settings.setJavaScriptEnabled(true); 526 settings.setJavaScriptCanOpenWindowsAutomatically(true); 527 528 final class DummyJavaScriptInterface { 529 private boolean mWasProvideResultCalled; 530 private String mResult; 531 532 private synchronized String waitForResult() { 533 while (!mWasProvideResultCalled) { 534 try { 535 wait(TEST_TIMEOUT); 536 } catch (InterruptedException e) { 537 continue; 538 } 539 if (!mWasProvideResultCalled) { 540 Assert.fail("Unexpected timeout"); 541 } 542 } 543 return mResult; 544 } 545 546 public synchronized boolean wasProvideResultCalled() { 547 return mWasProvideResultCalled; 548 } 549 550 @JavascriptInterface 551 public synchronized void provideResult(String result) { 552 mWasProvideResultCalled = true; 553 mResult = result; 554 notify(); 555 } 556 } 557 558 final DummyJavaScriptInterface obj = new DummyJavaScriptInterface(); 559 mWebView.addJavascriptInterface(obj, "dummy"); 560 assertFalse(obj.wasProvideResultCalled()); 561 562 startWebServer(false); 563 String url = mWebServer.getAssetUrl(TestHtmlConstants.ADD_JAVA_SCRIPT_INTERFACE_URL); 564 mOnUiThread.loadUrlAndWaitForCompletion(url); 565 assertEquals("Original title", obj.waitForResult()); 566 } 567 568 @UiThreadTest 569 public void testAddJavascriptInterfaceNullObject() throws Exception { 570 WebSettings settings = mWebView.getSettings(); 571 settings.setJavaScriptEnabled(true); 572 String setTitleToPropertyTypeHtml = "<html><head></head>" + 573 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>"; 574 575 // Test that the property is initially undefined. 576 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 577 "text/html", null); 578 assertEquals("undefined", mWebView.getTitle()); 579 580 // Test that adding a null object has no effect. 581 mWebView.addJavascriptInterface(null, "injectedObject"); 582 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 583 "text/html", null); 584 assertEquals("undefined", mWebView.getTitle()); 585 586 // Test that adding an object gives an object type. 587 final Object obj = new Object(); 588 mWebView.addJavascriptInterface(obj, "injectedObject"); 589 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 590 "text/html", null); 591 assertEquals("object", mWebView.getTitle()); 592 593 // Test that trying to replace with a null object has no effect. 594 mWebView.addJavascriptInterface(null, "injectedObject"); 595 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 596 "text/html", null); 597 assertEquals("object", mWebView.getTitle()); 598 } 599 600 @UiThreadTest 601 public void testRemoveJavascriptInterface() throws Exception { 602 WebSettings settings = mWebView.getSettings(); 603 settings.setJavaScriptEnabled(true); 604 String setTitleToPropertyTypeHtml = "<html><head></head>" + 605 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>"; 606 607 // Test that adding an object gives an object type. 608 mWebView.addJavascriptInterface(new Object(), "injectedObject"); 609 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 610 "text/html", null); 611 assertEquals("object", mWebView.getTitle()); 612 613 // Test that reloading the page after removing the object leaves the property undefined. 614 mWebView.removeJavascriptInterface("injectedObject"); 615 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 616 "text/html", null); 617 assertEquals("undefined", mWebView.getTitle()); 618 } 619 620 public void testUseRemovedJavascriptInterface() throws Throwable { 621 class RemovedObject { 622 @Override 623 @JavascriptInterface 624 public String toString() { 625 return "removedObject"; 626 } 627 628 @JavascriptInterface 629 public void remove() throws Throwable { 630 mOnUiThread.removeJavascriptInterface("removedObject"); 631 System.gc(); 632 } 633 } 634 class ResultObject { 635 private String mResult; 636 private boolean mIsResultAvailable; 637 638 @JavascriptInterface 639 public synchronized void setResult(String result) { 640 mResult = result; 641 mIsResultAvailable = true; 642 notify(); 643 } 644 public synchronized String getResult() { 645 while (!mIsResultAvailable) { 646 try { 647 wait(); 648 } catch (InterruptedException e) { 649 } 650 } 651 return mResult; 652 } 653 } 654 final ResultObject resultObject = new ResultObject(); 655 656 // Test that an object is still usable if removed while the page is in use, even if we have 657 // no external references to it. 658 mOnUiThread.getSettings().setJavaScriptEnabled(true); 659 mOnUiThread.addJavascriptInterface(new RemovedObject(), "removedObject"); 660 mOnUiThread.addJavascriptInterface(resultObject, "resultObject"); 661 mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" + 662 "<body onload=\"window.removedObject.remove();" + 663 "resultObject.setResult(removedObject.toString());\"></body></html>", 664 "text/html", null); 665 assertEquals("removedObject", resultObject.getResult()); 666 } 667 668 private final class TestPictureListener implements PictureListener { 669 public int callCount; 670 671 @Override 672 public void onNewPicture(WebView view, Picture picture) { 673 // Need to inform the listener tracking new picture 674 // for the "page loaded" knowledge since it has been replaced. 675 mOnUiThread.onNewPicture(); 676 this.callCount += 1; 677 } 678 } 679 680 private Picture waitForPictureToHaveColor(int color, 681 final TestPictureListener listener) throws Throwable { 682 final int MAX_ON_NEW_PICTURE_ITERATIONS = 5; 683 final AtomicReference<Picture> pictureRef = new AtomicReference<Picture>(); 684 for (int i = 0; i < MAX_ON_NEW_PICTURE_ITERATIONS; i++) { 685 final int oldCallCount = listener.callCount; 686 runTestOnUiThread(new Runnable() { 687 @Override 688 public void run() { 689 pictureRef.set(mWebView.capturePicture()); 690 } 691 }); 692 if (isPictureFilledWithColor(pictureRef.get(), color)) 693 break; 694 new PollingCheck(TEST_TIMEOUT) { 695 @Override 696 protected boolean check() { 697 return listener.callCount > oldCallCount; 698 } 699 }.run(); 700 } 701 return pictureRef.get(); 702 } 703 704 public void testCapturePicture() throws Exception, Throwable { 705 final TestPictureListener listener = new TestPictureListener(); 706 707 startWebServer(false); 708 final String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL); 709 mOnUiThread.setPictureListener(listener); 710 // Showing the blank page will fill the picture with the background color. 711 mOnUiThread.loadUrlAndWaitForCompletion(url); 712 // The default background color is white. 713 Picture oldPicture = waitForPictureToHaveColor(Color.WHITE, listener); 714 715 runTestOnUiThread(new Runnable() { 716 @Override 717 public void run() { 718 mWebView.setBackgroundColor(Color.CYAN); 719 } 720 }); 721 mOnUiThread.reloadAndWaitForCompletion(); 722 waitForPictureToHaveColor(Color.CYAN, listener); 723 724 // The content of the previously captured picture will not be updated automatically. 725 assertTrue(isPictureFilledWithColor(oldPicture, Color.WHITE)); 726 } 727 728 public void testSetPictureListener() throws Exception, Throwable { 729 final class MyPictureListener implements PictureListener { 730 public int callCount; 731 public WebView webView; 732 public Picture picture; 733 734 @Override 735 public void onNewPicture(WebView view, Picture picture) { 736 // Need to inform the listener tracking new picture 737 // for the "page loaded" knowledge since it has been replaced. 738 mOnUiThread.onNewPicture(); 739 this.callCount += 1; 740 this.webView = view; 741 this.picture = picture; 742 } 743 } 744 745 final MyPictureListener listener = new MyPictureListener(); 746 startWebServer(false); 747 final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 748 mOnUiThread.setPictureListener(listener); 749 mOnUiThread.loadUrlAndWaitForCompletion(url); 750 new PollingCheck(TEST_TIMEOUT) { 751 @Override 752 protected boolean check() { 753 return listener.callCount > 0; 754 } 755 }.run(); 756 assertEquals(mWebView, listener.webView); 757 assertNull(listener.picture); 758 759 final int oldCallCount = listener.callCount; 760 final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL); 761 mOnUiThread.loadUrlAndWaitForCompletion(newUrl); 762 new PollingCheck(TEST_TIMEOUT) { 763 @Override 764 protected boolean check() { 765 return listener.callCount > oldCallCount; 766 } 767 }.run(); 768 } 769 770 @UiThreadTest 771 public void testAccessHttpAuthUsernamePassword() { 772 try { 773 WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword(); 774 775 String host = "http://localhost:8080"; 776 String realm = "testrealm"; 777 String userName = "user"; 778 String password = "password"; 779 780 String[] result = mWebView.getHttpAuthUsernamePassword(host, realm); 781 assertNull(result); 782 783 mWebView.setHttpAuthUsernamePassword(host, realm, userName, password); 784 result = mWebView.getHttpAuthUsernamePassword(host, realm); 785 assertNotNull(result); 786 assertEquals(userName, result[0]); 787 assertEquals(password, result[1]); 788 789 String newPassword = "newpassword"; 790 mWebView.setHttpAuthUsernamePassword(host, realm, userName, newPassword); 791 result = mWebView.getHttpAuthUsernamePassword(host, realm); 792 assertNotNull(result); 793 assertEquals(userName, result[0]); 794 assertEquals(newPassword, result[1]); 795 796 String newUserName = "newuser"; 797 mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 798 result = mWebView.getHttpAuthUsernamePassword(host, realm); 799 assertNotNull(result); 800 assertEquals(newUserName, result[0]); 801 assertEquals(newPassword, result[1]); 802 803 // the user is set to null, can not change any thing in the future 804 mWebView.setHttpAuthUsernamePassword(host, realm, null, password); 805 result = mWebView.getHttpAuthUsernamePassword(host, realm); 806 assertNotNull(result); 807 assertNull(result[0]); 808 assertEquals(password, result[1]); 809 810 mWebView.setHttpAuthUsernamePassword(host, realm, userName, null); 811 result = mWebView.getHttpAuthUsernamePassword(host, realm); 812 assertNotNull(result); 813 assertEquals(userName, result[0]); 814 assertEquals(null, result[1]); 815 816 mWebView.setHttpAuthUsernamePassword(host, realm, null, null); 817 result = mWebView.getHttpAuthUsernamePassword(host, realm); 818 assertNotNull(result); 819 assertNull(result[0]); 820 assertNull(result[1]); 821 822 mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 823 result = mWebView.getHttpAuthUsernamePassword(host, realm); 824 assertNotNull(result); 825 assertEquals(newUserName, result[0]); 826 assertEquals(newPassword, result[1]); 827 } finally { 828 WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword(); 829 } 830 } 831 832 public void testLoadData() throws Throwable { 833 final String HTML_CONTENT = 834 "<html><head><title>Hello,World!</title></head><body></body>" + 835 "</html>"; 836 mOnUiThread.loadDataAndWaitForCompletion(HTML_CONTENT, 837 "text/html", null); 838 assertEquals("Hello,World!", mOnUiThread.getTitle()); 839 840 startWebServer(false); 841 final ChromeClient webChromeClient = new ChromeClient(mOnUiThread); 842 final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 843 runTestOnUiThread(new Runnable() { 844 @Override 845 public void run() { 846 mWebView.getSettings().setJavaScriptEnabled(true); 847 mOnUiThread.setWebChromeClient(webChromeClient); 848 mOnUiThread.loadDataAndWaitForCompletion( 849 "<html><head></head><body onload=\"" + 850 "document.title = " + 851 "document.getElementById('frame').contentWindow.location.href;" + 852 "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>", 853 "text/html", null); 854 } 855 }); 856 assertEquals(ConsoleMessage.MessageLevel.ERROR, webChromeClient.getMessageLevel(10000)); 857 } 858 859 @UiThreadTest 860 public void testLoadDataWithBaseUrl() throws Throwable { 861 assertNull(mWebView.getUrl()); 862 String imgUrl = TestHtmlConstants.SMALL_IMG_URL; // relative 863 // Snippet of HTML that will prevent favicon requests to the test server. 864 final String HTML_HEADER = "<html><head><link rel=\"shortcut icon\" href=\"#\" /></head>"; 865 866 // Trying to resolve a relative URL against a data URL without a base URL 867 // will fail and we won't make a request to the test web server. 868 // By using the test web server as the base URL we expect to see a request 869 // for the relative URL in the test server. 870 startWebServer(false); 871 String baseUrl = mWebServer.getAssetUrl("foo.html"); 872 String historyUrl = "http://www.example.com/"; 873 mWebServer.resetRequestState(); 874 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 875 HTML_HEADER + "<body><img src=\"" + imgUrl + "\"/></body></html>", 876 "text/html", "UTF-8", historyUrl); 877 // Verify that the resource request makes it to the server. 878 assertTrue(mWebServer.wasResourceRequested(imgUrl)); 879 assertEquals(historyUrl, mWebView.getUrl()); 880 assertEquals(historyUrl, mWebView.getOriginalUrl()); 881 882 // Check that reported URL is "about:blank" when supplied history URL 883 // is null. 884 imgUrl = TestHtmlConstants.LARGE_IMG_URL; 885 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 886 HTML_HEADER + "<body><img src=\"" + imgUrl + "\"/></body></html>", 887 "text/html", "UTF-8", null); 888 assertTrue(mWebServer.wasResourceRequested(imgUrl)); 889 assertEquals("about:blank", mWebView.getUrl()); 890 assertEquals("about:blank", mWebView.getOriginalUrl()); 891 892 // Test that JavaScript can access content from the same origin as the base URL. 893 mWebView.getSettings().setJavaScriptEnabled(true); 894 final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 895 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 896 HTML_HEADER + "<body onload=\"" + 897 "document.title = document.getElementById('frame').contentWindow.location.href;" + 898 "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>", 899 "text/html", "UTF-8", null); 900 assertEquals(crossOriginUrl, mWebView.getTitle()); 901 902 // Check that when the base URL uses the 'data' scheme, a 'data' scheme URL is used and the 903 // history URL is ignored. 904 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo", 905 HTML_HEADER + "<body>bar</body></html>", "text/html", "UTF-8", 906 historyUrl); 907 assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("data:text/html") == 0); 908 assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("bar") > 0); 909 910 // Check that when a non-data: base URL is used, we treat the String to load as 911 // a raw string and just dump it into the WebView, i.e. not decoding any URL entities. 912 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("http://www.foo.com", 913 HTML_HEADER + "<title>Hello World%21</title><body>bar</body></html>", 914 "text/html", "UTF-8", null); 915 assertEquals("Hello World%21", mWebView.getTitle()); 916 917 // Check that when a data: base URL is used, we treat the String to load as a data: URL 918 // and run load steps such as decoding URL entities (i.e., contrary to the test case 919 // above.) 920 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo", 921 HTML_HEADER + "<title>Hello World%21</title></html>", "text/html", "UTF-8", null); 922 assertEquals("Hello World!", mWebView.getTitle()); 923 924 // Check the method is null input safe. 925 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(null, null, null, null, null); 926 assertEquals("about:blank", mWebView.getUrl()); 927 assertEquals("about:blank", mWebView.getOriginalUrl()); 928 } 929 930 private static class WaitForFindResultsListener extends FutureTask<Integer> 931 implements WebView.FindListener { 932 public WaitForFindResultsListener() { 933 super(new Runnable() { 934 @Override 935 public void run() { } 936 }, null); 937 } 938 939 @Override 940 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, 941 boolean isDoneCounting) { 942 if (isDoneCounting) { 943 set(numberOfMatches); 944 } 945 } 946 } 947 948 public void testFindAll() throws Throwable { 949 // Make the page scrollable, so we can detect the scrolling to make sure the 950 // content fully loaded. 951 mOnUiThread.setInitialScale(100); 952 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 953 int dimension = Math.max(metrics.widthPixels, metrics.heightPixels); 954 // create a paragraph high enough to take up the entire screen 955 String p = "<p style=\"height:" + dimension + "px;\">" + 956 "Find all instances of find on the page and highlight them.</p>"; 957 958 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 959 + "</body></html>", "text/html", null); 960 961 WaitForFindResultsListener l = new WaitForFindResultsListener(); 962 int previousScrollY = mOnUiThread.getScrollY(); 963 mOnUiThread.pageDown(true); 964 // Wait for content fully loaded. 965 waitForScrollingComplete(previousScrollY); 966 mOnUiThread.setFindListener(l); 967 mOnUiThread.findAll("find"); 968 969 assertEquals(2, l.get().intValue()); 970 } 971 972 public void testFindNext() throws Throwable { 973 // Reset the scaling so that finding the next "all" text will require scrolling. 974 mOnUiThread.setInitialScale(100); 975 976 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 977 int dimension = Math.max(metrics.widthPixels, metrics.heightPixels); 978 // create a paragraph high enough to take up the entire screen 979 String p = "<p style=\"height:" + dimension + "px;\">" + 980 "Find all instances of a word on the page and highlight them.</p>"; 981 982 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + p + "</body></html>", "text/html", null); 983 984 // highlight all the strings found 985 mOnUiThread.findAll("all"); 986 getInstrumentation().waitForIdleSync(); 987 988 int previousScrollY = mOnUiThread.getScrollY(); 989 990 // Focus "all" in the second page and assert that the view scrolls. 991 mOnUiThread.findNext(true); 992 waitForScrollingComplete(previousScrollY); 993 assertTrue(mOnUiThread.getScrollY() > previousScrollY); 994 previousScrollY = mOnUiThread.getScrollY(); 995 996 // Focus "all" in the first page and assert that the view scrolls. 997 mOnUiThread.findNext(true); 998 waitForScrollingComplete(previousScrollY); 999 assertTrue(mOnUiThread.getScrollY() < previousScrollY); 1000 previousScrollY = mOnUiThread.getScrollY(); 1001 1002 // Focus "all" in the second page and assert that the view scrolls. 1003 mOnUiThread.findNext(false); 1004 waitForScrollingComplete(previousScrollY); 1005 assertTrue(mOnUiThread.getScrollY() > previousScrollY); 1006 previousScrollY = mOnUiThread.getScrollY(); 1007 1008 // Focus "all" in the first page and assert that the view scrolls. 1009 mOnUiThread.findNext(false); 1010 waitForScrollingComplete(previousScrollY); 1011 assertTrue(mOnUiThread.getScrollY() < previousScrollY); 1012 previousScrollY = mOnUiThread.getScrollY(); 1013 1014 // clear the result 1015 mOnUiThread.clearMatches(); 1016 getInstrumentation().waitForIdleSync(); 1017 1018 // can not scroll any more 1019 mOnUiThread.findNext(false); 1020 waitForScrollingComplete(previousScrollY); 1021 assertTrue(mOnUiThread.getScrollY() == previousScrollY); 1022 1023 mOnUiThread.findNext(true); 1024 waitForScrollingComplete(previousScrollY); 1025 assertTrue(mOnUiThread.getScrollY() == previousScrollY); 1026 } 1027 1028 public void testDocumentHasImages() throws Exception, Throwable { 1029 final class DocumentHasImageCheckHandler extends Handler { 1030 private boolean mReceived; 1031 private int mMsgArg1; 1032 public DocumentHasImageCheckHandler(Looper looper) { 1033 super(looper); 1034 } 1035 @Override 1036 public void handleMessage(Message msg) { 1037 synchronized(this) { 1038 mReceived = true; 1039 mMsgArg1 = msg.arg1; 1040 } 1041 } 1042 public synchronized boolean hasCalledHandleMessage() { 1043 return mReceived; 1044 } 1045 public synchronized int getMsgArg1() { 1046 return mMsgArg1; 1047 } 1048 } 1049 1050 startWebServer(false); 1051 final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL); 1052 1053 // Create a handler on the UI thread. 1054 final DocumentHasImageCheckHandler handler = 1055 new DocumentHasImageCheckHandler(mWebView.getHandler().getLooper()); 1056 1057 runTestOnUiThread(new Runnable() { 1058 @Override 1059 public void run() { 1060 mOnUiThread.loadDataAndWaitForCompletion("<html><body><img src=\"" 1061 + imgUrl + "\"/></body></html>", "text/html", null); 1062 Message response = new Message(); 1063 response.setTarget(handler); 1064 assertFalse(handler.hasCalledHandleMessage()); 1065 mWebView.documentHasImages(response); 1066 } 1067 }); 1068 new PollingCheck() { 1069 @Override 1070 protected boolean check() { 1071 return handler.hasCalledHandleMessage(); 1072 } 1073 }.run(); 1074 assertEquals(1, handler.getMsgArg1()); 1075 } 1076 1077 public void testPageScroll() throws Throwable { 1078 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1079 int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels); 1080 String p = "<p style=\"height:" + dimension + "px;\">" + 1081 "Scroll by half the size of the page.</p>"; 1082 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1083 + p + "</body></html>", "text/html", null); 1084 1085 // Wait for UI thread to settle and receive page dimentions from renderer 1086 // such that we can invoke page down. 1087 new PollingCheck() { 1088 @Override 1089 protected boolean check() { 1090 return mOnUiThread.pageDown(false); 1091 } 1092 }.run(); 1093 1094 do { 1095 getInstrumentation().waitForIdleSync(); 1096 } while (mOnUiThread.pageDown(false)); 1097 1098 getInstrumentation().waitForIdleSync(); 1099 int bottomScrollY = mOnUiThread.getScrollY(); 1100 1101 assertTrue(mOnUiThread.pageUp(false)); 1102 1103 do { 1104 getInstrumentation().waitForIdleSync(); 1105 } while (mOnUiThread.pageUp(false)); 1106 1107 getInstrumentation().waitForIdleSync(); 1108 int topScrollY = mOnUiThread.getScrollY(); 1109 1110 // jump to the bottom 1111 assertTrue(mOnUiThread.pageDown(true)); 1112 getInstrumentation().waitForIdleSync(); 1113 assertEquals(bottomScrollY, mOnUiThread.getScrollY()); 1114 1115 // jump to the top 1116 assertTrue(mOnUiThread.pageUp(true)); 1117 getInstrumentation().waitForIdleSync(); 1118 assertEquals(topScrollY, mOnUiThread.getScrollY()); 1119 } 1120 1121 public void testGetContentHeight() throws Throwable { 1122 mOnUiThread.loadDataAndWaitForCompletion( 1123 "<html><body></body></html>", "text/html", null); 1124 new PollingCheck() { 1125 @Override 1126 protected boolean check() { 1127 return mOnUiThread.getScale() != 0 && mOnUiThread.getContentHeight() != 0 1128 && mOnUiThread.getHeight() != 0; 1129 } 1130 }.run(); 1131 assertEquals(mOnUiThread.getHeight(), 1132 mOnUiThread.getContentHeight() * mOnUiThread.getScale(), 2f); 1133 1134 final int pageHeight = 600; 1135 // set the margin to 0 1136 final String p = "<p style=\"height:" + pageHeight 1137 + "px;margin:0px auto;\">Get the height of HTML content.</p>"; 1138 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1139 + "</body></html>", "text/html", null); 1140 new PollingCheck() { 1141 @Override 1142 protected boolean check() { 1143 return mOnUiThread.getContentHeight() > pageHeight; 1144 } 1145 }.run(); 1146 1147 final int extraSpace = mOnUiThread.getContentHeight() - pageHeight; 1148 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1149 + p + "</body></html>", "text/html", null); 1150 new PollingCheck() { 1151 @Override 1152 protected boolean check() { 1153 return pageHeight + pageHeight + extraSpace == mOnUiThread.getContentHeight(); 1154 } 1155 }.run(); 1156 } 1157 1158 @UiThreadTest 1159 public void testPlatformNotifications() { 1160 WebView.enablePlatformNotifications(); 1161 WebView.disablePlatformNotifications(); 1162 } 1163 1164 @UiThreadTest 1165 public void testAccessPluginList() { 1166 assertNotNull(WebView.getPluginList()); 1167 1168 // can not find a way to install plugins 1169 mWebView.refreshPlugins(false); 1170 } 1171 1172 @UiThreadTest 1173 public void testDestroy() { 1174 // Create a new WebView, since we cannot call destroy() on a view in the hierarchy 1175 WebView localWebView = new WebView(getActivity()); 1176 localWebView.destroy(); 1177 } 1178 1179 public void testFlingScroll() throws Throwable { 1180 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1181 final int dimension = 10 * Math.max(metrics.widthPixels, metrics.heightPixels); 1182 String p = "<p style=\"height:" + dimension + "px;" + 1183 "width:" + dimension + "px\">Test fling scroll.</p>"; 1184 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1185 + "</body></html>", "text/html", null); 1186 new PollingCheck() { 1187 @Override 1188 protected boolean check() { 1189 return mOnUiThread.getContentHeight() >= dimension; 1190 } 1191 }.run(); 1192 getInstrumentation().waitForIdleSync(); 1193 1194 final int previousScrollX = mOnUiThread.getScrollX(); 1195 final int previousScrollY = mOnUiThread.getScrollY(); 1196 1197 mOnUiThread.flingScroll(100, 100); 1198 1199 new PollingCheck() { 1200 @Override 1201 protected boolean check() { 1202 return mOnUiThread.getScrollX() > previousScrollX && 1203 mOnUiThread.getScrollY() > previousScrollY; 1204 } 1205 }.run(); 1206 } 1207 1208 public void testRequestFocusNodeHref() throws Throwable { 1209 startWebServer(false); 1210 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 1211 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1212 final String links = "<DL><p><DT><A HREF=\"" + url1 1213 + "\">HTML_URL1</A><DT><A HREF=\"" + url2 1214 + "\">HTML_URL2</A></DL><p>"; 1215 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + links + "</body></html>", "text/html", null); 1216 getInstrumentation().waitForIdleSync(); 1217 1218 final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper()); 1219 final Message hrefMsg = new Message(); 1220 hrefMsg.setTarget(handler); 1221 1222 // focus on first link 1223 handler.reset(); 1224 getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 1225 mOnUiThread.requestFocusNodeHref(hrefMsg); 1226 new PollingCheck() { 1227 @Override 1228 protected boolean check() { 1229 boolean done = false; 1230 if (handler.hasCalledHandleMessage()) { 1231 if (handler.mResultUrl != null) { 1232 done = true; 1233 } else { 1234 handler.reset(); 1235 Message newMsg = new Message(); 1236 newMsg.setTarget(handler); 1237 mOnUiThread.requestFocusNodeHref(newMsg); 1238 } 1239 } 1240 return done; 1241 } 1242 }.run(); 1243 assertEquals(url1, handler.getResultUrl()); 1244 1245 // focus on second link 1246 handler.reset(); 1247 final Message hrefMsg2 = new Message(); 1248 hrefMsg2.setTarget(handler); 1249 getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 1250 mOnUiThread.requestFocusNodeHref(hrefMsg2); 1251 new PollingCheck() { 1252 @Override 1253 protected boolean check() { 1254 boolean done = false; 1255 final String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1256 if (handler.hasCalledHandleMessage()) { 1257 if (handler.mResultUrl != null && 1258 handler.mResultUrl.equals(url2)) { 1259 done = true; 1260 } else { 1261 handler.reset(); 1262 Message newMsg = new Message(); 1263 newMsg.setTarget(handler); 1264 mOnUiThread.requestFocusNodeHref(newMsg); 1265 } 1266 } 1267 return done; 1268 } 1269 }.run(); 1270 assertEquals(url2, handler.getResultUrl()); 1271 1272 mOnUiThread.requestFocusNodeHref(null); 1273 } 1274 1275 public void testRequestImageRef() throws Exception, Throwable { 1276 final class ImageLoaded { 1277 public boolean mImageLoaded; 1278 1279 @JavascriptInterface 1280 public void loaded() { 1281 mImageLoaded = true; 1282 } 1283 } 1284 final ImageLoaded imageLoaded = new ImageLoaded(); 1285 runTestOnUiThread(new Runnable() { 1286 public void run() { 1287 mOnUiThread.getSettings().setJavaScriptEnabled(true); 1288 } 1289 }); 1290 mOnUiThread.addJavascriptInterface(imageLoaded, "imageLoaded"); 1291 AssetManager assets = getActivity().getAssets(); 1292 Bitmap bitmap = BitmapFactory.decodeStream(assets.open(TestHtmlConstants.LARGE_IMG_URL)); 1293 int imgWidth = bitmap.getWidth(); 1294 int imgHeight = bitmap.getHeight(); 1295 1296 startWebServer(false); 1297 final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.LARGE_IMG_URL); 1298 mOnUiThread.loadDataAndWaitForCompletion( 1299 "<html><head><title>Title</title><style type=\"text/css\">" 1300 + "#imgElement { -webkit-transform: translate3d(0,0,1); }" 1301 + "#imgElement.finish { -webkit-transform: translate3d(0,0,0);" 1302 + " -webkit-transition-duration: 1ms; }</style>" 1303 + "<script type=\"text/javascript\">function imgLoad() {" 1304 + "imgElement = document.getElementById('imgElement');" 1305 + "imgElement.addEventListener('webkitTransitionEnd'," 1306 + "function(e) { imageLoaded.loaded(); });" 1307 + "imgElement.className = 'finish';}</script>" 1308 + "</head><body><img id=\"imgElement\" src=\"" + imgUrl 1309 + "\" width=\"" + imgWidth + "\" height=\"" + imgHeight 1310 + "\" onLoad=\"imgLoad()\"/></body></html>", "text/html", null); 1311 new PollingCheck() { 1312 @Override 1313 protected boolean check() { 1314 return imageLoaded.mImageLoaded; 1315 } 1316 }.run(); 1317 getInstrumentation().waitForIdleSync(); 1318 1319 final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper()); 1320 final Message msg = new Message(); 1321 msg.setTarget(handler); 1322 1323 // touch the image 1324 handler.reset(); 1325 int[] location = mOnUiThread.getLocationOnScreen(); 1326 1327 long time = SystemClock.uptimeMillis(); 1328 getInstrumentation().sendPointerSync( 1329 MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 1330 location[0] + imgWidth / 2, 1331 location[1] + imgHeight / 2, 0)); 1332 getInstrumentation().waitForIdleSync(); 1333 mOnUiThread.requestImageRef(msg); 1334 new PollingCheck() { 1335 @Override 1336 protected boolean check() { 1337 boolean done = false; 1338 if (handler.hasCalledHandleMessage()) { 1339 if (handler.mResultUrl != null) { 1340 done = true; 1341 } else { 1342 handler.reset(); 1343 Message newMsg = new Message(); 1344 newMsg.setTarget(handler); 1345 mOnUiThread.requestImageRef(newMsg); 1346 } 1347 } 1348 return done; 1349 } 1350 }.run(); 1351 assertEquals(imgUrl, handler.mResultUrl); 1352 } 1353 1354 @UiThreadTest 1355 public void testDebugDump() { 1356 mWebView.debugDump(); 1357 } 1358 1359 public void testGetHitTestResult() throws Throwable { 1360 final String anchor = "<p><a href=\"" + TestHtmlConstants.EXT_WEB_URL1 1361 + "\">normal anchor</a></p>"; 1362 final String blankAnchor = "<p><a href=\"\">blank anchor</a></p>"; 1363 final String form = "<p><form><input type=\"text\" name=\"Test\"><br>" 1364 + "<input type=\"submit\" value=\"Submit\"></form></p>"; 1365 String phoneNo = "3106984000"; 1366 final String tel = "<p><a href=\"tel:" + phoneNo + "\">Phone</a></p>"; 1367 String email = "test (at) gmail.com"; 1368 final String mailto = "<p><a href=\"mailto:" + email + "\">Email</a></p>"; 1369 String location = "shanghai"; 1370 final String geo = "<p><a href=\"geo:0,0?q=" + location + "\">Location</a></p>"; 1371 1372 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("fake://home", 1373 "<html><body>" + anchor + blankAnchor + form + tel + mailto + 1374 geo + "</body></html>", "text/html", "UTF-8", null); 1375 getInstrumentation().waitForIdleSync(); 1376 1377 // anchor 1378 moveFocusDown(); 1379 HitTestResult hitTestResult = mOnUiThread.getHitTestResult(); 1380 assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType()); 1381 assertEquals(TestHtmlConstants.EXT_WEB_URL1, hitTestResult.getExtra()); 1382 1383 // blank anchor 1384 moveFocusDown(); 1385 hitTestResult = mOnUiThread.getHitTestResult(); 1386 assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType()); 1387 assertEquals("fake://home", hitTestResult.getExtra()); 1388 1389 // text field 1390 moveFocusDown(); 1391 hitTestResult = mOnUiThread.getHitTestResult(); 1392 assertEquals(HitTestResult.EDIT_TEXT_TYPE, hitTestResult.getType()); 1393 assertNull(hitTestResult.getExtra()); 1394 1395 // submit button 1396 moveFocusDown(); 1397 hitTestResult = mOnUiThread.getHitTestResult(); 1398 assertEquals(HitTestResult.UNKNOWN_TYPE, hitTestResult.getType()); 1399 assertNull(hitTestResult.getExtra()); 1400 1401 // phone number 1402 moveFocusDown(); 1403 hitTestResult = mOnUiThread.getHitTestResult(); 1404 assertEquals(HitTestResult.PHONE_TYPE, hitTestResult.getType()); 1405 assertEquals(phoneNo, hitTestResult.getExtra()); 1406 1407 // email 1408 moveFocusDown(); 1409 hitTestResult = mOnUiThread.getHitTestResult(); 1410 assertEquals(HitTestResult.EMAIL_TYPE, hitTestResult.getType()); 1411 assertEquals(email, hitTestResult.getExtra()); 1412 1413 // geo address 1414 moveFocusDown(); 1415 hitTestResult = mOnUiThread.getHitTestResult(); 1416 assertEquals(HitTestResult.GEO_TYPE, hitTestResult.getType()); 1417 assertEquals(location, hitTestResult.getExtra()); 1418 } 1419 1420 public void testSetInitialScale() throws Throwable { 1421 final String p = "<p style=\"height:1000px;width:1000px\">Test setInitialScale.</p>"; 1422 final float defaultScale = 1423 getInstrumentation().getTargetContext().getResources().getDisplayMetrics().density; 1424 1425 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1426 + "</body></html>", "text/html", null); 1427 1428 new PollingCheck(TEST_TIMEOUT) { 1429 @Override 1430 protected boolean check() { 1431 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; 1432 } 1433 }.run(); 1434 1435 mOnUiThread.setInitialScale(0); 1436 // modify content to fool WebKit into re-loading 1437 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1438 + "2" + "</body></html>", "text/html", null); 1439 1440 new PollingCheck(TEST_TIMEOUT) { 1441 @Override 1442 protected boolean check() { 1443 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; 1444 } 1445 }.run(); 1446 1447 mOnUiThread.setInitialScale(50); 1448 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1449 + "3" + "</body></html>", "text/html", null); 1450 1451 new PollingCheck(TEST_TIMEOUT) { 1452 @Override 1453 protected boolean check() { 1454 return Math.abs(0.5 - mOnUiThread.getScale()) < .01f; 1455 } 1456 }.run(); 1457 1458 mOnUiThread.setInitialScale(0); 1459 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1460 + "4" + "</body></html>", "text/html", null); 1461 1462 new PollingCheck(TEST_TIMEOUT) { 1463 @Override 1464 protected boolean check() { 1465 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; 1466 } 1467 }.run(); 1468 } 1469 1470 @UiThreadTest 1471 public void testGetFavicon() throws Exception { 1472 startWebServer(false); 1473 String url = mWebServer.getAssetUrl(TestHtmlConstants.TEST_FAVICON_URL); 1474 mOnUiThread.loadUrlAndWaitForCompletion(url); 1475 mWebView.getFavicon(); 1476 // ToBeFixed: Favicon is not loaded automatically. 1477 // assertNotNull(mWebView.getFavicon()); 1478 } 1479 1480 @UiThreadTest 1481 public void testClearHistory() throws Exception { 1482 startWebServer(false); 1483 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 1484 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1485 String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); 1486 1487 mOnUiThread.loadUrlAndWaitForCompletion(url1); 1488 pollingCheckWebBackForwardList(url1, 0, 1); 1489 1490 mOnUiThread.loadUrlAndWaitForCompletion(url2); 1491 pollingCheckWebBackForwardList(url2, 1, 2); 1492 1493 mOnUiThread.loadUrlAndWaitForCompletion(url3); 1494 pollingCheckWebBackForwardList(url3, 2, 3); 1495 1496 mWebView.clearHistory(); 1497 1498 // only current URL is left after clearing 1499 pollingCheckWebBackForwardList(url3, 0, 1); 1500 } 1501 1502 @UiThreadTest 1503 public void testSaveAndRestoreState() throws Throwable { 1504 // nothing to save 1505 assertNull(mWebView.saveState(new Bundle())); 1506 1507 startWebServer(false); 1508 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 1509 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1510 String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); 1511 1512 // make a history list 1513 mOnUiThread.loadUrlAndWaitForCompletion(url1); 1514 pollingCheckWebBackForwardList(url1, 0, 1); 1515 mOnUiThread.loadUrlAndWaitForCompletion(url2); 1516 pollingCheckWebBackForwardList(url2, 1, 2); 1517 mOnUiThread.loadUrlAndWaitForCompletion(url3); 1518 pollingCheckWebBackForwardList(url3, 2, 3); 1519 1520 // save the list 1521 Bundle bundle = new Bundle(); 1522 WebBackForwardList saveList = mWebView.saveState(bundle); 1523 assertNotNull(saveList); 1524 assertEquals(3, saveList.getSize()); 1525 assertEquals(2, saveList.getCurrentIndex()); 1526 assertEquals(url1, saveList.getItemAtIndex(0).getUrl()); 1527 assertEquals(url2, saveList.getItemAtIndex(1).getUrl()); 1528 assertEquals(url3, saveList.getItemAtIndex(2).getUrl()); 1529 1530 // change the content to a new "blank" web view without history 1531 final WebView newWebView = new WebView(getActivity()); 1532 1533 WebBackForwardList copyListBeforeRestore = newWebView.copyBackForwardList(); 1534 assertNotNull(copyListBeforeRestore); 1535 assertEquals(0, copyListBeforeRestore.getSize()); 1536 1537 // restore the list 1538 final WebBackForwardList restoreList = newWebView.restoreState(bundle); 1539 assertNotNull(restoreList); 1540 assertEquals(3, restoreList.getSize()); 1541 assertEquals(2, saveList.getCurrentIndex()); 1542 /* ToBeFixed: The WebHistoryItems do not get inflated. Uncomment remaining tests when fixed. 1543 // wait for the list items to get inflated 1544 new PollingCheck(TEST_TIMEOUT) { 1545 @Override 1546 protected boolean check() { 1547 return restoreList.getItemAtIndex(0).getUrl() != null && 1548 restoreList.getItemAtIndex(1).getUrl() != null && 1549 restoreList.getItemAtIndex(2).getUrl() != null; 1550 } 1551 }.run(); 1552 assertEquals(url1, restoreList.getItemAtIndex(0).getUrl()); 1553 assertEquals(url2, restoreList.getItemAtIndex(1).getUrl()); 1554 assertEquals(url3, restoreList.getItemAtIndex(2).getUrl()); 1555 1556 WebBackForwardList copyListAfterRestore = newWebView.copyBackForwardList(); 1557 assertNotNull(copyListAfterRestore); 1558 assertEquals(3, copyListAfterRestore.getSize()); 1559 assertEquals(2, copyListAfterRestore.getCurrentIndex()); 1560 assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl()); 1561 assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl()); 1562 assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl()); 1563 */ 1564 } 1565 1566 public void testSetWebViewClient() throws Throwable { 1567 final ScaleChangedWebViewClient webViewClient = new ScaleChangedWebViewClient(); 1568 mOnUiThread.setWebViewClient(webViewClient); 1569 startWebServer(false); 1570 1571 assertFalse(webViewClient.onScaleChangedCalled()); 1572 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1573 mOnUiThread.loadUrlAndWaitForCompletion(url1); 1574 1575 mOnUiThread.zoomIn(); 1576 webViewClient.waitForScaleChanged(); 1577 } 1578 1579 @UiThreadTest 1580 public void testInsecureSiteClearsCertificate() throws Throwable { 1581 final class MockWebViewClient extends WaitForLoadedClient { 1582 public MockWebViewClient() { 1583 super(mOnUiThread); 1584 } 1585 @Override 1586 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 1587 handler.proceed(); 1588 } 1589 } 1590 1591 startWebServer(true); 1592 mOnUiThread.setWebViewClient(new MockWebViewClient()); 1593 mOnUiThread.loadUrlAndWaitForCompletion( 1594 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); 1595 SslCertificate cert = mWebView.getCertificate(); 1596 assertNotNull(cert); 1597 assertEquals("Android", cert.getIssuedTo().getUName()); 1598 1599 stopWebServer(); 1600 1601 startWebServer(false); 1602 mOnUiThread.loadUrlAndWaitForCompletion( 1603 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); 1604 assertNull(mWebView.getCertificate()); 1605 } 1606 1607 @UiThreadTest 1608 public void testSecureSiteSetsCertificate() throws Throwable { 1609 final class MockWebViewClient extends WaitForLoadedClient { 1610 public MockWebViewClient() { 1611 super(mOnUiThread); 1612 } 1613 @Override 1614 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 1615 handler.proceed(); 1616 } 1617 } 1618 1619 startWebServer(false); 1620 mOnUiThread.loadUrlAndWaitForCompletion( 1621 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); 1622 assertNull(mWebView.getCertificate()); 1623 1624 stopWebServer(); 1625 1626 startWebServer(true); 1627 mOnUiThread.setWebViewClient(new MockWebViewClient()); 1628 mOnUiThread.loadUrlAndWaitForCompletion( 1629 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); 1630 SslCertificate cert = mWebView.getCertificate(); 1631 assertNotNull(cert); 1632 assertEquals("Android", cert.getIssuedTo().getUName()); 1633 } 1634 1635 @UiThreadTest 1636 public void testClearSslPreferences() throws Throwable { 1637 // Load the first page. We expect a call to 1638 // WebViewClient.onReceivedSslError(). 1639 final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient(); 1640 startWebServer(true); 1641 final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1642 mOnUiThread.setWebViewClient(webViewClient); 1643 mOnUiThread.clearSslPreferences(); 1644 mOnUiThread.loadUrlAndWaitForCompletion(url); 1645 assertTrue(webViewClient.wasOnReceivedSslErrorCalled()); 1646 1647 // Load the page again. We expect another call to 1648 // WebViewClient.onReceivedSslError() since we cleared sslpreferences. 1649 mOnUiThread.clearSslPreferences(); 1650 webViewClient.resetWasOnReceivedSslErrorCalled(); 1651 mOnUiThread.loadUrlAndWaitForCompletion(url); 1652 assertTrue(webViewClient.wasOnReceivedSslErrorCalled()); 1653 assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle()); 1654 1655 // Load the page once again, without clearing the sslpreferences. 1656 // Make sure we do not get the callback. 1657 webViewClient.resetWasOnReceivedSslErrorCalled(); 1658 mOnUiThread.loadUrlAndWaitForCompletion(url); 1659 assertFalse(webViewClient.wasOnReceivedSslErrorCalled()); 1660 assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle()); 1661 } 1662 1663 public void testOnReceivedSslError() throws Throwable { 1664 final class MockWebViewClient extends WaitForLoadedClient { 1665 private String mErrorUrl; 1666 private WebView mWebView; 1667 1668 public MockWebViewClient() { 1669 super(mOnUiThread); 1670 } 1671 @Override 1672 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 1673 mWebView = view; 1674 mErrorUrl = error.getUrl(); 1675 handler.proceed(); 1676 } 1677 public String errorUrl() { 1678 return mErrorUrl; 1679 } 1680 public WebView webView() { 1681 return mWebView; 1682 } 1683 } 1684 1685 startWebServer(true); 1686 final String errorUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1687 final MockWebViewClient webViewClient = new MockWebViewClient(); 1688 mOnUiThread.setWebViewClient(webViewClient); 1689 mOnUiThread.clearSslPreferences(); 1690 mOnUiThread.loadUrlAndWaitForCompletion(errorUrl); 1691 1692 assertEquals(mWebView, webViewClient.webView()); 1693 assertEquals(errorUrl, webViewClient.errorUrl()); 1694 } 1695 1696 public void testOnReceivedSslErrorProceed() throws Throwable { 1697 final class MockWebViewClient extends WaitForLoadedClient { 1698 public MockWebViewClient() { 1699 super(mOnUiThread); 1700 } 1701 @Override 1702 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 1703 handler.proceed(); 1704 } 1705 } 1706 1707 startWebServer(true); 1708 final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1709 mOnUiThread.setWebViewClient(new MockWebViewClient()); 1710 mOnUiThread.loadUrlAndWaitForCompletion(url); 1711 assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle()); 1712 } 1713 1714 public void testOnReceivedSslErrorCancel() throws Throwable { 1715 final class MockWebViewClient extends WaitForLoadedClient { 1716 public MockWebViewClient() { 1717 super(mOnUiThread); 1718 } 1719 @Override 1720 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 1721 handler.cancel(); 1722 } 1723 } 1724 1725 startWebServer(true); 1726 final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1727 mOnUiThread.setWebViewClient(new MockWebViewClient()); 1728 mOnUiThread.clearSslPreferences(); 1729 mOnUiThread.loadUrlAndWaitForCompletion(url); 1730 assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle())); 1731 } 1732 1733 public void testSslErrorProceedResponseReusedForSameHost() throws Throwable { 1734 // Load the first page. We expect a call to 1735 // WebViewClient.onReceivedSslError(). 1736 final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient(); 1737 startWebServer(true); 1738 final String firstUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 1739 mOnUiThread.setWebViewClient(webViewClient); 1740 mOnUiThread.clearSslPreferences(); 1741 mOnUiThread.loadUrlAndWaitForCompletion(firstUrl); 1742 assertTrue(webViewClient.wasOnReceivedSslErrorCalled()); 1743 1744 // Load the second page. We don't expect a call to 1745 // WebViewClient.onReceivedSslError(), but the page should load. 1746 webViewClient.resetWasOnReceivedSslErrorCalled(); 1747 final String sameHostUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1748 mOnUiThread.loadUrlAndWaitForCompletion(sameHostUrl); 1749 assertFalse(webViewClient.wasOnReceivedSslErrorCalled()); 1750 assertEquals("Second page", mOnUiThread.getTitle()); 1751 } 1752 1753 public void testSslErrorProceedResponseNotReusedForDifferentHost() throws Throwable { 1754 // Load the first page. We expect a call to 1755 // WebViewClient.onReceivedSslError(). 1756 final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient(); 1757 startWebServer(true); 1758 final String firstUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 1759 mOnUiThread.setWebViewClient(webViewClient); 1760 mOnUiThread.clearSslPreferences(); 1761 mOnUiThread.loadUrlAndWaitForCompletion(firstUrl); 1762 assertTrue(webViewClient.wasOnReceivedSslErrorCalled()); 1763 1764 // Load the second page. We expect another call to 1765 // WebViewClient.onReceivedSslError(). 1766 webViewClient.resetWasOnReceivedSslErrorCalled(); 1767 // The test server uses the host "localhost". "127.0.0.1" works as an 1768 // alias, but will be considered unique by the WebView. 1769 final String differentHostUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2).replace( 1770 "localhost", "127.0.0.1"); 1771 mOnUiThread.loadUrlAndWaitForCompletion(differentHostUrl); 1772 assertTrue(webViewClient.wasOnReceivedSslErrorCalled()); 1773 assertEquals("Second page", mOnUiThread.getTitle()); 1774 } 1775 1776 public void testRequestChildRectangleOnScreen() throws Throwable { 1777 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1778 final int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels); 1779 String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\"> </p>"; 1780 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1781 + "</body></html>", "text/html", null); 1782 new PollingCheck() { 1783 @Override 1784 protected boolean check() { 1785 return mOnUiThread.getContentHeight() >= dimension; 1786 } 1787 }.run(); 1788 1789 int origX = mOnUiThread.getScrollX(); 1790 int origY = mOnUiThread.getScrollY(); 1791 1792 int half = dimension / 2; 1793 Rect rect = new Rect(half, half, half + 1, half + 1); 1794 assertTrue(mOnUiThread.requestChildRectangleOnScreen(mWebView, rect, true)); 1795 assertTrue(mOnUiThread.getScrollX() > origX); 1796 assertTrue(mOnUiThread.getScrollY() > origY); 1797 } 1798 1799 public void testSetDownloadListener() throws Throwable { 1800 final class MyDownloadListener implements DownloadListener { 1801 public String url; 1802 public String mimeType; 1803 public long contentLength; 1804 public String contentDisposition; 1805 public boolean called; 1806 1807 @Override 1808 public void onDownloadStart(String url, String userAgent, String contentDisposition, 1809 String mimetype, long contentLength) { 1810 this.url = url; 1811 this.mimeType = mimetype; 1812 this.contentLength = contentLength; 1813 this.contentDisposition = contentDisposition; 1814 this.called = true; 1815 } 1816 } 1817 1818 final String mimeType = "application/octet-stream"; 1819 final int length = 100; 1820 final MyDownloadListener listener = new MyDownloadListener(); 1821 1822 startWebServer(false); 1823 final String url = mWebServer.getBinaryUrl(mimeType, length); 1824 1825 // By default, WebView sends an intent to ask the system to 1826 // handle loading a new URL. We set WebViewClient as 1827 // WebViewClient.shouldOverrideUrlLoading() returns false, so 1828 // the WebView will load the new URL. 1829 mOnUiThread.setDownloadListener(listener); 1830 mOnUiThread.getSettings().setJavaScriptEnabled(true); 1831 1832 // See b/13675265 for discussion on why the setTimeout is necessary. 1833 // Works around a Blink bug. 1834 mOnUiThread.loadDataAndWaitForCompletion( 1835 "<html><body onload=\"setTimeout(" + 1836 "function() { window.location = \'" + url + "\'; }, 100);\">" + 1837 "</body></html>", "text/html", null); 1838 // Wait for layout to complete before setting focus. 1839 getInstrumentation().waitForIdleSync(); 1840 1841 new PollingCheck(TEST_TIMEOUT) { 1842 @Override 1843 protected boolean check() { 1844 return listener.called; 1845 } 1846 }.run(); 1847 assertEquals(url, listener.url); 1848 assertTrue(listener.contentDisposition.contains("test.bin")); 1849 // ToBeFixed: uncomment the following tests after fixing the framework 1850 // assertEquals(mimeType, listener.mimeType); 1851 // assertEquals(length, listener.contentLength); 1852 } 1853 1854 @UiThreadTest 1855 public void testSetLayoutParams() { 1856 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(600, 800); 1857 mWebView.setLayoutParams(params); 1858 assertSame(params, mWebView.getLayoutParams()); 1859 } 1860 1861 @UiThreadTest 1862 public void testSetMapTrackballToArrowKeys() { 1863 mWebView.setMapTrackballToArrowKeys(true); 1864 } 1865 1866 public void testSetNetworkAvailable() throws Exception { 1867 WebSettings settings = mOnUiThread.getSettings(); 1868 settings.setJavaScriptEnabled(true); 1869 startWebServer(false); 1870 1871 String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL); 1872 mOnUiThread.loadUrlAndWaitForCompletion(url); 1873 assertEquals("ONLINE", mOnUiThread.getTitle()); 1874 1875 mOnUiThread.setNetworkAvailable(false); 1876 1877 // Wait for the DOM to receive notification of the network state change. 1878 new PollingCheck(TEST_TIMEOUT) { 1879 @Override 1880 protected boolean check() { 1881 return mOnUiThread.getTitle().equals("OFFLINE"); 1882 } 1883 }.run(); 1884 1885 mOnUiThread.setNetworkAvailable(true); 1886 1887 // Wait for the DOM to receive notification of the network state change. 1888 new PollingCheck(TEST_TIMEOUT) { 1889 @Override 1890 protected boolean check() { 1891 return mOnUiThread.getTitle().equals("ONLINE"); 1892 } 1893 }.run(); 1894 } 1895 1896 public void testSetWebChromeClient() throws Throwable { 1897 final class MockWebChromeClient extends WaitForProgressClient { 1898 private boolean mOnProgressChanged = false; 1899 1900 public MockWebChromeClient() { 1901 super(mOnUiThread); 1902 } 1903 1904 @Override 1905 public void onProgressChanged(WebView view, int newProgress) { 1906 super.onProgressChanged(view, newProgress); 1907 mOnProgressChanged = true; 1908 } 1909 public boolean onProgressChangedCalled() { 1910 return mOnProgressChanged; 1911 } 1912 } 1913 1914 final MockWebChromeClient webChromeClient = new MockWebChromeClient(); 1915 1916 mOnUiThread.setWebChromeClient(webChromeClient); 1917 getInstrumentation().waitForIdleSync(); 1918 assertFalse(webChromeClient.onProgressChangedCalled()); 1919 1920 startWebServer(false); 1921 final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1922 mOnUiThread.loadUrlAndWaitForCompletion(url); 1923 getInstrumentation().waitForIdleSync(); 1924 1925 new PollingCheck(TEST_TIMEOUT) { 1926 @Override 1927 protected boolean check() { 1928 return webChromeClient.onProgressChangedCalled(); 1929 } 1930 }.run(); 1931 } 1932 1933 public void testPauseResumeTimers() throws Throwable { 1934 class Monitor { 1935 private boolean mIsUpdated; 1936 1937 @JavascriptInterface 1938 public synchronized void update() { 1939 mIsUpdated = true; 1940 notify(); 1941 } 1942 public synchronized boolean waitForUpdate() { 1943 while (!mIsUpdated) { 1944 try { 1945 // This is slightly flaky, as we can't guarantee that 1946 // this is a sufficient time limit, but there's no way 1947 // around this. 1948 wait(1000); 1949 if (!mIsUpdated) { 1950 return false; 1951 } 1952 } catch (InterruptedException e) { 1953 } 1954 } 1955 mIsUpdated = false; 1956 return true; 1957 } 1958 }; 1959 final Monitor monitor = new Monitor(); 1960 final String updateMonitorHtml = "<html>" + 1961 "<body onload=\"monitor.update();\"></body></html>"; 1962 1963 // Test that JavaScript is executed even with timers paused. 1964 runTestOnUiThread(new Runnable() { 1965 @Override 1966 public void run() { 1967 mWebView.getSettings().setJavaScriptEnabled(true); 1968 mWebView.addJavascriptInterface(monitor, "monitor"); 1969 mWebView.pauseTimers(); 1970 mOnUiThread.loadDataAndWaitForCompletion(updateMonitorHtml, 1971 "text/html", null); 1972 } 1973 }); 1974 assertTrue(monitor.waitForUpdate()); 1975 1976 // Start a timer and test that it does not fire. 1977 mOnUiThread.loadDataAndWaitForCompletion( 1978 "<html><body onload='setTimeout(function(){monitor.update();},100)'>" + 1979 "</body></html>", "text/html", null); 1980 assertFalse(monitor.waitForUpdate()); 1981 1982 // Resume timers and test that the timer fires. 1983 mOnUiThread.resumeTimers(); 1984 assertTrue(monitor.waitForUpdate()); 1985 } 1986 1987 // verify query parameters can be passed correctly to android asset files 1988 public void testAndroidAssetQueryParam() { 1989 1990 WebSettings settings = mOnUiThread.getSettings(); 1991 settings.setJavaScriptEnabled(true); 1992 // test passing a parameter 1993 String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.PARAM_ASSET_URL+"?val=SUCCESS"); 1994 mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); 1995 assertEquals("SUCCESS", mOnUiThread.getTitle()); 1996 } 1997 1998 // verify anchors work correctly for android asset files 1999 public void testAndroidAssetAnchor() { 2000 2001 WebSettings settings = mOnUiThread.getSettings(); 2002 settings.setJavaScriptEnabled(true); 2003 // test using an anchor 2004 String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.ANCHOR_ASSET_URL+"#anchor"); 2005 mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); 2006 assertEquals("anchor", mOnUiThread.getTitle()); 2007 } 2008 2009 public void testEvaluateJavascript() { 2010 mOnUiThread.getSettings().setJavaScriptEnabled(true); 2011 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 2012 2013 EvaluateJsResultPollingCheck jsResult = new EvaluateJsResultPollingCheck("2"); 2014 mOnUiThread.evaluateJavascript("1+1", jsResult); 2015 jsResult.run(); 2016 2017 jsResult = new EvaluateJsResultPollingCheck("9"); 2018 mOnUiThread.evaluateJavascript("1+1; 4+5", jsResult); 2019 jsResult.run(); 2020 2021 final String EXPECTED_TITLE = "test"; 2022 mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null); 2023 new PollingCheck(TEST_TIMEOUT) { 2024 @Override 2025 protected boolean check() { 2026 return mOnUiThread.getTitle().equals(EXPECTED_TITLE); 2027 } 2028 }.run(); 2029 } 2030 2031 // Verify Print feature can create a PDF file with a correct preamble. 2032 public void testPrinting() throws Throwable { 2033 mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" + 2034 "<body>foo</body></html>", 2035 "text/html", null); 2036 final PrintDocumentAdapter adapter = mOnUiThread.createPrintDocumentAdapter(); 2037 printDocumentStart(adapter); 2038 PrintAttributes attributes = new PrintAttributes.Builder() 2039 .setMediaSize(PrintAttributes.MediaSize.ISO_A4) 2040 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300)) 2041 .setMinMargins(PrintAttributes.Margins.NO_MARGINS) 2042 .build(); 2043 final WebViewStubActivity activity = getActivity(); 2044 final File file = activity.getFileStreamPath(PRINTER_TEST_FILE); 2045 final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, 2046 ParcelFileDescriptor.parseMode("w")); 2047 final FutureTask<Boolean> result = 2048 new FutureTask<Boolean>(new Callable<Boolean>() { 2049 public Boolean call() { 2050 return true; 2051 } 2052 }); 2053 printDocumentLayout(adapter, null, attributes, 2054 new LayoutResultCallback() { 2055 // Called on UI thread 2056 @Override 2057 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 2058 savePrintedPage(adapter, descriptor, result); 2059 } 2060 }); 2061 try { 2062 result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS); 2063 assertTrue(file.length() > 0); 2064 FileInputStream in = new FileInputStream(file); 2065 byte[] b = new byte[PDF_PREAMBLE.length()]; 2066 in.read(b); 2067 String preamble = new String(b); 2068 assertEquals(PDF_PREAMBLE, preamble); 2069 } finally { 2070 // close the descriptor, if not closed already. 2071 descriptor.close(); 2072 file.delete(); 2073 } 2074 } 2075 2076 private void savePrintedPage(final PrintDocumentAdapter adapter, 2077 final ParcelFileDescriptor descriptor, final FutureTask<Boolean> result) { 2078 adapter.onWrite(new PageRange[] {PageRange.ALL_PAGES}, descriptor, 2079 new CancellationSignal(), 2080 new WriteResultCallback() { 2081 @Override 2082 public void onWriteFinished(PageRange[] pages) { 2083 try { 2084 descriptor.close(); 2085 result.run(); 2086 } catch (IOException ex) { 2087 fail("Failed file operation: " + ex.toString()); 2088 } 2089 } 2090 }); 2091 } 2092 2093 private void printDocumentStart(final PrintDocumentAdapter adapter) { 2094 mOnUiThread.runOnUiThread(new Runnable() { 2095 @Override 2096 public void run() { 2097 adapter.onStart(); 2098 } 2099 }); 2100 } 2101 2102 private void printDocumentLayout(final PrintDocumentAdapter adapter, 2103 final PrintAttributes oldAttributes, final PrintAttributes newAttributes, 2104 final LayoutResultCallback layoutResultCallback) { 2105 mOnUiThread.runOnUiThread(new Runnable() { 2106 @Override 2107 public void run() { 2108 adapter.onLayout(oldAttributes, newAttributes, new CancellationSignal(), 2109 layoutResultCallback, null); 2110 } 2111 }); 2112 } 2113 2114 @UiThreadTest 2115 public void testInternals() { 2116 // Do not test these APIs. They are implementation details. 2117 } 2118 2119 private static class HrefCheckHandler extends Handler { 2120 private boolean mHadRecieved; 2121 2122 private String mResultUrl; 2123 2124 public HrefCheckHandler(Looper looper) { 2125 super(looper); 2126 } 2127 2128 public boolean hasCalledHandleMessage() { 2129 return mHadRecieved; 2130 } 2131 2132 public String getResultUrl() { 2133 return mResultUrl; 2134 } 2135 2136 public void reset(){ 2137 mResultUrl = null; 2138 mHadRecieved = false; 2139 } 2140 2141 @Override 2142 public void handleMessage(Message msg) { 2143 mResultUrl = msg.getData().getString("url"); 2144 mHadRecieved = true; 2145 } 2146 } 2147 2148 private void moveFocusDown() throws Throwable { 2149 // send down key and wait for idle 2150 getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 2151 // waiting for idle isn't always sufficient for the key to be fully processed 2152 Thread.sleep(500); 2153 } 2154 2155 private void pollingCheckWebBackForwardList(final String currUrl, final int currIndex, 2156 final int size) { 2157 new PollingCheck() { 2158 @Override 2159 protected boolean check() { 2160 WebBackForwardList list = mWebView.copyBackForwardList(); 2161 return checkWebBackForwardList(list, currUrl, currIndex, size); 2162 } 2163 }.run(); 2164 } 2165 2166 private boolean checkWebBackForwardList(WebBackForwardList list, String currUrl, 2167 int currIndex, int size) { 2168 return (list != null) 2169 && (list.getSize() == size) 2170 && (list.getCurrentIndex() == currIndex) 2171 && list.getItemAtIndex(currIndex).getUrl().equals(currUrl); 2172 } 2173 2174 private void assertGoBackOrForwardBySteps(boolean expected, int steps) { 2175 // skip if steps equals to 0 2176 if (steps == 0) 2177 return; 2178 2179 int start = steps > 0 ? 1 : steps; 2180 int end = steps > 0 ? steps : -1; 2181 2182 // check all the steps in the history 2183 for (int i = start; i <= end; i++) { 2184 assertEquals(expected, mWebView.canGoBackOrForward(i)); 2185 2186 // shortcut methods for one step 2187 if (i == 1) { 2188 assertEquals(expected, mWebView.canGoForward()); 2189 } else if (i == -1) { 2190 assertEquals(expected, mWebView.canGoBack()); 2191 } 2192 } 2193 } 2194 2195 private boolean isPictureFilledWithColor(Picture picture, int color) { 2196 if (picture.getWidth() == 0 || picture.getHeight() == 0) 2197 return false; 2198 2199 Bitmap bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(), 2200 Config.ARGB_8888); 2201 picture.draw(new Canvas(bitmap)); 2202 2203 for (int i = 0; i < bitmap.getWidth(); i ++) { 2204 for (int j = 0; j < bitmap.getHeight(); j ++) { 2205 if (color != bitmap.getPixel(i, j)) { 2206 return false; 2207 } 2208 } 2209 } 2210 return true; 2211 } 2212 2213 // Find b1 inside b2 2214 private boolean checkBitmapInsideAnother(Bitmap b1, Bitmap b2) { 2215 int w = b1.getWidth(); 2216 int h = b1.getHeight(); 2217 2218 for (int i = 0; i < (b2.getWidth()-w+1); i++) { 2219 for (int j = 0; j < (b2.getHeight()-h+1); j++) { 2220 if (checkBitmapInsideAnother(b1, b2, i, j)) 2221 return true; 2222 } 2223 } 2224 return false; 2225 } 2226 2227 private boolean comparePixel(int p1, int p2, int maxError) { 2228 int err; 2229 err = Math.abs(((p1&0xff000000)>>>24) - ((p2&0xff000000)>>>24)); 2230 if (err > maxError) 2231 return false; 2232 2233 err = Math.abs(((p1&0x00ff0000)>>>16) - ((p2&0x00ff0000)>>>16)); 2234 if (err > maxError) 2235 return false; 2236 2237 err = Math.abs(((p1&0x0000ff00)>>>8) - ((p2&0x0000ff00)>>>8)); 2238 if (err > maxError) 2239 return false; 2240 2241 err = Math.abs(((p1&0x000000ff)>>>0) - ((p2&0x000000ff)>>>0)); 2242 if (err > maxError) 2243 return false; 2244 2245 return true; 2246 } 2247 2248 private boolean checkBitmapInsideAnother(Bitmap b1, Bitmap b2, int x, int y) { 2249 for (int i = 0; i < b1.getWidth(); i++) 2250 for (int j = 0; j < b1.getHeight(); j++) { 2251 if (!comparePixel(b1.getPixel(i, j), b2.getPixel(x + i, y + j), 10)) { 2252 return false; 2253 } 2254 } 2255 return true; 2256 } 2257 2258 /** 2259 * Waits at least MIN_SCROLL_WAIT_MS for scrolling to start. Once started, 2260 * scrolling is checked every SCROLL_WAIT_INTERVAL_MS for changes. Once 2261 * changes have stopped, the function exits. If no scrolling has happened 2262 * then the function exits after MIN_SCROLL_WAIT milliseconds. 2263 * @param previousScrollY The Y scroll position prior to waiting. 2264 */ 2265 private void waitForScrollingComplete(int previousScrollY) 2266 throws InterruptedException { 2267 int scrollY = previousScrollY; 2268 // wait at least MIN_SCROLL_WAIT for something to happen. 2269 long noChangeMinWait = SystemClock.uptimeMillis() + MIN_SCROLL_WAIT_MS; 2270 boolean scrollChanging = false; 2271 boolean scrollChanged = false; 2272 boolean minWaitExpired = false; 2273 while (scrollChanging || (!scrollChanged && !minWaitExpired)) { 2274 Thread.sleep(SCROLL_WAIT_INTERVAL_MS); 2275 int oldScrollY = scrollY; 2276 scrollY = mOnUiThread.getScrollY(); 2277 scrollChanging = (scrollY != oldScrollY); 2278 scrollChanged = (scrollY != previousScrollY); 2279 minWaitExpired = (SystemClock.uptimeMillis() > noChangeMinWait); 2280 } 2281 } 2282 2283 // Note that this class is not thread-safe. 2284 final class SslErrorWebViewClient extends WaitForLoadedClient { 2285 private boolean mWasOnReceivedSslErrorCalled; 2286 private String mErrorUrl; 2287 2288 public SslErrorWebViewClient() { 2289 super(mOnUiThread); 2290 } 2291 @Override 2292 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 2293 mWasOnReceivedSslErrorCalled = true; 2294 mErrorUrl = error.getUrl(); 2295 handler.proceed(); 2296 } 2297 public void resetWasOnReceivedSslErrorCalled() { 2298 mWasOnReceivedSslErrorCalled = false; 2299 } 2300 public boolean wasOnReceivedSslErrorCalled() { 2301 return mWasOnReceivedSslErrorCalled; 2302 } 2303 public String errorUrl() { 2304 return mErrorUrl; 2305 } 2306 } 2307 2308 final class ScaleChangedWebViewClient extends WaitForLoadedClient { 2309 private boolean mOnScaleChangedCalled = false; 2310 public ScaleChangedWebViewClient() { 2311 super(mOnUiThread); 2312 } 2313 2314 @Override 2315 public void onScaleChanged(WebView view, float oldScale, float newScale) { 2316 super.onScaleChanged(view, oldScale, newScale); 2317 synchronized (this) { 2318 mOnScaleChangedCalled = true; 2319 } 2320 } 2321 2322 public void waitForScaleChanged() { 2323 new PollingCheck(TEST_TIMEOUT) { 2324 @Override 2325 protected boolean check() { 2326 return onScaleChangedCalled(); 2327 } 2328 }.run(); 2329 synchronized (this) { 2330 mOnScaleChangedCalled = false; 2331 } 2332 } 2333 2334 public synchronized boolean onScaleChangedCalled() { 2335 return mOnScaleChangedCalled; 2336 } 2337 } 2338 } 2339