Skip to content

[webview_flutter_wkwebview] Extended Web View API on iOS to add flexibility when working with local HTML content #8787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 4.9.0

* Adds support for `PlatformWebViewController.loadFileWithParams`.
* Introduces `AndroidLoadFileParams`, a platform-specific extension of `LoadFileParams` for Android that adds support for `headers`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also add something like this

Suggested change
* Introduces `AndroidLoadFileParams`, a platform-specific extension of `LoadFileParams` for Android that adds support for `headers`.
* Adds support for `PlatformWebViewController.loadFileWithParams`.
* Introduces `AndroidLoadFileParams`, a platform-specific extension of `LoadFileParams` for Android that adds support for `headers`.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.


## 4.8.2

* Bumps gradle from 8.9.0 to 8.11.1.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
webview_flutter_platform_interface: ^2.13.0
webview_flutter_platform_interface: ^2.14.0

dev_dependencies:
espresso: ^0.4.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,41 @@ import 'android_webkit_constants.dart';
import 'platform_views_service_proxy.dart';
import 'weak_reference_utils.dart';

/// Object specifying parameters for loading a local file in a
/// [AndroidWebViewController].
@immutable
base class AndroidLoadFileParams extends LoadFileParams {
/// Constructs a [AndroidLoadFileParams], the subclass of a [LoadFileParams].
AndroidLoadFileParams({
required String absoluteFilePath,
this.headers = const <String, String>{},
}) : super(
absoluteFilePath: absoluteFilePath.startsWith('file://')
? absoluteFilePath
: Uri.file(absoluteFilePath).toString(),
);

/// Constructs a [AndroidLoadFileParams] using a [LoadFileParams].
factory AndroidLoadFileParams.fromLoadFileParams(
LoadFileParams params, {
Map<String, String> headers = const <String, String>{},
}) {
return AndroidLoadFileParams(
absoluteFilePath: params.absoluteFilePath,
headers: headers,
);
}

/// Additional HTTP headers to be included when loading the local file.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Separate first sentence.

Suggested change
/// Additional HTTP headers to be included when loading the local file.
/// Additional HTTP headers to be included when loading the local file.
///

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

///
/// If not provided at initialization time, doesn't add any additional headers.
///
/// On Android, WebView supports adding headers when loading local or remote
/// content. This can be useful for scenarios like authentication,
/// content-type overrides, or custom request context.
final Map<String, String> headers;
}

/// Object specifying creation parameters for creating a [AndroidWebViewController].
///
/// When adding additional fields make sure they can be null or have a default
Expand Down Expand Up @@ -386,12 +421,29 @@ class AndroidWebViewController extends PlatformWebViewController {
Future<void> loadFile(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As stated in the iOS implementation as well, we keep loadFile and it should just call loadFileWithParams.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

String absoluteFilePath,
) {
final String url = absoluteFilePath.startsWith('file://')
? absoluteFilePath
: Uri.file(absoluteFilePath).toString();
return loadFileWithParams(
AndroidLoadFileParams(
absoluteFilePath: absoluteFilePath,
),
);
}

_webView.settings.setAllowFileAccess(true);
return _webView.loadUrl(url, <String, String>{});
@override
Future<void> loadFileWithParams(
LoadFileParams params,
) async {
switch (params) {
case final AndroidLoadFileParams params:
await Future.wait(<Future<void>>[
_webView.settings.setAllowFileAccess(true),
_webView.loadUrl(params.absoluteFilePath, params.headers),
]);

default:
await loadFileWithParams(
AndroidLoadFileParams.fromLoadFileParams(params),
);
}
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: webview_flutter_android
description: A Flutter plugin that provides a WebView widget on Android.
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
version: 4.8.2
version: 4.9.0

environment:
sdk: ^3.6.0
Expand All @@ -21,7 +21,7 @@ dependencies:
flutter:
sdk: flutter
meta: ^1.10.0
webview_flutter_platform_interface: ^2.13.0
webview_flutter_platform_interface: ^2.14.0

dev_dependencies:
build_runner: ^2.1.4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ void main() {
));
}

test('loadFile without file prefix', () async {
test('Initializing WebView settings on controller creation', () async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the loadFile method will be kept in AndroidWebViewController, one of the tests for loadFile should be kept.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'loadFile' tests restored under 'loadFile' group.
NOTE: the test on 277 line was not related to the 'loadFile' method, this change just fixes the label.

final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
createControllerWithMocks(
Expand All @@ -292,56 +292,209 @@ void main() {
verify(mockWebSettings.setUseWideViewPort(false)).called(1);
});

test('loadFile without file prefix', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);
group('loadFile', () {
test('Without file prefix', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);

await controller.loadFile('/path/to/file.html');
await controller.loadFile('/path/to/file.html');

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/file.html',
<String, String>{},
)).called(1);
});
verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/file.html',
<String, String>{},
)).called(1);
});

