Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.11.0

* Adds support to show JavaScript dialog. See `PlatformWebViewController.setOnJavaScriptAlertDialog`, `PlatformWebViewController.setOnJavaScriptConfirmDialog` and `PlatformWebViewController.setOnJavaScriptTextInputDialog`.

## 3.10.1

* Fixes new lint warnings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,98 @@ Future<void> main() async {
},
);

testWidgets('can receive JavaScript alert dialogs',
(WidgetTester tester) async {
final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
);

final Completer<String> alertMessage = Completer<String>();
unawaited(controller.setOnJavaScriptAlertDialog(
(JavaScriptAlertDialogRequest request) async {
alertMessage.complete(request.message);
},
));

unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted));
unawaited(
controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))),
);

await tester.pumpWidget(Builder(
builder: (BuildContext context) {
return PlatformWebViewWidget(
PlatformWebViewWidgetCreationParams(controller: controller),
).build(context);
},
));

await controller.runJavaScript('alert("alert message")');
await expectLater(alertMessage.future, completion('alert message'));
});

testWidgets('can receive JavaScript confirm dialogs',
(WidgetTester tester) async {
final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
);

final Completer<String> confirmMessage = Completer<String>();
unawaited(controller.setOnJavaScriptConfirmDialog(
(JavaScriptConfirmDialogRequest request) async {
confirmMessage.complete(request.message);
return true;
},
));

unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted));
unawaited(
controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))),
);

await tester.pumpWidget(Builder(
builder: (BuildContext context) {
return PlatformWebViewWidget(
PlatformWebViewWidgetCreationParams(controller: controller),
).build(context);
},
));

await controller.runJavaScript('confirm("confirm message")');
await expectLater(confirmMessage.future, completion('confirm message'));
});

testWidgets('can receive JavaScript prompt dialogs',
(WidgetTester tester) async {
final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
);

unawaited(controller.setOnJavaScriptTextInputDialog(
(JavaScriptTextInputDialogRequest request) async {
return 'return message';
},
));

unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted));
unawaited(
controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))),
);

await tester.pumpWidget(Builder(
builder: (BuildContext context) {
return PlatformWebViewWidget(
PlatformWebViewWidgetCreationParams(controller: controller),
).build(context);
},
));

final Object promptResponse = await controller.runJavaScriptReturningResult(
'prompt("input message", "default text")',
);
expect(promptResponse, 'return message');
});

group('Logging', () {
testWidgets('can receive console log messages',
(WidgetTester tester) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,38 @@ const String kLogExamplePage = '''
</html>
''';

const String kAlertTestPage = '''
<!DOCTYPE html>
<html>
<head>
<script type = "text/javascript">
function showAlert(text) {
alert(text);
}

function showConfirm(text) {
var result = confirm(text);
alert(result);
}

function showPrompt(text, defaultText) {
var inputString = prompt('Enter input', 'Default text');
alert(inputString);
}
</script>
</head>

<body>
<p> Click the following button to see the effect </p>
<form>
<input type = "button" value = "Alert" onclick = "showAlert('Test Alert');" />
<input type = "button" value = "Confirm" onclick = "showConfirm('Test Confirm');" />
<input type = "button" value = "Prompt" onclick = "showPrompt('Test Prompt', 'Default Value');" />
</form>
</body>
</html>
''';

class WebViewExample extends StatefulWidget {
const WebViewExample({super.key, this.cookieManager});

Expand Down Expand Up @@ -297,6 +329,7 @@ enum MenuOptions {
setCookie,
logExample,
basicAuthentication,
javaScriptAlert,
}

class SampleMenu extends StatelessWidget {
Expand Down Expand Up @@ -348,6 +381,8 @@ class SampleMenu extends StatelessWidget {
_onLogExample();
case MenuOptions.basicAuthentication:
_promptForUrl(context);
case MenuOptions.javaScriptAlert:
_onJavaScriptAlertExample(context);
}
},
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
Expand Down Expand Up @@ -412,6 +447,10 @@ class SampleMenu extends StatelessWidget {
value: MenuOptions.basicAuthentication,
child: Text('Basic Authentication Example'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.javaScriptAlert,
child: Text('JavaScript Alert Example'),
),
],
);
}
Expand Down Expand Up @@ -536,6 +575,28 @@ class SampleMenu extends StatelessWidget {
return webViewController.loadHtmlString(kTransparentBackgroundPage);
}

Future<void> _onJavaScriptAlertExample(BuildContext context) {
webViewController.setOnJavaScriptAlertDialog(
(JavaScriptAlertDialogRequest request) async {
await _showAlert(context, request.message);
});

webViewController.setOnJavaScriptConfirmDialog(
(JavaScriptConfirmDialogRequest request) async {
final bool result = await _showConfirm(context, request.message);
return result;
});

webViewController.setOnJavaScriptTextInputDialog(
(JavaScriptTextInputDialogRequest request) async {
final String result =
await _showTextInput(context, request.message, request.defaultText);
return result;
});

return webViewController.loadHtmlString(kAlertTestPage);
}

Widget _getCookieList(String cookies) {
if (cookies == '""') {
return Container();
Expand Down Expand Up @@ -605,6 +666,65 @@ class SampleMenu extends StatelessWidget {
},
);
}

Future<void> _showAlert(BuildContext context, String message) async {
return showDialog<void>(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: const Text('OK'))
],
);
});
}

