diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml new file mode 100644 index 000000000..3b0b4626d --- /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 65bb3679c..317997300 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 dc96fff30..d19becd87 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 d8d827d0a..dcde91596 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 @@ public void injectJavascriptFileFromUrl(String urlFile, @Nullable Map(); 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 ff2151af4..ff7134740 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 0adae5aa8..4cb391599 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 12c8c40c8..f49fcef7d 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 4db0084ef..d34072bbb 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 14a052743..eb9d1d68f 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 6059031d8..f810981bd 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: