diff --git a/.idea/workspace.xml b/.idea/workspace.xml index f17d566a..9be54987 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -15,16 +15,39 @@ - - - - - - + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + @@ -44,11 +67,11 @@ - + - - + + @@ -56,84 +79,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -146,13 +91,6 @@ - weBVIEWCONTROL - getFa - supportZoom - layou - HttpAuthCredential - layoutA - AndroidInAppWebViewForceDark AndroidInAppWebViewModeMenuItem AndroidInAppWebViewMixedContentMode IosInAppWebViewSelectionGranularity @@ -176,6 +114,13 @@ evaluateJ AjaxRequest _onPlatformViewCreated + callback + evaluate + onLoadReso + reload + t-rex + ajaxReq + window. activity.getPreferences(0) @@ -191,8 +136,8 @@ $PROJECT_DIR$/example/android - $PROJECT_DIR$/lib/src $PROJECT_DIR$/lib + $PROJECT_DIR$/lib/src @@ -201,57 +146,57 @@ @@ -266,6 +211,27 @@ + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + +

+ placeholder 100x50 +

+ + + + + + + + + + \ No newline at end of file diff --git a/example/test_assets/page-1.html b/example/test_assets/page-1.html new file mode 100644 index 00000000..9babbb03 --- /dev/null +++ b/example/test_assets/page-1.html @@ -0,0 +1,40 @@ + + + + + + + Flutter InAppBrowser + + + + + +
+
+
+

Flutter InAppBrowser

+ +
+
+ +
+

Page 1

+

Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.

+

+ Learn more +

+
+ + +
+ + \ No newline at end of file diff --git a/example/test_assets/page-2.html b/example/test_assets/page-2.html new file mode 100644 index 00000000..537af895 --- /dev/null +++ b/example/test_assets/page-2.html @@ -0,0 +1,40 @@ + + + + + + + Flutter InAppBrowser + + + + + +
+
+
+

Flutter InAppBrowser

+ +
+
+ +
+

Page 2

+

Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.

+

+ Learn more +

