Skip to content

[webview_flutter_wkwebview] Add javascript panel interface for wkwebview #5795

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all 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.3

* Adds a check that throws an `ArgumentError` when `WebKitWebViewController.addJavaScriptChannel`
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 == nil ? @"" : request.URL.absoluteString
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