Commit f3b8b74e authored by Nate Fischer's avatar Nate Fischer Committed by Commit Bot

AW: implement support lib callbacks

This implements support library callbacks (WebViewclientCompat). This
creates a new layer (support_library/callback) which the glue layer
depends on. This dependency lets us instantiate a
SupportLibWebViewContentsClientAdapter inside setWebViewClient(), and
benefit from the glue layer's parameter cleanup code (e.g., in
onReceivedError2()).

The support_library/callback glue must be a separate layer from
support_library/ glue, as that already depends on the webkit glue to
initiate state. Implementing callbacks as a separate target avoids the
circular dependency.

This refactors the (post-L) glue layer callback methods to take the
following precedence:

 1. SupportLibWebViewContentsClientAdapter (if it supports the callback)
 2. WebViewClient (if on the appropriate platform level)
 3. Default behavior (implementation provided by the glue layer)

This implements both category 1 and 2 APIs.

Design doc: http://go/wv-support-library-callbacks

Bug: 781764
Test: manual - built test application with latest support-lib changes
Change-Id: I21e28493873e670cfd428c7eeef12b0e212aeec4
Reviewed-on: https://chromium-review.googlesource.com/989015Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Reviewed-by: default avatarGustav Sennton <gsennton@chromium.org>
Commit-Queue: Nate Fischer <ntfschr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548538}
parent 58f68d3c
......@@ -9,6 +9,8 @@ glue_library_deps = [
"//android_webview:android_webview_commandline_java",
"//android_webview:android_webview_platform_services_java",
"//android_webview:system_webview_manifest",
"//android_webview/support_library/boundary_interfaces:boundary_interface_java",
"//android_webview/support_library/callback:callback_java",
"//base:base_java",
"//components/autofill/android:autofill_java",
"//components/autofill/android:provider_java",
......
......@@ -60,6 +60,8 @@ import org.chromium.base.Callback;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.TraceEvent;
import org.chromium.support_lib_boundary.util.Features;
import org.chromium.support_lib_callback_glue.SupportLibWebViewContentsClientAdapter;
import java.lang.ref.WeakReference;
import java.security.Principal;
......@@ -101,6 +103,8 @@ class WebViewContentsClientAdapter extends AwContentsClient {
private final Context mContext;
// The WebViewClient instance that was passed to WebView.setWebViewClient().
protected WebViewClient mWebViewClient = sNullWebViewClient;
// Some callbacks will be forwarded to this client for apps using the support library.
private SupportLibWebViewContentsClientAdapter mSupportLibClient;
// The WebChromeClient instance that was passed to WebView.setContentViewClient().
private WebChromeClient mWebChromeClient;
// The listener receiving find-in-page API results.
......@@ -176,6 +180,9 @@ class WebViewContentsClientAdapter extends AwContentsClient {
} else {
mWebViewClient = sNullWebViewClient;
}
// Always reset mSupportLibClient, since the WebViewClient may no longer be a
// WebViewClientCompat, or may support a different set of Features.
mSupportLibClient = new SupportLibWebViewContentsClientAdapter(mWebViewClient);
}
WebViewClient getWebViewClient() {
......@@ -319,7 +326,10 @@ class WebViewContentsClientAdapter extends AwContentsClient {
TraceEvent.begin("WebViewContentsClientAdapter.shouldOverrideUrlLoading");
if (TRACE) Log.i(TAG, "shouldOverrideUrlLoading=" + request.url);
boolean result;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (mSupportLibClient.isFeatureAvailable(Features.SHOULD_OVERRIDE_WITH_REDIRECTS)) {
result = mSupportLibClient.shouldOverrideUrlLoading(
mWebView, new WebResourceRequestAdapter(request));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
result = mWebViewClient.shouldOverrideUrlLoading(
mWebView, new WebResourceRequestAdapter(request));
} else {
......@@ -547,11 +557,15 @@ class WebViewContentsClientAdapter extends AwContentsClient {
*/
@Override
public void onPageCommitVisible(String url) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
try {
TraceEvent.begin("WebViewContentsClientAdapter.onPageCommitVisible");
if (TRACE) Log.i(TAG, "onPageCommitVisible=" + url);
if (mSupportLibClient.isFeatureAvailable(Features.VISUAL_STATE_CALLBACK)) {
mSupportLibClient.onPageCommitVisible(mWebView, url);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mWebViewClient.onPageCommitVisible(mWebView, url);
}
// Otherwise, the API does not exist, so do nothing.
} finally {
TraceEvent.end("WebViewContentsClientAdapter.onPageCommitVisible");
}
......@@ -563,6 +577,10 @@ class WebViewContentsClientAdapter extends AwContentsClient {
@Override
public void onReceivedError(int errorCode, String description, String failingUrl) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) return;
// This event is handled by the support lib in {@link #onReceivedError2}.
if (mSupportLibClient.isFeatureAvailable(Features.WEB_RESOURCE_ERROR)) return;
try {
TraceEvent.begin("WebViewContentsClientAdapter.onReceivedError");
if (description == null || description.isEmpty()) {
......@@ -584,7 +602,6 @@ class WebViewContentsClientAdapter extends AwContentsClient {
*/
@Override
public void onReceivedError2(AwWebResourceRequest request, AwWebResourceError error) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
try {
TraceEvent.begin("WebViewContentsClientAdapter.onReceivedError");
if (error.description == null || error.description.isEmpty()) {
......@@ -594,8 +611,15 @@ class WebViewContentsClientAdapter extends AwContentsClient {
error.description = mWebViewDelegate.getErrorString(mContext, error.errorCode);
}
if (TRACE) Log.i(TAG, "onReceivedError=" + request.url);
if (mSupportLibClient.isFeatureAvailable(Features.WEB_RESOURCE_ERROR)) {
// Note: we must pass AwWebResourceError, since this class was introduced after L.
mSupportLibClient.onReceivedError(
mWebView, new WebResourceRequestAdapter(request), error);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mWebViewClient.onReceivedError(mWebView, new WebResourceRequestAdapter(request),
new WebResourceErrorImpl(error));
}
// Otherwise, this is handled by {@link #onReceivedError}.
} finally {
TraceEvent.end("WebViewContentsClientAdapter.onReceivedError");
}
......@@ -608,14 +632,12 @@ class WebViewContentsClientAdapter extends AwContentsClient {
@TargetApi(Build.VERSION_CODES.O_MR1)
public void onSafeBrowsingHit(AwWebResourceRequest request, int threatType,
final Callback<AwSafeBrowsingResponse> callback) {
// WebViewClient.onSafeBrowsingHit was added in O_MR1.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
callback.onResult(new AwSafeBrowsingResponse(SafeBrowsingAction.SHOW_INTERSTITIAL,
/* reporting */ true));
return;
}
try {
TraceEvent.begin("WebViewContentsClientAdapter.onSafeBrowsingHit");
if (mSupportLibClient.isFeatureAvailable(Features.SAFE_BROWSING_HIT)) {
mSupportLibClient.onSafeBrowsingHit(
mWebView, new WebResourceRequestAdapter(request), threatType, callback);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
mWebViewClient.onSafeBrowsingHit(mWebView, new WebResourceRequestAdapter(request),
threatType, new SafeBrowsingResponse() {
@Override
......@@ -626,8 +648,8 @@ class WebViewContentsClientAdapter extends AwContentsClient {
@Override
public void proceed(boolean report) {
callback.onResult(
new AwSafeBrowsingResponse(SafeBrowsingAction.PROCEED, report));
callback.onResult(new AwSafeBrowsingResponse(
SafeBrowsingAction.PROCEED, report));
}
@Override
......@@ -636,6 +658,10 @@ class WebViewContentsClientAdapter extends AwContentsClient {
SafeBrowsingAction.BACK_TO_SAFETY, report));
}
});
} else {
callback.onResult(new AwSafeBrowsingResponse(SafeBrowsingAction.SHOW_INTERSTITIAL,
/* reporting */ true));
}
} finally {
TraceEvent.end("WebViewContentsClientAdapter.onRenderProcessGone");
}
......@@ -643,14 +669,24 @@ class WebViewContentsClientAdapter extends AwContentsClient {
@Override
public void onReceivedHttpError(AwWebResourceRequest request, AwWebResourceResponse response) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
try {
TraceEvent.begin("WebViewContentsClientAdapter.onReceivedHttpError");
if (TRACE) Log.i(TAG, "onReceivedHttpError=" + request.url);
if (mSupportLibClient.isFeatureAvailable(Features.RECEIVE_HTTP_ERROR)) {
// Note: we do not create an immutable instance here, because that constructor is
// not available on L.
mSupportLibClient.onReceivedHttpError(mWebView,
new WebResourceRequestAdapter(request),
new WebResourceResponse(response.getMimeType(), response.getCharset(),
response.getStatusCode(), response.getReasonPhrase(),
response.getResponseHeaders(), response.getData()));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mWebViewClient.onReceivedHttpError(mWebView, new WebResourceRequestAdapter(request),
new WebResourceResponse(true, response.getMimeType(), response.getCharset(),
response.getStatusCode(), response.getReasonPhrase(),
response.getResponseHeaders(), response.getData()));
}
// Otherwise, the API does not exist, so do nothing.
} finally {
TraceEvent.end("WebViewContentsClientAdapter.onReceivedHttpError");
}
......
......@@ -13,6 +13,19 @@ public class Features {
// This class just contains constants representing features.
private Features() {}
// WebViewCompat.postVisualStateCallback
// WebViewCompat#postVisualStateCallback
// WebViewClientCompat#onPageCommitVisible
public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
// WebViewClientCompat#onReceivedError(WebView, WebResourceRequest, WebResourceError)
public static final String WEB_RESOURCE_ERROR = "WEB_RESOURCE_ERROR";
// WebViewClientCompat#onReceivedHttpError
public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
// WebViewClientCompat#onSafeBrowsingHit
public static final String SAFE_BROWSING_HIT = "SAFE_BROWSING_HIT";
// WebViewClientCompat#shouldOverrideUrlLoading
public static final String SHOULD_OVERRIDE_WITH_REDIRECTS = "SHOULD_OVERRIDE_WITH_REDIRECTS";
}
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
android_library("callback_java") {
java_files = [ "java/src/org/chromium/support_lib_callback_glue/SupportLibWebViewContentsClientAdapter.java" ]
deps = [
"//android_webview:android_webview_commandline_java",
"//android_webview:android_webview_java",
"//android_webview/support_library/boundary_interfaces:boundary_interface_java",
"//base:base_java",
]
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.support_lib_callback_glue;
import android.support.annotation.Nullable;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.chromium.android_webview.AwContentsClient.AwWebResourceError;
import org.chromium.android_webview.AwSafeBrowsingResponse;
import org.chromium.android_webview.SafeBrowsingAction;
import org.chromium.base.Callback;
import org.chromium.support_lib_boundary.SafeBrowsingResponseBoundaryInterface;
import org.chromium.support_lib_boundary.WebResourceErrorBoundaryInterface;
import org.chromium.support_lib_boundary.WebViewClientBoundaryInterface;
import org.chromium.support_lib_boundary.util.BoundaryInterfaceReflectionUtil;
import org.chromium.support_lib_boundary.util.Features;
import java.lang.reflect.InvocationHandler;
/**
* Support library glue version of WebViewContentsClientAdapter.
*/
public class SupportLibWebViewContentsClientAdapter {
private static final String WEBVIEW_CLIENT_COMPAT_NAME = "androidx.webkit.WebViewClientCompat";
// If {@code null}, this indicates the WebViewClient is not a WebViewClientCompat. Otherwise,
// this is a Proxy for the WebViewClientCompat.
@Nullable
private WebViewClientBoundaryInterface mWebViewClient;
private static class SafeBrowsingResponseDelegate
implements SafeBrowsingResponseBoundaryInterface {
private Callback<AwSafeBrowsingResponse> mCallback;
SafeBrowsingResponseDelegate(Callback<AwSafeBrowsingResponse> callback) {
mCallback = callback;
}
@Override
public void showInterstitial(boolean allowReporting) {
mCallback.onResult(new AwSafeBrowsingResponse(
SafeBrowsingAction.SHOW_INTERSTITIAL, allowReporting));
}
@Override
public void proceed(boolean report) {
mCallback.onResult(new AwSafeBrowsingResponse(SafeBrowsingAction.PROCEED, report));
}
@Override
public void backToSafety(boolean report) {
mCallback.onResult(
new AwSafeBrowsingResponse(SafeBrowsingAction.BACK_TO_SAFETY, report));
}
};
private static class WebResourceErrorDelegate implements WebResourceErrorBoundaryInterface {
private AwWebResourceError mError;
WebResourceErrorDelegate(AwWebResourceError error) {
mError = error;
}
@Override
public int getErrorCode() {
return mError.errorCode;
}
@Override
public CharSequence getDescription() {
return mError.description;
}
};
public SupportLibWebViewContentsClientAdapter(WebViewClient possiblyCompatClient) {
mWebViewClient = convertCompatClient(possiblyCompatClient);
}
@Nullable
private WebViewClientBoundaryInterface convertCompatClient(WebViewClient possiblyCompatClient) {
if (!clientIsCompat(possiblyCompatClient)) return null;
InvocationHandler handler =
BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(possiblyCompatClient);
return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
WebViewClientBoundaryInterface.class, handler);
}
private boolean clientIsCompat(WebViewClient possiblyCompatClient) {
try {
Class compatClass = Class.forName(WEBVIEW_CLIENT_COMPAT_NAME, false,
possiblyCompatClient.getClass().getClassLoader());
return compatClass.isInstance(possiblyCompatClient);
} catch (ClassNotFoundException e) {
// If WEBVIEW_CLIENT_COMPAT_NAME is not in the ClassLoader, then this cannot be an
// instance of WebViewClientCompat.
return false;
}
}
/**
* Indicates whether this client can handle the callback(s) assocated with {@param featureName}.
* This should be called with the correct feature name before invoking the corresponding
* callback, and the callback must not be called if this returns {@code false} for the feature.
*
* @param featureName the feature for the desired callback.
* @return {@code true} if this client can handle the feature.
*/
public boolean isFeatureAvailable(String featureName) {
if (mWebViewClient == null) return false;
// TODO(ntfschr): provide a real implementation, which consults the WebViewClientCompat.
return true;
}
public void onPageCommitVisible(WebView webView, String url) {
assert isFeatureAvailable(Features.VISUAL_STATE_CALLBACK);
mWebViewClient.onPageCommitVisible(webView, url);
}
public void onReceivedError(
WebView webView, WebResourceRequest request, final AwWebResourceError error) {
assert isFeatureAvailable(Features.WEB_RESOURCE_ERROR);
WebResourceErrorBoundaryInterface errorDelegate = new WebResourceErrorDelegate(error);
InvocationHandler errorHandler =
BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(errorDelegate);
mWebViewClient.onReceivedError(webView, request, errorHandler);
}
public void onReceivedHttpError(
WebView webView, WebResourceRequest request, WebResourceResponse response) {
assert isFeatureAvailable(Features.RECEIVE_HTTP_ERROR);
mWebViewClient.onReceivedHttpError(webView, request, response);
}
public void onSafeBrowsingHit(WebView webView, WebResourceRequest request, int threatType,
Callback<AwSafeBrowsingResponse> callback) {
assert isFeatureAvailable(Features.SAFE_BROWSING_HIT);
SafeBrowsingResponseBoundaryInterface responseDelegate =
new SafeBrowsingResponseDelegate(callback);
InvocationHandler responseHandler =
BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(responseDelegate);
mWebViewClient.onSafeBrowsingHit(webView, request, threatType, responseHandler);
}
public boolean shouldOverrideUrlLoading(WebView webView, WebResourceRequest request) {
assert isFeatureAvailable(Features.SHOULD_OVERRIDE_WITH_REDIRECTS);
return mWebViewClient.shouldOverrideUrlLoading(webView, request);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment