Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[webview_flutter_android] Adds a WebViewFlutterApi #3324

Merged
merged 9 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 3.3.2

* Fixes a potential bug where a `WebView` that was not added to the `InstanceManager` could be
returned by a `WebViewClient` or `WebChromeClient`.

## 3.3.1

* Updates links for the merge of flutter/plugins into flutter/packages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,42 @@ public void error(Throwable error) {
}
}
}
/**
* Flutter API for `WebView`.
*
* <p>This class may handle instantiating and adding Dart instances that are attached to a native
* instance or receiving callback methods from an overridden native class.
*
* <p>See https://developer.android.com/reference/android/webkit/WebView.
*
* <p>Generated class from Pigeon that represents Flutter messages that can be called from Java.
*/
public static class WebViewFlutterApi {
private final BinaryMessenger binaryMessenger;

public WebViewFlutterApi(BinaryMessenger argBinaryMessenger) {
this.binaryMessenger = argBinaryMessenger;
}

public interface Reply<T> {
void reply(T reply);
}
/** The codec used by WebViewFlutterApi. */
static MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
}
/** Create a new Dart instance and add it to the `InstanceManager`. */
public void create(@NonNull Long identifierArg, Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.WebViewFlutterApi.create", getCodec());
channel.send(
new ArrayList<Object>(Collections.singletonList(identifierArg)),
channelReply -> {
callback.reply(null);
});
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface WebSettingsHostApi {
void create(@NonNull Long instanceId, @NonNull Long webViewInstanceId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ public WebChromeClientFlutterApiImpl(
/** Passes arguments from {@link WebChromeClient#onProgressChanged} to Dart. */
public void onProgressChanged(
WebChromeClient webChromeClient, WebView webView, Long progress, Reply<Void> callback) {
final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
if (webViewIdentifier == null) {
throw new IllegalStateException("Could not find identifier for WebView.");
}
final WebViewFlutterApiImpl webViewFlutterApi =
new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
webViewFlutterApi.create(webView, reply -> {});

final Long webViewIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
super.onProgressChanged(
getIdentifierForClient(webChromeClient), webViewIdentifier, progress, callback);
}
Expand All @@ -53,6 +55,10 @@ public void onShowFileChooser(
WebView webView,
WebChromeClient.FileChooserParams fileChooserParams,
Reply<List<String>> callback) {
final WebViewFlutterApiImpl webViewFlutterApi =
new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
webViewFlutterApi.create(webView, reply -> {});

Long paramsInstanceId = instanceManager.getIdentifierForStrongReference(fileChooserParams);
if (paramsInstanceId == null) {
final FileChooserParamsFlutterApiImpl flutterApi =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewClientFlutterApi;
import java.util.HashMap;
import java.util.Objects;

/**
* Flutter Api implementation for {@link WebViewClient}.
*
* <p>Passes arguments of callbacks methods from a {@link WebViewClient} to Dart.
*/
public class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi {
private final BinaryMessenger binaryMessenger;
private final InstanceManager instanceManager;

@RequiresApi(api = Build.VERSION_CODES.M)
Expand Down Expand Up @@ -71,26 +73,31 @@ static GeneratedAndroidWebView.WebResourceRequestData createWebResourceRequestDa
public WebViewClientFlutterApiImpl(
BinaryMessenger binaryMessenger, InstanceManager instanceManager) {
super(binaryMessenger);
this.binaryMessenger = binaryMessenger;
this.instanceManager = instanceManager;
}

/** Passes arguments from {@link WebViewClient#onPageStarted} to Dart. */
public void onPageStarted(
WebViewClient webViewClient, WebView webView, String urlArg, Reply<Void> callback) {
final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
if (webViewIdentifier == null) {
throw new IllegalStateException("Could not find identifier for WebView.");
}
final WebViewFlutterApiImpl webViewFlutterApi =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: Should we just make a private field with this API on object construction, so we don't need the boilerplate of creating one in every single callback? I know it's cheap to create, but it should also be very cheap to just keep it around.

new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
webViewFlutterApi.create(webView, reply -> {});

final Long webViewIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
onPageStarted(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
}

/** Passes arguments from {@link WebViewClient#onPageFinished} to Dart. */
public void onPageFinished(
WebViewClient webViewClient, WebView webView, String urlArg, Reply<Void> callback) {
final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
if (webViewIdentifier == null) {
throw new IllegalStateException("Could not find identifier for WebView.");
}
final WebViewFlutterApiImpl webViewFlutterApi =
new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
webViewFlutterApi.create(webView, reply -> {});

final Long webViewIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
onPageFinished(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
}

Expand All @@ -105,10 +112,12 @@ public void onReceivedRequestError(
WebResourceRequest request,
WebResourceError error,
Reply<Void> callback) {
final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
if (webViewIdentifier == null) {
throw new IllegalStateException("Could not find identifier for WebView.");
}
final WebViewFlutterApiImpl webViewFlutterApi =
new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
webViewFlutterApi.create(webView, reply -> {});

final Long webViewIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
onReceivedRequestError(
getIdentifierForClient(webViewClient),
webViewIdentifier,
Expand All @@ -128,10 +137,12 @@ public void onReceivedRequestError(
WebResourceRequest request,
WebResourceErrorCompat error,
Reply<Void> callback) {
final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
if (webViewIdentifier == null) {
throw new IllegalStateException("Could not find identifier for WebView.");
}
final WebViewFlutterApiImpl webViewFlutterApi =
new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
webViewFlutterApi.create(webView, reply -> {});

final Long webViewIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
onReceivedRequestError(
getIdentifierForClient(webViewClient),
webViewIdentifier,
Expand All @@ -151,10 +162,12 @@ public void onReceivedError(
String descriptionArg,
String failingUrlArg,
Reply<Void> callback) {
final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
if (webViewIdentifier == null) {
throw new IllegalStateException("Could not find identifier for WebView.");
}
final WebViewFlutterApiImpl webViewFlutterApi =
new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
webViewFlutterApi.create(webView, reply -> {});

final Long webViewIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
onReceivedError(
getIdentifierForClient(webViewClient),
webViewIdentifier,
Expand All @@ -174,10 +187,12 @@ public void requestLoading(
WebView webView,
WebResourceRequest request,
Reply<Void> callback) {
final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
if (webViewIdentifier == null) {
throw new IllegalStateException("Could not find identifier for WebView.");
}
final WebViewFlutterApiImpl webViewFlutterApi =
new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
webViewFlutterApi.create(webView, reply -> {});

final Long webViewIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
requestLoading(
getIdentifierForClient(webViewClient),
webViewIdentifier,
Expand All @@ -190,10 +205,12 @@ public void requestLoading(
*/
public void urlLoading(
WebViewClient webViewClient, WebView webView, String urlArg, Reply<Void> callback) {
final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
if (webViewIdentifier == null) {
throw new IllegalStateException("Could not find identifier for WebView.");
}
final WebViewFlutterApiImpl webViewFlutterApi =
new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
webViewFlutterApi.create(webView, reply -> {});

final Long webViewIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
urlLoading(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2013 The Flutter 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 io.flutter.plugins.webviewflutter;

import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewFlutterApi;

/**
* Flutter API implementation for `WebView`.
*
* <p>This class may handle adding native instances that are attached to a Dart instance or passing
* arguments of callbacks methods to a Dart instance.
*/
public class WebViewFlutterApiImpl {
// To ease adding additional methods, this value is added prematurely.
@SuppressWarnings({"unused", "FieldCanBeLocal"})
private final BinaryMessenger binaryMessenger;

private final InstanceManager instanceManager;
private WebViewFlutterApi api;

/**
* Constructs a {@link WebViewFlutterApiImpl}.
*
* @param binaryMessenger used to communicate with Dart over asynchronous messages
* @param instanceManager maintains instances stored to communicate with attached Dart objects
*/
public WebViewFlutterApiImpl(
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
this.binaryMessenger = binaryMessenger;
this.instanceManager = instanceManager;
api = new WebViewFlutterApi(binaryMessenger);
}

/**
* Stores the `WebView` instance and notifies Dart to create and store a new `WebView` instance
* that is attached to this one. If `instance` has already been added, this method does nothing.
*/
public void create(@NonNull WebView instance, @NonNull WebViewFlutterApi.Reply<Void> callback) {
if (!instanceManager.containsInstance(instance)) {
api.create(instanceManager.addHostCreatedInstance(instance), callback);
}
}

/**
* Sets the Flutter API used to send messages to Dart.
*
* <p>This is only visible for testing.
*/
@VisibleForTesting
void setApi(@NonNull WebViewFlutterApi api) {
this.api = api;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ public class WebChromeClientTest {
public void setUp() {
instanceManager = InstanceManager.open(identifier -> {});

instanceManager.addDartCreatedInstance(mockWebView, 0L);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed so that it is tested that it can handle adding the mockWebView.


final WebChromeClientCreator webChromeClientCreator =
new WebChromeClientCreator() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ public class WebViewClientTest {
public void setUp() {
instanceManager = InstanceManager.open(identifier -> {});

instanceManager.addDartCreatedInstance(mockWebView, 0L);

final WebViewClientCreator webViewClientCreator =
new WebViewClientCreator() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand All @@ -18,6 +19,7 @@
import android.webkit.WebChromeClient;
import android.webkit.WebViewClient;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewFlutterApi;
import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.WebViewPlatformView;
import java.util.HashMap;
import java.util.Objects;
Expand Down Expand Up @@ -314,4 +316,23 @@ public void destroy() {

assertTrue(destroyCalled[0]);
}

@Test
public void flutterApiCreate() {
final InstanceManager instanceManager = InstanceManager.open(identifier -> {});

final WebViewFlutterApiImpl flutterApiImpl =
new WebViewFlutterApiImpl(mockBinaryMessenger, instanceManager);

final WebViewFlutterApi mockFlutterApi = mock(WebViewFlutterApi.class);
flutterApiImpl.setApi(mockFlutterApi);

flutterApiImpl.create(mockWebView, reply -> {});

final long instanceIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(mockWebView));
verify(mockFlutterApi).create(eq(instanceIdentifier), any());

instanceManager.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,43 @@ class WebViewHostApi {
}
}

/// Flutter API for `WebView`.
///
/// This class may handle instantiating and adding Dart instances that are
/// attached to a native instance or receiving callback methods from an
/// overridden native class.
///
/// See https://developer.android.com/reference/android/webkit/WebView.
abstract class WebViewFlutterApi {
static const MessageCodec<Object?> codec = StandardMessageCodec();

/// Create a new Dart instance and add it to the `InstanceManager`.
void create(int identifier);

static void setup(WebViewFlutterApi? api,
{BinaryMessenger? binaryMessenger}) {
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.WebViewFlutterApi.create', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMessageHandler(null);
} else {
channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.WebViewFlutterApi.create was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.WebViewFlutterApi.create was null, expected non-null int.');
api.create(arg_identifier!);
return;
});
}
}
}
}

class WebSettingsHostApi {
/// Constructor for [WebSettingsHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
Expand Down
Loading