diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index 6b1e5b76..a1135930 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -23,7 +23,8 @@ class _InAppWebViewExampleScreenState extends State { useHybridComposition: true, allowsInlineMediaPlayback: true, iframeAllow: "camera; microphone", - iframeAllowFullscreen: true + iframeAllowFullscreen: true, + javaScriptCanOpenWindowsAutomatically: false ); PullToRefreshController? pullToRefreshController; diff --git a/example/web/page.html b/example/web/page.html index 5410bbfc..20f71151 100644 --- a/example/web/page.html +++ b/example/web/page.html @@ -28,6 +28,7 @@ window.addEventListener('load', function (event) { setTimeout(function () { console.log('test'); + window.open('https://google.com'); }); }); diff --git a/lib/assets/web/web_support.js b/lib/assets/web/web_support.js index b5ba167f..3fd67dc9 100644 --- a/lib/assets/web/web_support.js +++ b/lib/assets/web/web_support.js @@ -6,7 +6,8 @@ window.flutter_inappwebview = { windows: {}, isFullscreen: false, documentTitle: null, - prepare: function () { + functionMap: {}, + prepare: function (settings) { var iframe = document.getElementById(window.flutter_inappwebview.iframeId); document.addEventListener('fullscreenchange', function(event) { @@ -30,6 +31,14 @@ window.flutter_inappwebview = { window.flutter_inappwebview.windowAutoincrementId = 0; window.flutter_inappwebview.windows = {}; + var url = iframe.src; + try { + url = iframe.contentWindow.location.href; + } catch (e) { + console.log(e); + } + window.flutter_inappwebview.nativeCommunication('onLoadStart', window.flutter_inappwebview.viewId, [url]); + try { var oldLogs = { 'log': iframe.contentWindow.console.log, @@ -58,24 +67,6 @@ window.flutter_inappwebview = { console.log(e); } - var url = iframe.src; - try { - url = iframe.contentWindow.location.href; - } catch (e) { - console.log(e); - } - 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) { @@ -88,6 +79,7 @@ window.flutter_inappwebview = { } 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); @@ -99,11 +91,7 @@ window.flutter_inappwebview = { } window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]); }; - } catch (e) { - console.log(e); - } - try { var originalOpen = iframe.contentWindow.open; iframe.contentWindow.open = function (url, target, windowFeatures) { var newWindow = originalOpen.call(iframe.contentWindow, ...arguments); @@ -111,18 +99,13 @@ window.flutter_inappwebview = { window.flutter_inappwebview.windowAutoincrementId++; window.flutter_inappwebview.windows[windowId] = newWindow; window.flutter_inappwebview.nativeCommunication('onCreateWindow', window.flutter_inappwebview.viewId, [windowId, url, target, windowFeatures]).then(function(){}, function(handledByClient) { - console.log(handledByClient); if (handledByClient) { newWindow.close(); } }); return newWindow; }; - } catch (e) { - console.log(e); - } - try { var originalPrint = iframe.contentWindow.print; iframe.contentWindow.print = function () { var iframeUrl = iframe.src; @@ -134,31 +117,14 @@ window.flutter_inappwebview = { window.flutter_inappwebview.nativeCommunication('onPrint', window.flutter_inappwebview.viewId, [iframeUrl]); originalPrint.call(iframe.contentWindow); }; - } catch (e) { - console.log(e); - } - iframe.contentWindow.addEventListener('scroll', function (event) { - var x = 0; - var y = 0; - try { - x = iframe.contentWindow.scrollX; - y = iframe.contentWindow.scrollY; - } catch (e) { - console.log(e); + window.flutter_inappwebview.functionMap = { + "window.open": iframe.contentWindow.open, + "window.print": iframe.contentWindow.print, + "window.history.pushState": iframe.contentWindow.history.pushState, + "window.history.replaceState": iframe.contentWindow.history.replaceState, } - window.flutter_inappwebview.nativeCommunication('onScrollChanged', window.flutter_inappwebview.viewId, [x, y]); - }); - iframe.contentWindow.addEventListener('focus', function (event) { - window.flutter_inappwebview.nativeCommunication('onWindowFocus', window.flutter_inappwebview.viewId); - }); - - iframe.contentWindow.addEventListener('blur', function (event) { - window.flutter_inappwebview.nativeCommunication('onWindowBlur', window.flutter_inappwebview.viewId); - }); - - try { var initialTitle = iframe.contentDocument.title; window.flutter_inappwebview.documentTitle = initialTitle; window.flutter_inappwebview.nativeCommunication('onTitleChanged', window.flutter_inappwebview.viewId, [initialTitle]); @@ -172,11 +138,7 @@ window.flutter_inappwebview = { iframe.contentDocument.querySelector('title'), { subtree: true, characterData: true, childList: true } ); - } catch (e) { - console.log(e); - } - try { var oldPixelRatio = iframe.contentWindow.devicePixelRatio; iframe.contentWindow.addEventListener('resize', function (e) { var newPixelRatio = iframe.contentWindow.devicePixelRatio; @@ -185,12 +147,70 @@ window.flutter_inappwebview = { oldPixelRatio = newPixelRatio; } }); + + 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]); + }); + + iframe.contentWindow.addEventListener('scroll', function (event) { + var x = 0; + var y = 0; + try { + x = iframe.contentWindow.scrollX; + y = iframe.contentWindow.scrollY; + } catch (e) { + console.log(e); + } + window.flutter_inappwebview.nativeCommunication('onScrollChanged', window.flutter_inappwebview.viewId, [x, y]); + }); + + iframe.contentWindow.addEventListener('focus', function (event) { + window.flutter_inappwebview.nativeCommunication('onWindowFocus', window.flutter_inappwebview.viewId); + }); + + iframe.contentWindow.addEventListener('blur', function (event) { + window.flutter_inappwebview.nativeCommunication('onWindowBlur', window.flutter_inappwebview.viewId); + }); } catch (e) { console.log(e); } + + try { + if (!settings.javaScriptCanOpenWindowsAutomatically) { + iframe.contentWindow.open = function () { + throw new Error('JavaScript cannot open windows automatically'); + }; + } + } catch (e) { + console.log(e); + } + + window.flutter_inappwebview.nativeCommunication('onLoadStop', window.flutter_inappwebview.viewId, [url]); }); } }, + setSettings: function (settings, newSettings) { + var iframe = window.flutter_inappwebview.iframe; + try { + if (settings.javaScriptCanOpenWindowsAutomatically != newSettings.javaScriptCanOpenWindowsAutomatically) { + if (!newSettings.javaScriptCanOpenWindowsAutomatically) { + iframe.contentWindow.open = function () { + throw new Error('JavaScript cannot open windows automatically'); + }; + } else { + iframe.contentWindow.open = window.flutter_inappwebview.functionMap["window.open"]; + } + } + } catch (e) { + console.log(e); + } + }, reload: function () { var iframe = window.flutter_inappwebview.iframe; if (iframe != null && iframe.contentWindow != null) { @@ -237,10 +257,8 @@ window.flutter_inappwebview = { var result = null; if (iframe != null) { try { - result = iframe.contentWindow.eval(source); - } catch (e) { - console.log(e); - } + result = JSON.stringify(iframe.contentWindow.eval(source)); + } catch (e) {} } return result; }, 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 90becc6b..de7b2b5a 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1552,7 +1552,7 @@ 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. + ///**NOTE for 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())) @@ -1565,6 +1565,8 @@ class InAppWebViewController ///Goes back in the history of the WebView. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**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)) @@ -1586,6 +1588,8 @@ class InAppWebViewController ///Goes forward in the history of the WebView. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**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)) @@ -1607,6 +1611,8 @@ class InAppWebViewController ///Goes to the history item that is the number of steps away from the current item. Steps is negative if backward and positive if forward. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**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)) @@ -1630,6 +1636,8 @@ class InAppWebViewController ///Navigates to a [WebHistoryItem] from the back-forward [WebHistory.list] and sets it as the current item. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS @@ -1654,9 +1662,12 @@ class InAppWebViewController ///Stops the WebView from loading. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebView.stopLoading](https://developer.android.com/reference/android/webkit/WebView#stopLoading())) ///- iOS ([Official API - WKWebView.stopLoading](https://developer.apple.com/documentation/webkit/wkwebview/1414981-stoploading)) + ///- Web ([Official API - Window.stop](https://developer.mozilla.org/en-US/docs/Web/API/Window/stop)) Future stopLoading() async { Map args = {}; await _channel.invokeMethod('stopLoading', args); @@ -1677,6 +1688,8 @@ class InAppWebViewController ///Instead, you should call this method, for example, inside the [WebView.onLoadStop] event or in any other events ///where you know the page is ready "enough". /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**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)) @@ -1687,7 +1700,7 @@ class InAppWebViewController args.putIfAbsent('source', () => source); args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); var data = await _channel.invokeMethod('evaluateJavascript', args); - if (data != null && defaultTargetPlatform == TargetPlatform.android) { + if (data != null && (defaultTargetPlatform == TargetPlatform.android || kIsWeb)) { try { // try to json decode the data coming from JavaScript // otherwise return it as it is. diff --git a/lib/src/in_app_webview/in_app_webview_settings.dart b/lib/src/in_app_webview/in_app_webview_settings.dart index dc3e2150..9e57160e 100755 --- a/lib/src/in_app_webview/in_app_webview_settings.dart +++ b/lib/src/in_app_webview/in_app_webview_settings.dart @@ -87,13 +87,17 @@ class InAppWebViewSettings ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web bool javaScriptEnabled; ///Set to `true` to allow JavaScript open windows without user interaction. The default value is `false`. /// + ///**NOTE for Web**: this setting will have effect only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web bool javaScriptCanOpenWindowsAutomatically; ///Set to `true` to prevent HTML5 audio or video from autoplaying. The default value is `true`. @@ -995,31 +999,31 @@ class InAppWebViewSettings ///- iOS bool upgradeKnownHostsToHTTPS; - ///Specifies a feature policy for the iframe. A list of origins the frame is allowed to display content from. - ///This attribute also accepts the values `self` and `src` which represent the origin in the iframe's src attribute. - ///The default value is `src`. + ///Specifies a feature policy for the `