From 1161380eabc431cadd712efd22a11d2df92bf638 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Fri, 22 Apr 2022 13:39:21 +0200 Subject: [PATCH] updated web support --- example/lib/in_app_webiew_example.screen.dart | 12 +- example/web/heavy-page.html | 29 +++++ example/web/page-2.html | 5 +- example/web/page.html | 2 + lib/assets/web/web_support.js | 59 ++++++++- .../in_app_webview_controller.dart | 3 + lib/src/in_app_webview/webview.dart | 4 + lib/src/web/in_app_web_view_web_element.dart | 119 +++++++++++++----- lib/src/web/web_platform.dart | 12 +- 9 files changed, 207 insertions(+), 38 deletions(-) create mode 100644 example/web/heavy-page.html diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index 4f7aab1d..c0ccefe6 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -119,10 +119,10 @@ class _InAppWebViewExampleScreenState extends State { InAppWebView( key: webViewKey, // contextMenu: contextMenu, - initialUrlRequest: - URLRequest(url: Uri.parse("https://www.pubnub.com/developers/demos/webrtc/launch/")), // initialUrlRequest: - // URLRequest(url: Uri.parse(Uri.base.toString().replaceFirst("/#/", "/") + 'page.html')), + // URLRequest(url: Uri.parse("https://www.pubnub.com/developers/demos/webrtc/launch/")), + initialUrlRequest: + URLRequest(url: Uri.parse(Uri.base.toString().replaceFirst("/#/", "/") + 'page.html')), // initialFile: "assets/index.html", initialUserScripts: UnmodifiableListView([]), initialSettings: settings, @@ -221,6 +221,12 @@ class _InAppWebViewExampleScreenState extends State { webViewController?.reload(); }, ), + ElevatedButton( + child: Icon(Icons.cancel), + onPressed: () { + webViewController?.stopLoading(); + }, + ), ], ), ]))); diff --git a/example/web/heavy-page.html b/example/web/heavy-page.html new file mode 100644 index 00000000..d3c8c60f --- /dev/null +++ b/example/web/heavy-page.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + flutter_inappwebview_example + + + +

Heavy Page

+ Go to page 1 +
+

Loading image...

+ Loading failed + + diff --git a/example/web/page-2.html b/example/web/page-2.html index d464fab7..04d21a57 100644 --- a/example/web/page-2.html +++ b/example/web/page-2.html @@ -1,7 +1,6 @@ - @@ -22,5 +21,9 @@

Simple Page 2

Go to page 1 +
+ Go to heavy-page +
+ diff --git a/example/web/page.html b/example/web/page.html index ac3f3139..86c5838e 100644 --- a/example/web/page.html +++ b/example/web/page.html @@ -22,5 +22,7 @@

Simple Page 1

Go to page 2 +
+ Go to heavy-page diff --git a/lib/assets/web/web_support.js b/lib/assets/web/web_support.js index d4b48b12..f6b79352 100644 --- a/lib/assets/web/web_support.js +++ b/lib/assets/web/web_support.js @@ -13,7 +13,44 @@ window.flutter_inappwebview = { } catch (e) { console.log(e); } - window.flutter_inappwebview.nativeCommunication('iframeLoaded', window.flutter_inappwebview.viewId, [url]); + window.flutter_inappwebview.nativeCommunication('onLoadStart', window.flutter_inappwebview.viewId, [url]); + window.flutter_inappwebview.nativeCommunication('onLoadStop', window.flutter_inappwebview.viewId, [url]); + + iframe.contentWindow.addEventListener('popstate', function (event) { + var iframeUrl = iframe.src; + try { + iframeUrl = iframe.contentWindow.location.href; + } catch (e) { + console.log(e); + } + window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]); + }); + try { + var originalPushState = iframe.contentWindow.history.pushState; + iframe.contentWindow.history.pushState = function (state, unused, url) { + originalPushState.call(iframe.contentWindow.history, state, unused, url); + var iframeUrl = iframe.src; + try { + iframeUrl = iframe.contentWindow.location.href; + } catch (e) { + console.log(e); + } + window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]); + }; + var originalReplaceState = iframe.contentWindow.history.replaceState; + iframe.contentWindow.history.replaceState = function (state, unused, url) { + originalReplaceState.call(iframe.contentWindow.history, state, unused, url); + var iframeUrl = iframe.src; + try { + iframeUrl = iframe.contentWindow.location.href; + } catch (e) { + console.log(e); + } + window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]); + }; + } catch (e) { + console.log(e); + } }); } }, @@ -48,6 +85,16 @@ window.flutter_inappwebview = { } } }, + goForwardOrForward: function (steps) { + var iframe = window.flutter_inappwebview.iframe; + if (iframe != null) { + try { + iframe.contentWindow.history.go(steps); + } catch (e) { + console.log(e); + } + } + }, evaluateJavascript: function (source) { var iframe = window.flutter_inappwebview.iframe; var result = null; @@ -59,5 +106,15 @@ window.flutter_inappwebview = { } } return result; + }, + stopLoading: function (steps) { + var iframe = window.flutter_inappwebview.iframe; + if (iframe != null) { + try { + iframe.contentWindow.stop(); + } catch (e) { + console.log(e); + } + } } }; diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart index a30eacaf..d0585399 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1586,6 +1586,7 @@ class InAppWebViewController ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebView.goBackOrForward](https://developer.android.com/reference/android/webkit/WebView#goBackOrForward(int))) ///- iOS ([Official API - WKWebView.go](https://developer.apple.com/documentation/webkit/wkwebview/1414991-go)) + ///- Web ([Official API - History.go](https://developer.mozilla.org/en-US/docs/Web/API/History/go)) Future goBackOrForward({required int steps}) async { Map args = {}; args.putIfAbsent('steps', () => steps); @@ -1608,6 +1609,7 @@ class InAppWebViewController ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web Future goTo({required WebHistoryItem historyItem}) async { var steps = historyItem.offset; if (steps != null) { @@ -1620,6 +1622,7 @@ class InAppWebViewController ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web Future isLoading() async { Map args = {}; return await _channel.invokeMethod('isLoading', args); diff --git a/lib/src/in_app_webview/webview.dart b/lib/src/in_app_webview/webview.dart index d1bd30b9..86a28188 100644 --- a/lib/src/in_app_webview/webview.dart +++ b/lib/src/in_app_webview/webview.dart @@ -25,6 +25,9 @@ abstract class WebView { ///Event fired when the [WebView] starts to load an [url]. /// + ///**NOTE**: on Web it will be dispatched at the same time of [onLoadStop] event + ///because there isn't any way to capture the real load start event from an iframe. + /// ///**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)) @@ -380,6 +383,7 @@ abstract class WebView { ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.doUpdateVisitedHistory](https://developer.android.com/reference/android/webkit/WebViewClient#doUpdateVisitedHistory(android.webkit.WebView,%20java.lang.String,%20boolean))) ///- iOS + ///- Web final void Function( InAppWebViewController controller, Uri? url, bool? isReload)? onUpdateVisitedHistory; diff --git a/lib/src/web/in_app_web_view_web_element.dart b/lib/src/web/in_app_web_view_web_element.dart index eda71b9d..02e697da 100644 --- a/lib/src/web/in_app_web_view_web_element.dart +++ b/lib/src/web/in_app_web_view_web_element.dart @@ -18,7 +18,7 @@ class InAppWebViewWebElement { late InAppWebViewSettings settings; late js.JsObject bridgeJsObject; - WebHistory webHistory = WebHistory(list: [], currentIndex: -1); + bool isLoading = false; InAppWebViewWebElement({required int viewId, required BinaryMessenger messenger}) { this._viewId = viewId; @@ -49,29 +49,44 @@ class InAppWebViewWebElement { return iframe.id; case "loadUrl": URLRequest urlRequest = URLRequest.fromMap(call.arguments["urlRequest"].cast())!; - await _loadUrl(urlRequest: urlRequest); + await loadUrl(urlRequest: urlRequest); break; case "loadData": String data = call.arguments["data"]; String mimeType = call.arguments["mimeType"]; - await _loadData(data: data, mimeType: mimeType); + await loadData(data: data, mimeType: mimeType); break; case "loadFile": String assetFilePath = call.arguments["assetFilePath"]; - await _loadFile(assetFilePath: assetFilePath); + await loadFile(assetFilePath: assetFilePath); break; case "reload": - await _reload(); + await reload(); break; case "goBack": - await _goBack(); + await goBack(); break; case "goForward": - await _goForward(); + await goForward(); break; + case "goBackOrForward": + int steps = call.arguments["steps"]; + await goBackOrForward(steps: steps); + break; + case "isLoading": + return isLoading; case "evaluateJavascript": String source = call.arguments["source"]; - return await _evaluateJavascript(source: source); + return await evaluateJavascript(source: source); + case "stopLoading": + await stopLoading(); + break; + case "getSettings": + return await settings.toMap(); + case "setSettings": + InAppWebViewSettings newSettings = InAppWebViewSettings.fromMap(call.arguments["settings"].cast()); + setSettings(newSettings); + break; default: throw PlatformException( code: 'Unimplemented', @@ -98,11 +113,11 @@ class InAppWebViewWebElement { void makeInitialLoad() async { if (initialUrlRequest != null) { - _loadUrl(urlRequest: initialUrlRequest!); + loadUrl(urlRequest: initialUrlRequest!); } else if (initialData != null) { - _loadData(data: initialData!.data, mimeType: initialData!.mimeType); + loadData(data: initialData!.data, mimeType: initialData!.mimeType); } else if (initialFile != null) { - _loadFile(assetFilePath: initialFile!); + loadFile(assetFilePath: initialFile!); } } @@ -125,55 +140,97 @@ class InAppWebViewWebElement { return 'data:$contentType,' + Uri.encodeFull(httpRequest.responseText ?? ''); } - Future _loadUrl({required URLRequest urlRequest}) async { + Future loadUrl({required URLRequest urlRequest}) async { if ((urlRequest.method == null || urlRequest.method == "GET") && (urlRequest.headers == null || urlRequest.headers!.isEmpty)) { iframe.src = urlRequest.url.toString(); } else { iframe.src = _convertHttpResponseToData(await _makeRequest(urlRequest)); } - var obj = { - "url": iframe.src - }; - _channel.invokeMethod("onLoadStart", obj); } - Future _loadData({required String data, String mimeType = "text/html"}) async { + Future 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 _loadFile({required String assetFilePath}) async { + Future loadFile({required String assetFilePath}) async { iframe.src = assetFilePath; - var obj = { - "url": iframe.src - }; - _channel.invokeMethod("onLoadStart", obj); } - Future _reload() async { + Future reload() async { bridgeJsObject.callMethod("reload"); } - Future _goBack() async { + Future goBack() async { bridgeJsObject.callMethod("goBack"); } - Future _goForward() async { + Future goForward() async { bridgeJsObject.callMethod("goForward"); } - Future _evaluateJavascript({required String source}) async { + Future goBackOrForward({required int steps}) async { + bridgeJsObject.callMethod("goBackOrForward", [steps]); + } + + Future evaluateJavascript({required String source}) async { return bridgeJsObject.callMethod("evaluateJavascript", [source]); } - onIFrameLoaded(String url) async { + Future stopLoading() async { + bridgeJsObject.callMethod("stopLoading"); + } + + Future setSettings(InAppWebViewSettings newSettings) async { + if (settings.iframeAllow != newSettings.iframeAllow) { + iframe.allow = newSettings.iframeAllow; + } + if (settings.iframeAllowFullscreen != newSettings.iframeAllowFullscreen) { + iframe.allowFullscreen = newSettings.iframeAllowFullscreen; + } + if (settings.iframeSandox != newSettings.iframeSandox) { + iframe.setAttribute("sandbox", newSettings.iframeSandox ?? ""); + } + if (settings.iframeWidth != newSettings.iframeWidth) { + iframe.style.width = newSettings.iframeWidth; + } + if (settings.iframeHeight != newSettings.iframeHeight) { + iframe.style.height = newSettings.iframeHeight; + } + if (settings.iframeReferrerPolicy != newSettings.iframeReferrerPolicy) { + iframe.referrerPolicy = newSettings.iframeReferrerPolicy; + } + if (settings.iframeName != newSettings.iframeName) { + iframe.name = newSettings.iframeName; + } + if (settings.iframeCsp != newSettings.iframeCsp) { + iframe.csp = newSettings.iframeCsp; + } + settings = newSettings; + } + + onLoadStart(String url) async { + isLoading = true; + + var obj = { + "url": url + }; + _channel.invokeMethod("onLoadStart", obj); + } + + onLoadStop(String url) async { + isLoading = false; + var obj = { "url": url }; _channel.invokeMethod("onLoadStop", obj); } + + onUpdateVisitedHistory(String url) async { + var obj = { + "url": url + }; + _channel.invokeMethod("onUpdateVisitedHistory", obj); + } } diff --git a/lib/src/web/web_platform.dart b/lib/src/web/web_platform.dart index d40e850c..54c656cf 100644 --- a/lib/src/web/web_platform.dart +++ b/lib/src/web/web_platform.dart @@ -53,9 +53,17 @@ void _dartNativeCommunication(String method, int viewId, [List? args]) { if (WebPlatformManager.webViews.containsKey(viewId)) { var webViewHtmlElement = WebPlatformManager.webViews[viewId] as InAppWebViewWebElement; switch (method) { - case 'iframeLoaded': + case 'onLoadStart': String url = args![0] as String; - webViewHtmlElement.onIFrameLoaded(url); + webViewHtmlElement.onLoadStart(url); + break; + case 'onLoadStop': + String url = args![0] as String; + webViewHtmlElement.onLoadStop(url); + break; + case 'onUpdateVisitedHistory': + String url = args![0] as String; + webViewHtmlElement.onUpdateVisitedHistory(url); break; } }