test('loadFile without file prefix and characters to be escaped', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);
test('Without file prefix and characters to be escaped', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);

await controller.loadFile('/path/to/?_<_>_.html');
await controller.loadFile('/path/to/?_<_>_.html');

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/%3F_%3C_%3E_.html',
<String, String>{},
)).called(1);
verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/%3F_%3C_%3E_.html',
<String, String>{},
)).called(1);
});

test('With file prefix', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
);

when(mockWebView.settings).thenReturn(mockWebSettings);

await controller.loadFile('file:///path/to/file.html');

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/file.html',
<String, String>{},
)).called(1);
});
});

test('loadFile with file prefix', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
);
group('loadFileWithParams', () {
group('Using LoadFileParams model', () {
test('Without file prefix', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);

when(mockWebView.settings).thenReturn(mockWebSettings);
await controller.loadFileWithParams(
const LoadFileParams(absoluteFilePath: '/path/to/file.html'),
);

await controller.loadFile('file:///path/to/file.html');
verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/file.html',
<String, String>{},
)).called(1);
});

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/file.html',
<String, String>{},
)).called(1);
test('Without file prefix and characters to be escaped', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);

await controller.loadFileWithParams(
const LoadFileParams(absoluteFilePath: '/path/to/?_<_>_.html'),
);

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/%3F_%3C_%3E_.html',
<String, String>{},
)).called(1);
});

test('With file prefix', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);

await controller.loadFileWithParams(
const LoadFileParams(absoluteFilePath: 'file:///path/to/file.html'),
);

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/file.html',
<String, String>{},
)).called(1);
});
});

group('Using WebKitLoadFileParams model', () {
test('Without file prefix', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);

await controller.loadFileWithParams(
AndroidLoadFileParams(absoluteFilePath: '/path/to/file.html'),
);

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/file.html',
<String, String>{},
)).called(1);
});

test('Without file prefix and characters to be escaped', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);

await controller.loadFileWithParams(
AndroidLoadFileParams(absoluteFilePath: '/path/to/?_<_>_.html'),
);

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/%3F_%3C_%3E_.html',
<String, String>{},
)).called(1);
});

test('With file prefix', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);

await controller.loadFileWithParams(
AndroidLoadFileParams(
absoluteFilePath: 'file:///path/to/file.html'),
);

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/file.html',
<String, String>{},
)).called(1);
});

test('With additional headers', () async {
final MockWebView mockWebView = MockWebView();
final MockWebSettings mockWebSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
mockSettings: mockWebSettings,
);

await controller.loadFileWithParams(
AndroidLoadFileParams(
absoluteFilePath: 'file:///path/to/file.html',
headers: const <String, String>{
'Authorization': 'Bearer test_token',
'Cache-Control': 'no-cache',
'X-Custom-Header': 'test-value',
},
),
);

verify(mockWebSettings.setAllowFileAccess(true)).called(1);
verify(mockWebView.loadUrl(
'file:///path/to/file.html',
const <String, String>{
'Authorization': 'Bearer test_token',
'Cache-Control': 'no-cache',
'X-Custom-Header': 'test-value',
},
)).called(1);
});
});
});

test('loadFlutterAsset when asset does not exist', () async {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 3.23.0

* Adds support for `PlatformWebViewController.loadFileWithParams`.
* Introduces `WebKitLoadFileParams`, a platform-specific extension of `LoadFileParams` for iOS and macOS that adds support for `readAccessPath`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment from Android.

Suggested change
* Introduces `WebKitLoadFileParams`, a platform-specific extension of `LoadFileParams` for iOS and macOS that adds support for `readAccessPath`.
* Adds support for `PlatformWebViewController.loadFileWithParams`.
* Introduces `WebKitLoadFileParams`, a platform-specific extension of `LoadFileParams` for iOS and macOS that adds support for `readAccessPath`.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.


## 3.22.1

* Changes the handling of a Flutter method failure from throwing an assertion error to logging the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies:
flutter:
sdk: flutter
path_provider: ^2.0.6
webview_flutter_platform_interface: ^2.13.0
webview_flutter_platform_interface: ^2.14.0
webview_flutter_wkwebview:
# When depending on this package from a real application you should use:
# webview_flutter: ^x.y.z
Expand Down
Loading