[webview_flutter] Implement loadRequest in iOS package. (#4480)
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
index 4db6dbf..c1b19fd 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.3.0
+
+* Implemented new `loadRequest` method from platform interface.
+
## 2.2.0
* Implemented new `runJavascript` and `runJavascriptReturningResult` methods in 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 ba0deb4..2be87fb 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
@@ -193,7 +193,6 @@
C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */,
5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */,
);
- name = Pods;
path = Pods;
sourceTree = "<group>";
};
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 9d127c2..61e43c1 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
@@ -5,6 +5,7 @@
@import Flutter;
@import XCTest;
@import webview_flutter_wkwebview;
+@import webview_flutter_wkwebview.Test;
// OCMock library doesn't generate a valid modulemap.
#import <OCMock/OCMock.h>
@@ -301,4 +302,196 @@
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
+- (void)testBuildNSURLRequestReturnsNilForNonDictionaryValue {
+ // Setup
+ FLTWebViewController *controller =
+ [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:nil
+ binaryMessenger:self.mockBinaryMessenger];
+
+ // Run
+ NSURLRequest *request = [controller buildNSURLRequest:@{@"request" : @"Non Dictionary Value"}];
+
+ // Verify
+ XCTAssertNil(request);
+}
+
+- (void)testBuildNSURLRequestReturnsNilForMissingURI {
+ // Setup
+ FLTWebViewController *controller =
+ [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:nil
+ binaryMessenger:self.mockBinaryMessenger];
+
+ // Run
+ NSURLRequest *request = [controller buildNSURLRequest:@{@"request" : @{}}];
+
+ // Verify
+ XCTAssertNil(request);
+}
+
+- (void)testBuildNSURLRequestReturnsNilForInvalidURI {
+ // Setup
+ FLTWebViewController *controller =
+ [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:nil
+ binaryMessenger:self.mockBinaryMessenger];
+
+ // Run
+ NSDictionary *requestData = @{@"uri" : @"invalid uri"};
+ NSURLRequest *request = [controller buildNSURLRequest:@{@"request" : requestData}];
+
+ // Verify
+ XCTAssertNil(request);
+}
+
+- (void)testBuildNSURLRequestBuildsNSMutableURLRequestWithOptionalParameters {
+ // Setup
+ FLTWebViewController *controller =
+ [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:nil
+ binaryMessenger:self.mockBinaryMessenger];
+
+ // Run
+ NSDictionary *requestData = @{
+ @"uri" : @"https://flutter.dev",
+ @"method" : @"POST",
+ @"headers" : @{@"Foo" : @"Bar"},
+ @"body" : [FlutterStandardTypedData
+ typedDataWithBytes:[@"Test Data" dataUsingEncoding:NSUTF8StringEncoding]],
+ };
+ NSURLRequest *request = [controller buildNSURLRequest:@{@"request" : requestData}];
+
+ // Verify
+ XCTAssertNotNil(request);
+ XCTAssertEqualObjects(request.URL.absoluteString, @"https://flutter.dev");
+ XCTAssertEqualObjects(request.HTTPMethod, @"POST");
+ XCTAssertEqualObjects(request.allHTTPHeaderFields, @{@"Foo" : @"Bar"});
+ XCTAssertEqualObjects(request.HTTPBody, [@"Test Data" dataUsingEncoding:NSUTF8StringEncoding]);
+}
+
+- (void)testBuildNSURLRequestBuildsNSMutableURLRequestWithoutOptionalParameters {
+ // Setup
+ FLTWebViewController *controller =
+ [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:nil
+ binaryMessenger:self.mockBinaryMessenger];
+
+ // Run
+ NSDictionary *requestData = @{
+ @"uri" : @"https://flutter.dev",
+ };
+ NSURLRequest *request = [controller buildNSURLRequest:@{@"request" : requestData}];
+
+ // Verify
+ XCTAssertNotNil(request);
+ XCTAssertEqualObjects(request.URL.absoluteString, @"https://flutter.dev");
+ XCTAssertEqualObjects(request.HTTPMethod, @"GET");
+ XCTAssertNil(request.allHTTPHeaderFields);
+ XCTAssertNil(request.HTTPBody);
+}
+
+- (void)testOnLoadUrlReturnsErrorResultForInvalidRequest {
+ // Setup
+ FLTWebViewController *controller =
+ [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:nil
+ binaryMessenger:self.mockBinaryMessenger];
+ XCTestExpectation *resultExpectation =
+ [self expectationWithDescription:@"Should return error result when request cannot be built"];
+
+ // Run
+ FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"loadUrl"
+ arguments:@{}];
+ [controller onLoadUrl:methodCall
+ result:^(id _Nullable result) {
+ XCTAssertTrue([result class] == [FlutterError class]);
+ [resultExpectation fulfill];
+ }];
+
+ // Verify
+ [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
+- (void)testOnLoadUrlLoadsRequestWithSuccessResult {
+ // Setup
+ FLTWebViewController *controller =
+ [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:nil
+ binaryMessenger:self.mockBinaryMessenger];
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"Should return nil"];
+ FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class);
+ controller.webView = mockView;
+
+ // Run
+ FlutterMethodCall *methodCall =
+ [FlutterMethodCall methodCallWithMethodName:@"loadUrl"
+ arguments:@{@"url" : @"https://flutter.dev/"}];
+ [controller onLoadUrl:methodCall
+ result:^(id _Nullable result) {
+ XCTAssertNil(result);
+ [resultExpectation fulfill];
+ }];
+
+ // Verify
+ OCMVerify([mockView loadRequest:[OCMArg any]]);
+ [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
+- (void)testOnLoadRequestReturnsErroResultForInvalidRequest {
+ // Setup
+ FLTWebViewController *controller =
+ [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:nil
+ binaryMessenger:self.mockBinaryMessenger];
+ XCTestExpectation *resultExpectation =
+ [self expectationWithDescription:@"Should return error result when request cannot be built"];
+
+ // Run
+ FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"loadRequest"
+ arguments:@{}];
+ [controller onLoadRequest:methodCall
+ result:^(id _Nullable result) {
+ XCTAssertTrue([result class] == [FlutterError class]);
+ [resultExpectation fulfill];
+ }];
+
+ // Verify
+ [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
+- (void)testOnLoadRequestLoadsRequestWithSuccessResult {
+ // Setup
+ FLTWebViewController *controller =
+ [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:nil
+ binaryMessenger:self.mockBinaryMessenger];
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"Should return nil"];
+ FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class);
+ controller.webView = mockView;
+
+ // Run
+ FlutterMethodCall *methodCall = [FlutterMethodCall
+ methodCallWithMethodName:@"loadRequest"
+ arguments:@{@"request" : @{@"uri" : @"https://flutter.dev/"}}];
+ [controller onLoadRequest:methodCall
+ result:^(id _Nullable result) {
+ XCTAssertNil(result);
+ [resultExpectation fulfill];
+ }];
+
+ // Verify
+ OCMVerify([mockView loadRequest:[OCMArg any]]);
+ [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
@end
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 21240f6..ea95cdd 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
@@ -6,6 +6,7 @@
import 'dart:async';
import 'dart:convert';
+import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
@@ -120,6 +121,7 @@
listCache,
clearCache,
navigationDelegate,
+ doPostRequest,
}
class _SampleMenu extends StatelessWidget {
@@ -157,6 +159,9 @@
case _MenuOptions.navigationDelegate:
_onNavigationDelegateExample(controller.data!, context);
break;
+ case _MenuOptions.doPostRequest:
+ _onDoPostRequest(controller.data!, context);
+ break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
@@ -189,6 +194,10 @@
value: _MenuOptions.navigationDelegate,
child: Text('Navigation Delegate example'),
),
+ const PopupMenuItem<_MenuOptions>(
+ value: _MenuOptions.doPostRequest,
+ child: Text('Post Request'),
+ ),
],
);
},
@@ -259,6 +268,17 @@
await controller.loadUrl('data:text/html;base64,$contentBase64');
}
+ void _onDoPostRequest(
+ WebViewController controller, BuildContext context) async {
+ WebViewRequest request = WebViewRequest(
+ uri: Uri.parse('https://httpbin.org/post'),
+ method: WebViewRequestMethod.post,
+ headers: {'foo': 'bar', 'Content-Type': 'text/plain'},
+ body: Uint8List.fromList('Test Body'.codeUnits),
+ );
+ await controller.loadRequest(request);
+ }
+
Widget _getCookieList(String cookies) {
if (cookies == null || cookies == '""') {
return Container();
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 403db1f..b2555cd 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
@@ -322,6 +322,11 @@
return _webViewPlatformController.loadUrl(url, headers);
}
+ /// Loads a page by making the specified request.
+ Future<void> loadRequest(WebViewRequest request) async {
+ return _webViewPlatformController.loadRequest(request);
+ }
+
/// Accessor to the current URL that the WebView is displaying.
///
/// If [WebView.initialUrl] was never specified, returns `null`.
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 db52124..6d8e463 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h
@@ -27,6 +27,7 @@
- (UIView*)view;
- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
+
@end
@interface FLTWebViewFactory : NSObject <FlutterPlatformViewFactory>
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 5e12f8a..b8355ad 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
@@ -5,6 +5,7 @@
#import "FlutterWebView.h"
#import "FLTWKNavigationDelegate.h"
#import "FLTWKProgressionDelegate.h"
+#import "FlutterWebView_Test.h"
#import "JavaScriptChannelHandler.h"
@implementation FLTWebViewFactory {
@@ -116,7 +117,11 @@
NSString* initialUrl = args[@"initialUrl"];
if ([initialUrl isKindOfClass:[NSString class]]) {
- [self loadUrl:initialUrl];
+ NSURL* url = [NSURL URLWithString:initialUrl];
+ if (url) {
+ NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
+ [_webView loadRequest:request];
+ }
}
}
return self;
@@ -137,6 +142,8 @@
[self onUpdateSettings:call result:result];
} else if ([[call method] isEqualToString:@"loadUrl"]) {
[self onLoadUrl:call result:result];
+ } else if ([[call method] isEqualToString:@"loadRequest"]) {
+ [self onLoadRequest:call result:result];
} else if ([[call method] isEqualToString:@"canGoBack"]) {
[self onCanGoBack:call result:result];
} else if ([[call method] isEqualToString:@"canGoForward"]) {
@@ -186,12 +193,34 @@
}
- (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result {
- if (![self loadRequest:[call arguments]]) {
+ NSMutableDictionary* requestData = [[NSMutableDictionary alloc] init];
+ if (call.arguments[@"url"]) {
+ requestData[@"uri"] = call.arguments[@"url"];
+ }
+ if (call.arguments[@"headers"]) {
+ requestData[@"headers"] = call.arguments[@"headers"];
+ }
+ NSURLRequest* request = [self buildNSURLRequest:@{@"request" : requestData}];
+ if (!request) {
result([FlutterError
errorWithCode:@"loadUrl_failed"
message:@"Failed parsing the URL"
details:[NSString stringWithFormat:@"Request was: '%@'", [call arguments]]]);
} else {
+ [_webView loadRequest:request];
+ result(nil);
+ }
+}
+
+- (void)onLoadRequest:(FlutterMethodCall*)call result:(FlutterResult)result {
+ NSURLRequest* request = [self buildNSURLRequest:[call arguments]];
+ if (!request) {
+ result([FlutterError
+ errorWithCode:@"loadRequest_failed"
+ message:@"Failed parsing the URL"
+ details:[NSString stringWithFormat:@"Request was: '%@'", [call arguments]]]);
+ } else {
+ [_webView loadRequest:request];
result(nil);
}
}
@@ -459,37 +488,47 @@
}
}
-- (bool)loadRequest:(NSDictionary<NSString*, id>*)request {
- if (!request) {
- return false;
+/**
+ * Parses the method call arguments and converts them to an NSURLRequest object.
+ *
+ * @param arguments the method call arguments.
+ *
+ * @return NSURLRequest object.
+ */
+- (NSURLRequest*)buildNSURLRequest:(NSDictionary<NSString*, id>*)arguments {
+ id requestParameters = arguments[@"request"];
+ if (![requestParameters isKindOfClass:[NSDictionary class]]) {
+ return nil;
}
- NSString* url = request[@"url"];
- if ([url isKindOfClass:[NSString class]]) {
- id headers = request[@"headers"];
- if ([headers isKindOfClass:[NSDictionary class]]) {
- return [self loadUrl:url withHeaders:headers];
- } else {
- return [self loadUrl:url];
- }
+ NSString* urlString = requestParameters[@"uri"];
+ if (!urlString) {
+ return nil;
}
- return false;
-}
-
-- (bool)loadUrl:(NSString*)url {
- return [self loadUrl:url withHeaders:[NSMutableDictionary dictionary]];
-}
-
-- (bool)loadUrl:(NSString*)url withHeaders:(NSDictionary<NSString*, NSString*>*)headers {
- NSURL* nsUrl = [NSURL URLWithString:url];
- if (!nsUrl) {
- return false;
+ NSURL* url = [NSURL URLWithString:urlString];
+ if (!url) {
+ return nil;
}
- NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:nsUrl];
- [request setAllHTTPHeaderFields:headers];
- [_webView loadRequest:request];
- return true;
+
+ NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
+
+ NSString* httpMethod = requestParameters[@"method"];
+ if (httpMethod) {
+ [request setHTTPMethod:httpMethod];
+ }
+
+ id httpBody = requestParameters[@"body"];
+ if ([httpBody isKindOfClass:[FlutterStandardTypedData class]]) {
+ [request setHTTPBody:[httpBody data]];
+ }
+
+ id headers = requestParameters[@"headers"];
+ if ([headers isKindOfClass:[NSDictionary class]]) {
+ [request setAllHTTPHeaderFields:headers];
+ }
+
+ return request;
}
- (void)registerJavaScriptChannels:(NSSet*)channelNames
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap
new file mode 100644
index 0000000..fa51430
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap
@@ -0,0 +1,10 @@
+framework module webview_flutter_wkwebview {
+ umbrella header "webview-umbrella.h"
+
+ export *
+ module * { export * }
+
+ explicit module Test {
+ header "FlutterWebView_Test.h"
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView_Test.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView_Test.h
new file mode 100644
index 0000000..13973f8
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView_Test.h
@@ -0,0 +1,21 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This header is available in the Test module. Import via "@import webview_flutter_wkwebview.Test;"
+
+#import <webview_flutter_wkwebview/FlutterWebView.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FLTWebViewController ()
+
+- (NSURLRequest*)buildNSURLRequest:(NSDictionary<NSString*, id>*)arguments;
+
+- (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result;
+
+- (void)onLoadRequest:(FlutterMethodCall*)call result:(FlutterResult)result;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h
new file mode 100644
index 0000000..c37282e
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h
@@ -0,0 +1,11 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Foundation/Foundation.h>
+#import <webview_flutter_wkwebview/FLTCookieManager.h>
+#import <webview_flutter_wkwebview/FLTWKNavigationDelegate.h>
+#import <webview_flutter_wkwebview/FLTWKProgressionDelegate.h>
+#import <webview_flutter_wkwebview/FLTWebViewFlutterPlugin.h>
+#import <webview_flutter_wkwebview/FlutterWebView.h>
+#import <webview_flutter_wkwebview/JavaScriptChannelHandler.h>
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/webview_flutter_wkwebview.podspec b/packages/webview_flutter/webview_flutter_wkwebview/ios/webview_flutter_wkwebview.podspec
index 2dfb336..89c0623 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/webview_flutter_wkwebview.podspec
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/webview_flutter_wkwebview.podspec
@@ -14,8 +14,9 @@
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :http => 'https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview' }
s.documentation_url = 'https://pub.dev/packages/webview_flutter'
- s.source_files = 'Classes/**/*'
+ s.source_files = 'Classes/**/*.{h,m}'
s.public_header_files = 'Classes/**/*.h'
+ s.module_map = 'Classes/FlutterWebView.modulemap'
s.dependency 'Flutter'
s.platform = :ios, '9.0'
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
index 5176adb..ff2b69f 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.2.0
+version: 2.3.0
environment:
sdk: ">=2.14.0 <3.0.0"
@@ -18,7 +18,7 @@
dependencies:
flutter:
sdk: flutter
- webview_flutter_platform_interface: ^1.2.0
+ webview_flutter_platform_interface: ^1.3.0
dev_dependencies:
flutter_driver: