From 68f25d0d4d49c4df54412ef0aff6994c18c2a8db Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Wed, 27 Apr 2022 16:59:49 +0200 Subject: [PATCH] updated web support --- .gitignore | 4 +- example/lib/in_app_webiew_example.screen.dart | 14 +- example/web/heavy-page.html | 2 +- example/web/page-2.html | 2 +- example/web/page.html | 2 +- lib/assets/web/web_support.js | 156 ++++++++++++++++-- .../in_app_webview_controller.dart | 90 ++++++++-- lib/src/web/in_app_web_view_web_element.dart | 110 +++++++++++- lib/src/web/web_platform.dart | 38 +++-- scripts/test.sh | 11 +- scripts/test_and_log.sh | 2 +- 11 files changed, 375 insertions(+), 56 deletions(-) diff --git a/.gitignore b/.gitignore index 4bf314d6..cac25594 100755 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,6 @@ build/ .fvm/ -flutter_driver_tests.log \ No newline at end of file +flutter_driver_tests.log +tool/chromedriver +tool/chromedriver.* \ No newline at end of file diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index c3a4a746..7dd0595c 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -1,4 +1,5 @@ import 'dart:collection'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; @@ -114,16 +115,17 @@ class _InAppWebViewExampleScreenState extends State { children: [ InAppWebView( key: webViewKey, + // initialUrlRequest: + // URLRequest(url: Uri.parse("https://flutter.dev")), initialUrlRequest: - URLRequest(url: Uri.parse("https://flutter.dev")), + URLRequest(url: Uri.parse(Uri.base.toString().replaceFirst("/#/", "/") + 'page.html')), // initialFile: "assets/index.html", initialUserScripts: UnmodifiableListView([]), initialSettings: settings, // contextMenu: contextMenu, pullToRefreshController: pullToRefreshController, - onWebViewCreated: (controller) async { + onWebViewCreated: (controller) { webViewController = controller; - print(await controller.getUrl()); }, onLoadStart: (controller, url) async { setState(() { @@ -149,10 +151,10 @@ class _InAppWebViewExampleScreenState extends State { "javascript", "about" ].contains(uri.scheme)) { - if (await canLaunch(url)) { + if (await canLaunchUrl(uri)) { // Launch the App - await launch( - url, + await launchUrl( + uri, ); // and cancel the request return NavigationActionPolicy.CANCEL; diff --git a/example/web/heavy-page.html b/example/web/heavy-page.html index d3c8c60f..2abc8c90 100644 --- a/example/web/heavy-page.html +++ b/example/web/heavy-page.html @@ -16,7 +16,7 @@ - flutter_inappwebview_example + heavy page diff --git a/example/web/page-2.html b/example/web/page-2.html index 04d21a57..281a589b 100644 --- a/example/web/page-2.html +++ b/example/web/page-2.html @@ -15,7 +15,7 @@ - flutter_inappwebview_example + page 2 diff --git a/example/web/page.html b/example/web/page.html index 8131b788..33d8aecb 100644 --- a/example/web/page.html +++ b/example/web/page.html @@ -16,7 +16,7 @@ - flutter_inappwebview_example + page diff --git a/lib/assets/web/web_support.js b/lib/assets/web/web_support.js index 4b829c24..472c9cae 100644 --- a/lib/assets/web/web_support.js +++ b/lib/assets/web/web_support.js @@ -16,7 +16,7 @@ window.flutter_inappwebview = { event.stopPropagation(); return false; }, - prepare: function (settings) { + prepare: function(settings) { webView.settings = settings; var iframe = document.getElementById(iframeId); @@ -117,7 +117,7 @@ window.flutter_inappwebview = { }; var originalPrint = iframe.contentWindow.print; - iframe.contentWindow.print = function () { + iframe.contentWindow.print = function() { var iframeUrl = iframe.src; try { iframeUrl = iframe.contentWindow.location.href; @@ -194,7 +194,7 @@ window.flutter_inappwebview = { try { if (!webView.settings.javaScriptCanOpenWindowsAutomatically) { - iframe.contentWindow.open = function () { + iframe.contentWindow.open = function() { throw new Error('JavaScript cannot open windows automatically'); }; } @@ -231,12 +231,12 @@ window.flutter_inappwebview = { }); } }, - setSettings: function (newSettings) { + setSettings: function(newSettings) { var iframe = webView.iframe; try { if (webView.settings.javaScriptCanOpenWindowsAutomatically != newSettings.javaScriptCanOpenWindowsAutomatically) { if (!newSettings.javaScriptCanOpenWindowsAutomatically) { - iframe.contentWindow.open = function () { + iframe.contentWindow.open = function() { throw new Error('JavaScript cannot open windows automatically'); }; } else { @@ -294,7 +294,7 @@ window.flutter_inappwebview = { webView.settings = newSettings; }, - reload: function () { + reload: function() { var iframe = webView.iframe; if (iframe != null && iframe.contentWindow != null) { try { @@ -305,7 +305,7 @@ window.flutter_inappwebview = { } } }, - goBack: function () { + goBack: function() { var iframe = webView.iframe; if (iframe != null) { try { @@ -315,7 +315,7 @@ window.flutter_inappwebview = { } } }, - goForward: function () { + goForward: function() { var iframe = webView.iframe; if (iframe != null) { try { @@ -325,7 +325,7 @@ window.flutter_inappwebview = { } } }, - goForwardOrForward: function (steps) { + goForwardOrForward: function(steps) { var iframe = webView.iframe; if (iframe != null) { try { @@ -335,7 +335,7 @@ window.flutter_inappwebview = { } } }, - evaluateJavascript: function (source) { + evaluateJavascript: function(source) { var iframe = webView.iframe; var result = null; if (iframe != null) { @@ -345,7 +345,7 @@ window.flutter_inappwebview = { } return result; }, - stopLoading: function (steps) { + stopLoading: function(steps) { var iframe = webView.iframe; if (iframe != null) { try { @@ -355,7 +355,7 @@ window.flutter_inappwebview = { } } }, - getUrl: function () { + getUrl: function() { var iframe = webView.iframe; var url = iframe.src; try { @@ -365,6 +365,138 @@ window.flutter_inappwebview = { } return url; }, + getTitle: function() { + var iframe = webView.iframe; + var title = null; + try { + title = iframe.contentDocument.title; + } catch (e) { + console.log(e); + } + return title; + }, + injectJavascriptFileFromUrl: function(urlFile, scriptHtmlTagAttributes) { + var iframe = webView.iframe; + try { + var d = iframe.contentDocument; + var script = d.createElement('script'); + for (var key of Object.keys(scriptHtmlTagAttributes)) { + if (scriptHtmlTagAttributes[key] != null) { + script[key] = scriptHtmlTagAttributes[key]; + } + } + if (script.id != null) { + script.onload = function() { + window.flutter_inappwebview.nativeCommunication('onInjectedScriptLoaded', webView.viewId, [script.id]); + } + script.onerror = function() { + window.flutter_inappwebview.nativeCommunication('onInjectedScriptError', webView.viewId, [script.id]); + } + } + script.src = urlFile; + if (d.body != null) { + d.body.appendChild(script); + } + } catch (e) { + console.log(e); + } + }, + injectCSSCode: function(source) { + var iframe = webView.iframe; + try { + var d = iframe.contentDocument; + var style = d.createElement('style'); + style.innerHTML = source; + if (d.head != null) { + d.head.appendChild(style); + } + } catch (e) { + console.log(e); + } + }, + injectCSSFileFromUrl: function(urlFile, cssLinkHtmlTagAttributes) { + var iframe = webView.iframe; + try { + var d = iframe.contentDocument; + var link = d.createElement('link'); + for (var key of Object.keys(cssLinkHtmlTagAttributes)) { + if (cssLinkHtmlTagAttributes[key] != null) { + link[key] = cssLinkHtmlTagAttributes[key]; + } + } + link.type = 'text/css'; + var alternateStylesheet = ""; + if (cssLinkHtmlTagAttributes.alternateStylesheet) { + alternateStylesheet = "alternate "; + } + link.rel = alternateStylesheet + "stylesheet"; + link.href = urlFile; + if (d.head != null) { + d.head.appendChild(link); + } + } catch (e) { + console.log(e); + } + }, + scrollTo: function(x, y, animated) { + var iframe = webView.iframe; + try { + if (animated) { + iframe.contentWindow.scrollTo({top: y, left: x, behavior: 'smooth'}); + } else { + iframe.contentWindow.scrollTo(x, y); + } + } catch (e) { + console.log(e); + } + }, + scrollBy: function(x, y, animated) { + var iframe = webView.iframe; + try { + if (animated) { + iframe.contentWindow.scrollBy({top: y, left: x, behavior: 'smooth'}); + } else { + iframe.contentWindow.scrollBy(x, y); + } + } catch (e) { + console.log(e); + } + }, + printCurrentPage: function() { + var iframe = webView.iframe; + try { + iframe.contentWindow.print(); + } catch (e) { + console.log(e); + } + }, + getContentHeight: function() { + var iframe = webView.iframe; + try { + return iframe.contentDocument.documentElement.scrollHeight; + } catch (e) { + console.log(e); + } + return null; + }, + getSelectedText: function() { + var iframe = webView.iframe; + try { + var txt; + var w = iframe.contentWindow; + if (w.getSelection) { + txt = w.getSelection().toString(); + } else if (w.document.getSelection) { + txt = w.document.getSelection().toString(); + } else if (w.document.selection) { + txt = w.document.selection.createRange().text; + } + return txt; + } catch (e) { + console.log(e); + } + return null; + }, }; return webView; 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 6570bdf4..2b52e0a1 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1034,6 +1034,22 @@ class InAppWebViewController _inAppBrowser!.onPrint(uri); } break; + case "onInjectedScriptLoaded": + String id = call.arguments[0]; + var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; + if ((_webview != null || _inAppBrowser != null) && + onLoadCallback != null) { + onLoadCallback(); + } + return null; + case "onInjectedScriptError": + String id = call.arguments[0]; + var onErrorCallback = _injectedScriptsFromURL[id]?.onError; + if ((_webview != null || _inAppBrowser != null) && + onErrorCallback != null) { + onErrorCallback(); + } + return null; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; // decode args to json @@ -1165,7 +1181,7 @@ class InAppWebViewController try { return jsonEncode(await javaScriptHandlersMap[handlerName]!(args)); } catch (error) { - print(error); + developer.log(error.toString(), name: this.runtimeType.toString()); return null; } } @@ -1194,9 +1210,12 @@ class InAppWebViewController ///Gets the title for the current page. /// + ///**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.getTitle](https://developer.android.com/reference/android/webkit/WebView#getTitle())) ///- iOS ([Official API - WKWebView.title](https://developer.apple.com/documentation/webkit/wkwebview/1415015-title)) + ///- Web Future getTitle() async { Map args = {}; return await _channel.invokeMethod('getTitle', args); @@ -1222,6 +1241,7 @@ class InAppWebViewController ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web Future getHtml() async { String? html; @@ -1245,13 +1265,13 @@ class InAppWebViewController html = utf8.decode(bytes.buffer.asUint8List()); } catch (e) {} } else { - HttpClient client = new HttpClient(); try { + HttpClient client = HttpClient(); var htmlRequest = await client.getUrl(webviewUrl); html = await (await htmlRequest.close()).transform(Utf8Decoder()).join(); } catch (e) { - print(e); + developer.log(e.toString(), name: this.runtimeType.toString()); } } @@ -1260,13 +1280,15 @@ class InAppWebViewController ///Gets the list of all favicons for the current page. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web Future> getFavicons() async { List favicons = []; - HttpClient client = new HttpClient(); var webviewUrl = await getUrl(); if (webviewUrl == null) { @@ -1336,6 +1358,7 @@ class InAppWebViewController // try to get /favicon.ico try { + HttpClient client = HttpClient(); var faviconUrl = webviewUrl.scheme + "://" + webviewUrl.host + "/favicon.ico"; var faviconUri = Uri.parse(faviconUrl); @@ -1345,8 +1368,8 @@ class InAppWebViewController favicons.add(Favicon(url: faviconUri, rel: "shortcut icon")); } } catch (e) { - print("/favicon.ico file not found: " + e.toString()); - // print(stacktrace); + developer.log("/favicon.ico file not found: " + e.toString(), + name: this.runtimeType.toString()); } // try to get the manifest file @@ -1358,13 +1381,14 @@ class InAppWebViewController webviewUrl.scheme + "://" + webviewUrl.host + "/manifest.json"; } try { + HttpClient client = HttpClient(); manifestRequest = await client.getUrl(Uri.parse(manifestUrl)); manifestResponse = await manifestRequest.close(); manifestFound = manifestResponse.statusCode == 200 && manifestResponse.headers.contentType?.mimeType == "application/json"; } catch (e) { - print("Manifest file not found: " + e.toString()); - // print(stacktrace); + developer.log("Manifest file not found: " + e.toString(), + name: this.runtimeType.toString()); } if (manifestFound) { @@ -1470,9 +1494,12 @@ class InAppWebViewController ///controller.postUrl(url: Uri.parse("https://www.example.com/"), postData: postData); ///``` /// + ///**NOTE for Web**: it will try to create an XMLHttpRequest and load the result inside the iframe. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebView.postUrl](https://developer.android.com/reference/android/webkit/WebView#postUrl(java.lang.String,%20byte[]))) ///- iOS + ///- Web Future postUrl({required Uri url, required Uint8List postData}) async { assert(url.toString().isNotEmpty); Map args = {}; @@ -1745,9 +1772,12 @@ 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 ///- iOS + ///- Web Future injectJavascriptFileFromUrl( {required Uri urlFile, ScriptHtmlTagAttributes? scriptHtmlTagAttributes}) async { @@ -1770,9 +1800,12 @@ 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 ///- iOS + ///- Web Future injectJavascriptFileFromAsset( {required String assetFilePath}) async { String source = await rootBundle.loadString(assetFilePath); @@ -1786,9 +1819,12 @@ 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 ///- iOS + ///- Web Future injectCSSCode({required String source}) async { Map args = {}; args.putIfAbsent('source', () => source); @@ -1804,9 +1840,12 @@ 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 ///- iOS + ///- Web Future injectCSSFileFromUrl( {required Uri urlFile, CSSLinkHtmlTagAttributes? cssLinkHtmlTagAttributes}) async { @@ -1825,9 +1864,12 @@ 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 ///- iOS + ///- Web Future injectCSSFileFromAsset({required String assetFilePath}) async { String source = await rootBundle.loadString(assetFilePath); await injectCSSCode(source: source); @@ -2076,9 +2118,12 @@ class InAppWebViewController /// ///[animated] `true` to animate the scroll transition, `false` to make the scoll transition immediate. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - View.scrollTo](https://developer.android.com/reference/android/view/View#scrollTo(int,%20int))) ///- iOS ([Official API - UIScrollView.setContentOffset](https://developer.apple.com/documentation/uikit/uiscrollview/1619400-setcontentoffset)) + ///- Web ([Official API - Window.scrollTo](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo)) Future scrollTo( {required int x, required int y, bool animated = false}) async { Map args = {}; @@ -2096,9 +2141,12 @@ class InAppWebViewController /// ///[animated] `true` to animate the scroll transition, `false` to make the scoll transition immediate. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - View.scrollBy](https://developer.android.com/reference/android/view/View#scrollBy(int,%20int))) ///- iOS ([Official API - UIScrollView.setContentOffset](https://developer.apple.com/documentation/uikit/uiscrollview/1619400-setcontentoffset)) + ///- Web ([Official API - Window.scrollBy](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollBy)) Future scrollBy( {required int x, required int y, bool animated = false}) async { Map args = {}; @@ -2137,9 +2185,12 @@ class InAppWebViewController /// ///**NOTE**: available on Android 21+. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - PrintManager](https://developer.android.com/reference/android/print/PrintManager)) ///- iOS ([Official API - UIPrintInteractionController](https://developer.apple.com/documentation/uikit/uiprintinteractioncontroller)) + ///- Web ([Official API - Window.print](https://developer.mozilla.org/en-US/docs/Web/API/Window/print)) Future printCurrentPage() async { Map args = {}; await _channel.invokeMethod('printCurrentPage', args); @@ -2147,9 +2198,12 @@ class InAppWebViewController ///Gets the height of the HTML content. /// + ///**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.getContentHeight](https://developer.android.com/reference/android/webkit/WebView#getContentHeight())) ///- iOS ([Official API - UIScrollView.contentSize](https://developer.apple.com/documentation/uikit/uiscrollview/1619399-contentsize)) + ///- Web ([Official API - Document.documentElement.scrollHeight](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight)) Future getContentHeight() async { Map args = {}; return await _channel.invokeMethod('getContentHeight', args); @@ -2186,9 +2240,12 @@ class InAppWebViewController ///This is not always the same as the URL passed to [InAppWebView.onLoadStart] because although the load for that URL has begun, ///the current page may not have changed. Also, there may have been redirects resulting in a different URL to that originally requested. /// + ///**NOTE for Web**: it will return the current value of the `iframe.src` attribute. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebView.getOriginalUrl](https://developer.android.com/reference/android/webkit/WebView#getOriginalUrl())) ///- iOS + ///- Web Future getOriginalUrl() async { Map args = {}; String? url = await _channel.invokeMethod('getOriginalUrl', args); @@ -2217,9 +2274,12 @@ class InAppWebViewController /// ///**NOTE for Android native WebView**: available only on Android 19+. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web Future getSelectedText() async { Map args = {}; return await _channel.invokeMethod('getSelectedText', args); @@ -2314,9 +2374,12 @@ class InAppWebViewController /// ///**NOTE**: It is implemented using JavaScript. /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS + ///- Web Future> getMetaTags() async { List metaTags = []; @@ -2379,11 +2442,14 @@ class InAppWebViewController ///Returns an instance of [Color] representing the `content` value of the ///`` tag of the current WebView, if available, otherwise `null`. /// - ///**NOTE**: on Android and iOS < 15.0, it is implemented using JavaScript. + ///**NOTE**: on Android, Web and iOS < 15.0, it is implemented using JavaScript. + /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS ([Official API - WKWebView.themeColor](https://developer.apple.com/documentation/webkit/wkwebview/3794258-themecolor)) + ///- Web Future getMetaThemeColor() async { Color? themeColor; @@ -2391,16 +2457,12 @@ class InAppWebViewController Map args = {}; themeColor = UtilColor.fromStringRepresentation( await _channel.invokeMethod('getMetaThemeColor', args)); + return themeColor; } catch (e) { // not implemented } - if (themeColor != null) { - return themeColor; - } - // try using javascript - var metaTags = await getMetaTags(); MetaTag? metaTagThemeColor; 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 0456a397..774a8f17 100644 --- a/lib/src/web/in_app_web_view_web_element.dart +++ b/lib/src/web/in_app_web_view_web_element.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'dart:html'; import 'dart:js' as js; @@ -49,7 +50,7 @@ class InAppWebViewWebElement { Future handleMethodCall(MethodCall call) async { switch (call.method) { case "getIFrameId": - return iframe.id; + return getIFrameId(); case "loadUrl": URLRequest urlRequest = URLRequest.fromMap( call.arguments["urlRequest"].cast())!; @@ -90,10 +91,51 @@ class InAppWebViewWebElement { case "setSettings": InAppWebViewSettings newSettings = InAppWebViewSettings.fromMap( call.arguments["settings"].cast()); - setSettings(newSettings); + await setSettings(newSettings); break; case "getUrl": - return getUrl(); + return await getUrl(); + case "getTitle": + return await getTitle(); + case "postUrl": + String url = call.arguments["url"]; + Uint8List postData = call.arguments["postData"]; + return await postUrl(url: url, postData: postData); + case "injectJavascriptFileFromUrl": + String urlFile = call.arguments["urlFile"]; + Map scriptHtmlTagAttributes = call.arguments["scriptHtmlTagAttributes"].cast(); + await injectJavascriptFileFromUrl(urlFile: urlFile, scriptHtmlTagAttributes: scriptHtmlTagAttributes); + break; + case "injectCSSCode": + String source = call.arguments["source"]; + await injectCSSCode(source: source); + break; + case "injectCSSFileFromUrl": + String urlFile = call.arguments["urlFile"]; + Map cssLinkHtmlTagAttributes = call.arguments["cssLinkHtmlTagAttributes"].cast(); + await injectCSSFileFromUrl(urlFile: urlFile, cssLinkHtmlTagAttributes: cssLinkHtmlTagAttributes); + break; + case "scrollTo": + int x = call.arguments["x"]; + int y = call.arguments["y"]; + bool animated = call.arguments["animated"]; + await scrollTo(x: x, y: y, animated: animated); + break; + case "scrollBy": + int x = call.arguments["x"]; + int y = call.arguments["y"]; + bool animated = call.arguments["animated"]; + await scrollBy(x: x, y: y, animated: animated); + break; + case "printCurrentPage": + await printCurrentPage(); + break; + case "getContentHeight": + return await getContentHeight(); + case "getOriginalUrl": + return await getOriginalUrl(); + case "getSelectedText": + return await getSelectedText(); case "dispose": dispose(); break; @@ -177,6 +219,10 @@ class InAppWebViewWebElement { Uri.encodeFull(httpRequest.responseText ?? ''); } + String getIFrameId() { + return iframe.id; + } + Future loadUrl({required URLRequest urlRequest}) async { if ((urlRequest.method == null || urlRequest.method == "GET") && (urlRequest.headers == null || urlRequest.headers!.isEmpty)) { @@ -227,6 +273,56 @@ class InAppWebViewWebElement { return url; } + Future getTitle() async { + return _callMethod("getTitle"); + } + + Future postUrl({required String url, required Uint8List postData}) async { + await loadUrl(urlRequest: URLRequest(url: Uri.parse(url), method: "POST", body: postData)); + } + + Future injectJavascriptFileFromUrl({required String urlFile, + Map? scriptHtmlTagAttributes}) async { + _callMethod("injectJavascriptFileFromUrl", + [urlFile, scriptHtmlTagAttributes != null ? + js.JsObject.jsify(scriptHtmlTagAttributes) : null]); + } + + Future injectCSSCode({required String source}) async { + _callMethod("injectCSSCode", [source]); + } + + Future injectCSSFileFromUrl({required String urlFile, + Map? cssLinkHtmlTagAttributes}) async { + _callMethod("injectCSSFileFromUrl", + [urlFile, cssLinkHtmlTagAttributes != null ? + js.JsObject.jsify(cssLinkHtmlTagAttributes) : null]); + } + + Future scrollTo({required int x, required int y, bool animated = false}) async { + _callMethod('scrollTo', [x, y, animated]); + } + + Future scrollBy({required int x, required int y, bool animated = false}) async { + _callMethod('scrollBy', [x, y, animated]); + } + + Future printCurrentPage() async { + _callMethod('printCurrentPage'); + } + + Future getContentHeight() async { + return _callMethod('getContentHeight'); + } + + Future getOriginalUrl() async { + return iframe.src; + } + + Future getSelectedText() async { + return _callMethod('getSelectedText'); + } + Set getSandbox() { var sandbox = iframe.sandbox; Set values = Set(); @@ -390,6 +486,14 @@ class InAppWebViewWebElement { await _channel?.invokeMethod("onZoomScaleChanged", obj); } + void onInjectedScriptLoaded(String id) async { + await _channel?.invokeMethod("onInjectedScriptLoaded", [id]); + } + + void onInjectedScriptError(String id) async { + await _channel?.invokeMethod("onInjectedScriptError", [id]); + } + void dispose() { _channel?.setMethodCallHandler(null); _channel = null; diff --git a/lib/src/web/web_platform.dart b/lib/src/web/web_platform.dart index 0a7bf8b0..1ebd36d8 100644 --- a/lib/src/web/web_platform.dart +++ b/lib/src/web/web_platform.dart @@ -48,32 +48,32 @@ Future _dartNativeCommunication(String method, dynamic viewId, WebPlatformManager.webViews[viewId] as InAppWebViewWebElement; switch (method) { case 'onLoadStart': - var url = args![0] as String; + String url = args![0]; webViewHtmlElement.onLoadStart(url); break; case 'onLoadStop': - var url = args![0] as String; + String url = args![0]; webViewHtmlElement.onLoadStop(url); break; case 'onUpdateVisitedHistory': - var url = args![0] as String; + String url = args![0]; webViewHtmlElement.onUpdateVisitedHistory(url); break; case 'onScrollChanged': - var x = (args![0] as double).toInt(); - var y = (args[1] as double).toInt(); + int x = (args![0] as double).toInt(); + int y = (args[1] as double).toInt(); webViewHtmlElement.onScrollChanged(x, y); break; case 'onConsoleMessage': - var type = args![0] as String; - var message = args[1] as String?; + String type = args![0]; + String? message = args[1]; 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?; + int windowId = args![0]; + String url = args[1] ?? 'about:blank'; + String? target = args[2]; + String? windowFeatures = args[3]; return await webViewHtmlElement.onCreateWindow( windowId, url, target, windowFeatures); case 'onWindowFocus': @@ -83,7 +83,7 @@ Future _dartNativeCommunication(String method, dynamic viewId, webViewHtmlElement.onWindowBlur(); break; case 'onPrint': - var url = args![0] as String?; + String? url = args![0]; webViewHtmlElement.onPrint(url); break; case 'onEnterFullscreen': @@ -93,14 +93,22 @@ Future _dartNativeCommunication(String method, dynamic viewId, webViewHtmlElement.onExitFullscreen(); break; case 'onTitleChanged': - var title = args![0] as String?; + String? title = args![0]; webViewHtmlElement.onTitleChanged(title); break; case 'onZoomScaleChanged': - var oldScale = args![0] as double; - var newScale = args[1] as double; + double oldScale = args![0]; + double newScale = args[1]; webViewHtmlElement.onZoomScaleChanged(oldScale, newScale); break; + case 'onInjectedScriptLoaded': + String id = args![0]; + webViewHtmlElement.onInjectedScriptLoaded(id); + break; + case 'onInjectedScriptError': + String id = args![0]; + webViewHtmlElement.onInjectedScriptError(id); + break; } } } diff --git a/scripts/test.sh b/scripts/test.sh index d5be1d31..5cbbadee 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -10,8 +10,13 @@ function error() { # on macOS local IP can be found using something like $(ipconfig getifaddr en0) # on linux local IP can be found using something like $(ifconfig en0 | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}') or $(ip route get 1 | awk '{print $NF;exit}') export NODE_SERVER_IP=$1 +PLATFORM=$2 FAILED=0 +if [ $PLATFORM = "web" ]; then + $PROJECT_DIR/tool/chromedriver --port=4444 & +fi + dart $PROJECT_DIR/tool/env.dart cd $PROJECT_DIR/nodejs_server_test_auth_basic_and_ssl @@ -21,7 +26,11 @@ flutter --version flutter clean cd $PROJECT_DIR/example flutter clean -flutter driver --driver=test_driver/integration_test.dart --target=integration_test/webview_flutter_test.dart +if [ $PLATFORM = "web" ]; then + flutter driver --driver=test_driver/integration_test.dart --target=integration_test/webview_flutter_test.dart --device-id=chrome +else + flutter driver --driver=test_driver/integration_test.dart --target=integration_test/webview_flutter_test.dart +fi if [ $? -eq 0 ]; then echo "Integration tests passed successfully." diff --git a/scripts/test_and_log.sh b/scripts/test_and_log.sh index 4500826b..797933fd 100755 --- a/scripts/test_and_log.sh +++ b/scripts/test_and_log.sh @@ -3,6 +3,6 @@ readonly SCRIPT_PATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" readonly PROJECT_DIR="$(dirname $SCRIPT_PATH)" -$SCRIPT_PATH/test.sh $1 2>&1 | tee $PROJECT_DIR/flutter_driver_tests.log +$SCRIPT_PATH/test.sh $1 $2 2>&1 | tee $PROJECT_DIR/flutter_driver_tests.log kill $(jobs -p) \ No newline at end of file