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      * The caller is responsible for destroying the WebView instance.
75      *
76      * @param webView The webView that the methods should call.
77      */
WebViewOnUiThread(WebView webView)78     public WebViewOnUiThread(WebView webView) {
79         super(webView);
80         mWebView = webView;
81     }
82 
cleanUp()83     public void cleanUp() {
84         super.destroy();
85     }
86 
setWebViewClient(final WebViewClient webViewClient)87     public void setWebViewClient(final WebViewClient webViewClient) {
88         WebkitUtils.onMainThreadSync(() -> {
89             mWebView.setWebViewClient(webViewClient);
90         });
91     }
92 
setWebChromeClient(final WebChromeClient webChromeClient)93     public void setWebChromeClient(final WebChromeClient webChromeClient) {
94         WebkitUtils.onMainThreadSync(() -> {
95             mWebView.setWebChromeClient(webChromeClient);
96         });
97     }
98 
99     /**
100      * Set the webview renderer client for {@code mWebView}, on the UI thread.
101      */
setWebViewRenderProcessClient( final WebViewRenderProcessClient webViewRenderProcessClient)102     public void setWebViewRenderProcessClient(
103             final WebViewRenderProcessClient webViewRenderProcessClient) {
104         setWebViewRenderProcessClient(mWebView, webViewRenderProcessClient);
105     }
106 
107     /**
108      * Set the webview renderer client for {@code webView}, on the UI thread.
109      */
setWebViewRenderProcessClient( final WebView webView, final WebViewRenderProcessClient webViewRenderProcessClient)110     public static void setWebViewRenderProcessClient(
111             final WebView webView,
112             final WebViewRenderProcessClient webViewRenderProcessClient) {
113         WebkitUtils.onMainThreadSync(() ->
114                 webView.setWebViewRenderProcessClient(webViewRenderProcessClient)
115         );
116     }
117 
118     /**
119      * Set the webview renderer client for {@code mWebView}, on the UI thread, with callbacks
120      * executed by {@code executor}
121      */
setWebViewRenderProcessClient( final Executor executor, final WebViewRenderProcessClient webViewRenderProcessClient)122     public void setWebViewRenderProcessClient(
123             final Executor executor, final WebViewRenderProcessClient webViewRenderProcessClient) {
124         setWebViewRenderProcessClient(mWebView, executor, webViewRenderProcessClient);
125     }
126 
127     /**
128      * Set the webview renderer client for {@code webView}, on the UI thread, with callbacks
129      * executed by {@code executor}
130      */
setWebViewRenderProcessClient( final WebView webView, final Executor executor, final WebViewRenderProcessClient webViewRenderProcessClient)131     public static void setWebViewRenderProcessClient(
132             final WebView webView,
133             final Executor executor,
134             final WebViewRenderProcessClient webViewRenderProcessClient) {
135         WebkitUtils.onMainThreadSync(() ->
136                 webView.setWebViewRenderProcessClient(executor, webViewRenderProcessClient)
137         );
138     }
139 
140     /**
141      * Get the webview renderer client currently set on {@code mWebView}, on the UI thread.
142      */
getWebViewRenderProcessClient()143     public WebViewRenderProcessClient getWebViewRenderProcessClient() {
144         return getWebViewRenderProcessClient(mWebView);
145     }
146 
147     /**
148      * Get the webview renderer client currently set on {@code webView}, on the UI thread.
149      */
getWebViewRenderProcessClient( final WebView webView)150     public static WebViewRenderProcessClient getWebViewRenderProcessClient(
151             final WebView webView) {
152         return WebkitUtils.onMainThreadSync(() -> {
153             return webView.getWebViewRenderProcessClient();
154         });
155     }
156 
setPictureListener(final PictureListener pictureListener)157     public void setPictureListener(final PictureListener pictureListener) {
158         WebkitUtils.onMainThreadSync(() -> {
159             mWebView.setPictureListener(pictureListener);
160         });
161     }
162 
setNetworkAvailable(final boolean available)163     public void setNetworkAvailable(final boolean available) {
164         WebkitUtils.onMainThreadSync(() -> {
165             mWebView.setNetworkAvailable(available);
166         });
167     }
168 
setDownloadListener(final DownloadListener listener)169     public void setDownloadListener(final DownloadListener listener) {
170         WebkitUtils.onMainThreadSync(() -> {
171             mWebView.setDownloadListener(listener);
172         });
173     }
174 
setBackgroundColor(final int color)175     public void setBackgroundColor(final int color) {
176         WebkitUtils.onMainThreadSync(() -> {
177             mWebView.setBackgroundColor(color);
178         });
179     }
180 
clearCache(final boolean includeDiskFiles)181     public void clearCache(final boolean includeDiskFiles) {
182         WebkitUtils.onMainThreadSync(() -> {
183             mWebView.clearCache(includeDiskFiles);
184         });
185     }
186 
clearHistory()187     public void clearHistory() {
188         WebkitUtils.onMainThreadSync(() -> {
189             mWebView.clearHistory();
190         });
191     }
192 
requestFocus()193     public void requestFocus() {
194         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
195             @Override
196             protected boolean check() {
197                 requestFocusOnUiThread();
198                 return hasFocus();
199             }
200         }.run();
201     }
202 
requestFocusOnUiThread()203     private void requestFocusOnUiThread() {
204         WebkitUtils.onMainThreadSync(() -> {
205             mWebView.requestFocus();
206         });
207     }
208 
hasFocus()209     private boolean hasFocus() {
210         return WebkitUtils.onMainThreadSync(() -> {
211             return mWebView.hasFocus();
212         });
213     }
214 
215     public boolean canZoomIn() {
216         return WebkitUtils.onMainThreadSync(() -> {
217             return mWebView.canZoomIn();
218         });
219     }
220 
221     public boolean canZoomOut() {
222         return WebkitUtils.onMainThreadSync(() -> {
223             return mWebView.canZoomOut();
224         });
225     }
226 
227     public boolean zoomIn() {
228         return WebkitUtils.onMainThreadSync(() -> {
229             return mWebView.zoomIn();
230         });
231     }
232 
233     public boolean zoomOut() {
234         return WebkitUtils.onMainThreadSync(() -> {
235             return mWebView.zoomOut();
236         });
237     }
238 
239     public void zoomBy(final float zoomFactor) {
240         WebkitUtils.onMainThreadSync(() -> {
241             mWebView.zoomBy(zoomFactor);
242         });
243     }
244 
245     public void setFindListener(final WebView.FindListener listener) {
246         WebkitUtils.onMainThreadSync(() -> {
247             mWebView.setFindListener(listener);
248         });
249     }
250 
251     public void removeJavascriptInterface(final String interfaceName) {
252         WebkitUtils.onMainThreadSync(() -> {
253             mWebView.removeJavascriptInterface(interfaceName);
254         });
255     }
256 
257     public WebMessagePort[] createWebMessageChannel() {
258         return WebkitUtils.onMainThreadSync(() -> {
259             return mWebView.createWebMessageChannel();
260         });
261     }
262 
263     public void postWebMessage(final WebMessage message, final Uri targetOrigin) {
264         WebkitUtils.onMainThreadSync(() -> {
265             mWebView.postWebMessage(message, targetOrigin);
266         });
267     }
268 
269     public void addJavascriptInterface(final Object object, final String name) {
270         WebkitUtils.onMainThreadSync(() -> {
271             mWebView.addJavascriptInterface(object, name);
272         });
273     }
274 
275     public void flingScroll(final int vx, final int vy) {
276         WebkitUtils.onMainThreadSync(() -> {
277             mWebView.flingScroll(vx, vy);
278         });
279     }
280 
281     public void requestFocusNodeHref(final Message hrefMsg) {
282         WebkitUtils.onMainThreadSync(() -> {
283             mWebView.requestFocusNodeHref(hrefMsg);
284         });
285     }
286 
287     public void requestImageRef(final Message msg) {
288         WebkitUtils.onMainThreadSync(() -> {
289             mWebView.requestImageRef(msg);
290         });
291     }
292 
293     public void setInitialScale(final int scaleInPercent) {
294         WebkitUtils.onMainThreadSync(() -> {
295                 mWebView.setInitialScale(scaleInPercent);
296         });
297     }
298 
299     public void clearSslPreferences() {
300         WebkitUtils.onMainThreadSync(() -> {
301             mWebView.clearSslPreferences();
302         });
303     }
304 
305     public void clearClientCertPreferences(final Runnable onCleared) {
306         WebkitUtils.onMainThreadSync(() -> {
307             WebView.clearClientCertPreferences(onCleared);
308         });
309     }
310 
311     public void resumeTimers() {
312         WebkitUtils.onMainThreadSync(() -> {
313             mWebView.resumeTimers();
314         });
315     }
316 
317     public void findNext(final boolean forward) {
318         WebkitUtils.onMainThreadSync(() -> {
319             mWebView.findNext(forward);
320         });
321     }
322 
323     public void clearMatches() {
324         WebkitUtils.onMainThreadSync(() -> {
325             mWebView.clearMatches();
326         });
327     }
328 
329     public void loadUrl(final String url) {
330         WebkitUtils.onMainThreadSync(() -> {
331             mWebView.loadUrl(url);
332         });
333     }
334 
335     public void stopLoading() {
336         WebkitUtils.onMainThreadSync(() -> {
337             mWebView.stopLoading();
338         });
339     }
340 
341     /**
342      * Reload the previous URL.
343      */
344     public void reload() {
345         WebkitUtils.onMainThreadSync(() -> {
346             mWebView.reload();
347         });
348     }
349 
350     public String getTitle() {
351         return WebkitUtils.onMainThreadSync(() -> {
352             return mWebView.getTitle();
353         });
354     }
355 
356     public WebSettings getSettings() {
357         return WebkitUtils.onMainThreadSync(() -> {
358             return mWebView.getSettings();
359         });
360     }
361 
362     public WebBackForwardList copyBackForwardList() {
363         return WebkitUtils.onMainThreadSync(() -> {
364             return mWebView.copyBackForwardList();
365         });
366     }
367 
368     public Bitmap getFavicon() {
369         return WebkitUtils.onMainThreadSync(() -> {
370             return mWebView.getFavicon();
371         });
372     }
373 
374     public String getUrl() {
375         return WebkitUtils.onMainThreadSync(() -> {
376             return mWebView.getUrl();
377         });
378     }
379 
380     public int getProgress() {
381         return WebkitUtils.onMainThreadSync(() -> {
382             return mWebView.getProgress();
383         });
384     }
385 
386     public int getHeight() {
387         return WebkitUtils.onMainThreadSync(() -> {
388             return mWebView.getHeight();
389         });
390     }
391 
392     public int getContentHeight() {
393         return WebkitUtils.onMainThreadSync(() -> {
394             return mWebView.getContentHeight();
395         });
396     }
397 
398     public boolean pageUp(final boolean top) {
399         return WebkitUtils.onMainThreadSync(() -> {
400             return mWebView.pageUp(top);
401         });
402     }
403 
404     public boolean pageDown(final boolean bottom) {
405         return WebkitUtils.onMainThreadSync(() -> {
406             return mWebView.pageDown(bottom);
407         });
408     }
409 
410     /**
411      * Post a visual state listener callback for mWebView on the UI thread.
412      */
413     public void postVisualStateCallback(final long requestId, final VisualStateCallback callback) {
414         WebkitUtils.onMainThreadSync(() -> {
415             mWebView.postVisualStateCallback(requestId, callback);
416         });
417     }
418 
419     public int[] getLocationOnScreen() {
420         final int[] location = new int[2];
421         return WebkitUtils.onMainThreadSync(() -> {
422             mWebView.getLocationOnScreen(location);
423             return location;
424         });
425     }
426 
427     public float getScale() {
428         return WebkitUtils.onMainThreadSync(() -> {
429             return mWebView.getScale();
430         });
431     }
432 
433     public boolean requestFocus(final int direction,
434             final Rect previouslyFocusedRect) {
435         return WebkitUtils.onMainThreadSync(() -> {
436             return mWebView.requestFocus(direction, previouslyFocusedRect);
437         });
438     }
439 
440     public HitTestResult getHitTestResult() {
441         return WebkitUtils.onMainThreadSync(() -> {
442             return mWebView.getHitTestResult();
443         });
444     }
445 
446     public int getScrollX() {
447         return WebkitUtils.onMainThreadSync(() -> {
448             return mWebView.getScrollX();
449         });
450     }
451 
452     public int getScrollY() {
453         return WebkitUtils.onMainThreadSync(() -> {
454             return mWebView.getScrollY();
455         });
456     }
457 
458     public final DisplayMetrics getDisplayMetrics() {
459         return WebkitUtils.onMainThreadSync(() -> {
460             return mWebView.getContext().getResources().getDisplayMetrics();
461         });
462     }
463 
464     public boolean requestChildRectangleOnScreen(final View child,
465             final Rect rect,
466             final boolean immediate) {
467         return WebkitUtils.onMainThreadSync(() -> {
468             return mWebView.requestChildRectangleOnScreen(child, rect,
469                     immediate);
470         });
471     }
472 
473     public int findAll(final String find) {
474         return WebkitUtils.onMainThreadSync(() -> {
475             return mWebView.findAll(find);
476         });
477     }
478 
479     public Picture capturePicture() {
480         return WebkitUtils.onMainThreadSync(() -> {
481             return mWebView.capturePicture();
482         });
483     }
484 
485     /**
486      * Execute javascript synchronously, returning the result.
487      */
488     public String evaluateJavascriptSync(final String script) {
489         final SettableFuture<String> future = SettableFuture.create();
490         evaluateJavascript(script, result -> future.set(result));
491         return WebkitUtils.waitForFuture(future);
492     }
493 
494     public void evaluateJavascript(final String script, final ValueCallback<String> result) {
495         WebkitUtils.onMainThread(() -> mWebView.evaluateJavascript(script, result));
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