[webview_flutter] Implementations of `loadFile` and `loadHtmlString` for WKWebView (#4486)

diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
index c1b19fd..cbf1f04 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.4.0
+
+* Implemented new `loadFile` and `loadHtmlString` methods from the platform interface.
+
 ## 2.3.0
 
 * Implemented new `loadRequest` method from platform interface.
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
index 2be87fb..e292b1b 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 46;
+	objectVersion = 50;
 	objects = {
 
 /* Begin PBXBuildFile section */
@@ -273,7 +273,7 @@
 			isa = PBXProject;
 			attributes = {
 				DefaultBuildSystemTypeForWorkspace = Original;
-				LastUpgradeCheck = 1030;
+				LastUpgradeCheck = 1300;
 				ORGANIZATIONNAME = "The Flutter Authors";
 				TargetAttributes = {
 					68BDCAE823C3F7CB00D9C032 = {
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index d7453a8..cb713d7 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1030"
+   LastUpgradeVersion = "1300"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
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 61e43c1..a3c314a 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
@@ -89,6 +89,201 @@
   }
 }
 
+- (void)testLoadFileSucceeds {
+  NSString *testFilePath = @"/assets/file.html";
+  NSURL *url = [NSURL fileURLWithPath:testFilePath isDirectory:NO];
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return successful result over the method channel."];
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
+  controller.webView = mockWebView;
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile"
+                                                             arguments:testFilePath]
+                    result:^(id _Nullable result) {
+                      XCTAssertNil(result);
+                      [resultExpectation fulfill];
+                    }];
+
+  [self waitForExpectations:@[ resultExpectation ] timeout:30.0];
+  OCMVerify([mockWebView loadFileURL:url
+             allowingReadAccessToURL:[url URLByDeletingLastPathComponent]]);
+}
+
+- (void)testLoadFileFailsWithInvalidPath {
+  NSArray *resultExpectations = @[
+    [self expectationWithDescription:@"Should return failed result when argument is nil."],
+    [self expectationWithDescription:
+              @"Should return failed result when argument is not of type NSString*."],
+    [self expectationWithDescription:
+              @"Should return failed result when argument is an empty string."],
+  ];
+
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
+  controller.webView = mockWebView;
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile" arguments:nil]
+                    result:^(id _Nullable result) {
+                      FlutterError *expected =
+                          [FlutterError errorWithCode:@"loadFile_failed"
+                                              message:@"Failed parsing file path."
+                                              details:@"Argument is nil."];
+                      [FLTWebViewTests assertFlutterError:result withExpected:expected];
+                      [resultExpectations[0] fulfill];
+                    }];
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile" arguments:@(10)]
+                    result:^(id _Nullable result) {
+                      FlutterError *expected =
+                          [FlutterError errorWithCode:@"loadFile_failed"
+                                              message:@"Failed parsing file path."
+                                              details:@"Argument is not of type NSString."];
+                      [FLTWebViewTests assertFlutterError:result withExpected:expected];
+                      [resultExpectations[1] fulfill];
+                    }];
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile" arguments:@""]
+                    result:^(id _Nullable result) {
+                      FlutterError *expected =
+                          [FlutterError errorWithCode:@"loadFile_failed"
+                                              message:@"Failed parsing file path."
+                                              details:@"Argument contains an empty string."];
+                      [FLTWebViewTests assertFlutterError:result withExpected:expected];
+                      [resultExpectations[2] fulfill];
+                    }];
+
+  [self waitForExpectations:resultExpectations timeout:1.0];
+  OCMReject([mockWebView loadFileURL:[OCMArg any] allowingReadAccessToURL:[OCMArg any]]);
+}
+
+- (void)testLoadFileSucceedsWithBaseUrl {
+  NSURL *baseUrl = [NSURL URLWithString:@"https://flutter.dev"];
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return successful result over the method channel."];
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
+  controller.webView = mockWebView;
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
+                                                             arguments:@{
+                                                               @"html" : @"some HTML string",
+                                                               @"baseUrl" : @"https://flutter.dev"
+                                                             }]
+                    result:^(id _Nullable result) {
+                      XCTAssertNil(result);
+                      [resultExpectation fulfill];
+                    }];
+
+  [self waitForExpectations:@[ resultExpectation ] timeout:30.0];
+  OCMVerify([mockWebView loadHTMLString:@"some HTML string" baseURL:baseUrl]);
+}
+
+- (void)testLoadFileSucceedsWithoutBaseUrl {
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return successful result over the method channel."];
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
+  controller.webView = mockWebView;
+  [controller
+      onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
+                                                     arguments:@{@"html" : @"some HTML string"}]
+            result:^(id _Nullable result) {
+              XCTAssertNil(result);
+              [resultExpectation fulfill];
+            }];
+
+  [self waitForExpectations:@[ resultExpectation ] timeout:30.0];
+  OCMVerify([mockWebView loadHTMLString:@"some HTML string" baseURL:nil]);
+}
+
+- (void)testLoadHtmlStringFailsWithInvalidArgument {
+  NSArray *resultExpectations = @[
+    [self expectationWithDescription:@"Should return failed result when argument is nil."],
+    [self expectationWithDescription:
+              @"Should return failed result when argument is not of type NSDictionary*."],
+    [self expectationWithDescription:@"Should return failed result when HTML argument is nil."],
+    [self expectationWithDescription:
+              @"Should return failed result when HTML argument is not of type NSString*."],
+    [self expectationWithDescription:
+              @"Should return failed result when HTML argument is an empty string."],
+  ];
+
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
+  controller.webView = mockWebView;
+  FlutterError *expected = [FlutterError
+      errorWithCode:@"loadHtmlString_failed"
+            message:@"Failed parsing arguments."
+            details:@"Arguments should be a dictionary containing at least a 'html' element and "
+                    @"optionally a 'baseUrl' argument. For example: `@{ @\"html\": @\"some html "
+                    @"code\", @\"baseUrl\": @\"https://flutter.dev\" }`"];
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
+                                                             arguments:nil]
+                    result:^(id _Nullable result) {
+                      [FLTWebViewTests assertFlutterError:result withExpected:expected];
+                      [resultExpectations[0] fulfill];
+                    }];
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
+                                                             arguments:@""]
+                    result:^(id _Nullable result) {
+                      [FLTWebViewTests assertFlutterError:result withExpected:expected];
+                      [resultExpectations[1] fulfill];
+                    }];
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
+                                                             arguments:@{}]
+                    result:^(id _Nullable result) {
+                      FlutterError *expected =
+                          [FlutterError errorWithCode:@"loadHtmlString_failed"
+                                              message:@"Failed parsing HTML string argument."
+                                              details:@"Argument is nil."];
+                      [FLTWebViewTests assertFlutterError:result withExpected:expected];
+                      [resultExpectations[2] fulfill];
+                    }];
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
+                                                             arguments:@{
+                                                               @"html" : @(42),
+                                                             }]
+                    result:^(id _Nullable result) {
+                      FlutterError *expected =
+                          [FlutterError errorWithCode:@"loadHtmlString_failed"
+                                              message:@"Failed parsing HTML string argument."
+                                              details:@"Argument is not of type NSString."];
+                      [FLTWebViewTests assertFlutterError:result withExpected:expected];
+                      [resultExpectations[3] fulfill];
+                    }];
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
+                                                             arguments:@{
+                                                               @"html" : @"",
+                                                             }]
+                    result:^(id _Nullable result) {
+                      FlutterError *expected =
+                          [FlutterError errorWithCode:@"loadHtmlString_failed"
+                                              message:@"Failed parsing HTML string argument."
+                                              details:@"Argument contains an empty string."];
+                      [FLTWebViewTests assertFlutterError:result withExpected:expected];
+                      [resultExpectations[4] fulfill];
+                    }];
+
+  [self waitForExpectations:resultExpectations timeout:1.0];
+  OCMReject([mockWebView loadHTMLString:[OCMArg any] baseURL:[OCMArg any]]);
+}
+
 - (void)testRunJavascriptFailsForNullString {
   // Setup
   FLTWebViewController *controller =
@@ -302,6 +497,14 @@
   [self waitForExpectationsWithTimeout:30.0 handler:nil];
 }
 
