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