diff --git a/example/integration_test/constants.dart b/example/integration_test/constants.dart index d774a815..c5d3faba 100644 --- a/example/integration_test/constants.dart +++ b/example/integration_test/constants.dart @@ -1,5 +1,5 @@ final TEST_URL_ABOUT_BLANK = Uri.parse('about:blank'); -final TEST_CROSS_PLATFORM_URL_1 = Uri.parse('https://flutter.dev'); +final TEST_CROSS_PLATFORM_URL_1 = Uri.parse('https://flutter.dev/'); final TEST_CROSS_PLATFORM_URL_2 = Uri.parse('https://www.bing.com/'); final TEST_URL_1 = Uri.parse('https://github.com/flutter'); final TEST_URL_2 = Uri.parse('https://www.google.com/'); diff --git a/example/integration_test/in_app_webview/initial_url_request.dart b/example/integration_test/in_app_webview/initial_url_request.dart index c6eaa934..fd15aba7 100644 --- a/example/integration_test/in_app_webview/initial_url_request.dart +++ b/example/integration_test/in_app_webview/initial_url_request.dart @@ -8,32 +8,75 @@ import 'package:flutter_test/flutter_test.dart'; import '../constants.dart'; void initialUrlRequest() { - final shouldSkip = !kIsWeb || ![ - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS, - ].contains(defaultTargetPlatform); + final shouldSkip = !kIsWeb || + ![ + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); - testWidgets('initialUrlRequest', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialUrlRequest: - URLRequest(url: TEST_CROSS_PLATFORM_URL_1), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, + group('initial url request', () { + final shouldSkipTest1 = !kIsWeb || + ![ + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + testWidgets('basic', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + ), ), - ), - ); + ); - final InAppWebViewController controller = - await controllerCompleter.future; - final String? currentUrl = (await controller.getUrl())?.toString(); + final InAppWebViewController controller = + await controllerCompleter.future; + final String? currentUrl = (await controller.getUrl())?.toString(); - expect(currentUrl, TEST_CROSS_PLATFORM_URL_1.toString()); - }, skip: shouldSkip); -} \ No newline at end of file + expect(currentUrl, TEST_CROSS_PLATFORM_URL_1.toString()); + }, skip: shouldSkipTest1); + + final shouldSkipTest2 = kIsWeb || + ![ + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + testWidgets('launches with allowsBackForwardNavigationGestures true', + (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: SizedBox( + width: 400, + height: 300, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: + URLRequest(url: TEST_URL_1), + initialSettings: InAppWebViewSettings( + allowsBackForwardNavigationGestures: true), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ), + ); + final InAppWebViewController controller = + await controllerCompleter.future; + final String? currentUrl = (await controller.getUrl())?.toString(); + expect(currentUrl, TEST_URL_1.toString()); + }, skip: shouldSkipTest2); + }); +} diff --git a/example/integration_test/in_app_webview/intercept_ajax_request.dart b/example/integration_test/in_app_webview/intercept_ajax_request.dart new file mode 100644 index 00000000..6dae7320 --- /dev/null +++ b/example/integration_test/in_app_webview/intercept_ajax_request.dart @@ -0,0 +1,376 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../.env.dart'; + +void interceptAjaxRequest() { + final shouldSkip = kIsWeb || + ![ + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + group('intercept ajax request', () { + testWidgets('send string data', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer shouldInterceptAjaxPostRequestCompleter = + Completer(); + final Completer> onAjaxReadyStateChangeCompleter = + Completer>(); + final Completer> onAjaxProgressCompleter = + Completer>(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ + + + + + + + InAppWebViewAjaxTest + + +

InAppWebViewAjaxTest

