1 /* 2 * Copyright (C) 2011 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.cts.util.PollingCheck; 20 import android.cts.util.TestThread; 21 import android.graphics.Bitmap; 22 import android.graphics.Picture; 23 import android.graphics.Rect; 24 import android.os.Bundle; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.os.SystemClock; 28 import android.print.PrintDocumentAdapter; 29 import android.test.InstrumentationTestCase; 30 import android.util.DisplayMetrics; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.view.ViewParent; 34 import android.webkit.DownloadListener; 35 import android.webkit.CookieManager; 36 import android.webkit.ValueCallback; 37 import android.webkit.WebBackForwardList; 38 import android.webkit.WebChromeClient; 39 import android.webkit.WebSettings; 40 import android.webkit.WebView.HitTestResult; 41 import android.webkit.WebView.PictureListener; 42 import android.webkit.WebView; 43 import android.webkit.WebViewClient; 44 45 import junit.framework.Assert; 46 47 import java.io.File; 48 import java.util.concurrent.Callable; 49 import java.util.Map; 50 51 /** 52 * Many tests need to run WebView code in the UI thread. This class 53 * wraps a WebView so that calls are ensured to arrive on the UI thread. 54 * 55 * All methods may be run on either the UI thread or test thread. 56 */ 57 public class WebViewOnUiThread { 58 /** 59 * The maximum time, in milliseconds (10 seconds) to wait for a load 60 * to be triggered. 61 */ 62 private static final long LOAD_TIMEOUT = 10000; 63 64 /** 65 * Set to true after onPageFinished is called. 66 */ 67 private boolean mLoaded; 68 69 /** 70 * Set to true after onNewPicture is called. Reset when onPageStarted 71 * is called. 72 */ 73 private boolean mNewPicture; 74 75 /** 76 * The progress, in percentage, of the page load. Valid values are between 77 * 0 and 100. 78 */ 79 private int mProgress; 80 81 /** 82 * The test that this class is being used in. Used for runTestOnUiThread. 83 */ 84 private InstrumentationTestCase mTest; 85 86 /** 87 * The WebView that calls will be made on. 88 */ 89 private WebView mWebView; 90 91 /** 92 * Initializes the webView with a WebViewClient, WebChromeClient, 93 * and PictureListener to prepare for loadUrlAndWaitForCompletion. 94 * 95 * A new WebViewOnUiThread should be called during setUp so as to 96 * reinitialize between calls. 97 * 98 * @param test The test in which this is being run. 99 * @param webView The webView that the methods should call. 100 * @see loadUrlAndWaitForCompletion 101 */ 102 public WebViewOnUiThread(InstrumentationTestCase test, WebView webView) { 103 mTest = test; 104 mWebView = webView; 105 final WebViewClient webViewClient = new WaitForLoadedClient(this); 106 final WebChromeClient webChromeClient = new WaitForProgressClient(this); 107 runOnUiThread(new Runnable() { 108 @Override 109 public void run() { 110 mWebView.setWebViewClient(webViewClient); 111 mWebView.setWebChromeClient(webChromeClient); 112 mWebView.setPictureListener(new WaitForNewPicture()); 113 } 114 }); 115 } 116 117 /** 118 * Called after a test is complete and the WebView should be disengaged from 119 * the tests. 120 */ 121 public void cleanUp() { 122 clearHistory(); 123 clearCache(true); 124 setPictureListener(null); 125 setWebChromeClient(null); 126 setWebViewClient(null); 127 } 128 129 /** 130 * Called from WaitForNewPicture, this is used to indicate that 131 * the page has been drawn. 132 */ 133 synchronized public void onNewPicture() { 134 mNewPicture = true; 135 this.notifyAll(); 136 } 137 138 /** 139 * Called from WaitForLoadedClient, this is used to clear the picture 140 * draw state so that draws before the URL begins loading don't count. 141 */ 142 synchronized public void onPageStarted() { 143 mNewPicture = false; // Earlier paints won't count. 144 } 145 146 /** 147 * Called from WaitForLoadedClient, this is used to indicate that 148 * the page is loaded, but not drawn yet. 149 */ 150 synchronized public void onPageFinished() { 151 mLoaded = true; 152 this.notifyAll(); 153 } 154 155 /** 156 * Called from the WebChrome client, this sets the current progress 157 * for a page. 158 * @param progress The progress made so far between 0 and 100. 159 */ 160 synchronized public void onProgressChanged(int progress) { 161 mProgress = progress; 162 this.notifyAll(); 163 } 164 165 public void setWebViewClient(final WebViewClient webViewClient) { 166 runOnUiThread(new Runnable() { 167 @Override 168 public void run() { 169 mWebView.setWebViewClient(webViewClient); 170 } 171 }); 172 } 173 174 public void setWebChromeClient(final WebChromeClient webChromeClient) { 175 runOnUiThread(new Runnable() { 176 @Override 177 public void run() { 178 mWebView.setWebChromeClient(webChromeClient); 179 } 180 }); 181 } 182 183 public void setPictureListener(final PictureListener pictureListener) { 184 runOnUiThread(new Runnable() { 185 @Override 186 public void run() { 187 mWebView.setPictureListener(pictureListener); 188 } 189 }); 190 } 191 192 public void setNetworkAvailable(final boolean available) { 193 runOnUiThread(new Runnable() { 194 @Override 195 public void run() { 196 mWebView.setNetworkAvailable(available); 197 } 198 }); 199 } 200 201 public void setDownloadListener(final DownloadListener listener) { 202 runOnUiThread(new Runnable() { 203 @Override 204 public void run() { 205 mWebView.setDownloadListener(listener); 206 } 207 }); 208 } 209 210 public void setBackgroundColor(final int color) { 211 runOnUiThread(new Runnable() { 212 @Override 213 public void run() { 214 mWebView.setBackgroundColor(color); 215 } 216 }); 217 } 218 219 public void clearCache(final boolean includeDiskFiles) { 220 runOnUiThread(new Runnable() { 221 @Override 222 public void run() { 223 mWebView.clearCache(includeDiskFiles); 224 } 225 }); 226 } 227 228 public void clearHistory() { 229 runOnUiThread(new Runnable() { 230 @Override 231 public void run() { 232 mWebView.clearHistory(); 233 } 234 }); 235 } 236 237 public void requestFocus() { 238 runOnUiThread(new Runnable() { 239 @Override 240 public void run() { 241 mWebView.requestFocus(); 242 } 243 }); 244 } 245 246 public boolean canZoomIn() { 247 return getValue(new ValueGetter<Boolean>() { 248 @Override 249 public Boolean capture() { 250 return mWebView.canZoomIn(); 251 } 252 }); 253 } 254 255 public boolean canZoomOut() { 256 return getValue(new ValueGetter<Boolean>() { 257 @Override 258 public Boolean capture() { 259 return mWebView.canZoomOut(); 260 } 261 }); 262 } 263 264 public boolean zoomIn() { 265 return getValue(new ValueGetter<Boolean>() { 266 @Override 267 public Boolean capture() { 268 return mWebView.zoomIn(); 269 } 270 }); 271 } 272 273 public boolean zoomOut() { 274 return getValue(new ValueGetter<Boolean>() { 275 @Override 276 public Boolean capture() { 277 return mWebView.zoomOut(); 278 } 279 }); 280 } 281 282 public void zoomBy(final float zoomFactor) { 283 runOnUiThread(new Runnable() { 284 @Override 285 public void run() { 286 mWebView.zoomBy(zoomFactor); 287 } 288 }); 289 } 290 291 public void setFindListener(final WebView.FindListener listener) { 292 runOnUiThread(new Runnable() { 293 @Override 294 public void run() { 295 mWebView.setFindListener(listener); 296 } 297 }); 298 } 299 300 public void removeJavascriptInterface(final String interfaceName) { 301 runOnUiThread(new Runnable() { 302 @Override 303 public void run() { 304 mWebView.removeJavascriptInterface(interfaceName); 305 } 306 }); 307 } 308 309 public void addJavascriptInterface(final Object object, final String name) { 310 runOnUiThread(new Runnable() { 311 @Override 312 public void run() { 313 mWebView.addJavascriptInterface(object, name); 314 } 315 }); 316 } 317 318 public void flingScroll(final int vx, final int vy) { 319 runOnUiThread(new Runnable() { 320 @Override 321 public void run() { 322 mWebView.flingScroll(vx, vy); 323 } 324 }); 325 } 326 327 public void requestFocusNodeHref(final Message hrefMsg) { 328 runOnUiThread(new Runnable() { 329 @Override 330 public void run() { 331 mWebView.requestFocusNodeHref(hrefMsg); 332 } 333 }); 334 } 335 336 public void requestImageRef(final Message msg) { 337 runOnUiThread(new Runnable() { 338 @Override 339 public void run() { 340 mWebView.requestImageRef(msg); 341 } 342 }); 343 } 344 345 public void setInitialScale(final int scaleInPercent) { 346 runOnUiThread(new Runnable() { 347 @Override 348 public void run() { 349 mWebView.setInitialScale(scaleInPercent); 350 } 351 }); 352 } 353 354 public void clearSslPreferences() { 355 runOnUiThread(new Runnable() { 356 @Override 357 public void run() { 358 mWebView.clearSslPreferences(); 359 } 360 }); 361 } 362 363 public void clearClientCertPreferences(final Runnable onCleared) { 364 runOnUiThread(new Runnable() { 365 @Override 366 public void run() { 367 WebView.clearClientCertPreferences(onCleared); 368 } 369 }); 370 } 371 372 public void resumeTimers() { 373 runOnUiThread(new Runnable() { 374 @Override 375 public void run() { 376 mWebView.resumeTimers(); 377 } 378 }); 379 } 380 381 public void findNext(final boolean forward) { 382 runOnUiThread(new Runnable() { 383 @Override 384 public void run() { 385 mWebView.findNext(forward); 386 } 387 }); 388 } 389 390 public void clearMatches() { 391 runOnUiThread(new Runnable() { 392 @Override 393 public void run() { 394 mWebView.clearMatches(); 395 } 396 }); 397 } 398 399 /** 400 * Calls loadUrl on the WebView and then waits onPageFinished, 401 * onNewPicture and onProgressChange to reach 100. 402 * Test fails if the load timeout elapses. 403 * @param url The URL to load. 404 */ 405 public void loadUrlAndWaitForCompletion(final String url) { 406 callAndWait(new Runnable() { 407 @Override 408 public void run() { 409 mWebView.loadUrl(url); 410 } 411 }); 412 } 413 414 /** 415 * Calls loadUrl on the WebView and then waits onPageFinished, 416 * onNewPicture and onProgressChange to reach 100. 417 * Test fails if the load timeout elapses. 418 * @param url The URL to load. 419 * @param extraHeaders The additional headers to be used in the HTTP request. 420 */ 421 public void loadUrlAndWaitForCompletion(final String url, 422 final Map<String, String> extraHeaders) { 423 callAndWait(new Runnable() { 424 @Override 425 public void run() { 426 mWebView.loadUrl(url, extraHeaders); 427 } 428 }); 429 } 430 431 public void loadUrl(final String url) { 432 runOnUiThread(new Runnable() { 433 @Override 434 public void run() { 435 mWebView.loadUrl(url); 436 } 437 }); 438 } 439 440 public void stopLoading() { 441 runOnUiThread(new Runnable() { 442 @Override 443 public void run() { 444 mWebView.stopLoading(); 445 } 446 }); 447 } 448 449 public void postUrlAndWaitForCompletion(final String url, final byte[] postData) { 450 callAndWait(new Runnable() { 451 @Override 452 public void run() { 453 mWebView.postUrl(url, postData); 454 } 455 }); 456 } 457 458 public void loadDataAndWaitForCompletion(final String data, 459 final String mimeType, final String encoding) { 460 callAndWait(new Runnable() { 461 @Override 462 public void run() { 463 mWebView.loadData(data, mimeType, encoding); 464 } 465 }); 466 } 467 468 public void loadDataWithBaseURLAndWaitForCompletion(final String baseUrl, 469 final String data, final String mimeType, final String encoding, 470 final String historyUrl) { 471 callAndWait(new Runnable() { 472 @Override 473 public void run() { 474 mWebView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, 475 historyUrl); 476 } 477 }); 478 } 479 480 /** 481 * Reloads a page and waits for it to complete reloading. Use reload 482 * if it is a form resubmission and the onFormResubmission responds 483 * by telling WebView not to resubmit it. 484 */ 485 public void reloadAndWaitForCompletion() { 486 callAndWait(new Runnable() { 487 @Override 488 public void run() { 489 mWebView.reload(); 490 } 491 }); 492 } 493 494 /** 495 * Reload the previous URL. Use reloadAndWaitForCompletion unless 496 * it is a form resubmission and the onFormResubmission responds 497 * by telling WebView not to resubmit it. 498 */ 499 public void reload() { 500 runOnUiThread(new Runnable() { 501 @Override 502 public void run() { 503 mWebView.reload(); 504 } 505 }); 506 } 507 508 /** 509 * Use this only when JavaScript causes a page load to wait for the 510 * page load to complete. Otherwise use loadUrlAndWaitForCompletion or 511 * similar functions. 512 */ 513 public void waitForLoadCompletion() { 514 waitForCriteria(LOAD_TIMEOUT, 515 new Callable<Boolean>() { 516 @Override 517 public Boolean call() { 518 return isLoaded(); 519 } 520 }); 521 clearLoad(); 522 } 523 524 private void waitForCriteria(long timeout, Callable<Boolean> doneCriteria) { 525 if (isUiThread()) { 526 waitOnUiThread(timeout, doneCriteria); 527 } else { 528 waitOnTestThread(timeout, doneCriteria); 529 } 530 } 531 532 public String getTitle() { 533 return getValue(new ValueGetter<String>() { 534 @Override 535 public String capture() { 536 return mWebView.getTitle(); 537 } 538 }); 539 } 540 541 public WebSettings getSettings() { 542 return getValue(new ValueGetter<WebSettings>() { 543 @Override 544 public WebSettings capture() { 545 return mWebView.getSettings(); 546 } 547 }); 548 } 549 550 public WebBackForwardList copyBackForwardList() { 551 return getValue(new ValueGetter<WebBackForwardList>() { 552 @Override 553 public WebBackForwardList capture() { 554 return mWebView.copyBackForwardList(); 555 } 556 }); 557 } 558 559 public Bitmap getFavicon() { 560 return getValue(new ValueGetter<Bitmap>() { 561 @Override 562 public Bitmap capture() { 563 return mWebView.getFavicon(); 564 } 565 }); 566 } 567 568 public String getUrl() { 569 return getValue(new ValueGetter<String>() { 570 @Override 571 public String capture() { 572 return mWebView.getUrl(); 573 } 574 }); 575 } 576 577 public int getProgress() { 578 return getValue(new ValueGetter<Integer>() { 579 @Override 580 public Integer capture() { 581 return mWebView.getProgress(); 582 } 583 }); 584 } 585 586 public int getHeight() { 587 return getValue(new ValueGetter<Integer>() { 588 @Override 589 public Integer capture() { 590 return mWebView.getHeight(); 591 } 592 }); 593 } 594 595 public int getContentHeight() { 596 return getValue(new ValueGetter<Integer>() { 597 @Override 598 public Integer capture() { 599 return mWebView.getContentHeight(); 600 } 601 }); 602 } 603 604 public boolean savePicture(final Bundle b, final File dest) { 605 return getValue(new ValueGetter<Boolean>() { 606 @Override 607 public Boolean capture() { 608 return mWebView.savePicture(b, dest); 609 } 610 }); 611 } 612 613 public boolean pageUp(final boolean top) { 614 return getValue(new ValueGetter<Boolean>() { 615 @Override 616 public Boolean capture() { 617 return mWebView.pageUp(top); 618 } 619 }); 620 } 621 622 public boolean pageDown(final boolean bottom) { 623 return getValue(new ValueGetter<Boolean>() { 624 @Override 625 public Boolean capture() { 626 return mWebView.pageDown(bottom); 627 } 628 }); 629 } 630 631 public int[] getLocationOnScreen() { 632 final int[] location = new int[2]; 633 return getValue(new ValueGetter<int[]>() { 634 @Override 635 public int[] capture() { 636 mWebView.getLocationOnScreen(location); 637 return location; 638 } 639 }); 640 } 641 642 public float getScale() { 643 return getValue(new ValueGetter<Float>() { 644 @Override 645 public Float capture() { 646 return mWebView.getScale(); 647 } 648 }); 649 } 650 651 public boolean requestFocus(final int direction, 652 final Rect previouslyFocusedRect) { 653 return getValue(new ValueGetter<Boolean>() { 654 @Override 655 public Boolean capture() { 656 return mWebView.requestFocus(direction, previouslyFocusedRect); 657 } 658 }); 659 } 660 661 public HitTestResult getHitTestResult() { 662 return getValue(new ValueGetter<HitTestResult>() { 663 @Override 664 public HitTestResult capture() { 665 return mWebView.getHitTestResult(); 666 } 667 }); 668 } 669 670 public int getScrollX() { 671 return getValue(new ValueGetter<Integer>() { 672 @Override 673 public Integer capture() { 674 return mWebView.getScrollX(); 675 } 676 }); 677 } 678 679 public int getScrollY() { 680 return getValue(new ValueGetter<Integer>() { 681 @Override 682 public Integer capture() { 683 return mWebView.getScrollY(); 684 } 685 }); 686 } 687 688 public final DisplayMetrics getDisplayMetrics() { 689 return getValue(new ValueGetter<DisplayMetrics>() { 690 @Override 691 public DisplayMetrics capture() { 692 return mWebView.getContext().getResources().getDisplayMetrics(); 693 } 694 }); 695 } 696 697 public boolean requestChildRectangleOnScreen(final View child, 698 final Rect rect, 699 final boolean immediate) { 700 return getValue(new ValueGetter<Boolean>() { 701 @Override 702 public Boolean capture() { 703 return mWebView.requestChildRectangleOnScreen(child, rect, 704 immediate); 705 } 706 }); 707 } 708 709 public int findAll(final String find) { 710 return getValue(new ValueGetter<Integer>() { 711 @Override 712 public Integer capture() { 713 return mWebView.findAll(find); 714 } 715 }); 716 } 717 718 public Picture capturePicture() { 719 return getValue(new ValueGetter<Picture>() { 720 @Override 721 public Picture capture() { 722 return mWebView.capturePicture(); 723 } 724 }); 725 } 726 727 public void evaluateJavascript(final String script, final ValueCallback<String> result) { 728 runOnUiThread(new Runnable() { 729 @Override 730 public void run() { 731 mWebView.evaluateJavascript(script, result); 732 } 733 }); 734 } 735 736 public void saveWebArchive(final String basename, final boolean autoname, 737 final ValueCallback<String> callback) { 738 runOnUiThread(new Runnable() { 739 @Override 740 public void run() { 741 mWebView.saveWebArchive(basename, autoname, callback); 742 } 743 }); 744 } 745 746 public WebView createWebView() { 747 return getValue(new ValueGetter<WebView>() { 748 @Override 749 public WebView capture() { 750 return new WebView(mWebView.getContext()); 751 } 752 }); 753 } 754 755 public PrintDocumentAdapter createPrintDocumentAdapter() { 756 return getValue(new ValueGetter<PrintDocumentAdapter>() { 757 @Override 758 public PrintDocumentAdapter capture() { 759 return mWebView.createPrintDocumentAdapter(); 760 } 761 }); 762 } 763 764 public void setLayoutHeightToMatchParent() { 765 runOnUiThread(new Runnable() { 766 @Override 767 public void run() { 768 ViewParent parent = mWebView.getParent(); 769 if (parent instanceof ViewGroup) { 770 ((ViewGroup) parent).getLayoutParams().height = 771 ViewGroup.LayoutParams.MATCH_PARENT; 772 } 773 mWebView.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT; 774 mWebView.requestLayout(); 775 } 776 }); 777 } 778 779 public void setAcceptThirdPartyCookies(final boolean accept) { 780 runOnUiThread(new Runnable() { 781 @Override 782 public void run() { 783 CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, accept); 784 } 785 }); 786 } 787 788 public boolean acceptThirdPartyCookies() { 789 return getValue(new ValueGetter<Boolean>() { 790 @Override 791 public Boolean capture() { 792 return CookieManager.getInstance().acceptThirdPartyCookies(mWebView); 793 } 794 }); 795 } 796 797 /** 798 * Helper for running code on the UI thread where an exception is 799 * a test failure. If this is already the UI thread then it runs 800 * the code immediately. 801 * 802 * @see runTestOnUiThread 803 * @param r The code to run in the UI thread 804 */ 805 public void runOnUiThread(Runnable r) { 806 try { 807 if (isUiThread()) { 808 r.run(); 809 } else { 810 mTest.runTestOnUiThread(r); 811 } 812 } catch (Throwable t) { 813 Assert.fail("Unexpected error while running on UI thread: " 814 + t.getMessage()); 815 } 816 } 817 818 /** 819 * Accessor for underlying WebView. 820 * @return The WebView being wrapped by this class. 821 */ 822 public WebView getWebView() { 823 return mWebView; 824 } 825 826 private<T> T getValue(ValueGetter<T> getter) { 827 runOnUiThread(getter); 828 return getter.getValue(); 829 } 830 831 private abstract class ValueGetter<T> implements Runnable { 832 private T mValue; 833 834 @Override 835 public void run() { 836 mValue = capture(); 837 } 838 839 protected abstract T capture(); 840 841 public T getValue() { 842 return mValue; 843 } 844 } 845 846 /** 847 * Returns true if the current thread is the UI thread based on the 848 * Looper. 849 */ 850 private static boolean isUiThread() { 851 return (Looper.myLooper() == Looper.getMainLooper()); 852 } 853 854 /** 855 * @return Whether or not the load has finished. 856 */ 857 private synchronized boolean isLoaded() { 858 return mLoaded && mNewPicture && mProgress == 100; 859 } 860 861 /** 862 * Makes a WebView call, waits for completion and then resets the 863 * load state in preparation for the next load call. 864 * @param call The call to make on the UI thread prior to waiting. 865 */ 866 private void callAndWait(Runnable call) { 867 Assert.assertTrue("WebViewOnUiThread.load*AndWaitForCompletion calls " 868 + "may not be mixed with load* calls directly on WebView " 869 + "without calling waitForLoadCompletion after the load", 870 !isLoaded()); 871 clearLoad(); // clear any extraneous signals from a previous load. 872 runOnUiThread(call); 873 waitForLoadCompletion(); 874 } 875 876 /** 877 * Called whenever a load has been completed so that a subsequent call to 878 * waitForLoadCompletion doesn't return immediately. 879 */ 880 synchronized private void clearLoad() { 881 mLoaded = false; 882 mNewPicture = false; 883 mProgress = 0; 884 } 885 886 /** 887 * Uses a polling mechanism, while pumping messages to check when the 888 * criteria is met. 889 */ 890 private void waitOnUiThread(long timeout, final Callable<Boolean> doneCriteria) { 891 new PollingCheck(timeout) { 892 @Override 893 protected boolean check() { 894 pumpMessages(); 895 try { 896 return doneCriteria.call(); 897 } catch (Exception e) { 898 Assert.fail("Unexpected error while checking the criteria: " 899 + e.getMessage()); 900 return true; 901 } 902 } 903 }.run(); 904 } 905 906 /** 907 * Uses a wait/notify to check when the criteria is met. 908 */ 909 private synchronized void waitOnTestThread(long timeout, Callable<Boolean> doneCriteria) { 910 try { 911 long waitEnd = SystemClock.uptimeMillis() + timeout; 912 long timeRemaining = timeout; 913 while (!doneCriteria.call() && timeRemaining > 0) { 914 this.wait(timeRemaining); 915 timeRemaining = waitEnd - SystemClock.uptimeMillis(); 916 } 917 Assert.assertTrue("Action failed to complete before timeout", doneCriteria.call()); 918 } catch (InterruptedException e) { 919 // We'll just drop out of the loop and fail 920 } catch (Exception e) { 921 Assert.fail("Unexpected error while checking the criteria: " 922 + e.getMessage()); 923 } 924 } 925 926 /** 927 * Pumps all currently-queued messages in the UI thread and then exits. 928 * This is useful to force processing while running tests in the UI thread. 929 */ 930 private void pumpMessages() { 931 class ExitLoopException extends RuntimeException { 932 } 933 934 // Force loop to exit when processing this. Loop.quit() doesn't 935 // work because this is the main Loop. 936 mWebView.getHandler().post(new Runnable() { 937 @Override 938 public void run() { 939 throw new ExitLoopException(); // exit loop! 940 } 941 }); 942 try { 943 // Pump messages until our message gets through. 944 Looper.loop(); 945 } catch (ExitLoopException e) { 946 } 947 } 948 949 /** 950 * A WebChromeClient used to capture the onProgressChanged for use 951 * in waitFor functions. If a test must override the WebChromeClient, 952 * it can derive from this class or call onProgressChanged 953 * directly. 954 */ 955 public static class WaitForProgressClient extends WebChromeClient { 956 private WebViewOnUiThread mOnUiThread; 957 958 public WaitForProgressClient(WebViewOnUiThread onUiThread) { 959 mOnUiThread = onUiThread; 960 } 961 962 @Override 963 public void onProgressChanged(WebView view, int newProgress) { 964 super.onProgressChanged(view, newProgress); 965 mOnUiThread.onProgressChanged(newProgress); 966 } 967 } 968 969 /** 970 * A WebViewClient that captures the onPageFinished for use in 971 * waitFor functions. Using initializeWebView sets the WaitForLoadedClient 972 * into the WebView. If a test needs to set a specific WebViewClient and 973 * needs the waitForCompletion capability then it should derive from 974 * WaitForLoadedClient or call WebViewOnUiThread.onPageFinished. 975 */ 976 public static class WaitForLoadedClient extends WebViewClient { 977 private WebViewOnUiThread mOnUiThread; 978 979 public WaitForLoadedClient(WebViewOnUiThread onUiThread) { 980 mOnUiThread = onUiThread; 981 } 982 983 @Override 984 public void onPageFinished(WebView view, String url) { 985 super.onPageFinished(view, url); 986 mOnUiThread.onPageFinished(); 987 } 988 989 @Override 990 public void onPageStarted(WebView view, String url, Bitmap favicon) { 991 super.onPageStarted(view, url, favicon); 992 mOnUiThread.onPageStarted(); 993 } 994 } 995 996 /** 997 * A PictureListener that captures the onNewPicture for use in 998 * waitForLoadCompletion. Using initializeWebView sets the PictureListener 999 * into the WebView. If a test needs to set a specific PictureListener and 1000 * needs the waitForCompletion capability then it should call 1001 * WebViewOnUiThread.onNewPicture. 1002 */ 1003 private class WaitForNewPicture implements PictureListener { 1004 @Override 1005 public void onNewPicture(WebView view, Picture picture) { 1006 WebViewOnUiThread.this.onNewPicture(); 1007 } 1008 } 1009 } 1010