Future<bool> _showConfirm(BuildContext context, String message) async {
return await showDialog<bool>(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(ctx).pop(false);
},
child: const Text('Cancel')),
TextButton(
onPressed: () {
Navigator.of(ctx).pop(true);
},
child: const Text('OK')),
],
);
}) ??
false;
}

Future<String> _showTextInput(
BuildContext context, String message, String? defaultText) async {
return await showDialog<String>(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(ctx).pop('Text test');
},
child: const Text('Enter')),
],
);
}) ??
'';
}
}

class NavigationControls extends StatelessWidget {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies:
flutter:
sdk: flutter
path_provider: ^2.0.6
webview_flutter_platform_interface: ^2.7.0
webview_flutter_platform_interface: ^2.9.0
webview_flutter_wkwebview:
# When depending on this package from a real application you should use:
# webview_flutter: ^x.y.z
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData(

FWFNSUrlRequestData *FWFNSUrlRequestDataFromNativeNSURLRequest(NSURLRequest *request) {
return [FWFNSUrlRequestData
makeWithUrl:request.URL.absoluteString
makeWithUrl:request.URL.absoluteString.length > 0 ? request.URL.absoluteString : @""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im a bit confused here - will absoluteString ever be nil? if so, may be less confusing to do nil check rather than length check?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testWidgets('target _blank opens in same window',
(WidgetTester tester) async {
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
final Completer<void> pageLoaded = Completer<void>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
key: GlobalKey(),
onWebViewCreated: (WebViewController controller) {
controllerCompleter.complete(controller);
},
javascriptMode: JavascriptMode.unrestricted,
onPageFinished: (String url) {
pageLoaded.complete(null);
},
),
),
);
final WebViewController controller = await controllerCompleter.future;
await controller.runJavascript('window.open("$primaryUrl", "_blank")');
await pageLoaded.future;
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, primaryUrl);
});

@hellohuanlin
It can be nil with this test case.
I updated code to nil check.
Thanks

httpMethod:request.HTTPMethod
httpBody:request.HTTPBody
? [FlutterStandardTypedData typedDataWithBytes:request.HTTPBody]
Expand All @@ -176,7 +176,9 @@ WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData(
}

FWFWKFrameInfoData *FWFWKFrameInfoDataFromNativeWKFrameInfo(WKFrameInfo *info) {
return [FWFWKFrameInfoData makeWithIsMainFrame:info.isMainFrame];
return [FWFWKFrameInfoData
makeWithIsMainFrame:info.isMainFrame
request:FWFNSUrlRequestDataFromNativeNSURLRequest(info.request)];
}

WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,9 @@ typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) {
@interface FWFWKFrameInfoData : NSObject
/// `init` unavailable to enforce nonnull fields, see the `make` class method.
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame;
+ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame request:(FWFNSUrlRequestData *)request;
@property(nonatomic, assign) BOOL isMainFrame;
@property(nonatomic, strong) FWFNSUrlRequestData *request;
@end

/// Mirror of NSError.
Expand Down Expand Up @@ -949,6 +950,27 @@ NSObject<FlutterMessageCodec> *FWFWKUIDelegateFlutterApiGetCodec(void);
(void (^)(
FWFWKPermissionDecisionData *_Nullable,
FlutterError *_Nullable))completion;
/// Callback to Dart function `WKUIDelegate.runJavaScriptAlertPanel`.
- (void)runJavaScriptAlertPanelForDelegateWithIdentifier:(NSInteger)identifier
message:(NSString *)message
frame:(FWFWKFrameInfoData *)frame
completion:
(void (^)(FlutterError *_Nullable))completion;
/// Callback to Dart function `WKUIDelegate.runJavaScriptConfirmPanel`.
- (void)runJavaScriptConfirmPanelForDelegateWithIdentifier:(NSInteger)identifier
message:(NSString *)message
frame:(FWFWKFrameInfoData *)frame
completion:
(void (^)(NSNumber *_Nullable,
FlutterError *_Nullable))completion;
/// Callback to Dart function `WKUIDelegate.runJavaScriptTextInputPanel`.
- (void)runJavaScriptTextInputPanelForDelegateWithIdentifier:(NSInteger)identifier
prompt:(NSString *)prompt
defaultText:(NSString *)defaultText
frame:(FWFWKFrameInfoData *)frame
completion:
(void (^)(NSString *_Nullable,
FlutterError *_Nullable))completion;
@end

/// The codec used by FWFWKHttpCookieStoreHostApi.
Expand Down
Loading