updated web support

This commit is contained in:
Lorenzo Pichilli 2022-04-22 02:24:50 +02:00
parent 176d41d328
commit 46fcafcf44
11 changed files with 218 additions and 26 deletions

View File

@ -119,7 +119,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
key: webViewKey,
// contextMenu: contextMenu,
initialUrlRequest:
URLRequest(url: Uri.parse("http://flutter.dev/")),
URLRequest(url: Uri.parse("https://flutter.dev")),
// initialFile: "assets/index.html",
initialUserScripts: UnmodifiableListView<UserScript>([]),
initialSettings: settings,

View File

@ -31,6 +31,9 @@
<title>flutter_inappwebview_example</title>
<link rel="manifest" href="manifest.json">
<!-- Load flutter_inappwebview web_support js library -->
<script src="/packages/flutter_inappwebview/assets/web/web_support.js" defer></script>
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to

26
example/web/page-2.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="Demonstrates how to use the flutter_inappwebview plugin.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="flutter_inappwebview_example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>flutter_inappwebview_example</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<h1>Simple Page 2</h1>
<a href="/page.html">Go to page 1</a>
</body>
</html>

26
example/web/page.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="Demonstrates how to use the flutter_inappwebview plugin.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="flutter_inappwebview_example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>flutter_inappwebview_example</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<h1>Simple Page 1</h1>
<a href="/page-2.html">Go to page 2</a>
</body>
</html>

View File

