Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit c1dc83a

Browse files
committed
Implementation of loadFile for WKWebView.
Adds native implementation for the `loadFile` method channel call to the webview_flutter_wkwebview package.
1 parent 8e05ac2 commit c1dc83a

File tree

5 files changed

+204
-0
lines changed

5 files changed

+204
-0
lines changed

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,104 @@ - (void)testContentInsetsSumAlwaysZeroAfterSetFrame {
8888
}
8989
}
9090

91+
- (void)testLoadFileSucceeds {
92+
NSString *testFilePath = @"/assets/file.html";
93+
NSURL *url = [NSURL fileURLWithPath:testFilePath isDirectory:NO];
94+
XCTestExpectation *resultExpectation =
95+
[self expectationWithDescription:@"Should return successful result over the method channel."];
96+
FLTWebViewController *controller =
97+
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
98+
viewIdentifier:1
99+
arguments:nil
100+
binaryMessenger:self.mockBinaryMessenger];
101+
FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
102+
controller.webView = mockWebView;
103+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile"
104+
arguments:testFilePath]
105+
result:^(id _Nullable result) {
106+
XCTAssertNil(result);
107+
[resultExpectation fulfill];
108+
}];
109+
110+
[self waitForExpectations:@[ resultExpectation ] timeout:30.0];
111+
OCMVerify([mockWebView loadFileURL:url
112+
allowingReadAccessToURL:[url URLByDeletingLastPathComponent]]);
113+
}
114+
115+
- (void)testLoadFileFailsWithNilPath {
116+
XCTestExpectation *resultExpectation =
117+
[self expectationWithDescription:@"Should return failed result over the method channel."];
118+
FLTWebViewController *controller =
119+
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
120+
viewIdentifier:1
121+
arguments:nil
122+
binaryMessenger:self.mockBinaryMessenger];
123+
FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
124+
controller.webView = mockWebView;
125+
[controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile" arguments:nil]
126+
result:^(id _Nullable result) {
127+
XCTAssertTrue([result class] == [FlutterError class]);
128+
FlutterError *errorResult = result;
129+
XCTAssertEqualObjects(errorResult.code, @"loadFile_failed");
130+
XCTAssertEqualObjects(errorResult.message, @"Failed parsing file path.");
131+
XCTAssertEqualObjects(errorResult.details, @"Argument is nil.");
132+
[resultExpectation fulfill];
133+
}];
134+
135+
[self waitForExpectations:@[ resultExpectation ] timeout:1.0];
136+
OCMReject([mockWebView loadFileURL:[OCMArg any] allowingReadAccessToURL:[OCMArg any]]);
137+
}
138+
139+
- (void)testLoadFileFailsWithNonStringPath {
140+
XCTestExpectation *resultExpectation =
141+
[self expectationWithDescription:@"Should return failed result over the method channel."];
142+
FLTWebViewController *controller =
143+
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
144+
viewIdentifier:1
145+
arguments:nil
146+
binaryMessenger:self.mockBinaryMessenger];
147+
FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
148+
controller.webView = mockWebView;
149+
[controller
150+
onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile" arguments:@(10)]
151+
result:^(id _Nullable result) {
152+
XCTAssertTrue([result class] == [FlutterError class]);
153+
FlutterError *errorResult = result;
154+
XCTAssertEqualObjects(errorResult.code, @"loadFile_failed");
155+
XCTAssertEqualObjects(errorResult.message, @"Failed parsing file path.");
156+
XCTAssertEqualObjects(errorResult.details, @"Argument is not of type NSString.");
157+
[resultExpectation fulfill];
158+
}];
159+
160+
[self waitForExpectations:@[ resultExpectation ] timeout:1.0];
161+
OCMReject([mockWebView loadFileURL:[OCMArg any] allowingReadAccessToURL:[OCMArg any]]);
162+
}
163+
164+
- (void)testLoadFileFailsWithEmptyPath {
165+
XCTestExpectation *resultExpectation =
166+
[self expectationWithDescription:@"Should return failed result over the method channel."];
167+
FLTWebViewController *controller =
168+
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
169+
viewIdentifier:1
170+
arguments:nil
171+
binaryMessenger:self.mockBinaryMessenger];
172+
FLTWKWebView *mockWebView = OCMClassMock(FLTWKWebView.class);
173+
controller.webView = mockWebView;
174+
[controller
175+
onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"loadFile" arguments:@""]
176+
result:^(id _Nullable result) {
177+
XCTAssertTrue([result class] == [FlutterError class]);
178+
FlutterError *errorResult = result;
179+
XCTAssertEqualObjects(errorResult.code, @"loadFile_failed");
180+
XCTAssertEqualObjects(errorResult.message, @"Failed parsing file path.");
181+
XCTAssertEqualObjects(errorResult.details, @"Argument contains an empty string.");
182+
[resultExpectation fulfill];
183+
}];
184+
185+
[self waitForExpectations:@[ resultExpectation ] timeout:1.0];
186+
OCMReject([mockWebView loadFileURL:[OCMArg any] allowingReadAccessToURL:[OCMArg any]]);
187+
}
188+
91189
- (void)testRunJavascriptFailsForNullString {
92190
// Setup
93191
FLTWebViewController *controller =

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66

77
import 'dart:async';
88
import 'dart:convert';
9+
import 'dart:io';
10+
911
import 'package:flutter/foundation.dart';
1012
import 'package:flutter/material.dart';
13+
import 'package:path_provider/path_provider.dart';
1114
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
1215

1316
import 'navigation_decision.dart';
@@ -33,6 +36,21 @@ The navigation delegate is set to block navigation to the youtube website.
3336
</html>
3437
''';
3538

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

@@ -120,6 +138,7 @@ enum _MenuOptions {
120138
listCache,
121139
clearCache,
122140
navigationDelegate,
141+
loadLocalFile,
123142
}
124143

125144
class _SampleMenu extends StatelessWidget {
@@ -157,6 +176,9 @@ class _SampleMenu extends StatelessWidget {
157176
case _MenuOptions.navigationDelegate:
158177
_onNavigationDelegateExample(controller.data!, context);
159178
break;
179+
case _MenuOptions.loadLocalFile:
180+
_onLoadLocalFileExample(controller.data!, context);
181+
break;
160182
}
161183
},
162184
itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
@@ -189,6 +211,10 @@ class _SampleMenu extends StatelessWidget {
189211
value: _MenuOptions.navigationDelegate,
190212
child: Text('Navigation Delegate example'),
191213
),
214+
const PopupMenuItem<_MenuOptions>(
215+
value: _MenuOptions.loadLocalFile,
216+
child: Text('Load local file'),
217+
),
192218
],
193219
);
194220
},
@@ -259,6 +285,13 @@ class _SampleMenu extends StatelessWidget {
259285
await controller.loadUrl('data:text/html;base64,$contentBase64');
260286
}
261287

288+
void _onLoadLocalFileExample(
289+
WebViewController controller, BuildContext context) async {
290+
String pathToIndex = await _prepareLocalFile();
291+
292+
await controller.loadFile(pathToIndex);
293+
}
294+
262295
Widget _getCookieList(String cookies) {
263296
if (cookies == null || cookies == '""') {
264297
return Container();
@@ -272,6 +305,20 @@ class _SampleMenu extends StatelessWidget {
272305
children: cookieWidgets.toList(),
273306
);
274307
}
308+
309+
static Future<String> _prepareLocalFile() async {
310+
final String tmpDir = (await getTemporaryDirectory()).path;
311+
File indexFile = File('$tmpDir/www/index.html');
312+
313+
if (await indexFile.exists()) {
314+
return indexFile.path;
315+
}
316+
317+
await Directory('$tmpDir/www').create(recursive: true);
318+
await indexFile.writeAsString(kLocalFileExamplePage);
319+
320+
return indexFile.path;
321+
}
275322
}
276323

277324
class _NavigationControls extends StatelessWidget {

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,20 @@ 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 != null || absoluteFilePath.isNotEmpty);
319+
return _webViewPlatformController.loadFile(absoluteFilePath);
320+
}
321+
308322
/// Loads the specified URL.
309323
///
310324
/// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will

packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ environment:
88
dependencies:
99
flutter:
1010
sdk: flutter
11+
12+
path_provider: ^2.0.6
13+
1114
webview_flutter_wkwebview:
1215
# When depending on this package from a real application you should use:
1316
# webview_flutter: ^x.y.z
@@ -31,3 +34,4 @@ flutter:
3134
assets:
3235
- assets/sample_audio.ogg
3336
- assets/sample_video.mp4
37+

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ - (UIView*)view {
135135
- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
136136
if ([[call method] isEqualToString:@"updateSettings"]) {
137137
[self onUpdateSettings:call result:result];
138+
} else if ([[call method] isEqualToString:@"loadFile"]) {
139+
[self onLoadFile:call result:result];
138140
} else if ([[call method] isEqualToString:@"loadUrl"]) {
139141
[self onLoadUrl:call result:result];
140142
} else if ([[call method] isEqualToString:@"canGoBack"]) {
@@ -185,6 +187,22 @@ - (void)onUpdateSettings:(FlutterMethodCall*)call result:(FlutterResult)result {
185187
result([FlutterError errorWithCode:@"updateSettings_failed" message:error details:nil]);
186188
}
187189

190+
- (void)onLoadFile:(FlutterMethodCall*)call result:(FlutterResult)result {
191+
NSString* error = nil;
192+
if (![FLTWebViewController isValidStringArgument:[call arguments] withErrorMessage:&error]) {
193+
result([FlutterError errorWithCode:@"loadFile_failed"
194+
message:@"Failed parsing file path."
195+
details:error]);
196+
return;
197+
}
198+
199+
NSURL* url = [NSURL fileURLWithPath:[call arguments] isDirectory:NO];
200+
NSURL* baseUrl = [url URLByDeletingLastPathComponent];
201+
202+
[_webView loadFileURL:url allowingReadAccessToURL:baseUrl];
203+
result(nil);
204+
}
205+
188206
- (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result {
189207
if (![self loadRequest:[call arguments]]) {
190208
result([FlutterError
@@ -517,6 +535,29 @@ - (void)updateUserAgent:(NSString*)userAgent {
517535
}
518536
}
519537

538+
+ (bool)isValidStringArgument:(id)argument withErrorMessage:(NSString**)errorDetails {
539+
if (!argument) {
540+
if (errorDetails) {
541+
*errorDetails = @"Argument is nil.";
542+
}
543+
return NO;
544+
}
545+
if (![argument isKindOfClass:NSString.class]) {
546+
if (errorDetails) {
547+
*errorDetails = @"Argument is not of type NSString.";
548+
}
549+
return NO;
550+
}
551+
if (![argument length]) {
552+
if (errorDetails) {
553+
*errorDetails = @"Argument contains an empty string.";
554+
}
555+
return NO;
556+
}
557+
558+
return YES;
559+
}
560+
520561
#pragma mark WKUIDelegate
521562

522563
- (WKWebView*)webView:(WKWebView*)webView

0 commit comments

Comments
 (0)