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