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.app.PendingIntent; 20 import android.os.Build; 21 import android.webkit.SafeBrowsingResponse; 22 import android.webkit.WebResourceError; 23 import android.webkit.WebResourceRequest; 24 import android.webkit.WebResourceResponse; 25 import android.webkit.WebView; 26 import android.webkit.WebViewClient; 27 28 import androidx.annotation.IntDef; 29 import androidx.annotation.RequiresApi; 30 import androidx.annotation.RestrictTo; 31 import androidx.annotation.UiThread; 32 import androidx.webkit.internal.SafeBrowsingResponseImpl; 33 import androidx.webkit.internal.WebResourceErrorImpl; 34 import androidx.webkit.internal.WebViewFeatureInternal; 35 36 import org.chromium.support_lib_boundary.WebViewClientBoundaryInterface; 37 import org.chromium.support_lib_boundary.util.Features; 38 import org.jspecify.annotations.NonNull; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.lang.reflect.InvocationHandler; 43 44 /** 45 * Compatibility version of {@link android.webkit.WebViewClient}. 46 */ 47 @SuppressWarnings("HiddenSuperclass") 48 public class WebViewClientCompat extends WebViewClient implements WebViewClientBoundaryInterface { 49 private static final String[] sSupportedFeatures = new String[] { 50 Features.VISUAL_STATE_CALLBACK, 51 Features.RECEIVE_WEB_RESOURCE_ERROR, 52 Features.RECEIVE_HTTP_ERROR, 53 Features.SHOULD_OVERRIDE_WITH_REDIRECTS, 54 Features.SAFE_BROWSING_HIT, 55 }; 56 57 @RestrictTo(RestrictTo.Scope.LIBRARY) 58 @IntDef(value = { 59 WebViewClient.SAFE_BROWSING_THREAT_UNKNOWN, 60 WebViewClient.SAFE_BROWSING_THREAT_MALWARE, 61 WebViewClient.SAFE_BROWSING_THREAT_PHISHING, 62 WebViewClient.SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE, 63 WebViewClient.SAFE_BROWSING_THREAT_BILLING, 64 }) 65 @Retention(RetentionPolicy.SOURCE) 66 public @interface SafeBrowsingThreat {} 67 68 /** 69 * Returns the list of features this client supports. This feature list should always be a 70 * subset of the Features declared in WebViewFeature. 71 * 72 */ 73 @Override 74 @RestrictTo(RestrictTo.Scope.LIBRARY) getSupportedFeatures()75 public final String @NonNull [] getSupportedFeatures() { 76 return sSupportedFeatures; 77 } 78 79 /** 80 * Notify the host application that {@link android.webkit.WebView} content left over from 81 * previous page navigations will no longer be drawn. 82 * 83 * <p>This callback can be used to determine the point at which it is safe to make a recycled 84 * {@link android.webkit.WebView} visible, ensuring that no stale content is shown. It is called 85 * at the earliest point at which it can be guaranteed that {@link WebView#onDraw} will no 86 * longer draw any content from previous navigations. The next draw will display either the 87 * {@link WebView#setBackgroundColor background color} of the {@link WebView}, or some of the 88 * contents of the newly loaded page. 89 * 90 * <p>This method is called when the body of the HTTP response has started loading, is reflected 91 * in the DOM, and will be visible in subsequent draws. This callback occurs early in the 92 * document loading process, and as such you should expect that linked resources (for example, 93 * CSS and images) may not be available. 94 * 95 * <p>For more fine-grained notification of visual state updates, see {@link 96 * WebViewCompat#postVisualStateCallback}. 97 * 98 * <p>Please note that all the conditions and recommendations applicable to 99 * {@link WebViewCompat#postVisualStateCallback} also apply to this API. 100 * 101 * <p>This callback is only called for main frame navigations. 102 * 103 * <p>This method is called only if {@link WebViewFeature#VISUAL_STATE_CALLBACK} is supported. 104 * You can check whether that flag is supported using {@link 105 * WebViewFeature#isFeatureSupported(String)}. 106 * 107 * @param view The {@link android.webkit.WebView} for which the navigation occurred. 108 * @param url The URL corresponding to the page navigation that triggered this callback. 109 */ 110 @Override 111 @UiThread onPageCommitVisible(@onNull WebView view, @NonNull String url)112 public void onPageCommitVisible(@NonNull WebView view, @NonNull String url) { 113 } 114 115 /** 116 * Invoked by chromium (for WebView APks 67+) for the {@code onReceivedError} event. 117 * Applications are not meant to override this, and should instead override the non-final {@link 118 * #onReceivedError(WebView, WebResourceRequest, WebResourceErrorCompat)} method. 119 * 120 */ 121 @RestrictTo(RestrictTo.Scope.LIBRARY) 122 @Override onReceivedError(@onNull WebView view, @NonNull WebResourceRequest request, @NonNull InvocationHandler handler)123 public final void onReceivedError(@NonNull WebView view, @NonNull WebResourceRequest request, 124 /* WebResourceError */ @NonNull InvocationHandler handler) { 125 onReceivedError(view, request, new WebResourceErrorImpl(handler)); 126 } 127 128 // Invoked by chromium (in legacy, pre-67 WebView APKs) for the {@code onReceivedError} event on 129 // {@link Build.VERSION_CODES.M} and above. This delegates the callback to the non-final method, 130 // which the app may have overridden. 131 /** 132 * Applications are not meant to override this, and should instead override the non-final {@link 133 * #onReceivedError(WebView, WebResourceRequest, WebResourceErrorCompat)} method. 134 */ 135 @Override 136 @RequiresApi(Build.VERSION_CODES.M) onReceivedError(@onNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceError error)137 public final void onReceivedError(@NonNull WebView view, @NonNull WebResourceRequest request, 138 @NonNull WebResourceError error) { 139 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; 140 onReceivedError(view, request, new WebResourceErrorImpl(error)); 141 } 142 143 /** 144 * Report web resource loading error to the host application. These errors usually indicate 145 * inability to connect to the server. Note that unlike the deprecated version of the callback, 146 * the new version will be called for any resource (iframe, image, etc.), not just for the main 147 * page. Thus, it is recommended to perform minimum required work in this callback. 148 * 149 * <p>This method is called only if {@link WebViewFeature#RECEIVE_WEB_RESOURCE_ERROR} is 150 * supported. You can check whether that flag is supported using {@link 151 * WebViewFeature#isFeatureSupported(String)}. 152 * 153 * @param view The WebView that is initiating the callback. 154 * @param request The originating request. 155 * @param error Information about the error occurred. 156 */ 157 @SuppressWarnings("deprecation") // for invoking the old onReceivedError. 158 @UiThread onReceivedError(@onNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceErrorCompat error)159 public void onReceivedError(@NonNull WebView view, @NonNull WebResourceRequest request, 160 @NonNull WebResourceErrorCompat error) { 161 if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE) 162 || !WebViewFeature.isFeatureSupported( 163 WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION)) { 164 // If the WebView APK drops supports for these APIs in the future, simply do nothing. 165 return; 166 } 167 if (request.isForMainFrame()) { 168 onReceivedError(view, 169 error.getErrorCode(), error.getDescription().toString(), 170 request.getUrl().toString()); 171 } 172 } 173 174 /** 175 * Notify the host application that an HTTP error has been received from the server while 176 * loading a resource. HTTP errors have status codes >= 400. This callback will be called 177 * for any resource (iframe, image, etc.), not just for the main page. Thus, it is recommended 178 * to perform minimum required work in this callback. Note that the content of the server 179 * response may not be provided within the {@code errorResponse} parameter. 180 * 181 * <p>This method is called only if {@link WebViewFeature#RECEIVE_HTTP_ERROR} is supported. You 182 * can check whether that flag is supported using {@link 183 * WebViewFeature#isFeatureSupported(String)}. 184 * 185 * @param view The WebView that is initiating the callback. 186 * @param request The originating request. 187 * @param errorResponse Information about the error occurred. 188 */ 189 @Override 190 @UiThread onReceivedHttpError(@onNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceResponse errorResponse)191 public void onReceivedHttpError(@NonNull WebView view, @NonNull WebResourceRequest request, 192 @NonNull WebResourceResponse errorResponse) { 193 } 194 195 /** 196 * Invoked by chromium (for WebView APks 67+) for the {@code onSafeBrowsingHit} event. 197 * Applications are not meant to override this, and should instead override the non-final {@link 198 * #onSafeBrowsingHit(WebView, WebResourceRequest, int, SafeBrowsingResponseCompat)} method. 199 * 200 */ 201 @RestrictTo(RestrictTo.Scope.LIBRARY) 202 @Override onSafeBrowsingHit(@onNull WebView view, @NonNull WebResourceRequest request, @SafeBrowsingThreat int threatType, @NonNull InvocationHandler handler)203 public final void onSafeBrowsingHit(@NonNull WebView view, @NonNull WebResourceRequest request, 204 @SafeBrowsingThreat int threatType, 205 /* SafeBrowsingResponse */ @NonNull InvocationHandler handler) { 206 onSafeBrowsingHit(view, request, threatType, new SafeBrowsingResponseImpl(handler)); 207 } 208 209 // Invoked by chromium (in legacy, pre-67 WebView APKs) for the {@code onSafeBrowsingHit} event 210 // on {@link Build.VERSION_CODES.O_MR1} and above. This delegates the callback to the non-final 211 // method, which the app may have overridden. 212 /** 213 * Applications are not meant to override this, and should instead override the non-final {@link 214 * #onSafeBrowsingHit(WebView, WebResourceRequest, int, SafeBrowsingResponseCompat)} method. 215 */ 216 @Override 217 @RequiresApi(Build.VERSION_CODES.O_MR1) onSafeBrowsingHit(@onNull WebView view, @NonNull WebResourceRequest request, @SafeBrowsingThreat int threatType, @NonNull SafeBrowsingResponse response)218 public final void onSafeBrowsingHit(@NonNull WebView view, @NonNull WebResourceRequest request, 219 @SafeBrowsingThreat int threatType, @NonNull SafeBrowsingResponse response) { 220 onSafeBrowsingHit(view, request, threatType, new SafeBrowsingResponseImpl(response)); 221 } 222 223 /** 224 * Notify the host application that a loading URL has been flagged by Safe Browsing. 225 * <p> 226 * The application must invoke the callback to indicate the preferred response. The default 227 * behavior is to show an interstitial to the user, with the reporting checkbox visible. 228 * <p> 229 * If the application needs to show its own custom interstitial UI, the callback can be invoked 230 * asynchronously with {@link SafeBrowsingResponseCompat#backToSafety} or {@link 231 * SafeBrowsingResponseCompat#proceed}, depending on user response. 232 * 233 * @param view The WebView that hit the malicious resource. 234 * @param request Object containing the details of the request. 235 * @param threatType The reason the resource was caught by Safe Browsing, corresponding to a 236 * {@code SAFE_BROWSING_THREAT_*} value. 237 * @param callback Applications must invoke one of the callback methods. 238 */ 239 @UiThread onSafeBrowsingHit(@onNull WebView view, @NonNull WebResourceRequest request, @SafeBrowsingThreat int threatType, @NonNull SafeBrowsingResponseCompat callback)240 public void onSafeBrowsingHit(@NonNull WebView view, @NonNull WebResourceRequest request, 241 @SafeBrowsingThreat int threatType, @NonNull SafeBrowsingResponseCompat callback) { 242 if (WebViewFeature.isFeatureSupported( 243 WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL)) { 244 callback.showInterstitial(true); 245 } else { 246 // This should not happen, but in case the WebView APK eventually drops support for 247 // showInterstitial(), raise a runtime exception and require the WebView APK to handle 248 // this. 249 throw WebViewFeatureInternal.getUnsupportedOperationException(); 250 } 251 } 252 253 /** 254 * Give the host application a chance to take over the control when a new 255 * url is about to be loaded in the current WebView. If WebViewClient is not 256 * provided, by default WebView will ask Activity Manager to choose the 257 * proper handler for the url. If WebViewClient is provided, return {@code true} 258 * means the host application handles the url, while return {@code false} means the 259 * current WebView handles the url. 260 * 261 * <p>Notes: 262 * <ul> 263 * <li>This method is not called for requests using the POST "method".</li> 264 * <li>This method is also called for subframes with non-http schemes, thus it is 265 * strongly disadvised to unconditionally call {@link WebView#loadUrl(String)} 266 * with the request's url from inside the method and then return {@code true}, 267 * as this will make WebView to attempt loading a non-http url, and thus fail.</li> 268 * </ul> 269 * 270 * <p>This method is called only if {@link WebViewFeature#SHOULD_OVERRIDE_WITH_REDIRECTS} is 271 * supported. You can check whether that flag is supported using {@link 272 * WebViewFeature#isFeatureSupported(String)}. 273 * 274 * @param view The WebView that is initiating the callback. 275 * @param request Object containing the details of the request. 276 * @return {@code true} if the host application wants to leave the current WebView 277 * and handle the url itself, otherwise return {@code false}. 278 */ 279 @Override 280 @SuppressWarnings("deprecation") // for invoking the old shouldOverrideUrlLoading. 281 @UiThread shouldOverrideUrlLoading(@onNull WebView view, @NonNull WebResourceRequest request)282 public boolean shouldOverrideUrlLoading(@NonNull WebView view, 283 @NonNull WebResourceRequest request) { 284 return shouldOverrideUrlLoading(view, request.getUrl().toString()); 285 } 286 287 /** 288 */ 289 // TODO(crbug.com/1284805): Replace the missing override suppression annotation with @Override 290 // After the boundary interface is rolled. 291 @SuppressWarnings("MissingOverride") 292 @RestrictTo(RestrictTo.Scope.LIBRARY) onWebAuthnIntent(@onNull WebView view, @NonNull PendingIntent intent, @NonNull InvocationHandler callback)293 public boolean onWebAuthnIntent(@NonNull WebView view, @NonNull PendingIntent intent, 294 /* WebAuthnCallbackBoundaryInterface */ @NonNull InvocationHandler callback) { 295 // TODO(crbug.com/1284805): Implement the actual logic. 296 return false; 297 } 298 } 299