From 61a439893bdbb8f74dfb9a870c8b7bb7403f901a Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 23 Apr 2022 22:10:02 +0200 Subject: [PATCH] added new events --- example/lib/in_app_webiew_example.screen.dart | 8 +- lib/assets/web/web_support.js | 99 ++++++++++++++++++- lib/flutter_inappwebview_web.dart | 3 + .../in_app_webview_controller.dart | 21 ++++ lib/src/in_app_webview/webview.dart | 21 ++++ lib/src/web/in_app_web_view_web_element.dart | 89 +++++++++++++++-- lib/src/web/web_platform.dart | 51 ++++++++-- 7 files changed, 269 insertions(+), 23 deletions(-) diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index 0a423865..6b1e5b76 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -22,7 +22,8 @@ class _InAppWebViewExampleScreenState extends State { mediaPlaybackRequiresUserGesture: false, useHybridComposition: true, allowsInlineMediaPlayback: true, - iframeAllow: "camera; microphone" + iframeAllow: "camera; microphone", + iframeAllowFullscreen: true ); PullToRefreshController? pullToRefreshController; @@ -46,7 +47,7 @@ class _InAppWebViewExampleScreenState extends State { await webViewController?.clearFocus(); }) ], - options: ContextMenuOptions(hideDefaultSystemContextMenuItems: false), + settings: ContextMenuSettings(hideDefaultSystemContextMenuItems: false), onCreateContextMenu: (hitTestResult) async { print("onCreateContextMenu"); print(hitTestResult.extra); @@ -184,6 +185,9 @@ class _InAppWebViewExampleScreenState extends State { onConsoleMessage: (controller, consoleMessage) { print(consoleMessage); }, + onZoomScaleChanged: (controller, oldScale, newScale) { + print("$oldScale $newScale"); + }, ), !kIsWeb && progress < 1.0 ? LinearProgressIndicator(value: progress) diff --git a/lib/assets/web/web_support.js b/lib/assets/web/web_support.js index 1f393135..b5ba167f 100644 --- a/lib/assets/web/web_support.js +++ b/lib/assets/web/web_support.js @@ -2,11 +2,34 @@ window.flutter_inappwebview = { viewId: null, iframeId: null, iframe: null, + windowAutoincrementId: 0, + windows: {}, + isFullscreen: false, + documentTitle: null, prepare: function () { var iframe = document.getElementById(window.flutter_inappwebview.iframeId); + + document.addEventListener('fullscreenchange', function(event) { + // document.fullscreenElement will point to the element that + // is in fullscreen mode if there is one. If there isn't one, + // the value of the property is null. + if (document.fullscreenElement && document.fullscreenElement.id == window.flutter_inappwebview.iframeId) { + window.flutter_inappwebview.isFullscreen = true; + window.flutter_inappwebview.nativeCommunication('onEnterFullscreen', window.flutter_inappwebview.viewId); + } else if (!document.fullscreenElement && window.flutter_inappwebview.isFullscreen) { + window.flutter_inappwebview.isFullscreen = false; + window.flutter_inappwebview.nativeCommunication('onExitFullscreen', window.flutter_inappwebview.viewId); + } else { + window.flutter_inappwebview.isFullscreen = false; + } + }); + if (iframe != null) { window.flutter_inappwebview.iframe = iframe; iframe.addEventListener('load', function (event) { + window.flutter_inappwebview.windowAutoincrementId = 0; + window.flutter_inappwebview.windows = {}; + try { var oldLogs = { 'log': iframe.contentWindow.console.log, @@ -80,6 +103,41 @@ window.flutter_inappwebview = { console.log(e); } + try { + var originalOpen = iframe.contentWindow.open; + iframe.contentWindow.open = function (url, target, windowFeatures) { + var newWindow = originalOpen.call(iframe.contentWindow, ...arguments); + var windowId = window.flutter_inappwebview.windowAutoincrementId; + 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; + try { + iframeUrl = iframe.contentWindow.location.href; + } catch (e) { + console.log(e); + } + 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; @@ -91,6 +149,45 @@ window.flutter_inappwebview = { } 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]); + new MutationObserver(function(mutations) { + var title = mutations[0].target.nodeValue; + if (title != window.flutter_inappwebview.documentTitle) { + window.flutter_inappwebview.documentTitle = title; + window.flutter_inappwebview.nativeCommunication('onTitleChanged', window.flutter_inappwebview.viewId, [title]); + } + }).observe( + 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; + if(newPixelRatio !== oldPixelRatio){ + window.flutter_inappwebview.nativeCommunication('onZoomScaleChanged', window.flutter_inappwebview.viewId, [oldPixelRatio, newPixelRatio]); + oldPixelRatio = newPixelRatio; + } + }); + } catch (e) { + console.log(e); + } }); } }, @@ -157,4 +254,4 @@ window.flutter_inappwebview = { } } } -}; +}; \ No newline at end of file diff --git a/lib/flutter_inappwebview_web.dart b/lib/flutter_inappwebview_web.dart index 06b6d905..1b1883e1 100755 --- a/lib/flutter_inappwebview_web.dart +++ b/lib/flutter_inappwebview_web.dart @@ -19,7 +19,10 @@ * */ +@JS() library flutter_inappwebview; +import 'package:js/js.dart'; + export 'src/main.dart'; export 'src/web/main.dart'; \ No newline at end of file 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 45a9b02c..90becc6b 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -994,6 +994,27 @@ class InAppWebViewController _inAppBrowser!.onOverScrolled(x, y, clampedX, clampedY); } break; + case "onWindowFocus": + if (_webview != null && _webview!.onWindowFocus != null) + _webview!.onWindowFocus!(this); + else if (_inAppBrowser != null) _inAppBrowser!.onWindowFocus(); + break; + case "onWindowBlur": + if (_webview != null && _webview!.onWindowBlur != null) + _webview!.onWindowBlur!(this); + else if (_inAppBrowser != null) _inAppBrowser!.onWindowBlur(); + break; + case "onPrint": + if ((_webview != null && _webview!.onPrint != null) || + _inAppBrowser != null) { + String? url = call.arguments["url"]; + Uri? uri = url != null ? Uri.parse(url) : null; + if (_webview != null && _webview!.onPrint != null) + _webview!.onPrint!(this, uri); + else + _inAppBrowser!.onPrint(uri); + } + break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; // decode args to json diff --git a/lib/src/in_app_webview/webview.dart b/lib/src/in_app_webview/webview.dart index 631a4cff..be2e64e0 100644 --- a/lib/src/in_app_webview/webview.dart +++ b/lib/src/in_app_webview/webview.dart @@ -188,9 +188,13 @@ abstract class WebView { ///Also, note that calling [InAppWebViewController.setSettings] method using the controller of the new created WebView, ///it will update also the WebView options of the caller WebView. /// + ///**NOTE for Web**: this event will be called only if the iframe has the same origin. + ///Also, there is no way to block the opening the window in a synchronous way, so returning `true` will just close it quickly. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebChromeClient.onCreateWindow](https://developer.android.com/reference/android/webkit/WebChromeClient#onCreateWindow(android.webkit.WebView,%20boolean,%20boolean,%20android.os.Message))) ///- iOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1536907-webview)) + ///- Web final Future Function(InAppWebViewController controller, CreateWindowAction createWindowAction)? onCreateWindow; @@ -205,17 +209,23 @@ abstract class WebView { ///Event fired when the JavaScript `window` object of the WebView has received focus. ///This is the result of the `focus` JavaScript event applied to the `window` object. /// + ///**NOTE for Web**: this event will be called only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web ([Official API - Window.onfocus](https://developer.mozilla.org/en-US/docs/Web/API/Window/focus_event)) final void Function(InAppWebViewController controller)? onWindowFocus; ///Event fired when the JavaScript `window` object of the WebView has lost focus. ///This is the result of the `blur` JavaScript event applied to the `window` object. /// + ///**NOTE for Web**: this event will be called only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web ([Official API - Window.onblur](https://developer.mozilla.org/en-US/docs/Web/API/Window/blur_event)) final void Function(InAppWebViewController controller)? onWindowBlur; ///Event fired when javascript calls the `alert()` method to display an alert dialog. @@ -400,9 +410,12 @@ abstract class WebView { /// ///[url] represents the url on which is called. /// + ///**NOTE for Web**: this event will be called only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web final void Function(InAppWebViewController controller, Uri? url)? onPrint; ///Event fired when an HTML element of the webview has been clicked and held. @@ -420,6 +433,7 @@ abstract class WebView { ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebChromeClient.onShowCustomView](https://developer.android.com/reference/android/webkit/WebChromeClient#onShowCustomView(android.view.View,%20android.webkit.WebChromeClient.CustomViewCallback))) ///- iOS ([Official API - UIWindow.didBecomeVisibleNotification](https://developer.apple.com/documentation/uikit/uiwindow/1621621-didbecomevisiblenotification)) + ///- Web ([Official API - Document.onfullscreenchange](https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenchange_event)) final void Function(InAppWebViewController controller)? onEnterFullscreen; ///Event fired when the current page has exited full screen mode. @@ -431,6 +445,7 @@ abstract class WebView { ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebChromeClient.onHideCustomView](https://developer.android.com/reference/android/webkit/WebChromeClient#onHideCustomView())) ///- iOS ([Official API - UIWindow.didBecomeHiddenNotification](https://developer.apple.com/documentation/uikit/uiwindow/1621617-didbecomehiddennotification)) + ///- Web ([Official API - Document.onfullscreenchange](https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenchange_event)) final void Function(InAppWebViewController controller)? onExitFullscreen; ///Called when the web view begins to receive web content. @@ -450,9 +465,12 @@ abstract class WebView { /// ///[title] represents the string containing the new title of the document. /// + ///**NOTE for Web**: this event will be called only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebChromeClient.onReceivedTitle](https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedTitle(android.webkit.WebView,%20java.lang.String))) ///- iOS + ///- Web final void Function(InAppWebViewController controller, String? title)? onTitleChanged; @@ -478,9 +496,12 @@ abstract class WebView { /// ///[newScale] The new zoom scale factor. /// + ///**NOTE for Web**: this event will be called only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onScaleChanged](https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float))) ///- iOS ([Official API - UIScrollViewDelegate.scrollViewDidZoom](https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619409-scrollviewdidzoom)) + ///- Web final void Function( InAppWebViewController controller, double oldScale, double newScale)? onZoomScaleChanged; 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 5c017b83..21808c84 100644 --- a/lib/src/web/in_app_web_view_web_element.dart +++ b/lib/src/web/in_app_web_view_web_element.dart @@ -201,40 +201,40 @@ class InAppWebViewWebElement { settings = newSettings; } - onLoadStart(String url) async { + void onLoadStart(String url) async { isLoading = true; var obj = { "url": url }; - _channel.invokeMethod("onLoadStart", obj); + await _channel.invokeMethod("onLoadStart", obj); } - onLoadStop(String url) async { + void onLoadStop(String url) async { isLoading = false; var obj = { "url": url }; - _channel.invokeMethod("onLoadStop", obj); + await _channel.invokeMethod("onLoadStop", obj); } - onUpdateVisitedHistory(String url) async { + void onUpdateVisitedHistory(String url) async { var obj = { "url": url }; - _channel.invokeMethod("onUpdateVisitedHistory", obj); + await _channel.invokeMethod("onUpdateVisitedHistory", obj); } - onScrollChanged(int x, int y) async { + void onScrollChanged(int x, int y) async { var obj = { "x": x, "y": y }; - _channel.invokeMethod("onScrollChanged", obj); + await _channel.invokeMethod("onScrollChanged", obj); } - onConsoleMessage(String type, String? message) async { + void onConsoleMessage(String type, String? message) async { int messageLevel = 1; switch (type) { case 'debug': @@ -255,6 +255,75 @@ class InAppWebViewWebElement { "messageLevel": messageLevel, "message": message }; - _channel.invokeMethod("onConsoleMessage", obj); + await _channel.invokeMethod("onConsoleMessage", obj); + } + + Future onCreateWindow(int windowId, String url, String? target, String? windowFeatures) async { + Map windowFeaturesMap = {}; + List features = windowFeatures?.split(",") ?? []; + for (var feature in features) { + var keyValue = feature.trim().split("="); + if (keyValue.length == 2) { + var key = keyValue[0].trim(); + var value = keyValue[1].trim(); + if (['height', 'width', 'x', 'y'].contains(key)) { + windowFeaturesMap[key] = double.parse(value); + } else { + windowFeaturesMap[key] = value; + } + } + } + + var obj = { + "windowId": windowId, + "isForMainFrame": true, + "request": { + "url": url, + "method": "GET" + }, + "windowFeatures": windowFeaturesMap + }; + return await _channel.invokeMethod("onCreateWindow", obj); + } + + void onWindowFocus() async { + await _channel.invokeMethod("onWindowFocus"); + } + + void onWindowBlur() async { + await _channel.invokeMethod("onWindowBlur"); + } + + void onPrint(String? url) async { + var obj = { + "url": url + }; + + await _channel.invokeMethod("onPrint", obj); + } + + void onEnterFullscreen() async { + await _channel.invokeMethod("onEnterFullscreen"); + } + + void onExitFullscreen() async { + await _channel.invokeMethod("onExitFullscreen"); + } + + void onTitleChanged(String? title) async { + var obj = { + "title": title + }; + + await _channel.invokeMethod("onTitleChanged", obj); + } + + void onZoomScaleChanged(double oldScale, double newScale) async { + var obj = { + "oldScale": oldScale, + "newScale": newScale + }; + + await _channel.invokeMethod("onZoomScaleChanged", obj); } } diff --git a/lib/src/web/web_platform.dart b/lib/src/web/web_platform.dart index 245f3598..8acf5541 100644 --- a/lib/src/web/web_platform.dart +++ b/lib/src/web/web_platform.dart @@ -43,38 +43,69 @@ 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); +external set _nativeCommunication(Future Function(String method, int viewId, [List? args]) f); /// Allows calling the assigned function from Dart as well. @JS() -external void nativeCommunication(); +external Future nativeCommunication(String method, int viewId, [List? args]); -void _dartNativeCommunication(String method, int viewId, [List? args]) { +Future _dartNativeCommunication(String method, int viewId, [List? args]) async { if (WebPlatformManager.webViews.containsKey(viewId)) { var webViewHtmlElement = WebPlatformManager.webViews[viewId] as InAppWebViewWebElement; switch (method) { case 'onLoadStart': - String url = args![0] as String; + var url = args![0] as String; webViewHtmlElement.onLoadStart(url); break; case 'onLoadStop': - String url = args![0] as String; + var url = args![0] as String; webViewHtmlElement.onLoadStop(url); break; case 'onUpdateVisitedHistory': - String url = args![0] as String; + var url = args![0] as String; webViewHtmlElement.onUpdateVisitedHistory(url); break; case 'onScrollChanged': - int x = args![0] as int; - int y = args[1] as int; + var x = (args![0] as double).toInt(); + var y = (args[1] as double).toInt(); webViewHtmlElement.onScrollChanged(x, y); break; case 'onConsoleMessage': - String type = args![0] as String; - String? message = args[1] as String?; + var type = args![0] as String; + var message = args[1] as String?; webViewHtmlElement.onConsoleMessage(type, message); break; + case 'onCreateWindow': + var windowId = args![0] as int; + var url = args[1] as String? ?? 'about:blank'; + var target = args[2] as String?; + var windowFeatures = args[3] as String?; + return await webViewHtmlElement.onCreateWindow(windowId, url, target, windowFeatures); + case 'onWindowFocus': + webViewHtmlElement.onWindowFocus(); + break; + case 'onWindowBlur': + webViewHtmlElement.onWindowBlur(); + break; + case 'onPrint': + var url = args![0] as String?; + webViewHtmlElement.onPrint(url); + break; + case 'onEnterFullscreen': + webViewHtmlElement.onEnterFullscreen(); + break; + case 'onExitFullscreen': + webViewHtmlElement.onExitFullscreen(); + break; + case 'onTitleChanged': + var title = args![0] as String?; + webViewHtmlElement.onTitleChanged(title); + break; + case 'onZoomScaleChanged': + var oldScale = args![0] as double; + var newScale = args[1] as double; + webViewHtmlElement.onZoomScaleChanged(oldScale, newScale); + break; } } } \ No newline at end of file