diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index 1a85bc8a53e5..ee1dde4fc62f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.14 + +* Fixed platform exception being thrown on iOS 14+ when evaluating any JavaScript evaluating to null or undefined. + ## 2.0.13 * Extract WKWebView implementation from `webview_flutter`. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m index 631c4a105063..0d6bd1ea4012 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m @@ -88,4 +88,129 @@ - (void)testContentInsetsSumAlwaysZeroAfterSetFrame { } } +- (void)testEvaluateJavaScriptShouldCallCallAsyncJavaScriptIOS14AndAbove { + if (@available(iOS 14, *)) { + // Setup + FLTWebViewController *controller = + [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) + viewIdentifier:1 + arguments:nil + binaryMessenger:self.mockBinaryMessenger]; + FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class); + [OCMStub([mockView callAsyncJavaScript:[OCMArg any] + arguments:[OCMArg any] + inFrame:[OCMArg any] + inContentWorld:[OCMArg any] + completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) { + // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668 + __unsafe_unretained void (^evalResultHandler)(id, NSError *); + [invocation getArgument:&evalResultHandler atIndex:6]; + evalResultHandler(@"RESULT", nil); + }]; + controller.webView = mockView; + XCTestExpectation *resultExpectation = [self + expectationWithDescription:@"Should return successful result over the method channel."]; + + // Run + [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"evaluateJavascript" + arguments:@"console.log('test')"] + result:^(id _Nullable result) { + XCTAssertTrue([@"RESULT" isEqualToString:result]); + [resultExpectation fulfill]; + }]; + + // Verify + OCMVerify([mockView + callAsyncJavaScript:[OCMArg isEqual:@"return eval(\"console.log('test')\");"] + arguments:[OCMArg any] + inFrame:[OCMArg any] + inContentWorld:[OCMArg any] + completionHandler:[OCMArg any]]); + OCMReject([mockView evaluateJavaScript:[OCMArg any] completionHandler:[OCMArg any]]); + [self waitForExpectationsWithTimeout:30.0 handler:nil]; + } +} + +- (void)testEvaluateJavaScriptShouldCallEvaluateJavaScriptBelowIOS14 { + if (@available(iOS 14, *)) { + } else { + // Setup + FLTWebViewController *controller = + [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) + viewIdentifier:1 + arguments:nil + binaryMessenger:self.mockBinaryMessenger]; + FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class); + [OCMStub([mockView evaluateJavaScript:[OCMArg any] + completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) { + // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668 + __unsafe_unretained void (^evalResultHandler)(id, NSError *); + [invocation getArgument:&evalResultHandler atIndex:3]; + evalResultHandler(@"RESULT", nil); + }]; + controller.webView = mockView; + XCTestExpectation *resultExpectation = [self + expectationWithDescription:@"Should return successful result over the method channel."]; + + // Run + [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"evaluateJavascript" + arguments:@"console.log('test')"] + result:^(id _Nullable result) { + XCTAssertTrue([@"RESULT" isEqualToString:result]); + [resultExpectation fulfill]; + }]; + + // Verify + OCMVerify([mockView evaluateJavaScript:@"console.log('test')" completionHandler:[OCMArg any]]); + [self waitForExpectationsWithTimeout:30.0 handler:nil]; + } +} + +- (void)testEvaluateJavaScriptShouldSendErrorResultOnError { + // Setup + FLTWebViewController *controller = + [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) + viewIdentifier:1 + arguments:nil + binaryMessenger:self.mockBinaryMessenger]; + NSError *testError = [NSError errorWithDomain:@"" + code:1 + userInfo:@{NSLocalizedDescriptionKey : @"Test Error"}]; + FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class); + XCTestExpectation *resultExpectation = + [self expectationWithDescription:@"Should return error result over the method channel."]; + if (@available(iOS 14, *)) { + [OCMStub([mockView callAsyncJavaScript:[OCMArg any] + arguments:[OCMArg any] + inFrame:[OCMArg any] + inContentWorld:[OCMArg any] + completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) { + // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668 + __unsafe_unretained void (^evalResultHandler)(id, NSError *); + [invocation getArgument:&evalResultHandler atIndex:6]; + evalResultHandler(nil, testError); + }]; + } else { + [OCMStub([mockView evaluateJavaScript:[OCMArg any] + completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) { + // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668 + __unsafe_unretained void (^evalResultHandler)(id, NSError *); + [invocation getArgument:&evalResultHandler atIndex:3]; + evalResultHandler(nil, testError); + }]; + } + controller.webView = mockView; + + // Run + [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"evaluateJavascript" + arguments:@"console.log('test')"] + result:^(id _Nullable result) { + XCTAssertTrue([result class] == [FlutterError class]); + [resultExpectation fulfill]; + }]; + + // Verify + [self waitForExpectationsWithTimeout:30.0 handler:nil]; +} + @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h index 6e795f7d1528..db52124d6a7c 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h @@ -7,26 +7,30 @@ NS_ASSUME_NONNULL_BEGIN +/** + * The WkWebView used for the plugin. + * + * This class overrides some methods in `WKWebView` to serve the needs for the plugin. + */ +@interface FLTWKWebView : WKWebView +@end + @interface FLTWebViewController : NSObject +@property(nonatomic) FLTWKWebView* webView; + - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args binaryMessenger:(NSObject*)messenger; - (UIView*)view; + +- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; @end @interface FLTWebViewFactory : NSObject - (instancetype)initWithMessenger:(NSObject*)messenger; @end -/** - * The WkWebView used for the plugin. - * - * This class overrides some methods in `WKWebView` to serve the needs for the plugin. - */ -@interface FLTWKWebView : WKWebView -@end - NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m index c6d926d3cfc2..4b03e6416ce1 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m @@ -222,6 +222,14 @@ - (void)onCurrentUrl:(FlutterMethodCall*)call result:(FlutterResult)result { result(_currentUrl); } +- (NSString*)escapeStringForJavaScript:(NSString*)string { + NSArray* arrayForEncoding = @[ string ]; + NSString* jsonString = [[NSString alloc] + initWithData:[NSJSONSerialization dataWithJSONObject:arrayForEncoding options:0 error:nil] + encoding:NSUTF8StringEncoding]; + return [jsonString substringWithRange:NSMakeRange(2, jsonString.length - 4)]; +} + - (void)onEvaluateJavaScript:(FlutterMethodCall*)call result:(FlutterResult)result { NSString* jsString = [call arguments]; if (!jsString) { @@ -230,18 +238,31 @@ - (void)onEvaluateJavaScript:(FlutterMethodCall*)call result:(FlutterResult)resu details:nil]); return; } - [_webView evaluateJavaScript:jsString - completionHandler:^(_Nullable id evaluateResult, NSError* _Nullable error) { - if (error) { - result([FlutterError - errorWithCode:@"evaluateJavaScript_failed" - message:@"Failed evaluating JavaScript" - details:[NSString stringWithFormat:@"JavaScript string was: '%@'\n%@", - jsString, error]]); - } else { - result([NSString stringWithFormat:@"%@", evaluateResult]); - } - }]; + void (^evalResultHandler)(id, NSError*) = ^(_Nullable id evaluateResult, + NSError* _Nullable error) { + if (error) { + result([FlutterError + errorWithCode:@"evaluateJavaScript_failed" + message:@"Failed evaluating JavaScript" + details:[NSString + stringWithFormat:@"JavaScript string was: '%@'\n%@", jsString, error]]); + } else if (evaluateResult == /*null*/ [NSNull null] || evaluateResult == /*undefined*/ nil) { + result(@"null"); + } else { + result([NSString stringWithFormat:@"%@", evaluateResult]); + } + }; + if (@available(iOS 14, *)) { + jsString = [NSString + stringWithFormat:@"return eval(\"%@\");", [self escapeStringForJavaScript:jsString]]; + [_webView callAsyncJavaScript:jsString + arguments:nil + inFrame:nil + inContentWorld:WKContentWorld.pageWorld + completionHandler:evalResultHandler]; + } else { + [_webView evaluateJavaScript:jsString completionHandler:evalResultHandler]; + } } - (void)onAddJavaScriptChannels:(FlutterMethodCall*)call result:(FlutterResult)result { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index c6f6d6f94f07..a7305cea7a94 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.0.13 +version: 2.0.14 environment: sdk: ">=2.14.0 <3.0.0"