diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml new file mode 100644 index 00000000..3b0b4626 --- /dev/null +++ b/.idea/libraries/Dart_Packages.xml @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index 65bb3679..31799730 100755 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -1,8 +1,6 @@ - - - + diff --git a/CHANGELOG.md b/CHANGELOG.md index dc96fff3..d19becd8 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.3.2 + +- Added `onLoad` and `onError` callbacks in `ScriptHtmlTagAttributes` class used by `InAppWebViewController.injectJavascriptFileFromUrl` +- `InAppWebViewController.injectJavascriptFileFromAsset` returns a `Future` type now + ## 5.3.1+1 - Removed duplicate lib exports 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 d8d827d0..dcde9159 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 @@ -1019,7 +1019,18 @@ final public class InAppWebView extends InputAwareWebView { } String idAttr = (String) scriptHtmlTagAttributes.get("id"); if (idAttr != null) { - scriptAttributes += " script.id = '" + idAttr.replaceAll("'", "\\\\'") + "'; "; + String scriptIdEscaped = idAttr.replaceAll("'", "\\\\'"); + scriptAttributes += " script.id = '" + scriptIdEscaped + "'; "; + scriptAttributes += " script.onload = function() {" + + " if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + " != null) {" + + " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onInjectedScriptLoaded', '" + scriptIdEscaped + "');" + + " }" + + "};"; + scriptAttributes += " script.onerror = function() {" + + " if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + " != null) {" + + " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onInjectedScriptError', '" + scriptIdEscaped + "');" + + " }" + + "};"; } Boolean asyncAttr = (Boolean) scriptHtmlTagAttributes.get("async"); if (asyncAttr != null && asyncAttr) { diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index 0f853c3c..8f69f5ce 100644 --- a/example/.flutter-plugins-dependencies +++ b/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-04-06 11:48:11.518272","version":"2.1.0-10.0.pre"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/fvm/versions/2.1.0-10.0.pre/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/fvm/versions/2.1.0-10.0.pre/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-04-07 18:05:10.935076","version":"2.1.0-10.0.pre"} \ No newline at end of file diff --git a/example/integration_test/webview_flutter_test.dart b/example/integration_test/webview_flutter_test.dart index 73f03bc3..ac02f42f 100644 --- a/example/integration_test/webview_flutter_test.dart +++ b/example/integration_test/webview_flutter_test.dart @@ -4142,6 +4142,8 @@ setTimeout(function() { testWidgets('injectJavascriptFileFromUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); + final Completer jQueryLoaded = Completer(); + final Completer jQueryLoadError = Completer(); await tester.pumpWidget( Directionality( @@ -4163,10 +4165,26 @@ setTimeout(function() { await controllerCompleter.future; 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(); + },)); + await jQueryLoadError.future; + expect( + await controller.evaluateJavascript( + source: "document.body.querySelector('#jquery-error') == null;"), + false); + expect( + await controller.evaluateJavascript(source: "window.jQuery == null;"), + true); + await controller.injectJavascriptFileFromUrl( urlFile: Uri.parse('https://code.jquery.com/jquery-3.3.1.min.js'), - scriptHtmlTagAttributes: ScriptHtmlTagAttributes(id: 'jquery')); - await Future.delayed(Duration(seconds: 4)); + scriptHtmlTagAttributes: ScriptHtmlTagAttributes(id: 'jquery', onLoad: () { + jQueryLoaded.complete(); + },)); + await jQueryLoaded.future; expect( await controller.evaluateJavascript( source: "document.body.querySelector('#jquery') == null;"), diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh index ff2151af..ff713474 100755 --- a/example/ios/Flutter/flutter_export_environment.sh +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -1,13 +1,13 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter" +export "FLUTTER_ROOT=/Users/lorenzopichilli/fvm/versions/2.1.0-10.0.pre" export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example" -export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart" +export "FLUTTER_TARGET=integration_test/webview_flutter_test.dart" export "FLUTTER_BUILD_DIR=build" export "SYMROOT=${SOURCE_ROOT}/../build/ios" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" -export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==" +export "DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" diff --git a/flutter_inappwebview.iml b/flutter_inappwebview.iml index 0adae5aa..4cb39159 100755 --- a/flutter_inappwebview.iml +++ b/flutter_inappwebview.iml @@ -80,5 +80,6 @@ + \ No newline at end of file diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index 12c8c40c..f49fcef7 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -1336,7 +1336,22 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi scriptAttributes += " script.type = '\(typeAttr.replacingOccurrences(of: "\'", with: "\\'"))'; " } if let idAttr = scriptHtmlTagAttributes["id"] as? String { - scriptAttributes += " script.id = '\(idAttr.replacingOccurrences(of: "\'", with: "\\'"))'; " + let scriptIdEscaped = idAttr.replacingOccurrences(of: "\'", with: "\\'") + scriptAttributes += " script.id = '\(scriptIdEscaped)'; " + scriptAttributes += """ + script.onload = function() { + if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { + window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptLoaded', '\(scriptIdEscaped)'); + } + }; + """ + scriptAttributes += """ + script.onerror = function() { + if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { + window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptError', '\(scriptIdEscaped)'); + } + }; + """ } if let asyncAttr = scriptHtmlTagAttributes["async"] as? Bool, asyncAttr { scriptAttributes += " script.async = true; " 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 4db0084e..d34072bb 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -55,6 +55,7 @@ class InAppWebViewController { HashMap(); List _userScripts = []; Set _webMessageListenerObjNames = Set(); + Map _injectedScriptsFromURL = {}; // ignore: unused_field dynamic _id; @@ -102,6 +103,7 @@ class InAppWebViewController { Future handleMethod(MethodCall call) async { switch (call.method) { case "onLoadStart": + _injectedScriptsFromURL.clear(); if ((_webview != null && _webview!.onLoadStart != null) || _inAppBrowser != null) { String? url = call.arguments["url"]; @@ -865,6 +867,22 @@ class InAppWebViewController { _webview!.onWindowBlur!(this); else if (_inAppBrowser != null) _inAppBrowser!.onWindowBlur(); return null; + case "onInjectedScriptLoaded": + String id = args[0]; + var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; + if ((_webview != null || _inAppBrowser != null) && + onLoadCallback != null) { + onLoadCallback(); + } + return null; + case "onInjectedScriptError": + String id = args[0]; + var onErrorCallback = _injectedScriptsFromURL[id]?.onError; + if ((_webview != null || _inAppBrowser != null) && + onErrorCallback != null) { + onErrorCallback(); + } + return null; } if (javaScriptHandlersMap.containsKey(handlerName)) { @@ -1372,6 +1390,10 @@ class InAppWebViewController { {required Uri urlFile, ScriptHtmlTagAttributes? scriptHtmlTagAttributes}) async { assert(urlFile.toString().isNotEmpty); + var id = scriptHtmlTagAttributes?.id; + if (scriptHtmlTagAttributes != null && id != null) { + _injectedScriptsFromURL[id] = scriptHtmlTagAttributes; + } Map args = {}; args.putIfAbsent('urlFile', () => urlFile.toString()); args.putIfAbsent( @@ -1385,10 +1407,10 @@ class InAppWebViewController { ///because, in these events, the [WebView] is not ready to handle it yet. ///Instead, you should call this method, for example, inside the [WebView.onLoadStop] event or in any other events ///where you know the page is ready "enough". - Future injectJavascriptFileFromAsset( + Future injectJavascriptFileFromAsset( {required String assetFilePath}) async { String source = await rootBundle.loadString(assetFilePath); - await evaluateJavascript(source: source); + return await evaluateJavascript(source: source); } ///Injects CSS into the WebView. @@ -2044,13 +2066,13 @@ class InAppWebViewController { ///[functionBody] is the JavaScript string to use as the function body. ///This method treats the string as an anonymous JavaScript function body and calls it with the named arguments in the arguments parameter. /// - ///[arguments] is a dictionary of the arguments to pass to the function call. - ///Each key in the dictionary corresponds to the name of an argument in the [functionBody] string, + ///[arguments] is a `Map` of the arguments to pass to the function call. + ///Each key in the `Map` corresponds to the name of an argument in the [functionBody] string, ///and the value of that key is the value to use during the evaluation of the code. ///Supported value types can be found in the official Flutter docs: ///[Platform channel data types support and codecs](https://flutter.dev/docs/development/platform-integration/platform-channels#codec), ///except for [Uint8List], [Int32List], [Int64List], and [Float64List] that should be converted into a [List]. - ///All items in an array or dictionary must also be one of the supported types. + ///All items in a `List` or `Map` must also be one of the supported types. /// ///[contentWorld], on iOS, it represents the namespace in which to evaluate the JavaScript [source] code. ///Instead, on Android, it will run the [source] code into an iframe. @@ -2059,6 +2081,11 @@ class InAppWebViewController { ///For more information about content worlds, see [ContentWorld]. ///Available on iOS 14.0+. /// + ///**NOTE**: This method shouldn't be called in the [WebView.onWebViewCreated] or [WebView.onLoadStart] events, + ///because, in these events, the [WebView] is not ready to handle it yet. + ///Instead, you should call this method, for example, inside the [WebView.onLoadStop] event or in any other events + ///where you know the page is ready "enough". + /// ///**NOTE for iOS**: available only on iOS 10.3+. /// ///**NOTE for Android**: available only on Android 21+. diff --git a/lib/src/types.dart b/lib/src/types.dart index 14a05274..eb9d1d68 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -6037,6 +6037,16 @@ class ScriptHtmlTagAttributes { ///Indicates which referrer to send when fetching the script, or resources fetched by the script. ReferrerPolicy? referrerPolicy; + ///Represents a callback function that will be called as soon as the script has been loaded successfully. + /// + ///**NOTE**: This callback requires the [id] property to be set. + Function()? onLoad; + + ///Represents a callback function that will be called if an error occurred while trying to load the script. + /// + ///**NOTE**: This callback requires the [id] property to be set. + Function()? onError; + ScriptHtmlTagAttributes( {this.type = "text/javascript", this.id, @@ -6046,7 +6056,14 @@ class ScriptHtmlTagAttributes { this.integrity, this.noModule, this.nonce, - this.referrerPolicy}); + this.referrerPolicy, + this.onLoad, + this.onError}) { + if (this.onLoad != null || this.onError != null) { + assert(this.id != null, + 'onLoad and onError callbacks require the id property to be set.'); + } + } Map toMap() { return { diff --git a/pubspec.yaml b/pubspec.yaml index 6059031d..f810981b 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.3.1+1 +version: 5.3.2 homepage: https://github.com/pichillilorenzo/flutter_inappwebview environment: