Skip to content

Commit 66ed8f4

Browse files
authored
[webview_flutter] Implementations of loadFile and loadHtmlString for WKWebView (flutter#4486)
1 parent a103bcd commit 66ed8f4

File tree

9 files changed

+393
-4
lines changed

9 files changed

+393
-4
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+
## 2.4.0
2+
3+
* Implemented new `loadFile` and `loadHtmlString` methods from the platform interface.
4+
15
## 2.3.0
26

37
* Implemented new `loadRequest` method from platform interface.

packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 46;
6+
objectVersion = 50;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -273,7 +273,7 @@
273273
isa = PBXProject;
274274
attributes = {
275275
DefaultBuildSystemTypeForWorkspace = Original;
276-
LastUpgradeCheck = 1030;
276+
LastUpgradeCheck = 1300;
277277
ORGANIZATIONNAME = "The Flutter Authors";
278278
TargetAttributes = {
279279
68BDCAE823C3F7CB00D9C032 = {

packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1030"
3+
LastUpgradeVersion = "1300"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,201 @@ - (void)testContentInsetsSumAlwaysZeroAfterSetFrame {
8989
}
9090
}
9191

92+
- (void)testLoadFileSucceeds {
93+
NSString *testFilePath = @"/assets/file.html";
94+
NSURL *url = [NSURL fileURLWithPath:testFilePath isDirectory:NO];
95+
XCTestExpectation *resultExpectation =
96+
[self expectationWithDescription:@"Should return successful result over the method channel."];
97+
FLTWebViewController *controller =
98+
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
99+
viewIdentifier:1
100+
arguments:nil
101+
binaryMessenger:self.mockBinaryMessenger];
102+
FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
103+
controller.webView = mockWebView;
104+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile"
105+
arguments:testFilePath]
106+
result:^(id _Nullable result) {
107+
XCTAssertNil(result);
108+
[resultExpectation fulfill];
109+
}];
110+
111+
[self waitForExpectations:@[ resultExpectation ] timeout:30.0];
112+
OCMVerify([mockWebView loadFileURL:url
113+
allowingReadAccessToURL:[url URLByDeletingLastPathComponent]]);
114+
}
115+
116+
- (void)testLoadFileFailsWithInvalidPath {
117+
NSArray *resultExpectations = @[
118+
[self expectationWithDescription:@"Should return failed result when argument is nil."],
119+
[self expectationWithDescription:
120+
@"Should return failed result when argument is not of type NSString*."],
121+
[self expectationWithDescription:
122+
@"Should return failed result when argument is an empty string."],
123+
];
124+
125+
FLTWebViewController *controller =
126+
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
127+
viewIdentifier:1
128+
arguments:nil
129+
binaryMessenger:self.mockBinaryMessenger];
130+
FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
131+
controller.webView = mockWebView;
132+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile" arguments:nil]
133+
result:^(id _Nullable result) {
134+
FlutterError *expected =
135+
[FlutterError errorWithCode:@"loadFile_failed"
136+
message:@"Failed parsing file path."
137+
details:@"Argument is nil."];
138+
[FLTWebViewTests assertFlutterError:result withExpected:expected];
139+
[resultExpectations[0] fulfill];
140+
}];
141+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile" arguments:@(10)]
142+
result:^(id _Nullable result) {
143+
FlutterError *expected =
144+
[FlutterError errorWithCode:@"loadFile_failed"
145+
message:@"Failed parsing file path."
146+
details:@"Argument is not of type NSString."];
147+
[FLTWebViewTests assertFlutterError:result withExpected:expected];
148+
[resultExpectations[1] fulfill];
149+
}];
150+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile" arguments:@""]
151+
result:^(id _Nullable result) {
152+
FlutterError *expected =
153+
[FlutterError errorWithCode:@"loadFile_failed"
154+
message:@"Failed parsing file path."
155+
details:@"Argument contains an empty string."];
156+
[FLTWebViewTests assertFlutterError:result withExpected:expected];
157+
[resultExpectations[2] fulfill];
158+
}];
159+
160+
[self waitForExpectations:resultExpectations timeout:1.0];
161+
OCMReject([mockWebView loadFileURL:[OCMArg any] allowingReadAccessToURL:[OCMArg any]]);
162+
}
163+
164+
- (void)testLoadFileSucceedsWithBaseUrl {
165+
NSURL *baseUrl = [NSURL URLWithString:@"https://flutter.dev"];
166+
XCTestExpectation *resultExpectation =
167+
[self expectationWithDescription:@"Should return successful result over the method channel."];
168+
FLTWebViewController *controller =
169+
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
170+
viewIdentifier:1
171+
arguments:nil
172+
binaryMessenger:self.mockBinaryMessenger];
173+
FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
174+
controller.webView = mockWebView;
175+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
176+
arguments:@{
177+
@"html" : @"some HTML string",
178+
@"baseUrl" : @"https://flutter.dev"
179+
}]
180+
result:^(id _Nullable result) {
181+
XCTAssertNil(result);
182+
[resultExpectation fulfill];
183+
}];
184+
185+
[self waitForExpectations:@[ resultExpectation ] timeout:30.0];
186+
OCMVerify([mockWebView loadHTMLString:@"some HTML string" baseURL:baseUrl]);
187+
}
188+
189+
- (void)testLoadFileSucceedsWithoutBaseUrl {
190+
XCTestExpectation *resultExpectation =
191+
[self expectationWithDescription:@"Should return successful result over the method channel."];
192+
FLTWebViewController *controller =
193+
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
194+
viewIdentifier:1
195+
arguments:nil
196+
binaryMessenger:self.mockBinaryMessenger];
197+
FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
198+
controller.webView = mockWebView;
199+
[controller
200+
onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
201+
arguments:@{@"html" : @"some HTML string"}]
202+
result:^(id _Nullable result) {
203+
XCTAssertNil(result);
204+
[resultExpectation fulfill];
205+
}];
206+
207+
[self waitForExpectations:@[ resultExpectation ] timeout:30.0];
208+
OCMVerify([mockWebView loadHTMLString:@"some HTML string" baseURL:nil]);
209+
}
210+
211+
- (void)testLoadHtmlStringFailsWithInvalidArgument {
212+
NSArray *resultExpectations = @[
213+
[self expectationWithDescription:@"Should return failed result when argument is nil."],
214+
[self expectationWithDescription:
215+
@"Should return failed result when argument is not of type NSDictionary*."],
216+
[self expectationWithDescription:@"Should return failed result when HTML argument is nil."],
217+
[self expectationWithDescription:
218+
@"Should return failed result when HTML argument is not of type NSString*."],
219+
[self expectationWithDescription:
220+
@"Should return failed result when HTML argument is an empty string."],
221+
];
222+
223+
FLTWebViewController *controller =
224+
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
225+
viewIdentifier:1
226+
arguments:nil
227+
binaryMessenger:self.mockBinaryMessenger];
228+
FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
229+
controller.webView = mockWebView;
230+
FlutterError *expected = [FlutterError
231+
errorWithCode:@"loadHtmlString_failed"
232+
message:@"Failed parsing arguments."
233+
details:@"Arguments should be a dictionary containing at least a 'html' element and "
234+
@"optionally a 'baseUrl' argument. For example: `@{ @\"html\": @\"some html "
235+
@"code\", @\"baseUrl\": @\"https://flutter.dev\" }`"];
236+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
237+
arguments:nil]
238+
result:^(id _Nullable result) {
239+
[FLTWebViewTests assertFlutterError:result withExpected:expected];
240+
[resultExpectations[0] fulfill];
241+
}];
242+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
243+
arguments:@""]
244+
result:^(id _Nullable result) {
245+
[FLTWebViewTests assertFlutterError:result withExpected:expected];
246+
[resultExpectations[1] fulfill];
247+
}];
248+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
249+
arguments:@{}]
250+
result:^(id _Nullable result) {
251+
FlutterError *expected =
252+
[FlutterError errorWithCode:@"loadHtmlString_failed"
253+
message:@"Failed parsing HTML string argument."
254+
details:@"Argument is nil."];
255+
[FLTWebViewTests assertFlutterError:result withExpected:expected];
256+
[resultExpectations[2] fulfill];
257+
}];
258+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
259+
arguments:@{
260+
@"html" : @(42),
261+
}]
262+
result:^(id _Nullable result) {
263+
FlutterError *expected =
264+
[FlutterError errorWithCode:@"loadHtmlString_failed"
265+
message:@"Failed parsing HTML string argument."
266+
details:@"Argument is not of type NSString."];
267+
[FLTWebViewTests assertFlutterError:result withExpected:expected];
268+
[resultExpectations[3] fulfill];
269+
}];
270+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadHtmlString"
271+
arguments:@{
272+
@"html" : @"",
273+
}]
274+
result:^(id _Nullable result) {
275+
FlutterError *expected =
276+
[FlutterError errorWithCode:@"loadHtmlString_failed"
277+
message:@"Failed parsing HTML string argument."
278+
details:@"Argument contains an empty string."];
279+
[FLTWebViewTests assertFlutterError:result withExpected:expected];
280+
[resultExpectations[4] fulfill];
281+
}];
282+
283+
[self waitForExpectations:resultExpectations timeout:1.0];
284+
OCMReject([mockWebView loadHTMLString:[OCMArg any] baseURL:[OCMArg any]]);
285+
}
286+
92287
- (void)testRunJavascriptFailsForNullString {
93288
// Setup
94289
FLTWebViewController *controller =
@@ -302,6 +497,14 @@ - (void)testRunJavascriptReturningResultReturnsErrorResultForWKError {
302497
[self waitForExpectationsWithTimeout:30.0 handler:nil];
303498
}
304499

500+
+ (void)assertFlutterError:(id)actual withExpected:(FlutterError *)expected {
501+
XCTAssertTrue([actual class] == [FlutterError class]);
502+
FlutterError *errorResult = actual;
503+
XCTAssertEqualObjects(errorResult.code, expected.code);
504+
XCTAssertEqualObjects(errorResult.message, expected.message);
505+
XCTAssertEqualObjects(errorResult.details, expected.details);
506+
}
507+
305508
- (void)testBuildNSURLRequestReturnsNilForNonDictionaryValue {
306509
// Setup
307510
FLTWebViewController *controller =

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66

77
import 'dart:async';
88
import 'dart:convert';
9+
import 'dart:io';
910
import 'dart:typed_data';
11+
1012
import 'package:flutter/foundation.dart';
1113
import 'package:flutter/material.dart';
14+
import 'package:path_provider/path_provider.dart';
1215
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
1316

1417
import 'navigation_decision.dart';
@@ -34,6 +37,25 @@ The navigation delegate is set to block navigation to the youtube website.
3437
</html>
3538
''';
3639

40+
const String kLocalFileExamplePage = '''
41+
<!DOCTYPE html>
42+
<html lang="en">
43+
<head>
44+
<title>Load file or HTML string example</title>
45+
</head>
46+
<body>
47+
48+
<h1>Local demo page</h1>
49+
<p>
50+
This is an example page used to demonstrate how to load a local file or HTML
51+
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
52+
webview</a> plugin.
53+
</p>
54+
55+
</body>
56+
</html>
57+
''';
58+
3759
class _WebViewExample extends StatefulWidget {
3860
const _WebViewExample({Key? key}) : super(key: key);
3961

@@ -121,6 +143,8 @@ enum _MenuOptions {
121143
listCache,
122144
clearCache,
123145
navigationDelegate,
146+
loadLocalFile,
147+
loadHtmlString,
124148
doPostRequest,
125149
}
126150

@@ -159,6 +183,12 @@ class _SampleMenu extends StatelessWidget {
159183
case _MenuOptions.navigationDelegate:
160184
_onNavigationDelegateExample(controller.data!, context);
161185
break;
186+
case _MenuOptions.loadLocalFile:
187+
_onLoadLocalFileExample(controller.data!, context);
188+
break;
189+
case _MenuOptions.loadHtmlString:
190+
_onLoadHtmlStringExample(controller.data!, context);
191+
break;
162192
case _MenuOptions.doPostRequest:
163193
_onDoPostRequest(controller.data!, context);
164194
break;
@@ -194,6 +224,14 @@ class _SampleMenu extends StatelessWidget {
194224
value: _MenuOptions.navigationDelegate,
195225
child: Text('Navigation Delegate example'),
196226
),
227+
const PopupMenuItem<_MenuOptions>(
228+
value: _MenuOptions.loadHtmlString,
229+
child: Text('Load HTML string'),
230+
),
231+
const PopupMenuItem<_MenuOptions>(
232+
value: _MenuOptions.loadLocalFile,
233+
child: Text('Load local file'),
234+
),
197235
const PopupMenuItem<_MenuOptions>(
198236
value: _MenuOptions.doPostRequest,
199237
child: Text('Post Request'),
@@ -268,6 +306,18 @@ class _SampleMenu extends StatelessWidget {
268306
await controller.loadUrl('data:text/html;base64,$contentBase64');
269307
}
270308

309+
void _onLoadLocalFileExample(
310+
WebViewController controller, BuildContext context) async {
311+
String pathToIndex = await _prepareLocalFile();
312+
313+
await controller.loadFile(pathToIndex);
314+
}
315+
316+
void _onLoadHtmlStringExample(
317+
WebViewController controller, BuildContext context) async {
318+
await controller.loadHtmlString(kLocalFileExamplePage);
319+
}
320+
271321
void _onDoPostRequest(
272322
WebViewController controller, BuildContext context) async {
273323
WebViewRequest request = WebViewRequest(
@@ -292,6 +342,16 @@ class _SampleMenu extends StatelessWidget {
292342
children: cookieWidgets.toList(),
293343
);
294344
}
345+
346+
static Future<String> _prepareLocalFile() async {
347+
final String tmpDir = (await getTemporaryDirectory()).path;
348+
File indexFile = File('$tmpDir/www/index.html');
349+
350+
await Directory('$tmpDir/www').create(recursive: true);
351+
await indexFile.writeAsString(kLocalFileExamplePage);
352+
353+
return indexFile.path;
354+
}
295355
}
296356

297357
class _NavigationControls extends StatelessWidget {

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,35 @@ class WebViewController {
305305

306306
WebView _widget;
307307

308+
/// Loads the file located on the specified [absoluteFilePath].
309+
///
310+
/// The [absoluteFilePath] parameter should contain the absolute path to the
311+
/// file as it is stored on the device. For example:
312+
/// `/Users/username/Documents/www/index.html`.
313+
///
314+
/// Throws an ArgumentError if the [absoluteFilePath] does not exist.
315+
Future<void> loadFile(
316+
String absoluteFilePath,
317+
) {
318+
assert(absoluteFilePath.isNotEmpty);
319+
return _webViewPlatformController.loadFile(absoluteFilePath);
320+
}
321+
322+
/// Loads the supplied HTML string.
323+
///
324+
/// The [baseUrl] parameter is used when resolving relative URLs within the
325+
/// HTML string.
326+
Future<void> loadHtmlString(
327+
String html, {
328+
String? baseUrl,
329+
}) {
330+
assert(html.isNotEmpty);
331+
return _webViewPlatformController.loadHtmlString(
332+
html,
333+
baseUrl: baseUrl,
334+
);
335+
}
336+
308337
/// Loads the specified URL.
309338
///
310339
/// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will

0 commit comments

Comments
 (0)