@ -0,0 +1,63 @@
window.flutter_inappwebview = {
viewId: null,
iframeId: null,
iframe: null,
prepare: function () {
var iframe = document.getElementById(window.flutter_inappwebview.iframeId);
if (iframe != null) {
window.flutter_inappwebview.iframe = iframe;
iframe.addEventListener('load', function (event) {
var url = iframe.src;
try {
url = iframe.contentWindow.location.href;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('iframeLoaded', window.flutter_inappwebview.viewId, [url]);
});
}
},
reload: function () {
var iframe = window.flutter_inappwebview.iframe;
if (iframe != null && iframe.contentWindow != null) {
try {
iframe.contentWindow.location.reload();
} catch (e) {
console.log(e);
iframe.contentWindow.location.href = iframe.src;
}
}
},
goBack: function () {
var iframe = window.flutter_inappwebview.iframe;
if (iframe != null) {
try {
iframe.contentWindow.history.back();
} catch (e) {
console.log(e);
}
}
},
goForward: function () {
var iframe = window.flutter_inappwebview.iframe;
if (iframe != null) {
try {
iframe.contentWindow.history.forward();
} catch (e) {
console.log(e);
}
}
},
evaluateJavascript: function (source) {
var iframe = window.flutter_inappwebview.iframe;
var result = null;
if (iframe != null) {
try {
result = iframe.contentWindow.eval(source);
} catch (e) {
console.log(e);
}
}
return result;
}
};

View File

@ -1385,9 +1385,13 @@ class InAppWebViewController
///
///**NOTE for Android**: when loading an URL Request using "POST" method, headers are ignored.
///
///**NOTE for Web**: if method is "GET" and headers are empty, it will change the `src` of the iframe.
///For all other cases it will try to create an XMLHttpRequest and load the result inside the iframe.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.loadUrl](https://developer.android.com/reference/android/webkit/WebView#loadUrl(java.lang.String))). If method is "POST", [Official API - WebView.postUrl](https://developer.android.com/reference/android/webkit/WebView#postUrl(java.lang.String,%20byte[]))
///- iOS ([Official API - WKWebView.load](https://developer.apple.com/documentation/webkit/wkwebview/1414954-load). If [allowingReadAccessTo] is used, [Official API - WKWebView.loadFileURL](https://developer.apple.com/documentation/webkit/wkwebview/1414973-loadfileurl))
///- Web
Future<void> loadUrl(
{required URLRequest urlRequest,
@Deprecated('Use allowingReadAccessTo instead')
@ -1432,7 +1436,9 @@ class InAppWebViewController
///
///- [mimeType] argument specifies the format of the data. The default value is `"text/html"`.
///- [encoding] argument specifies the encoding of the data. The default value is `"utf8"`.
///**NOTE**: not used on Web.
///- [historyUrl] is an Android-specific argument that represents the URL to use as the history entry. The default value is `about:blank`. If non-null, this must be a valid URL.
///**NOTE**: not used on Web.
///- [allowingReadAccessTo], used in combination with [baseUrl] (using the `file://` scheme),
///it represents the URL from which to read the web content.
///This [baseUrl] must be a file-based URL (using the `file://` scheme).
@ -1443,6 +1449,7 @@ class InAppWebViewController
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.loadDataWithBaseURL](https://developer.android.com/reference/android/webkit/WebView#loadDataWithBaseURL(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String)))
///- iOS ([Official API - WKWebView.loadHTMLString](https://developer.apple.com/documentation/webkit/wkwebview/1415004-loadhtmlstring) or [Official API - WKWebView.load](https://developer.apple.com/documentation/webkit/wkwebview/1415011-load))
///- Web
Future<void> loadData(
{required String data,
String mimeType = "text/html",
@ -1511,6 +1518,7 @@ class InAppWebViewController
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.loadUrl](https://developer.android.com/reference/android/webkit/WebView#loadUrl(java.lang.String)))
///- iOS ([Official API - WKWebView.load](https://developer.apple.com/documentation/webkit/wkwebview/1414954-load))
///- Web
Future<void> loadFile({required String assetFilePath}) async {
assert(assetFilePath.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
@ -1520,9 +1528,12 @@ class InAppWebViewController
///Reloads the WebView.
///
///**NOTE**: on Web, if `window.location.reload()` is not accessible inside the iframe, it will reload using the iframe `src` attribute.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.reload](https://developer.android.com/reference/android/webkit/WebView#reload()))
///- iOS ([Official API - WKWebView.reload](https://developer.apple.com/documentation/webkit/wkwebview/1414969-reload))
///- Web ([Official API - Location.reload](https://developer.mozilla.org/en-US/docs/Web/API/Location/reload))
Future<void> reload() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('reload', args);
@ -1533,6 +1544,7 @@ class InAppWebViewController
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.goBack](https://developer.android.com/reference/android/webkit/WebView#goBack()))
///- iOS ([Official API - WKWebView.goBack](https://developer.apple.com/documentation/webkit/wkwebview/1414952-goback))
///- Web ([Official API - History.back](https://developer.mozilla.org/en-US/docs/Web/API/History/back))
Future<void> goBack() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('goBack', args);
@ -1553,6 +1565,7 @@ class InAppWebViewController
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.goForward](https://developer.android.com/reference/android/webkit/WebView#goForward()))
///- iOS ([Official API - WKWebView.goForward](https://developer.apple.com/documentation/webkit/wkwebview/1414993-goforward))
///- Web ([Official API - History.forward](https://developer.mozilla.org/en-US/docs/Web/API/History/forward))
Future<void> goForward() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('goForward', args);
@ -1630,6 +1643,7 @@ class InAppWebViewController
///Those changes remain visible to all scripts, regardless of which content world you specify.
///For more information about content worlds, see [ContentWorld].
///Available on iOS 14.0+.
///**NOTE**: not used on Web.
///
///**NOTE**: This method shouldn't be called in the [WebView.onWebViewCreated] or [WebView.onLoadStart] events,
///because, in these events, the [WebView] is not ready to handle it yet.
@ -1639,6 +1653,7 @@ class InAppWebViewController
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.evaluateJavascript](https://developer.android.com/reference/android/webkit/WebView#evaluateJavascript(java.lang.String,%20android.webkit.ValueCallback%3Cjava.lang.String%3E)))
///- iOS ([Official API - WKWebView.evaluateJavascript](https://developer.apple.com/documentation/webkit/wkwebview/3656442-evaluatejavascript))
///- Web ([Official API - Window.eval](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval?retiredLocale=it))
Future<dynamic> evaluateJavascript(
{required String source, ContentWorld? contentWorld}) async {
Map<String, dynamic> args = <String, dynamic>{};

View File

@ -20,6 +20,7 @@ abstract class WebView {
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
final void Function(InAppWebViewController controller)? onWebViewCreated;
///Event fired when the [WebView] starts to load an [url].
@ -27,6 +28,7 @@ abstract class WebView {
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebViewClient.onPageStarted](https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)))
///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview))
///- Web
final void Function(InAppWebViewController controller, Uri? url)? onLoadStart;
///Event fired when the [WebView] finishes loading an [url].
@ -34,6 +36,7 @@ abstract class WebView {
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebViewClient.onPageFinished](https://developer.android.com/reference/android/webkit/WebViewClient#onPageFinished(android.webkit.WebView,%20java.lang.String)))
///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview))
///- Web ([Official API - Window.onload](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event))
final void Function(InAppWebViewController controller, Uri? url)? onLoadStop;
///Event fired when the [WebView] encounters an error loading an [url].

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:html';
import 'dart:js' as js;
import '../in_app_webview/in_app_webview_settings.dart';
import '../types.dart';
@ -16,12 +17,16 @@ class InAppWebViewWebElement {
String? initialFile;
late InAppWebViewSettings settings;
late js.JsObject bridgeJsObject;
WebHistory webHistory = WebHistory(list: [], currentIndex: -1);
InAppWebViewWebElement({required int viewId, required BinaryMessenger messenger}) {
this._viewId = viewId;
this._messenger = messenger;
iframe = IFrameElement()
..id = 'flutter_inappwebview-$_viewId'
..style.height = '100%'
..style.width = '100%'
..style.border = 'none';
_channel = MethodChannel(
@ -32,19 +37,16 @@ class InAppWebViewWebElement {
this._channel.setMethodCallHandler(handleMethodCall);
iframe.addEventListener('load', (event) async {
var obj = {
"url": iframe.src
};
_channel.invokeMethod("onLoadStart", obj);
await Future.delayed(Duration(milliseconds: 100));
_channel.invokeMethod("onLoadStop", obj);
});
bridgeJsObject = js.JsObject.fromBrowserObject(js.context['flutter_inappwebview']);
bridgeJsObject['viewId'] = _viewId;
bridgeJsObject['iframeId'] = iframe.id;
}
/// Handles method calls over the MethodChannel of this plugin.
Future<dynamic> handleMethodCall(MethodCall call) async {
switch (call.method) {
case "getIFrameId":
return iframe.id;
case "loadUrl":
URLRequest urlRequest = URLRequest.fromMap(call.arguments["urlRequest"].cast<String, dynamic>())!;
await _loadUrl(urlRequest: urlRequest);
@ -61,8 +63,15 @@ class InAppWebViewWebElement {
case "reload":
await _reload();
break;
case "getIFrameId":
return iframe.id;
case "goBack":
await _goBack();
break;
case "goForward":
await _goForward();
break;
case "evaluateJavascript":
String source = call.arguments["source"];
return await _evaluateJavascript(source: source);
default:
throw PlatformException(
code: 'Unimplemented',
@ -78,19 +87,13 @@ class InAppWebViewWebElement {
if (settings.iframeSandox != null) {
iframe.setAttribute("sandbox", settings.iframeSandox ?? "");
}
var width = settings.iframeWidth ?? iframe.width;
if (width == null || width.isEmpty) {
width = '100%';
}
var height = settings.iframeHeight ?? iframe.height;
if (height == null || height.isEmpty) {
height = '100%';
}
iframe.width = iframe.style.width = width;
iframe.height = iframe.style.height = height;
iframe.style.width = settings.iframeWidth ?? iframe.style.width;
iframe.style.height = settings.iframeHeight ?? iframe.style.height;
iframe.referrerPolicy = settings.iframeReferrerPolicy ?? iframe.referrerPolicy;
iframe.name = settings.iframeName ?? iframe.name;
iframe.csp = settings.iframeCsp ?? iframe.csp;
bridgeJsObject.callMethod("prepare");
}
void makeInitialLoad() async {
@ -129,20 +132,48 @@ class InAppWebViewWebElement {
} else {
iframe.src = _convertHttpResponseToData(await _makeRequest(urlRequest));
}
var obj = {
"url": iframe.src
};
_channel.invokeMethod("onLoadStart", obj);
}
Future<void> _loadData({required String data, String mimeType = "text/html"}) async {
iframe.src = 'data:$mimeType,' + Uri.encodeFull(data);
var obj = {
"url": iframe.src
};
_channel.invokeMethod("onLoadStart", obj);
}
Future<void> _loadFile({required String assetFilePath}) async {
iframe.src = assetFilePath;
var obj = {
"url": iframe.src
};
_channel.invokeMethod("onLoadStart", obj);
}
Future<void> _reload() async {
var src = iframe.src;
if (src != null) {
iframe.contentWindow?.location.href = src;
}
bridgeJsObject.callMethod("reload");
}
Future<void> _goBack() async {
bridgeJsObject.callMethod("goBack");
}
Future<void> _goForward() async {
bridgeJsObject.callMethod("goForward");
}
Future<dynamic> _evaluateJavascript({required String source}) async {
return bridgeJsObject.callMethod("evaluateJavascript", [source]);
}
onIFrameLoaded(String url) async {
var obj = {
"url": url
};
_channel.invokeMethod("onLoadStop", obj);
}
}

View File

@ -6,6 +6,8 @@ import 'shims/dart_ui.dart' as ui;
import 'in_app_web_view_web_element.dart';
import 'package:js/js.dart';
/// Builds an iframe based WebView.
///
/// This is used as the default implementation for [WebView] on web.
@ -24,6 +26,7 @@ class FlutterInAppWebViewWebPlatform {
static void registerWith(Registrar registrar) {
final pluginInstance = FlutterInAppWebViewWebPlatform(registrar);
_nativeCommunication = allowInterop(_dartNativeCommunication);
}
/// Handles method calls over the MethodChannel of this plugin.
@ -36,4 +39,24 @@ class FlutterInAppWebViewWebPlatform {
);
}
}
}
/// Allows assigning a function to be callable from `window.flutter_inappwebview.nativeCommunication()`
@JS('flutter_inappwebview.nativeCommunication')
external set _nativeCommunication(void Function(String method, int viewId, [List? args]) f);
/// Allows calling the assigned function from Dart as well.
@JS()
external void nativeCommunication();
void _dartNativeCommunication(String method, int viewId, [List? args]) {
if (WebPlatformManager.webViews.containsKey(viewId)) {
var webViewHtmlElement = WebPlatformManager.webViews[viewId] as InAppWebViewWebElement;
switch (method) {
case 'iframeLoaded':
String url = args![0] as String;
webViewHtmlElement.onIFrameLoaded(url);
break;
}
}
}

View File

@ -1,3 +1,3 @@
class WebPlatformManager {
abstract class WebPlatformManager {
static final Map<int, dynamic> webViews = {};
}

View File

@ -10,6 +10,7 @@ environment:
dependencies:
flutter:
sdk: flutter
js: ^0.6.3
dev_dependencies:
flutter_test:
@ -38,6 +39,7 @@ flutter:
assets:
- packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html
- packages/flutter_inappwebview/assets/t_rex_runner/t-rex.css
- packages/flutter_inappwebview/assets/web/web_support.js
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg