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 &gt;= 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 &quot;method&quot;.</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