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