+
+ + +
+ + \ No newline at end of file diff --git a/example/test_driver/app.dart b/example/test_driver/app.dart new file mode 100644 index 00000000..e9f5f5d9 --- /dev/null +++ b/example/test_driver/app.dart @@ -0,0 +1,12 @@ +import 'package:flutter_driver/driver_extension.dart'; +import 'app_test.dart'; +import 'main_test.dart' as app; + +void main() { + // This line enables the extension. + enableFlutterDriverExtension(); + + // Call the `main()` function of the app, or call `runApp` with + // any widget you are interested in testing. + app.main(); +} \ No newline at end of file diff --git a/example/test_driver/app_test.dart b/example/test_driver/app_test.dart new file mode 100644 index 00000000..a704088b --- /dev/null +++ b/example/test_driver/app_test.dart @@ -0,0 +1,102 @@ +// Imports the Flutter Driver API. +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart'; + +void main() { + + group('Flutter InAppBrowser', () { + FlutterDriver driver; + + // Connect to the Flutter driver before running any tests. + setUpAll(() async { + driver = await FlutterDriver.connect(); + }); + + // Close the connection to the driver after the tests have completed. + tearDownAll(() async { + if (driver != null) { + driver.close(); + } + }); + + myTest({@required String name, @required Function callback, Timeout timeout}) { + timeout = (timeout == null) ? new Timeout(new Duration(minutes: 5)) : timeout; + test(name, () async { + await Future.delayed(const Duration(milliseconds: 2000)); + callback(); + }, timeout: timeout); + } + + // + // IMPORTANT NOTE!!! + // These tests need to follow the same order of "var routes" in "buildRoutes()" function + // defined in main_test.dart + // + + myTest(name: 'InAppWebViewInitialUrlTest', callback: () async { + final appBarTitle = find.byValueKey('AppBarTitle'); + + while((await driver.getText(appBarTitle)) == "InAppWebViewInitialUrlTest") { + await Future.delayed(const Duration(milliseconds: 1000)); + } + + String url = await driver.getText(appBarTitle); + expect(url, "https://flutter.dev/"); + }); + + myTest(name: 'InAppWebViewInitialFileTest', callback: () async { + final appBarTitle = find.byValueKey('AppBarTitle'); + + while((await driver.getText(appBarTitle)) == "InAppWebViewInitialFileTest") { + await Future.delayed(const Duration(milliseconds: 1000)); + } + + String title = await driver.getText(appBarTitle); + expect(title, "true"); + }); + + myTest(name: 'InAppWebViewOnLoadResourceTest', callback: () async { + List resourceList = [ + "https://getbootstrap.com/docs/4.3/dist/css/bootstrap.min.css", + "https://code.jquery.com/jquery-3.3.1.min.js", + "https://via.placeholder.com/100x50" + ]; + final appBarTitle = find.byValueKey('AppBarTitle'); + + while((await driver.getText(appBarTitle)) == "InAppWebViewOnLoadResourceTest") { + await Future.delayed(const Duration(milliseconds: 1000)); + } + + String title = await driver.getText(appBarTitle); + print(title); + for (String resource in resourceList) { + expect(true, title.contains(resource)); + } + }); + + myTest(name: 'InAppWebViewJavaScriptHandlerTest', callback: () async { + final appBarTitle = find.byValueKey('AppBarTitle'); + + while((await driver.getText(appBarTitle)) == "InAppWebViewJavaScriptHandlerTest") { + await Future.delayed(const Duration(milliseconds: 1000)); + } + + String title = await driver.getText(appBarTitle); + expect(true, !title.contains("false")); + }); + + myTest(name: 'InAppWebViewAjaxTest', callback: () async { + final appBarTitle = find.byValueKey('AppBarTitle'); + + while((await driver.getText(appBarTitle)) == "InAppWebViewAjaxTest") { + await Future.delayed(const Duration(milliseconds: 1000)); + } + + String title = await driver.getText(appBarTitle); + expect(title, "Lorenzo Pichilli Lorenzo Pichilli"); + }); + }); +} \ No newline at end of file diff --git a/example/test_driver/custom_widget_test.dart b/example/test_driver/custom_widget_test.dart new file mode 100644 index 00000000..806539aa --- /dev/null +++ b/example/test_driver/custom_widget_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter/widgets.dart'; + +import 'package:flutter_inappbrowser/flutter_inappbrowser.dart'; + +class WidgetTest extends StatefulWidget { + final WidgetTestState state = WidgetTestState(); + + WidgetTest({Key key}): super(key: key); + + @override + WidgetTestState createState() { + return state; + } +} + +class WidgetTestState extends State { + InAppWebViewController webView; + String appBarTitle; + + @override + Widget build(BuildContext context) { + return null; + } +} \ No newline at end of file diff --git a/example/test_driver/in_app_webview_ajax_test.dart b/example/test_driver/in_app_webview_ajax_test.dart new file mode 100644 index 00000000..aa013860 --- /dev/null +++ b/example/test_driver/in_app_webview_ajax_test.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_inappbrowser/flutter_inappbrowser.dart'; + +import 'main_test.dart'; +import 'util_test.dart'; +import 'custom_widget_test.dart'; + +class InAppWebViewAjaxTest extends WidgetTest { + final InAppWebViewAjaxTestState state = InAppWebViewAjaxTestState(); + + @override + InAppWebViewAjaxTestState createState() => state; +} + +class InAppWebViewAjaxTestState extends WidgetTestState { + String appBarTitle = "InAppWebViewAjaxTest"; + int totTests = 2; + int testsDone = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: myAppBar(state: this, title: appBarTitle), + body: Container( + child: Column(children: [ + Expanded( + child: Container( + child: InAppWebView( + initialFile: "test_assets/in_app_webview_ajax_test.html", + initialHeaders: {}, + initialOptions: InAppWebViewWidgetOptions( + inAppWebViewOptions: InAppWebViewOptions( + clearCache: true, + debuggingEnabled: true, + useShouldInterceptAjaxRequest: true, + ) + ), + onWebViewCreated: (InAppWebViewController controller) { + webView = controller; + }, + onLoadStart: (InAppWebViewController controller, String url) { + + }, + onLoadStop: (InAppWebViewController controller, String url) { + + }, + shouldInterceptAjaxRequest: (InAppWebViewController controller, AjaxRequest ajaxRequest) async { + if (ajaxRequest.url.endsWith("/test-ajax-post")) { + ajaxRequest.responseType = 'json'; + ajaxRequest.data = "firstname=Lorenzo&lastname=Pichilli"; + } + return ajaxRequest; + }, + onAjaxReadyStateChange: (InAppWebViewController controller, AjaxRequest ajaxRequest) async { + if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && ajaxRequest.status == 200 && ajaxRequest.url.endsWith("/test-ajax-post")) { + Map res = ajaxRequest.response; + appBarTitle = (appBarTitle == "InAppWebViewAjaxTest") ? res['fullname'] : appBarTitle + " " + res['fullname']; + updateCountTest(context: context); + } + return AjaxRequestAction.PROCEED; + }, + onAjaxProgress: (InAppWebViewController controller, AjaxRequest ajaxRequest) async { + if (ajaxRequest.event.type == AjaxRequestEventType.LOAD && ajaxRequest.url.endsWith("/test-ajax-post")) { + Map res = ajaxRequest.response; + appBarTitle = (appBarTitle == "InAppWebViewAjaxTest") ? res['fullname'] : appBarTitle + " " + res['fullname']; + updateCountTest(context: context); + } + return AjaxRequestAction.PROCEED; + }, + ), + ), + ), + ]) + ) + ); + } + + void updateCountTest({@required BuildContext context}) { + testsDone++; + if (testsDone == totTests) { + setState(() { }); + nextTest(context: context, state: this); + } + } +} diff --git a/example/test/in_app_webview_initial_file_test.dart b/example/test_driver/in_app_webview_initial_file_test.dart similarity index 52% rename from example/test/in_app_webview_initial_file_test.dart rename to example/test_driver/in_app_webview_initial_file_test.dart index 45be8a27..754fb306 100644 --- a/example/test/in_app_webview_initial_file_test.dart +++ b/example/test_driver/in_app_webview_initial_file_test.dart @@ -2,34 +2,37 @@ import 'package:flutter/material.dart'; import 'package:flutter_inappbrowser/flutter_inappbrowser.dart'; +import 'main_test.dart'; import 'util_test.dart'; import 'custom_widget_test.dart'; class InAppWebViewInitialFileTest extends WidgetTest { - InAppWebViewInitialFileTest(): super(name: "InAppWebViewInitialFileTest"); + final InAppWebViewInitialFileTestState state = InAppWebViewInitialFileTestState(); @override - _InAppWebViewInitialFileTestState createState() => new _InAppWebViewInitialFileTestState(); + InAppWebViewInitialFileTestState createState() => state; } -class _InAppWebViewInitialFileTestState extends State { - InAppWebViewController webView; - String initialUrl = "https://flutter.dev/"; +class InAppWebViewInitialFileTestState extends WidgetTestState { + String appBarTitle = "InAppWebViewInitialFileTest"; @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('InAppWebViewInitialFileTest'), - ), + appBar: myAppBar(state: this, title: appBarTitle), body: Container( child: Column(children: [ Expanded( child: Container( child: InAppWebView( - initialFile: "assets/index.html", + initialFile: "test_assets/in_app_webview_initial_file_test.html", initialHeaders: {}, - initialOptions: InAppWebViewWidgetOptions(), + initialOptions: InAppWebViewWidgetOptions( + inAppWebViewOptions: InAppWebViewOptions( + clearCache: true, + debuggingEnabled: true + ) + ), onWebViewCreated: (InAppWebViewController controller) { webView = controller; }, @@ -37,8 +40,10 @@ class _InAppWebViewInitialFileTestState extends State new _InAppWebViewInitialUrlTestState(); + InAppWebViewInitialUrlTestState createState() => state; } -class _InAppWebViewInitialUrlTestState extends State { - InAppWebViewController webView; +class InAppWebViewInitialUrlTestState extends WidgetTestState { String initialUrl = "https://flutter.dev/"; + String appBarTitle = "InAppWebViewInitialUrlTest"; @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('InAppWebViewInitialUrlTest'), - ), + appBar: myAppBar(state: this, title: appBarTitle), body: Container( child: Column(children: [ Expanded( @@ -29,7 +28,12 @@ class _InAppWebViewInitialUrlTestState extends State child: InAppWebView( initialUrl: initialUrl, initialHeaders: {}, - initialOptions: InAppWebViewWidgetOptions(), + initialOptions: InAppWebViewWidgetOptions( + inAppWebViewOptions: InAppWebViewOptions( + clearCache: true, + debuggingEnabled: true + ) + ), onWebViewCreated: (InAppWebViewController controller) { webView = controller; }, @@ -37,8 +41,10 @@ class _InAppWebViewInitialUrlTestState extends State }, onLoadStop: (InAppWebViewController controller, String url) { - customAssert(widget: widget, name: "initialUrl", value: url == initialUrl); - nextTest(context: context); + setState(() { + appBarTitle = url; + }); + nextTest(context: context, state: this); }, ), ), diff --git a/example/test_driver/in_app_webview_javascript_handler_test.dart b/example/test_driver/in_app_webview_javascript_handler_test.dart new file mode 100644 index 00000000..172f8d14 --- /dev/null +++ b/example/test_driver/in_app_webview_javascript_handler_test.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_inappbrowser/flutter_inappbrowser.dart'; + +import 'main_test.dart'; +import 'util_test.dart'; +import 'custom_widget_test.dart'; + +class Foo { + String bar; + String baz; + + Foo({this.bar, this.baz}); + + Map toJson() { + return { + 'bar': this.bar, + 'baz': this.baz + }; + } +} + +class InAppWebViewJavaScriptHandlerTest extends WidgetTest { + final InAppWebViewJavaScriptHandlerTestState state = InAppWebViewJavaScriptHandlerTestState(); + + @override + InAppWebViewJavaScriptHandlerTestState createState() => state; +} + +class InAppWebViewJavaScriptHandlerTestState extends WidgetTestState { + + String appBarTitle = "InAppWebViewJavaScriptHandlerTest"; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: myAppBar(state: this, title: appBarTitle), + body: Container( + child: Column(children: [ + Expanded( + child: Container( + child: InAppWebView( + initialFile: "test_assets/in_app_webview_javascript_handler_test.html", + initialHeaders: {}, + initialOptions: InAppWebViewWidgetOptions( + inAppWebViewOptions: InAppWebViewOptions( + clearCache: true, + debuggingEnabled: true + ) + ), + onWebViewCreated: (InAppWebViewController controller) { + webView = controller; + + controller.addJavaScriptHandler(handlerName:'handlerFoo', callback: (args) { + appBarTitle = (args.length == 0).toString(); + return new Foo(bar: 'bar_value', baz: 'baz_value'); + }); + + controller.addJavaScriptHandler(handlerName: 'handlerFooWithArgs', callback: (args) { + appBarTitle += " " + (args[0] is int).toString(); + appBarTitle += " " + (args[1] is bool).toString(); + appBarTitle += " " + (args[2] is List).toString(); + appBarTitle += " " + (args[2] is List).toString(); + appBarTitle += " " + (args[3] is Map).toString(); + appBarTitle += " " + (args[4] is Map).toString(); + setState(() { }); + nextTest(context: context, state: this); + }); + + }, + onLoadStart: (InAppWebViewController controller, String url) { + + }, + onLoadStop: (InAppWebViewController controller, String url) { + + }, + ), + ), + ), + ]) + ) + ); + } +} diff --git a/example/test/in_app_webview_on_load_resource_test.dart b/example/test_driver/in_app_webview_on_load_resource_test.dart similarity index 66% rename from example/test/in_app_webview_on_load_resource_test.dart rename to example/test_driver/in_app_webview_on_load_resource_test.dart index 5f9d052f..1196f84a 100644 --- a/example/test/in_app_webview_on_load_resource_test.dart +++ b/example/test_driver/in_app_webview_on_load_resource_test.dart @@ -2,41 +2,41 @@ import 'package:flutter/material.dart'; import 'package:flutter_inappbrowser/flutter_inappbrowser.dart'; +import 'main_test.dart'; import 'util_test.dart'; import 'custom_widget_test.dart'; class InAppWebViewOnLoadResourceTest extends WidgetTest { - InAppWebViewOnLoadResourceTest(): super(name: "InAppWebViewOnLoadResourceTest"); + final InAppWebViewOnLoadResourceTestState state = InAppWebViewOnLoadResourceTestState(); @override - _InAppWebViewOnLoadResourceTestState createState() => new _InAppWebViewOnLoadResourceTestState(); + InAppWebViewOnLoadResourceTestState createState() => state; } -class _InAppWebViewOnLoadResourceTestState extends State { - InAppWebViewController webView; +class InAppWebViewOnLoadResourceTestState extends WidgetTestState { List resourceList = [ - "http://getbootstrap.com/docs/4.3/dist/css/bootstrap.min.css", + "https://getbootstrap.com/docs/4.3/dist/css/bootstrap.min.css", "https://code.jquery.com/jquery-3.3.1.min.js", "https://via.placeholder.com/100x50" ]; int countResources = 0; + String appBarTitle = "InAppWebViewOnLoadResourceTest"; @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('InAppWebViewOnLoadResourceTest'), - ), + appBar: myAppBar(state: this, title: appBarTitle), body: Container( child: Column(children: [ Expanded( child: Container( child: InAppWebView( - initialFile: "assets/index.html", + initialFile: "test_assets/in_app_webview_on_load_resource_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( inAppWebViewOptions: InAppWebViewOptions( clearCache: true, + debuggingEnabled: true, useOnLoadResource: true ) ), @@ -50,10 +50,11 @@ class _InAppWebViewOnLoadResourceTestState extends State testRoutes = []; +Map buildRoutes({@required BuildContext context}) { + var routes = { + '/': (context) => InAppWebViewInitialUrlTest(), + '/InAppWebViewInitialFileTest': (context) => InAppWebViewInitialFileTest(), + '/InAppWebViewOnLoadResourceTest': (context) => InAppWebViewOnLoadResourceTest(), + '/InAppWebViewJavaScriptHandlerTest': (context) => InAppWebViewJavaScriptHandlerTest(), + '/InAppWebViewAjaxTest': (context) => InAppWebViewAjaxTest(), + }; + routes.forEach((k, v) => testRoutes.add(k)); + return routes; +} + +AppBar myAppBar({@required WidgetTestState state, @required String title}) { + return AppBar( + title: Text( + title, + key: Key("AppBarTitle") + ), + actions: [ + IconButton( + icon: Icon(Icons.refresh), + onPressed: () { + if (state.webView != null) + state.webView.reload(); + }, + ), + ], + ); +} + +Future main() async { + runApp(new MyApp()); +} + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => new _MyAppState(); +} + +class _MyAppState extends State { + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'flutter_inappbrowser tests', + initialRoute: '/', + routes: buildRoutes(context: context) + ); + } +} \ No newline at end of file diff --git a/example/test_driver/util_test.dart b/example/test_driver/util_test.dart new file mode 100644 index 00000000..44db9b9f --- /dev/null +++ b/example/test_driver/util_test.dart @@ -0,0 +1,19 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'custom_widget_test.dart'; +import 'main_test.dart'; + +int currentTest = 0; + +void nextTest({@required BuildContext context, @required WidgetTestState state}) { + if (currentTest + 1 < testRoutes.length) { + currentTest++; + String nextRoute = testRoutes[currentTest]; + Future.delayed(const Duration(milliseconds: 2000)).then((value) { + Navigator.pushReplacementNamed(context, nextRoute); + }); + } +} \ No newline at end of file diff --git a/flutter_inappbrowser.iml b/flutter_inappbrowser.iml index 187fcb84..d81a44d5 100644 --- a/flutter_inappbrowser.iml +++ b/flutter_inappbrowser.iml @@ -21,6 +21,15 @@ + + + + + + + + + diff --git a/ios/Classes/CustomeSchemeHandler.swift b/ios/Classes/CustomeSchemeHandler.swift index 3d65b1ce..64ee0fe7 100644 --- a/ios/Classes/CustomeSchemeHandler.swift +++ b/ios/Classes/CustomeSchemeHandler.swift @@ -11,7 +11,10 @@ import WebKit @available(iOS 11.0, *) class CustomeSchemeHandler : NSObject, WKURLSchemeHandler { + var schemeHandlers: [Int:WKURLSchemeTask] = [:] + func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { + schemeHandlers[urlSchemeTask.hash] = urlSchemeTask let inAppWebView = webView as! InAppWebView if let url = urlSchemeTask.request.url, let scheme = url.scheme { inAppWebView.onLoadResourceCustomScheme(scheme: scheme, url: url.absoluteString, result: {(result) -> Void in @@ -25,9 +28,12 @@ class CustomeSchemeHandler : NSObject, WKURLSchemeHandler { json = r as! [String: Any] let urlResponse = URLResponse(url: url, mimeType: json["content-type"] as! String, expectedContentLength: -1, textEncodingName: json["content-encoding"] as! String) let data = json["data"] as! FlutterStandardTypedData - urlSchemeTask.didReceive(urlResponse) - urlSchemeTask.didReceive(data.data) - urlSchemeTask.didFinish() + if (self.schemeHandlers[urlSchemeTask.hash] != nil) { + urlSchemeTask.didReceive(urlResponse) + urlSchemeTask.didReceive(data.data) + urlSchemeTask.didFinish() + self.schemeHandlers.removeValue(forKey: urlSchemeTask.hash) + } } } }) @@ -35,6 +41,6 @@ class CustomeSchemeHandler : NSObject, WKURLSchemeHandler { } func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { - + schemeHandlers.removeValue(forKey: urlSchemeTask.hash) } } diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index edb14ad6..2908f7ec 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -82,6 +82,8 @@ window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() { } """ +let platformReadyJS = "window.dispatchEvent(new Event('flutterInAppBrowserPlatformReady'));"; + let findTextHighlightJS = """ var wkwebview_SearchResultCount = 0; var wkwebview_CurrentHighlight = 0; @@ -298,7 +300,6 @@ let interceptAjaxRequestsJS = """ }; ajax.prototype.setRequestHeader = function(header, value) { this._flutter_inappbrowser_request_headers[header] = value; - setRequestHeader.call(this, header, value); }; function handleEvent(e) { var self = this; @@ -439,7 +440,11 @@ let interceptAjaxRequestsJS = """ }; for (var header in result.headers) { var value = result.headers[header]; - self.setRequestHeader(header, value); + self._flutter_inappbrowser_request_headers[header] = value; + }; + for (var header in self._flutter_inappbrowser_request_headers) { + var value = self._flutter_inappbrowser_request_headers[header]; + setRequestHeader.call(self, header, value); }; if ((self._flutter_inappbrowser_method != result.method && result.method != null) || (self._flutter_inappbrowser_url != result.url && result.url != null)) { self.abort(); @@ -1342,6 +1347,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { currentURL = url InAppWebView.credentialsProposed = [] + evaluateJavaScript(platformReadyJS, completionHandler: nil) onLoadStop(url: (currentURL?.absoluteString)!) if IABController != nil { diff --git a/lib/src/content_blocker.dart b/lib/src/content_blocker.dart index 2e775888..6ddf9bd3 100644 --- a/lib/src/content_blocker.dart +++ b/lib/src/content_blocker.dart @@ -53,6 +53,11 @@ class ContentBlockerTriggerResourceType { static const SVG_DOCUMENT = const ContentBlockerTriggerResourceType._internal('svg-document'); ///Any untyped load static const RAW = const ContentBlockerTriggerResourceType._internal('raw'); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///ContentBlockerTriggerLoadType class represents the possible load type for a [ContentBlockerTrigger]. @@ -68,6 +73,11 @@ class ContentBlockerTriggerLoadType { static const FIRST_PARTY = const ContentBlockerTriggerLoadType._internal('first-party'); ///THIRD_PARTY is triggered if the resource is not from the same domain as the main page resource. static const THIRD_PARTY = const ContentBlockerTriggerLoadType._internal('third-party'); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///Trigger of the content blocker. The trigger tells to the WebView when to perform the corresponding action. @@ -187,6 +197,11 @@ class ContentBlockerActionType { static const CSS_DISPLAY_NONE = const ContentBlockerActionType._internal('css-display-none'); ///Changes a URL from http to https. URLs with a specified (nondefault) port and links using other protocols are unaffected. static const MAKE_HTTPS = const ContentBlockerActionType._internal('make-https'); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///Action associated to the trigger. The action tells to the WebView what to do when the trigger is matched. diff --git a/lib/src/in_app_browser.dart b/lib/src/in_app_browser.dart index 5ffa6f08..4b0bdf6a 100644 --- a/lib/src/in_app_browser.dart +++ b/lib/src/in_app_browser.dart @@ -97,7 +97,7 @@ class InAppBrowser { /// uses-material-design: true /// /// assets: - /// - assets/t-rex.html + /// - assets/index.html /// - assets/css/ /// - assets/images/ /// @@ -106,7 +106,7 @@ class InAppBrowser { ///Example of a `main.dart` file: ///```dart ///... - ///inAppBrowser.openFile("assets/t-rex.html"); + ///inAppBrowser.openFile("assets/index.html"); ///... ///``` /// diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart index 1c52bd49..a3077ac2 100755 --- a/lib/src/in_app_webview.dart +++ b/lib/src/in_app_webview.dart @@ -153,6 +153,10 @@ class InAppWebView extends StatefulWidget { ///[ajaxRequest] represents the `XMLHttpRequest`. /// ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptAjaxRequest] option to `true`. + ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code + ///used to intercept ajax requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). + ///Inside the `window.addEventListener("flutterInAppBrowserPlatformReady")` event, the fetch requests will be intercept for sure. final Future Function(InAppWebViewController controller, AjaxRequest ajaxRequest) shouldInterceptAjaxRequest; ///Event fired whenever the `readyState` attribute of an `XMLHttpRequest` changes. @@ -161,6 +165,10 @@ class InAppWebView extends StatefulWidget { ///[ajaxRequest] represents the [XMLHttpRequest]. /// ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptAjaxRequest] option to `true`. + ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code + ///used to intercept ajax requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). + ///Inside the `window.addEventListener("flutterInAppBrowserPlatformReady")` event, the fetch requests will be intercept for sure. final Future Function(InAppWebViewController controller, AjaxRequest ajaxRequest) onAjaxReadyStateChange; ///Event fired as an `XMLHttpRequest` progress. @@ -169,6 +177,10 @@ class InAppWebView extends StatefulWidget { ///[ajaxRequest] represents the [XMLHttpRequest]. /// ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptAjaxRequest] option to `true`. + ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code + ///used to intercept ajax requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). + ///Inside the `window.addEventListener("flutterInAppBrowserPlatformReady")` event, the fetch requests will be intercept for sure. final Future Function(InAppWebViewController controller, AjaxRequest ajaxRequest) onAjaxProgress; ///Event fired when an request is sent to a server through [Fetch API](https://developer.mozilla.org/it/docs/Web/API/Fetch_API). @@ -177,6 +189,10 @@ class InAppWebView extends StatefulWidget { ///[fetchRequest] represents a resource request. /// ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptFetchRequest] option to `true`. + ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code + ///used to intercept fetch requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). + ///Inside the `window.addEventListener("flutterInAppBrowserPlatformReady")` event, the fetch requests will be intercept for sure. final Future Function(InAppWebViewController controller, FetchRequest fetchRequest) shouldInterceptFetchRequest; ///Event fired when the navigation state of the [InAppWebView] changes throught the usage of @@ -922,7 +938,7 @@ class InAppWebViewController { /// uses-material-design: true /// /// assets: - /// - assets/t-rex.html + /// - assets/index.html /// - assets/css/ /// - assets/images/ /// @@ -931,7 +947,7 @@ class InAppWebViewController { ///Example of a `main.dart` file: ///```dart ///... - ///inAppBrowser.loadFile("assets/t-rex.html"); + ///inAppBrowser.loadFile("assets/index.html"); ///... ///``` Future loadFile({@required String assetFilePath, Map headers = const {}}) async { @@ -1111,6 +1127,14 @@ class InAppWebViewController { ///The JavaScript function that can be used to call the handler is `window.flutter_inappbrowser.callHandler(handlerName , ...args)`, where `args` are [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters). ///The `args` will be stringified automatically using `JSON.stringify(args)` method and then they will be decoded on the Dart side. /// + ///In order to call `window.flutter_inappbrowser.callHandler(handlerName , ...args)` properly, you need to wait and listen the JavaScript event `flutterInAppBrowserPlatformReady`. + ///This event will be dispatch as soon as the platform (Android or iOS) is ready to handle the `callHandler` method. + ///```javascript + /// window.addEventListener("flutterInAppBrowserPlatformReady", function(event) { + /// console.log("ready"); + /// }); + ///``` + /// ///`window.flutter_inappbrowser.callHandler` returns a JavaScript [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) ///that can be used to get the json result returned by [JavaScriptHandlerCallback]. ///In this case, simply return data that you want to send and it will be automatically json encoded using [jsonEncode] from the `dart:convert` library. @@ -1118,6 +1142,7 @@ class InAppWebViewController { ///So, on the JavaScript side, to get data coming from the Dart side, you will use: ///```html /// ///``` + /// + ///Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly: + ///```dart + /// // Inject JavaScript that will receive data back from Flutter + /// inAppWebViewController.evaluateJavascript(source: """ + /// window.flutter_inappbrowser.callHandler('test', 'Text from Javascript').then(function(result) { + /// console.log(result); + /// }); + /// """); + ///``` void addJavaScriptHandler({@required String handlerName, @required JavaScriptHandlerCallback callback}) { assert(!javaScriptHandlerForbiddenNames.contains(handlerName)); this.javaScriptHandlersMap[handlerName] = (callback); diff --git a/lib/src/types.dart b/lib/src/types.dart index 9f5ca480..e977cdb7 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -39,6 +39,12 @@ class ConsoleMessageLevel { static const WARNING = const ConsoleMessageLevel._internal(2); static const ERROR = const ConsoleMessageLevel._internal(3); static const DEBUG = const ConsoleMessageLevel._internal(4); + + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///Public class representing a resource response of the [InAppBrowser] WebView. @@ -204,6 +210,11 @@ class JsAlertResponseAction { toValue() => _value; static const CONFIRM = const JsAlertResponseAction._internal(0); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///JsAlertResponse class represents the response used by the [onJsAlert] event to control a JavaScript alert dialog. @@ -237,6 +248,11 @@ class JsConfirmResponseAction { static const CONFIRM = const JsConfirmResponseAction._internal(0); static const CANCEL = const JsConfirmResponseAction._internal(1); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///JsConfirmResponse class represents the response used by the [onJsConfirm] event to control a JavaScript confirm dialog. @@ -273,6 +289,11 @@ class JsPromptResponseAction { static const CONFIRM = const JsPromptResponseAction._internal(0); static const CANCEL = const JsPromptResponseAction._internal(1); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///JsPromptResponse class represents the response used by the [onJsPrompt] event to control a JavaScript prompt dialog. @@ -323,6 +344,11 @@ class SafeBrowsingThreat { static const SAFE_BROWSING_THREAT_PHISHING = const SafeBrowsingThreat._internal(2); static const SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE = const SafeBrowsingThreat._internal(3); static const SAFE_BROWSING_THREAT_BILLING = const SafeBrowsingThreat._internal(4); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///SafeBrowsingResponseAction class used by [SafeBrowsingResponse] class. @@ -337,6 +363,11 @@ class SafeBrowsingResponseAction { static const PROCEED = const SafeBrowsingResponseAction._internal(1); ///Display the default interstitial. static const SHOW_INTERSTITIAL = const SafeBrowsingResponseAction._internal(2); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///SafeBrowsingResponse class represents the response used by the [onSafeBrowsingHit] event. @@ -369,6 +400,11 @@ class HttpAuthResponseAction { static const PROCEED = const HttpAuthResponseAction._internal(1); ///Uses the credentials stored for the current host. static const USE_SAVED_HTTP_AUTH_CREDENTIALS = const HttpAuthResponseAction._internal(2); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///HttpAuthResponse class represents the response used by the [onReceivedHttpAuthRequest] event. @@ -442,6 +478,11 @@ class ServerTrustAuthResponseAction { static const CANCEL = const ServerTrustAuthResponseAction._internal(0); ///Instructs the WebView to proceed with the authentication challenge. static const PROCEED = const ServerTrustAuthResponseAction._internal(1); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///ServerTrustAuthResponse class represents the response used by the [onReceivedServerTrustAuthRequest] event. @@ -489,6 +530,11 @@ class ClientCertResponseAction { static const PROCEED = const ClientCertResponseAction._internal(1); ///Ignore the request for now. static const IGNORE = const ClientCertResponseAction._internal(2); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///ClientCertResponse class represents the response used by the [onReceivedClientCertRequest] event. @@ -564,6 +610,11 @@ class AndroidInAppWebViewCacheMode { static const LOAD_NO_CACHE = const AndroidInAppWebViewCacheMode._internal(2); ///Don't use the network, load from the cache. static const LOAD_CACHE_ONLY = const AndroidInAppWebViewCacheMode._internal(3); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///AndroidInAppWebViewModeMenuItem class represents an Android-specific class used to disable the action mode menu items. @@ -587,6 +638,11 @@ class AndroidInAppWebViewModeMenuItem { static const MENU_ITEM_WEB_SEARCH = const AndroidInAppWebViewModeMenuItem._internal(2); ///Disable all the action mode menu items for text processing. static const MENU_ITEM_PROCESS_TEXT = const AndroidInAppWebViewModeMenuItem._internal(4); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///AndroidInAppWebViewForceDark class represents an Android-specific class used to indicate the force dark mode. @@ -609,6 +665,11 @@ class AndroidInAppWebViewForceDark { static const FORCE_DARK_AUTO = const AndroidInAppWebViewForceDark._internal(1); ///Unconditionally enable force dark. In this mode WebView content will always be rendered so as to emulate a dark theme. static const FORCE_DARK_ON = const AndroidInAppWebViewForceDark._internal(2); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///AndroidInAppWebViewLayoutAlgorithm class represents an Android-specific class used to set the underlying layout algorithm. @@ -627,6 +688,11 @@ class AndroidInAppWebViewLayoutAlgorithm { /// ///**NOTE**: available on Android 19+. static const TEXT_AUTOSIZING = const AndroidInAppWebViewLayoutAlgorithm._internal("TEXT_AUTOSIZING"); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///AndroidInAppWebViewMixedContentMode class represents an Android-specific class used to configure the WebView's behavior when a secure origin attempts to load a resource from an insecure origin. @@ -654,6 +720,11 @@ class AndroidInAppWebViewMixedContentMode { ///This mode is intended to be used by apps that are not in control of the content that they render but desire to operate in a reasonably secure environment. ///For highest security, apps are recommended to use [AndroidInAppWebViewMixedContentMode.MIXED_CONTENT_NEVER_ALLOW]. static const MIXED_CONTENT_COMPATIBILITY_MODE = const AndroidInAppWebViewMixedContentMode._internal(2); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///IosInAppWebViewSelectionGranularity class represents an iOS-specific class used to set the level of granularity with which the user can interactively select content in the web view. @@ -671,6 +742,11 @@ class IosInAppWebViewSelectionGranularity { static const DYNAMIC = const IosInAppWebViewSelectionGranularity._internal(0); ///Selection endpoints can be placed at any character boundary. static const CHARACTER = const IosInAppWebViewSelectionGranularity._internal(1); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///IosInAppWebViewDataDetectorTypes class represents an iOS-specific class used to specify a dataDetectoryTypes value that adds interactivity to web content that matches the value. @@ -705,6 +781,11 @@ class IosInAppWebViewDataDetectorTypes { static const SPOTLIGHT_SUGGESTION = const IosInAppWebViewDataDetectorTypes._internal("SPOTLIGHT_SUGGESTION"); ///All of the above data types are turned into links when detected. Choosing this value will automatically include any new detection type that is added. static const ALL = const IosInAppWebViewDataDetectorTypes._internal("ALL"); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///InAppWebViewUserPreferredContentMode class represents the content mode to prefer when loading and rendering a webpage. @@ -724,6 +805,11 @@ class InAppWebViewUserPreferredContentMode { static const MOBILE = const InAppWebViewUserPreferredContentMode._internal(1); ///Represents content targeting desktop browsers. static const DESKTOP = const InAppWebViewUserPreferredContentMode._internal(2); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///IosWebViewOptionsPresentationStyle class represents an iOS-specific class used to specify the modal presentation style when presenting a view controller. @@ -759,6 +845,11 @@ class IosWebViewOptionsPresentationStyle { /// ///**NOTE**: available on iOS 13.0+. static const AUTOMATIC = const IosWebViewOptionsPresentationStyle._internal(9); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///IosWebViewOptionsTransitionStyle class represents an iOS-specific class used to specify the transition style when presenting a view controller. @@ -786,6 +877,11 @@ class IosWebViewOptionsTransitionStyle { ///On dismissal, the curled up page unfurls itself back on top of the presented view. ///A view controller presented using this transition is itself prevented from presenting any additional view controllers. static const PARTIAL_CURL = const IosWebViewOptionsTransitionStyle._internal(3); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///IosWebViewOptionsTransitionStyle class represents an iOS-specific class used to set the custom style for the dismiss button. @@ -807,6 +903,11 @@ class IosSafariOptionsDismissButtonStyle { static const CLOSE = const IosSafariOptionsDismissButtonStyle._internal(1); ///Makes the button title the localized string "Cancel". static const CANCEL = const IosSafariOptionsDismissButtonStyle._internal(2); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///InAppWebViewWidgetOptions class represents the options that can be used for an [InAppWebView]. @@ -856,6 +957,11 @@ class AjaxRequestAction { ///Proceeds with the current [AjaxRequest]. static const PROCEED = const AjaxRequestAction._internal(1); + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; + Map toMap() { return { "action": _value, @@ -892,6 +998,11 @@ class AjaxRequestEventType { static const ABORT = const AjaxRequestEventType._internal("abort"); ///The TIMEOUT event is fired when progression is terminated due to preset time expiring. static const TIMEOUT = const AjaxRequestEventType._internal("timeout"); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///AjaxRequestEvent class used by [AjaxRequest] class. It represents events measuring progress of an [AjaxRequest]. @@ -934,6 +1045,11 @@ class AjaxRequestReadyState { static const LOADING = const AjaxRequestReadyState._internal(3); ///The operation is complete. static const DONE = const AjaxRequestReadyState._internal(4); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///AjaxRequest class represents a JavaScript [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object. @@ -1029,6 +1145,11 @@ class FetchRequestAction { static const ABORT = const FetchRequestAction._internal(0); ///Proceeds with the fetch request. static const PROCEED = const FetchRequestAction._internal(1); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } ///FetchRequestCredential class is an interface for [FetchRequestCredentialDefault], [FetchRequestFederatedCredential] and [FetchRequestPasswordCredential] classes. diff --git a/nodejs_server_test_auth_basic_and_ssl/index.js b/nodejs_server_test_auth_basic_and_ssl/index.js index 0f990757..2e8d7a06 100644 --- a/nodejs_server_test_auth_basic_and_ssl/index.js +++ b/nodejs_server_test_auth_basic_and_ssl/index.js @@ -113,6 +113,7 @@ app.get("/", (req, res) => { app.post("/test-post", (req, res) => { console.log(JSON.stringify(req.headers)) + console.log(JSON.stringify(req.body)) res.send(` @@ -127,10 +128,12 @@ app.post("/test-post", (req, res) => { app.post("/test-ajax-post", (req, res) => { console.log(JSON.stringify(req.headers)) + console.log(JSON.stringify(req.body)) res.set("Content-Type", "application/json") res.send(JSON.stringify({ - "name": req.body.name, - "key2": "value2" + "firstname": req.body.firstname, + "lastname": req.body.lastname, + "fullname": req.body.firstname + " " + req.body.lastname, })) res.end() })