++ (void)assertFlutterError:(id)actual withExpected:(FlutterError *)expected {
+  XCTAssertTrue([actual class] == [FlutterError class]);
+  FlutterError *errorResult = actual;
+  XCTAssertEqualObjects(errorResult.code, expected.code);
+  XCTAssertEqualObjects(errorResult.message, expected.message);
+  XCTAssertEqualObjects(errorResult.details, expected.details);
+}
+
 - (void)testBuildNSURLRequestReturnsNilForNonDictionaryValue {
   // Setup
   FLTWebViewController *controller =
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
index ea95cdd..72168ec 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
@@ -6,9 +6,12 @@
 
 import 'dart:async';
 import 'dart:convert';
+import 'dart:io';
 import 'dart:typed_data';
+
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
+import 'package:path_provider/path_provider.dart';
 import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
 
 import 'navigation_decision.dart';
@@ -34,6 +37,25 @@
 </html>
 ''';
 
+const String kLocalFileExamplePage = '''
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Load file or HTML string example</title>
+</head>
+<body>
+
+<h1>Local demo page</h1>
+<p>
+  This is an example page used to demonstrate how to load a local file or HTML 
+  string using the <a href="https://pub.dev/packages/webview_flutter">Flutter 
+  webview</a> plugin.
+</p>
+
+</body>
+</html>
+''';
+
 class _WebViewExample extends StatefulWidget {
   const _WebViewExample({Key? key}) : super(key: key);
 
@@ -121,6 +143,8 @@
   listCache,
   clearCache,
   navigationDelegate,
+  loadLocalFile,
+  loadHtmlString,
   doPostRequest,
 }
 
@@ -159,6 +183,12 @@
               case _MenuOptions.navigationDelegate:
                 _onNavigationDelegateExample(controller.data!, context);
                 break;
+              case _MenuOptions.loadLocalFile:
+                _onLoadLocalFileExample(controller.data!, context);
+                break;
+              case _MenuOptions.loadHtmlString:
+                _onLoadHtmlStringExample(controller.data!, context);
+                break;
               case _MenuOptions.doPostRequest:
                 _onDoPostRequest(controller.data!, context);
                 break;
@@ -195,6 +225,14 @@
               child: Text('Navigation Delegate example'),
             ),
             const PopupMenuItem<_MenuOptions>(
+              value: _MenuOptions.loadHtmlString,
+              child: Text('Load HTML string'),
+            ),
+            const PopupMenuItem<_MenuOptions>(
+              value: _MenuOptions.loadLocalFile,
+              child: Text('Load local file'),
+            ),
+            const PopupMenuItem<_MenuOptions>(
               value: _MenuOptions.doPostRequest,
               child: Text('Post Request'),
             ),
@@ -268,6 +306,18 @@
     await controller.loadUrl('data:text/html;base64,$contentBase64');
   }
 
+  void _onLoadLocalFileExample(
+      WebViewController controller, BuildContext context) async {
+    String pathToIndex = await _prepareLocalFile();
+
+    await controller.loadFile(pathToIndex);
+  }
+
+  void _onLoadHtmlStringExample(
+      WebViewController controller, BuildContext context) async {
+    await controller.loadHtmlString(kLocalFileExamplePage);
+  }
+
   void _onDoPostRequest(
       WebViewController controller, BuildContext context) async {
     WebViewRequest request = WebViewRequest(
@@ -292,6 +342,16 @@
       children: cookieWidgets.toList(),
     );
   }
+
+  static Future<String> _prepareLocalFile() async {
+    final String tmpDir = (await getTemporaryDirectory()).path;
+    File indexFile = File('$tmpDir/www/index.html');
+
+    await Directory('$tmpDir/www').create(recursive: true);
+    await indexFile.writeAsString(kLocalFileExamplePage);
+
+    return indexFile.path;
+  }
 }
 
 class _NavigationControls extends StatelessWidget {
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
index b2555cd..ab4b773 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
@@ -305,6 +305,35 @@
 
   WebView _widget;
 
+  /// Loads the file located on the specified [absoluteFilePath].
+  ///
+  /// The [absoluteFilePath] parameter should contain the absolute path to the
+  /// file as it is stored on the device. For example:
+  /// `/Users/username/Documents/www/index.html`.
+  ///
+  /// Throws an ArgumentError if the [absoluteFilePath] does not exist.
+  Future<void> loadFile(
+    String absoluteFilePath,
+  ) {
+    assert(absoluteFilePath.isNotEmpty);
+    return _webViewPlatformController.loadFile(absoluteFilePath);
+  }
+
+  /// Loads the supplied HTML string.
+  ///
+  /// The [baseUrl] parameter is used when resolving relative URLs within the
+  /// HTML string.
+  Future<void> loadHtmlString(
+    String html, {
+    String? baseUrl,
+  }) {
+    assert(html.isNotEmpty);
+    return _webViewPlatformController.loadHtmlString(
+      html,
+      baseUrl: baseUrl,
+    );
+  }
+
   /// Loads the specified URL.
   ///
   /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
index 229da5e..c8001c8 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
@@ -8,6 +8,9 @@
 dependencies:
   flutter:
     sdk: flutter
+
+  path_provider: ^2.0.6
+  
   webview_flutter_wkwebview:
     # When depending on this package from a real application you should use:
     #   webview_flutter: ^x.y.z
@@ -31,3 +34,4 @@
   assets:
     - assets/sample_audio.ogg
     - assets/sample_video.mp4
+    
\ No newline at end of file
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 b8355ad..351d1ae 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
@@ -140,6 +140,10 @@
 - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
   if ([[call method] isEqualToString:@"updateSettings"]) {
     [self onUpdateSettings:call result:result];
+  } else if ([[call method] isEqualToString:@"loadFile"]) {
+    [self onLoadFile:call result:result];
+  } else if ([[call method] isEqualToString:@"loadHtmlString"]) {
+    [self onLoadHtmlString:call result:result];
   } else if ([[call method] isEqualToString:@"loadUrl"]) {
     [self onLoadUrl:call result:result];
   } else if ([[call method] isEqualToString:@"loadRequest"]) {
@@ -192,6 +196,60 @@
   result([FlutterError errorWithCode:@"updateSettings_failed" message:error details:nil]);
 }
 
+- (void)onLoadFile:(FlutterMethodCall*)call result:(FlutterResult)result {
+  NSString* error = nil;
+  if (![FLTWebViewController isValidStringArgument:[call arguments] withErrorMessage:&error]) {
+    result([FlutterError errorWithCode:@"loadFile_failed"
+                               message:@"Failed parsing file path."
+                               details:error]);
+    return;
+  }
+
+  NSURL* url = [NSURL fileURLWithPath:[call arguments] isDirectory:NO];
+
+  if (!url) {
+    NSString* errorDetails = [NSString stringWithFormat:@"Initializing NSURL with the supplied "
+                                                        @"'%@' path resulted in a nil value.",
+                                                        [call arguments]];
+    result([FlutterError errorWithCode:@"loadFile_failed"
+                               message:@"Failed parsing file path."
+                               details:errorDetails]);
+    return;
+  }
+
+  NSURL* baseUrl = [url URLByDeletingLastPathComponent];
+
+  [_webView loadFileURL:url allowingReadAccessToURL:baseUrl];
+  result(nil);
+}
+
+- (void)onLoadHtmlString:(FlutterMethodCall*)call result:(FlutterResult)result {
+  NSDictionary* arguments = [call arguments];
+  if (![arguments isKindOfClass:NSDictionary.class]) {
+    result([FlutterError
+        errorWithCode:@"loadHtmlString_failed"
+              message:@"Failed parsing arguments."
+              details:@"Arguments should be a dictionary containing at least a 'html' element and "
+                      @"optionally a 'baseUrl' argument. For example: `@{ @\"html\": @\"some html "
+                      @"code\", @\"baseUrl\": @\"https://flutter.dev\" }`"]);
+    return;
+  }
+
+  NSString* htmlString = [call arguments][@"html"];
+  NSString* baseUrl =
+      [call arguments][@"baseUrl"] == [NSNull null] ? nil : [call arguments][@"baseUrl"];
+  NSString* error = nil;
+  if (![FLTWebViewController isValidStringArgument:htmlString withErrorMessage:&error]) {
+    result([FlutterError errorWithCode:@"loadHtmlString_failed"
+                               message:@"Failed parsing HTML string argument."
+                               details:error]);
+    return;
+  }
+
+  [_webView loadHTMLString:htmlString baseURL:[NSURL URLWithString:baseUrl]];
+  result(nil);
+}
+
 - (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result {
   NSMutableDictionary* requestData = [[NSMutableDictionary alloc] init];
   if (call.arguments[@"url"]) {
@@ -556,6 +614,37 @@
   }
 }
 
+/**
+ * Validates if the given `argument` is a non-null, non-empty string.
+ *
+ * @param argument The argument that should be validated.
+ * @param errorDetails An optional NSString variable which will contain a detailed error message in
+ * case the supplied argument is not valid.
+ * @return `YES` if the given `argument` is a valid non-null, non-empty string; otherwise `NO`.
+ */
++ (BOOL)isValidStringArgument:(id)argument withErrorMessage:(NSString**)errorDetails {
+  if (!argument) {
+    if (errorDetails) {
+      *errorDetails = @"Argument is nil.";
+    }
+    return NO;
+  }
+  if (![argument isKindOfClass:NSString.class]) {
+    if (errorDetails) {
+      *errorDetails = @"Argument is not of type NSString.";
+    }
+    return NO;
+  }
+  if (![argument length]) {
+    if (errorDetails) {
+      *errorDetails = @"Argument contains an empty string.";
+    }
+    return NO;
+  }
+
+  return YES;
+}
+
 #pragma mark WKUIDelegate
 
 - (WKWebView*)webView:(WKWebView*)webView
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
index ff2b69f..466c1a2 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
@@ -2,7 +2,7 @@
 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.3.0
+version: 2.4.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"