From ce6e9066ba3f4ea4d7d0e0077af97cfb942c2174 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Fri, 22 Apr 2022 16:24:41 +0200 Subject: [PATCH 1/7] Fixed Android error in some cases when calling setServiceWorkerClient java method on ServiceWorkerManager initialization --- CHANGELOG.md | 4 + README.md | 2 - .../ServiceWorkerManager.java | 104 +++-- .../webview_flutter_test.dart | 408 ++++++++++-------- .../lib/in_app_browser_example.screen.dart | 4 +- example/lib/in_app_webiew_example.screen.dart | 173 ++++---- example/lib/main.dart | 5 +- .../android/service_worker_controller.dart | 20 +- pubspec.yaml | 2 +- 9 files changed, 409 insertions(+), 313 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7db1649..24a8cd27 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.4.0+3 + +- Fixed Android error in some cases when calling `setServiceWorkerClient` java method on `ServiceWorkerManager` initialization + ## 5.4.0+2 - Fixed Android `ChromeCustomTabsActivity` not responding to the `ActionBroadcastReceiver` diff --git a/README.md b/README.md index 807cd3f0..3b0f4b04 100755 --- a/README.md +++ b/README.md @@ -59,6 +59,4 @@ Add `flutter_inappwebview` as a [dependency in your pubspec.yaml file](https://f ## Support -Use the [develop branch](https://github.com/pichillilorenzo/flutter_inappwebview/tree/develop) when making a pull request. - Did you find this plugin useful? Please consider to [make a donation](https://inappwebview.dev/donate/) to help improve it! \ No newline at end of file diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ServiceWorkerManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ServiceWorkerManager.java index 804af946..aab8f3a2 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ServiceWorkerManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ServiceWorkerManager.java @@ -38,50 +38,6 @@ public class ServiceWorkerManager implements MethodChannel.MethodCallHandler { channel.setMethodCallHandler(this); if (WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_BASIC_USAGE)) { serviceWorkerController = ServiceWorkerControllerCompat.getInstance(); - serviceWorkerController.setServiceWorkerClient(new ServiceWorkerClientCompat() { - @Nullable - @Override - public WebResourceResponse shouldInterceptRequest(@NonNull WebResourceRequest request) { - final Map obj = new HashMap<>(); - obj.put("url", request.getUrl().toString()); - obj.put("method", request.getMethod()); - obj.put("headers", request.getRequestHeaders()); - obj.put("isForMainFrame", request.isForMainFrame()); - obj.put("hasGesture", request.hasGesture()); - obj.put("isRedirect", request.isRedirect()); - - Util.WaitFlutterResult flutterResult; - try { - flutterResult = Util.invokeMethodAndWait(channel, "shouldInterceptRequest", obj); - } catch (InterruptedException e) { - e.printStackTrace(); - return null; - } - - if (flutterResult.error != null) { - Log.e(LOG_TAG, flutterResult.error); - } - else if (flutterResult.result != null) { - Map res = (Map) flutterResult.result; - String contentType = (String) res.get("contentType"); - String contentEncoding = (String) res.get("contentEncoding"); - byte[] data = (byte[]) res.get("data"); - Map responseHeaders = (Map) res.get("headers"); - Integer statusCode = (Integer) res.get("statusCode"); - String reasonPhrase = (String) res.get("reasonPhrase"); - - ByteArrayInputStream inputStream = (data != null) ? new ByteArrayInputStream(data) : null; - - if ((responseHeaders == null && statusCode == null && reasonPhrase == null) || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return new WebResourceResponse(contentType, contentEncoding, inputStream); - } else { - return new WebResourceResponse(contentType, contentEncoding, statusCode, reasonPhrase, responseHeaders, inputStream); - } - } - - return null; - } - }); } else { serviceWorkerController = null; } @@ -92,6 +48,13 @@ public class ServiceWorkerManager implements MethodChannel.MethodCallHandler { ServiceWorkerWebSettingsCompat serviceWorkerWebSettings = (serviceWorkerController != null) ? serviceWorkerController.getServiceWorkerWebSettings() : null; switch (call.method) { + case "setServiceWorkerClient": + { + Boolean isNull = (Boolean) call.argument("isNull"); + setServiceWorkerClient(isNull); + } + result.success(true); + break; case "getAllowContentAccess": if (serviceWorkerWebSettings != null && WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS)) { result.success(serviceWorkerWebSettings.getAllowContentAccess()); @@ -152,9 +115,62 @@ public class ServiceWorkerManager implements MethodChannel.MethodCallHandler { result.notImplemented(); } } + + private void setServiceWorkerClient(Boolean isNull) { + if (serviceWorkerController != null) { + serviceWorkerController.setServiceWorkerClient(isNull ? null : new ServiceWorkerClientCompat() { + @Nullable + @Override + public WebResourceResponse shouldInterceptRequest(@NonNull WebResourceRequest request) { + final Map obj = new HashMap<>(); + obj.put("url", request.getUrl().toString()); + obj.put("method", request.getMethod()); + obj.put("headers", request.getRequestHeaders()); + obj.put("isForMainFrame", request.isForMainFrame()); + obj.put("hasGesture", request.hasGesture()); + obj.put("isRedirect", request.isRedirect()); + + Util.WaitFlutterResult flutterResult; + try { + flutterResult = Util.invokeMethodAndWait(channel, "shouldInterceptRequest", obj); + } catch (InterruptedException e) { + e.printStackTrace(); + return null; + } + + if (flutterResult.error != null) { + Log.e(LOG_TAG, flutterResult.error); + } + else if (flutterResult.result != null) { + Map res = (Map) flutterResult.result; + String contentType = (String) res.get("contentType"); + String contentEncoding = (String) res.get("contentEncoding"); + byte[] data = (byte[]) res.get("data"); + Map responseHeaders = (Map) res.get("headers"); + Integer statusCode = (Integer) res.get("statusCode"); + String reasonPhrase = (String) res.get("reasonPhrase"); + + ByteArrayInputStream inputStream = (data != null) ? new ByteArrayInputStream(data) : null; + + if ((responseHeaders == null && statusCode == null && reasonPhrase == null) || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return new WebResourceResponse(contentType, contentEncoding, inputStream); + } else { + return new WebResourceResponse(contentType, contentEncoding, statusCode, reasonPhrase, responseHeaders, inputStream); + } + } + + return null; + } + }); + } + } public void dispose() { channel.setMethodCallHandler(null); + if (serviceWorkerController != null) { + serviceWorkerController.setServiceWorkerClient(null); + serviceWorkerController = null; + } plugin = null; } } diff --git a/example/integration_test/webview_flutter_test.dart b/example/integration_test/webview_flutter_test.dart index e4a01c10..7d011eb3 100644 --- a/example/integration_test/webview_flutter_test.dart +++ b/example/integration_test/webview_flutter_test.dart @@ -77,7 +77,7 @@ void main() { AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true); } - group('InAppWebView', () { + group('InAppWebView', () { testWidgets('initialUrlRequest', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( @@ -1709,13 +1709,14 @@ void main() { group('intercept ajax request', () { testWidgets('send string data', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer shouldInterceptAjaxPostRequestCompleter = - Completer(); + Completer(); final Completer> onAjaxReadyStateChangeCompleter = - Completer>(); + Completer>(); final Completer> onAjaxProgressCompleter = - Completer>(); + Completer>(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -1745,9 +1746,9 @@ void main() { """), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptAjaxRequest: true, - )), + clearCache: true, + useShouldInterceptAjaxRequest: true, + )), onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, @@ -1780,9 +1781,9 @@ void main() { await shouldInterceptAjaxPostRequestCompleter.future; final Map onAjaxReadyStateChangeValue = - await onAjaxReadyStateChangeCompleter.future; + await onAjaxReadyStateChangeCompleter.future; final Map onAjaxProgressValue = - await onAjaxProgressCompleter.future; + await onAjaxProgressCompleter.future; expect( mapEquals(onAjaxReadyStateChangeValue, @@ -1795,13 +1796,14 @@ void main() { }); testWidgets('send json data', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer shouldInterceptAjaxPostRequestCompleter = - Completer(); + Completer(); final Completer> onAjaxReadyStateChangeCompleter = - Completer>(); + Completer>(); final Completer> onAjaxProgressCompleter = - Completer>(); + Completer>(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -1835,15 +1837,16 @@ void main() { """), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptAjaxRequest: true, - )), + clearCache: true, + useShouldInterceptAjaxRequest: true, + )), onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, shouldInterceptAjaxRequest: (controller, ajaxRequest) async { String data = ajaxRequest.data; - assert(data.contains('"firstname":"Foo"') && data.contains('"lastname":"Bar"')); + assert(data.contains('"firstname":"Foo"') && + data.contains('"lastname":"Bar"')); ajaxRequest.responseType = 'json'; ajaxRequest.data = '{"firstname": "Foo2", "lastname": "Bar2"}'; @@ -1871,9 +1874,9 @@ void main() { await shouldInterceptAjaxPostRequestCompleter.future; final Map onAjaxReadyStateChangeValue = - await onAjaxReadyStateChangeCompleter.future; + await onAjaxReadyStateChangeCompleter.future; final Map onAjaxProgressValue = - await onAjaxProgressCompleter.future; + await onAjaxProgressCompleter.future; expect( mapEquals(onAjaxReadyStateChangeValue, @@ -1886,13 +1889,14 @@ void main() { }); testWidgets('send URLSearchParams data', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer shouldInterceptAjaxPostRequestCompleter = - Completer(); + Completer(); final Completer> onAjaxReadyStateChangeCompleter = - Completer>(); + Completer>(); final Completer> onAjaxProgressCompleter = - Completer>(); + Completer>(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -1924,9 +1928,9 @@ void main() { """), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptAjaxRequest: true, - )), + clearCache: true, + useShouldInterceptAjaxRequest: true, + )), onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, @@ -1959,9 +1963,9 @@ void main() { await shouldInterceptAjaxPostRequestCompleter.future; final Map onAjaxReadyStateChangeValue = - await onAjaxReadyStateChangeCompleter.future; + await onAjaxReadyStateChangeCompleter.future; final Map onAjaxProgressValue = - await onAjaxProgressCompleter.future; + await onAjaxProgressCompleter.future; expect( mapEquals(onAjaxReadyStateChangeValue, @@ -1974,13 +1978,14 @@ void main() { }); testWidgets('send FormData', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer shouldInterceptAjaxPostRequestCompleter = - Completer(); + Completer(); final Completer> onAjaxReadyStateChangeCompleter = - Completer>(); + Completer>(); final Completer> onAjaxProgressCompleter = - Completer>(); + Completer>(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -2012,9 +2017,9 @@ void main() { """), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptAjaxRequest: true, - )), + clearCache: true, + useShouldInterceptAjaxRequest: true, + )), onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, @@ -2025,7 +2030,9 @@ void main() { var bodyString = String.fromCharCodes(body); assert(bodyString.indexOf("WebKitFormBoundary") >= 0); - ajaxRequest.data = utf8.encode(bodyString.replaceFirst("Foo", "Foo2").replaceFirst("Bar", "Bar2")); + ajaxRequest.data = utf8.encode(bodyString + .replaceFirst("Foo", "Foo2") + .replaceFirst("Bar", "Bar2")); ajaxRequest.responseType = 'json'; shouldInterceptAjaxPostRequestCompleter.complete(controller); return ajaxRequest; @@ -2051,9 +2058,9 @@ void main() { await shouldInterceptAjaxPostRequestCompleter.future; final Map onAjaxReadyStateChangeValue = - await onAjaxReadyStateChangeCompleter.future; + await onAjaxReadyStateChangeCompleter.future; final Map onAjaxProgressValue = - await onAjaxProgressCompleter.future; + await onAjaxProgressCompleter.future; expect( mapEquals(onAjaxReadyStateChangeValue, @@ -2068,11 +2075,12 @@ void main() { group('intercept fetch request', () { testWidgets('send string data', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer> fetchPostCompleter = - Completer>(); + Completer>(); final Completer shouldInterceptFetchPostRequestCompleter = - Completer(); + Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -2113,9 +2121,9 @@ void main() { """), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptFetchRequest: true, - )), + clearCache: true, + useShouldInterceptFetchRequest: true, + )), onWebViewCreated: (controller) { controllerCompleter.complete(controller); @@ -2147,11 +2155,12 @@ void main() { }); testWidgets('send json data', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer> fetchPostCompleter = - Completer>(); + Completer>(); final Completer shouldInterceptFetchPostRequestCompleter = - Completer(); + Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -2196,9 +2205,9 @@ void main() { """), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptFetchRequest: true, - )), + clearCache: true, + useShouldInterceptFetchRequest: true, + )), onWebViewCreated: (controller) { controllerCompleter.complete(controller); @@ -2211,7 +2220,8 @@ void main() { }, shouldInterceptFetchRequest: (controller, fetchRequest) async { String body = fetchRequest.body; - assert(body.contains('"firstname":"Foo"') && body.contains('"lastname":"Bar"')); + assert(body.contains('"firstname":"Foo"') && + body.contains('"lastname":"Bar"')); fetchRequest.body = '{"firstname": "Foo2", "lastname": "Bar2"}'; shouldInterceptFetchPostRequestCompleter.complete(); @@ -2231,11 +2241,12 @@ void main() { }); testWidgets('send URLSearchParams data', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer> fetchPostCompleter = - Completer>(); + Completer>(); final Completer shouldInterceptFetchPostRequestCompleter = - Completer(); + Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -2278,9 +2289,9 @@ void main() { """), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptFetchRequest: true, - )), + clearCache: true, + useShouldInterceptFetchRequest: true, + )), onWebViewCreated: (controller) { controllerCompleter.complete(controller); @@ -2312,11 +2323,12 @@ void main() { }); testWidgets('send FormData', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer> fetchPostCompleter = - Completer>(); + Completer>(); final Completer shouldInterceptFetchPostRequestCompleter = - Completer(); + Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -2357,9 +2369,9 @@ void main() { """), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptFetchRequest: true, - )), + clearCache: true, + useShouldInterceptFetchRequest: true, + )), onWebViewCreated: (controller) { controllerCompleter.complete(controller); @@ -2377,7 +2389,9 @@ void main() { var bodyString = String.fromCharCodes(body); assert(bodyString.indexOf("WebKitFormBoundary") >= 0); - fetchRequest.body = utf8.encode(bodyString.replaceFirst("Foo", "Foo2").replaceFirst("Bar", "Bar2")); + fetchRequest.body = utf8.encode(bodyString + .replaceFirst("Foo", "Foo2") + .replaceFirst("Bar", "Bar2")); shouldInterceptFetchPostRequestCompleter.complete(); return fetchRequest; }, @@ -2404,24 +2418,24 @@ void main() { child: InAppWebView( key: GlobalKey(), initialUrlRequest: - URLRequest(url: Uri.parse('https://flutter.dev/')), + URLRequest(url: Uri.parse('https://flutter.dev/')), onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, initialOptions: InAppWebViewGroupOptions( crossPlatform: - InAppWebViewOptions(clearCache: true, contentBlockers: [ - ContentBlocker( - trigger: + InAppWebViewOptions(clearCache: true, contentBlockers: [ + ContentBlocker( + trigger: ContentBlockerTrigger(urlFilter: ".*", resourceType: [ - ContentBlockerTriggerResourceType.IMAGE, - ContentBlockerTriggerResourceType.STYLE_SHEET - ], ifTopUrl: [ - "https://flutter.dev/" - ]), - action: ContentBlockerAction( - type: ContentBlockerActionType.BLOCK)) - ])), + ContentBlockerTriggerResourceType.IMAGE, + ContentBlockerTriggerResourceType.STYLE_SHEET + ], ifTopUrl: [ + "https://flutter.dev/" + ]), + action: ContentBlockerAction( + type: ContentBlockerActionType.BLOCK)) + ])), onLoadStop: (controller, url) { pageLoaded.complete(); }, @@ -3261,7 +3275,7 @@ setTimeout(function() { child: InAppWebView( key: GlobalKey(), initialUrlRequest: - URLRequest(url: Uri.parse('https://github.com/flutter')), + URLRequest(url: Uri.parse('https://github.com/flutter')), onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, @@ -3278,7 +3292,7 @@ setTimeout(function() { ); final InAppWebViewController controller = - await controllerCompleter.future; + await controllerCompleter.future; await pageLoaded.future; listenForScaleChange = true; @@ -4167,10 +4181,14 @@ setTimeout(function() { await pageLoaded.future; await controller.injectJavascriptFileFromUrl( - urlFile: Uri.parse('https://www.notawebsite..com/jquery-3.3.1.min.js'), - scriptHtmlTagAttributes: ScriptHtmlTagAttributes(id: 'jquery-error', onError: () { - jQueryLoadError.complete(); - },)); + urlFile: + Uri.parse('https://www.notawebsite..com/jquery-3.3.1.min.js'), + scriptHtmlTagAttributes: ScriptHtmlTagAttributes( + id: 'jquery-error', + onError: () { + jQueryLoadError.complete(); + }, + )); await jQueryLoadError.future; expect( await controller.evaluateJavascript( @@ -4182,9 +4200,12 @@ setTimeout(function() { await controller.injectJavascriptFileFromUrl( urlFile: Uri.parse('https://code.jquery.com/jquery-3.3.1.min.js'), - scriptHtmlTagAttributes: ScriptHtmlTagAttributes(id: 'jquery', onLoad: () { - jQueryLoaded.complete(); - },)); + scriptHtmlTagAttributes: ScriptHtmlTagAttributes( + id: 'jquery', + onLoad: () { + jQueryLoaded.complete(); + }, + )); await jQueryLoaded.future; expect( await controller.evaluateJavascript( @@ -4903,21 +4924,19 @@ setTimeout(function() { expect(await InAppWebViewController.getDefaultUserAgent(), isNotNull); }); - testWidgets('launches with pull-to-refresh feature', (WidgetTester tester) async { + testWidgets('launches with pull-to-refresh feature', + (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final pullToRefreshController = PullToRefreshController( options: PullToRefreshOptions( - color: Colors.blue, - size: AndroidPullToRefreshSize.DEFAULT, - backgroundColor: Colors.grey, - enabled: true, - slingshotDistance: 150, - distanceToTriggerSync: 150, - attributedTitle: IOSNSAttributedString(string: "test") - ), - onRefresh: () { - - }, + color: Colors.blue, + size: AndroidPullToRefreshSize.DEFAULT, + backgroundColor: Colors.grey, + enabled: true, + slingshotDistance: 150, + distanceToTriggerSync: 150, + attributedTitle: IOSNSAttributedString(string: "test")), + onRefresh: () {}, ); await tester.pumpWidget( @@ -4925,12 +4944,11 @@ setTimeout(function() { textDirection: TextDirection.ltr, child: InAppWebView( key: GlobalKey(), - initialUrlRequest: URLRequest(url: Uri.parse('https://github.com/flutter')), + initialUrlRequest: + URLRequest(url: Uri.parse('https://github.com/flutter')), initialOptions: InAppWebViewGroupOptions( - android: AndroidInAppWebViewOptions( - useHybridComposition: true - ) - ), + android: + AndroidInAppWebViewOptions(useHybridComposition: true)), pullToRefreshController: pullToRefreshController, onWebViewCreated: (controller) { controllerCompleter.complete(controller); @@ -4939,22 +4957,22 @@ setTimeout(function() { ), ); final InAppWebViewController controller = - await controllerCompleter.future; + await controllerCompleter.future; final String? currentUrl = (await controller.getUrl())?.toString(); expect(currentUrl, 'https://github.com/flutter'); }); group('WebMessage', () { testWidgets('WebMessageChannel', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer webMessageCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: InAppWebView( key: GlobalKey(), - initialData: InAppWebViewInitialData( - data: """ + initialData: InAppWebViewInitialData(data: """ @@ -4989,15 +5007,20 @@ setTimeout(function() { webMessageCompleter.complete(consoleMessage.message); }, onLoadStop: (controller, url) async { - var webMessageChannel = await controller.createWebMessageChannel(); + var webMessageChannel = + await controller.createWebMessageChannel(); var port1 = webMessageChannel!.port1; var port2 = webMessageChannel.port2; await port1.setWebMessageCallback((message) async { - await port1.postMessage(WebMessage(data: message! + " and back")); + await port1 + .postMessage(WebMessage(data: message! + " and back")); }); - await controller.postWebMessage(message: WebMessage(data: "capturePort", ports: [port2]), targetOrigin: Uri.parse("*")); - await controller.evaluateJavascript(source: "document.getElementById('button').click();"); + await controller.postWebMessage( + message: WebMessage(data: "capturePort", ports: [port2]), + targetOrigin: Uri.parse("*")); + await controller.evaluateJavascript( + source: "document.getElementById('button').click();"); }, ), ), @@ -5009,7 +5032,8 @@ setTimeout(function() { }); testWidgets('WebMessageListener', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); + final Completer controllerCompleter = + Completer(); final Completer pageLoaded = Completer(); final Completer webMessageCompleter = Completer(); await tester.pumpWidget( @@ -5021,8 +5045,10 @@ setTimeout(function() { await controller.addWebMessageListener(WebMessageListener( jsObjectName: "myTestObj", allowedOriginRules: Set.from(["https://*.example.com"]), - onPostMessage: (message, sourceOrigin, isMainFrame, replyProxy) { - assert(sourceOrigin.toString() == "https://www.example.com"); + onPostMessage: + (message, sourceOrigin, isMainFrame, replyProxy) { + assert( + sourceOrigin.toString() == "https://www.example.com"); assert(isMainFrame); replyProxy.postMessage(message! + " and back"); @@ -5042,7 +5068,8 @@ setTimeout(function() { ), ); final controller = await controllerCompleter.future; - await controller.loadUrl(urlRequest: URLRequest(url: Uri.parse("https://www.example.com/"))); + await controller.loadUrl( + urlRequest: URLRequest(url: Uri.parse("https://www.example.com/"))); await pageLoaded.future; await controller.evaluateJavascript(source: """ @@ -5273,7 +5300,9 @@ setTimeout(function() { }, skip: !Platform.isAndroid); test('setWebContentsDebuggingEnabled', () async { - expect(AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true), completes); + expect( + AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true), + completes); }, skip: !Platform.isAndroid); }, skip: !Platform.isAndroid); @@ -5377,8 +5406,7 @@ setTimeout(function() { textDirection: TextDirection.ltr, child: InAppWebView( key: GlobalKey(), - initialData: InAppWebViewInitialData( - data: """ + initialData: InAppWebViewInitialData(data: """ @@ -5393,8 +5421,7 @@ setTimeout(function() { - """ - ), + """), initialOptions: InAppWebViewGroupOptions( ios: IOSInAppWebViewOptions( applePayAPIEnabled: true, @@ -5421,6 +5448,47 @@ setTimeout(function() { }, skip: !Platform.isIOS); }); + group('Service Worker', () { + testWidgets('AndroidInAppWebViewController', (WidgetTester tester) async { + final Completer completer = Completer(); + + var swAvailable = await AndroidWebViewFeature.isFeatureSupported( + AndroidWebViewFeature.SERVICE_WORKER_BASIC_USAGE); + var swInterceptAvailable = await AndroidWebViewFeature.isFeatureSupported( + AndroidWebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST); + + if (swAvailable && swInterceptAvailable) { + AndroidServiceWorkerController serviceWorkerController = + AndroidServiceWorkerController.instance(); + + await serviceWorkerController + .setServiceWorkerClient(AndroidServiceWorkerClient( + shouldInterceptRequest: (request) async { + if (!completer.isCompleted) { + completer.complete(); + } + return null; + }, + )); + } else { + completer.complete(); + } + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: + URLRequest(url: Uri.parse('https://mdn.github.io/sw-test/')), + ), + ), + ); + + expect(completer.future, completes); + }, skip: !Platform.isAndroid); + }); + group('Cookie Manager', () { testWidgets('set, get, delete', (WidgetTester tester) async { CookieManager cookieManager = CookieManager.instance(); @@ -5474,7 +5542,8 @@ setTimeout(function() { final Completer pageLoaded = Completer(); var headlessWebView = new HeadlessInAppWebView( - initialUrlRequest: URLRequest(url: Uri.parse("https://github.com/flutter")), + initialUrlRequest: + URLRequest(url: Uri.parse("https://github.com/flutter")), onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, @@ -5503,20 +5572,20 @@ setTimeout(function() { final Completer pageLoaded = Completer(); var headlessWebView = new HeadlessInAppWebView( - initialUrlRequest: URLRequest(url: Uri.parse("https://github.com/flutter")), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - onLoadStop: (controller, url) async { - pageLoaded.complete(); - } - ); + initialUrlRequest: + URLRequest(url: Uri.parse("https://github.com/flutter")), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + onLoadStop: (controller, url) async { + pageLoaded.complete(); + }); await headlessWebView.run(); expect(headlessWebView.isRunning(), true); final InAppWebViewController controller = - await controllerCompleter.future; + await controllerCompleter.future; await pageLoaded.future; final String? url = (await controller.getUrl())?.toString(); @@ -5537,11 +5606,12 @@ setTimeout(function() { final Completer controllerCompleter = Completer(); var headlessWebView = new HeadlessInAppWebView( - initialUrlRequest: URLRequest(url: Uri.parse("https://github.com/flutter")), - initialSize: Size(600, 800), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, + initialUrlRequest: + URLRequest(url: Uri.parse("https://github.com/flutter")), + initialSize: Size(600, 800), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, ); await headlessWebView.run(); @@ -5566,7 +5636,8 @@ setTimeout(function() { final Completer pageLoaded = Completer(); var headlessWebView = new HeadlessInAppWebView( - initialUrlRequest: URLRequest(url: Uri.parse("https://github.com/flutter")), + initialUrlRequest: + URLRequest(url: Uri.parse("https://github.com/flutter")), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions(javaScriptEnabled: false)), onWebViewCreated: (controller) { @@ -5610,8 +5681,7 @@ setTimeout(function() { expect(inAppBrowser.isOpened(), true); expect(() async { await inAppBrowser.openUrlRequest( - urlRequest: - URLRequest(url: Uri.parse("https://flutter.dev"))); + urlRequest: URLRequest(url: Uri.parse("https://flutter.dev"))); }, throwsA(isInstanceOf())); await inAppBrowser.firstPageLoaded.future; @@ -5633,13 +5703,14 @@ setTimeout(function() { await inAppBrowser.show(); }, throwsA(isInstanceOf())); - await inAppBrowser.openFile(assetFilePath: "test_assets/in_app_webview_initial_file_test.html"); + await inAppBrowser.openFile( + assetFilePath: "test_assets/in_app_webview_initial_file_test.html"); await inAppBrowser.browserCreated.future; expect(inAppBrowser.isOpened(), true); expect(() async { await inAppBrowser.openUrlRequest( urlRequest: - URLRequest(url: Uri.parse("https://github.com/flutter"))); + URLRequest(url: Uri.parse("https://github.com/flutter"))); }, throwsA(isInstanceOf())); await inAppBrowser.firstPageLoaded.future; @@ -5661,7 +5732,8 @@ setTimeout(function() { await inAppBrowser.show(); }, throwsA(isInstanceOf())); - await inAppBrowser.openData(data: """ + await inAppBrowser.openData( + data: """ @@ -5685,7 +5757,7 @@ setTimeout(function() { expect(() async { await inAppBrowser.openUrlRequest( urlRequest: - URLRequest(url: Uri.parse("https://github.com/flutter"))); + URLRequest(url: Uri.parse("https://github.com/flutter"))); }, throwsA(isInstanceOf())); await inAppBrowser.firstPageLoaded.future; @@ -5728,12 +5800,12 @@ setTimeout(function() { var chromeSafariBrowser = new MyChromeSafariBrowser(); expect(chromeSafariBrowser.isOpened(), false); - await chromeSafariBrowser.open(url: Uri.parse("https://github.com/flutter")); + await chromeSafariBrowser.open( + url: Uri.parse("https://github.com/flutter")); await chromeSafariBrowser.browserCreated.future; expect(chromeSafariBrowser.isOpened(), true); expect(() async { - await chromeSafariBrowser.open( - url: Uri.parse("https://flutter.dev")); + await chromeSafariBrowser.open(url: Uri.parse("https://flutter.dev")); }, throwsA(isInstanceOf())); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); @@ -5748,21 +5820,18 @@ setTimeout(function() { expect(chromeSafariBrowser.isOpened(), false); await chromeSafariBrowser.open( - url: Uri.parse("https://github.com/flutter"), - options: ChromeSafariBrowserClassOptions( - android: AndroidChromeCustomTabsOptions( - isSingleInstance: true - ) - ) - ); + url: Uri.parse("https://github.com/flutter"), + options: ChromeSafariBrowserClassOptions( + android: + AndroidChromeCustomTabsOptions(isSingleInstance: true))); await chromeSafariBrowser.browserCreated.future; expect(chromeSafariBrowser.isOpened(), true); expect(() async { - await chromeSafariBrowser.open( - url: Uri.parse("https://flutter.dev")); + await chromeSafariBrowser.open(url: Uri.parse("https://flutter.dev")); }, throwsA(isInstanceOf())); - await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); + await expectLater( + chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.close(); await chromeSafariBrowser.browserClosed.future; expect(chromeSafariBrowser.isOpened(), false); @@ -5776,18 +5845,15 @@ setTimeout(function() { url: Uri.parse("https://github.com/flutter"), options: ChromeSafariBrowserClassOptions( android: AndroidChromeCustomTabsOptions( - isTrustedWebActivity: true - ) - ) - ); + isTrustedWebActivity: true))); await chromeSafariBrowser.browserCreated.future; expect(chromeSafariBrowser.isOpened(), true); expect(() async { - await chromeSafariBrowser.open( - url: Uri.parse("https://flutter.dev")); + await chromeSafariBrowser.open(url: Uri.parse("https://flutter.dev")); }, throwsA(isInstanceOf())); - await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); + await expectLater( + chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.close(); await chromeSafariBrowser.browserClosed.future; expect(chromeSafariBrowser.isOpened(), false); @@ -5801,19 +5867,15 @@ setTimeout(function() { url: Uri.parse("https://github.com/flutter"), options: ChromeSafariBrowserClassOptions( android: AndroidChromeCustomTabsOptions( - isTrustedWebActivity: true, - isSingleInstance: true - ) - ) - ); + isTrustedWebActivity: true, isSingleInstance: true))); await chromeSafariBrowser.browserCreated.future; expect(chromeSafariBrowser.isOpened(), true); expect(() async { - await chromeSafariBrowser.open( - url: Uri.parse("https://flutter.dev")); + await chromeSafariBrowser.open(url: Uri.parse("https://flutter.dev")); }, throwsA(isInstanceOf())); - await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); + await expectLater( + chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.close(); await chromeSafariBrowser.browserClosed.future; expect(chromeSafariBrowser.isOpened(), false); @@ -5837,8 +5899,8 @@ setTimeout(function() { textDirection: TextDirection.ltr, child: InAppWebView( key: GlobalKey(), - initialUrlRequest: - URLRequest(url: Uri.parse('http://localhost:8080/test_assets/index.html')), + initialUrlRequest: URLRequest( + url: Uri.parse('http://localhost:8080/test_assets/index.html')), onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, @@ -5846,7 +5908,7 @@ setTimeout(function() { ), ); final InAppWebViewController controller = - await controllerCompleter.future; + await controllerCompleter.future; final String? currentUrl = (await controller.getUrl())?.toString(); expect(currentUrl, 'http://localhost:8080/test_assets/index.html'); }); diff --git a/example/lib/in_app_browser_example.screen.dart b/example/lib/in_app_browser_example.screen.dart index f8e8e7eb..86b09622 100755 --- a/example/lib/in_app_browser_example.screen.dart +++ b/example/lib/in_app_browser_example.screen.dart @@ -83,7 +83,6 @@ class InAppBrowserExampleScreen extends StatefulWidget { } class _InAppBrowserExampleScreenState extends State { - late PullToRefreshController pullToRefreshController; @override @@ -99,7 +98,8 @@ class _InAppBrowserExampleScreenState extends State { widget.browser.webViewController.reload(); } else if (Platform.isIOS) { widget.browser.webViewController.loadUrl( - urlRequest: URLRequest(url: await widget.browser.webViewController.getUrl())); + urlRequest: URLRequest( + url: await widget.browser.webViewController.getUrl())); } }, ); diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index 90f77064..fff25346 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -17,15 +17,13 @@ class InAppWebViewExampleScreen extends StatefulWidget { } class _InAppWebViewExampleScreenState extends State { - final GlobalKey webViewKey = GlobalKey(); InAppWebViewController? webViewController; InAppWebViewGroupOptions options = InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( - useShouldOverrideUrlLoading: true, - mediaPlaybackRequiresUserGesture: false - ), + useShouldOverrideUrlLoading: true, + mediaPlaybackRequiresUserGesture: false), android: AndroidInAppWebViewOptions( useHybridComposition: true, ), @@ -102,9 +100,7 @@ class _InAppWebViewExampleScreenState extends State { body: SafeArea( child: Column(children: [ TextField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.search) - ), + decoration: InputDecoration(prefixIcon: Icon(Icons.search)), controller: urlController, keyboardType: TextInputType.url, onSubmitted: (value) { @@ -112,94 +108,95 @@ class _InAppWebViewExampleScreenState extends State { if (url.scheme.isEmpty) { url = Uri.parse("https://www.google.com/search?q=" + value); } - webViewController?.loadUrl( - urlRequest: URLRequest(url: url)); + webViewController?.loadUrl(urlRequest: URLRequest(url: url)); }, ), Expanded( - child: Stack( - children: [ - InAppWebView( - key: webViewKey, - // contextMenu: contextMenu, - initialUrlRequest: - URLRequest(url: Uri.parse("https://github.com/flutter")), - // initialFile: "assets/index.html", - initialUserScripts: UnmodifiableListView([]), - initialOptions: options, - pullToRefreshController: pullToRefreshController, - onWebViewCreated: (controller) { - webViewController = controller; - }, - onLoadStart: (controller, url) { - setState(() { - this.url = url.toString(); - urlController.text = this.url; - }); - }, - androidOnPermissionRequest: (controller, origin, resources) async { - return PermissionRequestResponse( - resources: resources, - action: PermissionRequestResponseAction.GRANT); - }, - shouldOverrideUrlLoading: (controller, navigationAction) async { - var uri = navigationAction.request.url!; + child: Stack( + children: [ + InAppWebView( + key: webViewKey, + // contextMenu: contextMenu, + initialUrlRequest: URLRequest( + url: Uri.parse("https://mdn.github.io/sw-test/")), + // initialFile: "assets/index.html", + initialUserScripts: UnmodifiableListView([]), + initialOptions: options, + pullToRefreshController: pullToRefreshController, + onWebViewCreated: (controller) { + webViewController = controller; + }, + onLoadStart: (controller, url) { + setState(() { + this.url = url.toString(); + urlController.text = this.url; + }); + }, + androidOnPermissionRequest: + (controller, origin, resources) async { + return PermissionRequestResponse( + resources: resources, + action: PermissionRequestResponseAction.GRANT); + }, + shouldOverrideUrlLoading: + (controller, navigationAction) async { + var uri = navigationAction.request.url!; - if (![ - "http", - "https", - "file", - "chrome", - "data", - "javascript", - "about" - ].contains(uri.scheme)) { - if (await canLaunch(url)) { - // Launch the App - await launch( - url, - ); - // and cancel the request - return NavigationActionPolicy.CANCEL; - } + if (![ + "http", + "https", + "file", + "chrome", + "data", + "javascript", + "about" + ].contains(uri.scheme)) { + if (await canLaunch(url)) { + // Launch the App + await launch( + url, + ); + // and cancel the request + return NavigationActionPolicy.CANCEL; } + } - return NavigationActionPolicy.ALLOW; - }, - onLoadStop: (controller, url) async { + return NavigationActionPolicy.ALLOW; + }, + onLoadStop: (controller, url) async { + pullToRefreshController.endRefreshing(); + setState(() { + this.url = url.toString(); + urlController.text = this.url; + }); + }, + onLoadError: (controller, url, code, message) { + pullToRefreshController.endRefreshing(); + }, + onProgressChanged: (controller, progress) { + if (progress == 100) { pullToRefreshController.endRefreshing(); - setState(() { - this.url = url.toString(); - urlController.text = this.url; - }); - }, - onLoadError: (controller, url, code, message) { - pullToRefreshController.endRefreshing(); - }, - onProgressChanged: (controller, progress) { - if (progress == 100) { - pullToRefreshController.endRefreshing(); - } - setState(() { - this.progress = progress / 100; - urlController.text = this.url; - }); - }, - onUpdateVisitedHistory: (controller, url, androidIsReload) { - setState(() { - this.url = url.toString(); - urlController.text = this.url; - }); - }, - onConsoleMessage: (controller, consoleMessage) { - print(consoleMessage); - }, - ), - progress < 1.0 - ? LinearProgressIndicator(value: progress) - : Container(), - ], - ), + } + setState(() { + this.progress = progress / 100; + urlController.text = this.url; + }); + }, + onUpdateVisitedHistory: (controller, url, androidIsReload) { + setState(() { + this.url = url.toString(); + urlController.text = this.url; + }); + }, + onConsoleMessage: (controller, consoleMessage) { + print(consoleMessage); + }, + ), + progress < 1.0 + ? LinearProgressIndicator(value: progress) + : Container(), + ], + ), ), ButtonBar( alignment: MainAxisAlignment.center, diff --git a/example/lib/main.dart b/example/lib/main.dart index 895042e5..3d9154cc 100755 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -31,12 +31,13 @@ Future main() async { AndroidServiceWorkerController serviceWorkerController = AndroidServiceWorkerController.instance(); - serviceWorkerController.serviceWorkerClient = AndroidServiceWorkerClient( + await serviceWorkerController + .setServiceWorkerClient(AndroidServiceWorkerClient( shouldInterceptRequest: (request) async { print(request); return null; }, - ); + )); } } diff --git a/lib/src/android/service_worker_controller.dart b/lib/src/android/service_worker_controller.dart index 2d364e45..1c51fe06 100644 --- a/lib/src/android/service_worker_controller.dart +++ b/lib/src/android/service_worker_controller.dart @@ -13,7 +13,25 @@ class AndroidServiceWorkerController { static const MethodChannel _channel = const MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_android_serviceworkercontroller'); - AndroidServiceWorkerClient? serviceWorkerClient; + AndroidServiceWorkerClient? _serviceWorkerClient; + + AndroidServiceWorkerClient? get serviceWorkerClient => _serviceWorkerClient; + + @Deprecated("Use setServiceWorkerClient instead") + set serviceWorkerClient(AndroidServiceWorkerClient? value) { + Map args = {}; + args.putIfAbsent('isNull', () => value == null); + _channel.invokeMethod("setServiceWorkerClient", args); + _serviceWorkerClient = value; + } + + ///Sets the service worker client + setServiceWorkerClient(AndroidServiceWorkerClient? value) async { + Map args = {}; + args.putIfAbsent('isNull', () => value == null); + await _channel.invokeMethod("setServiceWorkerClient", args); + _serviceWorkerClient = value; + } ///Gets the [AndroidServiceWorkerController] shared instance. static AndroidServiceWorkerController instance() { diff --git a/pubspec.yaml b/pubspec.yaml index 3191400f..359bb63c 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. -version: 5.4.0+2 +version: 5.4.0+3 homepage: https://github.com/pichillilorenzo/flutter_inappwebview environment: From d1e4dc55d0e7847c65cc2ad64f79d86d99e3742a Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Fri, 22 Apr 2022 16:32:03 +0200 Subject: [PATCH 2/7] updated example --- example/lib/in_app_webiew_example.screen.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index fff25346..b656d6fe 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -117,8 +117,8 @@ class _InAppWebViewExampleScreenState extends State { InAppWebView( key: webViewKey, // contextMenu: contextMenu, - initialUrlRequest: URLRequest( - url: Uri.parse("https://mdn.github.io/sw-test/")), + initialUrlRequest: + URLRequest(url: Uri.parse("https://github.com/flutter")), // initialFile: "assets/index.html", initialUserScripts: UnmodifiableListView([]), initialOptions: options, From 2d31a2f58b980fa2c840d360c91f637a71113d92 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 23 Apr 2022 04:02:37 +0200 Subject: [PATCH 3/7] =?UTF-8?q?Managed=20iOS=20native=20detachFromEngine?= =?UTF-8?q?=20flutter=20plugin=20event=20and=20updated=20dispose=C2=A0meth?= =?UTF-8?q?ods,=20Updated=20Android=20native=20HeadlessInAppWebViewManager?= =?UTF-8?q?.dispose=20and=20HeadlessInAppWebView.dispose=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 ++ .../HeadlessInAppWebView.java | 14 +++-- .../HeadlessInAppWebViewManager.java | 6 ++ ios/Classes/CredentialDatabase.swift | 62 +++++++++++++++---- .../HeadlessInAppWebViewManager.swift | 11 ++++ .../InAppBrowser/InAppBrowserManager.swift | 6 ++ .../InAppBrowserWebViewController.swift | 13 ++-- .../FlutterWebViewController.swift | 3 +- ios/Classes/InAppWebView/InAppWebView.swift | 16 ++--- ios/Classes/InAppWebViewMethodHandler.swift | 8 ++- ios/Classes/InAppWebViewStatic.swift | 8 +++ ios/Classes/MyCookieManager.swift | 46 +++++++++++--- ios/Classes/MyWebStorageManager.swift | 32 ++++++++-- ios/Classes/PlatformUtil.swift | 6 ++ .../PullToRefresh/PullToRefreshControl.swift | 1 + .../ChromeSafariBrowserManager.swift | 6 ++ .../SafariViewController.swift | 17 +++-- ios/Classes/SwiftFlutterPlugin.swift | 25 +++++++- .../Types/WKUserContentController.swift | 6 +- ios/Classes/Types/WebMessage.swift | 1 + ios/Classes/Types/WebMessageChannel.swift | 3 +- ios/Classes/Types/WebMessageListener.swift | 1 + ios/Classes/Types/WebMessagePort.swift | 1 + ios/Classes/Util.swift | 4 +- pubspec.yaml | 2 +- 25 files changed, 245 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24a8cd27..3f8d43c1 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.4.1 + +- Managed iOS native `detachFromEngine` flutter plugin event and updated `dispose` methods +- Updated Android native `HeadlessInAppWebViewManager.dispose` and `HeadlessInAppWebView.dispose` methods + ## 5.4.0+3 - Fixed Android error in some cases when calling `setServiceWorkerClient` java method on `ServiceWorkerManager` initialization diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java index b389ef96..bcf68b5e 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java @@ -110,12 +110,16 @@ public class HeadlessInAppWebView implements MethodChannel.MethodCallHandler { public void dispose() { channel.setMethodCallHandler(null); HeadlessInAppWebViewManager.webViews.remove(id); - ViewGroup contentView = (ViewGroup) plugin.activity.findViewById(android.R.id.content); - ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); - if (mainView != null) { - mainView.removeView(flutterWebView.getView()); + if (plugin != null) { + ViewGroup contentView = (ViewGroup) plugin.activity.findViewById(android.R.id.content); + ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); + if (mainView != null && flutterWebView != null) { + mainView.removeView(flutterWebView.getView()); + } + } + if (flutterWebView != null) { + flutterWebView.dispose(); } - flutterWebView.dispose(); flutterWebView = null; plugin = null; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java index 6539601c..e15b0095 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java @@ -26,7 +26,9 @@ import androidx.annotation.Nullable; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview.in_app_webview.FlutterWebView; +import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import io.flutter.plugin.common.MethodCall; @@ -77,6 +79,10 @@ public class HeadlessInAppWebViewManager implements MethodChannel.MethodCallHand public void dispose() { channel.setMethodCallHandler(null); + Collection headlessInAppWebViews = webViews.values(); + for (HeadlessInAppWebView headlessInAppWebView : headlessInAppWebViews) { + headlessInAppWebView.dispose(); + } webViews.clear(); } } diff --git a/ios/Classes/CredentialDatabase.swift b/ios/Classes/CredentialDatabase.swift index d4c4f64c..5bfb1e64 100755 --- a/ios/Classes/CredentialDatabase.swift +++ b/ios/Classes/CredentialDatabase.swift @@ -31,7 +31,11 @@ class CredentialDatabase: NSObject, FlutterPlugin { switch call.method { case "getAllAuthCredentials": var allCredentials: [[String: Any?]] = [] - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + guard let credentialStore = CredentialDatabase.credentialStore else { + result(allCredentials) + return + } + for (protectionSpace, credentials) in credentialStore.allCredentials { var crendentials: [[String: Any?]] = [] for c in credentials { let credential: [String: Any?] = c.value.toMap() @@ -43,10 +47,17 @@ class CredentialDatabase: NSObject, FlutterPlugin { "credentials": crendentials ] allCredentials.append(dict) - } } + } + } result(allCredentials) break case "getHttpAuthCredentials": + var crendentials: [[String: Any?]] = [] + guard let credentialStore = CredentialDatabase.credentialStore else { + result(crendentials) + return + } + let host = arguments!["host"] as! String let urlProtocol = arguments!["protocol"] as? String let urlPort = arguments!["port"] as? Int ?? 0 @@ -54,9 +65,8 @@ class CredentialDatabase: NSObject, FlutterPlugin { if let r = realm, r.isEmpty { realm = nil } - var crendentials: [[String: Any?]] = [] - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + for (protectionSpace, credentials) in credentialStore.allCredentials { if protectionSpace.host == host && protectionSpace.realm == realm && protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort { for c in credentials { @@ -68,6 +78,11 @@ class CredentialDatabase: NSObject, FlutterPlugin { result(crendentials) break case "setHttpAuthCredential": + guard let credentialStore = CredentialDatabase.credentialStore else { + result(false) + return + } + let host = arguments!["host"] as! String let urlProtocol = arguments!["protocol"] as? String let urlPort = arguments!["port"] as? Int ?? 0 @@ -78,11 +93,17 @@ class CredentialDatabase: NSObject, FlutterPlugin { let username = arguments!["username"] as! String let password = arguments!["password"] as! String let credential = URLCredential(user: username, password: password, persistence: .permanent) - CredentialDatabase.credentialStore!.set(credential, - for: URLProtectionSpace(host: host, port: urlPort, protocol: urlProtocol, realm: realm, authenticationMethod: NSURLAuthenticationMethodHTTPBasic)) + credentialStore.set(credential, + for: URLProtectionSpace(host: host, port: urlPort, protocol: urlProtocol, + realm: realm, authenticationMethod: NSURLAuthenticationMethodHTTPBasic)) result(true) break case "removeHttpAuthCredential": + guard let credentialStore = CredentialDatabase.credentialStore else { + result(false) + return + } + let host = arguments!["host"] as! String let urlProtocol = arguments!["protocol"] as? String let urlPort = arguments!["port"] as? Int ?? 0 @@ -96,7 +117,7 @@ class CredentialDatabase: NSObject, FlutterPlugin { var credential: URLCredential? = nil; var protectionSpaceCredential: URLProtectionSpace? = nil - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + for (protectionSpace, credentials) in credentialStore.allCredentials { if protectionSpace.host == host && protectionSpace.realm == realm && protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort { for c in credentials { @@ -113,12 +134,17 @@ class CredentialDatabase: NSObject, FlutterPlugin { } if let c = credential, let protectionSpace = protectionSpaceCredential { - CredentialDatabase.credentialStore!.remove(c, for: protectionSpace) + credentialStore.remove(c, for: protectionSpace) } result(true) break case "removeHttpAuthCredentials": + guard let credentialStore = CredentialDatabase.credentialStore else { + result(false) + return + } + let host = arguments!["host"] as! String let urlProtocol = arguments!["protocol"] as? String let urlPort = arguments!["port"] as? Int ?? 0 @@ -130,7 +156,7 @@ class CredentialDatabase: NSObject, FlutterPlugin { var credentialsToRemove: [URLCredential] = []; var protectionSpaceCredential: URLProtectionSpace? = nil - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + for (protectionSpace, credentials) in credentialStore.allCredentials { if protectionSpace.host == host && protectionSpace.realm == realm && protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort { protectionSpaceCredential = protectionSpace @@ -145,16 +171,21 @@ class CredentialDatabase: NSObject, FlutterPlugin { if let protectionSpace = protectionSpaceCredential { for credential in credentialsToRemove { - CredentialDatabase.credentialStore!.remove(credential, for: protectionSpace) + credentialStore.remove(credential, for: protectionSpace) } } result(true) break case "clearAllAuthCredentials": - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + guard let credentialStore = CredentialDatabase.credentialStore else { + result(false) + return + } + + for (protectionSpace, credentials) in credentialStore.allCredentials { for credential in credentials { - CredentialDatabase.credentialStore!.remove(credential.value, for: protectionSpace) + credentialStore.remove(credential.value, for: protectionSpace) } } result(true) @@ -164,4 +195,11 @@ class CredentialDatabase: NSObject, FlutterPlugin { break } } + + public func dispose() { + CredentialDatabase.channel?.setMethodCallHandler(nil) + CredentialDatabase.channel = nil + CredentialDatabase.registrar = nil + CredentialDatabase.credentialStore = nil + } } diff --git a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index 0bcc777c..9a75e97e 100644 --- a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift +++ b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -57,4 +57,15 @@ public class HeadlessInAppWebViewManager: NSObject, FlutterPlugin { headlessInAppWebView.onWebViewCreated() flutterWebView.makeInitialLoad(params: params as NSDictionary) } + + public func dispose() { + HeadlessInAppWebViewManager.channel?.setMethodCallHandler(nil) + HeadlessInAppWebViewManager.channel = nil + HeadlessInAppWebViewManager.registrar = nil + let headlessWebViews = HeadlessInAppWebViewManager.webViews.values + headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView) in + headlessWebView.dispose() + } + HeadlessInAppWebViewManager.webViews.removeAll() + } } diff --git a/ios/Classes/InAppBrowser/InAppBrowserManager.swift b/ios/Classes/InAppBrowser/InAppBrowserManager.swift index defda931..1bf90000 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserManager.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserManager.swift @@ -140,4 +140,10 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { } result(true) } + + public func dispose() { + InAppBrowserManager.channel?.setMethodCallHandler(nil) + InAppBrowserManager.channel = nil + InAppBrowserManager.registrar = nil + } } diff --git a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift index f806105d..ffcc6f7f 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -182,6 +182,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega deinit { print("InAppBrowserWebViewController - dealloc") + dispose() } public override func viewDidDisappear(_ animated: Bool) { @@ -550,7 +551,10 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public func dispose() { - webView.dispose() + onExit() + channel?.setMethodCallHandler(nil) + channel = nil + webView?.dispose() webView = nil view = nil if previousStatusBarStyle != -1 { @@ -563,18 +567,15 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega backButton.target = nil reloadButton.target = nil shareButton.target = nil - onExit() - channel?.setMethodCallHandler(nil) - channel = nil methodCallDelegate?.webView = nil methodCallDelegate = nil } public func onBrowserCreated() { - channel!.invokeMethod("onBrowserCreated", arguments: []) + channel?.invokeMethod("onBrowserCreated", arguments: []) } public func onExit() { - channel!.invokeMethod("onExit", arguments: []) + channel?.invokeMethod("onExit", arguments: []) } } diff --git a/ios/Classes/InAppWebView/FlutterWebViewController.swift b/ios/Classes/InAppWebView/FlutterWebViewController.swift index 8f883d2b..aa8f51a3 100755 --- a/ios/Classes/InAppWebView/FlutterWebViewController.swift +++ b/ios/Classes/InAppWebView/FlutterWebViewController.swift @@ -164,7 +164,8 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { func dispose() { channel?.setMethodCallHandler(nil) - methodCallDelegate?.webView = nil + channel = nil + methodCallDelegate?.dispose() methodCallDelegate = nil webView?.dispose() webView = nil diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index 225c3973..4e79f56b 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -1673,8 +1673,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi completionHandler(.useCredential, credential) break case 2: - if InAppWebView.credentialsProposed.count == 0 { - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + if InAppWebView.credentialsProposed.count == 0, let credentialStore = CredentialDatabase.credentialStore { + for (protectionSpace, credentials) in credentialStore.allCredentials { if protectionSpace.host == host && protectionSpace.realm == realm && protectionSpace.protocol == prot && protectionSpace.port == port { for credential in credentials { @@ -2900,6 +2900,12 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } public func dispose() { + channel = nil + removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) + removeObserver(self, forKeyPath: #keyPath(WKWebView.url)) + removeObserver(self, forKeyPath: #keyPath(WKWebView.title)) + scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset)) + scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale)) resumeTimers() stopLoading() disposeWebMessageChannels() @@ -2920,15 +2926,10 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { InAppWebView.windowWebViews.removeValue(forKey: wId) } configuration.userContentController.dispose(windowId: windowId) - removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) - removeObserver(self, forKeyPath: #keyPath(WKWebView.url)) - removeObserver(self, forKeyPath: #keyPath(WKWebView.title)) NotificationCenter.default.removeObserver(self) for imp in customIMPs { imp_removeBlock(imp) } - scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset)) - scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale)) longPressRecognizer.removeTarget(self, action: #selector(longPressGestureDetected)) longPressRecognizer.delegate = nil scrollView.removeGestureRecognizer(longPressRecognizer) @@ -2945,7 +2946,6 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { navigationDelegate = nil scrollView.delegate = nil isPausedTimersCompletionHandler = nil - channel = nil SharedLastTouchPointTimestamp.removeValue(forKey: self) callAsyncJavaScriptBelowIOS14Results.removeAll() super.removeFromSuperview() diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift index ca13aa8c..fe45528d 100644 --- a/ios/Classes/InAppWebViewMethodHandler.swift +++ b/ios/Classes/InAppWebViewMethodHandler.swift @@ -553,8 +553,12 @@ public class InAppWebViewMethodHandler: FlutterMethodCallDelegate { } } - deinit { - print("InAppWebViewMethodHandler - dealloc") + public func dispose() { webView = nil } + + deinit { + print("InAppWebViewMethodHandler - dealloc") + dispose() + } } diff --git a/ios/Classes/InAppWebViewStatic.swift b/ios/Classes/InAppWebViewStatic.swift index aa36a906..58e5f874 100755 --- a/ios/Classes/InAppWebViewStatic.swift +++ b/ios/Classes/InAppWebViewStatic.swift @@ -72,4 +72,12 @@ class InAppWebViewStatic: NSObject, FlutterPlugin { completionHandler(defaultUserAgent) } } + + public func dispose() { + InAppWebViewStatic.channel?.setMethodCallHandler(nil) + InAppWebViewStatic.channel = nil + InAppWebViewStatic.registrar = nil + InAppWebViewStatic.webViewForUserAgent = nil + InAppWebViewStatic.defaultUserAgent = nil + } } diff --git a/ios/Classes/MyCookieManager.swift b/ios/Classes/MyCookieManager.swift index 80fb2c9a..1f892d87 100755 --- a/ios/Classes/MyCookieManager.swift +++ b/ios/Classes/MyCookieManager.swift @@ -100,6 +100,11 @@ class MyCookieManager: NSObject, FlutterPlugin { isHttpOnly: Bool?, sameSite: String?, result: @escaping FlutterResult) { + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(false) + return + } + var properties: [HTTPCookiePropertyKey: Any] = [:] properties[.originURL] = url properties[.name] = name @@ -138,7 +143,7 @@ class MyCookieManager: NSObject, FlutterPlugin { let cookie = HTTPCookie(properties: properties)! - MyCookieManager.httpCookieStore!.setCookie(cookie, completionHandler: {() in + httpCookieStore.setCookie(cookie, completionHandler: {() in result(true) }) } @@ -146,8 +151,13 @@ class MyCookieManager: NSObject, FlutterPlugin { public static func getCookies(url: String, result: @escaping FlutterResult) { var cookieList: [[String: Any?]] = [] + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(cookieList) + return + } + if let urlHost = URL(string: url)?.host { - MyCookieManager.httpCookieStore!.getAllCookies { (cookies) in + httpCookieStore.getAllCookies { (cookies) in for cookie in cookies { if urlHost.hasSuffix(cookie.domain) || ".\(urlHost)".hasSuffix(cookie.domain) { var sameSite: String? = nil @@ -189,7 +199,12 @@ class MyCookieManager: NSObject, FlutterPlugin { public static func getAllCookies(result: @escaping FlutterResult) { var cookieList: [[String: Any?]] = [] - MyCookieManager.httpCookieStore!.getAllCookies { (cookies) in + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(cookieList) + return + } + + httpCookieStore.getAllCookies { (cookies) in for cookie in cookies { var sameSite: String? = nil if #available(iOS 13.0, *) { @@ -221,7 +236,12 @@ class MyCookieManager: NSObject, FlutterPlugin { } public static func deleteCookie(url: String, name: String, domain: String, path: String, result: @escaping FlutterResult) { - MyCookieManager.httpCookieStore!.getAllCookies { (cookies) in + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(false) + return + } + + httpCookieStore.getAllCookies { (cookies) in for cookie in cookies { var originURL = "" if cookie.properties![.originURL] is String { @@ -234,7 +254,7 @@ class MyCookieManager: NSObject, FlutterPlugin { continue } if (cookie.domain == domain || cookie.domain == ".\(domain)" || ".\(cookie.domain)" == domain) && cookie.name == name && cookie.path == path { - MyCookieManager.httpCookieStore!.delete(cookie, completionHandler: { + httpCookieStore.delete(cookie, completionHandler: { result(true) }) return @@ -245,7 +265,12 @@ class MyCookieManager: NSObject, FlutterPlugin { } public static func deleteCookies(url: String, domain: String, path: String, result: @escaping FlutterResult) { - MyCookieManager.httpCookieStore!.getAllCookies { (cookies) in + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(false) + return + } + + httpCookieStore.getAllCookies { (cookies) in for cookie in cookies { var originURL = "" if cookie.properties![.originURL] is String { @@ -258,7 +283,7 @@ class MyCookieManager: NSObject, FlutterPlugin { continue } if (cookie.domain == domain || cookie.domain == ".\(domain)" || ".\(cookie.domain)" == domain) && cookie.path == path { - MyCookieManager.httpCookieStore!.delete(cookie, completionHandler: nil) + httpCookieStore.delete(cookie, completionHandler: nil) } } result(true) @@ -272,4 +297,11 @@ class MyCookieManager: NSObject, FlutterPlugin { result(true) }) } + + public func dispose() { + MyCookieManager.channel?.setMethodCallHandler(nil) + MyCookieManager.channel = nil + MyCookieManager.registrar = nil + MyCookieManager.httpCookieStore = nil + } } diff --git a/ios/Classes/MyWebStorageManager.swift b/ios/Classes/MyWebStorageManager.swift index 8daaeb23..426f30e5 100755 --- a/ios/Classes/MyWebStorageManager.swift +++ b/ios/Classes/MyWebStorageManager.swift @@ -53,7 +53,13 @@ class MyWebStorageManager: NSObject, FlutterPlugin { public static func fetchDataRecords(dataTypes: Set, result: @escaping FlutterResult) { var recordList: [[String: Any?]] = [] - MyWebStorageManager.websiteDataStore!.fetchDataRecords(ofTypes: dataTypes) { (data) in + + guard let websiteDataStore = MyWebStorageManager.websiteDataStore else { + result(recordList) + return + } + + websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in for record in data { recordList.append([ "displayName": record.displayName, @@ -68,7 +74,13 @@ class MyWebStorageManager: NSObject, FlutterPlugin { public static func removeDataFor(dataTypes: Set, recordList: [[String: Any?]], result: @escaping FlutterResult) { var records: [WKWebsiteDataRecord] = [] - MyWebStorageManager.websiteDataStore!.fetchDataRecords(ofTypes: dataTypes) { (data) in + + guard let websiteDataStore = MyWebStorageManager.websiteDataStore else { + result(false) + return + } + + websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in for record in data { for r in recordList { let displayName = r["displayName"] as! String @@ -78,16 +90,28 @@ class MyWebStorageManager: NSObject, FlutterPlugin { } } } - MyWebStorageManager.websiteDataStore!.removeData(ofTypes: dataTypes, for: records) { + websiteDataStore.removeData(ofTypes: dataTypes, for: records) { result(true) } } } public static func removeDataModifiedSince(dataTypes: Set, timestamp: Int64, result: @escaping FlutterResult) { + guard let websiteDataStore = MyWebStorageManager.websiteDataStore else { + result(false) + return + } + let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp)) - MyWebStorageManager.websiteDataStore!.removeData(ofTypes: dataTypes, modifiedSince: date as Date) { + websiteDataStore.removeData(ofTypes: dataTypes, modifiedSince: date as Date) { result(true) } } + + public func dispose() { + MyWebStorageManager.channel?.setMethodCallHandler(nil) + MyWebStorageManager.channel = nil + MyWebStorageManager.registrar = nil + MyWebStorageManager.websiteDataStore = nil + } } diff --git a/ios/Classes/PlatformUtil.swift b/ios/Classes/PlatformUtil.swift index e84c96ce..0bf29b40 100644 --- a/ios/Classes/PlatformUtil.swift +++ b/ios/Classes/PlatformUtil.swift @@ -60,4 +60,10 @@ class PlatformUtil: NSObject, FlutterPlugin { formatter.timeZone = timezone return formatter.string(from: PlatformUtil.getDateFromMilliseconds(date: date)) } + + public func dispose() { + PlatformUtil.channel?.setMethodCallHandler(nil) + PlatformUtil.channel = nil + PlatformUtil.registrar = nil + } } diff --git a/ios/Classes/PullToRefresh/PullToRefreshControl.swift b/ios/Classes/PullToRefresh/PullToRefreshControl.swift index 8753ce62..481bf8d9 100644 --- a/ios/Classes/PullToRefresh/PullToRefreshControl.swift +++ b/ios/Classes/PullToRefresh/PullToRefreshControl.swift @@ -109,5 +109,6 @@ public class PullToRefreshControl : UIRefreshControl, FlutterPlugin { deinit { print("PullToRefreshControl - dealloc") + dispose() } } diff --git a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift index 137d0105..e351427e 100755 --- a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift +++ b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift @@ -91,4 +91,10 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { result(FlutterError.init(code: "ChromeSafariBrowserManager", message: "SafariViewController is not available!", details: nil)) } + + public func dispose() { + ChromeSafariBrowserManager.channel?.setMethodCallHandler(nil) + ChromeSafariBrowserManager.channel = nil + ChromeSafariBrowserManager.registrar = nil + } } diff --git a/ios/Classes/SafariViewController/SafariViewController.swift b/ios/Classes/SafariViewController/SafariViewController.swift index 0dbea527..83786fe5 100755 --- a/ios/Classes/SafariViewController/SafariViewController.swift +++ b/ios/Classes/SafariViewController/SafariViewController.swift @@ -22,6 +22,7 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa deinit { print("SafariViewController - dealloc") + dispose() } public func prepareMethodChannel() { @@ -123,20 +124,21 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa // } public func onChromeSafariBrowserOpened() { - channel!.invokeMethod("onChromeSafariBrowserOpened", arguments: []) + channel?.invokeMethod("onChromeSafariBrowserOpened", arguments: []) } public func onChromeSafariBrowserCompletedInitialLoad() { - channel!.invokeMethod("onChromeSafariBrowserCompletedInitialLoad", arguments: []) + channel?.invokeMethod("onChromeSafariBrowserCompletedInitialLoad", arguments: []) } public func onChromeSafariBrowserClosed() { - channel!.invokeMethod("onChromeSafariBrowserClosed", arguments: []) + channel?.invokeMethod("onChromeSafariBrowserClosed", arguments: []) } public func dispose() { + channel?.setMethodCallHandler(nil) + channel = nil delegate = nil - channel!.setMethodCallHandler(nil) } } @@ -180,7 +182,12 @@ class CustomUIActivity : UIActivity { } override func perform() { - let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_chromesafaribrowser_" + viewId, binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) + guard let registrar = SwiftFlutterPlugin.instance?.registrar else { + return + } + + let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_chromesafaribrowser_" + viewId, + binaryMessenger: registrar.messenger()) let arguments: [String: Any?] = [ "url": url.absoluteString, diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift index 98977ee1..75631fac 100755 --- a/ios/Classes/SwiftFlutterPlugin.swift +++ b/ios/Classes/SwiftFlutterPlugin.swift @@ -49,16 +49,39 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { headlessInAppWebViewManager = HeadlessInAppWebViewManager(registrar: registrar) chromeSafariBrowserManager = ChromeSafariBrowserManager(registrar: registrar) inAppWebViewStatic = InAppWebViewStatic(registrar: registrar) + credentialDatabase = CredentialDatabase(registrar: registrar) if #available(iOS 11.0, *) { myCookieManager = MyCookieManager(registrar: registrar) } if #available(iOS 9.0, *) { myWebStorageManager = MyWebStorageManager(registrar: registrar) } - credentialDatabase = CredentialDatabase(registrar: registrar) } public static func register(with registrar: FlutterPluginRegistrar) { SwiftFlutterPlugin.instance = SwiftFlutterPlugin(with: registrar) } + + public func detachFromEngine(for registrar: FlutterPluginRegistrar) { + platformUtil?.dispose() + platformUtil = nil + inAppBrowserManager?.dispose() + inAppBrowserManager = nil + headlessInAppWebViewManager?.dispose() + headlessInAppWebViewManager = nil + chromeSafariBrowserManager?.dispose() + chromeSafariBrowserManager = nil + inAppWebViewStatic?.dispose() + inAppWebViewStatic = nil + credentialDatabase?.dispose() + credentialDatabase = nil + if #available(iOS 11.0, *) { + (myCookieManager as! MyCookieManager?)?.dispose() + myCookieManager = nil + } + if #available(iOS 9.0, *) { + (myWebStorageManager as! MyWebStorageManager?)?.dispose() + myWebStorageManager = nil + } + } } diff --git a/ios/Classes/Types/WKUserContentController.swift b/ios/Classes/Types/WKUserContentController.swift index 10115da4..53c901a7 100644 --- a/ios/Classes/Types/WKUserContentController.swift +++ b/ios/Classes/Types/WKUserContentController.swift @@ -21,7 +21,7 @@ extension WKUserContentController { var contentWorlds: Set { get { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) - return WKUserContentController._contentWorlds[tmpAddress]! + return WKUserContentController._contentWorlds[tmpAddress] ?? [] } set(newValue) { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) @@ -33,7 +33,7 @@ extension WKUserContentController { var userOnlyScripts: [WKUserScriptInjectionTime:OrderedSet] { get { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) - return WKUserContentController._userOnlyScripts[tmpAddress]! + return WKUserContentController._userOnlyScripts[tmpAddress] ?? [:] } set(newValue) { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) @@ -45,7 +45,7 @@ extension WKUserContentController { var pluginScripts: [WKUserScriptInjectionTime:OrderedSet] { get { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) - return WKUserContentController._pluginScripts[tmpAddress]! + return WKUserContentController._pluginScripts[tmpAddress] ?? [:] } set(newValue) { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) diff --git a/ios/Classes/Types/WebMessage.swift b/ios/Classes/Types/WebMessage.swift index 169926b2..106232c3 100644 --- a/ios/Classes/Types/WebMessage.swift +++ b/ios/Classes/Types/WebMessage.swift @@ -23,5 +23,6 @@ public class WebMessage : NSObject { deinit { print("WebMessage - dealloc") + dispose() } } diff --git a/ios/Classes/Types/WebMessageChannel.swift b/ios/Classes/Types/WebMessageChannel.swift index de9e75aa..2a06b28e 100644 --- a/ios/Classes/Types/WebMessageChannel.swift +++ b/ios/Classes/Types/WebMessageChannel.swift @@ -126,6 +126,7 @@ public class WebMessageChannel : FlutterMethodCallDelegate { public func dispose() { channel?.setMethodCallHandler(nil) + channel = nil for port in ports { port.dispose() } @@ -140,11 +141,11 @@ public class WebMessageChannel : FlutterMethodCallDelegate { } })(); """) - channel = nil webView = nil } deinit { print("WebMessageChannel - dealloc") + dispose() } } diff --git a/ios/Classes/Types/WebMessageListener.swift b/ios/Classes/Types/WebMessageListener.swift index 0d85c331..21150c84 100644 --- a/ios/Classes/Types/WebMessageListener.swift +++ b/ios/Classes/Types/WebMessageListener.swift @@ -222,5 +222,6 @@ public class WebMessageListener : FlutterMethodCallDelegate { deinit { print("WebMessageListener - dealloc") + dispose() } } diff --git a/ios/Classes/Types/WebMessagePort.swift b/ios/Classes/Types/WebMessagePort.swift index dff16845..801ab68f 100644 --- a/ios/Classes/Types/WebMessagePort.swift +++ b/ios/Classes/Types/WebMessagePort.swift @@ -118,5 +118,6 @@ public class WebMessagePort : NSObject { deinit { print("WebMessagePort - dealloc") + dispose() } } diff --git a/ios/Classes/Util.swift b/ios/Classes/Util.swift index 121930ab..0bf74e84 100644 --- a/ios/Classes/Util.swift +++ b/ios/Classes/Util.swift @@ -12,7 +12,7 @@ var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:] public class Util { public static func getUrlAsset(assetFilePath: String) throws -> URL { - let key = SwiftFlutterPlugin.instance!.registrar!.lookupKey(forAsset: assetFilePath) + let key = SwiftFlutterPlugin.instance?.registrar?.lookupKey(forAsset: assetFilePath) guard let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } @@ -20,7 +20,7 @@ public class Util { } public static func getAbsPathAsset(assetFilePath: String) throws -> String { - let key = SwiftFlutterPlugin.instance!.registrar!.lookupKey(forAsset: assetFilePath) + let key = SwiftFlutterPlugin.instance?.registrar?.lookupKey(forAsset: assetFilePath) guard let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } diff --git a/pubspec.yaml b/pubspec.yaml index 359bb63c..51954c14 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. -version: 5.4.0+3 +version: 5.4.1 homepage: https://github.com/pichillilorenzo/flutter_inappwebview environment: From b881909957f771252ec2577c28bc00267e00e408 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 23 Apr 2022 14:00:47 +0200 Subject: [PATCH 4/7] updated context menu item and options to settings --- .../{IWebViewSettings.java => ISettings.java} | 4 +- .../ChromeCustomTabsSettings.java | 4 +- .../in_app_browser/InAppBrowserSettings.java | 4 +- ...uOptions.java => ContextMenuSettings.java} | 6 +- .../in_app_webview/InAppWebView.java | 13 +- .../in_app_webview/InAppWebViewSettings.java | 4 +- .../PullToRefreshSettings.java | 4 +- .../chrome_safari_browser_example.screen.dart | 3 +- .../lib/in_app_browser_example.screen.dart | 14 +- example/lib/in_app_webiew_example.screen.dart | 190 +++++++++--------- example/lib/main.dart | 3 +- ...ptions.swift => ContextMenuSettings.swift} | 2 +- ...IWebViewSettings.swift => ISettings.swift} | 4 +- .../InAppBrowser/InAppBrowserSettings.swift | 2 +- ios/Classes/InAppWebView/InAppWebView.swift | 17 +- .../InAppWebView/InAppWebViewSettings.swift | 2 +- .../PullToRefresh/PullToRefreshSettings.swift | 2 +- .../SafariBrowserSettings.swift | 2 +- .../chrome_safari_browser_settings.dart | 10 +- lib/src/context_menu.dart | 74 ++++++- .../in_app_browser_settings.dart | 6 +- .../apple/in_app_webview_controller.dart | 3 +- lib/src/in_app_webview/in_app_webview.dart | 3 +- .../in_app_webview_controller.dart | 31 +-- .../in_app_webview_settings.dart | 36 ++-- lib/src/in_app_webview/webview.dart | 3 +- lib/src/types.dart | 58 +++--- 27 files changed, 279 insertions(+), 225 deletions(-) rename android/src/main/java/com/pichillilorenzo/flutter_inappwebview/{IWebViewSettings.java => ISettings.java} (62%) rename android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/{ContextMenuOptions.java => ContextMenuSettings.java} (82%) rename ios/Classes/{ContextMenuOptions.swift => ContextMenuSettings.swift} (81%) rename ios/Classes/{IWebViewSettings.swift => ISettings.swift} (90%) diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/IWebViewSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ISettings.java similarity index 62% rename from android/src/main/java/com/pichillilorenzo/flutter_inappwebview/IWebViewSettings.java rename to android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ISettings.java index 3a2dc606..e6d326ab 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/IWebViewSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ISettings.java @@ -2,8 +2,8 @@ package com.pichillilorenzo.flutter_inappwebview; import java.util.Map; -public interface IWebViewSettings { - public IWebViewSettings parse(Map settings); +public interface ISettings { + public ISettings parse(Map settings); public Map toMap(); public Map getRealSettings(T obj); } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsSettings.java index 0d4c0845..f8af7c91 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsSettings.java @@ -7,14 +7,14 @@ import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.trusted.ScreenOrientation; import androidx.browser.trusted.TrustedWebActivityDisplayMode; -import com.pichillilorenzo.flutter_inappwebview.IWebViewSettings; +import com.pichillilorenzo.flutter_inappwebview.ISettings; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class ChromeCustomTabsSettings implements IWebViewSettings { +public class ChromeCustomTabsSettings implements ISettings { final static String LOG_TAG = "ChromeCustomTabsSettings"; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserSettings.java index 12c188f8..682f1820 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserSettings.java @@ -2,13 +2,13 @@ package com.pichillilorenzo.flutter_inappwebview.in_app_browser; import androidx.annotation.Nullable; -import com.pichillilorenzo.flutter_inappwebview.IWebViewSettings; +import com.pichillilorenzo.flutter_inappwebview.ISettings; import com.pichillilorenzo.flutter_inappwebview.R; import java.util.HashMap; import java.util.Map; -public class InAppBrowserSettings implements IWebViewSettings { +public class InAppBrowserSettings implements ISettings { public static final String LOG_TAG = "InAppBrowserSettings"; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/ContextMenuOptions.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/ContextMenuSettings.java similarity index 82% rename from android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/ContextMenuOptions.java rename to android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/ContextMenuSettings.java index 6e937450..cbbafe5d 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/ContextMenuOptions.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/ContextMenuSettings.java @@ -1,16 +1,16 @@ package com.pichillilorenzo.flutter_inappwebview.in_app_webview; -import com.pichillilorenzo.flutter_inappwebview.IWebViewSettings; +import com.pichillilorenzo.flutter_inappwebview.ISettings; import java.util.HashMap; import java.util.Map; -public class ContextMenuOptions implements IWebViewSettings { +public class ContextMenuSettings implements ISettings { public static final String LOG_TAG = "ContextMenuOptions"; public Boolean hideDefaultSystemContextMenuItems = false; - public ContextMenuOptions parse(Map options) { + public ContextMenuSettings parse(Map options) { for (Map.Entry pair : options.entrySet()) { String key = pair.getKey(); Object value = pair.getValue(); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java index 3d7a987f..f4eac58e 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java @@ -55,6 +55,7 @@ import androidx.webkit.WebViewFeature; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview.types.DownloadStartRequest; +import com.pichillilorenzo.flutter_inappwebview.types.HitTestResult; import com.pichillilorenzo.flutter_inappwebview.types.InAppWebViewInterface; import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappwebview.R; @@ -1367,17 +1368,17 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie LinearLayout menuItemListLayout = (LinearLayout) horizontalScrollView.getChildAt(0); List> customMenuItems = new ArrayList<>(); - ContextMenuOptions contextMenuOptions = new ContextMenuOptions(); + ContextMenuSettings contextMenuSettings = new ContextMenuSettings(); if (contextMenu != null) { customMenuItems = (List>) contextMenu.get("menuItems"); - Map contextMenuOptionsMap = (Map) contextMenu.get("options"); + Map contextMenuOptionsMap = (Map) contextMenu.get("settings"); if (contextMenuOptionsMap != null) { - contextMenuOptions.parse(contextMenuOptionsMap); + contextMenuSettings.parse(contextMenuOptionsMap); } } customMenuItems = customMenuItems == null ? new ArrayList>() : customMenuItems; - if (contextMenuOptions.hideDefaultSystemContextMenuItems == null || !contextMenuOptions.hideDefaultSystemContextMenuItems) { + if (contextMenuSettings.hideDefaultSystemContextMenuItems == null || !contextMenuSettings.hideDefaultSystemContextMenuItems) { for (int i = 0; i < actionMenu.size(); i++) { final MenuItem menuItem = actionMenu.getItem(i); final int itemId = menuItem.getItemId(); @@ -1393,6 +1394,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie callback.onActionItemClicked(actionMode, menuItem); Map obj = new HashMap<>(); + obj.put("id", itemId); obj.put("androidId", itemId); obj.put("iosId", null); obj.put("title", itemTitle); @@ -1406,7 +1408,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie } for (final Map menuItem : customMenuItems) { - final int itemId = (int) menuItem.get("androidId"); + final int itemId = (int) menuItem.get("id"); final String itemTitle = (String) menuItem.get("title"); TextView text = (TextView) LayoutInflater.from(this.getContext()) .inflate(R.layout.floating_action_mode_item, this, false); @@ -1417,6 +1419,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie hideContextMenu(); Map obj = new HashMap<>(); + obj.put("id", itemId); obj.put("androidId", itemId); obj.put("iosId", null); obj.put("title", itemTitle); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebViewSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebViewSettings.java index 2fb040be..3b3e0c07 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebViewSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebViewSettings.java @@ -6,7 +6,7 @@ import android.webkit.WebSettings; import androidx.annotation.Nullable; -import com.pichillilorenzo.flutter_inappwebview.IWebViewSettings; +import com.pichillilorenzo.flutter_inappwebview.ISettings; import com.pichillilorenzo.flutter_inappwebview.types.InAppWebViewInterface; import com.pichillilorenzo.flutter_inappwebview.types.PreferredContentModeOptionType; @@ -18,7 +18,7 @@ import java.util.Map; import static android.webkit.WebSettings.LayoutAlgorithm.NARROW_COLUMNS; import static android.webkit.WebSettings.LayoutAlgorithm.NORMAL; -public class InAppWebViewSettings implements IWebViewSettings { +public class InAppWebViewSettings implements ISettings { public static final String LOG_TAG = "InAppWebViewSettings"; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/pull_to_refresh/PullToRefreshSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/pull_to_refresh/PullToRefreshSettings.java index 4a13c943..479a9697 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/pull_to_refresh/PullToRefreshSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/pull_to_refresh/PullToRefreshSettings.java @@ -2,12 +2,12 @@ package com.pichillilorenzo.flutter_inappwebview.pull_to_refresh; import androidx.annotation.Nullable; -import com.pichillilorenzo.flutter_inappwebview.IWebViewSettings; +import com.pichillilorenzo.flutter_inappwebview.ISettings; import java.util.HashMap; import java.util.Map; -public class PullToRefreshSettings implements IWebViewSettings { +public class PullToRefreshSettings implements ISettings { public static final String LOG_TAG = "PullToRefreshSettings"; public Boolean enabled = true; diff --git a/example/lib/chrome_safari_browser_example.screen.dart b/example/lib/chrome_safari_browser_example.screen.dart index f1ab1ea5..b21f569a 100755 --- a/example/lib/chrome_safari_browser_example.screen.dart +++ b/example/lib/chrome_safari_browser_example.screen.dart @@ -71,8 +71,7 @@ class _ChromeSafariBrowserExampleScreenState keepAliveEnabled: true, dismissButtonStyle: DismissButtonStyle.CLOSE, presentationStyle: - ModalPresentationStyle.OVER_FULL_SCREEN - )); + ModalPresentationStyle.OVER_FULL_SCREEN)); }, child: Text("Open Chrome Safari Browser")), )); diff --git a/example/lib/in_app_browser_example.screen.dart b/example/lib/in_app_browser_example.screen.dart index c7a5e22b..2a28996b 100755 --- a/example/lib/in_app_browser_example.screen.dart +++ b/example/lib/in_app_browser_example.screen.dart @@ -121,14 +121,14 @@ class _InAppBrowserExampleScreenState extends State { ElevatedButton( onPressed: () async { await widget.browser.openUrlRequest( - urlRequest: - URLRequest(url: Uri.parse("https://flutter.dev")), - settings: InAppBrowserClassSettings( - webViewSettings: InAppWebViewSettings( - useShouldOverrideUrlLoading: true, - useOnLoadResource: true, - ), + urlRequest: + URLRequest(url: Uri.parse("https://flutter.dev")), + settings: InAppBrowserClassSettings( + webViewSettings: InAppWebViewSettings( + useShouldOverrideUrlLoading: true, + useOnLoadResource: true, ), + ), ); }, child: Text("Open In-App Browser")), diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index f6b6a4d6..c43c5caa 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -1,11 +1,7 @@ import 'dart:collection'; -// import 'dart:convert'; -import 'dart:io'; -// import 'dart:typed_data'; - +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -// import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'main.dart'; @@ -17,7 +13,6 @@ class InAppWebViewExampleScreen extends StatefulWidget { } class _InAppWebViewExampleScreenState extends State { - final GlobalKey webViewKey = GlobalKey(); InAppWebViewController? webViewController; @@ -25,8 +20,7 @@ class _InAppWebViewExampleScreenState extends State { useShouldOverrideUrlLoading: true, mediaPlaybackRequiresUserGesture: false, useHybridComposition: true, - allowsInlineMediaPlayback: true - ); + allowsInlineMediaPlayback: true); late PullToRefreshController pullToRefreshController; late ContextMenu contextMenu; @@ -41,8 +35,7 @@ class _InAppWebViewExampleScreenState extends State { contextMenu = ContextMenu( menuItems: [ ContextMenuItem( - androidId: 1, - iosId: "1", + id: 1, title: "Special", action: () async { print("Menu item Special clicked!"); @@ -50,7 +43,7 @@ class _InAppWebViewExampleScreenState extends State { await webViewController?.clearFocus(); }) ], - options: ContextMenuOptions(hideDefaultSystemContextMenuItems: false), + settings: ContextMenuSettings(hideDefaultSystemContextMenuItems: false), onCreateContextMenu: (hitTestResult) async { print("onCreateContextMenu"); print(hitTestResult.extra); @@ -60,9 +53,7 @@ class _InAppWebViewExampleScreenState extends State { print("onHideContextMenu"); }, onContextMenuActionItemClicked: (contextMenuItemClicked) async { - var id = (Platform.isAndroid) - ? contextMenuItemClicked.androidId - : contextMenuItemClicked.iosId; + var id = contextMenuItemClicked.id; print("onContextMenuActionItemClicked: " + id.toString() + " " + @@ -74,9 +65,9 @@ class _InAppWebViewExampleScreenState extends State { color: Colors.blue, ), onRefresh: () async { - if (Platform.isAndroid) { + if (defaultTargetPlatform == TargetPlatform.android) { webViewController?.reload(); - } else if (Platform.isIOS) { + } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { webViewController?.loadUrl( urlRequest: URLRequest(url: await webViewController?.getUrl())); } @@ -97,9 +88,7 @@ class _InAppWebViewExampleScreenState extends State { body: SafeArea( child: Column(children: [ TextField( - decoration: InputDecoration( - prefixIcon: Icon(Icons.search) - ), + decoration: InputDecoration(prefixIcon: Icon(Icons.search)), controller: urlController, keyboardType: TextInputType.url, onSubmitted: (value) { @@ -107,94 +96,95 @@ class _InAppWebViewExampleScreenState extends State { if (url.scheme.isEmpty) { url = Uri.parse("https://www.google.com/search?q=" + value); } - webViewController?.loadUrl( - urlRequest: URLRequest(url: url)); + webViewController?.loadUrl(urlRequest: URLRequest(url: url)); }, ), Expanded( - child: Stack( - children: [ - InAppWebView( - key: webViewKey, - // contextMenu: contextMenu, - initialUrlRequest: - URLRequest(url: Uri.parse("http://github.com/flutter/")), - // initialFile: "assets/index.html", - initialUserScripts: UnmodifiableListView([]), - initialSettings: settings, - pullToRefreshController: pullToRefreshController, - onWebViewCreated: (controller) { - webViewController = controller; - }, - onLoadStart: (controller, url) async { - setState(() { - this.url = url.toString(); - urlController.text = this.url; - }); - }, - onPermissionRequest: (controller, request) async { - return PermissionResponse( - resources: request.resources, - action: PermissionResponseAction.GRANT); - }, - shouldOverrideUrlLoading: (controller, navigationAction) async { - var uri = navigationAction.request.url!; + child: Stack( + children: [ + InAppWebView( + key: webViewKey, + // contextMenu: contextMenu, + initialUrlRequest: + URLRequest(url: Uri.parse("http://github.com/flutter/")), + // initialFile: "assets/index.html", + initialUserScripts: UnmodifiableListView([]), + initialSettings: settings, + // contextMenu: contextMenu, + pullToRefreshController: pullToRefreshController, + onWebViewCreated: (controller) { + webViewController = controller; + }, + onLoadStart: (controller, url) async { + setState(() { + this.url = url.toString(); + urlController.text = this.url; + }); + }, + onPermissionRequest: (controller, request) async { + return PermissionResponse( + resources: request.resources, + action: PermissionResponseAction.GRANT); + }, + shouldOverrideUrlLoading: + (controller, navigationAction) async { + var uri = navigationAction.request.url!; - if (![ - "http", - "https", - "file", - "chrome", - "data", - "javascript", - "about" - ].contains(uri.scheme)) { - if (await canLaunch(url)) { - // Launch the App - await launch( - url, - ); - // and cancel the request - return NavigationActionPolicy.CANCEL; - } + if (![ + "http", + "https", + "file", + "chrome", + "data", + "javascript", + "about" + ].contains(uri.scheme)) { + if (await canLaunch(url)) { + // Launch the App + await launch( + url, + ); + // and cancel the request + return NavigationActionPolicy.CANCEL; } + } - return NavigationActionPolicy.ALLOW; - }, - onLoadStop: (controller, url) async { + return NavigationActionPolicy.ALLOW; + }, + onLoadStop: (controller, url) async { + pullToRefreshController.endRefreshing(); + setState(() { + this.url = url.toString(); + urlController.text = this.url; + }); + }, + onLoadError: (controller, url, code, message) { + pullToRefreshController.endRefreshing(); + }, + onProgressChanged: (controller, progress) { + if (progress == 100) { pullToRefreshController.endRefreshing(); - setState(() { - this.url = url.toString(); - urlController.text = this.url; - }); - }, - onLoadError: (controller, url, code, message) { - pullToRefreshController.endRefreshing(); - }, - onProgressChanged: (controller, progress) { - if (progress == 100) { - pullToRefreshController.endRefreshing(); - } - setState(() { - this.progress = progress / 100; - urlController.text = this.url; - }); - }, - onUpdateVisitedHistory: (controller, url, androidIsReload) { - setState(() { - this.url = url.toString(); - urlController.text = this.url; - }); - }, - onConsoleMessage: (controller, consoleMessage) { - print(consoleMessage); - }, - ), - progress < 1.0 - ? LinearProgressIndicator(value: progress) - : Container(), - ], - ), + } + setState(() { + this.progress = progress / 100; + urlController.text = this.url; + }); + }, + onUpdateVisitedHistory: (controller, url, androidIsReload) { + setState(() { + this.url = url.toString(); + urlController.text = this.url; + }); + }, + onConsoleMessage: (controller, consoleMessage) { + print(consoleMessage); + }, + ), + progress < 1.0 + ? LinearProgressIndicator(value: progress) + : Container(), + ], + ), ), ButtonBar( alignment: MainAxisAlignment.center, diff --git a/example/lib/main.dart b/example/lib/main.dart index 2a782777..4c65d583 100755 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -31,8 +31,7 @@ Future main() async { ServiceWorkerController serviceWorkerController = ServiceWorkerController.instance(); - await serviceWorkerController - .setServiceWorkerClient(ServiceWorkerClient( + await serviceWorkerController.setServiceWorkerClient(ServiceWorkerClient( shouldInterceptRequest: (request) async { print(request); return null; diff --git a/ios/Classes/ContextMenuOptions.swift b/ios/Classes/ContextMenuSettings.swift similarity index 81% rename from ios/Classes/ContextMenuOptions.swift rename to ios/Classes/ContextMenuSettings.swift index f113670f..e0b5594d 100644 --- a/ios/Classes/ContextMenuOptions.swift +++ b/ios/Classes/ContextMenuSettings.swift @@ -7,7 +7,7 @@ import Foundation -class ContextMenuOptions: IWebViewSettings { +class ContextMenuSettings: ISettings { var hideDefaultSystemContextMenuItems = false; diff --git a/ios/Classes/IWebViewSettings.swift b/ios/Classes/ISettings.swift similarity index 90% rename from ios/Classes/IWebViewSettings.swift rename to ios/Classes/ISettings.swift index 4a3c247b..7e12fdd5 100755 --- a/ios/Classes/IWebViewSettings.swift +++ b/ios/Classes/ISettings.swift @@ -8,13 +8,13 @@ import Foundation @objcMembers -public class IWebViewSettings: NSObject { +public class ISettings: NSObject { override init(){ super.init() } - func parse(settings: [String: Any?]) -> IWebViewSettings { + func parse(settings: [String: Any?]) -> ISettings { for (key, value) in settings { if !(value is NSNull), value != nil, self.responds(to: Selector(key)) { self.setValue(value, forKey: key) diff --git a/ios/Classes/InAppBrowser/InAppBrowserSettings.swift b/ios/Classes/InAppBrowser/InAppBrowserSettings.swift index 5dd8d07c..ea265651 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserSettings.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserSettings.swift @@ -8,7 +8,7 @@ import Foundation @objcMembers -public class InAppBrowserSettings: IWebViewSettings { +public class InAppBrowserSettings: ISettings { var hidden = false var hideToolbarTop = true diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index 856270c3..c0cbc1cf 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -203,13 +203,15 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if let menu = self.contextMenu { if let menuItems = menu["menuItems"] as? [[String : Any]] { for menuItem in menuItems { - let id = menuItem["iosId"] as! String + let id = menuItem["id"]! let title = menuItem["title"] as! String - let targetMethodName = "onContextMenuActionItemClicked-" + String(self.hash) + "-" + id + let targetMethodName = "onContextMenuActionItemClicked-" + String(self.hash) + "-" + + (id is Int64 ? String(id as! Int64) : id as! String) if !self.responds(to: Selector(targetMethodName)) { let customAction: () -> Void = { let arguments: [String: Any?] = [ - "iosId": id, + "id": id, + "iosId": id is Int64 ? String(id as! Int64) : id as! String, "androidId": nil, "title": title ] @@ -244,10 +246,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } if let menu = contextMenu { - let contextMenuOptions = ContextMenuOptions() - if let contextMenuOptionsMap = menu["options"] as? [String: Any?] { - let _ = contextMenuOptions.parse(settings: contextMenuOptionsMap) - if !action.description.starts(with: "onContextMenuActionItemClicked-") && contextMenuOptions.hideDefaultSystemContextMenuItems { + let contextMenuSettings = ContextMenuSettings() + if let contextMenuSettingsMap = menu["settings"] as? [String: Any?] { + let _ = contextMenuSettings.parse(settings: contextMenuSettingsMap) + if !action.description.starts(with: "onContextMenuActionItemClicked-") && contextMenuSettings.hideDefaultSystemContextMenuItems { return false } } @@ -256,6 +258,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if contextMenuIsShowing, !action.description.starts(with: "onContextMenuActionItemClicked-") { let id = action.description.compactMap({ $0.asciiValue?.description }).joined() let arguments: [String: Any?] = [ + "id": id, "iosId": id, "androidId": nil, "title": action.description diff --git a/ios/Classes/InAppWebView/InAppWebViewSettings.swift b/ios/Classes/InAppWebView/InAppWebViewSettings.swift index c51b1c99..d3597bb5 100755 --- a/ios/Classes/InAppWebView/InAppWebViewSettings.swift +++ b/ios/Classes/InAppWebView/InAppWebViewSettings.swift @@ -9,7 +9,7 @@ import Foundation import WebKit @objcMembers -public class InAppWebViewSettings: IWebViewSettings { +public class InAppWebViewSettings: ISettings { var useShouldOverrideUrlLoading = false var useOnLoadResource = false diff --git a/ios/Classes/PullToRefresh/PullToRefreshSettings.swift b/ios/Classes/PullToRefresh/PullToRefreshSettings.swift index 01125db7..f241c59d 100644 --- a/ios/Classes/PullToRefresh/PullToRefreshSettings.swift +++ b/ios/Classes/PullToRefresh/PullToRefreshSettings.swift @@ -7,7 +7,7 @@ import Foundation -public class PullToRefreshSettings : IWebViewSettings { +public class PullToRefreshSettings : ISettings { var enabled = true var color: String? diff --git a/ios/Classes/SafariViewController/SafariBrowserSettings.swift b/ios/Classes/SafariViewController/SafariBrowserSettings.swift index af67ee76..dd9d1f8a 100755 --- a/ios/Classes/SafariViewController/SafariBrowserSettings.swift +++ b/ios/Classes/SafariViewController/SafariBrowserSettings.swift @@ -9,7 +9,7 @@ import Foundation @available(iOS 9.0, *) @objcMembers -public class SafariBrowserSettings: IWebViewSettings { +public class SafariBrowserSettings: ISettings { var entersReaderIfAvailable = false var barCollapsingEnabled = false diff --git a/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart b/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart index a9ccc7ff..7187c56b 100755 --- a/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart +++ b/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart @@ -242,8 +242,8 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { settings.additionalTrustedOrigins = map["additionalTrustedOrigins"]; switch (map["displayMode"]["type"]) { case "IMMERSIVE_MODE": - settings.displayMode = - TrustedWebActivityImmersiveDisplayMode.fromMap(map["displayMode"]); + settings.displayMode = TrustedWebActivityImmersiveDisplayMode.fromMap( + map["displayMode"]); break; case "DEFAULT_MODE": default: @@ -256,15 +256,15 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { settings.entersReaderIfAvailable = map["entersReaderIfAvailable"]; settings.barCollapsingEnabled = map["barCollapsingEnabled"]; settings.dismissButtonStyle = - DismissButtonStyle.fromValue(map["dismissButtonStyle"])!; + DismissButtonStyle.fromValue(map["dismissButtonStyle"])!; settings.preferredBarTintColor = UtilColor.fromHex(map["preferredBarTintColor"]); settings.preferredControlTintColor = UtilColor.fromHex(map["preferredControlTintColor"]); settings.presentationStyle = - ModalPresentationStyle.fromValue(map["presentationStyle"])!; + ModalPresentationStyle.fromValue(map["presentationStyle"])!; settings.transitionStyle = - ModalTransitionStyle.fromValue(map["transitionStyle"])!; + ModalTransitionStyle.fromValue(map["transitionStyle"])!; } return settings; } diff --git a/lib/src/context_menu.dart b/lib/src/context_menu.dart index 1ff12542..038ddcfe 100644 --- a/lib/src/context_menu.dart +++ b/lib/src/context_menu.dart @@ -1,3 +1,5 @@ +import 'package:flutter/foundation.dart'; + import 'in_app_webview/webview.dart'; import 'types.dart'; @@ -20,9 +22,13 @@ class ContextMenu { final void Function(ContextMenuItem contextMenuItemClicked)? onContextMenuActionItemClicked; - ///Context menu options. + ///Use [settings] instead + @Deprecated("Use settings instead") final ContextMenuOptions? options; + ///Context menu settings. + final ContextMenuSettings? settings; + ///List of the custom [ContextMenuItem]. final List menuItems; @@ -31,12 +37,14 @@ class ContextMenu { this.onCreateContextMenu, this.onHideContextMenu, this.options, + this.settings, this.onContextMenuActionItemClicked}); Map toMap() { return { "menuItems": menuItems.map((menuItem) => menuItem.toMap()).toList(), - "options": options?.toMap() + // ignore: deprecated_member_use_from_same_package + "settings": settings?.toMap() ?? options?.toMap() }; } @@ -52,12 +60,19 @@ class ContextMenu { ///Class that represent an item of the [ContextMenu]. class ContextMenuItem { - ///Android menu item ID. + ///Use [id] instead. + @Deprecated("Use id instead") int? androidId; - ///iOS menu item ID. + ///Use [id] instead. + @Deprecated("Use id instead") String? iosId; + ///Menu item ID. It cannot be `null` and it can be a [String] or an [int]. + /// + ///**NOTE for Android**: it must be an [int] value. + dynamic id; + ///Menu item title. String title; @@ -65,10 +80,31 @@ class ContextMenuItem { Function()? action; ContextMenuItem( - {this.androidId, this.iosId, required this.title, this.action}); + {this.id, + this.androidId, + this.iosId, + required this.title, + this.action}) { + if (defaultTargetPlatform == TargetPlatform.android) { + // ignore: deprecated_member_use_from_same_package + this.id = this.id ?? this.androidId; + assert(this.id is int); + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + // ignore: deprecated_member_use_from_same_package + this.id = this.id ?? this.iosId; + } + assert(this.id != null && (this.id is int || this.id is String)); + } Map toMap() { - return {"androidId": androidId, "iosId": iosId, "title": title}; + return { + "id": id, + // ignore: deprecated_member_use_from_same_package + "androidId": androidId, + // ignore: deprecated_member_use_from_same_package + "iosId": iosId, + "title": title + }; } Map toJson() { @@ -81,7 +117,31 @@ class ContextMenuItem { } } -///Class that represents available options used by [ContextMenu]. +///Class that represents available settings used by [ContextMenu]. +class ContextMenuSettings { + ///Whether all the default system context menu items should be hidden or not. The default value is `false`. + bool hideDefaultSystemContextMenuItems; + + ContextMenuSettings({this.hideDefaultSystemContextMenuItems = false}); + + Map toMap() { + return { + "hideDefaultSystemContextMenuItems": hideDefaultSystemContextMenuItems + }; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} + +///Use [ContextMenuSettings] instead. +@Deprecated("Use ContextMenuSettings instead") class ContextMenuOptions { ///Whether all the default system context menu items should be hidden or not. The default value is `false`. bool hideDefaultSystemContextMenuItems; diff --git a/lib/src/in_app_browser/in_app_browser_settings.dart b/lib/src/in_app_browser/in_app_browser_settings.dart index 023829bd..05aaab6f 100755 --- a/lib/src/in_app_browser/in_app_browser_settings.dart +++ b/lib/src/in_app_browser/in_app_browser_settings.dart @@ -287,7 +287,7 @@ class InAppBrowserSettings settings.closeOnCannotGoBack = map["closeOnCannotGoBack"]; settings.allowGoBackWithBackButton = map["allowGoBackWithBackButton"]; settings.shouldCloseOnBackButtonPressed = - map["shouldCloseOnBackButtonPressed"]; + map["shouldCloseOnBackButtonPressed"]; } if (Platform.isIOS || Platform.isMacOS) { settings.toolbarTopTranslucent = map["toolbarTopTranslucent"]; @@ -302,9 +302,9 @@ class InAppBrowserSettings settings.closeButtonCaption = map["closeButtonCaption"]; settings.closeButtonColor = UtilColor.fromHex(map["closeButtonColor"]); settings.presentationStyle = - ModalPresentationStyle.fromValue(map["presentationStyle"])!; + ModalPresentationStyle.fromValue(map["presentationStyle"])!; settings.transitionStyle = - ModalTransitionStyle.fromValue(map["transitionStyle"])!; + ModalTransitionStyle.fromValue(map["transitionStyle"])!; } return settings; } diff --git a/lib/src/in_app_webview/apple/in_app_webview_controller.dart b/lib/src/in_app_webview/apple/in_app_webview_controller.dart index ad43faa1..9c3de23d 100644 --- a/lib/src/in_app_webview/apple/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/apple/in_app_webview_controller.dart @@ -107,7 +107,8 @@ abstract class AppleInAppWebViewControllerMixin { ///- iOS ([Official API - WKWebView.requestMediaPlaybackState](https://developer.apple.com/documentation/webkit/wkwebview/3752241-requestmediaplaybackstate)). Future requestMediaPlaybackState() async { Map args = {}; - return MediaPlaybackState.fromValue(await _channel.invokeMethod('requestMediaPlaybackState', args)); + return MediaPlaybackState.fromValue( + await _channel.invokeMethod('requestMediaPlaybackState', args)); } } diff --git a/lib/src/in_app_webview/in_app_webview.dart b/lib/src/in_app_webview/in_app_webview.dart index 5a48f574..89dc27cc 100755 --- a/lib/src/in_app_webview/in_app_webview.dart +++ b/lib/src/in_app_webview/in_app_webview.dart @@ -469,8 +469,7 @@ class InAppWebView extends StatefulWidget implements WebView { NavigationResponse navigationResponse)? onNavigationResponse; @override - final Future Function( - InAppWebViewController controller, + final Future Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequest; @override diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart index b68b9445..191a98f0 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -732,9 +732,9 @@ class InAppWebViewController List resources = call.arguments["resources"].cast(); Map arguments = - call.arguments.cast(); + call.arguments.cast(); PermissionRequest permissionRequest = - PermissionRequest.fromMap(arguments)!; + PermissionRequest.fromMap(arguments)!; if (_webview != null) { if (_webview!.onPermissionRequest != null) @@ -748,11 +748,12 @@ class InAppWebViewController ?.toMap(); } } else { - return (await _inAppBrowser! - .onPermissionRequest(permissionRequest))?.toMap() ?? - (await _inAppBrowser! + return (await _inAppBrowser!.onPermissionRequest(permissionRequest)) + ?.toMap() ?? + (await _inAppBrowser! // ignore: deprecated_member_use_from_same_package - .androidOnPermissionRequest(origin, resources))?.toMap(); + .androidOnPermissionRequest(origin, resources)) + ?.toMap(); } } break; @@ -945,16 +946,18 @@ class InAppWebViewController if (contextMenu != null) { int? androidId = call.arguments["androidId"]; String? iosId = call.arguments["iosId"]; + dynamic id = call.arguments["id"]; String title = call.arguments["title"]; ContextMenuItem menuItemClicked = ContextMenuItem( - androidId: androidId, iosId: iosId, title: title, action: null); + id: id, + androidId: androidId, + iosId: iosId, + title: title, + action: null); for (var menuItem in contextMenu.menuItems) { - if ((defaultTargetPlatform == TargetPlatform.android && - menuItem.androidId == androidId) || - (defaultTargetPlatform == TargetPlatform.iOS && - menuItem.iosId == iosId)) { + if (menuItem.id == id) { menuItemClicked = menuItem; if (menuItem.action != null) { menuItem.action!(); @@ -1847,7 +1850,8 @@ class InAppWebViewController ///Use [setSettings] instead. @Deprecated('Use setSettings instead') Future setOptions({required InAppWebViewGroupOptions options}) async { - InAppWebViewSettings settings = InAppWebViewSettings.fromMap(options.toMap()); + InAppWebViewSettings settings = + InAppWebViewSettings.fromMap(options.toMap()); await setSettings(settings: settings); } @@ -2306,7 +2310,8 @@ class InAppWebViewController try { Map args = {}; - themeColor = UtilColor.fromStringRepresentation(await _channel.invokeMethod('getMetaThemeColor', args)); + themeColor = UtilColor.fromStringRepresentation( + await _channel.invokeMethod('getMetaThemeColor', args)); } catch (e) { // not implemented } diff --git a/lib/src/in_app_webview/in_app_webview_settings.dart b/lib/src/in_app_webview/in_app_webview_settings.dart index d5becaca..7b23b00d 100755 --- a/lib/src/in_app_webview/in_app_webview_settings.dart +++ b/lib/src/in_app_webview/in_app_webview_settings.dart @@ -1357,7 +1357,7 @@ class InAppWebViewSettings settings.hardwareAcceleration = map["hardwareAcceleration"]; settings.supportMultipleWindows = map["supportMultipleWindows"]; settings.regexToCancelSubFramesLoading = - map["regexToCancelSubFramesLoading"]; + map["regexToCancelSubFramesLoading"]; settings.useHybridComposition = map["useHybridComposition"]; settings.useShouldInterceptRequest = map["useShouldInterceptRequest"]; settings.useOnRenderProcessGone = map["useOnRenderProcessGone"]; @@ -1367,7 +1367,7 @@ class InAppWebViewSettings settings.verticalScrollbarPosition = VerticalScrollbarPosition.fromValue(map["verticalScrollbarPosition"]); settings.scrollBarDefaultDelayBeforeFade = - map["scrollBarDefaultDelayBeforeFade"]; + map["scrollBarDefaultDelayBeforeFade"]; settings.scrollbarFadingEnabled = map["scrollbarFadingEnabled"]; settings.scrollBarFadeDuration = map["scrollBarFadeDuration"]; settings.rendererPriorityPolicy = RendererPriorityPolicy.fromMap( @@ -1386,28 +1386,28 @@ class InAppWebViewSettings settings.disallowOverScroll = map["disallowOverScroll"]; settings.enableViewportScale = map["enableViewportScale"]; settings.suppressesIncrementalRendering = - map["suppressesIncrementalRendering"]; + map["suppressesIncrementalRendering"]; settings.allowsAirPlayForMediaPlayback = - map["allowsAirPlayForMediaPlayback"]; + map["allowsAirPlayForMediaPlayback"]; settings.allowsBackForwardNavigationGestures = - map["allowsBackForwardNavigationGestures"]; + map["allowsBackForwardNavigationGestures"]; settings.allowsLinkPreview = map["allowsLinkPreview"]; settings.ignoresViewportScaleLimits = map["ignoresViewportScaleLimits"]; settings.allowsInlineMediaPlayback = map["allowsInlineMediaPlayback"]; settings.allowsPictureInPictureMediaPlayback = - map["allowsPictureInPictureMediaPlayback"]; + map["allowsPictureInPictureMediaPlayback"]; settings.isFraudulentWebsiteWarningEnabled = - map["isFraudulentWebsiteWarningEnabled"]; + map["isFraudulentWebsiteWarningEnabled"]; settings.selectionGranularity = - SelectionGranularity.fromValue(map["selectionGranularity"])!; + SelectionGranularity.fromValue(map["selectionGranularity"])!; settings.dataDetectorTypes = dataDetectorTypes; settings.sharedCookiesEnabled = map["sharedCookiesEnabled"]; settings.automaticallyAdjustsScrollIndicatorInsets = - map["automaticallyAdjustsScrollIndicatorInsets"]; + map["automaticallyAdjustsScrollIndicatorInsets"]; settings.accessibilityIgnoresInvertColors = - map["accessibilityIgnoresInvertColors"]; + map["accessibilityIgnoresInvertColors"]; settings.decelerationRate = - ScrollViewDecelerationRate.fromValue(map["decelerationRate"])!; + ScrollViewDecelerationRate.fromValue(map["decelerationRate"])!; settings.alwaysBounceVertical = map["alwaysBounceVertical"]; settings.alwaysBounceHorizontal = map["alwaysBounceHorizontal"]; settings.scrollsToTop = map["scrollsToTop"]; @@ -1415,24 +1415,26 @@ class InAppWebViewSettings settings.maximumZoomScale = map["maximumZoomScale"]; settings.minimumZoomScale = map["minimumZoomScale"]; settings.contentInsetAdjustmentBehavior = - ScrollViewContentInsetAdjustmentBehavior.fromValue( - map["contentInsetAdjustmentBehavior"])!; + ScrollViewContentInsetAdjustmentBehavior.fromValue( + map["contentInsetAdjustmentBehavior"])!; settings.isDirectionalLockEnabled = map["isDirectionalLockEnabled"]; settings.mediaType = map["mediaType"]; settings.pageZoom = map["pageZoom"]; settings.limitsNavigationsToAppBoundDomains = - map["limitsNavigationsToAppBoundDomains"]; + map["limitsNavigationsToAppBoundDomains"]; settings.useOnNavigationResponse = map["useOnNavigationResponse"]; settings.applePayAPIEnabled = map["applePayAPIEnabled"]; settings.allowingReadAccessTo = map["allowingReadAccessTo"] != null ? Uri.parse(map["allowingReadAccessTo"]) : null; settings.disableLongPressContextMenuOnLinks = - map["disableLongPressContextMenuOnLinks"]; + map["disableLongPressContextMenuOnLinks"]; settings.disableInputAccessoryView = map["disableInputAccessoryView"]; - settings.underPageBackgroundColor = UtilColor.fromHex(map["underPageBackgroundColor"]); + settings.underPageBackgroundColor = + UtilColor.fromHex(map["underPageBackgroundColor"]); settings.isTextInteractionEnabled = map["isTextInteractionEnabled"]; - settings.isSiteSpecificQuirksModeEnabled = map["isSiteSpecificQuirksModeEnabled"]; + settings.isSiteSpecificQuirksModeEnabled = + map["isSiteSpecificQuirksModeEnabled"]; settings.upgradeKnownHostsToHTTPS = map["upgradeKnownHostsToHTTPS"]; } return settings; diff --git a/lib/src/in_app_webview/webview.dart b/lib/src/in_app_webview/webview.dart index af7036fa..326144c0 100644 --- a/lib/src/in_app_webview/webview.dart +++ b/lib/src/in_app_webview/webview.dart @@ -510,8 +510,7 @@ abstract class WebView { /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebChromeClient.onPermissionRequest](https://developer.android.com/reference/android/webkit/WebChromeClient#onPermissionRequest(android.webkit.PermissionRequest))) - final Future Function( - InAppWebViewController controller, + final Future Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequest; ///Use [onGeolocationPermissionsShowPrompt] instead. diff --git a/lib/src/types.dart b/lib/src/types.dart index e9fa0c63..6d323c96 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -4769,7 +4769,8 @@ class PermissionResourceType { PermissionResourceType.RESOURCE_VIDEO_CAPTURE, ].toSet(); - static final Set _appleValues = [ + static final Set _appleValues = + [ PermissionResourceType.CAMERA, PermissionResourceType.MICROPHONE, PermissionResourceType.CAMERA_AND_MICROPHONE, @@ -4779,7 +4780,8 @@ class PermissionResourceType { static PermissionResourceType? fromValue(dynamic? value) { if (value != null) { try { - Set valueList = [].toSet(); + Set valueList = + [].toSet(); if (Platform.isAndroid) { valueList = PermissionResourceType._androidValues; } else if (Platform.isIOS || Platform.isMacOS) { @@ -4815,52 +4817,51 @@ class PermissionResourceType { ///Resource belongs to audio capture device, like microphone. /// ///**NOTE**: available only on Android. - static const RESOURCE_AUDIO_CAPTURE = const PermissionResourceType._internal('android.webkit.resource.AUDIO_CAPTURE'); + static const RESOURCE_AUDIO_CAPTURE = const PermissionResourceType._internal( + 'android.webkit.resource.AUDIO_CAPTURE'); - ///Resource will allow sysex messages to be sent to or received from MIDI devices. - ///These messages are privileged operations, e.g. modifying sound libraries and sampling data, or even updating the MIDI device's firmware. + ///Resource will allow sysex messages to be sent to or received from MIDI devices. + ///These messages are privileged operations, e.g. modifying sound libraries and sampling data, or even updating the MIDI device's firmware. ///Permission may be requested for this resource in API levels 21 and above, if the Android device has been updated to WebView 45 or above. /// ///**NOTE**: available only on Android. - static const RESOURCE_MIDI_SYSEX = - const PermissionResourceType._internal('android.webkit.resource.MIDI_SYSEX'); + static const RESOURCE_MIDI_SYSEX = const PermissionResourceType._internal( + 'android.webkit.resource.MIDI_SYSEX'); ///Resource belongs to protected media identifier. After the user grants this resource, the origin can use EME APIs to generate the license requests. /// ///**NOTE**: available only on Android. static const RESOURCE_PROTECTED_MEDIA_ID = - const PermissionResourceType._internal('android.webkit.resource.PROTECTED_MEDIA_ID'); - + const PermissionResourceType._internal( + 'android.webkit.resource.PROTECTED_MEDIA_ID'); ///Resource belongs to video capture device, like camera. /// ///**NOTE**: available only on Android. - static const RESOURCE_VIDEO_CAPTURE = - const PermissionResourceType._internal('android.webkit.resource.VIDEO_CAPTURE'); + static const RESOURCE_VIDEO_CAPTURE = const PermissionResourceType._internal( + 'android.webkit.resource.VIDEO_CAPTURE'); ///A media device that can capture video. /// ///**NOTE**: available only on iOS. - static const CAMERA = - const PermissionResourceType._internal(0); + static const CAMERA = const PermissionResourceType._internal(0); ///A media device that can capture audio. /// ///**NOTE**: available only on iOS. - static const MICROPHONE = - const PermissionResourceType._internal(1); + static const MICROPHONE = const PermissionResourceType._internal(1); ///A media device or devices that can capture audio and video. /// ///**NOTE**: available only on iOS. static const CAMERA_AND_MICROPHONE = - const PermissionResourceType._internal(2); + const PermissionResourceType._internal(2); ///Resource belongs to the device’s orientation and motion. /// ///**NOTE**: available only on iOS. static const DEVICE_ORIENTATION_AND_MOTION = - const PermissionResourceType._internal('deviceOrientationAndMotion'); + const PermissionResourceType._internal('deviceOrientationAndMotion'); bool operator ==(value) => value == _value; @@ -4880,9 +4881,7 @@ class PermissionRequest { FrameInfo? frame; PermissionRequest( - {required this.origin, - this.resources = const [], - this.frame}); + {required this.origin, this.resources = const [], this.frame}); static PermissionRequest? fromMap(Map? map) { if (map == null) { @@ -4891,8 +4890,7 @@ class PermissionRequest { List resources = []; if (map["resources"] != null) { - (map["resources"].cast() as List) - .forEach((element) { + (map["resources"].cast() as List).forEach((element) { var resource = PermissionResourceType.fromValue(element); if (resource != null) { resources.add(resource); @@ -4908,7 +4906,7 @@ class PermissionRequest { Map toMap() { return { - "origin": origin.toString(), + "origin": origin.toString(), "resources": resources.map((e) => e.toValue()).toList(), "frame": frame?.toMap() }; @@ -4933,8 +4931,7 @@ class PermissionResponse { PermissionResponseAction? action; PermissionResponse( - {this.resources = const [], - this.action = PermissionResponseAction.DENY}); + {this.resources = const [], this.action = PermissionResponseAction.DENY}); Map toMap() { return { @@ -10688,19 +10685,16 @@ class MediaPlaybackState { static const NONE = const MediaPlaybackState._internal(0); ///The media is playing. - static const PLAYING = - const MediaPlaybackState._internal(1); + static const PLAYING = const MediaPlaybackState._internal(1); ///The media playback is paused. - static const PAUSED = - const MediaPlaybackState._internal(2); + static const PAUSED = const MediaPlaybackState._internal(2); ///The media is not playing, and cannot be resumed until the user revokes the suspension. - static const SUSPENDED = - const MediaPlaybackState._internal(3); + static const SUSPENDED = const MediaPlaybackState._internal(3); bool operator ==(value) => value == _value; @override int get hashCode => _value.hashCode; -} \ No newline at end of file +} From f511e4d1f5441f277231c9e05a70cc917755fb64 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 23 Apr 2022 15:43:57 +0200 Subject: [PATCH 5/7] Fixed Android default context menu over custom context menu on API Level 31+ --- CHANGELOG.md | 4 ++++ .../in_app_webview/InAppWebView.java | 19 ++++++++++++------- pubspec.yaml | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8d43c1..aff19c95 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.4.1+1 + +- Fixed Android default context menu over custom context menu on API Level 31+ + ## 5.4.1 - Managed iOS native `detachFromEngine` flutter plugin event and updated `dispose` methods diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java index 4a4c0567..3ae40f08 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java @@ -41,7 +41,6 @@ import android.webkit.URLUtil; import android.webkit.ValueCallback; import android.webkit.WebBackForwardList; import android.webkit.WebHistoryItem; -import android.webkit.WebMessage; import android.webkit.WebSettings; import android.webkit.WebStorage; import android.widget.HorizontalScrollView; @@ -55,8 +54,6 @@ import androidx.webkit.WebViewCompat; import androidx.webkit.WebViewFeature; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; -import com.pichillilorenzo.flutter_inappwebview.types.DownloadStartRequest; -import com.pichillilorenzo.flutter_inappwebview.types.InAppWebViewInterface; import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.Util; @@ -77,6 +74,8 @@ import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.PrintJS; import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.PromisePolyfillJS; import com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshLayout; import com.pichillilorenzo.flutter_inappwebview.types.ContentWorld; +import com.pichillilorenzo.flutter_inappwebview.types.DownloadStartRequest; +import com.pichillilorenzo.flutter_inappwebview.types.InAppWebViewInterface; import com.pichillilorenzo.flutter_inappwebview.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview.types.PreferredContentModeOptionType; import com.pichillilorenzo.flutter_inappwebview.types.URLRequest; @@ -1357,8 +1356,16 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie } Menu actionMenu = actionMode.getMenu(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + actionMode.hide(3000); + } + List defaultMenuItems = new ArrayList<>(); + for (int i = 0; i < actionMenu.size(); i++) { + defaultMenuItems.add(actionMenu.getItem(i)); + } + actionMenu.clear(); + actionMode.finish(); if (options.disableContextMenu) { - actionMenu.clear(); return actionMode; } @@ -1379,8 +1386,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie customMenuItems = customMenuItems == null ? new ArrayList>() : customMenuItems; if (contextMenuOptions.hideDefaultSystemContextMenuItems == null || !contextMenuOptions.hideDefaultSystemContextMenuItems) { - for (int i = 0; i < actionMenu.size(); i++) { - final MenuItem menuItem = actionMenu.getItem(i); + for (final MenuItem menuItem : defaultMenuItems) { final int itemId = menuItem.getItemId(); final String itemTitle = menuItem.getTitle().toString(); @@ -1457,7 +1463,6 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie checkContextMenuShouldBeClosedTask.run(); } } - actionMenu.clear(); return actionMode; } diff --git a/pubspec.yaml b/pubspec.yaml index 51954c14..6e180b82 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. -version: 5.4.1 +version: 5.4.1+1 homepage: https://github.com/pichillilorenzo/flutter_inappwebview environment: From 8315b811a8adfa2883527b1eb0ba894618771dfe Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 23 Apr 2022 17:39:39 +0200 Subject: [PATCH 6/7] implemented WKDownloadDelegate iOS protocol for iOS 14.5 --- .../types/NavigationAction.java | 1 + ios/Classes/InAppWebView/InAppWebView.swift | 119 ++++++++++++------ ios/Classes/Types/WKNavigationAction.swift | 7 +- lib/src/types.dart | 32 ++++- 4 files changed, 121 insertions(+), 38 deletions(-) diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/NavigationAction.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/NavigationAction.java index 578fa471..c6e6fef8 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/NavigationAction.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/NavigationAction.java @@ -25,6 +25,7 @@ public class NavigationAction { navigationActionMap.put("navigationType", null); navigationActionMap.put("sourceFrame", null); navigationActionMap.put("targetFrame", null); + navigationActionMap.put("shouldPerformDownload", null); return navigationActionMap; } diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index c0cbc1cf..eccf2de3 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -9,7 +9,10 @@ import Flutter import Foundation import WebKit -public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate, PullToRefreshDelegate { +public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, + WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate, + WKDownloadDelegate, + PullToRefreshDelegate { var windowId: Int64? var windowCreated = false @@ -1517,11 +1520,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi var action = response["action"] as? Int action = action != nil ? action : 0; switch action { - case 1: - decisionHandler(.grant) - break - default: - decisionHandler(.deny) + case 1: + decisionHandler(.grant) + break + case 2: + decisionHandler(.prompt) + break + default: + decisionHandler(.deny) } return; } @@ -1555,11 +1561,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi var action = response["action"] as? Int action = action != nil ? action : 0; switch action { - case 1: - decisionHandler(.grant) - break - default: - decisionHandler(.deny) + case 1: + decisionHandler(.grant) + break + case 2: + decisionHandler(.prompt) + break + default: + decisionHandler(.deny) } return; } @@ -1578,6 +1587,39 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi }) } + @available(iOS 14.5, *) + public func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) { + if let url = response.url, let useOnDownloadStart = settings?.useOnDownloadStart, useOnDownloadStart { + let downloadStartRequest = DownloadStartRequest(url: url.absoluteString, + userAgent: nil, + contentDisposition: nil, + mimeType: response.mimeType, + contentLength: response.expectedContentLength, + suggestedFilename: suggestedFilename, + textEncodingName: response.textEncodingName) + onDownloadStartRequest(request: downloadStartRequest) + } + download.delegate = nil + // cancel the download + completionHandler(nil) + } + + @available(iOS 14.5, *) + public func webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) { + let response = navigationResponse.response + if let url = response.url, let useOnDownloadStart = settings?.useOnDownloadStart, useOnDownloadStart { + let downloadStartRequest = DownloadStartRequest(url: url.absoluteString, + userAgent: nil, + contentDisposition: nil, + mimeType: response.mimeType, + contentLength: response.expectedContentLength, + suggestedFilename: response.suggestedFilename, + textEncodingName: response.textEncodingName) + onDownloadStartRequest(request: downloadStartRequest) + } + download.delegate = nil + } + public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { @@ -1587,6 +1629,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi return } + if #available(iOS 14.5, *), navigationAction.request.url!.absoluteString.hasSuffix(".dat") { + decisionHandler(.download) + return + } + if navigationAction.request.url != nil { if let useShouldOverrideUrlLoading = settings?.useShouldOverrideUrlLoading, useShouldOverrideUrlLoading { @@ -1605,7 +1652,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if let r = result { response = r as! [String: Any] let action = response["action"] as? Int - let navigationActionPolicy = WKNavigationActionPolicy.init(rawValue: action ?? WKNavigationActionPolicy.cancel.rawValue) ?? + let navigationActionPolicy = WKNavigationActionPolicy + .init(rawValue: action ?? WKNavigationActionPolicy.cancel.rawValue) ?? WKNavigationActionPolicy.cancel decisionHandler(navigationActionPolicy) return; @@ -1647,15 +1695,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi var response: [String: Any] if let r = result { response = r as! [String: Any] - var action = response["action"] as? Int - action = action != nil ? action : 0; - switch action { - case 1: - decisionHandler(.allow) - break - default: - decisionHandler(.cancel) - } + let action = response["action"] as? Int + let navigationActionPolicy = WKNavigationResponsePolicy + .init(rawValue: action ?? WKNavigationResponsePolicy.cancel.rawValue) ?? + WKNavigationResponsePolicy.cancel + decisionHandler(navigationActionPolicy) return; } decisionHandler(.allow) @@ -1664,21 +1708,26 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } if let useOnDownloadStart = settings?.useOnDownloadStart, useOnDownloadStart { - let mimeType = navigationResponse.response.mimeType - if let url = navigationResponse.response.url, navigationResponse.isForMainFrame { - if url.scheme != "file", mimeType != nil, !mimeType!.starts(with: "text/") { - let downloadStartRequest = DownloadStartRequest(url: url.absoluteString, - userAgent: nil, - contentDisposition: nil, - mimeType: mimeType, - contentLength: navigationResponse.response.expectedContentLength, - suggestedFilename: navigationResponse.response.suggestedFilename, - textEncodingName: navigationResponse.response.textEncodingName) - onDownloadStartRequest(request: downloadStartRequest) - if useOnNavigationResponse == nil || !useOnNavigationResponse! { - decisionHandler(.cancel) + if #available(iOS 14.5, *), !navigationResponse.canShowMIMEType { + decisionHandler(.download) + return + } else { + let mimeType = navigationResponse.response.mimeType + if let url = navigationResponse.response.url, navigationResponse.isForMainFrame { + if url.scheme != "file", mimeType != nil, !mimeType!.starts(with: "text/") { + let downloadStartRequest = DownloadStartRequest(url: url.absoluteString, + userAgent: nil, + contentDisposition: nil, + mimeType: mimeType, + contentLength: navigationResponse.response.expectedContentLength, + suggestedFilename: navigationResponse.response.suggestedFilename, + textEncodingName: navigationResponse.response.textEncodingName) + onDownloadStartRequest(request: downloadStartRequest) + if useOnNavigationResponse == nil || !useOnNavigationResponse! { + decisionHandler(.cancel) + } + return } - return } } } diff --git a/ios/Classes/Types/WKNavigationAction.swift b/ios/Classes/Types/WKNavigationAction.swift index b475151f..1fdc5c17 100644 --- a/ios/Classes/Types/WKNavigationAction.swift +++ b/ios/Classes/Types/WKNavigationAction.swift @@ -10,6 +10,10 @@ import WebKit extension WKNavigationAction { public func toMap () -> [String:Any?] { + var shouldPerformDownload: Bool? = nil + if #available(iOS 14.5, *) { + shouldPerformDownload = self.shouldPerformDownload + } return [ "request": request.toMap(), "isForMainFrame": targetFrame?.isMainFrame ?? false, @@ -17,7 +21,8 @@ extension WKNavigationAction { "isRedirect": nil, "navigationType": navigationType.rawValue, "sourceFrame": sourceFrame.toMap(), - "targetFrame": targetFrame?.toMap() + "targetFrame": targetFrame?.toMap(), + "shouldPerformDownload": shouldPerformDownload ] } } diff --git a/lib/src/types.dart b/lib/src/types.dart index 6d323c96..94921b9c 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -4717,6 +4717,11 @@ class PermissionResponseAction { ///Grants origin the permission to access the given resources. static const GRANT = const PermissionResponseAction._internal(1); + ///Prompt the user for permission for the requested resource. + /// + ///**NOTE**: available only on iOS 15.0+. It will fallback to [DENY]. + static const PROMPT = const PermissionResponseAction._internal(2); + bool operator ==(value) => value == _value; @override @@ -4875,6 +4880,9 @@ class PermissionRequest { Uri origin; ///List of resources the web content wants to access. + /// + ///**NOTE for iOS**: this list will have only 1 element and will be used by the [PermissionResponse.action] + ///as the resource to consider when applying the corresponding action. List resources; ///The frame that initiates the request in the web view. @@ -4925,6 +4933,8 @@ class PermissionRequest { ///Class that represents the response used by the [WebView.onPermissionRequest] event. class PermissionResponse { ///Resources granted to be accessed by origin. + /// + ///**NOTE for iOS**: not used. The [action] taken is based on the [PermissionRequest.resources]. List resources; ///Indicate the [PermissionResponseAction] to take in response of a permission request. @@ -4993,6 +5003,11 @@ class NavigationActionPolicy { ///Allow the navigation to continue. static const ALLOW = const NavigationActionPolicy._internal(1); + ///Turn the navigation into a download. + /// + ///**NOTE**: available only on iOS 14.5+. It will fallback to [CANCEL]. + static const DOWNLOAD = const NavigationActionPolicy._internal(2); + bool operator ==(value) => value == _value; @override @@ -5761,6 +5776,11 @@ class NavigationAction { ///**NOTE**: available only on iOS. FrameInfo? targetFrame; + ///A value indicating whether the web content used a download attribute to indicate that this should be downloaded. + /// + ///**NOTE**: available only on iOS. + bool? shouldPerformDownload; + NavigationAction( {required this.request, required this.isForMainFrame, @@ -5773,7 +5793,8 @@ class NavigationAction { @Deprecated("Use sourceFrame instead") this.iosSourceFrame, this.sourceFrame, @Deprecated("Use targetFrame instead") this.iosTargetFrame, - this.targetFrame}) { + this.targetFrame, + this.shouldPerformDownload}) { // ignore: deprecated_member_use_from_same_package this.hasGesture = this.hasGesture ?? this.androidHasGesture; // ignore: deprecated_member_use_from_same_package @@ -5818,7 +5839,8 @@ class NavigationAction { // ignore: deprecated_member_use_from_same_package IOSWKFrameInfo.fromMap(map["targetFrame"]?.cast()), targetFrame: - FrameInfo.fromMap(map["targetFrame"]?.cast())); + FrameInfo.fromMap(map["targetFrame"]?.cast()), + shouldPerformDownload: map["shouldPerformDownload"]); } Map toMap() { @@ -5847,6 +5869,7 @@ class NavigationAction { "iosTargetFrame": targetFrame?.toMap() ?? iosTargetFrame?.toMap(), // ignore: deprecated_member_use_from_same_package "targetFrame": targetFrame?.toMap() ?? iosTargetFrame?.toMap(), + "shouldPerformDownload": shouldPerformDownload }; } @@ -8953,6 +8976,11 @@ class NavigationResponseAction { ///Allow the navigation to continue. static const ALLOW = const NavigationResponseAction._internal(1); + ///Turn the navigation into a download. + /// + ///**NOTE**: available only on iOS 14.5+. It will fallback to [CANCEL]. + static const DOWNLOAD = const NavigationResponseAction._internal(2); + bool operator ==(value) => value == _value; @override From 2a9391df412bf8c66d12b113f160c08391a0996a Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 23 Apr 2022 17:40:43 +0200 Subject: [PATCH 7/7] removed unused code --- ios/Classes/InAppWebView/InAppWebView.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index eccf2de3..27d80a2d 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -1629,11 +1629,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, return } - if #available(iOS 14.5, *), navigationAction.request.url!.absoluteString.hasSuffix(".dat") { - decisionHandler(.download) - return - } - if navigationAction.request.url != nil { if let useShouldOverrideUrlLoading = settings?.useShouldOverrideUrlLoading, useShouldOverrideUrlLoading {