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