[webview_flutter_android] Updates Android implementation to deallocate native resources automatically (#6428)
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 910d20d..ffe7c96 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@ +## 2.10.2 + +* Updates internal code to release native objects automatically with InstanceManager. + ## 2.10.1 * Adds a method to the `WebView` wrapper to retrieve the X and Y positions simultaneously.
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 index 1981d8e..0d4797e 100644 --- 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
@@ -47,20 +47,6 @@ 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<Void> callback) { - if (instanceManager.containsInstance(downloadListener)) { - dispose(getIdentifierForListener(downloadListener), callback); - } else { - callback.reply(null); - } - } - private long getIdentifierForListener(DownloadListener listener) { final Long identifier = instanceManager.getIdentifierForStrongReference(listener); if (identifier == 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 ed0c2ae..a9cbcbd 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
@@ -6,7 +6,6 @@ import android.webkit.DownloadListener; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi; /** @@ -21,11 +20,9 @@ /** * Implementation of {@link DownloadListener} that passes arguments of callback methods to Dart. - * - * <p>No messages are sent to Dart after {@link DownloadListenerImpl#release} is called. */ - public static class DownloadListenerImpl implements DownloadListener, Releasable { - @Nullable private DownloadListenerFlutterApiImpl flutterApi; + public static class DownloadListenerImpl implements DownloadListener { + private final DownloadListenerFlutterApiImpl flutterApi; /** * Creates a {@link DownloadListenerImpl} that passes arguments of callbacks methods to Dart. @@ -43,18 +40,8 @@ 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; + flutterApi.onDownloadStart( + this, url, userAgent, contentDisposition, mimetype, contentLength, reply -> {}); } }
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 9a6b1c4..af096bf 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
@@ -543,8 +543,6 @@ public interface WebViewHostApi { void create(@NonNull Long instanceId, @NonNull Boolean useHybridComposition); - void dispose(@NonNull Long instanceId); - void loadData( @NonNull Long instanceId, @NonNull String data, @@ -656,31 +654,6 @@ { BasicMessageChannel<Object> channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.dispose", getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - Map<String, Object> wrapped = new HashMap<>(); - try { - ArrayList<Object> args = (ArrayList<Object>) message; - Number instanceIdArg = (Number) args.get(0); - if (instanceIdArg == null) { - throw new NullPointerException("instanceIdArg unexpectedly null."); - } - api.dispose((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); - } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel<Object> channel = - new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.loadData", getCodec()); if (api != null) { channel.setMessageHandler( @@ -1469,8 +1442,6 @@ public interface WebSettingsHostApi { void create(@NonNull Long instanceId, @NonNull Long webViewInstanceId); - void dispose(@NonNull Long instanceId); - void setDomStorageEnabled(@NonNull Long instanceId, @NonNull Boolean flag); void setJavaScriptCanOpenWindowsAutomatically(@NonNull Long instanceId, @NonNull Boolean flag); @@ -1538,31 +1509,6 @@ { BasicMessageChannel<Object> channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebSettingsHostApi.dispose", getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - Map<String, Object> wrapped = new HashMap<>(); - try { - ArrayList<Object> args = (ArrayList<Object>) message; - Number instanceIdArg = (Number) args.get(0); - if (instanceIdArg == null) { - throw new NullPointerException("instanceIdArg unexpectedly null."); - } - api.dispose((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); - } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel<Object> channel = - new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled", getCodec()); @@ -2022,19 +1968,6 @@ return JavaScriptChannelFlutterApiCodec.INSTANCE; } - public void dispose(@NonNull Long instanceIdArg, Reply<Void> callback) { - BasicMessageChannel<Object> channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.JavaScriptChannelFlutterApi.dispose", - getCodec()); - channel.send( - new ArrayList<Object>(Arrays.asList(instanceIdArg)), - channelReply -> { - callback.reply(null); - }); - } - public void postMessage( @NonNull Long instanceIdArg, @NonNull String messageArg, Reply<Void> callback) { BasicMessageChannel<Object> channel = @@ -2154,17 +2087,6 @@ return WebViewClientFlutterApiCodec.INSTANCE; } - public void dispose(@NonNull Long instanceIdArg, Reply<Void> callback) { - BasicMessageChannel<Object> channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebViewClientFlutterApi.dispose", getCodec()); - channel.send( - new ArrayList<Object>(Arrays.asList(instanceIdArg)), - channelReply -> { - callback.reply(null); - }); - } - public void onPageStarted( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, @@ -2347,17 +2269,6 @@ return DownloadListenerFlutterApiCodec.INSTANCE; } - public void dispose(@NonNull Long instanceIdArg, Reply<Void> callback) { - BasicMessageChannel<Object> channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.DownloadListenerFlutterApi.dispose", getCodec()); - channel.send( - new ArrayList<Object>(Arrays.asList(instanceIdArg)), - channelReply -> { - callback.reply(null); - }); - } - public void onDownloadStart( @NonNull Long instanceIdArg, @NonNull String urlArg, @@ -2394,7 +2305,7 @@ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebChromeClientHostApi { - void create(@NonNull Long instanceId, @NonNull Long webViewClientInstanceId); + void create(@NonNull Long instanceId); /** The codec used by WebChromeClientHostApi. */ static MessageCodec<Object> getCodec() { @@ -2420,15 +2331,7 @@ if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); } - Number webViewClientInstanceIdArg = (Number) args.get(1); - if (webViewClientInstanceIdArg == null) { - throw new NullPointerException("webViewClientInstanceIdArg unexpectedly null."); - } - api.create( - (instanceIdArg == null) ? null : instanceIdArg.longValue(), - (webViewClientInstanceIdArg == null) - ? null - : webViewClientInstanceIdArg.longValue()); + api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.put("result", null); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); @@ -2545,17 +2448,6 @@ return WebChromeClientFlutterApiCodec.INSTANCE; } - public void dispose(@NonNull Long instanceIdArg, Reply<Void> callback) { - BasicMessageChannel<Object> channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebChromeClientFlutterApi.dispose", getCodec()); - channel.send( - new ArrayList<Object>(Arrays.asList(instanceIdArg)), - channelReply -> { - callback.reply(null); - }); - } - public void onProgressChanged( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg,
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 ce6f2b8..cf2c262 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
@@ -8,7 +8,6 @@ import android.os.Looper; import android.webkit.JavascriptInterface; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; /** * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets @@ -16,13 +15,11 @@ * * <p>Exposes a single method named `postMessage` to JavaScript, which sends a message to the Dart * code. - * - * <p>No messages are sent to Dart after {@link JavaScriptChannel#release} is called. */ -public class JavaScriptChannel implements Releasable { +public class JavaScriptChannel { private final Handler platformThreadHandler; final String javaScriptChannelName; - @Nullable private JavaScriptChannelFlutterApiImpl flutterApi; + private final JavaScriptChannelFlutterApiImpl flutterApi; /** * Creates a {@link JavaScriptChannel} that passes arguments of callback methods to Dart. @@ -46,9 +43,7 @@ public void postMessage(final String message) { final Runnable postMessageRunnable = () -> { - if (flutterApi != null) { - flutterApi.postMessage(JavaScriptChannel.this, message, reply -> {}); - } + flutterApi.postMessage(JavaScriptChannel.this, message, reply -> {}); }; if (platformThreadHandler.getLooper() == Looper.myLooper()) { @@ -57,12 +52,4 @@ 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 index dbac833..ca08926 100644 --- 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
@@ -33,20 +33,6 @@ super.postMessage(getIdentifierForJavaScriptChannel(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<Void> callback) { - if (instanceManager.containsInstance(javaScriptChannel)) { - dispose(getIdentifierForJavaScriptChannel(javaScriptChannel), callback); - } else { - callback.reply(null); - } - } - private long getIdentifierForJavaScriptChannel(JavaScriptChannel javaScriptChannel) { final Long identifier = instanceManager.getIdentifierForStrongReference(javaScriptChannel); if (identifier == null) {
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 deleted file mode 100644 index 9c4ed76..0000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/Releasable.java +++ /dev/null
@@ -1,14 +0,0 @@ -// 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 index 28d63ec..cf263e2 100644 --- 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
@@ -40,20 +40,6 @@ getIdentifierForClient(webChromeClient), webViewIdentifier, 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<Void> callback) { - if (instanceManager.containsInstance(webChromeClient)) { - dispose(getIdentifierForClient(webChromeClient), callback); - } else { - callback.reply(null); - } - } - private long getIdentifierForClient(WebChromeClient webChromeClient) { final Long identifier = instanceManager.getIdentifierForStrongReference(webChromeClient); if (identifier == 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 0f50c82..7b5241e 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
@@ -29,20 +29,17 @@ /** * 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; + public static class WebChromeClientImpl extends WebChromeClient { + private final WebChromeClientFlutterApiImpl flutterApi; + @Nullable 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) { + public WebChromeClientImpl(@NonNull WebChromeClientFlutterApiImpl flutterApi) { this.flutterApi = flutterApi; - this.webViewClient = webViewClient; } @Override @@ -67,6 +64,14 @@ @VisibleForTesting boolean onCreateWindow( final WebView view, Message resultMsg, @Nullable WebView onCreateWindowWebView) { + // WebChromeClient requires a WebViewClient because of a bug fix that makes + // calls to WebViewClient.requestLoading/WebViewClient.urlLoading when a new + // window is opened. This is to make sure a url opened by `Window.open` has + // a secure url. + if (webViewClient == null) { + return false; + } + final WebViewClient windowWebViewClient = new WebViewClient() { @RequiresApi(api = Build.VERSION_CODES.N) @@ -102,9 +107,7 @@ @Override public void onProgressChanged(WebView view, int progress) { - if (flutterApi != null) { - flutterApi.onProgressChanged(this, view, (long) progress, reply -> {}); - } + flutterApi.onProgressChanged(this, view, (long) progress, reply -> {}); } /** @@ -113,17 +116,9 @@ * * @param webViewClient the forwarding {@link WebViewClient} */ - public void setWebViewClient(WebViewClient webViewClient) { + public void setWebViewClient(@NonNull 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}. */ @@ -132,12 +127,10 @@ * 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} + * @return the created {@link WebChromeClientHostApiImpl.WebChromeClientImpl} */ - public WebChromeClientImpl createWebChromeClient( - WebChromeClientFlutterApiImpl flutterApi, WebViewClient webViewClient) { - return new WebChromeClientImpl(flutterApi, webViewClient); + public WebChromeClientImpl createWebChromeClient(WebChromeClientFlutterApiImpl flutterApi) { + return new WebChromeClientImpl(flutterApi); } } @@ -158,11 +151,9 @@ } @Override - public void create(Long instanceId, Long webViewClientInstanceId) { - final WebViewClient webViewClient = - (WebViewClient) instanceManager.getInstance(webViewClientInstanceId); + public void create(Long instanceId) { final WebChromeClient webChromeClient = - webChromeClientCreator.createWebChromeClient(flutterApi, webViewClient); + webChromeClientCreator.createWebChromeClient(flutterApi); instanceManager.addDartCreatedInstance(webChromeClient, 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 5b6f9e7..98fd4fc 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
@@ -50,11 +50,6 @@ } @Override - public void dispose(Long instanceId) { - instanceManager.remove(instanceId); - } - - @Override public void setDomStorageEnabled(Long instanceId, Boolean flag) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setDomStorageEnabled(flag);
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 index c23e8e7..0dc0bbb 100644 --- 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
@@ -197,20 +197,6 @@ urlLoading(getIdentifierForClient(webViewClient), webViewIdentifier, 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<Void> callback) { - if (instanceManager.containsInstance(webViewClient)) { - dispose(getIdentifierForClient(webViewClient), callback); - } else { - callback.reply(null); - } - } - private long getIdentifierForClient(WebViewClient webViewClient) { final Long identifier = instanceManager.getIdentifierForStrongReference(webViewClient); if (identifier == 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 4833ee9..fd08cfa 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,7 +14,6 @@ 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; @@ -29,15 +28,10 @@ private final WebViewClientCreator webViewClientCreator; 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; + public static class WebViewClientImpl extends WebViewClient { + private final WebViewClientFlutterApiImpl flutterApi; private final boolean shouldOverrideUrlLoading; /** @@ -54,47 +48,35 @@ @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { - if (flutterApi != null) { - flutterApi.onPageStarted(this, view, url, reply -> {}); - } + flutterApi.onPageStarted(this, view, url, reply -> {}); } @Override public void onPageFinished(WebView view, String url) { - if (flutterApi != null) { - flutterApi.onPageFinished(this, view, url, reply -> {}); - } + 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 -> {}); - } + 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 -> {}); - } + flutterApi.onReceivedError( + this, view, (long) errorCode, description, failingUrl, reply -> {}); } @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - if (flutterApi != null) { - flutterApi.requestLoading(this, view, request, reply -> {}); - } + 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 -> {}); - } + flutterApi.urlLoading(this, view, url, reply -> {}); return shouldOverrideUrlLoading; } @@ -104,22 +86,14 @@ // 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; - } } /** * Implementation of {@link WebViewClientCompat} that passes arguments of callback methods to * Dart. */ - public static class WebViewClientCompatImpl extends WebViewClientCompat - implements ReleasableWebViewClient { - private @Nullable WebViewClientFlutterApiImpl flutterApi; + public static class WebViewClientCompatImpl extends WebViewClientCompat { + private final WebViewClientFlutterApiImpl flutterApi; private final boolean shouldOverrideUrlLoading; public WebViewClientCompatImpl( @@ -130,16 +104,12 @@ @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { - if (flutterApi != null) { - flutterApi.onPageStarted(this, view, url, reply -> {}); - } + flutterApi.onPageStarted(this, view, url, reply -> {}); } @Override public void onPageFinished(WebView view, String url) { - if (flutterApi != null) { - flutterApi.onPageFinished(this, view, url, reply -> {}); - } + flutterApi.onPageFinished(this, view, url, reply -> {}); } // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is @@ -151,35 +121,27 @@ @NonNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceErrorCompat error) { - if (flutterApi != null) { - flutterApi.onReceivedRequestError(this, view, request, error, reply -> {}); - } + 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 -> {}); - } + 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 -> {}); - } + 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 -> {}); - } + flutterApi.urlLoading(this, view, url, reply -> {}); return shouldOverrideUrlLoading; } @@ -189,13 +151,6 @@ // 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; - } } /** Handles creating {@link WebViewClient}s for a {@link WebViewClientHostApiImpl}. */
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index 8db976a..61d18b1 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
@@ -16,6 +16,7 @@ import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CookieManagerHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaObjectHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebSettingsHostApi; @@ -76,15 +77,22 @@ Context context, View containerView, FlutterAssetManager flutterAssetManager) { - - instanceManager = InstanceManager.open(identifier -> {}); + instanceManager = + InstanceManager.open( + identifier -> + new GeneratedAndroidWebView.JavaObjectFlutterApi(binaryMessenger) + .dispose(identifier, reply -> {})); viewRegistry.registerViewFactory( "plugins.flutter.io/webview", new FlutterWebViewFactory(instanceManager)); webViewHostApi = new WebViewHostApiImpl( - instanceManager, new WebViewHostApiImpl.WebViewProxy(), context, containerView); + instanceManager, + binaryMessenger, + new WebViewHostApiImpl.WebViewProxy(), + context, + containerView); javaScriptChannelHostApi = new JavaScriptChannelHostApiImpl( instanceManager, @@ -92,6 +100,7 @@ new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), new Handler(context.getMainLooper())); + JavaObjectHostApi.setup(binaryMessenger, new JavaObjectHostApiImpl(instanceManager)); WebViewHostApi.setup(binaryMessenger, webViewHostApi); JavaScriptChannelHostApi.setup(binaryMessenger, javaScriptChannelHostApi); WebViewClientHostApi.setup(
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 f257ace..c18fb37 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
@@ -14,12 +14,10 @@ import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.flutter.plugin.common.BinaryMessenger; 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; import java.util.Objects; @@ -33,6 +31,7 @@ private final WebViewProxy webViewProxy; // Only used with WebView using virtual displays. @Nullable private final View containerView; + private final BinaryMessenger binaryMessenger; private Context context; @@ -42,10 +41,14 @@ * Creates a {@link WebViewPlatformView}. * * @param context an Activity Context to access application assets + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager mangages instances used to communicate with the corresponding objects + * in Dart * @return the created {@link WebViewPlatformView} */ - public WebViewPlatformView createWebView(Context context) { - return new WebViewPlatformView(context); + public WebViewPlatformView createWebView( + Context context, BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + return new WebViewPlatformView(context, binaryMessenger, instanceManager); } /** @@ -56,8 +59,12 @@ * @return the created {@link InputAwareWebViewPlatformView} */ public InputAwareWebViewPlatformView createInputAwareWebView( - Context context, @Nullable View containerView) { - return new InputAwareWebViewPlatformView(context, containerView); + Context context, + BinaryMessenger binaryMessenger, + InstanceManager instanceManager, + @Nullable View containerView) { + return new InputAwareWebViewPlatformView( + context, binaryMessenger, instanceManager, containerView); } /** @@ -70,51 +77,26 @@ } } - private static class ReleasableValue<T extends Releasable> { - @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<WebViewClientHostApiImpl.ReleasableWebViewClient> - currentWebViewClient = new ReleasableValue<>(); - private final ReleasableValue<DownloadListenerImpl> currentDownloadListener = - new ReleasableValue<>(); - private final ReleasableValue<WebChromeClientImpl> currentWebChromeClient = - new ReleasableValue<>(); - private final Map<String, ReleasableValue<JavaScriptChannel>> javaScriptInterfaces = - new HashMap<>(); + public static class WebViewPlatformView extends WebView implements PlatformView { + private WebViewClient currentWebViewClient; + private WebChromeClientImpl currentWebChromeClient; /** * Creates a {@link WebViewPlatformView}. * * @param context an Activity Context to access application assets. This value cannot be null. */ - public WebViewPlatformView(Context context) { + public WebViewPlatformView( + Context context, BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(context); + currentWebViewClient = new WebViewClient(); + currentWebChromeClient = + new WebChromeClientImpl( + new WebChromeClientFlutterApiImpl(binaryMessenger, instanceManager)); + + setWebViewClient(currentWebViewClient); + setWebChromeClient(currentWebChromeClient); } @Override @@ -130,56 +112,18 @@ @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); + currentWebViewClient = webViewClient; + currentWebChromeClient.setWebViewClient(webViewClient); } @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> javaScriptChannel = javaScriptInterfaces.get(name); - if (javaScriptChannel != null && javaScriptChannel.get() != object) { - javaScriptChannel.release(); - } - javaScriptInterfaces.put(name, new ReleasableValue<>((JavaScriptChannel) object)); + if (!(client instanceof WebChromeClientImpl)) { + throw new AssertionError("Client must be a WebChromeClientImpl."); } - } - - @Override - public void removeJavascriptInterface(@NonNull String name) { - super.removeJavascriptInterface(name); - final ReleasableValue<JavaScriptChannel> javaScriptChannel = javaScriptInterfaces.get(name); - javaScriptChannel.release(); - javaScriptInterfaces.remove(name); - } - - @Override - public void release() { - currentWebViewClient.release(); - currentDownloadListener.release(); - currentWebChromeClient.release(); - for (ReleasableValue<JavaScriptChannel> channel : javaScriptInterfaces.values()) { - channel.release(); - } - javaScriptInterfaces.clear(); + currentWebChromeClient = (WebChromeClientImpl) client; + currentWebChromeClient.setWebViewClient(currentWebViewClient); } } @@ -189,23 +133,28 @@ */ @SuppressLint("ViewConstructor") public static class InputAwareWebViewPlatformView extends InputAwareWebView - implements PlatformView, Releasable { - private final ReleasableValue<WebViewClientHostApiImpl.ReleasableWebViewClient> - currentWebViewClient = new ReleasableValue<>(); - private final ReleasableValue<DownloadListenerImpl> currentDownloadListener = - new ReleasableValue<>(); - private final ReleasableValue<WebChromeClientImpl> currentWebChromeClient = - new ReleasableValue<>(); - private final Map<String, ReleasableValue<JavaScriptChannel>> javaScriptInterfaces = - new HashMap<>(); + implements PlatformView { + private WebViewClient currentWebViewClient; + private WebChromeClientImpl currentWebChromeClient; /** * Creates a {@link InputAwareWebViewPlatformView}. * * @param context an Activity Context to access application assets. This value cannot be null. */ - public InputAwareWebViewPlatformView(Context context, View containerView) { + public InputAwareWebViewPlatformView( + Context context, + BinaryMessenger binaryMessenger, + InstanceManager instanceManager, + View containerView) { super(context, containerView); + currentWebViewClient = new WebViewClient(); + currentWebChromeClient = + new WebChromeClientImpl( + new WebChromeClientFlutterApiImpl(binaryMessenger, instanceManager)); + + setWebViewClient(currentWebViewClient); + setWebChromeClient(currentWebChromeClient); } @Override @@ -242,56 +191,18 @@ @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); + currentWebViewClient = webViewClient; + currentWebChromeClient.setWebViewClient(webViewClient); } @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> javaScriptChannel = javaScriptInterfaces.get(name); - if (javaScriptChannel != null && javaScriptChannel.get() != object) { - javaScriptChannel.release(); - } - javaScriptInterfaces.put(name, new ReleasableValue<>((JavaScriptChannel) object)); + if (!(client instanceof WebChromeClientImpl)) { + throw new AssertionError("Client must be a WebChromeClientImpl."); } - } - - @Override - public void removeJavascriptInterface(@NonNull String name) { - super.removeJavascriptInterface(name); - final ReleasableValue<JavaScriptChannel> javaScriptChannel = javaScriptInterfaces.get(name); - javaScriptChannel.release(); - javaScriptInterfaces.remove(name); - } - - @Override - public void release() { - currentWebViewClient.release(); - currentDownloadListener.release(); - currentWebChromeClient.release(); - for (ReleasableValue<JavaScriptChannel> channel : javaScriptInterfaces.values()) { - channel.release(); - } - javaScriptInterfaces.clear(); + currentWebChromeClient = (WebChromeClientImpl) client; + currentWebChromeClient.setWebViewClient(currentWebViewClient); } } @@ -299,16 +210,19 @@ * 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 binaryMessenger used to communicate with Dart over asynchronous messages * @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, + BinaryMessenger binaryMessenger, WebViewProxy webViewProxy, Context context, @Nullable View containerView) { this.instanceManager = instanceManager; + this.binaryMessenger = binaryMessenger; this.webViewProxy = webViewProxy; this.context = context; this.containerView = containerView; @@ -332,23 +246,15 @@ final WebView webView = useHybridComposition - ? webViewProxy.createWebView(context) - : webViewProxy.createInputAwareWebView(context, containerView); + ? webViewProxy.createWebView(context, binaryMessenger, instanceManager) + : webViewProxy.createInputAwareWebView( + context, binaryMessenger, instanceManager, containerView); displayListenerProxy.onPostWebViewInitialization(displayManager); instanceManager.addDartCreatedInstance(webView, instanceId); } @Override - public void dispose(Long instanceId) { - final WebView instance = (WebView) instanceManager.getInstance(instanceId); - if (instance != null) { - ((Releasable) instance).release(); - instanceManager.remove(instanceId); - } - } - - @Override public void loadData(Long instanceId, String data, String mimeType, String encoding) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.loadData(data, mimeType, encoding);
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 da25dac..caffbb9 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,8 @@ 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.DownloadListenerHostApiImpl.DownloadListenerImpl; import org.junit.After; @@ -67,11 +64,5 @@ 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 4bde211..c9a5e64 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,8 +6,6 @@ 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; @@ -62,10 +60,5 @@ public void postMessage() { 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 03d48d1..e821537 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
@@ -10,13 +10,11 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.net.Uri; import android.os.Message; -import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebView.WebViewTransport; @@ -50,21 +48,20 @@ instanceManager = InstanceManager.open(identifier -> {}); instanceManager.addDartCreatedInstance(mockWebView, 0L); - instanceManager.addDartCreatedInstance(mockWebViewClient, 1L); final WebChromeClientCreator webChromeClientCreator = new WebChromeClientCreator() { @Override public WebChromeClientImpl createWebChromeClient( - WebChromeClientFlutterApiImpl flutterApi, WebViewClient webViewClient) { - webChromeClient = super.createWebChromeClient(flutterApi, webViewClient); + WebChromeClientFlutterApiImpl flutterApi) { + webChromeClient = super.createWebChromeClient(flutterApi); return webChromeClient; } }; hostApiImpl = new WebChromeClientHostApiImpl(instanceManager, webChromeClientCreator, mockFlutterApi); - hostApiImpl.create(2L, 1L); + hostApiImpl.create(2L); } @After @@ -76,11 +73,6 @@ public void onProgressChanged() { 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()); } @Test @@ -91,6 +83,7 @@ final Message message = new Message(); message.obj = mock(WebViewTransport.class); + webChromeClient.setWebViewClient(mockWebViewClient); assertTrue(webChromeClient.onCreateWindow(mockWebView, message, mockOnCreateWindowWebView)); /// Capture the WebViewClient used with onCreateWindow WebView.
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 5d0cb70..87b39d4 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
@@ -8,8 +8,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -72,11 +70,6 @@ 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 @@ -90,12 +83,6 @@ 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 @@ -103,11 +90,6 @@ 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()); } @Test
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 30bc256..ecaab77 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
@@ -15,10 +15,7 @@ import android.webkit.ValueCallback; 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.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.WebViewPlatformView; import java.util.HashMap; import org.junit.After; @@ -39,6 +36,8 @@ @Mock Context mockContext; + @Mock BinaryMessenger mockBinaryMessenger; + InstanceManager testInstanceManager; WebViewHostApiImpl testHostApiImpl; @@ -46,9 +45,11 @@ public void setUp() { testInstanceManager = InstanceManager.open(identifier -> {}); - when(mockWebViewProxy.createWebView(mockContext)).thenReturn(mockWebView); + when(mockWebViewProxy.createWebView(mockContext, mockBinaryMessenger, testInstanceManager)) + .thenReturn(mockWebView); testHostApiImpl = - new WebViewHostApiImpl(testInstanceManager, mockWebViewProxy, mockContext, null); + new WebViewHostApiImpl( + testInstanceManager, mockBinaryMessenger, mockWebViewProxy, mockContext, null); testHostApiImpl.create(0L, true); } @@ -58,112 +59,6 @@ } @Test - 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 public void loadData() { testHostApiImpl.loadData( 0L, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "text/plain", "base64");
diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index b7c32f5..d67a9b2 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
@@ -18,6 +18,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:webview_flutter_android/src/instance_manager.dart'; +import 'package:webview_flutter_android/src/weak_reference_utils.dart'; import 'package:webview_flutter_android/webview_android.dart'; import 'package:webview_flutter_android/webview_surface_android.dart'; import 'package:webview_flutter_android_example/navigation_decision.dart'; @@ -103,6 +105,28 @@ ); }); + testWidgets( + 'withWeakRefenceTo allows encapsulating class to be garbage collected', + (WidgetTester tester) async { + final Completer<int> gcCompleter = Completer<int>(); + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: gcCompleter.complete, + ); + + ClassWithCallbackClass? instance = ClassWithCallbackClass(); + instanceManager.addHostCreatedInstance(instance.callbackClass, 0); + instance = null; + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance + .watchPerformance(() async { + await tester.pumpAndSettle(); + }); + + final int gcIdentifier = await gcCompleter.future; + expect(gcIdentifier, 0); + }, timeout: const Timeout(Duration(seconds: 10))); + testWidgets('evaluateJavascript', (WidgetTester tester) async { final Completer<WebViewController> controllerCompleter = Completer<WebViewController>(); @@ -1512,3 +1536,33 @@ ); } } + +class CopyableObjectWithCallback with Copyable { + CopyableObjectWithCallback(this.callback); + + final VoidCallback callback; + + @override + CopyableObjectWithCallback copy() { + return CopyableObjectWithCallback(callback); + } +} + +class ClassWithCallbackClass { + ClassWithCallbackClass() { + callbackClass = CopyableObjectWithCallback( + withWeakRefenceTo( + this, + (WeakReference<ClassWithCallbackClass> weakReference) { + return () { + // Weak reference to `this` in callback. + // ignore: unnecessary_statements + weakReference; + }; + }, + ), + ); + } + + late final CopyableObjectWithCallback callbackClass; +}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index e087031..fa5b516 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
@@ -38,7 +38,9 @@ /// Global instance of [InstanceManager]. static final InstanceManager globalInstanceManager = InstanceManager( - onWeakReferenceRemoved: (_) {}, + onWeakReferenceRemoved: (int identifier) { + JavaObjectHostApiImpl().dispose(identifier); + }, ); /// Pigeon Host Api implementation for [JavaObject]. @@ -90,8 +92,6 @@ @visibleForTesting static WebViewHostApiImpl api = WebViewHostApiImpl(); - WebViewClient? _currentWebViewClient; - /// Whether the [WebView] will be rendered with an [AndroidViewSurface]. /// /// This implementation uses hybrid composition to render the WebView Widget. @@ -330,8 +330,6 @@ /// /// This will replace the current handler. Future<void> setWebViewClient(WebViewClient webViewClient) { - _currentWebViewClient = webViewClient; - WebViewClient.api.createFromInstance(webViewClient); return api.setWebViewClientFromInstance(this, webViewClient); } @@ -375,11 +373,8 @@ /// Registers the interface to be used when content can not be handled by the rendering engine, and should be downloaded instead. /// /// This will replace the current handler. - Future<void> setDownloadListener(DownloadListener? listener) async { - await Future.wait(<Future<void>>[ - if (listener != null) DownloadListener.api.createFromInstance(listener), - api.setDownloadListenerFromInstance(this, listener) - ]); + Future<void> setDownloadListener(DownloadListener? listener) { + return api.setDownloadListenerFromInstance(this, listener); } /// Sets the chrome handler. @@ -387,20 +382,8 @@ /// This is an implementation of [WebChromeClient] for use in handling /// JavaScript dialogs, favicons, titles, and the progress. This will replace /// the current handler. - Future<void> setWebChromeClient(WebChromeClient? client) async { - // WebView requires a WebViewClient because of a bug fix that makes - // calls to WebViewClient.requestLoading/WebViewClient.urlLoading when a new - // window is opened. This is to make sure a url opened by `Window.open` has - // a secure url. - assert( - _currentWebViewClient != null, - "Can't set a WebChromeClient without setting a WebViewClient first.", - ); - await Future.wait(<Future<void>>[ - if (client != null) - WebChromeClient.api.createFromInstance(client, _currentWebViewClient!), - api.setWebChromeClientFromInstance(this, client), - ]); + Future<void> setWebChromeClient(WebChromeClient? client) { + return api.setWebChromeClientFromInstance(this, client); } /// Sets the background color of this WebView. @@ -408,15 +391,6 @@ return api.setBackgroundColorFromInstance(this, color.value); } - /// Releases all resources used by the [WebView]. - /// - /// Any methods called after [release] will throw an exception. - Future<void> release() { - _currentWebViewClient = null; - WebSettings.api.disposeFromInstance(settings); - return api.disposeFromInstance(this); - } - @override WebView copy() { return WebView.detached(useHybridComposition: useHybridComposition); @@ -624,9 +598,10 @@ /// Constructs a [JavaScriptChannel]. JavaScriptChannel( this.channelName, { - void Function(String message)? postMessage, + required this.postMessage, }) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); + api.createFromInstance(this); } /// Constructs a [JavaScriptChannel] without creating the associated Java @@ -636,7 +611,7 @@ /// create copies. JavaScriptChannel.detached( this.channelName, { - void Function(String message)? postMessage, + required this.postMessage, }) : super.detached(); /// Pigeon Host Api implementation for [JavaScriptChannel]. @@ -647,7 +622,7 @@ final String channelName; /// Callback method when javaScript calls `postMessage` on the object instance passed. - void postMessage(String message) {} + final void Function(String message) postMessage; @override JavaScriptChannel copy() { @@ -660,25 +635,15 @@ /// Constructs a [WebViewClient]. WebViewClient({ this.shouldOverrideUrlLoading = true, - void Function(WebView webView, String url)? onPageStarted, - void Function(WebView webView, String url)? onPageFinished, - void Function( - WebView webView, - WebResourceRequest request, - WebResourceError error, - )? - onReceivedRequestError, - void Function( - WebView webView, - int errorCode, - String description, - String failingUrl, - )? - onReceivedError, - void Function(WebView webView, WebResourceRequest request)? requestLoading, - void Function(WebView webView, String url)? urlLoading, + this.onPageStarted, + this.onPageFinished, + this.onReceivedRequestError, + @Deprecated('Only called on Android version < 23.') this.onReceivedError, + this.requestLoading, + this.urlLoading, }) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); + api.createFromInstance(this); } /// Constructs a [WebViewClient] without creating the associated Java object. @@ -687,23 +652,12 @@ /// create copies. WebViewClient.detached({ this.shouldOverrideUrlLoading = true, - void Function(WebView webView, String url)? onPageStarted, - void Function(WebView webView, String url)? onPageFinished, - void Function( - WebView webView, - WebResourceRequest request, - WebResourceError error, - )? - onReceivedRequestError, - void Function( - WebView webView, - int errorCode, - String description, - String failingUrl, - )? - onReceivedError, - void Function(WebView webView, WebResourceRequest request)? requestLoading, - void Function(WebView webView, String url)? urlLoading, + this.onPageStarted, + this.onPageFinished, + this.onReceivedRequestError, + @Deprecated('Only called on Android version < 23.') this.onReceivedError, + this.requestLoading, + this.urlLoading, }) : super.detached(); /// User authentication failed on server. @@ -812,7 +766,7 @@ /// embedded frame changes, i.e. clicking a link whose target is an iframe, it /// will also not be called for fragment navigations (navigations to /// #fragment_id). - void onPageStarted(WebView webView, String url) {} + final void Function(WebView webView, String url)? onPageStarted; // TODO(bparrishMines): Update documentation when WebView.postVisualStateCallback is added. /// Notify the host application that a page has finished loading. @@ -820,7 +774,7 @@ /// This method is called only for main frame. Receiving an [onPageFinished] /// callback does not guarantee that the next frame drawn by WebView will /// reflect the state of the DOM at this point. - void onPageFinished(WebView webView, String url) {} + final void Function(WebView webView, String url)? onPageFinished; /// Report web resource loading error to the host application. /// @@ -829,23 +783,23 @@ /// be called for any resource (iframe, image, etc.), not just for the main /// page. Thus, it is recommended to perform minimum required work in this /// callback. - void onReceivedRequestError( + final void Function( WebView webView, WebResourceRequest request, WebResourceError error, - ) {} + )? onReceivedRequestError; /// Report an error to the host application. /// /// These errors are unrecoverable (i.e. the main resource is unavailable). /// The errorCode parameter corresponds to one of the error* constants. @Deprecated('Only called on Android version < 23.') - void onReceivedError( + final void Function( WebView webView, int errorCode, String description, String failingUrl, - ) {} + )? onReceivedError; // TODO(bparrishMines): Update documentation once synchronous url handling is supported. /// When a URL is about to be loaded in the current [WebView]. @@ -855,7 +809,8 @@ /// [WebViewClient] is provided, setting [shouldOverrideUrlLoading] to true /// causes the current [WebView] to abort loading the URL, while returning /// false causes the [WebView] to continue loading the URL as usual. - void requestLoading(WebView webView, WebResourceRequest request) {} + final void Function(WebView webView, WebResourceRequest request)? + requestLoading; // TODO(bparrishMines): Update documentation once synchronous url handling is supported. /// When a URL is about to be loaded in the current [WebView]. @@ -865,7 +820,7 @@ /// [WebViewClient] is provided, setting [shouldOverrideUrlLoading] to true /// causes the current [WebView] to abort loading the URL, while returning /// false causes the [WebView] to continue loading the URL as usual. - void urlLoading(WebView webView, String url) {} + final void Function(WebView webView, String url)? urlLoading; @override WebViewClient copy() { @@ -885,17 +840,9 @@ /// engine for [WebView], and should be downloaded instead. class DownloadListener extends JavaObject { /// Constructs a [DownloadListener]. - DownloadListener({ - void Function( - String url, - String userAgent, - String contentDisposition, - String mimetype, - int contentLength, - )? - onDownloadStart, - }) : super.detached() { + DownloadListener({required this.onDownloadStart}) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); + api.createFromInstance(this); } /// Constructs a [DownloadListener] without creating the associated Java @@ -903,43 +850,33 @@ /// /// This should only be used by subclasses created by this library or to /// create copies. - DownloadListener.detached({ - void Function( - String url, - String userAgent, - String contentDisposition, - String mimetype, - int contentLength, - )? - onDownloadStart, - }) : super.detached(); + DownloadListener.detached({required this.onDownloadStart}) : super.detached(); /// Pigeon Host Api implementation for [DownloadListener]. @visibleForTesting static DownloadListenerHostApiImpl api = DownloadListenerHostApiImpl(); /// Notify the host application that a file should be downloaded. - void onDownloadStart( + final void Function( String url, String userAgent, String contentDisposition, String mimetype, int contentLength, - ) {} + ) onDownloadStart; @override DownloadListener copy() { - return DownloadListener(onDownloadStart: onDownloadStart); + return DownloadListener.detached(onDownloadStart: onDownloadStart); } } /// Handles JavaScript dialogs, favicons, titles, and the progress for [WebView]. class WebChromeClient extends JavaObject { /// Constructs a [WebChromeClient]. - WebChromeClient({ - void Function(WebView webView, int progress)? onProgressChanged, - }) : super.detached() { + WebChromeClient({this.onProgressChanged}) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); + api.createFromInstance(this); } /// Constructs a [WebChromeClient] without creating the associated Java @@ -947,16 +884,14 @@ /// /// This should only be used by subclasses created by this library or to /// create copies. - WebChromeClient.detached({ - void Function(WebView webView, int progress)? onProgressChanged, - }) : super.detached(); + WebChromeClient.detached({this.onProgressChanged}) : super.detached(); /// Pigeon Host Api implementation for [WebChromeClient]. @visibleForTesting static WebChromeClientHostApiImpl api = WebChromeClientHostApiImpl(); /// Notify the host application that a file should be downloaded. - void onProgressChanged(WebView webView, int progress) {} + final void Function(WebView webView, int progress)? onProgressChanged; @override WebChromeClient copy() {
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart index e793dbb..49385e5 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
@@ -303,30 +303,6 @@ } } - Future<void> dispose(int arg_instanceId) async { - final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( - 'dev.flutter.pigeon.WebViewHostApi.dispose', codec, - binaryMessenger: _binaryMessenger); - final Map<Object?, Object?>? replyMap = - await channel.send(<Object?>[arg_instanceId]) as Map<Object?, Object?>?; - if (replyMap == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyMap['error'] != null) { - final Map<Object?, Object?> error = - (replyMap['error'] as Map<Object?, Object?>?)!; - throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], - ); - } else { - return; - } - } - Future<void> loadData(int arg_instanceId, String arg_data, String? arg_mimeType, String? arg_encoding) async { final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( @@ -1027,30 +1003,6 @@ } } - Future<void> dispose(int arg_instanceId) async { - final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( - 'dev.flutter.pigeon.WebSettingsHostApi.dispose', codec, - binaryMessenger: _binaryMessenger); - final Map<Object?, Object?>? replyMap = - await channel.send(<Object?>[arg_instanceId]) as Map<Object?, Object?>?; - if (replyMap == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyMap['error'] != null) { - final Map<Object?, Object?> error = - (replyMap['error'] as Map<Object?, Object?>?)!; - throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], - ); - } else { - return; - } - } - Future<void> setDomStorageEnabled(int arg_instanceId, bool arg_flag) async { final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( 'dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled', codec, @@ -1401,31 +1353,11 @@ static const MessageCodec<Object?> codec = _JavaScriptChannelFlutterApiCodec(); - void dispose(int instanceId); void postMessage(int instanceId, String message); static void setup(JavaScriptChannelFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( - 'dev.flutter.pigeon.JavaScriptChannelFlutterApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMessageHandler(null); - } else { - channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.JavaScriptChannelFlutterApi.dispose was null.'); - final List<Object?> args = (message as List<Object?>?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.JavaScriptChannelFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return; - }); - } - } - { - final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( 'dev.flutter.pigeon.JavaScriptChannelFlutterApi.postMessage', codec, binaryMessenger: binaryMessenger); if (api == null) { @@ -1524,7 +1456,6 @@ abstract class WebViewClientFlutterApi { static const MessageCodec<Object?> codec = _WebViewClientFlutterApiCodec(); - void dispose(int instanceId); void onPageStarted(int instanceId, int webViewInstanceId, String url); void onPageFinished(int instanceId, int webViewInstanceId, String url); void onReceivedRequestError(int instanceId, int webViewInstanceId, @@ -1538,25 +1469,6 @@ {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( - 'dev.flutter.pigeon.WebViewClientFlutterApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMessageHandler(null); - } else { - channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.dispose was null.'); - final List<Object?> args = (message as List<Object?>?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return; - }); - } - } - { - final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( 'dev.flutter.pigeon.WebViewClientFlutterApi.onPageStarted', codec, binaryMessenger: binaryMessenger); if (api == null) { @@ -1771,32 +1683,12 @@ abstract class DownloadListenerFlutterApi { static const MessageCodec<Object?> codec = _DownloadListenerFlutterApiCodec(); - void dispose(int instanceId); void onDownloadStart(int instanceId, String url, String userAgent, String contentDisposition, String mimetype, int contentLength); static void setup(DownloadListenerFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( - 'dev.flutter.pigeon.DownloadListenerFlutterApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMessageHandler(null); - } else { - channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.dispose was null.'); - final List<Object?> args = (message as List<Object?>?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return; - }); - } - } - { - final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( 'dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart', codec, binaryMessenger: binaryMessenger); @@ -1849,14 +1741,12 @@ static const MessageCodec<Object?> codec = _WebChromeClientHostApiCodec(); - Future<void> create( - int arg_instanceId, int arg_webViewClientInstanceId) async { + Future<void> create(int arg_instanceId) async { final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( 'dev.flutter.pigeon.WebChromeClientHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map<Object?, Object?>? replyMap = await channel - .send(<Object?>[arg_instanceId, arg_webViewClientInstanceId]) - as Map<Object?, Object?>?; + final Map<Object?, Object?>? replyMap = + await channel.send(<Object?>[arg_instanceId]) as Map<Object?, Object?>?; if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -1958,31 +1848,11 @@ abstract class WebChromeClientFlutterApi { static const MessageCodec<Object?> codec = _WebChromeClientFlutterApiCodec(); - void dispose(int instanceId); void onProgressChanged(int instanceId, int webViewInstanceId, int progress); static void setup(WebChromeClientFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( - 'dev.flutter.pigeon.WebChromeClientFlutterApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMessageHandler(null); - } else { - channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.dispose was null.'); - final List<Object?> args = (message as List<Object?>?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return; - }); - } - } - { - final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( 'dev.flutter.pigeon.WebChromeClientFlutterApi.onProgressChanged', codec, binaryMessenger: binaryMessenger);
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index 4f2c51c..c9fe2ef 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
@@ -144,15 +144,6 @@ ); } - /// Helper method to convert instances ids to objects. - Future<void> disposeFromInstance(WebView instance) async { - final int? instanceId = instanceManager.getIdentifier(instance); - if (instanceId != null) { - instanceManager.remove(instanceId); - await dispose(instanceId); - } - } - /// Helper method to convert the instances ids to objects. Future<void> loadDataFromInstance( WebView instance, @@ -368,15 +359,6 @@ } /// Helper method to convert instances ids to objects. - Future<void> disposeFromInstance(WebSettings instance) async { - final int? instanceId = instanceManager.getIdentifier(instance); - if (instanceId != null) { - instanceManager.remove(instanceId); - return dispose(instanceId); - } - } - - /// Helper method to convert instances ids to objects. Future<void> setDomStorageEnabledFromInstance( WebSettings instance, bool flag, @@ -532,11 +514,6 @@ final InstanceManager instanceManager; @override - void dispose(int instanceId) { - instanceManager.remove(instanceId); - } - - @override void postMessage(int instanceId, String message) { final JavaScriptChannel? instance = instanceManager .getInstanceWithWeakReference(instanceId) as JavaScriptChannel?; @@ -579,11 +556,6 @@ final InstanceManager instanceManager; @override - void dispose(int instanceId) { - instanceManager.remove(instanceId); - } - - @override void onPageFinished(int instanceId, int webViewInstanceId, String url) { final WebViewClient? instance = instanceManager .getInstanceWithWeakReference(instanceId) as WebViewClient?; @@ -597,7 +569,9 @@ webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.onPageFinished(webViewInstance!, url); + if (instance!.onPageFinished != null) { + instance.onPageFinished!(webViewInstance!, url); + } } @override @@ -614,7 +588,9 @@ webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.onPageStarted(webViewInstance!, url); + if (instance!.onPageStarted != null) { + instance.onPageStarted!(webViewInstance!, url); + } } @override @@ -638,12 +614,14 @@ 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); // ignore: deprecated_member_use_from_same_package - instance!.onReceivedError( - webViewInstance!, - errorCode, - description, - failingUrl, - ); + if (instance!.onReceivedError != null) { + instance.onReceivedError!( + webViewInstance!, + errorCode, + description, + failingUrl, + ); + } } @override @@ -665,11 +643,13 @@ webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.onReceivedRequestError( - webViewInstance!, - _toWebResourceRequest(request), - _toWebResourceError(error), - ); + if (instance!.onReceivedRequestError != null) { + instance.onReceivedRequestError!( + webViewInstance!, + _toWebResourceRequest(request), + _toWebResourceError(error), + ); + } } @override @@ -690,7 +670,12 @@ webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.requestLoading(webViewInstance!, _toWebResourceRequest(request)); + if (instance!.requestLoading != null) { + instance.requestLoading!( + webViewInstance!, + _toWebResourceRequest(request), + ); + } } @override @@ -711,7 +696,9 @@ webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.urlLoading(webViewInstance!, url); + if (instance!.urlLoading != null) { + instance.urlLoading!(webViewInstance!, url); + } } } @@ -746,11 +733,6 @@ final InstanceManager instanceManager; @override - void dispose(int instanceId) { - instanceManager.remove(instanceId); - } - - @override void onDownloadStart( int instanceId, String url, @@ -788,16 +770,10 @@ final InstanceManager instanceManager; /// Helper method to convert instances ids to objects. - Future<void> createFromInstance( - WebChromeClient instance, - WebViewClient webViewClient, - ) async { + Future<void> createFromInstance(WebChromeClient instance) async { if (instanceManager.getIdentifier(instance) == null) { final int identifier = instanceManager.addDartCreatedInstance(instance); - return create( - identifier, - instanceManager.getIdentifier(webViewClient)!, - ); + return create(identifier); } } } @@ -812,11 +788,6 @@ final InstanceManager instanceManager; @override - void dispose(int instanceId) { - instanceManager.remove(instanceId); - } - - @override void onProgressChanged(int instanceId, int webViewInstanceId, int progress) { final WebChromeClient? instance = instanceManager .getInstanceWithWeakReference(instanceId) as WebChromeClient?; @@ -830,7 +801,9 @@ webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.onProgressChanged(webViewInstance!, progress); + if (instance!.onProgressChanged != null) { + instance.onProgressChanged!(webViewInstance!, progress); + } } }
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart b/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart new file mode 100644 index 0000000..ad0c9eb --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart
@@ -0,0 +1,34 @@ +// 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. + +/// Helper method for creating callbacks methods with a weak reference. +/// +/// Example: +/// ``` +/// final JavascriptChannelRegistry javascriptChannelRegistry = ... +/// +/// final WKScriptMessageHandler handler = WKScriptMessageHandler( +/// didReceiveScriptMessage: withWeakRefenceTo( +/// javascriptChannelRegistry, +/// (WeakReference<JavascriptChannelRegistry> weakReference) { +/// return ( +/// WKUserContentController userContentController, +/// WKScriptMessage message, +/// ) { +/// weakReference.target?.onJavascriptChannelMessage( +/// message.name, +/// message.body!.toString(), +/// ); +/// }; +/// }, +/// ), +/// ); +/// ``` +S withWeakRefenceTo<T extends Object, S extends Object>( + T reference, + S Function(WeakReference<T> weakReference) onCreate, +) { + final WeakReference<T> weakReference = WeakReference<T>(reference); + return onCreate(weakReference); +}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index ff6265d..4760ccf 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
@@ -6,6 +6,7 @@ import 'dart:typed_data'; import 'package:flutter/widgets.dart'; +import 'package:webview_flutter_android/src/weak_reference_utils.dart'; import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; @@ -86,12 +87,6 @@ } @override - void dispose() { - super.dispose(); - controller._dispose(); - } - - @override Widget build(BuildContext context) { return widget.onBuildWidget(controller); } @@ -137,7 +132,10 @@ final Map<String, WebViewAndroidJavaScriptChannel> _javaScriptChannels = <String, WebViewAndroidJavaScriptChannel>{}; - late WebViewAndroidWebViewClient _webViewClient; + late android_webview.WebViewClient _webViewClient; + + bool _hasNavigationDelegate = false; + bool _hasProgressTracking = false; /// Represents the WebView maintained by platform code. late final android_webview.WebView webView; @@ -160,20 +158,50 @@ /// Receives callbacks when content should be downloaded instead. @visibleForTesting - late final WebViewAndroidDownloadListener downloadListener = - WebViewAndroidDownloadListener(loadUrl: loadUrl); + late final android_webview.DownloadListener downloadListener = + android_webview.DownloadListener( + onDownloadStart: withWeakRefenceTo( + this, + (WeakReference<WebViewAndroidPlatformController> weakReference) { + return ( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + weakReference.target?._handleNavigationRequest( + url: url, + isForMainFrame: true, + ); + }; + }, + ), + ); /// Handles JavaScript dialogs, favicons, titles, new windows, and the progress for [android_webview.WebView]. @visibleForTesting - late final WebViewAndroidWebChromeClient webChromeClient = - WebViewAndroidWebChromeClient(); + late final android_webview.WebChromeClient webChromeClient = + android_webview.WebChromeClient( + onProgressChanged: withWeakRefenceTo( + this, + (WeakReference<WebViewAndroidPlatformController> weakReference) { + return (_, int progress) { + final WebViewAndroidPlatformController? controller = + weakReference.target; + if (controller != null && controller._hasProgressTracking) { + controller.callbacksHandler.onProgress(progress); + } + }; + }, + )); /// Manages the JavaScript storage APIs. final android_webview.WebStorage webStorage; /// Receive various notifications and requests for [android_webview.WebView]. @visibleForTesting - WebViewAndroidWebViewClient get webViewClient => _webViewClient; + android_webview.WebViewClient get webViewClient => _webViewClient; @override Future<void> loadHtmlString(String html, {String? baseUrl}) { @@ -272,10 +300,9 @@ @override Future<void> updateSettings(WebSettings setting) async { + _hasProgressTracking = setting.hasProgressTracking ?? _hasProgressTracking; await Future.wait(<Future<void>>[ _setUserAgent(setting.userAgent), - if (setting.hasProgressTracking != null) - _setHasProgressTracking(setting.hasProgressTracking!), if (setting.hasNavigationDelegate != null) _setHasNavigationDelegate(setting.hasNavigationDelegate!), if (setting.javascriptMode != null) @@ -355,8 +382,6 @@ @override Future<int> getScrollY() => webView.getScrollY(); - Future<void> _dispose() => webView.release(); - void _setCreationParams(CreationParams creationParams) { final WebSettings? webSettings = creationParams.webSettings; if (webSettings != null) { @@ -389,33 +414,59 @@ .forEach(WebViewCookieManagerPlatform.instance!.setCookie); } - Future<void> _setHasProgressTracking(bool hasProgressTracking) async { - if (hasProgressTracking) { - webChromeClient._onProgress = callbacksHandler.onProgress; - } else { - webChromeClient._onProgress = null; - } - } - Future<void> _setHasNavigationDelegate(bool hasNavigationDelegate) { - if (hasNavigationDelegate) { - downloadListener._onNavigationRequest = - callbacksHandler.onNavigationRequest; - _webViewClient = WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: callbacksHandler.onPageStarted, - onPageFinishedCallback: callbacksHandler.onPageFinished, - onWebResourceErrorCallback: callbacksHandler.onWebResourceError, - loadUrl: loadUrl, - onNavigationRequestCallback: callbacksHandler.onNavigationRequest, - ); - } else { - downloadListener._onNavigationRequest = null; - _webViewClient = WebViewAndroidWebViewClient( - onPageStartedCallback: callbacksHandler.onPageStarted, - onPageFinishedCallback: callbacksHandler.onPageFinished, - onWebResourceErrorCallback: callbacksHandler.onWebResourceError, - ); - } + _hasNavigationDelegate = hasNavigationDelegate; + + final WeakReference<WebViewAndroidPlatformController> weakThis = + WeakReference<WebViewAndroidPlatformController>(this); + _webViewClient = android_webview.WebViewClient( + shouldOverrideUrlLoading: hasNavigationDelegate, + onPageStarted: (_, String url) { + weakThis.target?.callbacksHandler.onPageStarted(url); + }, + onPageFinished: (_, String url) { + weakThis.target?.callbacksHandler.onPageFinished(url); + }, + onReceivedError: ( + _, + int errorCode, + String description, + String failingUrl, + ) { + weakThis.target?.callbacksHandler.onWebResourceError(WebResourceError( + errorCode: errorCode, + description: description, + failingUrl: failingUrl, + errorType: _errorCodeToErrorType(errorCode), + )); + }, + onReceivedRequestError: ( + _, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + ) { + if (request.isForMainFrame) { + weakThis.target?.callbacksHandler.onWebResourceError(WebResourceError( + errorCode: error.errorCode, + description: error.description, + failingUrl: request.url, + errorType: _errorCodeToErrorType(error.errorCode), + )); + } + }, + urlLoading: (_, String url) { + weakThis.target?._handleNavigationRequest( + url: url, + isForMainFrame: true, + ); + }, + requestLoading: (_, android_webview.WebResourceRequest request) { + weakThis.target?._handleNavigationRequest( + url: request.url, + isForMainFrame: request.isForMainFrame, + ); + }, + ); return webView.setWebViewClient(_webViewClient); } @@ -444,114 +495,6 @@ Future<void> _setZoomEnabled(bool zoomEnabled) { return webView.settings.setSupportZoom(zoomEnabled); } -} - -/// Exposes a channel to receive calls from javaScript. -class WebViewAndroidJavaScriptChannel - extends android_webview.JavaScriptChannel { - /// Creates a [WebViewAndroidJavaScriptChannel]. - WebViewAndroidJavaScriptChannel( - String channelName, this.javascriptChannelRegistry) - : super(channelName); - - /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. - final JavascriptChannelRegistry javascriptChannelRegistry; - - @override - void postMessage(String message) { - javascriptChannelRegistry.onJavascriptChannelMessage(channelName, message); - } -} - -/// Receives callbacks when content can not be handled by the rendering engine for [WebViewAndroidPlatformController], and should be downloaded instead. -/// -/// When handling navigation requests, this calls [onNavigationRequestCallback] -/// when a [android_webview.WebView] attempts to navigate to a new page. If -/// this callback return true, this calls [loadUrl]. -class WebViewAndroidDownloadListener extends android_webview.DownloadListener { - /// Creates a [WebViewAndroidDownloadListener]. - WebViewAndroidDownloadListener({required this.loadUrl}); - - // Changed by WebViewAndroidPlatformController. - FutureOr<bool> Function({ - required String url, - required bool isForMainFrame, - })? _onNavigationRequest; - - /// Callback to load a URL when a navigation request is approved. - final Future<void> Function(String url, Map<String, String>? headers) loadUrl; - - @override - void onDownloadStart( - String url, - String userAgent, - String contentDisposition, - String mimetype, - int contentLength, - ) { - if (_onNavigationRequest == null) { - return; - } - - final FutureOr<bool> returnValue = _onNavigationRequest!( - url: url, - isForMainFrame: true, - ); - - if (returnValue is bool && returnValue) { - loadUrl(url, <String, String>{}); - } else { - (returnValue as Future<bool>).then((bool shouldLoadUrl) { - if (shouldLoadUrl) { - loadUrl(url, <String, String>{}); - } - }); - } - } -} - -/// Receives various navigation requests and errors for [WebViewAndroidPlatformController]. -/// -/// When handling navigation requests, this calls [onNavigationRequestCallback] -/// when a [android_webview.WebView] attempts to navigate to a new page. If -/// this callback return true, this calls [loadUrl]. -class WebViewAndroidWebViewClient extends android_webview.WebViewClient { - /// Creates a [WebViewAndroidWebViewClient] that doesn't handle navigation requests. - WebViewAndroidWebViewClient({ - required this.onPageStartedCallback, - required this.onPageFinishedCallback, - required this.onWebResourceErrorCallback, - }) : loadUrl = null, - onNavigationRequestCallback = null, - super(shouldOverrideUrlLoading: false); - - /// Creates a [WebViewAndroidWebViewClient] that handles navigation requests. - WebViewAndroidWebViewClient.handlesNavigation({ - required this.onPageStartedCallback, - required this.onPageFinishedCallback, - required this.onWebResourceErrorCallback, - required this.onNavigationRequestCallback, - required this.loadUrl, - }) : super(shouldOverrideUrlLoading: true); - - /// Callback when [android_webview.WebViewClient] receives a callback from [android_webview.WebViewClient].onPageStarted. - final void Function(String url) onPageStartedCallback; - - /// Callback when [android_webview.WebViewClient] receives a callback from [android_webview.WebViewClient].onPageFinished. - final void Function(String url) onPageFinishedCallback; - - /// Callback when [android_webview.WebViewClient] receives an error callback. - void Function(WebResourceError error) onWebResourceErrorCallback; - - /// Checks whether a navigation request should be approved or disaproved. - final FutureOr<bool> Function({ - required String url, - required bool isForMainFrame, - })? onNavigationRequestCallback; - - /// Callback when a navigation request is approved. - final Future<void> Function(String url, Map<String, String>? headers)? - loadUrl; static WebResourceErrorType _errorCodeToErrorType(int errorCode) { switch (errorCode) { @@ -594,110 +537,55 @@ ); } - /// Whether this [android_webview.WebViewClient] handles navigation requests. - bool get handlesNavigation => - loadUrl != null && onNavigationRequestCallback != null; - - @override - void onPageStarted(android_webview.WebView webView, String url) { - onPageStartedCallback(url); - } - - @override - void onPageFinished(android_webview.WebView webView, String url) { - onPageFinishedCallback(url); - } - - @override - void onReceivedError( - android_webview.WebView webView, - int errorCode, - String description, - String failingUrl, - ) { - onWebResourceErrorCallback(WebResourceError( - errorCode: errorCode, - description: description, - failingUrl: failingUrl, - errorType: _errorCodeToErrorType(errorCode), - )); - } - - @override - void onReceivedRequestError( - android_webview.WebView webView, - android_webview.WebResourceRequest request, - android_webview.WebResourceError error, - ) { - if (request.isForMainFrame) { - onWebResourceErrorCallback(WebResourceError( - errorCode: error.errorCode, - description: error.description, - failingUrl: request.url, - errorType: _errorCodeToErrorType(error.errorCode), - )); - } - } - - @override - void urlLoading(android_webview.WebView webView, String url) { - if (!handlesNavigation) { + void _handleNavigationRequest({ + required String url, + required bool isForMainFrame, + }) { + if (!_hasNavigationDelegate) { return; } - final FutureOr<bool> returnValue = onNavigationRequestCallback!( + final FutureOr<bool> returnValue = callbacksHandler.onNavigationRequest( url: url, - isForMainFrame: true, + isForMainFrame: isForMainFrame, ); if (returnValue is bool && returnValue) { - loadUrl!(url, <String, String>{}); + loadUrl(url, <String, String>{}); } else if (returnValue is Future<bool>) { returnValue.then((bool shouldLoadUrl) { if (shouldLoadUrl) { - loadUrl!(url, <String, String>{}); - } - }); - } - } - - @override - void requestLoading( - android_webview.WebView webView, - android_webview.WebResourceRequest request, - ) { - if (!handlesNavigation) { - return; - } - - final FutureOr<bool> returnValue = onNavigationRequestCallback!( - url: request.url, - isForMainFrame: request.isForMainFrame, - ); - - if (returnValue is bool && returnValue) { - loadUrl!(request.url, <String, String>{}); - } else if (returnValue is Future<bool>) { - returnValue.then((bool shouldLoadUrl) { - if (shouldLoadUrl) { - loadUrl!(request.url, <String, String>{}); + loadUrl(url, <String, String>{}); } }); } } } -/// Handles JavaScript dialogs, favicons, titles, and the progress for [WebViewAndroidPlatformController]. -class WebViewAndroidWebChromeClient extends android_webview.WebChromeClient { - // Changed by WebViewAndroidPlatformController. - void Function(int progress)? _onProgress; +/// Exposes a channel to receive calls from javaScript. +class WebViewAndroidJavaScriptChannel + extends android_webview.JavaScriptChannel { + /// Creates a [WebViewAndroidJavaScriptChannel]. + WebViewAndroidJavaScriptChannel( + String channelName, + this.javascriptChannelRegistry, + ) : super( + channelName, + postMessage: withWeakRefenceTo( + javascriptChannelRegistry, + (WeakReference<JavascriptChannelRegistry> weakReference) { + return (String message) { + weakReference.target?.onJavascriptChannelMessage( + channelName, + message, + ); + }; + }, + ), + ); - @override - void onProgressChanged(android_webview.WebView webView, int progress) { - if (_onProgress != null) { - _onProgress!(progress); - } - } + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. + final JavascriptChannelRegistry javascriptChannelRegistry; } /// Handles constructing [android_webview.WebView]s and calling static methods.
diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index f3946ed..26f773d 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
@@ -88,8 +88,6 @@ abstract class WebViewHostApi { void create(int instanceId, bool useHybridComposition); - void dispose(int instanceId); - void loadData( int instanceId, String data, @@ -169,8 +167,6 @@ abstract class WebSettingsHostApi { void create(int instanceId, int webViewInstanceId); - void dispose(int instanceId); - void setDomStorageEnabled(int instanceId, bool flag); void setJavaScriptCanOpenWindowsAutomatically(int instanceId, bool flag); @@ -203,8 +199,6 @@ @FlutterApi() abstract class JavaScriptChannelFlutterApi { - void dispose(int instanceId); - void postMessage(int instanceId, String message); } @@ -215,8 +209,6 @@ @FlutterApi() abstract class WebViewClientFlutterApi { - void dispose(int instanceId); - void onPageStarted(int instanceId, int webViewInstanceId, String url); void onPageFinished(int instanceId, int webViewInstanceId, String url); @@ -252,8 +244,6 @@ @FlutterApi() abstract class DownloadListenerFlutterApi { - void dispose(int instanceId); - void onDownloadStart( int instanceId, String url, @@ -266,7 +256,7 @@ @HostApi(dartHostTestHandler: 'TestWebChromeClientHostApi') abstract class WebChromeClientHostApi { - void create(int instanceId, int webViewClientInstanceId); + void create(int instanceId); } @HostApi(dartHostTestHandler: 'TestAssetManagerHostApi') @@ -278,8 +268,6 @@ @FlutterApi() abstract class WebChromeClientFlutterApi { - void dispose(int instanceId); - void onProgressChanged(int instanceId, int webViewInstanceId, int progress); }
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 62f4cc9..f5f2e3c 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@ description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.10.1 +version: 2.10.2 environment: sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index f3ec4bd..d10f4e7 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
@@ -270,6 +270,7 @@ final WebViewClient mockWebViewClient = MockWebViewClient(); when(mockWebViewClient.copy()).thenReturn(MockWebViewClient()); when(mockWebViewClient.shouldOverrideUrlLoading).thenReturn(false); + instanceManager.addDartCreatedInstance(mockWebViewClient); webView.setWebViewClient(mockWebViewClient); final int webViewClientInstanceId = @@ -334,6 +335,7 @@ final DownloadListener mockDownloadListener = MockDownloadListener(); when(mockDownloadListener.copy()).thenReturn(MockDownloadListener()); + instanceManager.addDartCreatedInstance(mockDownloadListener); webView.setDownloadListener(mockDownloadListener); final int downloadListenerInstanceId = @@ -345,16 +347,6 @@ }); test('setWebChromeClient', () { - // Setting a WebChromeClient requires setting a WebViewClient first. - TestWebViewClientHostApi.setup(MockTestWebViewClientHostApi()); - WebViewClient.api = WebViewClientHostApiImpl( - instanceManager: instanceManager, - ); - final WebViewClient mockWebViewClient = MockWebViewClient(); - when(mockWebViewClient.copy()).thenReturn(MockWebViewClient()); - when(mockWebViewClient.shouldOverrideUrlLoading).thenReturn(false); - webView.setWebViewClient(mockWebViewClient); - TestWebChromeClientHostApi.setup(MockTestWebChromeClientHostApi()); WebChromeClient.api = WebChromeClientHostApiImpl( instanceManager: instanceManager, @@ -362,6 +354,7 @@ final WebChromeClient mockWebChromeClient = MockWebChromeClient(); when(mockWebChromeClient.copy()).thenReturn(MockWebChromeClient()); + instanceManager.addDartCreatedInstance(mockWebChromeClient); webView.setWebChromeClient(mockWebChromeClient); final int webChromeClientInstanceId = @@ -372,21 +365,6 @@ )); }); - test('release', () { - final MockTestWebSettingsHostApi mockWebSettingsPlatformHostApi = - MockTestWebSettingsHostApi(); - TestWebSettingsHostApi.setup(mockWebSettingsPlatformHostApi); - - WebSettings.api = - WebSettingsHostApiImpl(instanceManager: instanceManager); - final int webSettingsInstanceId = - instanceManager.getIdentifier(webView.settings)!; - - webView.release(); - verify(mockWebSettingsPlatformHostApi.dispose(webSettingsInstanceId)); - verify(mockPlatformHostApi.dispose(webViewInstanceId)); - }); - test('copy', () { expect(webView.copy(), isA<WebView>()); }); @@ -544,16 +522,22 @@ }); test('postMessage', () { + late final String result; + when(mockJavaScriptChannel.postMessage).thenReturn((String message) { + result = message; + }); + flutterApi.postMessage( mockJavaScriptChannelInstanceId, 'Hello, World!', ); - verify(mockJavaScriptChannel.postMessage('Hello, World!')); + + expect(result, 'Hello, World!'); }); test('copy', () { expect( - JavaScriptChannel.detached('channel').copy(), + JavaScriptChannel.detached('channel', postMessage: (_) {}).copy(), isA<JavaScriptChannel>(), ); }); @@ -588,30 +572,51 @@ }); test('onPageStarted', () { + late final List<Object> result; + when(mockWebViewClient.onPageStarted).thenReturn( + (WebView webView, String url) { + result = <Object>[webView, url]; + }, + ); + flutterApi.onPageStarted( mockWebViewClientInstanceId, mockWebViewInstanceId, 'https://www.google.com', ); - verify(mockWebViewClient.onPageStarted( - mockWebView, - 'https://www.google.com', - )); + + expect(result, <Object>[mockWebView, 'https://www.google.com']); }); test('onPageFinished', () { + late final List<Object> result; + when(mockWebViewClient.onPageFinished).thenReturn( + (WebView webView, String url) { + result = <Object>[webView, url]; + }, + ); + flutterApi.onPageFinished( mockWebViewClientInstanceId, mockWebViewInstanceId, 'https://www.google.com', ); - verify(mockWebViewClient.onPageFinished( - mockWebView, - 'https://www.google.com', - )); + + expect(result, <Object>[mockWebView, 'https://www.google.com']); }); test('onReceivedRequestError', () { + late final List<Object> result; + when(mockWebViewClient.onReceivedRequestError).thenReturn( + ( + WebView webView, + WebResourceRequest request, + WebResourceError error, + ) { + result = <Object>[webView, request, error]; + }, + ); + flutterApi.onReceivedRequestError( mockWebViewClientInstanceId, mockWebViewInstanceId, @@ -626,14 +631,25 @@ WebResourceErrorData(errorCode: 34, description: 'error description'), ); - verify(mockWebViewClient.onReceivedRequestError( - mockWebView, - argThat(isNotNull), - argThat(isNotNull), - )); + expect( + result, + containsAllInOrder(<Object?>[mockWebView, isNotNull, isNotNull]), + ); }); test('onReceivedError', () { + late final List<Object> result; + when(mockWebViewClient.onReceivedError).thenReturn( + ( + WebView webView, + int errorCode, + String description, + String failingUrl, + ) { + result = <Object>[webView, errorCode, description, failingUrl]; + }, + ); + flutterApi.onReceivedError( mockWebViewClientInstanceId, mockWebViewInstanceId, @@ -642,15 +658,22 @@ 'https://www.google.com', ); - verify(mockWebViewClient.onReceivedError( - mockWebView, - 14, - 'desc', - 'https://www.google.com', - )); + expect( + result, + containsAllInOrder( + <Object?>[mockWebView, 14, 'desc', 'https://www.google.com'], + ), + ); }); test('requestLoading', () { + late final List<Object> result; + when(mockWebViewClient.requestLoading).thenReturn( + (WebView webView, WebResourceRequest request) { + result = <Object>[webView, request]; + }, + ); + flutterApi.requestLoading( mockWebViewClientInstanceId, mockWebViewInstanceId, @@ -664,20 +687,27 @@ ), ); - verify(mockWebViewClient.requestLoading( - mockWebView, - argThat(isNotNull), - )); + expect( + result, + containsAllInOrder(<Object?>[mockWebView, isNotNull]), + ); }); test('urlLoading', () { + late final List<Object> result; + when(mockWebViewClient.urlLoading).thenReturn( + (WebView webView, String url) { + result = <Object>[webView, url]; + }, + ); + flutterApi.urlLoading(mockWebViewClientInstanceId, mockWebViewInstanceId, 'https://www.google.com'); - verify(mockWebViewClient.urlLoading( - mockWebView, - 'https://www.google.com', - )); + expect( + result, + containsAllInOrder(<Object?>[mockWebView, 'https://www.google.com']), + ); }); test('copy', () { @@ -705,7 +735,26 @@ instanceManager.addDartCreatedInstance(mockDownloadListener); }); - test('onPageStarted', () { + test('onDownloadStart', () { + late final List<Object> result; + when(mockDownloadListener.onDownloadStart).thenReturn( + ( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + result = <Object>[ + url, + userAgent, + contentDisposition, + mimetype, + contentLength, + ]; + }, + ); + flutterApi.onDownloadStart( mockDownloadListenerInstanceId, 'url', @@ -714,17 +763,26 @@ 'mimetype', 45, ); - verify(mockDownloadListener.onDownloadStart( - 'url', - 'userAgent', - 'contentDescription', - 'mimetype', - 45, - )); + + expect( + result, + containsAllInOrder(<Object?>[ + 'url', + 'userAgent', + 'contentDescription', + 'mimetype', + 45, + ]), + ); }); test('copy', () { - expect(DownloadListener.detached().copy(), isA<DownloadListener>()); + expect( + DownloadListener.detached( + onDownloadStart: (_, __, ____, _____, ______) {}, + ).copy(), + isA<DownloadListener>(), + ); }); }); @@ -757,13 +815,21 @@ instanceManager.addDartCreatedInstance(mockWebView); }); - test('onPageStarted', () { + test('onProgressChanged', () { + late final List<Object> result; + when(mockWebChromeClient.onProgressChanged).thenReturn( + (WebView webView, int progress) { + result = <Object>[webView, progress]; + }, + ); + flutterApi.onProgressChanged( mockWebChromeClientInstanceId, mockWebViewInstanceId, 76, ); - verify(mockWebChromeClient.onProgressChanged(mockWebView, 76)); + + expect(result, containsAllInOrder(<Object?>[mockWebView, 76])); }); test('copy', () {
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index 116ac83..a53b639 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
@@ -69,12 +69,11 @@ } @override - void onDownloadStart(String? url, String? userAgent, - String? contentDisposition, String? mimetype, int? contentLength) => - super.noSuchMethod( - Invocation.method(#onDownloadStart, - [url, userAgent, contentDisposition, mimetype, contentLength]), - returnValueForMissingStub: null); + void Function(String, String, String, String, int) get onDownloadStart => + (super.noSuchMethod(Invocation.getter(#onDownloadStart), + returnValue: (String url, String userAgent, String contentDisposition, + String mimetype, int contentLength) {}) as void Function( + String, String, String, String, int)); @override _i2.DownloadListener copy() => (super.noSuchMethod(Invocation.method(#copy, []), @@ -94,9 +93,9 @@ (super.noSuchMethod(Invocation.getter(#channelName), returnValue: '') as String); @override - void postMessage(String? message) => - super.noSuchMethod(Invocation.method(#postMessage, [message]), - returnValueForMissingStub: null); + void Function(String) get postMessage => + (super.noSuchMethod(Invocation.getter(#postMessage), + returnValue: (String message) {}) as void Function(String)); @override _i2.JavaScriptChannel copy() => (super.noSuchMethod(Invocation.method(#copy, []), @@ -158,9 +157,8 @@ } @override - void create(int? instanceId, int? webViewClientInstanceId) => - super.noSuchMethod( - Invocation.method(#create, [instanceId, webViewClientInstanceId]), + void create(int? instanceId) => + super.noSuchMethod(Invocation.method(#create, [instanceId]), returnValueForMissingStub: null); } @@ -178,10 +176,6 @@ Invocation.method(#create, [instanceId, webViewInstanceId]), returnValueForMissingStub: null); @override - void dispose(int? instanceId) => - super.noSuchMethod(Invocation.method(#dispose, [instanceId]), - returnValueForMissingStub: null); - @override void setDomStorageEnabled(int? instanceId, bool? flag) => super.noSuchMethod( Invocation.method(#setDomStorageEnabled, [instanceId, flag]), returnValueForMissingStub: null); @@ -290,10 +284,6 @@ Invocation.method(#create, [instanceId, useHybridComposition]), returnValueForMissingStub: null); @override - void dispose(int? instanceId) => - super.noSuchMethod(Invocation.method(#dispose, [instanceId]), - returnValueForMissingStub: null); - @override void loadData( int? instanceId, String? data, String? mimeType, String? encoding) => super.noSuchMethod( @@ -444,10 +434,6 @@ } @override - void onProgressChanged(_i2.WebView? webView, int? progress) => super - .noSuchMethod(Invocation.method(#onProgressChanged, [webView, progress]), - returnValueForMissingStub: null); - @override _i2.WebChromeClient copy() => (super.noSuchMethod(Invocation.method(#copy, []), returnValue: _FakeWebChromeClient_3()) as _i2.WebChromeClient); @@ -602,11 +588,6 @@ returnValue: Future<void>.value(), returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); @override - _i5.Future<void> release() => - (super.noSuchMethod(Invocation.method(#release, []), - returnValue: Future<void>.value(), - returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); - @override _i2.WebView copy() => (super.noSuchMethod(Invocation.method(#copy, []), returnValue: _FakeWebView_6()) as _i2.WebView); } @@ -624,35 +605,6 @@ (super.noSuchMethod(Invocation.getter(#shouldOverrideUrlLoading), returnValue: false) as bool); @override - void onPageStarted(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#onPageStarted, [webView, url]), - returnValueForMissingStub: null); - @override - void onPageFinished(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#onPageFinished, [webView, url]), - returnValueForMissingStub: null); - @override - void onReceivedRequestError(_i2.WebView? webView, - _i2.WebResourceRequest? request, _i2.WebResourceError? error) => - super.noSuchMethod( - Invocation.method(#onReceivedRequestError, [webView, request, error]), - returnValueForMissingStub: null); - @override - void onReceivedError(_i2.WebView? webView, int? errorCode, - String? description, String? failingUrl) => - super.noSuchMethod( - Invocation.method( - #onReceivedError, [webView, errorCode, description, failingUrl]), - returnValueForMissingStub: null); - @override - void requestLoading(_i2.WebView? webView, _i2.WebResourceRequest? request) => - super.noSuchMethod(Invocation.method(#requestLoading, [webView, request]), - returnValueForMissingStub: null); - @override - void urlLoading(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#urlLoading, [webView, url]), - returnValueForMissingStub: null); - @override _i2.WebViewClient copy() => (super.noSuchMethod(Invocation.method(#copy, []), returnValue: _FakeWebViewClient_7()) as _i2.WebViewClient); }
diff --git a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart index afc80ab..91a7bf1 100644 --- a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart
@@ -73,7 +73,6 @@ static const MessageCodec<Object?> codec = _TestWebViewHostApiCodec(); void create(int instanceId, bool useHybridComposition); - void dispose(int instanceId); void loadData( int instanceId, String data, String? mimeType, String? encoding); void loadDataWithBaseUrl(int instanceId, String? baseUrl, String data, @@ -127,25 +126,6 @@ } { final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( - 'dev.flutter.pigeon.WebViewHostApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMockMessageHandler(null); - } else { - channel.setMockMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.WebViewHostApi.dispose was null.'); - final List<Object?> args = (message as List<Object?>?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.WebViewHostApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return <Object?, Object?>{}; - }); - } - } - { - final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( 'dev.flutter.pigeon.WebViewHostApi.loadData', codec, binaryMessenger: binaryMessenger); if (api == null) { @@ -692,7 +672,6 @@ static const MessageCodec<Object?> codec = _TestWebSettingsHostApiCodec(); void create(int instanceId, int webViewInstanceId); - void dispose(int instanceId); void setDomStorageEnabled(int instanceId, bool flag); void setJavaScriptCanOpenWindowsAutomatically(int instanceId, bool flag); void setSupportMultipleWindows(int instanceId, bool support); @@ -731,25 +710,6 @@ } { final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( - 'dev.flutter.pigeon.WebSettingsHostApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMockMessageHandler(null); - } else { - channel.setMockMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.WebSettingsHostApi.dispose was null.'); - final List<Object?> args = (message as List<Object?>?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.WebSettingsHostApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return <Object?, Object?>{}; - }); - } - } - { - final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( 'dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled', codec, binaryMessenger: binaryMessenger); if (api == null) { @@ -1130,7 +1090,7 @@ abstract class TestWebChromeClientHostApi { static const MessageCodec<Object?> codec = _TestWebChromeClientHostApiCodec(); - void create(int instanceId, int webViewClientInstanceId); + void create(int instanceId); static void setup(TestWebChromeClientHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1147,10 +1107,7 @@ final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.create was null, expected non-null int.'); - final int? arg_webViewClientInstanceId = (args[1] as int?); - assert(arg_webViewClientInstanceId != null, - 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.create was null, expected non-null int.'); - api.create(arg_instanceId!, arg_webViewClientInstanceId!); + api.create(arg_instanceId!); return <Object?, Object?>{}; }); }
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart index 73076d9..5eea6db 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
@@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/widgets.dart'; @@ -26,10 +25,10 @@ android_webview.WebStorage, android_webview.WebView, android_webview.WebResourceRequest, - WebViewAndroidDownloadListener, + android_webview.DownloadListener, WebViewAndroidJavaScriptChannel, - WebViewAndroidWebChromeClient, - WebViewAndroidWebViewClient, + android_webview.WebChromeClient, + android_webview.WebViewClient, JavascriptChannelRegistry, WebViewPlatformCallbacksHandler, WebViewProxy, @@ -45,9 +44,9 @@ late MockWebViewProxy mockWebViewProxy; late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; - late WebViewAndroidWebViewClient webViewClient; - late WebViewAndroidDownloadListener downloadListener; - late WebViewAndroidWebChromeClient webChromeClient; + late android_webview.WebViewClient webViewClient; + late android_webview.DownloadListener downloadListener; + late android_webview.WebChromeClient webChromeClient; late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; @@ -235,7 +234,6 @@ ), ); - expect(testController.webViewClient.handlesNavigation, isTrue); expect(testController.webViewClient.shouldOverrideUrlLoading, isTrue); }); @@ -693,20 +691,20 @@ group('WebViewPlatformCallbacksHandler', () { testWidgets('onPageStarted', (WidgetTester tester) async { await buildWidget(tester); - webViewClient.onPageStarted(mockWebView, 'https://google.com'); + webViewClient.onPageStarted!(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onPageStarted('https://google.com')); }); testWidgets('onPageFinished', (WidgetTester tester) async { await buildWidget(tester); - webViewClient.onPageFinished(mockWebView, 'https://google.com'); + webViewClient.onPageFinished!(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onPageFinished('https://google.com')); }); testWidgets('onWebResourceError from onReceivedError', (WidgetTester tester) async { await buildWidget(tester); - webViewClient.onReceivedError( + webViewClient.onReceivedError!( mockWebView, android_webview.WebViewClient.errorAuthentication, 'description', @@ -727,7 +725,7 @@ testWidgets('onWebResourceError from onReceivedRequestError', (WidgetTester tester) async { await buildWidget(tester); - webViewClient.onReceivedRequestError( + webViewClient.onReceivedRequestError!( mockWebView, android_webview.WebResourceRequest( url: 'https://google.com', @@ -762,7 +760,7 @@ url: 'https://google.com', )).thenReturn(true); - webViewClient.urlLoading(mockWebView, 'https://google.com'); + webViewClient.urlLoading!(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onNavigationRequest( url: 'https://google.com', isForMainFrame: true, @@ -778,7 +776,7 @@ url: 'https://google.com', )).thenReturn(true); - webViewClient.requestLoading( + webViewClient.requestLoading!( mockWebView, android_webview.WebResourceRequest( url: 'https://google.com', @@ -843,192 +841,4 @@ verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(false)); }); }); - - group('WebViewAndroidWebViewClient', () { - test( - 'urlLoading should call loadUrl when onNavigationRequestCallback returns true', - () { - final Completer<void> completer = Completer<void>(); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - true, - loadUrl: (String url, Map<String, String>? headers) async { - completer.complete(); - }); - - webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); - expect(completer.isCompleted, isTrue); - }); - - test( - 'urlLoading should call loadUrl when onNavigationRequestCallback returns a Future true', - () async { - final Completer<void> completer = Completer<void>(); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - Future<bool>.value(true), - loadUrl: (String url, Map<String, String>? headers) async { - completer.complete(); - }); - - webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); - expect(completer.future, completes); - }); - - test( - 'urlLoading should not call laodUrl when onNavigationRequestCallback returns false', - () async { - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - false, - loadUrl: (String url, Map<String, String>? headers) async { - fail( - 'loadUrl should not be called if onNavigationRequestCallback returns false.'); - }); - - webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); - }); - - test( - 'urlLoading should not call loadUrl when onNavigationRequestCallback returns a Future false', - () { - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - Future<bool>.value(false), - loadUrl: (String url, Map<String, String>? headers) async { - fail( - 'loadUrl should not be called if onNavigationRequestCallback returns false.'); - }); - - webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); - }); - - test( - 'requestLoading should call loadUrl when onNavigationRequestCallback returns true', - () { - final Completer<void> completer = Completer<void>(); - final MockWebResourceRequest mockRequest = MockWebResourceRequest(); - when(mockRequest.isForMainFrame).thenReturn(true); - when(mockRequest.url).thenReturn('https://flutter.dev'); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - true, - loadUrl: (String url, Map<String, String>? headers) async { - expect(url, 'https://flutter.dev'); - completer.complete(); - }); - - webViewClient.requestLoading(MockWebView(), mockRequest); - expect(completer.isCompleted, isTrue); - }); - - test( - 'requestLoading should call loadUrl when onNavigationRequestCallback returns a Future true', - () async { - final Completer<void> completer = Completer<void>(); - final MockWebResourceRequest mockRequest = MockWebResourceRequest(); - when(mockRequest.isForMainFrame).thenReturn(true); - when(mockRequest.url).thenReturn('https://flutter.dev'); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - Future<bool>.value(true), - loadUrl: (String url, Map<String, String>? headers) async { - expect(url, 'https://flutter.dev'); - completer.complete(); - }); - - webViewClient.requestLoading(MockWebView(), mockRequest); - expect(completer.future, completes); - }); - - test( - 'requestLoading should not call loadUrl when onNavigationRequestCallback returns false', - () { - final MockWebResourceRequest mockRequest = MockWebResourceRequest(); - when(mockRequest.isForMainFrame).thenReturn(true); - when(mockRequest.url).thenReturn('https://flutter.dev'); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - false, - loadUrl: (String url, Map<String, String>? headers) { - fail( - 'loadUrl should not be called if onNavigationRequestCallback returns false.'); - }); - - webViewClient.requestLoading(MockWebView(), mockRequest); - }); - - test( - 'requestLoading should not call loadUrl when onNavigationRequestCallback returns a Future false', - () { - final MockWebResourceRequest mockRequest = MockWebResourceRequest(); - when(mockRequest.isForMainFrame).thenReturn(true); - when(mockRequest.url).thenReturn('https://flutter.dev'); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - Future<bool>.value(false), - loadUrl: (String url, Map<String, String>? headers) { - fail( - 'loadUrl should not be called if onNavigationRequestCallback returns false.'); - }); - - webViewClient.requestLoading(MockWebView(), mockRequest); - }); - }); }
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart index acaff1c..860562e 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
@@ -305,11 +305,6 @@ returnValue: Future<void>.value(), returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); @override - _i5.Future<void> release() => - (super.noSuchMethod(Invocation.method(#release, []), - returnValue: Future<void>.value(), - returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); - @override _i2.WebView copy() => (super.noSuchMethod(Invocation.method(#copy, []), returnValue: _FakeWebView_3()) as _i2.WebView); } @@ -344,28 +339,20 @@ returnValue: <String, String>{}) as Map<String, String>); } -/// A class which mocks [WebViewAndroidDownloadListener]. +/// A class which mocks [DownloadListener]. /// /// See the documentation for Mockito's code generation for more information. -class MockWebViewAndroidDownloadListener extends _i1.Mock - implements _i7.WebViewAndroidDownloadListener { - MockWebViewAndroidDownloadListener() { +class MockDownloadListener extends _i1.Mock implements _i2.DownloadListener { + MockDownloadListener() { _i1.throwOnMissingStub(this); } @override - _i5.Future<void> Function(String, Map<String, String>?) get loadUrl => - (super.noSuchMethod(Invocation.getter(#loadUrl), - returnValue: (String url, Map<String, String>? headers) => - Future<void>.value()) as _i5.Future<void> Function( - String, Map<String, String>?)); - @override - void onDownloadStart(String? url, String? userAgent, - String? contentDisposition, String? mimetype, int? contentLength) => - super.noSuchMethod( - Invocation.method(#onDownloadStart, - [url, userAgent, contentDisposition, mimetype, contentLength]), - returnValueForMissingStub: null); + void Function(String, String, String, String, int) get onDownloadStart => + (super.noSuchMethod(Invocation.getter(#onDownloadStart), + returnValue: (String url, String userAgent, String contentDisposition, + String mimetype, int contentLength) {}) as void Function( + String, String, String, String, int)); @override _i2.DownloadListener copy() => (super.noSuchMethod(Invocation.method(#copy, []), @@ -391,101 +378,42 @@ (super.noSuchMethod(Invocation.getter(#channelName), returnValue: '') as String); @override - void postMessage(String? message) => - super.noSuchMethod(Invocation.method(#postMessage, [message]), - returnValueForMissingStub: null); + void Function(String) get postMessage => + (super.noSuchMethod(Invocation.getter(#postMessage), + returnValue: (String message) {}) as void Function(String)); @override _i2.JavaScriptChannel copy() => (super.noSuchMethod(Invocation.method(#copy, []), returnValue: _FakeJavaScriptChannel_6()) as _i2.JavaScriptChannel); } -/// A class which mocks [WebViewAndroidWebChromeClient]. +/// A class which mocks [WebChromeClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockWebViewAndroidWebChromeClient extends _i1.Mock - implements _i7.WebViewAndroidWebChromeClient { - MockWebViewAndroidWebChromeClient() { +class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { + MockWebChromeClient() { _i1.throwOnMissingStub(this); } @override - void onProgressChanged(_i2.WebView? webView, int? progress) => super - .noSuchMethod(Invocation.method(#onProgressChanged, [webView, progress]), - returnValueForMissingStub: null); - @override _i2.WebChromeClient copy() => (super.noSuchMethod(Invocation.method(#copy, []), returnValue: _FakeWebChromeClient_7()) as _i2.WebChromeClient); } -/// A class which mocks [WebViewAndroidWebViewClient]. +/// A class which mocks [WebViewClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockWebViewAndroidWebViewClient extends _i1.Mock - implements _i7.WebViewAndroidWebViewClient { - MockWebViewAndroidWebViewClient() { +class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { + MockWebViewClient() { _i1.throwOnMissingStub(this); } @override - void Function(String) get onPageStartedCallback => - (super.noSuchMethod(Invocation.getter(#onPageStartedCallback), - returnValue: (String url) {}) as void Function(String)); - @override - void Function(String) get onPageFinishedCallback => - (super.noSuchMethod(Invocation.getter(#onPageFinishedCallback), - returnValue: (String url) {}) as void Function(String)); - @override - void Function(_i4.WebResourceError) get onWebResourceErrorCallback => - (super.noSuchMethod(Invocation.getter(#onWebResourceErrorCallback), - returnValue: (_i4.WebResourceError error) {}) - as void Function(_i4.WebResourceError)); - @override - set onWebResourceErrorCallback( - void Function(_i4.WebResourceError)? _onWebResourceErrorCallback) => - super.noSuchMethod( - Invocation.setter( - #onWebResourceErrorCallback, _onWebResourceErrorCallback), - returnValueForMissingStub: null); - @override - bool get handlesNavigation => - (super.noSuchMethod(Invocation.getter(#handlesNavigation), - returnValue: false) as bool); - @override bool get shouldOverrideUrlLoading => (super.noSuchMethod(Invocation.getter(#shouldOverrideUrlLoading), returnValue: false) as bool); @override - void onPageStarted(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#onPageStarted, [webView, url]), - returnValueForMissingStub: null); - @override - void onPageFinished(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#onPageFinished, [webView, url]), - returnValueForMissingStub: null); - @override - void onReceivedError(_i2.WebView? webView, int? errorCode, - String? description, String? failingUrl) => - super.noSuchMethod( - Invocation.method( - #onReceivedError, [webView, errorCode, description, failingUrl]), - returnValueForMissingStub: null); - @override - void onReceivedRequestError(_i2.WebView? webView, - _i2.WebResourceRequest? request, _i2.WebResourceError? error) => - super.noSuchMethod( - Invocation.method(#onReceivedRequestError, [webView, request, error]), - returnValueForMissingStub: null); - @override - void urlLoading(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#urlLoading, [webView, url]), - returnValueForMissingStub: null); - @override - void requestLoading(_i2.WebView? webView, _i2.WebResourceRequest? request) => - super.noSuchMethod(Invocation.method(#requestLoading, [webView, request]), - returnValueForMissingStub: null); - @override _i2.WebViewClient copy() => (super.noSuchMethod(Invocation.method(#copy, []), returnValue: _FakeWebViewClient_8()) as _i2.WebViewClient); }