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 * @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 * @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 * @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