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.graphics.Bitmap;
20 import android.graphics.Canvas;
21 import android.graphics.Picture;
22 import android.graphics.Rect;
23 import android.net.Uri;
24 import android.net.http.SslCertificate;
25 import android.os.Message;
26 import android.print.PrintDocumentAdapter;
27 import android.util.DisplayMetrics;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.ViewParent;
31 import android.webkit.CookieManager;
32 import android.webkit.DownloadListener;
33 import android.webkit.ValueCallback;
34 import android.webkit.WebBackForwardList;
35 import android.webkit.WebChromeClient;
36 import android.webkit.WebMessage;
37 import android.webkit.WebMessagePort;
38 import android.webkit.WebSettings;
39 import android.webkit.WebView;
40 import android.webkit.WebView.HitTestResult;
41 import android.webkit.WebView.PictureListener;
42 import android.webkit.WebView.VisualStateCallback;
43 import android.webkit.WebViewClient;
44 import android.webkit.WebViewRenderProcessClient;
45 
46 import com.android.compatibility.common.util.PollingCheck;
47 
48 import com.google.common.util.concurrent.SettableFuture;
49 
50 import java.util.concurrent.Executor;
51 
52 /**
53  * Many tests need to run WebView code in the UI thread. This class
54  * wraps a WebView so that calls are ensured to arrive on the UI thread.
55  *
56  * All methods may be run on either the UI thread or test thread.
57  *
58  * This should remain functionally equivalent to androidx.webkit.WebViewOnUiThread.
59  * Modifications to this class should be reflected in that class as necessary. See
60  * http://go/modifying-webview-cts.
61  */
62 public class WebViewOnUiThread extends WebViewSyncLoader {
63     /**
64      * The WebView that calls will be made on.
65      */
66     private WebView mWebView;
67 
68     /**
69      * Wraps a WebView to ensure that methods are run on the UI thread.
70      *
71      * A new WebViewOnUiThread should be called during setUp so as to
72      * reinitialize between calls.
73      *
74      * @param webView The webView that the methods should call.
75      */
WebViewOnUiThread(WebView webView)76     public WebViewOnUiThread(WebView webView) {
77         super(webView);
78         mWebView = webView;
79     }
80 
cleanUp()81     public void cleanUp() {
82         super.destroy();
83     }
84 
setWebViewClient(final WebViewClient webViewClient)85     public void setWebViewClient(final WebViewClient webViewClient) {
86         WebkitUtils.onMainThreadSync(() -> {
87             mWebView.setWebViewClient(webViewClient);
88         });
89     }
90 
setWebChromeClient(final WebChromeClient webChromeClient)91     public void setWebChromeClient(final WebChromeClient webChromeClient) {
92         WebkitUtils.onMainThreadSync(() -> {
93             mWebView.setWebChromeClient(webChromeClient);
94         });
95     }
96 
97     /**
98      * Set the webview renderer client for {@code mWebView}, on the UI thread.
99      */
setWebViewRenderProcessClient( final WebViewRenderProcessClient webViewRenderProcessClient)100     public void setWebViewRenderProcessClient(
101             final WebViewRenderProcessClient webViewRenderProcessClient) {
102         setWebViewRenderProcessClient(mWebView, webViewRenderProcessClient);
103     }
104 
105     /**
106      * Set the webview renderer client for {@code webView}, on the UI thread.
107      */
setWebViewRenderProcessClient( final WebView webView, final WebViewRenderProcessClient webViewRenderProcessClient)108     public static void setWebViewRenderProcessClient(
109             final WebView webView,
110             final WebViewRenderProcessClient webViewRenderProcessClient) {
111         WebkitUtils.onMainThreadSync(() ->
112                 webView.setWebViewRenderProcessClient(webViewRenderProcessClient)
113         );
114     }
115 
116     /**
117      * Set the webview renderer client for {@code mWebView}, on the UI thread, with callbacks
118      * executed by {@code executor}
119      */
setWebViewRenderProcessClient( final Executor executor, final WebViewRenderProcessClient webViewRenderProcessClient)120     public void setWebViewRenderProcessClient(
121             final Executor executor, final WebViewRenderProcessClient webViewRenderProcessClient) {
122         setWebViewRenderProcessClient(mWebView, executor, webViewRenderProcessClient);
123     }
124 
125     /**
126      * Set the webview renderer client for {@code webView}, on the UI thread, with callbacks
127      * executed by {@code executor}
128      */
setWebViewRenderProcessClient( final WebView webView, final Executor executor, final WebViewRenderProcessClient webViewRenderProcessClient)129     public static void setWebViewRenderProcessClient(
130             final WebView webView,
131             final Executor executor,
132             final WebViewRenderProcessClient webViewRenderProcessClient) {
133         WebkitUtils.onMainThreadSync(() ->
134                 webView.setWebViewRenderProcessClient(executor, webViewRenderProcessClient)
135         );
136     }
137 
138     /**
139      * Get the webview renderer client currently set on {@code mWebView}, on the UI thread.
140      */
getWebViewRenderProcessClient()141     public WebViewRenderProcessClient getWebViewRenderProcessClient() {
142         return getWebViewRenderProcessClient(mWebView);
143     }
144 
145     /**
146      * Get the webview renderer client currently set on {@code webView}, on the UI thread.
147      */
getWebViewRenderProcessClient( final WebView webView)148     public static WebViewRenderProcessClient getWebViewRenderProcessClient(
149             final WebView webView) {
150         return WebkitUtils.onMainThreadSync(() -> {
151             return webView.getWebViewRenderProcessClient();
152         });
153     }
154 
setPictureListener(final PictureListener pictureListener)155     public void setPictureListener(final PictureListener pictureListener) {
156         WebkitUtils.onMainThreadSync(() -> {
157             mWebView.setPictureListener(pictureListener);
158         });
159     }
160 
setNetworkAvailable(final boolean available)161     public void setNetworkAvailable(final boolean available) {
162         WebkitUtils.onMainThreadSync(() -> {
163             mWebView.setNetworkAvailable(available);
164         });
165     }
166 
setDownloadListener(final DownloadListener listener)167     public void setDownloadListener(final DownloadListener listener) {
168         WebkitUtils.onMainThreadSync(() -> {
169             mWebView.setDownloadListener(listener);
170         });
171     }
172 
setBackgroundColor(final int color)173     public void setBackgroundColor(final int color) {
174         WebkitUtils.onMainThreadSync(() -> {
175             mWebView.setBackgroundColor(color);
176         });
177     }
178 
clearCache(final boolean includeDiskFiles)179     public void clearCache(final boolean includeDiskFiles) {
180         WebkitUtils.onMainThreadSync(() -> {
181             mWebView.clearCache(includeDiskFiles);
182         });
183     }
184 
clearHistory()185     public void clearHistory() {
186         WebkitUtils.onMainThreadSync(() -> {
187             mWebView.clearHistory();
188         });
189     }
190 
requestFocus()191     public void requestFocus() {
192         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
193             @Override
194             protected boolean check() {
195                 requestFocusOnUiThread();
196                 return hasFocus();
197             }
198         }.run();
199     }
200 
requestFocusOnUiThread()201     private void requestFocusOnUiThread() {
202         WebkitUtils.onMainThreadSync(() -> {
203             mWebView.requestFocus();
204         });
205     }
206 
hasFocus()207     private boolean hasFocus() {
208         return WebkitUtils.onMainThreadSync(() -> {
209             return mWebView.hasFocus();
210         });
211     }
212 
213     public boolean canZoomIn() {
214         return WebkitUtils.onMainThreadSync(() -> {
215             return mWebView.canZoomIn();
216         });
217     }
218 
219     public boolean canZoomOut() {
220         return WebkitUtils.onMainThreadSync(() -> {
221             return mWebView.canZoomOut();
222         });
223     }
224 
225     public boolean zoomIn() {
226         return WebkitUtils.onMainThreadSync(() -> {
227             return mWebView.zoomIn();
228         });
229     }
230 
231     public boolean zoomOut() {
232         return WebkitUtils.onMainThreadSync(() -> {
233             return mWebView.zoomOut();
234         });
235     }
236 
237     public void zoomBy(final float zoomFactor) {
238         WebkitUtils.onMainThreadSync(() -> {
239             mWebView.zoomBy(zoomFactor);
240         });
241     }
242 
243     public void setFindListener(final WebView.FindListener listener) {
244         WebkitUtils.onMainThreadSync(() -> {
245             mWebView.setFindListener(listener);
246         });
247     }
248 
249     public void removeJavascriptInterface(final String interfaceName) {
250         WebkitUtils.onMainThreadSync(() -> {
251             mWebView.removeJavascriptInterface(interfaceName);
252         });
253     }
254 
255     public WebMessagePort[] createWebMessageChannel() {
256         return WebkitUtils.onMainThreadSync(() -> {
257             return mWebView.createWebMessageChannel();
258         });
259     }
260 
261     public void postWebMessage(final WebMessage message, final Uri targetOrigin) {
262         WebkitUtils.onMainThreadSync(() -> {
263             mWebView.postWebMessage(message, targetOrigin);
264         });
265     }
266 
267     public void addJavascriptInterface(final Object object, final String name) {
268         WebkitUtils.onMainThreadSync(() -> {
269             mWebView.addJavascriptInterface(object, name);
270         });
271     }
272 
273     public void flingScroll(final int vx, final int vy) {
274         WebkitUtils.onMainThreadSync(() -> {
275             mWebView.flingScroll(vx, vy);
276         });
277     }
278 
279     public void requestFocusNodeHref(final Message hrefMsg) {
280         WebkitUtils.onMainThreadSync(() -> {
281             mWebView.requestFocusNodeHref(hrefMsg);
282         });
283     }
284 
285     public void requestImageRef(final Message msg) {
286         WebkitUtils.onMainThreadSync(() -> {
287             mWebView.requestImageRef(msg);
288         });
289     }
290 
291     public void setInitialScale(final int scaleInPercent) {
292         WebkitUtils.onMainThreadSync(() -> {
293                 mWebView.setInitialScale(scaleInPercent);
294         });
295     }
296 
297     public void clearSslPreferences() {
298         WebkitUtils.onMainThreadSync(() -> {
299             mWebView.clearSslPreferences();
300         });
301     }
302 
303     public void clearClientCertPreferences(final Runnable onCleared) {
304         WebkitUtils.onMainThreadSync(() -> {
305             WebView.clearClientCertPreferences(onCleared);
306         });
307     }
308 
309     public void resumeTimers() {
310         WebkitUtils.onMainThreadSync(() -> {
311             mWebView.resumeTimers();
312         });
313     }
314 
315     public void findNext(final boolean forward) {
316         WebkitUtils.onMainThreadSync(() -> {
317             mWebView.findNext(forward);
318         });
319     }
320 
321     public void clearMatches() {
322         WebkitUtils.onMainThreadSync(() -> {
323             mWebView.clearMatches();
324         });
325     }
326 
327     public void loadUrl(final String url) {
328         WebkitUtils.onMainThreadSync(() -> {
329             mWebView.loadUrl(url);
330         });
331     }
332 
333     public void stopLoading() {
334         WebkitUtils.onMainThreadSync(() -> {
335             mWebView.stopLoading();
336         });
337     }
338 
339     /**
340      * Reload the previous URL.
341      */
342     public void reload() {
343         WebkitUtils.onMainThreadSync(() -> {
344             mWebView.reload();
345         });
346     }
347 
348     public String getTitle() {
349         return WebkitUtils.onMainThreadSync(() -> {
350             return mWebView.getTitle();
351         });
352     }
353 
354     public WebSettings getSettings() {
355         return WebkitUtils.onMainThreadSync(() -> {
356             return mWebView.getSettings();
357         });
358     }
359 
360     public WebBackForwardList copyBackForwardList() {
361         return WebkitUtils.onMainThreadSync(() -> {
362             return mWebView.copyBackForwardList();
363         });
364     }
365 
366     public Bitmap getFavicon() {
367         return WebkitUtils.onMainThreadSync(() -> {
368             return mWebView.getFavicon();
369         });
370     }
371 
372     public String getUrl() {
373         return WebkitUtils.onMainThreadSync(() -> {
374             return mWebView.getUrl();
375         });
376     }
377 
378     public int getProgress() {
379         return WebkitUtils.onMainThreadSync(() -> {
380             return mWebView.getProgress();
381         });
382     }
383 
384     public int getHeight() {
385         return WebkitUtils.onMainThreadSync(() -> {
386             return mWebView.getHeight();
387         });
388     }
389 
390     public int getContentHeight() {
391         return WebkitUtils.onMainThreadSync(() -> {
392             return mWebView.getContentHeight();
393         });
394     }
395 
396     public boolean pageUp(final boolean top) {
397         return WebkitUtils.onMainThreadSync(() -> {
398             return mWebView.pageUp(top);
399         });
400     }
401 
402     public boolean pageDown(final boolean bottom) {
403         return WebkitUtils.onMainThreadSync(() -> {
404             return mWebView.pageDown(bottom);
405         });
406     }
407 
408     /**
409      * Post a visual state listener callback for mWebView on the UI thread.
410      */
411     public void postVisualStateCallback(final long requestId, final VisualStateCallback callback) {
412         WebkitUtils.onMainThreadSync(() -> {
413             mWebView.postVisualStateCallback(requestId, callback);
414         });
415     }
416 
417     public int[] getLocationOnScreen() {
418         final int[] location = new int[2];
419         return WebkitUtils.onMainThreadSync(() -> {
420             mWebView.getLocationOnScreen(location);
421             return location;
422         });
423     }
424 
425     public float getScale() {
426         return WebkitUtils.onMainThreadSync(() -> {
427             return mWebView.getScale();
428         });
429     }
430 
431     public boolean requestFocus(final int direction,
432             final Rect previouslyFocusedRect) {
433         return WebkitUtils.onMainThreadSync(() -> {
434             return mWebView.requestFocus(direction, previouslyFocusedRect);
435         });
436     }
437 
438     public HitTestResult getHitTestResult() {
439         return WebkitUtils.onMainThreadSync(() -> {
440             return mWebView.getHitTestResult();
441         });
442     }
443 
444     public int getScrollX() {
445         return WebkitUtils.onMainThreadSync(() -> {
446             return mWebView.getScrollX();
447         });
448     }
449 
450     public int getScrollY() {
451         return WebkitUtils.onMainThreadSync(() -> {
452             return mWebView.getScrollY();
453         });
454     }
455 
456     public final DisplayMetrics getDisplayMetrics() {
457         return WebkitUtils.onMainThreadSync(() -> {
458             return mWebView.getContext().getResources().getDisplayMetrics();
459         });
460     }
461 
462     public boolean requestChildRectangleOnScreen(final View child,
463             final Rect rect,
464             final boolean immediate) {
465         return WebkitUtils.onMainThreadSync(() -> {
466             return mWebView.requestChildRectangleOnScreen(child, rect,
467                     immediate);
468         });
469     }
470 
471     public int findAll(final String find) {
472         return WebkitUtils.onMainThreadSync(() -> {
473             return mWebView.findAll(find);
474         });
475     }
476 
477     public Picture capturePicture() {
478         return WebkitUtils.onMainThreadSync(() -> {
479             return mWebView.capturePicture();
480         });
481     }
482 
483     /**
484      * Execute javascript synchronously, returning the result.
485      */
486     public String evaluateJavascriptSync(final String script) {
487         final SettableFuture<String> future = SettableFuture.create();
488         evaluateJavascript(script, result -> future.set(result));
489         return WebkitUtils.waitForFuture(future);
490     }
491 
492     public void evaluateJavascript(final String script, final ValueCallback<String> result) {
493         WebkitUtils.onMainThreadSync(() -> {
494             mWebView.evaluateJavascript(script, result);
495         });
496     }
497 
498     public void saveWebArchive(final String basename, final boolean autoname,
499                                final ValueCallback<String> callback) {
500         WebkitUtils.onMainThreadSync(() -> {
501             mWebView.saveWebArchive(basename, autoname, callback);
502         });
503     }
504 
505     public SslCertificate getCertificate() {
506         return WebkitUtils.onMainThreadSync(() -> {
507             return mWebView.getCertificate();
508         });
509     }
510 
511     public WebView createWebView() {
512         return WebkitUtils.onMainThreadSync(() -> {
513             return new WebView(mWebView.getContext());
514         });
515     }
516 
517     public PrintDocumentAdapter createPrintDocumentAdapter() {
518         return WebkitUtils.onMainThreadSync(() -> {
519             return mWebView.createPrintDocumentAdapter();
520         });
521     }
522 
523     public void setLayoutHeightToMatchParent() {
524         WebkitUtils.onMainThreadSync(() -> {
525             ViewParent parent = mWebView.getParent();
526             if (parent instanceof ViewGroup) {
527                 ((ViewGroup) parent).getLayoutParams().height =
528                     ViewGroup.LayoutParams.MATCH_PARENT;
529             }
530             mWebView.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
531             mWebView.requestLayout();
532         });
533     }
534 
535     public void setLayoutToMatchParent() {
536         WebkitUtils.onMainThreadSync(() -> {
537             setMatchParent((View) mWebView.getParent());
538             setMatchParent(mWebView);
539             mWebView.requestLayout();
540         });
541     }
542 
543     public void setAcceptThirdPartyCookies(final boolean accept) {
544         WebkitUtils.onMainThreadSync(() -> {
545             CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, accept);
546         });
547     }
548 
549     public boolean acceptThirdPartyCookies() {
550         return WebkitUtils.onMainThreadSync(() -> {
551             return CookieManager.getInstance().acceptThirdPartyCookies(mWebView);
552         });
553     }
554 
555     /**
556      * Accessor for underlying WebView.
557      * @return The WebView being wrapped by this class.
558      */
559     public WebView getWebView() {
560         return mWebView;
561     }
562 
563     /**
564      * Wait for the current state of the DOM to be ready to render on the next draw.
565      */
566     public void waitForDOMReadyToRender() {
567         final SettableFuture<Void> future = SettableFuture.create();
568         postVisualStateCallback(0, new VisualStateCallback() {
569             @Override
570             public void onComplete(long requestId) {
571                 future.set(null);
572             }
573         });
574         WebkitUtils.waitForFuture(future);
575     }
576 
577     /**
578      * Capture a bitmap representation of the current WebView state.
579      *
580      * This synchronises so that the bitmap contents reflects the current DOM state, rather than
581      * potentially capturing a previously generated frame.
582      */
583     public Bitmap captureBitmap() {
584         getSettings().setOffscreenPreRaster(true);
585         waitForDOMReadyToRender();
586         return WebkitUtils.onMainThreadSync(() -> {
587             Bitmap bitmap = Bitmap.createBitmap(mWebView.getWidth(), mWebView.getHeight(),
588                     Bitmap.Config.ARGB_8888);
589             Canvas canvas = new Canvas(bitmap);
590             mWebView.draw(canvas);
591             return bitmap;
592         });
593     }
594 
595     /**
596      * Set LayoutParams to MATCH_PARENT.
597      *
598      * @param view Target view
599      */
600     private void setMatchParent(View view) {
601         ViewGroup.LayoutParams params = view.getLayoutParams();
602         params.height = ViewGroup.LayoutParams.MATCH_PARENT;
603         params.width = ViewGroup.LayoutParams.MATCH_PARENT;
604         view.setLayoutParams(params);
605     }
606 }
607