Home | History | Annotate | Download | only in cts
      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