+ + + + """), + initialSettings: InAppWebViewSettings( + clearCache: true, + useShouldInterceptAjaxRequest: true, + ), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + shouldInterceptAjaxRequest: (controller, ajaxRequest) async { + assert(ajaxRequest.data == "firstname=Foo&lastname=Bar"); + + ajaxRequest.responseType = 'json'; + ajaxRequest.data = "firstname=Foo2&lastname=Bar2"; + shouldInterceptAjaxPostRequestCompleter.complete(controller); + return ajaxRequest; + }, + onAjaxReadyStateChange: (controller, ajaxRequest) async { + if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && + ajaxRequest.status == 200) { + Map res = ajaxRequest.response; + onAjaxReadyStateChangeCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + onAjaxProgress: (controller, ajaxRequest) async { + if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { + Map res = ajaxRequest.response; + onAjaxProgressCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + ), + ), + ); + + await shouldInterceptAjaxPostRequestCompleter.future; + final Map onAjaxReadyStateChangeValue = + await onAjaxReadyStateChangeCompleter.future; + final Map onAjaxProgressValue = + await onAjaxProgressCompleter.future; + + expect( + mapEquals(onAjaxReadyStateChangeValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + expect( + mapEquals( + onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + + testWidgets('send json data', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer shouldInterceptAjaxPostRequestCompleter = + Completer(); + final Completer> onAjaxReadyStateChangeCompleter = + Completer>(); + final Completer> onAjaxProgressCompleter = + Completer>(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ + + + + + + + InAppWebViewAjaxTest + + +

InAppWebViewAjaxTest

+ + + + """), + initialSettings: InAppWebViewSettings( + 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"')); + + ajaxRequest.responseType = 'json'; + ajaxRequest.data = '{"firstname": "Foo2", "lastname": "Bar2"}'; + shouldInterceptAjaxPostRequestCompleter.complete(controller); + return ajaxRequest; + }, + onAjaxReadyStateChange: (controller, ajaxRequest) async { + if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && + ajaxRequest.status == 200) { + Map res = ajaxRequest.response; + onAjaxReadyStateChangeCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + onAjaxProgress: (controller, ajaxRequest) async { + if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { + Map res = ajaxRequest.response; + onAjaxProgressCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + ), + ), + ); + + await shouldInterceptAjaxPostRequestCompleter.future; + final Map onAjaxReadyStateChangeValue = + await onAjaxReadyStateChangeCompleter.future; + final Map onAjaxProgressValue = + await onAjaxProgressCompleter.future; + + expect( + mapEquals(onAjaxReadyStateChangeValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + expect( + mapEquals( + onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + + testWidgets('send URLSearchParams data', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer shouldInterceptAjaxPostRequestCompleter = + Completer(); + final Completer> onAjaxReadyStateChangeCompleter = + Completer>(); + final Completer> onAjaxProgressCompleter = + Completer>(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ + + + + + + + InAppWebViewAjaxTest + + +

InAppWebViewAjaxTest

+ + + + """), + initialSettings: InAppWebViewSettings( + clearCache: true, + useShouldInterceptAjaxRequest: true, + ), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + shouldInterceptAjaxRequest: (controller, ajaxRequest) async { + assert(ajaxRequest.data == "firstname=Foo&lastname=Bar"); + + ajaxRequest.responseType = 'json'; + ajaxRequest.data = "firstname=Foo2&lastname=Bar2"; + shouldInterceptAjaxPostRequestCompleter.complete(controller); + return ajaxRequest; + }, + onAjaxReadyStateChange: (controller, ajaxRequest) async { + if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && + ajaxRequest.status == 200) { + Map res = ajaxRequest.response; + onAjaxReadyStateChangeCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + onAjaxProgress: (controller, ajaxRequest) async { + if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { + Map res = ajaxRequest.response; + onAjaxProgressCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + ), + ), + ); + + await shouldInterceptAjaxPostRequestCompleter.future; + final Map onAjaxReadyStateChangeValue = + await onAjaxReadyStateChangeCompleter.future; + final Map onAjaxProgressValue = + await onAjaxProgressCompleter.future; + + expect( + mapEquals(onAjaxReadyStateChangeValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + expect( + mapEquals( + onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + + testWidgets('send FormData', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer shouldInterceptAjaxPostRequestCompleter = + Completer(); + final Completer> onAjaxReadyStateChangeCompleter = + Completer>(); + final Completer> onAjaxProgressCompleter = + Completer>(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ + + + + + + + InAppWebViewAjaxTest + + +

InAppWebViewAjaxTest

+ + + + """), + initialSettings: InAppWebViewSettings( + clearCache: true, + useShouldInterceptAjaxRequest: true, + ), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + shouldInterceptAjaxRequest: (controller, ajaxRequest) async { + assert(ajaxRequest.data != null); + + var body = ajaxRequest.data.cast(); + var bodyString = String.fromCharCodes(body); + assert(bodyString.indexOf("WebKitFormBoundary") >= 0); + + ajaxRequest.data = utf8.encode(bodyString + .replaceFirst("Foo", "Foo2") + .replaceFirst("Bar", "Bar2")); + ajaxRequest.responseType = 'json'; + shouldInterceptAjaxPostRequestCompleter.complete(controller); + return ajaxRequest; + }, + onAjaxReadyStateChange: (controller, ajaxRequest) async { + if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && + ajaxRequest.status == 200) { + Map res = ajaxRequest.response; + onAjaxReadyStateChangeCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + onAjaxProgress: (controller, ajaxRequest) async { + if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { + Map res = ajaxRequest.response; + onAjaxProgressCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + ), + ), + ); + + await shouldInterceptAjaxPostRequestCompleter.future; + final Map onAjaxReadyStateChangeValue = + await onAjaxReadyStateChangeCompleter.future; + final Map onAjaxProgressValue = + await onAjaxProgressCompleter.future; + + expect( + mapEquals(onAjaxReadyStateChangeValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + expect( + mapEquals( + onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + }, skip: shouldSkip); +} diff --git a/example/integration_test/in_app_webview/main.dart b/example/integration_test/in_app_webview/main.dart index 7d30a0ef..67d901c0 100644 --- a/example/integration_test/in_app_webview/main.dart +++ b/example/integration_test/in_app_webview/main.dart @@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'audio_playback_policy.dart'; import 'get_title.dart'; import 'initial_url_request.dart'; +import 'intercept_ajax_request.dart'; import 'javascript_code_evaluation.dart'; import 'javascript_handler.dart'; import 'load_file_url.dart'; @@ -14,6 +15,7 @@ import 'set_custom_useragent.dart'; import 'set_get_settings.dart'; import 'should_override_url_loading.dart'; import 'video_playback_policy.dart'; +import 'webview_windows.dart'; void main() { group('InAppWebView', () { @@ -31,5 +33,7 @@ void main() { programmaticScroll(); shouldOverrideUrlLoading(); onLoadError(); + webViewWindows(); + interceptAjaxRequest(); }); } \ No newline at end of file diff --git a/example/integration_test/in_app_webview/webview_windows.dart b/example/integration_test/in_app_webview/webview_windows.dart new file mode 100644 index 00000000..7f778bc1 --- /dev/null +++ b/example/integration_test/in_app_webview/webview_windows.dart @@ -0,0 +1,337 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../constants.dart'; + +void webViewWindows() { + final shouldSkip = !kIsWeb || + ![ + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + group("WebView Windows", () { + final shouldSkipTest1 = kIsWeb || + ![ + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + testWidgets('onCreateWindow return false', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer pageLoaded = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialFile: + "test_assets/in_app_webview_on_create_window_test.html", + initialSettings: InAppWebViewSettings( + clearCache: true, + javaScriptCanOpenWindowsAutomatically: true, + ), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + onLoadStop: (controller, url) { + if (url!.toString() == TEST_CROSS_PLATFORM_URL_1.toString()) { + pageLoaded.complete(); + } + }, + onCreateWindow: (controller, createNavigationAction) async { + controller.loadUrl(urlRequest: createNavigationAction.request); + return false; + }, + ), + ), + ); + + await expectLater(pageLoaded.future, completes); + }, skip: shouldSkipTest1); + + final shouldSkipTest2 = kIsWeb || + ![ + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + testWidgets('onCreateWindow return true', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer onCreateWindowCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialFile: + "test_assets/in_app_webview_on_create_window_test.html", + initialSettings: InAppWebViewSettings( + clearCache: true, + javaScriptCanOpenWindowsAutomatically: true, + supportMultipleWindows: true), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + onCreateWindow: (controller, createNavigationAction) async { + onCreateWindowCompleter.complete(createNavigationAction.windowId); + return true; + }, + ), + ), + ); + + var windowId = await onCreateWindowCompleter.future; + + final Completer windowControllerCompleter = + Completer(); + final Completer windowPageLoaded = Completer(); + final Completer onCloseWindowCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + windowId: windowId, + initialSettings: InAppWebViewSettings( + clearCache: true, + ), + onWebViewCreated: (controller) { + windowControllerCompleter.complete(controller); + }, + onLoadStop: (controller, url) async { + if (url!.scheme != "about" && !windowPageLoaded.isCompleted) { + windowPageLoaded.complete(url.toString()); + await controller.evaluateJavascript(source: "window.close();"); + } + }, + onCloseWindow: (controller) { + onCloseWindowCompleter.complete(); + }, + ), + ), + ); + + final String windowUrlLoaded = await windowPageLoaded.future; + + expect(windowUrlLoaded, TEST_CROSS_PLATFORM_URL_1.toString()); + await expectLater(onCloseWindowCompleter.future, completes); + }, skip: shouldSkipTest2); + + final shouldSkipTest3 = kIsWeb || + ![ + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + testWidgets('window.open() with target _blank opens in same window', + (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + initialUrlRequest: URLRequest(url: TEST_URL_ABOUT_BLANK), + initialSettings: InAppWebViewSettings( + javaScriptEnabled: true, + javaScriptCanOpenWindowsAutomatically: true), + onLoadStop: (controller, url) { + pageLoads.add(url!.toString()); + }, + ), + ), + ); + await pageLoads.stream.first; + final InAppWebViewController controller = + await controllerCompleter.future; + + await controller.evaluateJavascript( + source: 'window.open("$TEST_URL_ABOUT_BLANK", "_blank");'); + await pageLoads.stream.first; + final String? currentUrl = (await controller.getUrl())?.toString(); + expect(currentUrl, TEST_URL_ABOUT_BLANK.toString()); + + pageLoads.close(); + }, skip: shouldSkipTest3); + + final shouldSkipTest4 = kIsWeb || + ![ + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + testWidgets( + 'can open new window and go back', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + initialSettings: InAppWebViewSettings( + javaScriptEnabled: true, + javaScriptCanOpenWindowsAutomatically: true, + ), + onLoadStop: (controller, url) { + pageLoads.add(url!.toString()); + }, + ), + ), + ); + await pageLoads.stream.first; + final InAppWebViewController controller = + await controllerCompleter.future; + + await controller.evaluateJavascript( + source: 'window.open("$TEST_URL_1");'); + await pageLoads.stream.first; + expect( + (await controller.getUrl())?.toString(), contains(TEST_URL_1.host)); + + await controller.goBack(); + await pageLoads.stream.first; + expect((await controller.getUrl())?.toString(), + contains(TEST_CROSS_PLATFORM_URL_1.host)); + + pageLoads.close(); + }, skip: shouldSkipTest4); + + final shouldSkipTest5 = kIsWeb || + ![ + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + testWidgets( + 'javascript does not run in parent window', + (WidgetTester tester) async { + final String iframe = ''' + + + '''; + final String iframeTestBase64 = + base64Encode(const Utf8Encoder().convert(iframe)); + + final String openWindowTest = ''' + + + + XSS test + + + + + + '''; + final String openWindowTestBase64 = + base64Encode(const Utf8Encoder().convert(openWindowTest)); + final Completer controllerCompleter = + Completer(); + final Completer pageLoadCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: URLRequest( + url: Uri.parse( + 'data:text/html;charset=utf-8;base64,$openWindowTestBase64')), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + initialSettings: InAppWebViewSettings( + javaScriptEnabled: true, + javaScriptCanOpenWindowsAutomatically: true, + ), + onLoadStop: (controller, url) { + pageLoadCompleter.complete(); + }, + ), + ), + ); + + final InAppWebViewController controller = + await controllerCompleter.future; + await pageLoadCompleter.future; + + expect(controller.evaluateJavascript(source: 'iframeLoaded'), + completion(true)); + expect( + controller.evaluateJavascript( + source: + 'document.querySelector("p") && document.querySelector("p").textContent'), + completion(null), + ); + }, skip: shouldSkipTest5); + + final shouldSkipTest6 = !kIsWeb; + + testWidgets('onCreateWindow called on Web', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer onCreateWindowCalled = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: URLRequest(url: TEST_WEB_PLATFORM_URL_1), + initialSettings: InAppWebViewSettings( + clearCache: true, + javaScriptCanOpenWindowsAutomatically: true, + ), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + onCreateWindow: (controller, createNavigationAction) async { + onCreateWindowCalled.complete(createNavigationAction.request.url.toString()); + return false; + }, + ), + ), + ); + + final InAppWebViewController controller = await controllerCompleter.future; + await controller.evaluateJavascript(source: "window.open('$TEST_CROSS_PLATFORM_URL_1')"); + + var url = await onCreateWindowCalled.future; + expect(url, TEST_CROSS_PLATFORM_URL_1.toString()); + }, skip: shouldSkipTest6); + }, skip: shouldSkip); +} diff --git a/example/integration_test/webview_flutter_test.dart b/example/integration_test/webview_flutter_test.dart index af21803a..1484d972 100644 --- a/example/integration_test/webview_flutter_test.dart +++ b/example/integration_test/webview_flutter_test.dart @@ -27,562 +27,7 @@ void main() { group('InAppWebView', () { - testWidgets('launches with allowsBackForwardNavigationGestures true on iOS', - (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: SizedBox( - width: 400, - height: 300, - child: InAppWebView( - key: GlobalKey(), - initialUrlRequest: - URLRequest(url: Uri.parse('https://github.com/flutter')), - initialOptions: InAppWebViewGroupOptions( - ios: IOSInAppWebViewOptions( - allowsBackForwardNavigationGestures: true)), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ), - ); - final InAppWebViewController controller = - await controllerCompleter.future; - final String? currentUrl = (await controller.getUrl())?.toString(); - expect(currentUrl, 'https://github.com/flutter'); - }, skip: defaultTargetPlatform != TargetPlatform.iOS); - testWidgets('target _blank opens in same window', - (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - initialUrlRequest: URLRequest(url: Uri.parse("about:blank")), - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - javaScriptEnabled: true, - javaScriptCanOpenWindowsAutomatically: true), - ), - onLoadStop: (controller, url) { - pageLoads.add(url!.toString()); - }, - ), - ), - ); - await pageLoads.stream.first; - final InAppWebViewController controller = - await controllerCompleter.future; - - await controller.evaluateJavascript( - source: 'window.open("about:blank", "_blank");'); - await pageLoads.stream.first; - final String? currentUrl = (await controller.getUrl())?.toString(); - expect(currentUrl, 'about:blank'); - - pageLoads.close(); - }); - - testWidgets( - 'can open new window and go back', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialUrlRequest: - URLRequest(url: Uri.parse('https://flutter.dev')), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - javaScriptEnabled: true, - javaScriptCanOpenWindowsAutomatically: true), - ), - onLoadStop: (controller, url) { - pageLoads.add(url!.toString()); - }, - ), - ), - ); - await pageLoads.stream.first; - final InAppWebViewController controller = - await controllerCompleter.future; - - await controller.evaluateJavascript( - source: 'window.open("https://github.com/flutter");'); - await pageLoads.stream.first; - expect((await controller.getUrl())?.toString(), - contains('github.com/flutter')); - - await controller.goBack(); - await pageLoads.stream.first; - expect( - (await controller.getUrl())?.toString(), contains('flutter.dev')); - - pageLoads.close(); - }, - skip: true /* defaultTargetPlatform != TargetPlatform.android */, - ); - - testWidgets( - 'javascript does not run in parent window', - (WidgetTester tester) async { - final String iframe = ''' - - - '''; - final String iframeTestBase64 = - base64Encode(const Utf8Encoder().convert(iframe)); - - final String openWindowTest = ''' - - - - XSS test - - - - - - '''; - final String openWindowTestBase64 = - base64Encode(const Utf8Encoder().convert(openWindowTest)); - final Completer controllerCompleter = - Completer(); - final Completer pageLoadCompleter = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialUrlRequest: URLRequest( - url: Uri.parse( - 'data:text/html;charset=utf-8;base64,$openWindowTestBase64')), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - javaScriptEnabled: true, - javaScriptCanOpenWindowsAutomatically: true), - ), - onLoadStop: (controller, url) { - pageLoadCompleter.complete(); - }, - ), - ), - ); - - final InAppWebViewController controller = - await controllerCompleter.future; - await pageLoadCompleter.future; - - expect(controller.evaluateJavascript(source: 'iframeLoaded'), - completion(true)); - expect( - controller.evaluateJavascript( - source: - 'document.querySelector("p") && document.querySelector("p").textContent'), - completion(null), - ); - }, - skip: defaultTargetPlatform != TargetPlatform.android, - ); - - group('intercept ajax request', () { - testWidgets('send string data', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer shouldInterceptAjaxPostRequestCompleter = - Completer(); - final Completer> onAjaxReadyStateChangeCompleter = - Completer>(); - final Completer> onAjaxProgressCompleter = - Completer>(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialData: InAppWebViewInitialData(data: """ - - - - - - - InAppWebViewAjaxTest - - -

InAppWebViewAjaxTest

- - - - """), - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptAjaxRequest: true, - )), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - shouldInterceptAjaxRequest: (controller, ajaxRequest) async { - assert(ajaxRequest.data == "firstname=Foo&lastname=Bar"); - - ajaxRequest.responseType = 'json'; - ajaxRequest.data = "firstname=Foo2&lastname=Bar2"; - shouldInterceptAjaxPostRequestCompleter.complete(controller); - return ajaxRequest; - }, - onAjaxReadyStateChange: (controller, ajaxRequest) async { - if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && - ajaxRequest.status == 200) { - Map res = ajaxRequest.response; - onAjaxReadyStateChangeCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, - onAjaxProgress: (controller, ajaxRequest) async { - if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { - Map res = ajaxRequest.response; - onAjaxProgressCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, - ), - ), - ); - - await shouldInterceptAjaxPostRequestCompleter.future; - final Map onAjaxReadyStateChangeValue = - await onAjaxReadyStateChangeCompleter.future; - final Map onAjaxProgressValue = - await onAjaxProgressCompleter.future; - - expect( - mapEquals(onAjaxReadyStateChangeValue, - {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); - expect( - mapEquals( - onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); - }); - - testWidgets('send json data', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer shouldInterceptAjaxPostRequestCompleter = - Completer(); - final Completer> onAjaxReadyStateChangeCompleter = - Completer>(); - final Completer> onAjaxProgressCompleter = - Completer>(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialData: InAppWebViewInitialData(data: """ - - - - - - - InAppWebViewAjaxTest - - -

InAppWebViewAjaxTest

- - - - """), - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - 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"')); - - ajaxRequest.responseType = 'json'; - ajaxRequest.data = '{"firstname": "Foo2", "lastname": "Bar2"}'; - shouldInterceptAjaxPostRequestCompleter.complete(controller); - return ajaxRequest; - }, - onAjaxReadyStateChange: (controller, ajaxRequest) async { - if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && - ajaxRequest.status == 200) { - Map res = ajaxRequest.response; - onAjaxReadyStateChangeCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, - onAjaxProgress: (controller, ajaxRequest) async { - if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { - Map res = ajaxRequest.response; - onAjaxProgressCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, - ), - ), - ); - - await shouldInterceptAjaxPostRequestCompleter.future; - final Map onAjaxReadyStateChangeValue = - await onAjaxReadyStateChangeCompleter.future; - final Map onAjaxProgressValue = - await onAjaxProgressCompleter.future; - - expect( - mapEquals(onAjaxReadyStateChangeValue, - {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); - expect( - mapEquals( - onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); - }); - - testWidgets('send URLSearchParams data', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer shouldInterceptAjaxPostRequestCompleter = - Completer(); - final Completer> onAjaxReadyStateChangeCompleter = - Completer>(); - final Completer> onAjaxProgressCompleter = - Completer>(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialData: InAppWebViewInitialData(data: """ - - - - - - - InAppWebViewAjaxTest - - -

InAppWebViewAjaxTest

- - - - """), - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptAjaxRequest: true, - )), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - shouldInterceptAjaxRequest: (controller, ajaxRequest) async { - assert(ajaxRequest.data == "firstname=Foo&lastname=Bar"); - - ajaxRequest.responseType = 'json'; - ajaxRequest.data = "firstname=Foo2&lastname=Bar2"; - shouldInterceptAjaxPostRequestCompleter.complete(controller); - return ajaxRequest; - }, - onAjaxReadyStateChange: (controller, ajaxRequest) async { - if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && - ajaxRequest.status == 200) { - Map res = ajaxRequest.response; - onAjaxReadyStateChangeCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, - onAjaxProgress: (controller, ajaxRequest) async { - if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { - Map res = ajaxRequest.response; - onAjaxProgressCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, - ), - ), - ); - - await shouldInterceptAjaxPostRequestCompleter.future; - final Map onAjaxReadyStateChangeValue = - await onAjaxReadyStateChangeCompleter.future; - final Map onAjaxProgressValue = - await onAjaxProgressCompleter.future; - - expect( - mapEquals(onAjaxReadyStateChangeValue, - {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); - expect( - mapEquals( - onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); - }); - - testWidgets('send FormData', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer shouldInterceptAjaxPostRequestCompleter = - Completer(); - final Completer> onAjaxReadyStateChangeCompleter = - Completer>(); - final Completer> onAjaxProgressCompleter = - Completer>(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialData: InAppWebViewInitialData(data: """ - - - - - - - InAppWebViewAjaxTest - - -

InAppWebViewAjaxTest

- - - - """), - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptAjaxRequest: true, - )), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - shouldInterceptAjaxRequest: (controller, ajaxRequest) async { - assert(ajaxRequest.data != null); - - var body = ajaxRequest.data.cast(); - var bodyString = String.fromCharCodes(body); - assert(bodyString.indexOf("WebKitFormBoundary") >= 0); - - ajaxRequest.data = utf8.encode(bodyString - .replaceFirst("Foo", "Foo2") - .replaceFirst("Bar", "Bar2")); - ajaxRequest.responseType = 'json'; - shouldInterceptAjaxPostRequestCompleter.complete(controller); - return ajaxRequest; - }, - onAjaxReadyStateChange: (controller, ajaxRequest) async { - if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && - ajaxRequest.status == 200) { - Map res = ajaxRequest.response; - onAjaxReadyStateChangeCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, - onAjaxProgress: (controller, ajaxRequest) async { - if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { - Map res = ajaxRequest.response; - onAjaxProgressCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, - ), - ), - ); - - await shouldInterceptAjaxPostRequestCompleter.future; - final Map onAjaxReadyStateChangeValue = - await onAjaxReadyStateChangeCompleter.future; - final Map onAjaxProgressValue = - await onAjaxProgressCompleter.future; - - expect( - mapEquals(onAjaxReadyStateChangeValue, - {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); - expect( - mapEquals( - onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); - }); - }); group('intercept fetch request', () { testWidgets('send string data', (WidgetTester tester) async { @@ -1091,112 +536,7 @@ void main() { expect(consoleMessage.messageLevel, ConsoleMessageLevel.LOG); }); - group("WebView Windows", () { - testWidgets('onCreateWindow return false', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialFile: - "test_assets/in_app_webview_on_create_window_test.html", - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - clearCache: true, - javaScriptCanOpenWindowsAutomatically: true, - )), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - onLoadStop: (controller, url) { - if (url!.toString() == "https://flutter.dev/") { - pageLoaded.complete(); - } - }, - onCreateWindow: (controller, createNavigationAction) async { - controller.loadUrl(urlRequest: createNavigationAction.request); - return false; - }, - ), - ), - ); - await expectLater(pageLoaded.future, completes); - }); - - testWidgets('onCreateWindow return true', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer onCreateWindowCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialFile: - "test_assets/in_app_webview_on_create_window_test.html", - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - clearCache: true, - javaScriptCanOpenWindowsAutomatically: true, - ), - android: - AndroidInAppWebViewOptions(supportMultipleWindows: true)), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - onCreateWindow: (controller, createNavigationAction) async { - onCreateWindowCompleter - .complete(createNavigationAction.windowId); - return true; - }, - ), - ), - ); - - var windowId = await onCreateWindowCompleter.future; - - final Completer windowControllerCompleter = - Completer(); - final Completer windowPageLoaded = Completer(); - final Completer onCloseWindowCompleter = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - windowId: windowId, - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - clearCache: true, - )), - onWebViewCreated: (controller) { - windowControllerCompleter.complete(controller); - }, - onLoadStop: (controller, url) async { - if (url!.scheme != "about" && !windowPageLoaded.isCompleted) { - windowPageLoaded.complete(url.toString()); - await controller.evaluateJavascript( - source: "window.close();"); - } - }, - onCloseWindow: (controller) { - onCloseWindowCompleter.complete(); - }, - ), - ), - ); - - final String windowUrlLoaded = await windowPageLoaded.future; - - expect(windowUrlLoaded, "https://flutter.dev/"); - await expectLater(onCloseWindowCompleter.future, completes); - }); - }); testWidgets('onFindResultReceived', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); diff --git a/lib/src/in_app_webview/webview.dart b/lib/src/in_app_webview/webview.dart index d64725c0..f882c246 100644 --- a/lib/src/in_app_webview/webview.dart +++ b/lib/src/in_app_webview/webview.dart @@ -204,7 +204,7 @@ abstract class WebView { ///Also, note that calling [InAppWebViewController.setSettings] method using the controller of the new created WebView, ///it will update also the WebView options of the caller WebView. /// - ///**NOTE for Web**: this event will be called only if the iframe has the same origin. + ///**NOTE for Web**: this event will be called only if the iframe has the same origin. It works only for `window.open()` javascript calls. ///Also, there is no way to block the opening the window in a synchronous way, so returning `true` will just close it quickly. /// ///**Supported Platforms/Implementations**: