Skip to content

Commit 1923b69

Browse files
committed
Add javascript panel interface for wkwebview
1 parent b61735b commit 1923b69

19 files changed

+791
-23
lines changed

packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.11.0
2+
3+
* Adds support to show JavaScript dialog. See `PlatformWebViewController.setOnJavaScriptAlertDialog`, `PlatformWebViewController.setOnJavaScriptConfirmDialog` and `PlatformWebViewController.setOnJavaScriptTextInputDialog`.
4+
15
## 3.10.1
26

37
* Fixes new lint warnings.

packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,38 @@ const String kLogExamplePage = '''
108108
</html>
109109
''';
110110

111+
const String kAlertTestPage = '''
112+
<!DOCTYPE html>
113+
<html>
114+
<head>
115+
<script type = "text/javascript">
116+
function showAlert(text) {
117+
alert(text);
118+
}
119+
120+
function showConfirm(text) {
121+
var result = confirm(text);
122+
alert(result);
123+
}
124+
125+
function showPrompt(text, defaultText) {
126+
var inputString = prompt('Enter input', 'Default text');
127+
alert(inputString);
128+
}
129+
</script>
130+
</head>
131+
132+
<body>
133+
<p> Click the following button to see the effect </p>
134+
<form>
135+
<input type = "button" value = "Alert" onclick = "showAlert('Test Alert');" />
136+
<input type = "button" value = "Confirm" onclick = "showConfirm('Test Confirm');" />
137+
<input type = "button" value = "Prompt" onclick = "showPrompt('Test Prompt', 'Default Value');" />
138+
</form>
139+
</body>
140+
</html>
141+
''';
142+
111143
class WebViewExample extends StatefulWidget {
112144
const WebViewExample({super.key, this.cookieManager});
113145

@@ -297,6 +329,7 @@ enum MenuOptions {
297329
setCookie,
298330
logExample,
299331
basicAuthentication,
332+
javaScriptAlert,
300333
}
301334

302335
class SampleMenu extends StatelessWidget {
@@ -348,6 +381,8 @@ class SampleMenu extends StatelessWidget {
348381
_onLogExample();
349382
case MenuOptions.basicAuthentication:
350383
_promptForUrl(context);
384+
case MenuOptions.javaScriptAlert:
385+
_onJavaScriptAlertExample(context);
351386
}
352387
},
353388
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
@@ -412,6 +447,10 @@ class SampleMenu extends StatelessWidget {
412447
value: MenuOptions.basicAuthentication,
413448
child: Text('Basic Authentication Example'),
414449
),
450+
const PopupMenuItem<MenuOptions>(
451+
value: MenuOptions.javaScriptAlert,
452+
child: Text('JavaScript Alert Example'),
453+
),
415454
],
416455
);
417456
}
@@ -536,6 +575,28 @@ class SampleMenu extends StatelessWidget {
536575
return webViewController.loadHtmlString(kTransparentBackgroundPage);
537576
}
538577

578+
Future<void> _onJavaScriptAlertExample(BuildContext context) {
579+
webViewController.setOnJavaScriptAlertDialog(
580+
(JavaScriptAlertDialogRequest request) async {
581+
await _showAlert(context, request.message);
582+
});
583+
584+
webViewController.setOnJavaScriptConfirmDialog(
585+
(JavaScriptConfirmDialogRequest request) async {
586+
final bool result = await _showConfirm(context, request.message);
587+
return result;
588+
});
589+
590+
webViewController.setOnJavaScriptTextInputDialog(
591+
(JavaScriptTextInputDialogRequest request) async {
592+
final String result =
593+
await _showTextInput(context, request.message, request.defaultText);
594+
return result;
595+
});
596+
597+
return webViewController.loadHtmlString(kAlertTestPage);
598+
}
599+
539600
Widget _getCookieList(String cookies) {
540601
if (cookies == '""') {
541602
return Container();
@@ -605,6 +666,67 @@ class SampleMenu extends StatelessWidget {
605666
},
606667
);
607668
}
669+
670+
Future<void> _showAlert(BuildContext context, String message) async {
671+
return showDialog<void>(
672+
context: context,
673+
builder: (BuildContext ctx) {
674+
return AlertDialog(
675+
content: Text(message),
676+
actions: <Widget>[
677+
TextButton(
678+
onPressed: () {
679+
Navigator.of(ctx).pop();
680+
},
681+
child: const Text('OK'))
682+
],
683+
);
684+
});
685+
}
686+
687+
Future<bool> _showConfirm(BuildContext context, String message) async {
688+
final dynamic result = await showDialog<bool>(
689+
context: context,
690+
builder: (BuildContext ctx) {
691+
return AlertDialog(
692+
content: Text(message),
693+
actions: <Widget>[
694+
TextButton(
695+
onPressed: () {
696+
Navigator.of(ctx).pop(false);
697+
},
698+
child: const Text('Cancel')),
699+
TextButton(
700+
onPressed: () {
701+
Navigator.of(ctx).pop(true);
702+
},
703+
child: const Text('OK')),
704+
],
705+
);
706+
});
707+
708+
return result.runtimeType == bool && result as bool;
709+
}
710+
711+
Future<String> _showTextInput(
712+
BuildContext context, String message, String? defaultText) async {
713+
final dynamic result = await showDialog<String>(
714+
context: context,
715+
builder: (BuildContext ctx) {
716+
return AlertDialog(
717+
content: Text(message),
718+
actions: <Widget>[
719+
TextButton(
720+
onPressed: () {
721+
Navigator.of(ctx).pop('Text test');
722+
},
723+
child: const Text('Enter')),
724+
],
725+
);
726+
});
727+
728+
return result.runtimeType == String ? result as String : '';
729+
}
608730
}
609731

