1 /*
2  * Copyright 2018 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 androidx.webkit;
18 
19 import android.annotation.SuppressLint;
20 import android.content.Context;
21 import android.content.pm.PackageInfo;
22 import android.content.pm.PackageManager;
23 import android.net.Uri;
24 import android.os.Build;
25 import android.os.Bundle;
26 import android.os.CancellationSignal;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.webkit.ValueCallback;
30 import android.webkit.WebChromeClient;
31 import android.webkit.WebSettings;
32 import android.webkit.WebView;
33 import android.webkit.WebViewClient;
34 
35 import androidx.annotation.AnyThread;
36 import androidx.annotation.IntRange;
37 import androidx.annotation.RequiresFeature;
38 import androidx.annotation.RequiresOptIn;
39 import androidx.annotation.RestrictTo;
40 import androidx.annotation.UiThread;
41 import androidx.annotation.VisibleForTesting;
42 import androidx.webkit.internal.ApiFeature;
43 import androidx.webkit.internal.ApiHelperForM;
44 import androidx.webkit.internal.ApiHelperForO;
45 import androidx.webkit.internal.ApiHelperForOMR1;
46 import androidx.webkit.internal.ApiHelperForP;
47 import androidx.webkit.internal.ApiHelperForQ;
48 import androidx.webkit.internal.WebMessageAdapter;
49 import androidx.webkit.internal.WebMessagePortImpl;
50 import androidx.webkit.internal.WebViewFeatureInternal;
51 import androidx.webkit.internal.WebViewGlueCommunicator;
52 import androidx.webkit.internal.WebViewProviderAdapter;
53 import androidx.webkit.internal.WebViewProviderFactory;
54 import androidx.webkit.internal.WebViewRenderProcessClientFrameworkAdapter;
55 import androidx.webkit.internal.WebViewRenderProcessImpl;
56 
57 import org.chromium.support_lib_boundary.WebViewProviderBoundaryInterface;
58 import org.jspecify.annotations.NonNull;
59 import org.jspecify.annotations.Nullable;
60 
61 import java.lang.annotation.ElementType;
62 import java.lang.annotation.Retention;
63 import java.lang.annotation.RetentionPolicy;
64 import java.lang.annotation.Target;
65 import java.lang.reflect.InvocationTargetException;
66 import java.lang.reflect.Method;
67 import java.util.ArrayList;
68 import java.util.HashSet;
69 import java.util.List;
70 import java.util.Set;
71 import java.util.WeakHashMap;
72 import java.util.concurrent.Executor;
73 
74 /**
75  * Compatibility version of {@link android.webkit.WebView}
76  */
77 public class WebViewCompat {
78     private static final Uri WILDCARD_URI = Uri.parse("*");
79     private static final Uri EMPTY_URI = Uri.parse("");
80 
81     private static boolean sShouldCacheProvider = true;
82     private static final WeakHashMap<WebView, WebViewProviderAdapter> sProviderAdapterCache =
83             new WeakHashMap<>();
84 
WebViewCompat()85     private WebViewCompat() {
86     } // Don't allow instances of this class to be constructed.
87 
88     /**
89      * Callback interface supplied to {@link #postVisualStateCallback} for receiving
90      * notifications about the visual state.
91      */
92     public interface VisualStateCallback {
93         /**
94          * Invoked when the visual state is ready to be drawn in the next {@link WebView#onDraw}.
95          *
96          * @param requestId The identifier passed to {@link #postVisualStateCallback} when this
97          *                  callback was posted.
98          */
99         @UiThread
onComplete(long requestId)100         void onComplete(long requestId);
101     }
102 
103     /**
104      * This listener receives messages sent on the JavaScript object which was injected by {@link
105      * #addWebMessageListener(WebView, String, Set, WebViewCompat.WebMessageListener)}.
106      */
107     public interface WebMessageListener {
108         /**
109          * Receives a message sent by a {@code postMessage()} on the injected JavaScript object.
110          *
111          * <p> Note that when the frame is {@code file:} or {@code content:} origin, the value of
112          * {@code sourceOrigin} is a string {@code "null"}. However we highly recommend to not use
113          * {@code file:} or {@code content:} URLs, see {@link WebViewAssetLoader} for serving local
114          * content under {@code http:} or {@code https:} domain.
115          *
116          * @param view         The {@link WebView} containing the frame which sent this message.
117          * @param message      The message from JavaScript.
118          * @param sourceOrigin The origin of the frame that the message is from.
119          * @param isMainFrame  {@code true} If the message is from the main frame.
120          * @param replyProxy   Used to reply back to the JavaScript object.
121          */
122         @UiThread
onPostMessage(@onNull WebView view, @NonNull WebMessageCompat message, @NonNull Uri sourceOrigin, boolean isMainFrame, @NonNull JavaScriptReplyProxy replyProxy)123         void onPostMessage(@NonNull WebView view, @NonNull WebMessageCompat message,
124                 @NonNull Uri sourceOrigin, boolean isMainFrame,
125                 @NonNull JavaScriptReplyProxy replyProxy);
126     }
127 
128     /**
129      * Posts a {@link VisualStateCallback}, which will be called when
130      * the current state of the WebView is ready to be drawn.
131      *
132      * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not
133      * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. The
134      * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents
135      * of the DOM at the current time are ready to be drawn the next time the {@link WebView} draws.
136      *
137      * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the
138      * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may
139      * also contain updates applied after the callback was posted.
140      *
141      * <p>The state of the DOM covered by this API includes the following:
142      * <ul>
143      * <li>primitive HTML elements (div, img, span, etc..)</li>
144      * <li>images</li>
145      * <li>CSS animations</li>
146      * <li>WebGL</li>
147      * <li>canvas</li>
148      * </ul>
149      * It does not include the state of:
150      * <ul>
151      * <li>the video tag</li>
152      * </ul>
153      *
154      * <p>To guarantee that the {@link WebView} will successfully render the first frame
155      * after the {@link VisualStateCallback#onComplete} method has been called a set of
156      * conditions must be met:
157      * <ul>
158      * <li>If the {@link WebView}'s visibility is set to {@link android.view.View#VISIBLE VISIBLE}
159      * then * the {@link WebView} must be attached to the view hierarchy.</li>
160      * <li>If the {@link WebView}'s visibility is set to
161      * {@link android.view.View#INVISIBLE INVISIBLE} then the {@link WebView} must be attached to
162      * the view hierarchy and must be made {@link android.view.View#VISIBLE VISIBLE} from the
163      * {@link VisualStateCallback#onComplete} method.</li>
164      * <li>If the {@link WebView}'s visibility is set to {@link android.view.View#GONE GONE} then
165      * the {@link WebView} must be attached to the view hierarchy and its
166      * {@link android.widget.AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be
167      * set to fixed values and must be made {@link android.view.View#VISIBLE VISIBLE} from the
168      * {@link VisualStateCallback#onComplete} method.</li>
169      * </ul>
170      *
171      * <p>When using this API it is also recommended to enable pre-rasterization if the {@link
172      * WebView} is off screen to avoid flickering. See
173      * {@link android.webkit.WebSettings#setOffscreenPreRaster} for more details and do consider its
174      * caveats.
175      *
176      * <p>
177      * This method should only be called if
178      * {@link WebViewFeature#isFeatureSupported(String)}
179      * returns true for {@link WebViewFeature#VISUAL_STATE_CALLBACK}.
180      *
181      * @param webview   The WebView to post to.
182      * @param requestId An id that will be returned in the callback to allow callers to match
183      *                  requests with callbacks.
184      * @param callback  The callback to be invoked.
185      */
186     @UiThread
187     @RequiresFeature(name = WebViewFeature.VISUAL_STATE_CALLBACK,
188             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
postVisualStateCallback(@onNull WebView webview, long requestId, final @NonNull VisualStateCallback callback)189     public static void postVisualStateCallback(@NonNull WebView webview, long requestId,
190             final @NonNull VisualStateCallback callback) {
191         ApiFeature.M feature = WebViewFeatureInternal.VISUAL_STATE_CALLBACK;
192         if (feature.isSupportedByFramework()) {
193             ApiHelperForM.postVisualStateCallback(webview, requestId, callback);
194         } else if (feature.isSupportedByWebView()) {
195             checkThread(webview);
196             getProvider(webview).insertVisualStateCallback(requestId, callback);
197         } else {
198             throw WebViewFeatureInternal.getUnsupportedOperationException();
199         }
200     }
201 
202     /**
203      * Starts Safe Browsing initialization.
204      * <p>
205      * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
206      * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
207      * devices {@code callback} will receive {@code false}.
208      * <p>
209      * This should not be called if Safe Browsing has been disabled by manifest tag or {@link
210      * android.webkit.WebSettings#setSafeBrowsingEnabled}. This prepares resources used for Safe
211      * Browsing.
212      * <p>
213      * This should be called with the Application Context (and will always use the Application
214      * context to do its work regardless).
215      *
216      * <p>
217      * This method should only be called if
218      * {@link WebViewFeature#isFeatureSupported(String)}
219      * returns true for {@link WebViewFeature#START_SAFE_BROWSING}.
220      *
221      * @param context  Application Context.
222      * @param callback will be called on the UI thread with {@code true} if initialization is
223      *                 successful, {@code false} otherwise.
224      * @deprecated In WebView version 122.0.6174.0 and later, this initialization is done
225      * automatically, so there is no need to call this API. If called, this API will invoke
226      * the {@code callback} immediately with {@code true}, given that Safe Browsing
227      * is enabled and supported on the device.
228      */
229     @AnyThread
230     @Deprecated
231     @RequiresFeature(name = WebViewFeature.START_SAFE_BROWSING,
232             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
startSafeBrowsing(@onNull Context context, @Nullable ValueCallback<Boolean> callback)233     public static void startSafeBrowsing(@NonNull Context context,
234             @Nullable ValueCallback<Boolean> callback) {
235         ApiFeature.O_MR1 feature = WebViewFeatureInternal.START_SAFE_BROWSING;
236         if (feature.isSupportedByFramework()) {
237             ApiHelperForOMR1.startSafeBrowsing(context, callback);
238         } else if (feature.isSupportedByWebView()) {
239             getFactory().getStatics().initSafeBrowsing(context, callback);
240         } else {
241             throw WebViewFeatureInternal.getUnsupportedOperationException();
242         }
243     }
244 
245     /**
246      * Configures a set of hosts (domain names/IP addresses) that are exempt from SafeBrowsing
247      * checks. The set is global for all the WebViews.
248      * <p>
249      * Each rule should take one of these:
250      * <table>
251      * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
252      * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
253      * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
254      * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
255      * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
256      * </table>
257      * <p>
258      * All other rules, including wildcards, are invalid.
259      * <p>
260      * The correct syntax for hosts is defined by <a
261      * href="https://tools.ietf.org/html/rfc3986#section-3.2.2">RFC 3986</a>.
262      *
263      * <p>
264      * This method should only be called if
265      * {@link WebViewFeature#isFeatureSupported(String)}
266      * returns true for {@link WebViewFeature#SAFE_BROWSING_ALLOWLIST}.
267      *
268      * @param hosts    the set of hosts for which to skip Safe Browsing checks
269      * @param callback will be called with {@code true} if hosts are successfully added to the
270      *                 allowlist, {@code false} if any hosts are malformed. The callback will be
271      *                 run on the UI
272      *                 thread
273      */
274     @AnyThread
275     @RequiresFeature(name = WebViewFeature.SAFE_BROWSING_ALLOWLIST,
276             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
setSafeBrowsingAllowlist(@onNull Set<String> hosts, @Nullable ValueCallback<Boolean> callback)277     public static void setSafeBrowsingAllowlist(@NonNull Set<String> hosts,
278             @Nullable ValueCallback<Boolean> callback) {
279         ApiFeature.O_MR1 preferredFeature =
280                 WebViewFeatureInternal.SAFE_BROWSING_ALLOWLIST_PREFERRED_TO_PREFERRED;
281         ApiFeature.O_MR1 deprecatedFeature =
282                 WebViewFeatureInternal.SAFE_BROWSING_ALLOWLIST_PREFERRED_TO_DEPRECATED;
283         if (preferredFeature.isSupportedByWebView()) {
284             getFactory().getStatics().setSafeBrowsingAllowlist(hosts, callback);
285             return;
286         }
287         List<String> hostsList = new ArrayList<>(hosts);
288         if (deprecatedFeature.isSupportedByFramework()) {
289             ApiHelperForOMR1.setSafeBrowsingWhitelist(hostsList, callback);
290         } else if (deprecatedFeature.isSupportedByWebView()) {
291             getFactory().getStatics().setSafeBrowsingWhitelist(hostsList, callback);
292         } else {
293             throw WebViewFeatureInternal.getUnsupportedOperationException();
294         }
295     }
296 
297     /**
298      * Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks.
299      * The list is global for all the WebViews.
300      * <p>
301      * Each rule should take one of these:
302      * <table>
303      * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
304      * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
305      * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
306      * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
307      * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
308      * </table>
309      * <p>
310      * All other rules, including wildcards, are invalid.
311      * <p>
312      * The correct syntax for hosts is defined by <a
313      * href="https://tools.ietf.org/html/rfc3986#section-3.2.2">RFC 3986</a>.
314      *
315      * <p>
316      * This method should only be called if
317      * {@link WebViewFeature#isFeatureSupported(String)}
318      * returns true for {@link WebViewFeature#SAFE_BROWSING_WHITELIST}.
319      *
320      * @param hosts    the list of hosts
321      * @param callback will be called with {@code true} if hosts are successfully added to the
322      *                 allowlist. It will be called with {@code false} if any hosts are malformed
323      *                 . The callback
324      *                 will be run on the UI thread
325      * @deprecated Please use {@link #setSafeBrowsingAllowlist(Set, ValueCallback)} instead.
326      */
327     @AnyThread
328     @Deprecated
329     @RequiresFeature(name = WebViewFeature.SAFE_BROWSING_WHITELIST,
330             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
setSafeBrowsingWhitelist(@onNull List<String> hosts, @Nullable ValueCallback<Boolean> callback)331     public static void setSafeBrowsingWhitelist(@NonNull List<String> hosts,
332             @Nullable ValueCallback<Boolean> callback) {
333         setSafeBrowsingAllowlist(new HashSet<>(hosts), callback);
334     }
335 
336     /**
337      * Returns a URL pointing to the privacy policy for Safe Browsing reporting.
338      *
339      * <p>
340      * This method should only be called if
341      * {@link WebViewFeature#isFeatureSupported(String)}
342      * returns true for {@link WebViewFeature#SAFE_BROWSING_PRIVACY_POLICY_URL}.
343      *
344      * @return the url pointing to a privacy policy document which can be displayed to users.
345      */
346     @AnyThread
347     @RequiresFeature(name = WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL,
348             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
getSafeBrowsingPrivacyPolicyUrl()349     public static @NonNull Uri getSafeBrowsingPrivacyPolicyUrl() {
350         ApiFeature.O_MR1 feature =
351                 WebViewFeatureInternal.SAFE_BROWSING_PRIVACY_POLICY_URL;
352         if (feature.isSupportedByFramework()) {
353             return ApiHelperForOMR1.getSafeBrowsingPrivacyPolicyUrl();
354         } else if (feature.isSupportedByWebView()) {
355             return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl();
356         } else {
357             throw WebViewFeatureInternal.getUnsupportedOperationException();
358         }
359     }
360 
361     /**
362      * If WebView has already been loaded into the current process this method will return the
363      * package that was used to load it. Otherwise, the package that would be used if the WebView
364      * was loaded right now will be returned; this does not cause WebView to be loaded, so this
365      * information may become outdated at any time.
366      * The WebView package changes either when the current WebView package is updated, disabled, or
367      * uninstalled. It can also be changed through a Developer Setting.
368      * If the WebView package changes, any app process that has loaded WebView will be killed. The
369      * next time the app starts and loads WebView it will use the new WebView package instead.
370      *
371      * @return the current WebView package, or {@code null} if there is none.
372      */
373     // Note that this API is not protected by a {@link androidx.webkit.WebViewFeature} since
374     // this feature is not dependent on the WebView APK.
375     @AnyThread
getCurrentWebViewPackage(@onNull Context context)376     public static @Nullable PackageInfo getCurrentWebViewPackage(@NonNull Context context) {
377         PackageInfo info = getCurrentLoadedWebViewPackage();
378         if (info != null) return info;
379 
380         // If WebViewFactory.getLoadedPackageInfo() returns null then WebView hasn't been loaded
381         // yet, in that case we need to fetch the name of the WebView package, and fetch the
382         // corresponding PackageInfo through the PackageManager
383         return getNotYetLoadedWebViewPackageInfo(context);
384     }
385 
386     /**
387      * @return the loaded WebView package, or null if no WebView is created.
388      * @see #getCurrentWebViewPackage(Context)
389      */
390     @AnyThread
391     @RestrictTo(RestrictTo.Scope.LIBRARY)
getCurrentLoadedWebViewPackage()392     public static @Nullable PackageInfo getCurrentLoadedWebViewPackage() {
393         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
394             return ApiHelperForO.getCurrentWebViewPackage();
395         } else { // L-N
396             try {
397                 return getLoadedWebViewPackageInfo();
398             } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException
399                      | NoSuchMethodException ignored) {
400             }
401         }
402         return null;
403     }
404 
405     /**
406      * Return the PackageInfo of the currently loaded WebView APK. This method uses reflection and
407      * propagates any exceptions thrown, to the caller.
408      */
409     @SuppressLint("PrivateApi")
getLoadedWebViewPackageInfo()410     private static PackageInfo getLoadedWebViewPackageInfo()
411             throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
412             IllegalAccessException {
413         Class<?> webViewFactoryClass = Class.forName("android.webkit.WebViewFactory");
414         return (PackageInfo) webViewFactoryClass.getMethod(
415                 "getLoadedPackageInfo").invoke(null);
416     }
417 
418     /**
419      * Return the PackageInfo of the WebView APK that would have been used as WebView implementation
420      * if WebView was to be loaded right now.
421      */
422     @SuppressLint("PrivateApi")
423     @SuppressWarnings("deprecation")
getNotYetLoadedWebViewPackageInfo(Context context)424     private static PackageInfo getNotYetLoadedWebViewPackageInfo(Context context) {
425         String webviewPackageName;
426         try {
427             if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
428                 Class<?> webViewFactoryClass = Class.forName("android.webkit.WebViewFactory");
429 
430                 webviewPackageName = (String) webViewFactoryClass.getMethod(
431                         "getWebViewPackageName").invoke(null);
432             } else {
433                 Class<?> webviewUpdateServiceClass =
434                         Class.forName("android.webkit.WebViewUpdateService");
435                 webviewPackageName = (String) webviewUpdateServiceClass.getMethod(
436                         "getCurrentWebViewPackageName").invoke(null);
437             }
438         } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException
439                  | NoSuchMethodException e) {
440             return null;
441         }
442         if (webviewPackageName == null) return null;
443         PackageManager pm = context.getPackageManager();
444         try {
445             return pm.getPackageInfo(webviewPackageName, 0);
446         } catch (PackageManager.NameNotFoundException e) {
447             return null;
448         }
449     }
450 
getProvider(WebView webview)451     private static WebViewProviderAdapter getProvider(WebView webview) {
452         ApiFeature.NoFramework feature = WebViewFeatureInternal.CACHE_PROVIDER;
453         if (feature.isSupportedByWebView() && sShouldCacheProvider) {
454             WebViewProviderAdapter adapter = sProviderAdapterCache.get(webview);
455             if (adapter == null) {
456                 adapter = new WebViewProviderAdapter(createProvider(webview));
457                 sProviderAdapterCache.put(webview, adapter);
458             }
459             return adapter;
460         }
461         return new WebViewProviderAdapter(createProvider(webview));
462     }
463 
464     /**
465      * Creates a message channel to communicate with JS and returns the message
466      * ports that represent the endpoints of this message channel. The HTML5 message
467      * channel functionality is described
468      * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here
469      * </a>
470      *
471      * <p>The returned message channels are entangled and already in started state.
472      *
473      * <p>
474      * This method should only be called if
475      * {@link WebViewFeature#isFeatureSupported(String)}
476      * returns true for {@link WebViewFeature#CREATE_WEB_MESSAGE_CHANNEL}.
477      *
478      * @return an array of size two, containing the two message ports that form the message channel.
479      */
480     @UiThread
481     @RequiresFeature(name = WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL,
482             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
createWebMessageChannel( @onNull WebView webview)483     public static WebMessagePortCompat @NonNull [] createWebMessageChannel(
484             @NonNull WebView webview) {
485         final ApiFeature.M feature = WebViewFeatureInternal.CREATE_WEB_MESSAGE_CHANNEL;
486         if (feature.isSupportedByFramework()) {
487             return WebMessagePortImpl.portsToCompat(ApiHelperForM.createWebMessageChannel(webview));
488         } else if (feature.isSupportedByWebView()) {
489             checkThread(webview);
490             return getProvider(webview).createWebMessageChannel();
491         } else {
492             throw WebViewFeatureInternal.getUnsupportedOperationException();
493         }
494     }
495 
496     /**
497      * Post a message to main frame. The embedded application can restrict the
498      * messages to a certain target origin. See
499      * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages">
500      * HTML5 spec</a> for how target origin can be used.
501      * <p>
502      * A target origin can be set as a wildcard ("*"). However this is not recommended.
503      * See the page above for security issues.
504      *
505      * <p>
506      * This method should only be called if
507      * {@link WebViewFeature#isFeatureSupported(String)}
508      * returns true for {@link WebViewFeature#POST_WEB_MESSAGE}.
509      *
510      * <p>
511      * When posting a {@link WebMessageCompat} with type {@link WebMessageCompat#TYPE_ARRAY_BUFFER},
512      * this method should check if {@link WebViewFeature#isFeatureSupported(String)} returns true
513      * for {@link WebViewFeature#WEB_MESSAGE_ARRAY_BUFFER}. Example:
514      * <pre class="prettyprint">
515      * if (message.getType() == WebMessageCompat.TYPE_ARRAY_BUFFER) {
516      *     if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER) {
517      *         // ArrayBuffer message is supported, send message here.
518      *         WebViewCompat.postWebMessage(webview, message, ...);
519      *     }
520      * }
521      * </pre
522      *
523      * @param webview The WebView to post to.
524      * @param message the WebMessage
525      * @param targetOrigin the target origin.
526      */
527     @UiThread
528     @RequiresFeature(name = WebViewFeature.POST_WEB_MESSAGE,
529             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
postWebMessage(@onNull WebView webview, @NonNull WebMessageCompat message, @NonNull Uri targetOrigin)530     public static void postWebMessage(@NonNull WebView webview, @NonNull WebMessageCompat message,
531             @NonNull Uri targetOrigin) {
532         // The wildcard ("*") Uri was first supported in WebView 60, see
533         // crrev/5ec5b67cbab33cea51b0ee11a286c885c2de4d5d, so on some Android versions using "*"
534         // won't work. WebView has always supported using an empty Uri "" as a wildcard - so convert
535         // "*" into "" here.
536         if (WILDCARD_URI.equals(targetOrigin)) {
537             targetOrigin = EMPTY_URI;
538         }
539 
540         final ApiFeature.M feature = WebViewFeatureInternal.POST_WEB_MESSAGE;
541         // Only String type is supported by framework.
542         if (feature.isSupportedByFramework() && message.getType() == WebMessageCompat.TYPE_STRING) {
543             ApiHelperForM.postWebMessage(webview,
544                     WebMessagePortImpl.compatToFrameworkMessage(message), targetOrigin);
545         } else if (feature.isSupportedByWebView()
546                 && WebMessageAdapter.isMessagePayloadTypeSupportedByWebView(message.getType())) {
547             checkThread(webview);
548             getProvider(webview).postWebMessage(message, targetOrigin);
549         } else {
550             throw WebViewFeatureInternal.getUnsupportedOperationException();
551         }
552     }
553 
554     /**
555      * Adds a {@link WebMessageListener} to the {@link WebView} and injects a JavaScript object into
556      * each frame that the {@link WebMessageListener} will listen on.
557      *
558      * <p>
559      * The injected JavaScript object will be named {@code jsObjectName} in the global scope. This
560      * will inject the JavaScript object in any frame whose origin matches {@code
561      * allowedOriginRules} for every navigation after this call, and the JavaScript object will be
562      * available immediately when the page begins to load.
563      *
564      * <p>
565      * Each {@code allowedOriginRules} entry must follow the format {@code SCHEME "://" [
566      * HOSTNAME_PATTERN [ ":" PORT ] ]}, each part is explained in the below table:
567      *
568      * <table>
569      * <tr><th>Rule</th><th>Description</th><th>Example</th></tr>
570      * <p>
571      * <tr>
572      * <td>http/https with hostname</td>
573      * <td>{@code SCHEME} is http or https; {@code HOSTNAME_PATTERN} is a regular hostname; {@code
574      * PORT} is optional, when not present, the rule will match port {@code 80} for http and port
575      * {@code 443} for https.</td>
576      * <td><ul>
577      * <li>{@code https://foobar.com:8080} - Matches https:// URL on port 8080, whose normalized
578      * host is foobar.com.</li>
579      * <li>{@code https://www.example.com} - Matches https:// URL on port 443, whose normalized host
580      * is www.example.com.</li>
581      * </ul></td>
582      * </tr>
583      * <p>
584      * <tr>
585      * <td>http/https with pattern matching</td>
586      * <td>{@code SCHEME} is http or https; {@code HOSTNAME_PATTERN} is a sub-domain matching
587      * pattern with a leading {@code *.}; {@code PORT} is optional, when not present, the rule will
588      * match port {@code 80} for http and port {@code 443} for https.</td>
589      *
590      * <td><ul>
591      * <li>{@code https://*.example.com} - Matches https://calendar.example.com and
592      * https://foo.bar.example.com but not https://example.com.</li>
593      * <li>{@code https://*.example.com:8080} - Matches https://calendar.example.com:8080</li>
594      * </ul></td>
595      * </tr>
596      * <p>
597      * <tr>
598      * <td>http/https with IP literal</td>
599      * <td>{@code SCHEME} is https or https; {@code HOSTNAME_PATTERN} is IP literal; {@code PORT} is
600      * optional, when not present, the rule will match port {@code 80} for http and port {@code 443}
601      * for https.</td>
602      *
603      * <td><ul>
604      * <li>{@code https://127.0.0.1} - Matches https:// URL on port 443, whose IPv4 address is
605      * 127.0.0.1</li>
606      * <li>{@code https://[::1]} or {@code https://[0:0::1]}- Matches any URL to the IPv6 loopback
607      * address with port 443.</li>
608      * <li>{@code https://[::1]:99} - Matches any https:// URL to the IPv6 loopback on port 99.</li>
609      * </ul></td>
610      * </tr>
611      * <p>
612      * <tr>
613      * <td>Custom scheme</td>
614      * <td>{@code SCHEME} is a custom scheme; {@code HOSTNAME_PATTERN} and {@code PORT} must not be
615      * present.</td>
616      * <td><ul>
617      * <li>{@code my-app-scheme://} - Matches any my-app-scheme:// URL.</li>
618      * </ul></td>
619      * </tr>
620      * <p>
621      * <tr><td>{@code *}</td>
622      * <td>Wildcard rule, matches any origin.</td>
623      * <td><ul><li>{@code *}</li></ul></td>
624      * </table>
625      *
626      * <p>
627      * Note that this is a powerful API, as the JavaScript object will be injected when the frame's
628      * origin matches any one of the allowed origins. The HTTPS scheme is strongly recommended for
629      * security; allowing HTTP origins exposes the injected object to any potential network-based
630      * attackers. If a wildcard {@code "*"} is provided, it will inject the JavaScript object to all
631      * frames. A wildcard should only be used if the app wants <b>any</b> third party web page to be
632      * able to use the injected object. When using a wildcard, the app must treat received messages
633      * as untrustworthy and validate any data carefully.
634      *
635      * <p>
636      * This method can be called multiple times to inject multiple JavaScript objects.
637      *
638      * <p>
639      * Let's say the injected JavaScript object is named {@code myObject}. We will have following
640      * methods on that object once it is available to use:
641      * <pre class="prettyprint">
642      * // Web page (in JavaScript)
643      * // message needs to be a JavaScript String or ArrayBuffer, MessagePorts is an optional
644      * // parameter.
645      * myObject.postMessage(message[, MessagePorts])
646      * <p>
647      * // To receive messages posted from the app side, assign a function to the "onmessage"
648      * // property. This function should accept a single "event" argument. "event" has a "data"
649      * // property, which is the message String or ArrayBuffer from the app side.
650      * myObject.onmessage = function(event) { ... }
651      * <p>
652      * // To be compatible with DOM EventTarget's addEventListener, it accepts type and listener
653      * // parameters, where type can be only "message" type and listener can only be a JavaScript
654      * // function for myObject. An event object will be passed to listener with a "data" property,
655      * // which is the message String or ArrayBuffer from the app side.
656      * myObject.addEventListener(type, listener)
657      * <p>
658      * // To be compatible with DOM EventTarget's removeEventListener, it accepts type and listener
659      * // parameters, where type can be only "message" type and listener can only be a JavaScript
660      * // function for myObject.
661      * myObject.removeEventListener(type, listener)
662      * </pre>
663      *
664      * <p>
665      * We start the communication between JavaScript and the app from the JavaScript side. In order
666      * to send message from the app to JavaScript, it needs to post a message from JavaScript first,
667      * so the app will have a {@link JavaScriptReplyProxy} object to respond. Example:
668      * <pre class="prettyprint">
669      * // Web page (in JavaScript)
670      * myObject.onmessage = function(event) {
671      *   // prints "Got it!" when we receive the app's response.
672      *   console.log(event.data);
673      * }
674      * myObject.postMessage("I'm ready!");
675      * </pre>
676      * <pre class="prettyprint">
677      * // App (in Java)
678      * WebMessageListener myListener = new WebMessageListener() {
679      *   &#064;Override
680      *   public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
681      *            boolean isMainFrame, JavaScriptReplyProxy replyProxy) {
682      *     // do something about view, message, sourceOrigin and isMainFrame.
683      *     replyProxy.postMessage("Got it!");
684      *   }
685      * };
686      * if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
687      *   WebViewCompat.addWebMessageListener(webView, "myObject", rules, myListener);
688      * }
689      * </pre>
690      *
691      * <p>
692      * Suppose the communication is already setup, to send ArrayBuffer from the app to web, it
693      * needs to check feature flag({@link WebViewFeature#WEB_MESSAGE_ARRAY_BUFFER}). Here is a
694      * example to send file content from app to web:
695      * <pre class="prettyprint">
696      * // App (in Java)
697      * WebMessageListener myListener = new WebMessageListener() {
698      *   &#064;Override
699      *   public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
700      *            boolean isMainFrame, JavaScriptReplyProxy replyProxy) {
701      *     // Communication is setup, send file data to web.
702      *     if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER)) {
703      *       // Suppose readFileData method is to read content from file.
704      *       byte[] fileData = readFileData("myFile.dat");
705      *       replyProxy.postMessage(fileData);
706      *     }
707      *   }
708      * }
709      * </pre>
710      * <pre class="prettyprint">
711      * // Web page (in JavaScript)
712      * myObject.onmessage = function(event) {
713      *   if (event.data instanceof ArrayBuffer) {
714      *     const data = event.data;  // Received file content from app.
715      *     const dataView = new DataView(data);
716      *     // Consume file content by using JavaScript DataView to access ArrayBuffer.
717      *   }
718      * }
719      * myObject.postMessage("Setup!");
720      * </pre>
721      *
722      * <p>
723      * Suppose the communication is already setup, and feature flag
724      * {@link WebViewFeature#WEB_MESSAGE_ARRAY_BUFFER} is check. Here is a example to download
725      * image in WebView, and send to app:
726      * <pre class="prettyprint">
727      * // Web page (in JavaScript)
728      * const response = await fetch('example.jpg');
729      * if (response.ok) {
730      *     const imageData = await response.arrayBuffer();
731      *     myObject.postMessage(imageData);
732      * }
733      * </pre>
734      * <pre class="prettyprint">
735      * // App (in Java)
736      * WebMessageListener myListener = new WebMessageListener() {
737      *   &#064;Override
738      *   public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
739      *            boolean isMainFrame, JavaScriptReplyProxy replyProxy) {
740      *     if (message.getType() == WebMessageCompat.TYPE_ARRAY_BUFFER) {
741      *       byte[] imageData = message.getArrayBuffer();
742      *       // do something like draw image on ImageView.
743      *     }
744      *   }
745      * };
746      * </pre>
747      *
748      * <p>
749      * This method should only be called if {@link WebViewFeature#isFeatureSupported(String)}
750      * returns true for {@link WebViewFeature#WEB_MESSAGE_LISTENER}.
751      *
752      * @param webView            The {@link WebView} instance that we are interacting with.
753      * @param jsObjectName       The name for the injected JavaScript object for this {@link
754      *                           WebMessageListener}.
755      * @param allowedOriginRules A set of matching rules for the allowed origins.
756      * @param listener           The {@link WebMessageListener WebMessageListener} to handle
757      *                           postMessage() calls on the JavaScript object.
758      * @throws IllegalArgumentException If one of the {@code allowedOriginRules} is invalid.
759      * @see JavaScriptReplyProxy
760      * @see WebMessageListener
761      */
762     // UI thread not currently enforced, but required
763     @UiThread
764     @RequiresFeature(name = WebViewFeature.WEB_MESSAGE_LISTENER,
765             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
addWebMessageListener(@onNull WebView webView, @NonNull String jsObjectName, @NonNull Set<String> allowedOriginRules, @NonNull WebMessageListener listener)766     public static void addWebMessageListener(@NonNull WebView webView, @NonNull String jsObjectName,
767             @NonNull Set<String> allowedOriginRules, @NonNull WebMessageListener listener) {
768         final ApiFeature.NoFramework feature = WebViewFeatureInternal.WEB_MESSAGE_LISTENER;
769         if (feature.isSupportedByWebView()) {
770             getProvider(webView).addWebMessageListener(
771                     jsObjectName, allowedOriginRules.toArray(new String[0]), listener);
772         } else {
773             throw WebViewFeatureInternal.getUnsupportedOperationException();
774         }
775     }
776 
777     /**
778      * Removes the {@link WebMessageListener WebMessageListener} associated with {@code
779      * jsObjectName}.
780      *
781      * <p>
782      * Note that after this call, the injected JavaScript object is still in the JavaScript context,
783      * however any message sent after this call won't reach the {@link WebMessageListener
784      * WebMessageListener}.
785      *
786      * <p>
787      * This method should only be called if {@link WebViewFeature#isFeatureSupported(String)}
788      * returns true for {@link WebViewFeature#WEB_MESSAGE_LISTENER}.
789      *
790      * @param webview      The WebView object to remove from.
791      * @param jsObjectName The JavaScript object's name that was previously passed to {@link
792      *                     #addWebMessageListener(WebView, String, Set, WebMessageListener)}.
793      * @see #addWebMessageListener(WebView, String, Set, WebMessageListener)
794      */
795     // UI thread not currently enforced, but required
796     @UiThread
797     @RequiresFeature(name = WebViewFeature.WEB_MESSAGE_LISTENER,
798             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
removeWebMessageListener( @onNull WebView webview, @NonNull String jsObjectName)799     public static void removeWebMessageListener(
800             @NonNull WebView webview, @NonNull String jsObjectName) {
801         final ApiFeature.NoFramework feature = WebViewFeatureInternal.WEB_MESSAGE_LISTENER;
802         if (feature.isSupportedByWebView()) {
803             getProvider(webview).removeWebMessageListener(jsObjectName);
804         } else {
805             throw WebViewFeatureInternal.getUnsupportedOperationException();
806         }
807     }
808 
809     /**
810      * Adds a JavaScript script to the {@link WebView} which will be executed in any frame whose
811      * origin matches {@code allowedOriginRules} when the document begins to load.
812      *
813      * <p>Note that the script will run before any of the page's JavaScript code and the DOM tree
814      * might not be ready at this moment. It will block the loading of the page until it's finished,
815      * so should be kept as short as possible.
816      *
817      * <p>The injected object from {@link #addWebMessageListener(WebView, String, Set,
818      * WebMessageListener)} API will be injected first and the script can rely on the injected
819      * object to send messages to the app.
820      *
821      * <p>The script will only run in frames which begin loading after the call returns, therefore
822      * it should typically be called before making any {@code loadUrl()}, {@code loadData()} or
823      * {@code loadDataWithBaseURL()} call to load the page.
824      *
825      * <p>This method can be called multiple times to inject multiple scripts. If more than one
826      * script matches a frame's origin, they will be executed in the order they were added.
827      *
828      * <p>See {@link #addWebMessageListener(WebView, String, Set, WebMessageListener)} for the rules
829      * of the {@code allowedOriginRules} parameter.
830      *
831      * <p>This method should only be called if {@link WebViewFeature#isFeatureSupported(String)}
832      * returns true for {@link WebViewFeature#DOCUMENT_START_SCRIPT}.
833      *
834      * @param webview            The {@link WebView} instance that we are interacting with.
835      * @param script             The JavaScript script to be executed.
836      * @param allowedOriginRules A set of matching rules for the allowed origins.
837      * @return the {@link ScriptHandler}, which is a handle for removing the script.
838      * @throws IllegalArgumentException If one of the {@code allowedOriginRules} is invalid.
839      * @see #addWebMessageListener(WebView, String, Set, WebMessageListener)
840      * @see ScriptHandler
841      */
842     // UI thread not currently enforced, but required
843     @UiThread
844     @RequiresFeature(
845             name = WebViewFeature.DOCUMENT_START_SCRIPT,
846             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
addDocumentStartJavaScript( @onNull WebView webview, @NonNull String script, @NonNull Set<String> allowedOriginRules)847     public static @NonNull ScriptHandler addDocumentStartJavaScript(
848             @NonNull WebView webview,
849             @NonNull String script,
850             @NonNull Set<String> allowedOriginRules) {
851         final ApiFeature.NoFramework feature = WebViewFeatureInternal.DOCUMENT_START_SCRIPT;
852         if (feature.isSupportedByWebView()) {
853             return getProvider(webview)
854                     .addDocumentStartJavaScript(script, allowedOriginRules.toArray(new String[0]));
855         } else {
856             throw WebViewFeatureInternal.getUnsupportedOperationException();
857         }
858     }
859 
860     /**
861      * Gets the WebViewClient for the WebView argument.
862      *
863      * <p>
864      * This method should only be called if
865      * {@link WebViewFeature#isFeatureSupported(String)}
866      * returns true for {@link WebViewFeature#GET_WEB_VIEW_CLIENT}.
867      *
868      * @return the WebViewClient, or a default client if not yet set
869      */
870     @UiThread
871     @RequiresFeature(name = WebViewFeature.GET_WEB_VIEW_CLIENT,
872             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
getWebViewClient(@onNull WebView webview)873     public static @NonNull WebViewClient getWebViewClient(@NonNull WebView webview) {
874         final ApiFeature.O feature = WebViewFeatureInternal.GET_WEB_VIEW_CLIENT;
875         if (feature.isSupportedByFramework()) {
876             return ApiHelperForO.getWebViewClient(webview);
877         } else if (feature.isSupportedByWebView()) {
878             checkThread(webview);
879             return getProvider(webview).getWebViewClient();
880         } else {
881             throw WebViewFeatureInternal.getUnsupportedOperationException();
882         }
883     }
884 
885     /**
886      * Gets the WebChromeClient.
887      *
888      * <p>
889      * This method should only be called if
890      * {@link WebViewFeature#isFeatureSupported(String)}
891      * returns true for {@link WebViewFeature#GET_WEB_CHROME_CLIENT}.
892      *
893      * @return the WebChromeClient, or {@code null} if not yet set
894      */
895     @UiThread
896     @RequiresFeature(name = WebViewFeature.GET_WEB_CHROME_CLIENT,
897             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
getWebChromeClient(@onNull WebView webview)898     public static @Nullable WebChromeClient getWebChromeClient(@NonNull WebView webview) {
899         final ApiFeature.O feature = WebViewFeatureInternal.GET_WEB_CHROME_CLIENT;
900         if (feature.isSupportedByFramework()) {
901             return ApiHelperForO.getWebChromeClient(webview);
902         } else if (feature.isSupportedByWebView()) {
903             checkThread(webview);
904             return getProvider(webview).getWebChromeClient();
905         } else {
906             throw WebViewFeatureInternal.getUnsupportedOperationException();
907         }
908     }
909 
910     /**
911      * Gets the WebView renderer associated with this WebView.
912      *
913      * <p>In Android O and above, WebView may run in "multiprocess"
914      * mode. In multiprocess mode, rendering of web content is performed by
915      * a sandboxed renderer process separate to the application process.
916      * This renderer process may be shared with other WebViews in the
917      * application, but is not shared with other application processes.
918      *
919      * <p>If WebView is running in multiprocess mode, this method returns a
920      * handle to the renderer process associated with the WebView, which can
921      * be used to control the renderer process.
922      *
923      * <p>This method should only be called if
924      * {@link WebViewFeature#isFeatureSupported(String)}
925      * returns true for {@link WebViewFeature#GET_WEB_VIEW_RENDERER}.
926      *
927      * @return the {@link WebViewRenderProcess} renderer handle associated
928      * with this {@link android.webkit.WebView}, or {@code null} if
929      * WebView is not running in multiprocess mode.
930      */
931     @UiThread
932     @RequiresFeature(name = WebViewFeature.GET_WEB_VIEW_RENDERER,
933             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
getWebViewRenderProcess(@onNull WebView webview)934     public static @Nullable WebViewRenderProcess getWebViewRenderProcess(@NonNull WebView webview) {
935         final ApiFeature.Q feature = WebViewFeatureInternal.GET_WEB_VIEW_RENDERER;
936         if (feature.isSupportedByFramework()) {
937             android.webkit.WebViewRenderProcess renderer = ApiHelperForQ.getWebViewRenderProcess(
938                     webview);
939             return renderer != null ? WebViewRenderProcessImpl.forFrameworkObject(renderer) : null;
940         } else if (feature.isSupportedByWebView()) {
941             checkThread(webview);
942             return getProvider(webview).getWebViewRenderProcess();
943         } else {
944             throw WebViewFeatureInternal.getUnsupportedOperationException();
945         }
946     }
947 
948     /**
949      * Sets the renderer client object associated with this WebView.
950      *
951      * <p>The renderer client encapsulates callbacks relevant to WebView renderer
952      * state. See {@link WebViewRenderProcessClient} for details.
953      *
954      * <p>Although many WebView instances may share a single underlying renderer, and renderers may
955      * live either in the application process, or in a sandboxed process that is isolated from
956      * the application process, instances of {@link WebViewRenderProcessClient} are set per-WebView.
957      * Callbacks represent renderer events from the perspective of this WebView, and may or may
958      * not be correlated with renderer events affecting other WebViews.
959      *
960      * <p>This method should only be called if
961      * {@link WebViewFeature#isFeatureSupported(String)}
962      * returns true for {@link WebViewFeature#WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE}.
963      *
964      * @param webview                    the {@link WebView} on which to monitor responsiveness.
965      * @param executor                   the {@link Executor} that will be used to execute
966      *                                   callbacks.
967      * @param webViewRenderProcessClient the {@link WebViewRenderProcessClient} to set for
968      *                                   callbacks.
969      */
970     // WebViewRenderProcessClient is a callback class, so it should be last. See
971     // https://issuetracker.google.com/issues/139770271.
972     @SuppressLint("LambdaLast")
973     @UiThread
974     @RequiresFeature(name = WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
975             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
setWebViewRenderProcessClient( @onNull WebView webview, @NonNull Executor executor, @NonNull WebViewRenderProcessClient webViewRenderProcessClient)976     public static void setWebViewRenderProcessClient(
977             @NonNull WebView webview,
978             /* @CallbackExecutor */ @NonNull Executor executor,
979             @NonNull WebViewRenderProcessClient webViewRenderProcessClient) {
980         final ApiFeature.Q feature =
981                 WebViewFeatureInternal.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE;
982         if (feature.isSupportedByFramework()) {
983             ApiHelperForQ.setWebViewRenderProcessClient(webview, executor,
984                     webViewRenderProcessClient);
985         } else if (feature.isSupportedByWebView()) {
986             checkThread(webview);
987             getProvider(webview).setWebViewRenderProcessClient(
988                     executor, webViewRenderProcessClient);
989         } else {
990             throw WebViewFeatureInternal.getUnsupportedOperationException();
991         }
992     }
993 
994     /**
995      * Sets the renderer client object associated with this WebView.
996      *
997      * <p>See
998      * {@link WebViewCompat#setWebViewRenderProcessClient(WebView, Executor, WebViewRenderProcessClient)} for
999      * details, with the following differences:
1000      *
1001      * <p>Callbacks will execute directly on the thread on which this WebView was instantiated.
1002      *
1003      * <p>Passing {@code null} for {@code webViewRenderProcessClient} will clear the renderer client
1004      * object for this WebView.
1005      *
1006      * <p>This method should only be called if
1007      * {@link WebViewFeature#isFeatureSupported(String)}
1008      * returns true for {@link WebViewFeature#WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE}.
1009      *
1010      * @param webview                    the {@link WebView} on which to monitor responsiveness.
1011      * @param webViewRenderProcessClient the {@link WebViewRenderProcessClient} to set for
1012      *                                   callbacks.
1013      */
1014     @UiThread
1015     @RequiresFeature(name = WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
1016             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
setWebViewRenderProcessClient( @onNull WebView webview, @Nullable WebViewRenderProcessClient webViewRenderProcessClient)1017     public static void setWebViewRenderProcessClient(
1018             @NonNull WebView webview,
1019             @Nullable WebViewRenderProcessClient webViewRenderProcessClient) {
1020         final ApiFeature.Q feature =
1021                 WebViewFeatureInternal.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE;
1022         if (feature.isSupportedByFramework()) {
1023             ApiHelperForQ.setWebViewRenderProcessClient(webview, webViewRenderProcessClient);
1024         } else if (feature.isSupportedByWebView()) {
1025             checkThread(webview);
1026             getProvider(webview).setWebViewRenderProcessClient(null, webViewRenderProcessClient);
1027         } else {
1028             throw WebViewFeatureInternal.getUnsupportedOperationException();
1029         }
1030     }
1031 
1032     /**
1033      * Gets the renderer client object associated with this WebView.
1034      *
1035      * <p>This method should only be called if
1036      * {@link WebViewFeature#isFeatureSupported(String)}
1037      * returns true for {@link WebViewFeature#WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE}.
1038      *
1039      * @return the {@link WebViewRenderProcessClient} object associated with this WebView, if
1040      * one has been set via
1041      * {@link #setWebViewRenderProcessClient(WebView, WebViewRenderProcessClient)} or {@code null}
1042      * otherwise.
1043      */
1044     @UiThread
1045     @RequiresFeature(name = WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
1046             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
getWebViewRenderProcessClient( @onNull WebView webview)1047     public static @Nullable WebViewRenderProcessClient getWebViewRenderProcessClient(
1048             @NonNull WebView webview) {
1049         final ApiFeature.Q feature =
1050                 WebViewFeatureInternal.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE;
1051         if (feature.isSupportedByFramework()) {
1052             android.webkit.WebViewRenderProcessClient renderer =
1053                     ApiHelperForQ.getWebViewRenderProcessClient(webview);
1054             if (renderer == null
1055                     || !(renderer instanceof WebViewRenderProcessClientFrameworkAdapter)) {
1056                 return null;
1057             }
1058             return ((WebViewRenderProcessClientFrameworkAdapter) renderer)
1059                     .getFrameworkRenderProcessClient();
1060         } else if (feature.isSupportedByWebView()) {
1061             checkThread(webview);
1062             return getProvider(webview).getWebViewRenderProcessClient();
1063         } else {
1064             throw WebViewFeatureInternal.getUnsupportedOperationException();
1065         }
1066     }
1067 
1068     /**
1069      * Returns true if {@link WebView} is running in multi process mode.
1070      *
1071      * <p>In Android O and above, WebView may run in "multiprocess"
1072      * mode. In multiprocess mode, rendering of web content is performed by
1073      * a sandboxed renderer process separate to the application process.
1074      * This renderer process may be shared with other WebViews in the
1075      * application, but is not shared with other application processes.
1076      */
1077     @AnyThread
1078     @RequiresFeature(name = WebViewFeature.MULTI_PROCESS,
1079             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
isMultiProcessEnabled()1080     public static boolean isMultiProcessEnabled() {
1081         final ApiFeature.NoFramework feature = WebViewFeatureInternal.MULTI_PROCESS;
1082         if (feature.isSupportedByWebView()) {
1083             return getFactory().getStatics().isMultiProcessEnabled();
1084         } else {
1085             throw WebViewFeatureInternal.getUnsupportedOperationException();
1086         }
1087     }
1088 
1089     /**
1090      * Gets the WebView variations encoded to be used as the X-Client-Data HTTP header.
1091      *
1092      * <p>The app is responsible for adding the X-Client-Data header to any request that may use
1093      * variations metadata, such as requests to Google web properties. The returned string will be a
1094      * base64 encoded ClientVariations proto:
1095      * <a href="https://source.chromium.org/chromium/chromium/src/+/main:components/variations/proto/client_variations.proto">
1096      * https://source.chromium.org/chromium/chromium/src/+/main:components/variations/proto
1097      * /client_variations.proto</a>
1098      *
1099      * @return the variations header. The string may be empty if the header is not available.
1100      * @see WebView#loadUrl(String, java.util.Map)
1101      */
1102     @AnyThread
1103     @RequiresFeature(
1104             name = WebViewFeature.GET_VARIATIONS_HEADER,
1105             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
getVariationsHeader()1106     public static @NonNull String getVariationsHeader() {
1107         final ApiFeature.NoFramework feature = WebViewFeatureInternal.GET_VARIATIONS_HEADER;
1108         if (feature.isSupportedByWebView()) {
1109             return getFactory().getStatics().getVariationsHeader();
1110         } else {
1111             throw WebViewFeatureInternal.getUnsupportedOperationException();
1112         }
1113     }
1114 
1115     /**
1116      * Sets the Profile with its name as the current Profile for this WebView.
1117      * <ul>
1118      * <li> This should be called before doing anything else with WebView other than attaching it to
1119      * the view hierarchy.
1120      * <li> This should be only called if WebView is to use a Profile other than the default.
1121      * <li> This method will create the profile if it doesn't exist.
1122      * </ul>
1123      *
1124      * @param webView     the WebView to modify.
1125      * @param profileName the name of the profile to use in the passed {@code webView}.
1126      * @throws IllegalStateException if the WebView has been destroyed.
1127      * @throws IllegalStateException if the previous profile has been accessed via a call to
1128      *                               {@link WebViewCompat#getProfile(WebView)}.
1129      * @throws IllegalStateException if the profile has already been set previously via this method.
1130      * @throws IllegalStateException if {@link WebView#evaluateJavascript(String, ValueCallback)} is
1131      *                               called on the WebView before this method.
1132      * @throws IllegalStateException if the WebView has previously navigated to a web page.
1133      */
1134     @UiThread
1135     @RequiresFeature(
1136             name = WebViewFeature.MULTI_PROFILE,
1137             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
setProfile(@onNull WebView webView, @NonNull String profileName)1138     public static void setProfile(@NonNull WebView webView,
1139             @NonNull String profileName) {
1140         final ApiFeature.NoFramework feature = WebViewFeatureInternal.MULTI_PROFILE;
1141         if (feature.isSupportedByWebView()) {
1142             getProvider(webView).setProfileWithName(profileName);
1143         } else {
1144             throw WebViewFeatureInternal.getUnsupportedOperationException();
1145         }
1146     }
1147 
1148     /**
1149      * Gets the Profile associated with this WebView.
1150      * <p>
1151      * Gets the profile object set on this WebView using
1152      * {@link WebViewCompat#setProfile(WebView, String)}, or the default profile if it has not
1153      * been changed.
1154      *
1155      * @param webView the WebView to get the profile object associated with.
1156      * @return the profile object set to this WebView.
1157      * @throws IllegalStateException if the WebView has been destroyed.
1158      */
1159     @UiThread
1160     @RequiresFeature(
1161             name = WebViewFeature.MULTI_PROFILE,
1162             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
getProfile(@onNull WebView webView)1163     public static @NonNull Profile getProfile(@NonNull WebView webView) {
1164         final ApiFeature.NoFramework feature = WebViewFeatureInternal.MULTI_PROFILE;
1165         if (feature.isSupportedByWebView()) {
1166             return getProvider(webView).getProfile();
1167         } else {
1168             throw WebViewFeatureInternal.getUnsupportedOperationException();
1169         }
1170     }
1171 
1172     /**
1173      * Returns whether this WebView is muted.
1174      *
1175      * @param webView the WebView for which to check mute status.
1176      * @return true if the WebView is muted, false otherwise.
1177      */
1178     // UI thread not currently enforced, but required
1179     @UiThread
1180     @RequiresFeature(name = WebViewFeature.MUTE_AUDIO,
1181             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
isAudioMuted(@onNull WebView webView)1182     public static boolean isAudioMuted(@NonNull WebView webView) {
1183         final ApiFeature.NoFramework feature = WebViewFeatureInternal.MUTE_AUDIO;
1184         if (feature.isSupportedByWebView()) {
1185             return getProvider(webView).isAudioMuted();
1186         } else {
1187             throw WebViewFeatureInternal.getUnsupportedOperationException();
1188         }
1189     }
1190 
1191     /**
1192      * Mute or un-mute this WebView.
1193      *
1194      * @param webView the WebView for which to control muting.
1195      * @param mute    true to mute the WebView; false to un-mute the WebView.
1196      */
1197     // UI thread not currently enforced, but required
1198     @UiThread
1199     @RequiresFeature(name = WebViewFeature.MUTE_AUDIO,
1200             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
setAudioMuted(@onNull WebView webView, boolean mute)1201     public static void setAudioMuted(@NonNull WebView webView, boolean mute) {
1202         final ApiFeature.NoFramework feature = WebViewFeatureInternal.MUTE_AUDIO;
1203         if (feature.isSupportedByWebView()) {
1204             getProvider(webView).setAudioMuted(mute);
1205         } else {
1206             throw WebViewFeatureInternal.getUnsupportedOperationException();
1207         }
1208     }
1209 
1210     /**
1211      * Denotes that the startUpWebView API surface is experimental.
1212      * <p>
1213      * It may change without warning and should not be relied upon for non-experimental purposes.
1214      */
1215     @Retention(RetentionPolicy.CLASS)
1216     @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
1217     @RequiresOptIn(level = RequiresOptIn.Level.ERROR)
1218     public @interface ExperimentalAsyncStartUp {
1219     }
1220 
1221     /**
1222      * Callback interface for
1223      * {@link WebViewCompat#startUpWebView(WebViewStartUpConfig, WebViewStartUpCallback)}.
1224      */
1225     @ExperimentalAsyncStartUp
1226     public interface WebViewStartUpCallback {
1227         /**
1228          * Called when WebView startup completes successfully.
1229          *
1230          * @param result The async startup result.
1231          */
onSuccess(@onNull WebViewStartUpResult result)1232         void onSuccess(@NonNull WebViewStartUpResult result);
1233     }
1234 
1235     /**
1236      * Asynchronously trigger WebView startup.
1237      * <p>
1238      * WebView startup is a time-consuming process that is normally triggered during the first
1239      * usage of WebView related APIs. WebView startup happens once per process.
1240      * For example, the first call to {@code new WebView()} can take longer to
1241      * complete than future calls due to WebView startup being triggered. The Android
1242      * UI thread remains blocked till the startup completes.
1243      * <p>
1244      * This method allows callers to trigger WebView startup at a time of their choosing.
1245      * <p>
1246      * There are performance improvements this API provides.
1247      * This method ensures that the portions of WebView startup which are able to run in the
1248      * background will do so. Other portions of startup will still run on the UI thread.
1249      * <p>
1250      * Any APIs in {@code android.webkit} and {@code androidx.webkit} (including
1251      * {@link WebViewFeature}) MUST only be called after the callback is invoked in order to
1252      * ensure the maximum benefit.
1253      * There is no feature check or call to {@link WebViewFeature} required for using this method.
1254      * <p>
1255      * This API can be called multiple times. The callback will be called promptly if startup
1256      * has already completed.
1257      * <p>
1258      * This is an experimental API and unsuitable for non-experimental use.
1259      * This method can be removed in future versions of the library.
1260      *
1261      * @param config   configuration for startup.
1262      * @param callback the callback triggered when WebView startup is complete. This will be called
1263      *                 on the main looper (Looper.getMainLooper()).
1264      */
1265     @ExperimentalAsyncStartUp
1266     @AnyThread
startUpWebView( @onNull WebViewStartUpConfig config, @NonNull WebViewStartUpCallback callback)1267     public static void startUpWebView(
1268             @NonNull WebViewStartUpConfig config, @NonNull WebViewStartUpCallback callback) {
1269         config.getBackgroundExecutor().execute(() -> {
1270             // Invoke provider init.
1271             WebViewGlueCommunicator.getWebViewClassLoader();
1272             if (WebViewFeatureInternal.ASYNC_WEBVIEW_STARTUP.isSupportedByWebView()) {
1273                 // We want to ensure that the callback is run on the Android main looper. The callee
1274                 // doesn't guarantee this. It's also desirable to post it to make sure that we don't
1275                 // run the app's callback synchronously from inside startChromiumLocked:
1276                 // - This helps avoid making the blocking task longer.
1277                 // - If the app's callback has a problem the stack trace will hopefully make it
1278                 // clearer that it's not WebView's fault since WebView code will not be in the
1279                 // stack trace.
1280                 getFactory().startUpWebView(config, (result) -> {
1281                     new Handler(Looper.getMainLooper()).post(() -> callback.onSuccess(result));
1282                 });
1283                 return;
1284             }
1285             if (config.shouldRunUiThreadStartUpTasks()) {
1286                 // We never access the context in Chromium-based WebView and `startUpWebView` will
1287                 // only be called on Android API versions where the WebView is Chromium-based, so
1288                 // passing `null`.
1289                 // This method implicitly does WebView startup.
1290                 WebSettings.getDefaultUserAgent(null);
1291             } else {
1292                 // On versions of WebView without the underlying support for the API the only part
1293                 // of startup we can do without blocking the UI thread already happened during
1294                 // `getWebViewClassLoader` above and so there's nothing more to do.
1295             }
1296             // Trigger the callback from the main looper.
1297             // The framework doesn't support providing any diagnostic information, therefore,
1298             // returning `null` for every method.
1299             new Handler(Looper.getMainLooper()).post(
1300                     () -> callback.onSuccess(new NullReturningWebViewStartUpResult()));
1301         });
1302     }
1303 
1304     /**
1305      * Sets the default {@link android.net.TrafficStats} tag to use when accounting socket traffic
1306      * caused by WebView. If set, this tag is global for all requests sent by the WebView library
1307      * within your app.
1308      *
1309      * <p>If no tag is set (e.g. this method isn't called), then Android accounts for the socket
1310      * traffic caused by WebView as if the tag value were set to 0. See
1311      * {@link android.net.TrafficStats#setThreadStatsTag(int)} for more information.
1312      *
1313      * <p><b>NOTE</b>: Setting a tag disallows sharing of sockets with requests with other tags,
1314      * which may adversely affect performance by prohibiting connection sharing. In other words, use
1315      * of multiplexed sockets (e.g. HTTP/2 and QUIC) will only be allowed if all requests have
1316      * the same socket tag. To minimize impact, you should not change the value of this tag often.
1317      *
1318      * @param tag the tag value used when accounting for socket traffic caused by the WebView
1319      *            library in your app. <em>Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF}
1320      *            are reserved and used internally by system services like
1321      *            {@link android.app.DownloadManager} when performing traffic on behalf of an
1322      *            application</em>.
1323      */
1324     @AnyThread
1325     @RequiresFeature(name = WebViewFeature.DEFAULT_TRAFFICSTATS_TAGGING,
1326             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
setDefaultTrafficStatsTag(int tag)1327     public static void setDefaultTrafficStatsTag(int tag) {
1328         final ApiFeature.NoFramework feature = WebViewFeatureInternal.DEFAULT_TRAFFICSTATS_TAGGING;
1329         if (feature.isSupportedByWebView()) {
1330             getFactory().getStatics().setDefaultTrafficStatsTag(tag);
1331         } else {
1332             throw WebViewFeatureInternal.getUnsupportedOperationException();
1333         }
1334     }
1335 
1336     private static class NullReturningWebViewStartUpResult implements WebViewStartUpResult {
1337         @Override
getTotalTimeInUiThreadMillis()1338         public Long getTotalTimeInUiThreadMillis() {
1339             return null;
1340         }
1341 
1342         @Override
getMaxTimePerTaskInUiThreadMillis()1343         public Long getMaxTimePerTaskInUiThreadMillis() {
1344             return null;
1345         }
1346 
1347         @Override
getBlockingStartUpLocations()1348         public List<BlockingStartUpLocation> getBlockingStartUpLocations() {
1349             return null;
1350         }
1351     }
1352 
1353     /**
1354      * Denotes that the PrerenderUrl API surface is experimental.
1355      * <p>
1356      * It may change without warning and should not be relied upon for non-experimental purposes.
1357      */
1358     @Retention(RetentionPolicy.CLASS)
1359     @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
1360     @RequiresOptIn(level = RequiresOptIn.Level.ERROR)
1361     public @interface ExperimentalUrlPrerender {
1362     }
1363 
1364     /**
1365      * Starts a URL prerender request for this WebView. Must be called from the UI thread.
1366      * <p>
1367      * This WebView will use a URL request matching algorithm during execution
1368      * of all variants of {@link android.webkit.WebView#loadUrl(String)} for
1369      * determining if there was a prerender request executed for the
1370      * provided URL. This includes prerender requests that are "in progress".
1371      * If a prerender request is matched, WebView will leverage that for
1372      * handling the URL, otherwise the URL will be handled normally (i.e.
1373      * through a network request).
1374      * <p>
1375      * Applications will still be responsible for calling
1376      * {@link android.webkit.WebView#loadUrl(String)} to display web contents
1377      * in a WebView.
1378      * <p>
1379      * A prerendered page can also match a navigation initiated by clicking a
1380      * hyperlink.
1381      * <p>
1382      * Only supports HTTPS scheme.
1383      * <p>
1384      * The {@link CancellationSignal} will make the best effort to cancel an
1385      * in-flight prerender request; however cancellation it is not guaranteed.
1386      *
1387      * @param webView            the WebView for which we trigger the prerender request.
1388      * @param url                the url associated with the prerender request.
1389      * @param cancellationSignal used to trigger prerender cancellation.
1390      * @param callbackExecutor   the executor to resolve the callback with.
1391      * @param callback           callbacks for reporting result back to application.
1392      */
1393     @RequiresFeature(name = WebViewFeature.PRERENDER_WITH_URL,
1394             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
1395     @UiThread
1396     @ExperimentalUrlPrerender
prerenderUrlAsync( @onNull WebView webView, @NonNull String url, @Nullable CancellationSignal cancellationSignal, @NonNull Executor callbackExecutor, @NonNull PrerenderOperationCallback callback)1397     public static void prerenderUrlAsync(
1398             @NonNull WebView webView,
1399             @NonNull String url,
1400             @Nullable CancellationSignal cancellationSignal,
1401             @NonNull Executor callbackExecutor,
1402             @NonNull PrerenderOperationCallback callback) {
1403         ApiFeature.NoFramework feature = WebViewFeatureInternal.PRERENDER_WITH_URL;
1404         if (feature.isSupportedByWebView()) {
1405             getProvider(webView).prerenderUrlAsync(url, cancellationSignal, callbackExecutor,
1406                     callback);
1407         } else {
1408             throw WebViewFeatureInternal.getUnsupportedOperationException();
1409         }
1410     }
1411 
1412     /**
1413      * The same as
1414      * {@link WebViewCompat#prerenderUrlAsync(WebView, String, CancellationSignal, Executor, PrerenderOperationCallback)},
1415      * but allows customizing the request by providing {@link SpeculativeLoadingParameters}.
1416      *
1417      * @param webView            the WebView for which we trigger the prerender request.
1418      * @param url                the url associated with the prerender request.
1419      * @param cancellationSignal used to trigger prerender cancellation.
1420      * @param callbackExecutor   the executor to resolve the callback with.
1421      * @param params             parameters to customize the prerender request.
1422      * @param callback           callbacks for reporting result back to application.
1423      */
1424     @RequiresFeature(name = WebViewFeature.PRERENDER_WITH_URL,
1425             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
1426     @UiThread
1427     @ExperimentalUrlPrerender
prerenderUrlAsync( @onNull WebView webView, @NonNull String url, @Nullable CancellationSignal cancellationSignal, @NonNull Executor callbackExecutor, @NonNull SpeculativeLoadingParameters params, @NonNull PrerenderOperationCallback callback)1428     public static void prerenderUrlAsync(
1429             @NonNull WebView webView,
1430             @NonNull String url,
1431             @Nullable CancellationSignal cancellationSignal,
1432             @NonNull Executor callbackExecutor,
1433             @NonNull SpeculativeLoadingParameters params,
1434             @NonNull PrerenderOperationCallback callback) {
1435         ApiFeature.NoFramework feature = WebViewFeatureInternal.PRERENDER_WITH_URL;
1436         if (feature.isSupportedByWebView()) {
1437             getProvider(webView).prerenderUrlAsync(url, cancellationSignal, callbackExecutor,
1438                     params,
1439                     callback);
1440         } else {
1441             throw WebViewFeatureInternal.getUnsupportedOperationException();
1442         }
1443     }
1444 
1445     /**
1446      * Denotes that the WebViewCompat#saveState API surface is experimental.
1447      * <p>
1448      * It may change without warning and should not be relied upon for non-experimental purposes.
1449      */
1450     @Retention(RetentionPolicy.CLASS)
1451     @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
1452     @RequiresOptIn(level = RequiresOptIn.Level.ERROR)
1453     public @interface ExperimentalSaveState {
1454     }
1455 
1456     /**
1457      * Saves the state of the provided WebView, such as for use with
1458      * {@link android.app.Activity#onSaveInstanceState}. This is an extension of
1459      * {@link WebView#saveState(Bundle)} and the returned state can be restored through
1460      * {@link WebView#restoreState(Bundle)}.
1461      *
1462      * @param webView             the {@link WebView} whose state is to be saved.
1463      * @param outState            the {@link Bundle} to store the state in.
1464      * @param maxSizeBytes        the maximum size (in bytes) that the returned state can be. If the
1465      *                            WebView contains more state, history entries further back will
1466      *                            not be
1467      *                            saved.
1468      * @param includeForwardState whether to include entries that can only be reached through going
1469      *                            forward in history (such as through {@link WebView#goForward()}.
1470      *                            Some apps don't give the user a way to go forward, so won't need
1471      *                            to save the forward history.
1472      */
1473     @RequiresFeature(name = WebViewFeature.SAVE_STATE,
1474             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
1475     @UiThread
1476     @ExperimentalSaveState
saveState(@onNull WebView webView, @NonNull Bundle outState, @IntRange(from = 1) int maxSizeBytes, boolean includeForwardState)1477     public static void saveState(@NonNull WebView webView,
1478             @NonNull Bundle outState,
1479             @IntRange(from = 1) int maxSizeBytes,
1480             boolean includeForwardState) {
1481         ApiFeature.NoFramework feature = WebViewFeatureInternal.SAVE_STATE;
1482         if (feature.isSupportedByWebView()) {
1483             getProvider(webView).saveState(outState, maxSizeBytes, includeForwardState);
1484         } else {
1485             throw WebViewFeatureInternal.getUnsupportedOperationException();
1486         }
1487     }
1488 
1489     /**
1490      * Denotes that the WebViewCompat#setShouldCacheProvider API surface is experimental.
1491      * <p>
1492      * It may change without warning and should not be relied upon for non-experimental purposes.
1493      */
1494     @Retention(RetentionPolicy.CLASS)
1495     @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
1496     @RequiresOptIn(level = RequiresOptIn.Level.ERROR)
1497     public @interface ExperimentalCacheProvider {
1498     }
1499 
1500     /**
1501      * Enables or disables caching of WebView provider objects (objects internal to the
1502      * androidx.webkit library). Caching should have no effect on behavior but will improve
1503      * performance.
1504      *
1505      * @param shouldCacheProvider whether to enable caching of WebView provider objects.
1506      */
1507     @RequiresFeature(name = WebViewFeature.CACHE_PROVIDER,
1508             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
1509     @AnyThread
1510     @ExperimentalCacheProvider
setShouldCacheProvider(boolean shouldCacheProvider)1511     public static void setShouldCacheProvider(boolean shouldCacheProvider) {
1512         ApiFeature.NoFramework feature = WebViewFeatureInternal.CACHE_PROVIDER;
1513         if (feature.isSupportedByWebView()) {
1514             sShouldCacheProvider = shouldCacheProvider;
1515         } else {
1516             throw WebViewFeatureInternal.getUnsupportedOperationException();
1517         }
1518     }
1519 
1520     /**
1521      * Sets the {@link WebNavigationClient} for the given {@link WebView}.
1522      *
1523      * @param webView The {@link WebView} to set the client for.
1524      * @param client  The {@link WebNavigationClient} to set.
1525      * @throws UnsupportedOperationException if the
1526      *                                       {@link WebViewFeature#NAVIGATION_CALLBACK_BASIC}
1527      *                                       feature is not supported.
1528      */
1529     @RequiresFeature(name = WebViewFeature.NAVIGATION_CALLBACK_BASIC,
1530             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
1531     @UiThread
1532     @WebNavigationClient.ExperimentalNavigationCallback
setWebNavigationClient(@onNull WebView webView, @NonNull WebNavigationClient client)1533     public static void setWebNavigationClient(@NonNull WebView webView,
1534             @NonNull WebNavigationClient client) {
1535         ApiFeature.NoFramework feature = WebViewFeatureInternal.NAVIGATION_CALLBACK_BASIC;
1536         if (feature.isSupportedByWebView()) {
1537             getProvider(webView).setWebNavigationClient(client);
1538         } else {
1539             throw WebViewFeatureInternal.getUnsupportedOperationException();
1540         }
1541     }
1542 
1543     /**
1544      * Gets the {@link WebNavigationClient} currently set for the given {@link WebView}.
1545      *
1546      * @param webView The {@link WebView} to get the client from.
1547      * @return The {@link WebNavigationClient} currently set, or {@code null} if none is set.
1548      * @throws UnsupportedOperationException if the
1549      *                                       {@link WebViewFeature#NAVIGATION_CALLBACK_BASIC}
1550      *                                       feature is not supported.
1551      */
1552     @RequiresFeature(name = WebViewFeature.NAVIGATION_CALLBACK_BASIC,
1553             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
1554     @UiThread
1555     @WebNavigationClient.ExperimentalNavigationCallback
getWebNavigationClient(@onNull WebView webView)1556     public static @NonNull WebNavigationClient getWebNavigationClient(@NonNull WebView webView) {
1557         ApiFeature.NoFramework feature = WebViewFeatureInternal.NAVIGATION_CALLBACK_BASIC;
1558         if (feature.isSupportedByWebView()) {
1559             return getProvider(webView).getWebNavigationClient();
1560         } else {
1561             throw WebViewFeatureInternal.getUnsupportedOperationException();
1562         }
1563     }
1564 
getFactory()1565     private static WebViewProviderFactory getFactory() {
1566         return WebViewGlueCommunicator.getFactory();
1567     }
1568 
createProvider(WebView webview)1569     private static WebViewProviderBoundaryInterface createProvider(WebView webview) {
1570         return getFactory().createWebView(webview);
1571     }
1572 
1573     @SuppressWarnings({"JavaReflectionMemberAccess", "PrivateApi"})
checkThread(WebView webview)1574     private static void checkThread(WebView webview) {
1575         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1576             Looper webViewLooper = ApiHelperForP.getWebViewLooper(webview);
1577             if (webViewLooper != Looper.myLooper()) {
1578                 throw new RuntimeException("A WebView method was called on thread '"
1579                         + Thread.currentThread().getName() + "'. "
1580                         + "All WebView methods must be called on the same thread. "
1581                         + "(Expected Looper " + webViewLooper + " called on "
1582                         + Looper.myLooper() + ", FYI main Looper is " + Looper.getMainLooper()
1583                         + ")");
1584             }
1585         } else {
1586             try {
1587                 Method checkThreadMethod = WebView.class.getDeclaredMethod("checkThread");
1588                 checkThreadMethod.setAccessible(true);
1589                 // WebView.checkThread() performs some logging and potentially throws an exception
1590                 // if WebView is used on the wrong thread.
1591                 checkThreadMethod.invoke(webview);
1592             } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1593                 throw new RuntimeException(e);
1594             }
1595         }
1596     }
1597 
1598     @VisibleForTesting
1599     /*package*/ static WeakHashMap<WebView, WebViewProviderAdapter>
getProviderAdapterCacheForTesting()1600             getProviderAdapterCacheForTesting() {
1601         return sProviderAdapterCache;
1602     }
1603 }
1604