diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java new file mode 100644 index 000000000000..2dd98c47d582 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java @@ -0,0 +1,64 @@ +// 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.DownloadListener; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerFlutterApi; + +/** + * Flutter Api implementation for {@link DownloadListener}. + * + *

Passes arguments of callbacks methods from a {@link DownloadListener} to Dart. + */ +public class DownloadListenerFlutterApiImpl extends DownloadListenerFlutterApi { + private final InstanceManager instanceManager; + + /** + * Creates a Flutter api that sends messages to Dart. + * + * @param binaryMessenger handles sending messages to Dart + * @param instanceManager maintains instances stored to communicate with Dart objects + */ + public DownloadListenerFlutterApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + /** Passes arguments from {@link DownloadListener#onDownloadStart} to Dart. */ + public void onDownloadStart( + DownloadListener downloadListener, + String url, + String userAgent, + String contentDisposition, + String mimetype, + long contentLength, + Reply callback) { + onDownloadStart( + instanceManager.getInstanceId(downloadListener), + url, + userAgent, + contentDisposition, + mimetype, + contentLength, + callback); + } + + /** + * Communicates to Dart that the reference to a {@link DownloadListener} was removed. + * + * @param downloadListener the instance whose reference will be removed + * @param callback reply callback with return value from Dart + */ + public void dispose(DownloadListener downloadListener, Reply callback) { + final Long instanceId = instanceManager.removeInstance(downloadListener); + if (instanceId != null) { + dispose(instanceId, callback); + } else { + callback.reply(null); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java index 202be87d7d1e..9694f396ad2e 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java @@ -5,40 +5,92 @@ package io.flutter.plugins.webviewflutter; import android.webkit.DownloadListener; -import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerFlutterApi; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi; -class DownloadListenerHostApiImpl implements GeneratedAndroidWebView.DownloadListenerHostApi { +/** + * Host api implementation for {@link DownloadListener}. + * + *

Handles creating {@link DownloadListener}s that intercommunicate with a paired Dart object. + */ +public class DownloadListenerHostApiImpl implements DownloadListenerHostApi { private final InstanceManager instanceManager; private final DownloadListenerCreator downloadListenerCreator; - private final GeneratedAndroidWebView.DownloadListenerFlutterApi downloadListenerFlutterApi; - - static class DownloadListenerCreator { - DownloadListener createDownloadListener( - Long instanceId, DownloadListenerFlutterApi downloadListenerFlutterApi) { - return (url, userAgent, contentDisposition, mimetype, contentLength) -> - downloadListenerFlutterApi.onDownloadStart( - instanceId, url, userAgent, contentDisposition, mimetype, contentLength, reply -> {}); + private final DownloadListenerFlutterApiImpl flutterApi; + + /** + * Implementation of {@link DownloadListener} that passes arguments of callback methods to Dart. + * + *

No messages are sent to Dart after {@link DownloadListenerImpl#release} is called. + */ + public static class DownloadListenerImpl implements DownloadListener, Releasable { + @Nullable private DownloadListenerFlutterApiImpl flutterApi; + + /** + * Creates a {@link DownloadListenerImpl} that passes arguments of callbacks methods to Dart. + * + * @param flutterApi handles sending messages to Dart + */ + public DownloadListenerImpl(@NonNull DownloadListenerFlutterApiImpl flutterApi) { + this.flutterApi = flutterApi; + } + + @Override + public void onDownloadStart( + String url, + String userAgent, + String contentDisposition, + String mimetype, + long contentLength) { + if (flutterApi != null) { + flutterApi.onDownloadStart( + this, url, userAgent, contentDisposition, mimetype, contentLength, reply -> {}); + } + } + + @Override + public void release() { + if (flutterApi != null) { + flutterApi.dispose(this, reply -> {}); + } + flutterApi = null; } } - DownloadListenerHostApiImpl( + /** Handles creating {@link DownloadListenerImpl}s for a {@link DownloadListenerHostApiImpl}. */ + public static class DownloadListenerCreator { + /** + * Creates a {@link DownloadListenerImpl}. + * + * @param flutterApi handles sending messages to Dart + * @return the created {@link DownloadListenerImpl} + */ + public DownloadListenerImpl createDownloadListener(DownloadListenerFlutterApiImpl flutterApi) { + return new DownloadListenerImpl(flutterApi); + } + } + + /** + * Creates a host API that handles creating {@link DownloadListener}s. + * + * @param instanceManager maintains instances stored to communicate with Dart objects + * @param downloadListenerCreator handles creating {@link DownloadListenerImpl}s + * @param flutterApi handles sending messages to Dart + */ + public DownloadListenerHostApiImpl( InstanceManager instanceManager, DownloadListenerCreator downloadListenerCreator, - DownloadListenerFlutterApi downloadListenerFlutterApi) { + DownloadListenerFlutterApiImpl flutterApi) { this.instanceManager = instanceManager; this.downloadListenerCreator = downloadListenerCreator; - this.downloadListenerFlutterApi = downloadListenerFlutterApi; + this.flutterApi = flutterApi; } @Override public void create(Long instanceId) { final DownloadListener downloadListener = - downloadListenerCreator.createDownloadListener(instanceId, downloadListenerFlutterApi); + downloadListenerCreator.createDownloadListener(flutterApi); instanceManager.addInstance(downloadListener, instanceId); } - - @Override - public void dispose(Long instanceId) { - instanceManager.removeInstance(instanceId); - } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index ba2b9b1ac481..a34274893117 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v1.0.7), do not edit directly. +// Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.webviewflutter; @@ -1313,8 +1313,6 @@ private JavaScriptChannelHostApiCodec() {} public interface JavaScriptChannelHostApi { void create(Long instanceId, String channelName); - void dispose(Long instanceId); - /** The codec used by JavaScriptChannelHostApi. */ static MessageCodec getCodec() { return JavaScriptChannelHostApiCodec.INSTANCE; @@ -1354,31 +1352,6 @@ static void setup(BinaryMessenger binaryMessenger, JavaScriptChannelHostApi api) channel.setMessageHandler(null); } } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.JavaScriptChannelHostApi.dispose", getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - Map wrapped = new HashMap<>(); - try { - ArrayList args = (ArrayList) message; - Number instanceIdArg = (Number) args.get(0); - if (instanceIdArg == null) { - throw new NullPointerException("instanceIdArg unexpectedly null."); - } - api.dispose(instanceIdArg.longValue()); - wrapped.put("result", null); - } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } } } @@ -1405,6 +1378,19 @@ static MessageCodec getCodec() { return JavaScriptChannelFlutterApiCodec.INSTANCE; } + public void dispose(Long instanceIdArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.JavaScriptChannelFlutterApi.dispose", + getCodec()); + channel.send( + new ArrayList(Arrays.asList(instanceIdArg)), + channelReply -> { + callback.reply(null); + }); + } + public void postMessage(Long instanceIdArg, String messageArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -1429,8 +1415,6 @@ private WebViewClientHostApiCodec() {} public interface WebViewClientHostApi { void create(Long instanceId, Boolean shouldOverrideUrlLoading); - void dispose(Long instanceId); - /** The codec used by WebViewClientHostApi. */ static MessageCodec getCodec() { return WebViewClientHostApiCodec.INSTANCE; @@ -1471,31 +1455,6 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { channel.setMessageHandler(null); } } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebViewClientHostApi.dispose", getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - Map wrapped = new HashMap<>(); - try { - ArrayList args = (ArrayList) message; - Number instanceIdArg = (Number) args.get(0); - if (instanceIdArg == null) { - throw new NullPointerException("instanceIdArg unexpectedly null."); - } - api.dispose(instanceIdArg.longValue()); - wrapped.put("result", null); - } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } } } @@ -1513,9 +1472,6 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) { case (byte) 129: return WebResourceRequestData.fromMap((Map) readValue(buffer)); - case (byte) 130: - return WebResourceRequestData.fromMap((Map) readValue(buffer)); - default: return super.readValueOfType(type, buffer); } @@ -1529,9 +1485,6 @@ protected void writeValue(ByteArrayOutputStream stream, Object value) { } else if (value instanceof WebResourceRequestData) { stream.write(129); writeValue(stream, ((WebResourceRequestData) value).toMap()); - } else if (value instanceof WebResourceRequestData) { - stream.write(130); - writeValue(stream, ((WebResourceRequestData) value).toMap()); } else { super.writeValue(stream, value); } @@ -1554,6 +1507,17 @@ static MessageCodec getCodec() { return WebViewClientFlutterApiCodec.INSTANCE; } + public void dispose(Long instanceIdArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.WebViewClientFlutterApi.dispose", getCodec()); + channel.send( + new ArrayList(Arrays.asList(instanceIdArg)), + channelReply -> { + callback.reply(null); + }); + } + public void onPageStarted( Long instanceIdArg, Long webViewInstanceIdArg, String urlArg, Reply callback) { BasicMessageChannel channel = @@ -1666,8 +1630,6 @@ private DownloadListenerHostApiCodec() {} public interface DownloadListenerHostApi { void create(Long instanceId); - void dispose(Long instanceId); - /** The codec used by DownloadListenerHostApi. */ static MessageCodec getCodec() { return DownloadListenerHostApiCodec.INSTANCE; @@ -1703,31 +1665,6 @@ static void setup(BinaryMessenger binaryMessenger, DownloadListenerHostApi api) channel.setMessageHandler(null); } } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.DownloadListenerHostApi.dispose", getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - Map wrapped = new HashMap<>(); - try { - ArrayList args = (ArrayList) message; - Number instanceIdArg = (Number) args.get(0); - if (instanceIdArg == null) { - throw new NullPointerException("instanceIdArg unexpectedly null."); - } - api.dispose(instanceIdArg.longValue()); - wrapped.put("result", null); - } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } } } @@ -1754,6 +1691,17 @@ static MessageCodec getCodec() { return DownloadListenerFlutterApiCodec.INSTANCE; } + public void dispose(Long instanceIdArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.DownloadListenerFlutterApi.dispose", getCodec()); + channel.send( + new ArrayList(Arrays.asList(instanceIdArg)), + channelReply -> { + callback.reply(null); + }); + } + public void onDownloadStart( Long instanceIdArg, String urlArg, @@ -1792,8 +1740,6 @@ private WebChromeClientHostApiCodec() {} public interface WebChromeClientHostApi { void create(Long instanceId, Long webViewClientInstanceId); - void dispose(Long instanceId); - /** The codec used by WebChromeClientHostApi. */ static MessageCodec getCodec() { return WebChromeClientHostApiCodec.INSTANCE; @@ -1833,31 +1779,6 @@ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) { channel.setMessageHandler(null); } } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebChromeClientHostApi.dispose", getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - Map wrapped = new HashMap<>(); - try { - ArrayList args = (ArrayList) message; - Number instanceIdArg = (Number) args.get(0); - if (instanceIdArg == null) { - throw new NullPointerException("instanceIdArg unexpectedly null."); - } - api.dispose(instanceIdArg.longValue()); - wrapped.put("result", null); - } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } } } @@ -1884,6 +1805,17 @@ static MessageCodec getCodec() { return WebChromeClientFlutterApiCodec.INSTANCE; } + public void dispose(Long instanceIdArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.WebChromeClientFlutterApi.dispose", getCodec()); + channel.send( + new ArrayList(Arrays.asList(instanceIdArg)), + channelReply -> { + callback.reply(null); + }); + } + public void onProgressChanged( Long instanceIdArg, Long webViewInstanceIdArg, Long progressArg, Reply callback) { BasicMessageChannel channel = diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java index bfa7d6f17345..a368baf266dd 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java @@ -8,32 +8,77 @@ import java.util.HashMap; import java.util.Map; -class InstanceManager { +/** + * Maintains instances to intercommunicate with Dart objects. + * + *

When an instance is added with an instanceId, either can be used to retrieve the other. + */ +public class InstanceManager { private final LongSparseArray instanceIdsToInstances = new LongSparseArray<>(); private final Map instancesToInstanceIds = new HashMap<>(); - /** Add a new instance with instanceId. */ - void addInstance(Object instance, long instanceId) { + /** + * Add a new instance to the manager. + * + *

If an instance or instanceId has already been added, it will be replaced by the new values. + * + * @param instance the new object to be added + * @param instanceId unique id of the added object + */ + public void addInstance(Object instance, long instanceId) { instancesToInstanceIds.put(instance, instanceId); instanceIdsToInstances.append(instanceId, instance); } - /** Remove the instance from the manager. */ - void removeInstance(long instanceId) { + /** + * Remove the instance with instanceId from the manager. + * + * @param instanceId the id of the instance to be removed + * @return the removed instance if the manager contains the instanceId, otherwise null + */ + public Object removeInstanceWithId(long instanceId) { final Object instance = instanceIdsToInstances.get(instanceId); if (instance != null) { instanceIdsToInstances.remove(instanceId); instancesToInstanceIds.remove(instance); } + return instance; } - /** Retrieve the Object paired with instanceId. */ - Object getInstance(long instanceId) { + /** + * Remove the instance from the manager. + * + * @param instance the instance to be removed + * @return the instanceId of the removed instance if the manager contains the value, otherwise + * null + */ + public Long removeInstance(Object instance) { + final Long instanceId = instancesToInstanceIds.get(instance); + if (instanceId != null) { + instanceIdsToInstances.remove(instanceId); + instancesToInstanceIds.remove(instance); + } + return instanceId; + } + + /** + * Retrieve the Object paired with instanceId. + * + * @param instanceId the instanceId of the desired instance + * @return the instance stored with the instanceId if the manager contains the value, otherwise + * null + */ + public Object getInstance(long instanceId) { return instanceIdsToInstances.get(instanceId); } - /** Retrieve the instanceId paired with instance. */ - Long getInstanceId(Object instance) { + /** + * Retrieve the instanceId paired with an instance. + * + * @param instance the value paired with the desired instanceId + * @return the instanceId paired with instance if the manager contains the value, otherwise null + */ + public Long getInstanceId(Object instance) { return instancesToInstanceIds.get(instance); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java index 2f987c0f86b3..96ae3ce1fd4d 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java @@ -7,6 +7,8 @@ import android.os.Handler; import android.os.Looper; import android.webkit.JavascriptInterface; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; @@ -14,13 +16,16 @@ * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets * up. * - *

Exposes a single method named `postMessage` to JavaScript, which sends a message over a method - * channel to the Dart code. + *

Exposes a single method named `postMessage` to JavaScript, which sends a message to the Dart + * code. + * + *

No messages are sent to Dart after {@link JavaScriptChannel#release} is called. */ -class JavaScriptChannel { +public class JavaScriptChannel implements Releasable { private final MethodChannel methodChannel; - final String javaScriptChannelName; private final Handler platformThreadHandler; + final String javaScriptChannelName; + @Nullable private JavaScriptChannelFlutterApiImpl flutterApi; /** * @param methodChannel the Flutter WebView method channel to which JS messages are sent @@ -35,24 +40,58 @@ class JavaScriptChannel { this.platformThreadHandler = platformThreadHandler; } + /** + * Creates a {@link JavaScriptChannel} that passes arguments of callback methods to Dart. + * + * @param flutterApi the Flutter Api to which JS messages are sent + * @param channelName JavaScript channel the message was sent through + * @param platformThreadHandler handles making callbacks on the desired thread + */ + public JavaScriptChannel( + @NonNull JavaScriptChannelFlutterApiImpl flutterApi, + String channelName, + Handler platformThreadHandler) { + this.flutterApi = flutterApi; + this.javaScriptChannelName = channelName; + this.platformThreadHandler = platformThreadHandler; + methodChannel = null; + } + // Suppressing unused warning as this is invoked from JavaScript. @SuppressWarnings("unused") @JavascriptInterface public void postMessage(final String message) { - Runnable postMessageRunnable = - new Runnable() { - @Override - public void run() { - HashMap arguments = new HashMap<>(); - arguments.put("channel", javaScriptChannelName); - arguments.put("message", message); - methodChannel.invokeMethod("javascriptChannelMessage", arguments); - } - }; - if (platformThreadHandler.getLooper() == Looper.myLooper()) { - postMessageRunnable.run(); - } else { - platformThreadHandler.post(postMessageRunnable); + Runnable postMessageRunnable = null; + + if (flutterApi != null) { + postMessageRunnable = () -> flutterApi.postMessage(this, message, reply -> {}); + } else if (methodChannel != null) { + postMessageRunnable = + new Runnable() { + @Override + public void run() { + HashMap arguments = new HashMap<>(); + arguments.put("channel", javaScriptChannelName); + arguments.put("message", message); + methodChannel.invokeMethod("javascriptChannelMessage", arguments); + } + }; + } + + if (postMessageRunnable != null) { + if (platformThreadHandler.getLooper() == Looper.myLooper()) { + postMessageRunnable.run(); + } else { + platformThreadHandler.post(postMessageRunnable); + } + } + } + + @Override + public void release() { + if (flutterApi != null) { + flutterApi.dispose(this, reply -> {}); } + flutterApi = null; } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java new file mode 100644 index 000000000000..120f66dbdf9a --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java @@ -0,0 +1,50 @@ +// 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 io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelFlutterApi; + +/** + * Flutter Api implementation for {@link JavaScriptChannel}. + * + *

Passes arguments of callbacks methods from a {@link JavaScriptChannel} to Dart. + */ +public class JavaScriptChannelFlutterApiImpl extends JavaScriptChannelFlutterApi { + private final InstanceManager instanceManager; + + /** + * Creates a Flutter api that sends messages to Dart. + * + * @param binaryMessenger Handles sending messages to Dart. + * @param instanceManager Maintains instances stored to communicate with Dart objects. + */ + public JavaScriptChannelFlutterApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + /** Passes arguments from {@link JavaScriptChannel#postMessage} to Dart. */ + public void postMessage( + JavaScriptChannel javaScriptChannel, String messageArg, Reply callback) { + super.postMessage(instanceManager.getInstanceId(javaScriptChannel), messageArg, callback); + } + + /** + * Communicates to Dart that the reference to a {@link JavaScriptChannel} was removed. + * + * @param javaScriptChannel The instance whose reference will be removed. + * @param callback Reply callback with return value from Dart. + */ + public void dispose(JavaScriptChannel javaScriptChannel, Reply callback) { + final Long instanceId = instanceManager.removeInstance(javaScriptChannel); + if (instanceId != null) { + dispose(instanceId, callback); + } else { + callback.reply(null); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java index 2d42d952955c..ac67a817179e 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java @@ -5,44 +5,53 @@ package io.flutter.plugins.webviewflutter; import android.os.Handler; -import android.os.Looper; -import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelFlutterApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi; -class JavaScriptChannelHostApiImpl implements GeneratedAndroidWebView.JavaScriptChannelHostApi { +/** + * Host api implementation for {@link JavaScriptChannel}. + * + *

Handles creating {@link JavaScriptChannel}s that intercommunicate with a paired Dart object. + */ +public class JavaScriptChannelHostApiImpl implements JavaScriptChannelHostApi { private final InstanceManager instanceManager; private final JavaScriptChannelCreator javaScriptChannelCreator; - private final JavaScriptChannelFlutterApi javaScriptChannelFlutterApi; + private final JavaScriptChannelFlutterApiImpl flutterApi; private final Handler platformThreadHandler; - static class JavaScriptChannelCreator { - JavaScriptChannel createJavaScriptChannel( - Long instanceId, - JavaScriptChannelFlutterApi javaScriptChannelFlutterApi, + /** Handles creating {@link JavaScriptChannel}s for a {@link JavaScriptChannelHostApiImpl}. */ + public static class JavaScriptChannelCreator { + /** + * Creates a {@link JavaScriptChannel}. + * + * @param flutterApi handles sending messages to Dart + * @param channelName JavaScript channel the message should be sent through + * @param platformThreadHandler handles making callbacks on the desired thread + * @return the created {@link JavaScriptChannel} + */ + public JavaScriptChannel createJavaScriptChannel( + JavaScriptChannelFlutterApiImpl flutterApi, String channelName, Handler platformThreadHandler) { - return new JavaScriptChannel(null, channelName, platformThreadHandler) { - @Override - public void postMessage(String message) { - final Runnable postMessageRunnable = - () -> javaScriptChannelFlutterApi.postMessage(instanceId, message, reply -> {}); - if (platformThreadHandler.getLooper() == Looper.myLooper()) { - postMessageRunnable.run(); - } else { - platformThreadHandler.post(postMessageRunnable); - } - } - }; + return new JavaScriptChannel(flutterApi, channelName, platformThreadHandler); } } - JavaScriptChannelHostApiImpl( + /** + * Creates a host API that handles creating {@link JavaScriptChannel}s. + * + * @param instanceManager maintains instances stored to communicate with Dart objects + * @param javaScriptChannelCreator handles creating {@link JavaScriptChannel}s + * @param flutterApi handles sending messages to Dart + * @param platformThreadHandler handles making callbacks on the desired thread + */ + public JavaScriptChannelHostApiImpl( InstanceManager instanceManager, JavaScriptChannelCreator javaScriptChannelCreator, - JavaScriptChannelFlutterApi javaScriptChannelFlutterApi, + JavaScriptChannelFlutterApiImpl flutterApi, Handler platformThreadHandler) { this.instanceManager = instanceManager; this.javaScriptChannelCreator = javaScriptChannelCreator; - this.javaScriptChannelFlutterApi = javaScriptChannelFlutterApi; + this.flutterApi = flutterApi; this.platformThreadHandler = platformThreadHandler; } @@ -50,12 +59,7 @@ public void postMessage(String message) { public void create(Long instanceId, String channelName) { final JavaScriptChannel javaScriptChannel = javaScriptChannelCreator.createJavaScriptChannel( - instanceId, javaScriptChannelFlutterApi, channelName, platformThreadHandler); + flutterApi, channelName, platformThreadHandler); instanceManager.addInstance(javaScriptChannel, instanceId); } - - @Override - public void dispose(Long instanceId) { - instanceManager.removeInstance(instanceId); - } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/Releasable.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/Releasable.java new file mode 100644 index 000000000000..9c4ed7650640 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/Releasable.java @@ -0,0 +1,14 @@ +// 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; + +/** + * Represents a resource, or a holder of resources, which may be released once they are no longer + * needed. + */ +interface Releasable { + /** Notify that that the reference to an object will be removed by a holder. */ + void release(); +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java new file mode 100644 index 000000000000..2ab9275b41c3 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java @@ -0,0 +1,56 @@ +// 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.WebChromeClient; +import android.webkit.WebView; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientFlutterApi; + +/** + * Flutter Api implementation for {@link WebChromeClient}. + * + *

Passes arguments of callbacks methods from a {@link WebChromeClient} to Dart. + */ +public class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { + private final InstanceManager instanceManager; + + /** + * Creates a Flutter api that sends messages to Dart. + * + * @param binaryMessenger handles sending messages to Dart + * @param instanceManager maintains instances stored to communicate with Dart objects + */ + public WebChromeClientFlutterApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + /** Passes arguments from {@link WebChromeClient#onProgressChanged} to Dart. */ + public void onProgressChanged( + WebChromeClient webChromeClient, WebView webView, Long progress, Reply callback) { + super.onProgressChanged( + instanceManager.getInstanceId(webChromeClient), + instanceManager.getInstanceId(webView), + progress, + callback); + } + + /** + * Communicates to Dart that the reference to a {@link WebChromeClient}} was removed. + * + * @param webChromeClient the instance whose reference will be removed + * @param callback reply callback with return value from Dart + */ + public void dispose(WebChromeClient webChromeClient, Reply callback) { + final Long instanceId = instanceManager.removeInstance(webChromeClient); + if (instanceId != null) { + dispose(instanceId, callback); + } else { + callback.reply(null); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java index 32f8fcbdeed9..f8bc5129a3ca 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java @@ -11,68 +11,125 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientFlutterApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi; -class WebChromeClientHostApiImpl implements GeneratedAndroidWebView.WebChromeClientHostApi { +/** + * Host api implementation for {@link WebChromeClient}. + * + *

Handles creating {@link WebChromeClient}s that intercommunicate with a paired Dart object. + */ +public class WebChromeClientHostApiImpl implements WebChromeClientHostApi { private final InstanceManager instanceManager; private final WebChromeClientCreator webChromeClientCreator; - private final WebChromeClientFlutterApi webChromeClientFlutterApi; - - static class WebChromeClientCreator { - WebChromeClient createWebChromeClient( - Long instanceId, - InstanceManager instanceManager, - WebViewClient webViewClient, - WebChromeClientFlutterApi webChromeClientFlutterApi) { - return new WebChromeClient() { - // Verifies that a url opened by `Window.open` has a secure url. - @Override - public boolean onCreateWindow( - final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { - final WebViewClient newWindowWebViewClient = - new WebViewClient() { - @RequiresApi(api = Build.VERSION_CODES.N) - @Override - public boolean shouldOverrideUrlLoading( - @NonNull WebView view, @NonNull WebResourceRequest request) { - webViewClient.shouldOverrideUrlLoading(view, request); - return true; - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - webViewClient.shouldOverrideUrlLoading(view, url); - return true; - } - }; - - final WebView newWebView = new WebView(view.getContext()); - newWebView.setWebViewClient(newWindowWebViewClient); - - final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; - transport.setWebView(newWebView); - resultMsg.sendToTarget(); - - return true; - } - - @Override - public void onProgressChanged(WebView view, int progress) { - webChromeClientFlutterApi.onProgressChanged( - instanceId, instanceManager.getInstanceId(view), (long) progress, reply -> {}); - } - }; + private final WebChromeClientFlutterApiImpl flutterApi; + + /** + * Implementation of {@link WebChromeClient} that passes arguments of callback methods to Dart. + */ + public static class WebChromeClientImpl extends WebChromeClient implements Releasable { + @Nullable private WebChromeClientFlutterApiImpl flutterApi; + private WebViewClient webViewClient; + + /** + * Creates a {@link WebChromeClient} that passes arguments of callbacks methods to Dart. + * + * @param flutterApi handles sending messages to Dart + * @param webViewClient receives forwarded calls from {@link WebChromeClient#onCreateWindow} + */ + public WebChromeClientImpl( + @NonNull WebChromeClientFlutterApiImpl flutterApi, WebViewClient webViewClient) { + this.flutterApi = flutterApi; + this.webViewClient = webViewClient; + } + + // Verifies that a url opened by `Window.open` has a secure url. + @Override + public boolean onCreateWindow( + final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { + final WebViewClient newWindowWebViewClient = + new WebViewClient() { + @RequiresApi(api = Build.VERSION_CODES.N) + @Override + public boolean shouldOverrideUrlLoading( + @NonNull WebView view, @NonNull WebResourceRequest request) { + webViewClient.shouldOverrideUrlLoading(view, request); + return true; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + webViewClient.shouldOverrideUrlLoading(view, url); + return true; + } + }; + + final WebView newWebView = new WebView(view.getContext()); + newWebView.setWebViewClient(newWindowWebViewClient); + + final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; + transport.setWebView(newWebView); + resultMsg.sendToTarget(); + + return true; + } + + @Override + public void onProgressChanged(WebView view, int progress) { + if (flutterApi != null) { + flutterApi.onProgressChanged(this, view, (long) progress, reply -> {}); + } + } + + /** + * Set the {@link WebViewClient} that calls to {@link WebChromeClient#onCreateWindow} are passed + * to. + * + * @param webViewClient the forwarding {@link WebViewClient} + */ + public void setWebViewClient(WebViewClient webViewClient) { + this.webViewClient = webViewClient; + } + + @Override + public void release() { + if (flutterApi != null) { + flutterApi.dispose(this, reply -> {}); + } + flutterApi = null; + } + } + + /** Handles creating {@link WebChromeClient}s for a {@link WebChromeClientHostApiImpl}. */ + public static class WebChromeClientCreator { + /** + * Creates a {@link DownloadListenerHostApiImpl.DownloadListenerImpl}. + * + * @param flutterApi handles sending messages to Dart + * @param webViewClient receives forwarded calls from {@link WebChromeClient#onCreateWindow} + * @return the created {@link DownloadListenerHostApiImpl.DownloadListenerImpl} + */ + public WebChromeClientImpl createWebChromeClient( + WebChromeClientFlutterApiImpl flutterApi, WebViewClient webViewClient) { + return new WebChromeClientImpl(flutterApi, webViewClient); } } - WebChromeClientHostApiImpl( + /** + * Creates a host API that handles creating {@link WebChromeClient}s. + * + * @param instanceManager maintains instances stored to communicate with Dart objects + * @param webChromeClientCreator handles creating {@link WebChromeClient}s + * @param flutterApi handles sending messages to Dart + */ + public WebChromeClientHostApiImpl( InstanceManager instanceManager, WebChromeClientCreator webChromeClientCreator, - WebChromeClientFlutterApi webChromeClientFlutterApi) { + WebChromeClientFlutterApiImpl flutterApi) { this.instanceManager = instanceManager; this.webChromeClientCreator = webChromeClientCreator; - this.webChromeClientFlutterApi = webChromeClientFlutterApi; + this.flutterApi = flutterApi; } @Override @@ -80,13 +137,7 @@ public void create(Long instanceId, Long webViewClientInstanceId) { final WebViewClient webViewClient = (WebViewClient) instanceManager.getInstance(webViewClientInstanceId); final WebChromeClient webChromeClient = - webChromeClientCreator.createWebChromeClient( - instanceId, instanceManager, webViewClient, webChromeClientFlutterApi); + webChromeClientCreator.createWebChromeClient(flutterApi, webViewClient); instanceManager.addInstance(webChromeClient, instanceId); } - - @Override - public void dispose(Long instanceId) { - instanceManager.removeInstance(instanceId); - } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java index e70a867c23ff..239ef473b546 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java @@ -6,18 +6,38 @@ import android.webkit.WebSettings; import android.webkit.WebView; - -class WebSettingsHostApiImpl implements GeneratedAndroidWebView.WebSettingsHostApi { +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebSettingsHostApi; + +/** + * Host api implementation for {@link WebSettings}. + * + *

Handles creating {@link WebSettings}s that intercommunicate with a paired Dart object. + */ +public class WebSettingsHostApiImpl implements WebSettingsHostApi { private final InstanceManager instanceManager; private final WebSettingsCreator webSettingsCreator; - static class WebSettingsCreator { - WebSettings createWebSettings(WebView webView) { + /** Handles creating {@link WebSettings} for a {@link WebSettingsHostApiImpl}. */ + public static class WebSettingsCreator { + /** + * Creates a {@link WebSettings}. + * + * @param webView the {@link WebView} which the settings affect + * @return the created {@link WebSettings} + */ + public WebSettings createWebSettings(WebView webView) { return webView.getSettings(); } } - WebSettingsHostApiImpl(InstanceManager instanceManager, WebSettingsCreator webSettingsCreator) { + /** + * Creates a host API that handles creating {@link WebSettings} and invoke its methods. + * + * @param instanceManager maintains instances stored to communicate with Dart objects + * @param webSettingsCreator handles creating {@link WebSettings}s + */ + public WebSettingsHostApiImpl( + InstanceManager instanceManager, WebSettingsCreator webSettingsCreator) { this.instanceManager = instanceManager; this.webSettingsCreator = webSettingsCreator; } @@ -30,7 +50,7 @@ public void create(Long instanceId, Long webViewInstanceId) { @Override public void dispose(Long instanceId) { - instanceManager.removeInstance(instanceId); + instanceManager.removeInstanceWithId(instanceId); } @Override diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java new file mode 100644 index 000000000000..9e462faa58a7 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java @@ -0,0 +1,198 @@ +// 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.annotation.SuppressLint; +import android.os.Build; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import androidx.annotation.RequiresApi; +import androidx.webkit.WebResourceErrorCompat; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewClientFlutterApi; + +/** + * Flutter Api implementation for {@link WebViewClient}. + * + *

Passes arguments of callbacks methods from a {@link WebViewClient} to Dart. + */ +public class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { + private final InstanceManager instanceManager; + + @RequiresApi(api = Build.VERSION_CODES.M) + static GeneratedAndroidWebView.WebResourceErrorData createWebResourceErrorData( + WebResourceError error) { + final GeneratedAndroidWebView.WebResourceErrorData errorData = + new GeneratedAndroidWebView.WebResourceErrorData(); + errorData.setErrorCode((long) error.getErrorCode()); + errorData.setDescription(error.getDescription().toString()); + + return errorData; + } + + @SuppressLint("RequiresFeature") + static GeneratedAndroidWebView.WebResourceErrorData createWebResourceErrorData( + WebResourceErrorCompat error) { + final GeneratedAndroidWebView.WebResourceErrorData errorData = + new GeneratedAndroidWebView.WebResourceErrorData(); + errorData.setErrorCode((long) error.getErrorCode()); + errorData.setDescription(error.getDescription().toString()); + + return errorData; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + static GeneratedAndroidWebView.WebResourceRequestData createWebResourceRequestData( + WebResourceRequest request) { + final GeneratedAndroidWebView.WebResourceRequestData requestData = + new GeneratedAndroidWebView.WebResourceRequestData(); + requestData.setUrl(request.getUrl().toString()); + requestData.setIsForMainFrame(request.isForMainFrame()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + requestData.setIsRedirect(request.isRedirect()); + } + requestData.setHasGesture(request.hasGesture()); + requestData.setMethod(request.getMethod()); + requestData.setRequestHeaders(request.getRequestHeaders()); + + return requestData; + } + + /** + * Creates a Flutter api that sends messages to Dart. + * + * @param binaryMessenger handles sending messages to Dart + * @param instanceManager maintains instances stored to communicate with Dart objects + */ + public WebViewClientFlutterApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + /** Passes arguments from {@link WebViewClient#onPageStarted} to Dart. */ + public void onPageStarted( + WebViewClient webViewClient, WebView webView, String urlArg, Reply callback) { + onPageStarted( + instanceManager.getInstanceId(webViewClient), + instanceManager.getInstanceId(webView), + urlArg, + callback); + } + + /** Passes arguments from {@link WebViewClient#onPageFinished} to Dart. */ + public void onPageFinished( + WebViewClient webViewClient, WebView webView, String urlArg, Reply callback) { + onPageFinished( + instanceManager.getInstanceId(webViewClient), + instanceManager.getInstanceId(webView), + urlArg, + callback); + } + + /** + * Passes arguments from {@link WebViewClient#onReceivedError(WebView, WebResourceRequest, + * WebResourceError)} to Dart. + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public void onReceivedRequestError( + WebViewClient webViewClient, + WebView webView, + WebResourceRequest request, + WebResourceError error, + Reply callback) { + onReceivedRequestError( + instanceManager.getInstanceId(webViewClient), + instanceManager.getInstanceId(webView), + createWebResourceRequestData(request), + createWebResourceErrorData(error), + callback); + } + + /** + * Passes arguments from {@link androidx.webkit.WebViewClientCompat#onReceivedError(WebView, + * WebResourceRequest, WebResourceError)} to Dart. + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void onReceivedRequestError( + WebViewClient webViewClient, + WebView webView, + WebResourceRequest request, + WebResourceErrorCompat error, + Reply callback) { + onReceivedRequestError( + instanceManager.getInstanceId(webViewClient), + instanceManager.getInstanceId(webView), + createWebResourceRequestData(request), + createWebResourceErrorData(error), + callback); + } + + /** + * Passes arguments from {@link WebViewClient#onReceivedError(WebView, int, String, String)} to + * Dart. + */ + public void onReceivedError( + WebViewClient webViewClient, + WebView webView, + Long errorCodeArg, + String descriptionArg, + String failingUrlArg, + Reply callback) { + onReceivedError( + instanceManager.getInstanceId(webViewClient), + instanceManager.getInstanceId(webView), + errorCodeArg, + descriptionArg, + failingUrlArg, + callback); + } + + /** + * Passes arguments from {@link WebViewClient#shouldOverrideUrlLoading(WebView, + * WebResourceRequest)} to Dart. + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void requestLoading( + WebViewClient webViewClient, + WebView webView, + WebResourceRequest request, + Reply callback) { + requestLoading( + instanceManager.getInstanceId(webViewClient), + instanceManager.getInstanceId(webView), + createWebResourceRequestData(request), + callback); + } + + /** + * Passes arguments from {@link WebViewClient#shouldOverrideUrlLoading(WebView, String)} to Dart. + */ + public void urlLoading( + WebViewClient webViewClient, WebView webView, String urlArg, Reply callback) { + urlLoading( + instanceManager.getInstanceId(webViewClient), + instanceManager.getInstanceId(webView), + urlArg, + callback); + } + + /** + * Communicates to Dart that the reference to a {@link WebViewClient} was removed. + * + * @param webViewClient the instance whose reference will be removed + * @param callback reply callback with return value from Dart + */ + public void dispose(WebViewClient webViewClient, Reply callback) { + final Long instanceId = instanceManager.removeInstance(webViewClient); + if (instanceId != null) { + dispose(instanceId, callback); + } else { + callback.reply(null); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java index 4d17eb129db8..6b659fae9c0f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java @@ -14,61 +14,200 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.webkit.WebResourceErrorCompat; import androidx.webkit.WebViewClientCompat; -import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewClientFlutterApi; -class WebViewClientHostApiImpl implements GeneratedAndroidWebView.WebViewClientHostApi { +/** + * Host api implementation for {@link WebViewClient}. + * + *

Handles creating {@link WebViewClient}s that intercommunicate with a paired Dart object. + */ +public class WebViewClientHostApiImpl implements GeneratedAndroidWebView.WebViewClientHostApi { private final InstanceManager instanceManager; private final WebViewClientCreator webViewClientCreator; - private final WebViewClientFlutterApi webViewClientFlutterApi; + private final WebViewClientFlutterApiImpl flutterApi; + + /** + * An interface implemented by a class that extends {@link WebViewClient} and {@link Releasable}. + */ + public interface ReleasableWebViewClient extends Releasable {} + + /** Implementation of {@link WebViewClient} that passes arguments of callback methods to Dart. */ + @RequiresApi(Build.VERSION_CODES.N) + public static class WebViewClientImpl extends WebViewClient implements ReleasableWebViewClient { + @Nullable private WebViewClientFlutterApiImpl flutterApi; + private final boolean shouldOverrideUrlLoading; + + /** + * Creates a {@link WebViewClient} that passes arguments of callbacks methods to Dart. + * + * @param flutterApi handles sending messages to Dart + * @param shouldOverrideUrlLoading whether loading a url should be overridden + */ + public WebViewClientImpl( + @NonNull WebViewClientFlutterApiImpl flutterApi, boolean shouldOverrideUrlLoading) { + this.shouldOverrideUrlLoading = shouldOverrideUrlLoading; + this.flutterApi = flutterApi; + } - @RequiresApi(api = Build.VERSION_CODES.M) - static GeneratedAndroidWebView.WebResourceErrorData createWebResourceErrorData( - WebResourceError error) { - final GeneratedAndroidWebView.WebResourceErrorData errorData = - new GeneratedAndroidWebView.WebResourceErrorData(); - errorData.setErrorCode((long) error.getErrorCode()); - errorData.setDescription(error.getDescription().toString()); + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + if (flutterApi != null) { + flutterApi.onPageStarted(this, view, url, reply -> {}); + } + } - return errorData; - } + @Override + public void onPageFinished(WebView view, String url) { + if (flutterApi != null) { + flutterApi.onPageFinished(this, view, url, reply -> {}); + } + } + + @Override + public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { + if (flutterApi != null) { + flutterApi.onReceivedRequestError(this, view, request, error, reply -> {}); + } + } + + @Override + public void onReceivedError( + WebView view, int errorCode, String description, String failingUrl) { + if (flutterApi != null) { + flutterApi.onReceivedError( + this, view, (long) errorCode, description, failingUrl, reply -> {}); + } + } - @SuppressLint("RequiresFeature") - static GeneratedAndroidWebView.WebResourceErrorData createWebResourceErrorData( - WebResourceErrorCompat error) { - final GeneratedAndroidWebView.WebResourceErrorData errorData = - new GeneratedAndroidWebView.WebResourceErrorData(); - errorData.setErrorCode((long) error.getErrorCode()); - errorData.setDescription(error.getDescription().toString()); + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + if (flutterApi != null) { + flutterApi.requestLoading(this, view, request, reply -> {}); + } + return shouldOverrideUrlLoading; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (flutterApi != null) { + flutterApi.urlLoading(this, view, url, reply -> {}); + } + return shouldOverrideUrlLoading; + } + + @Override + public void onUnhandledKeyEvent(WebView view, KeyEvent event) { + // Deliberately empty. Occasionally the webview will mark events as having failed to be + // handled even though they were handled. We don't want to propagate those as they're not + // truly lost. + } - return errorData; + public void release() { + if (flutterApi != null) { + flutterApi.dispose(this, reply -> {}); + } + flutterApi = null; + } } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - static GeneratedAndroidWebView.WebResourceRequestData createWebResourceRequestData( - WebResourceRequest request) { - final GeneratedAndroidWebView.WebResourceRequestData requestData = - new GeneratedAndroidWebView.WebResourceRequestData(); - requestData.setUrl(request.getUrl().toString()); - requestData.setIsForMainFrame(request.isForMainFrame()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - requestData.setIsRedirect(request.isRedirect()); - } - requestData.setHasGesture(request.hasGesture()); - requestData.setMethod(request.getMethod()); - requestData.setRequestHeaders(request.getRequestHeaders()); - - return requestData; + /** + * Implementation of {@link WebViewClientCompat} that passes arguments of callback methods to + * Dart. + */ + public static class WebViewClientCompatImpl extends WebViewClientCompat + implements ReleasableWebViewClient { + private @Nullable WebViewClientFlutterApiImpl flutterApi; + private final boolean shouldOverrideUrlLoading; + + public WebViewClientCompatImpl( + @NonNull WebViewClientFlutterApiImpl flutterApi, boolean shouldOverrideUrlLoading) { + this.shouldOverrideUrlLoading = shouldOverrideUrlLoading; + this.flutterApi = flutterApi; + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + if (flutterApi != null) { + flutterApi.onPageStarted(this, view, url, reply -> {}); + } + } + + @Override + public void onPageFinished(WebView view, String url) { + if (flutterApi != null) { + flutterApi.onPageFinished(this, view, url, reply -> {}); + } + } + + // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is + // enabled. The deprecated method is called when a device doesn't support this. + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @SuppressLint("RequiresFeature") + @Override + public void onReceivedError( + @NonNull WebView view, + @NonNull WebResourceRequest request, + @NonNull WebResourceErrorCompat error) { + if (flutterApi != null) { + flutterApi.onReceivedRequestError(this, view, request, error, reply -> {}); + } + } + + @Override + public void onReceivedError( + WebView view, int errorCode, String description, String failingUrl) { + if (flutterApi != null) { + flutterApi.onReceivedError( + this, view, (long) errorCode, description, failingUrl, reply -> {}); + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean shouldOverrideUrlLoading( + @NonNull WebView view, @NonNull WebResourceRequest request) { + if (flutterApi != null) { + flutterApi.requestLoading(this, view, request, reply -> {}); + } + return shouldOverrideUrlLoading; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (flutterApi != null) { + flutterApi.urlLoading(this, view, url, reply -> {}); + } + return shouldOverrideUrlLoading; + } + + @Override + public void onUnhandledKeyEvent(WebView view, KeyEvent event) { + // Deliberately empty. Occasionally the webview will mark events as having failed to be + // handled even though they were handled. We don't want to propagate those as they're not + // truly lost. + } + + public void release() { + if (flutterApi != null) { + flutterApi.dispose(this, reply -> {}); + } + flutterApi = null; + } } - static class WebViewClientCreator { - WebViewClient createWebViewClient( - Long instanceId, - InstanceManager instanceManager, - Boolean shouldOverrideUrlLoading, - WebViewClientFlutterApi webViewClientFlutterApi) { + /** Handles creating {@link WebViewClient}s for a {@link WebViewClientHostApiImpl}. */ + public static class WebViewClientCreator { + /** + * Creates a {@link WebViewClient}. + * + * @param flutterApi handles sending messages to Dart + * @return the created {@link WebViewClient} + */ + public WebViewClient createWebViewClient( + WebViewClientFlutterApiImpl flutterApi, boolean shouldOverrideUrlLoading) { // WebViewClientCompat is used to get // shouldOverrideUrlLoading(WebView view, WebResourceRequest request) // invoked by the webview on older Android devices, without it pages that use iframes will @@ -78,162 +217,33 @@ WebViewClient createWebViewClient( // to bug https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see // https://github.com/flutter/flutter/issues/29446. if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return new WebViewClient() { - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - webViewClientFlutterApi.onPageStarted( - instanceId, instanceManager.getInstanceId(view), url, reply -> {}); - } - - @Override - public void onPageFinished(WebView view, String url) { - webViewClientFlutterApi.onPageFinished( - instanceId, instanceManager.getInstanceId(view), url, reply -> {}); - } - - @Override - public void onReceivedError( - WebView view, WebResourceRequest request, WebResourceError error) { - webViewClientFlutterApi.onReceivedRequestError( - instanceId, - instanceManager.getInstanceId(view), - createWebResourceRequestData(request), - createWebResourceErrorData(error), - reply -> {}); - } - - @SuppressWarnings("deprecation") - @Override - public void onReceivedError( - WebView view, int errorCode, String description, String failingUrl) { - webViewClientFlutterApi.onReceivedError( - instanceId, - instanceManager.getInstanceId(view), - (long) errorCode, - description, - failingUrl, - reply -> {}); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - webViewClientFlutterApi.requestLoading( - instanceId, - instanceManager.getInstanceId(view), - createWebResourceRequestData(request), - reply -> {}); - return shouldOverrideUrlLoading; - } - - @SuppressWarnings("deprecation") - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - webViewClientFlutterApi.urlLoading( - instanceId, instanceManager.getInstanceId(view), url, reply -> {}); - return shouldOverrideUrlLoading; - } - - @Override - public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - // Deliberately empty. Occasionally the webview will mark events as having failed to be - // handled even though they were handled. We don't want to propagate those as they're not - // truly lost. - } - }; + return new WebViewClientImpl(flutterApi, shouldOverrideUrlLoading); } else { - return new WebViewClientCompat() { - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - webViewClientFlutterApi.onPageStarted( - instanceId, instanceManager.getInstanceId(view), url, reply -> {}); - } - - @Override - public void onPageFinished(WebView view, String url) { - webViewClientFlutterApi.onPageFinished( - instanceId, instanceManager.getInstanceId(view), url, reply -> {}); - } - - // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is - // enabled. The deprecated method is called when a device doesn't support this. - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - @SuppressLint("RequiresFeature") - @Override - public void onReceivedError( - @NonNull WebView view, - @NonNull WebResourceRequest request, - @NonNull WebResourceErrorCompat error) { - webViewClientFlutterApi.onReceivedRequestError( - instanceId, - instanceManager.getInstanceId(view), - createWebResourceRequestData(request), - createWebResourceErrorData(error), - reply -> {}); - } - - @SuppressWarnings("deprecation") - @Override - public void onReceivedError( - WebView view, int errorCode, String description, String failingUrl) { - webViewClientFlutterApi.onReceivedError( - instanceId, - instanceManager.getInstanceId(view), - (long) errorCode, - description, - failingUrl, - reply -> {}); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public boolean shouldOverrideUrlLoading( - @NonNull WebView view, @NonNull WebResourceRequest request) { - webViewClientFlutterApi.requestLoading( - instanceId, - instanceManager.getInstanceId(view), - createWebResourceRequestData(request), - reply -> {}); - return shouldOverrideUrlLoading; - } - - @SuppressWarnings("deprecation") - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - webViewClientFlutterApi.urlLoading( - instanceId, instanceManager.getInstanceId(view), url, reply -> {}); - return shouldOverrideUrlLoading; - } - - @Override - public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - // Deliberately empty. Occasionally the webview will mark events as having failed to be - // handled even though they were handled. We don't want to propagate those as they're not - // truly lost. - } - }; + return new WebViewClientCompatImpl(flutterApi, shouldOverrideUrlLoading); } } } - WebViewClientHostApiImpl( + /** + * Creates a host API that handles creating {@link WebViewClient}s. + * + * @param instanceManager maintains instances stored to communicate with Dart objects + * @param webViewClientCreator handles creating {@link WebViewClient}s + * @param flutterApi handles sending messages to Dart + */ + public WebViewClientHostApiImpl( InstanceManager instanceManager, WebViewClientCreator webViewClientCreator, - WebViewClientFlutterApi webViewClientFlutterApi) { + WebViewClientFlutterApiImpl flutterApi) { this.instanceManager = instanceManager; this.webViewClientCreator = webViewClientCreator; - this.webViewClientFlutterApi = webViewClientFlutterApi; + this.flutterApi = flutterApi; } @Override public void create(Long instanceId, Boolean shouldOverrideUrlLoading) { final WebViewClient webViewClient = - webViewClientCreator.createWebViewClient( - instanceId, instanceManager, shouldOverrideUrlLoading, webViewClientFlutterApi); + webViewClientCreator.createWebViewClient(flutterApi, shouldOverrideUrlLoading); instanceManager.addInstance(webViewClient, instanceId); } - - @Override - public void dispose(Long instanceId) { - instanceManager.removeInstance(instanceId); - } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java index 35bdc608d6ff..2d96fc3f8a60 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java @@ -4,36 +4,118 @@ package io.flutter.plugins.webviewflutter; +import android.annotation.SuppressLint; import android.content.Context; +import android.hardware.display.DisplayManager; import android.view.View; import android.webkit.DownloadListener; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerImpl; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewHostApi; +import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientImpl; +import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.ReleasableWebViewClient; +import java.util.HashMap; import java.util.Map; -class WebViewHostApiImpl implements GeneratedAndroidWebView.WebViewHostApi { +/** + * Host api implementation for {@link WebView}. + * + *

Handles creating {@link WebView}s that intercommunicate with a paired Dart object. + */ +public class WebViewHostApiImpl implements WebViewHostApi { + // TODO(bparrishMines): This can be removed once pigeon supports null values: https://github.com/flutter/flutter/issues/59118 + // Workaround to represent null Strings since pigeon doesn't support null + // values. + private static final String nullStringIdentifier = ""; + private final InstanceManager instanceManager; private final WebViewProxy webViewProxy; private final Context context; - - static class WebViewProxy { - WebView createWebView(Context context) { + // Only used with WebView using virtual displays. + @Nullable private final View containerView; + + /** Handles creating and calling static methods for {@link WebView}s. */ + public static class WebViewProxy { + /** + * Creates a {@link WebViewPlatformView}. + * + * @param context an Activity Context to access application assets + * @return the created {@link WebViewPlatformView} + */ + public WebViewPlatformView createWebView(Context context) { return new WebViewPlatformView(context); } - WebView createInputAwareWebView(Context context) { - return new InputAwareWebViewPlatformView(context, null); + /** + * Creates a {@link InputAwareWebViewPlatformView}. + * + * @param context an Activity Context to access application assets + * @param containerView parent View of the WebView + * @return the created {@link InputAwareWebViewPlatformView} + */ + public InputAwareWebViewPlatformView createInputAwareWebView( + Context context, @Nullable View containerView) { + return new InputAwareWebViewPlatformView(context, containerView); } - void setWebContentsDebuggingEnabled(boolean enabled) { + /** + * Forwards call to {@link WebView#setWebContentsDebuggingEnabled}. + * + * @param enabled whether debugging should be enabled + */ + public void setWebContentsDebuggingEnabled(boolean enabled) { WebView.setWebContentsDebuggingEnabled(enabled); } } - private static class WebViewPlatformView extends WebView implements PlatformView { + private static class ReleasableValue { + @Nullable private T value; + + ReleasableValue() {} + + ReleasableValue(@Nullable T value) { + this.value = value; + } + + void set(@Nullable T newValue) { + release(); + value = newValue; + } + + @Nullable + T get() { + return value; + } + + void release() { + if (value != null) { + value.release(); + } + value = null; + } + } + + /** Implementation of {@link WebView} that can be used as a Flutter {@link PlatformView}s. */ + public static class WebViewPlatformView extends WebView implements PlatformView, Releasable { + private final ReleasableValue + currentWebViewClient = new ReleasableValue<>(); + private final ReleasableValue currentDownloadListener = + new ReleasableValue<>(); + private final ReleasableValue currentWebChromeClient = + new ReleasableValue<>(); + private final Map> javaScriptInterfaces = + new HashMap<>(); + + /** + * Creates a {@link WebViewPlatformView}. + * + * @param context an Activity Context to access application assets. This value cannot be null. + */ public WebViewPlatformView(Context context) { super(context); } @@ -47,11 +129,85 @@ public View getView() { public void dispose() { destroy(); } + + @Override + public void setWebViewClient(WebViewClient webViewClient) { + super.setWebViewClient(webViewClient); + currentWebViewClient.set((ReleasableWebViewClient) webViewClient); + + final WebChromeClientImpl webChromeClient = currentWebChromeClient.get(); + if (webChromeClient != null) { + ((WebChromeClientImpl) webChromeClient).setWebViewClient(webViewClient); + } + } + + @Override + public void setDownloadListener(DownloadListener listener) { + super.setDownloadListener(listener); + currentDownloadListener.set((DownloadListenerImpl) listener); + } + + @Override + public void setWebChromeClient(WebChromeClient client) { + super.setWebChromeClient(client); + currentWebChromeClient.set((WebChromeClientImpl) client); + } + + @SuppressLint("JavascriptInterface") + @Override + public void addJavascriptInterface(Object object, String name) { + super.addJavascriptInterface(object, name); + if (object instanceof JavaScriptChannel) { + final ReleasableValue javaScriptChannel = javaScriptInterfaces.get(name); + if (javaScriptChannel != null && javaScriptChannel.get() != object) { + javaScriptChannel.release(); + } + javaScriptInterfaces.put(name, new ReleasableValue<>((JavaScriptChannel) object)); + } + } + + @Override + public void removeJavascriptInterface(@NonNull String name) { + super.removeJavascriptInterface(name); + final ReleasableValue javaScriptChannel = javaScriptInterfaces.get(name); + javaScriptChannel.release(); + javaScriptInterfaces.remove(name); + } + + @Override + public void release() { + currentWebViewClient.release(); + currentDownloadListener.release(); + currentWebChromeClient.release(); + for (ReleasableValue channel : javaScriptInterfaces.values()) { + channel.release(); + } + javaScriptInterfaces.clear(); + } } - private static class InputAwareWebViewPlatformView extends InputAwareWebView - implements PlatformView { - InputAwareWebViewPlatformView(Context context, View containerView) { + /** + * Implementation of {@link InputAwareWebView} that can be used as a Flutter {@link + * PlatformView}s. + */ + @SuppressLint("ViewConstructor") + public static class InputAwareWebViewPlatformView extends InputAwareWebView + implements PlatformView, Releasable { + private final ReleasableValue + currentWebViewClient = new ReleasableValue<>(); + private final ReleasableValue currentDownloadListener = + new ReleasableValue<>(); + private final ReleasableValue currentWebChromeClient = + new ReleasableValue<>(); + private final Map> javaScriptInterfaces = + new HashMap<>(); + + /** + * Creates a {@link InputAwareWebViewPlatformView}. + * + * @param context an Activity Context to access application assets. This value cannot be null. + */ + public InputAwareWebViewPlatformView(Context context, View containerView) { super(context, containerView); } @@ -72,7 +228,7 @@ public void onFlutterViewDetached() { @Override public void dispose() { - dispose(); + super.dispose(); destroy(); } @@ -85,26 +241,104 @@ public void onInputConnectionLocked() { public void onInputConnectionUnlocked() { unlockInputConnection(); } + + @Override + public void setWebViewClient(WebViewClient webViewClient) { + super.setWebViewClient(webViewClient); + currentWebViewClient.set((ReleasableWebViewClient) webViewClient); + + final WebChromeClientImpl webChromeClient = currentWebChromeClient.get(); + if (webChromeClient != null) { + webChromeClient.setWebViewClient(webViewClient); + } + } + + @Override + public void setDownloadListener(DownloadListener listener) { + super.setDownloadListener(listener); + currentDownloadListener.set((DownloadListenerImpl) listener); + } + + @Override + public void setWebChromeClient(WebChromeClient client) { + super.setWebChromeClient(client); + currentWebChromeClient.set((WebChromeClientImpl) client); + } + + @SuppressLint("JavascriptInterface") + @Override + public void addJavascriptInterface(Object object, String name) { + super.addJavascriptInterface(object, name); + if (object instanceof JavaScriptChannel) { + final ReleasableValue javaScriptChannel = javaScriptInterfaces.get(name); + if (javaScriptChannel != null && javaScriptChannel.get() != object) { + javaScriptChannel.release(); + } + javaScriptInterfaces.put(name, new ReleasableValue<>((JavaScriptChannel) object)); + } + } + + @Override + public void removeJavascriptInterface(@NonNull String name) { + super.removeJavascriptInterface(name); + final ReleasableValue javaScriptChannel = javaScriptInterfaces.get(name); + javaScriptChannel.release(); + javaScriptInterfaces.remove(name); + } + + @Override + public void release() { + currentWebViewClient.release(); + currentDownloadListener.release(); + currentWebChromeClient.release(); + for (ReleasableValue channel : javaScriptInterfaces.values()) { + channel.release(); + } + javaScriptInterfaces.clear(); + } } - WebViewHostApiImpl(InstanceManager instanceManager, WebViewProxy webViewProxy, Context context) { + /** + * Creates a host API that handles creating {@link WebView}s and invoking its methods. + * + * @param instanceManager maintains instances stored to communicate with Dart objects + * @param webViewProxy handles creating {@link WebView}s and calling its static methods + * @param context an Activity Context to access application assets. This value cannot be null. + * @param containerView parent of the webView + */ + public WebViewHostApiImpl( + InstanceManager instanceManager, + WebViewProxy webViewProxy, + Context context, + @Nullable View containerView) { this.instanceManager = instanceManager; this.webViewProxy = webViewProxy; this.context = context; + this.containerView = containerView; } @Override public void create(Long instanceId, Boolean useHybridComposition) { + DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); + DisplayManager displayManager = + (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + displayListenerProxy.onPreWebViewInitialization(displayManager); + final WebView webView = useHybridComposition ? webViewProxy.createWebView(context) - : webViewProxy.createInputAwareWebView(context); + : webViewProxy.createInputAwareWebView(context, containerView); + + displayListenerProxy.onPostWebViewInitialization(displayManager); instanceManager.addInstance(webView, instanceId); } @Override public void dispose(Long instanceId) { - instanceManager.removeInstance(instanceId); + final WebView instance = (WebView) instanceManager.removeInstanceWithId(instanceId); + if (instance != null) { + ((Releasable) instance).release(); + } } @Override @@ -116,7 +350,8 @@ public void loadUrl(Long instanceId, String url, Map headers) { @Override public String getUrl(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); - return webView.getUrl(); + final String result = webView.getUrl(); + return result != null ? result : nullStringIdentifier; } @Override @@ -165,7 +400,8 @@ public void evaluateJavascript( @Override public String getTitle(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); - return webView.getTitle(); + final String result = webView.getTitle(); + return result != null ? result : nullStringIdentifier; } @Override @@ -228,6 +464,6 @@ public void setDownloadListener(Long instanceId, Long listenerInstanceId) { @Override public void setWebChromeClient(Long instanceId, Long clientInstanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); - webView.setWebChromeClient((WebChromeClient) instanceManager.getInstance(instanceId)); + webView.setWebChromeClient((WebChromeClient) instanceManager.getInstance(clientInstanceId)); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java index 5ba073c7f418..239119375e83 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java @@ -6,11 +6,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.webkit.DownloadListener; import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerCreator; -import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerFlutterApi; +import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerImpl; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -21,45 +23,49 @@ public class DownloadListenerTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); - @Mock public DownloadListenerFlutterApi mockFlutterApi; + @Mock public DownloadListenerFlutterApiImpl mockFlutterApi; - InstanceManager testInstanceManager; - DownloadListenerHostApiImpl testHostApiImpl; - DownloadListener testDownloadListener; + InstanceManager instanceManager; + DownloadListenerHostApiImpl hostApiImpl; + DownloadListenerImpl downloadListener; @Before public void setUp() { - testInstanceManager = new InstanceManager(); + instanceManager = new InstanceManager(); final DownloadListenerCreator downloadListenerCreator = new DownloadListenerCreator() { @Override - DownloadListener createDownloadListener( - Long instanceId, DownloadListenerFlutterApi downloadListenerFlutterApi) { - testDownloadListener = - super.createDownloadListener(instanceId, downloadListenerFlutterApi); - return testDownloadListener; + public DownloadListenerImpl createDownloadListener( + DownloadListenerFlutterApiImpl flutterApi) { + downloadListener = super.createDownloadListener(flutterApi); + return downloadListener; } }; - testHostApiImpl = - new DownloadListenerHostApiImpl( - testInstanceManager, downloadListenerCreator, mockFlutterApi); - testHostApiImpl.create(0L); + hostApiImpl = + new DownloadListenerHostApiImpl(instanceManager, downloadListenerCreator, mockFlutterApi); + hostApiImpl.create(0L); } @Test public void postMessage() { - testDownloadListener.onDownloadStart( + downloadListener.onDownloadStart( "https://www.google.com", "userAgent", "contentDisposition", "mimetype", 54); verify(mockFlutterApi) .onDownloadStart( - eq(0L), + eq(downloadListener), eq("https://www.google.com"), eq("userAgent"), eq("contentDisposition"), eq("mimetype"), eq(54L), any()); + + reset(mockFlutterApi); + downloadListener.release(); + downloadListener.onDownloadStart("", "", "", "", 23); + verify(mockFlutterApi, never()) + .onDownloadStart((DownloadListener) any(), any(), any(), any(), any(), eq(23), any()); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java index 697ea0b70b90..3de81da81bec 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java @@ -6,10 +6,11 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.os.Handler; -import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelFlutterApi; import io.flutter.plugins.webviewflutter.JavaScriptChannelHostApiImpl.JavaScriptChannelCreator; import org.junit.Before; import org.junit.Rule; @@ -21,40 +22,44 @@ public class JavaScriptChannelTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); - @Mock public GeneratedAndroidWebView.JavaScriptChannelFlutterApi mockFlutterApi; + @Mock public JavaScriptChannelFlutterApiImpl mockFlutterApi; - InstanceManager testInstanceManager; - JavaScriptChannelHostApiImpl testHostApiImpl; - JavaScriptChannel testJavaScriptChannel; + InstanceManager instanceManager; + JavaScriptChannelHostApiImpl hostApiImpl; + JavaScriptChannel javaScriptChannel; @Before public void setUp() { - testInstanceManager = new InstanceManager(); + instanceManager = new InstanceManager(); final JavaScriptChannelCreator javaScriptChannelCreator = new JavaScriptChannelCreator() { @Override - JavaScriptChannel createJavaScriptChannel( - Long instanceId, - JavaScriptChannelFlutterApi javaScriptChannelFlutterApi, + public JavaScriptChannel createJavaScriptChannel( + JavaScriptChannelFlutterApiImpl javaScriptChannelFlutterApi, String channelName, Handler platformThreadHandler) { - testJavaScriptChannel = + javaScriptChannel = super.createJavaScriptChannel( - instanceId, javaScriptChannelFlutterApi, channelName, platformThreadHandler); - return testJavaScriptChannel; + javaScriptChannelFlutterApi, channelName, platformThreadHandler); + return javaScriptChannel; } }; - testHostApiImpl = + hostApiImpl = new JavaScriptChannelHostApiImpl( - testInstanceManager, javaScriptChannelCreator, mockFlutterApi, new Handler()); - testHostApiImpl.create(0L, "aChannelName"); + instanceManager, javaScriptChannelCreator, mockFlutterApi, new Handler()); + hostApiImpl.create(0L, "aChannelName"); } @Test public void postMessage() { - testJavaScriptChannel.postMessage("A message post."); - verify(mockFlutterApi).postMessage(eq(0L), eq("A message post."), any()); + javaScriptChannel.postMessage("A message post."); + verify(mockFlutterApi).postMessage(eq(javaScriptChannel), eq("A message post."), any()); + + reset(mockFlutterApi); + javaScriptChannel.release(); + javaScriptChannel.postMessage("a message"); + verify(mockFlutterApi, never()).postMessage((JavaScriptChannel) any(), any(), any()); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java index 5ab3ab10fe03..d6593ab24462 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java @@ -6,13 +6,15 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; -import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientFlutterApi; import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientCreator; +import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientImpl; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -23,45 +25,45 @@ public class WebChromeClientTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); - @Mock public WebChromeClientFlutterApi mockFlutterApi; + @Mock public WebChromeClientFlutterApiImpl mockFlutterApi; @Mock public WebView mockWebView; @Mock public WebViewClient mockWebViewClient; - InstanceManager testInstanceManager; - WebChromeClientHostApiImpl testHostApiImpl; - WebChromeClient testWebChromeClient; + InstanceManager instanceManager; + WebChromeClientHostApiImpl hostApiImpl; + WebChromeClientImpl webChromeClient; @Before public void setUp() { - testInstanceManager = new InstanceManager(); - testInstanceManager.addInstance(mockWebView, 0L); - testInstanceManager.addInstance(mockWebViewClient, 1L); + instanceManager = new InstanceManager(); + instanceManager.addInstance(mockWebView, 0L); + instanceManager.addInstance(mockWebViewClient, 1L); final WebChromeClientCreator webChromeClientCreator = new WebChromeClientCreator() { @Override - WebChromeClient createWebChromeClient( - Long instanceId, - InstanceManager instanceManager, - WebViewClient webViewClient, - WebChromeClientFlutterApi webChromeClientFlutterApi) { - testWebChromeClient = - super.createWebChromeClient( - instanceId, instanceManager, webViewClient, webChromeClientFlutterApi); - return testWebChromeClient; + public WebChromeClientImpl createWebChromeClient( + WebChromeClientFlutterApiImpl flutterApi, WebViewClient webViewClient) { + webChromeClient = super.createWebChromeClient(flutterApi, webViewClient); + return webChromeClient; } }; - testHostApiImpl = - new WebChromeClientHostApiImpl(testInstanceManager, webChromeClientCreator, mockFlutterApi); - testHostApiImpl.create(2L, 1L); + hostApiImpl = + new WebChromeClientHostApiImpl(instanceManager, webChromeClientCreator, mockFlutterApi); + hostApiImpl.create(2L, 1L); } @Test public void onProgressChanged() { - testWebChromeClient.onProgressChanged(mockWebView, 23); - verify(mockFlutterApi).onProgressChanged(eq(2L), eq(0L), eq(23L), any()); + webChromeClient.onProgressChanged(mockWebView, 23); + verify(mockFlutterApi).onProgressChanged(eq(webChromeClient), eq(mockWebView), eq(23L), any()); + + reset(mockFlutterApi); + webChromeClient.release(); + webChromeClient.onProgressChanged(mockWebView, 11); + verify(mockFlutterApi, never()).onProgressChanged((WebChromeClient) any(), any(), any(), any()); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java index f6d2054564e2..62d272366a6f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java @@ -6,11 +6,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.webkit.WebView; import android.webkit.WebViewClient; -import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewClientFlutterApi; +import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.WebViewClientCompatImpl; import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.WebViewClientCreator; import org.junit.Before; import org.junit.Rule; @@ -22,56 +24,76 @@ public class WebViewClientTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); - @Mock public WebViewClientFlutterApi mockFlutterApi; + @Mock public WebViewClientFlutterApiImpl mockFlutterApi; @Mock public WebView mockWebView; - InstanceManager testInstanceManager; - WebViewClientHostApiImpl testHostApiImpl; - WebViewClient testWebViewClient; + InstanceManager instanceManager; + WebViewClientHostApiImpl hostApiImpl; + WebViewClientCompatImpl webViewClient; @Before public void setUp() { - testInstanceManager = new InstanceManager(); - testInstanceManager.addInstance(mockWebView, 0L); + instanceManager = new InstanceManager(); + instanceManager.addInstance(mockWebView, 0L); final WebViewClientCreator webViewClientCreator = new WebViewClientCreator() { @Override - WebViewClient createWebViewClient( - Long instanceId, - InstanceManager instanceManager, - Boolean shouldOverrideUrlLoading, - WebViewClientFlutterApi webViewClientFlutterApi) { - testWebViewClient = - super.createWebViewClient( - instanceId, instanceManager, shouldOverrideUrlLoading, webViewClientFlutterApi); - return testWebViewClient; + public WebViewClient createWebViewClient( + WebViewClientFlutterApiImpl flutterApi, boolean shouldOverrideUrlLoading) { + webViewClient = + (WebViewClientCompatImpl) + super.createWebViewClient(flutterApi, shouldOverrideUrlLoading); + return webViewClient; } }; - testHostApiImpl = - new WebViewClientHostApiImpl(testInstanceManager, webViewClientCreator, mockFlutterApi); - testHostApiImpl.create(1L, true); + hostApiImpl = + new WebViewClientHostApiImpl(instanceManager, webViewClientCreator, mockFlutterApi); + hostApiImpl.create(1L, true); } @Test public void onPageStarted() { - testWebViewClient.onPageStarted(mockWebView, "https://www.google.com", null); - verify(mockFlutterApi).onPageStarted(eq(1L), eq(0L), eq("https://www.google.com"), any()); + webViewClient.onPageStarted(mockWebView, "https://www.google.com", null); + verify(mockFlutterApi) + .onPageStarted(eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), any()); + + reset(mockFlutterApi); + webViewClient.release(); + webViewClient.onPageStarted(mockWebView, "", null); + verify(mockFlutterApi, never()).onPageStarted((WebViewClient) any(), any(), any(), any()); } @Test public void onReceivedError() { - testWebViewClient.onReceivedError(mockWebView, 32, "description", "https://www.google.com"); + webViewClient.onReceivedError(mockWebView, 32, "description", "https://www.google.com"); verify(mockFlutterApi) .onReceivedError( - eq(1L), eq(0L), eq(32L), eq("description"), eq("https://www.google.com"), any()); + eq(webViewClient), + eq(mockWebView), + eq(32L), + eq("description"), + eq("https://www.google.com"), + any()); + + reset(mockFlutterApi); + webViewClient.release(); + webViewClient.onReceivedError(mockWebView, 33, "", ""); + verify(mockFlutterApi, never()) + .onReceivedError((WebViewClient) any(), any(), any(), any(), any(), any()); } @Test public void urlLoading() { - testWebViewClient.shouldOverrideUrlLoading(mockWebView, "https://www.google.com"); - verify(mockFlutterApi).urlLoading(eq(1L), eq(0L), eq("https://www.google.com"), any()); + webViewClient.shouldOverrideUrlLoading(mockWebView, "https://www.google.com"); + verify(mockFlutterApi) + .urlLoading(eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), any()); + + reset(mockFlutterApi); + webViewClient.release(); + webViewClient.shouldOverrideUrlLoading(mockWebView, ""); + verify(mockFlutterApi, never()).urlLoading((WebViewClient) any(), any(), any(), any()); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java index b914ce913e76..e506188d0c9d 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java @@ -13,8 +13,13 @@ import android.content.Context; import android.webkit.DownloadListener; import android.webkit.ValueCallback; -import android.webkit.WebView; +import android.webkit.WebChromeClient; import android.webkit.WebViewClient; +import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerImpl; +import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientImpl; +import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.WebViewClientImpl; +import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.InputAwareWebViewPlatformView; +import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.WebViewPlatformView; import java.util.HashMap; import org.junit.Before; import org.junit.Rule; @@ -27,7 +32,7 @@ public class WebViewTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); - @Mock public WebView mockWebView; + @Mock public WebViewPlatformView mockWebView; @Mock WebViewHostApiImpl.WebViewProxy mockWebViewProxy; @@ -40,45 +45,115 @@ public class WebViewTest { public void setUp() { testInstanceManager = new InstanceManager(); when(mockWebViewProxy.createWebView(mockContext)).thenReturn(mockWebView); - testHostApiImpl = new WebViewHostApiImpl(testInstanceManager, mockWebViewProxy, mockContext); + testHostApiImpl = + new WebViewHostApiImpl(testInstanceManager, mockWebViewProxy, mockContext, null); testHostApiImpl.create(0L, true); } @Test - public void errorCodes() { - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_AUTHENTICATION), - "authentication"); - assertEquals(FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_BAD_URL), "badUrl"); - assertEquals(FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_CONNECT), "connect"); - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_FAILED_SSL_HANDSHAKE), - "failedSslHandshake"); - assertEquals(FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_FILE), "file"); - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_FILE_NOT_FOUND), "fileNotFound"); - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_HOST_LOOKUP), "hostLookup"); - assertEquals(FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_IO), "io"); - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_PROXY_AUTHENTICATION), - "proxyAuthentication"); - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_REDIRECT_LOOP), "redirectLoop"); - assertEquals(FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_TIMEOUT), "timeout"); - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_TOO_MANY_REQUESTS), - "tooManyRequests"); - assertEquals(FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_UNKNOWN), "unknown"); - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_UNSAFE_RESOURCE), - "unsafeResource"); - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_UNSUPPORTED_AUTH_SCHEME), - "unsupportedAuthScheme"); - assertEquals( - FlutterWebViewClient.errorCodeToString(WebViewClient.ERROR_UNSUPPORTED_SCHEME), - "unsupportedScheme"); + public void releaseWebView() { + final WebViewPlatformView webView = new WebViewPlatformView(mockContext); + + final WebViewClientImpl mockWebViewClient = mock(WebViewClientImpl.class); + final WebChromeClientImpl mockWebChromeClient = mock(WebChromeClientImpl.class); + final DownloadListenerImpl mockDownloadListener = mock(DownloadListenerImpl.class); + final JavaScriptChannel mockJavaScriptChannel = mock(JavaScriptChannel.class); + + webView.setWebViewClient(mockWebViewClient); + webView.setWebChromeClient(mockWebChromeClient); + webView.setDownloadListener(mockDownloadListener); + webView.addJavascriptInterface(mockJavaScriptChannel, "jchannel"); + + webView.release(); + + verify(mockWebViewClient).release(); + verify(mockWebChromeClient).release(); + verify(mockDownloadListener).release(); + verify(mockJavaScriptChannel).release(); + } + + @Test + public void releaseWebViewDependents() { + final WebViewPlatformView webView = new WebViewPlatformView(mockContext); + + final WebViewClientImpl mockWebViewClient = mock(WebViewClientImpl.class); + final WebChromeClientImpl mockWebChromeClient = mock(WebChromeClientImpl.class); + final DownloadListenerImpl mockDownloadListener = mock(DownloadListenerImpl.class); + final JavaScriptChannel mockJavaScriptChannel = mock(JavaScriptChannel.class); + final JavaScriptChannel mockJavaScriptChannel2 = mock(JavaScriptChannel.class); + + webView.setWebViewClient(mockWebViewClient); + webView.setWebChromeClient(mockWebChromeClient); + webView.setDownloadListener(mockDownloadListener); + webView.addJavascriptInterface(mockJavaScriptChannel, "jchannel"); + + // Release should be called on the object added above. + webView.addJavascriptInterface(mockJavaScriptChannel2, "jchannel"); + verify(mockJavaScriptChannel).release(); + + webView.setWebViewClient(null); + webView.setWebChromeClient(null); + webView.setDownloadListener(null); + webView.removeJavascriptInterface("jchannel"); + + verify(mockWebViewClient).release(); + verify(mockWebChromeClient).release(); + verify(mockDownloadListener).release(); + verify(mockJavaScriptChannel2).release(); + } + + @Test + public void releaseInputAwareWebView() { + final InputAwareWebViewPlatformView webView = + new InputAwareWebViewPlatformView(mockContext, null); + + final WebViewClientImpl mockWebViewClient = mock(WebViewClientImpl.class); + final WebChromeClientImpl mockWebChromeClient = mock(WebChromeClientImpl.class); + final DownloadListenerImpl mockDownloadListener = mock(DownloadListenerImpl.class); + final JavaScriptChannel mockJavaScriptChannel = mock(JavaScriptChannel.class); + + webView.setWebViewClient(mockWebViewClient); + webView.setWebChromeClient(mockWebChromeClient); + webView.setDownloadListener(mockDownloadListener); + webView.addJavascriptInterface(mockJavaScriptChannel, "jchannel"); + + webView.release(); + + verify(mockWebViewClient).release(); + verify(mockWebChromeClient).release(); + verify(mockDownloadListener).release(); + verify(mockJavaScriptChannel).release(); + } + + @Test + public void releaseInputAwareWebViewDependents() { + final InputAwareWebViewPlatformView webView = + new InputAwareWebViewPlatformView(mockContext, null); + + final WebViewClientImpl mockWebViewClient = mock(WebViewClientImpl.class); + final WebChromeClientImpl mockWebChromeClient = mock(WebChromeClientImpl.class); + final DownloadListenerImpl mockDownloadListener = mock(DownloadListenerImpl.class); + final JavaScriptChannel mockJavaScriptChannel = mock(JavaScriptChannel.class); + final JavaScriptChannel mockJavaScriptChannel2 = mock(JavaScriptChannel.class); + + webView.setWebViewClient(mockWebViewClient); + webView.setWebChromeClient(mockWebChromeClient); + webView.setDownloadListener(mockDownloadListener); + webView.addJavascriptInterface(mockJavaScriptChannel, "jchannel"); + + // Release should be called on the object added above. + webView.addJavascriptInterface(mockJavaScriptChannel2, "jchannel"); + verify(mockJavaScriptChannel).release(); + + webView.setWebViewClient(null); + webView.setWebChromeClient(null); + webView.setDownloadListener(null); + webView.removeJavascriptInterface("jchannel"); + + verify(mockWebViewClient).release(); + verify(mockWebChromeClient).release(); + verify(mockDownloadListener).release(); + verify(mockJavaScriptChannel2).release(); } @Test @@ -195,7 +270,8 @@ public void setWebViewClient() { @Test public void addJavaScriptChannel() { - final JavaScriptChannel javaScriptChannel = new JavaScriptChannel(null, "aName", null); + final JavaScriptChannel javaScriptChannel = + new JavaScriptChannel(mock(JavaScriptChannelFlutterApiImpl.class), "aName", null); testInstanceManager.addInstance(javaScriptChannel, 1L); testHostApiImpl.addJavaScriptChannel(0L, 1L); @@ -204,7 +280,8 @@ public void addJavaScriptChannel() { @Test public void removeJavaScriptChannel() { - final JavaScriptChannel javaScriptChannel = new JavaScriptChannel(null, "aName", null); + final JavaScriptChannel javaScriptChannel = + new JavaScriptChannel(mock(JavaScriptChannelFlutterApiImpl.class), "aName", null); testInstanceManager.addInstance(javaScriptChannel, 1L); testHostApiImpl.removeJavaScriptChannel(0L, 1L); @@ -219,4 +296,13 @@ public void setDownloadListener() { testHostApiImpl.setDownloadListener(0L, 1L); verify(mockWebView).setDownloadListener(mockDownloadListener); } + + @Test + public void setWebChromeClient() { + final WebChromeClient mockWebChromeClient = mock(WebChromeClient.class); + testInstanceManager.addInstance(mockWebChromeClient, 1L); + + testHostApiImpl.setWebChromeClient(0L, 1L); + verify(mockWebView).setWebChromeClient(mockWebChromeClient); + } }