610732
class NavigationControls extends StatelessWidget {

packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ dependencies:
1010
flutter:
1111
sdk: flutter
1212
path_provider: ^2.0.6
13-
webview_flutter_platform_interface: ^2.7.0
13+
webview_flutter_platform_interface: ^2.9.0
1414
webview_flutter_wkwebview:
1515
# When depending on this package from a real application you should use:
1616
# webview_flutter: ^x.y.z
@@ -31,4 +31,4 @@ flutter:
3131
- assets/sample_audio.ogg
3232
- assets/sample_video.mp4
3333
- assets/www/index.html
34-
- assets/www/styles/style.css
34+
- assets/www/styles/style.css

packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData(
167167

168168
FWFNSUrlRequestData *FWFNSUrlRequestDataFromNativeNSURLRequest(NSURLRequest *request) {
169169
return [FWFNSUrlRequestData
170-
makeWithUrl:request.URL.absoluteString
170+
makeWithUrl:request.URL.absoluteString.length > 0 ? request.URL.absoluteString : @""
171171
httpMethod:request.HTTPMethod
172172
httpBody:request.HTTPBody
173173
? [FlutterStandardTypedData typedDataWithBytes:request.HTTPBody]
@@ -176,7 +176,9 @@ WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData(
176176
}
177177

178178
FWFWKFrameInfoData *FWFWKFrameInfoDataFromNativeWKFrameInfo(WKFrameInfo *info) {
179-
return [FWFWKFrameInfoData makeWithIsMainFrame:info.isMainFrame];
179+
return [FWFWKFrameInfoData
180+
makeWithIsMainFrame:info.isMainFrame
181+
request:FWFNSUrlRequestDataFromNativeNSURLRequest(info.request)];
180182
}
181183

182184
WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData(

packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,8 +464,9 @@ typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) {
464464
@interface FWFWKFrameInfoData : NSObject
465465
/// `init` unavailable to enforce nonnull fields, see the `make` class method.
466466
- (instancetype)init NS_UNAVAILABLE;
467-
+ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame;
467+
+ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame request:(FWFNSUrlRequestData *)request;
468468
@property(nonatomic, assign) BOOL isMainFrame;
469+
@property(nonatomic, strong) FWFNSUrlRequestData *request;
469470
@end
470471

471472
/// Mirror of NSError.
@@ -949,6 +950,27 @@ NSObject<FlutterMessageCodec> *FWFWKUIDelegateFlutterApiGetCodec(void);
949950
(void (^)(
950951
FWFWKPermissionDecisionData *_Nullable,
951952
FlutterError *_Nullable))completion;
953+
/// Callback to Dart function `WKUIDelegate.runJavaScriptAlertPanel`.
954+
- (void)runJavaScriptAlertPanelForDelegateWithIdentifier:(NSInteger)identifier
955+
message:(NSString *)message
956+
frame:(FWFWKFrameInfoData *)frame
957+
completion:
958+
(void (^)(FlutterError *_Nullable))completion;
959+
/// Callback to Dart function `WKUIDelegate.runJavaScriptConfirmPanel`.
960+
- (void)runJavaScriptConfirmPanelForDelegateWithIdentifier:(NSInteger)identifier
961+
message:(NSString *)message
962+
frame:(FWFWKFrameInfoData *)frame
963+
completion:
964+
(void (^)(NSNumber *_Nullable,
965+
FlutterError *_Nullable))completion;
966+
/// Callback to Dart function `WKUIDelegate.runJavaScriptTextInputPanel`.
967+
- (void)runJavaScriptTextInputPanelForDelegateWithIdentifier:(NSInteger)identifier
968+
prompt:(NSString *)prompt
969+
defaultText:(NSString *)defaultText
970+
frame:(FWFWKFrameInfoData *)frame
971+
completion:
972+
(void (^)(NSString *_Nullable,
973+
FlutterError *_Nullable))completion;
952974
@end
953975

954976
/// The codec used by FWFWKHttpCookieStoreHostApi.

packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,14 +609,16 @@ - (NSArray *)toList {
609609
@end
610610

611611
@implementation FWFWKFrameInfoData
612-
+ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame {
612+
+ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame request:(FWFNSUrlRequestData *)request {
613613
FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init];
614614
pigeonResult.isMainFrame = isMainFrame;
615+
pigeonResult.request = request;
615616
return pigeonResult;
616617
}
617618
+ (FWFWKFrameInfoData *)fromList:(NSArray *)list {
618619
FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init];
619620
pigeonResult.isMainFrame = [GetNullableObjectAtIndex(list, 0) boolValue];
621+
pigeonResult.request = [FWFNSUrlRequestData nullableFromList:(GetNullableObjectAtIndex(list, 1))];
620622
return pigeonResult;
621623
}
622624
+ (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list {
@@ -625,6 +627,7 @@ + (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list {
625627
- (NSArray *)toList {
626628
return @[
627629
@(self.isMainFrame),
630+
(self.request ? [self.request toList] : [NSNull null]),
628631
];
629632
}
630633
@end
@@ -3098,6 +3101,99 @@ - (void)requestMediaCapturePermissionForDelegateWithIdentifier:(NSInteger)arg_id
30983101
}
30993102
}];
31003103
}
3104+
- (void)runJavaScriptAlertPanelForDelegateWithIdentifier:(NSInteger)arg_identifier
3105+
message:(NSString *)arg_message
3106+
frame:(FWFWKFrameInfoData *)arg_frame
3107+
completion:
3108+
(void (^)(FlutterError *_Nullable))completion {
3109+
FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel
3110+
messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi."
3111+
@"runJavaScriptAlertPanel"
3112+
binaryMessenger:self.binaryMessenger
3113+
codec:FWFWKUIDelegateFlutterApiGetCodec()];
3114+
[channel
3115+
sendMessage:@[ @(arg_identifier), arg_message ?: [NSNull null], arg_frame ?: [NSNull null] ]
3116+
reply:^(NSArray<id> *reply) {
3117+
if (reply != nil) {
3118+
if (reply.count > 1) {
3119+
completion([FlutterError errorWithCode:reply[0]
3120+
message:reply[1]
3121+
details:reply[2]]);
3122+
} else {
3123+
completion(nil);
3124+
}
3125+
} else {
3126+
completion([FlutterError errorWithCode:@"channel-error"
3127+
message:@"Unable to establish connection on channel."
3128+
details:@""]);
3129+
}
3130+
}];
3131+
}
3132+
- (void)runJavaScriptConfirmPanelForDelegateWithIdentifier:(NSInteger)arg_identifier
3133+
message:(NSString *)arg_message
3134+
frame:(FWFWKFrameInfoData *)arg_frame
3135+
completion:
3136+
(void (^)(NSNumber *_Nullable,
3137+
FlutterError *_Nullable))completion {
3138+
FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel
3139+
messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi."
3140+
@"runJavaScriptConfirmPanel"
3141+
binaryMessenger:self.binaryMessenger
3142+
codec:FWFWKUIDelegateFlutterApiGetCodec()];
3143+
[channel
3144+
sendMessage:@[ @(arg_identifier), arg_message ?: [NSNull null], arg_frame ?: [NSNull null] ]
3145+
reply:^(NSArray<id> *reply) {
3146+
if (reply != nil) {
3147+
if (reply.count > 1) {
3148+
completion(nil, [FlutterError errorWithCode:reply[0]
3149+
message:reply[1]
3150+
details:reply[2]]);
3151+
} else {
3152+
NSNumber *output = reply[0] == [NSNull null] ? nil : reply[0];
3153+
completion(output, nil);
3154+
}
3155+
} else {
3156+
completion(nil,
3157+
[FlutterError errorWithCode:@"channel-error"
3158+
message:@"Unable to establish connection on channel."
3159+
details:@""]);
3160+
}
3161+
}];
3162+
}
3163+
- (void)runJavaScriptTextInputPanelForDelegateWithIdentifier:(NSInteger)arg_identifier
3164+
prompt:(NSString *)arg_prompt
3165+
defaultText:(NSString *)arg_defaultText
3166+
frame:(FWFWKFrameInfoData *)arg_frame
3167+
completion:(void (^)(NSString *_Nullable,
3168+
FlutterError *_Nullable))
3169+
completion {
3170+
FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel
3171+
messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi."
3172+
@"runJavaScriptTextInputPanel"
3173+
binaryMessenger:self.binaryMessenger
3174+
codec:FWFWKUIDelegateFlutterApiGetCodec()];
3175+
[channel sendMessage:@[
3176+
@(arg_identifier), arg_prompt ?: [NSNull null], arg_defaultText ?: [NSNull null],
3177+
arg_frame ?: [NSNull null]
3178+
]
3179+
reply:^(NSArray<id> *reply) {
3180+
if (reply != nil) {
3181+
if (reply.count > 1) {
3182+
completion(nil, [FlutterError errorWithCode:reply[0]
3183+
message:reply[1]
3184+
details:reply[2]]);
3185+
} else {
3186+
NSString *output = reply[0] == [NSNull null] ? nil : reply[0];
3187+
completion(output, nil);
3188+
}
3189+
} else {
3190+
completion(nil, [FlutterError
3191+
errorWithCode:@"channel-error"
3192+
message:@"Unable to establish connection on channel."
3193+
details:@""]);
3194+
}
3195+
}];
3196+
}
31013197
@end
31023198

31033199
@interface FWFWKHttpCookieStoreHostApiCodecReader : FlutterStandardReader

0 commit comments

Comments
 (0)