updated web support

This commit is contained in:
Lorenzo Pichilli 2022-04-27 16:59:49 +02:00
parent 3bad02d6e4
commit 68f25d0d4d
11 changed files with 375 additions and 56 deletions

2
.gitignore vendored
View File

@ -30,3 +30,5 @@ build/
.fvm/ .fvm/
flutter_driver_tests.log flutter_driver_tests.log
tool/chromedriver
tool/chromedriver.*

View File

@ -1,4 +1,5 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart';
@ -114,16 +115,17 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
children: [ children: [
InAppWebView( InAppWebView(
key: webViewKey, key: webViewKey,
// initialUrlRequest:
// URLRequest(url: Uri.parse("https://flutter.dev")),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse("https://flutter.dev")), URLRequest(url: Uri.parse(Uri.base.toString().replaceFirst("/#/", "/") + 'page.html')),
// initialFile: "assets/index.html", // initialFile: "assets/index.html",
initialUserScripts: UnmodifiableListView<UserScript>([]), initialUserScripts: UnmodifiableListView<UserScript>([]),
initialSettings: settings, initialSettings: settings,
// contextMenu: contextMenu, // contextMenu: contextMenu,
pullToRefreshController: pullToRefreshController, pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) async { onWebViewCreated: (controller) {
webViewController = controller; webViewController = controller;
print(await controller.getUrl());
}, },
onLoadStart: (controller, url) async { onLoadStart: (controller, url) async {
setState(() { setState(() {
@ -149,10 +151,10 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
"javascript", "javascript",
"about" "about"
].contains(uri.scheme)) { ].contains(uri.scheme)) {
if (await canLaunch(url)) { if (await canLaunchUrl(uri)) {
// Launch the App // Launch the App
await launch( await launchUrl(
url, uri,
); );
// and cancel the request // and cancel the request
return NavigationActionPolicy.CANCEL; return NavigationActionPolicy.CANCEL;

View File

@ -16,7 +16,7 @@
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
<title>flutter_inappwebview_example</title> <title>heavy page</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
</head> </head>
<body> <body>

View File

@ -15,7 +15,7 @@
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
<title>flutter_inappwebview_example</title> <title>page 2</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
</head> </head>
<body> <body>

View File

@ -16,7 +16,7 @@
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
<title>flutter_inappwebview_example</title> <title>page</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
</head> </head>
<body style="min-height: 3000px;min-width: 3000px;"> <body style="min-height: 3000px;min-width: 3000px;">

View File

@ -365,6 +365,138 @@ window.flutter_inappwebview = {
} }
return url; 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; return webView;

View File

@ -1034,6 +1034,22 @@ class InAppWebViewController
_inAppBrowser!.onPrint(uri); _inAppBrowser!.onPrint(uri);
} }
break; 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": case "onCallJsHandler":
String handlerName = call.arguments["handlerName"]; String handlerName = call.arguments["handlerName"];
// decode args to json // decode args to json
@ -1165,7 +1181,7 @@ class InAppWebViewController
try { try {
return jsonEncode(await javaScriptHandlersMap[handlerName]!(args)); return jsonEncode(await javaScriptHandlersMap[handlerName]!(args));
} catch (error) { } catch (error) {
print(error); developer.log(error.toString(), name: this.runtimeType.toString());
return null; return null;
} }
} }
@ -1194,9 +1210,12 @@ class InAppWebViewController
///Gets the title for the current page. ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.getTitle](https://developer.android.com/reference/android/webkit/WebView#getTitle())) ///- 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)) ///- iOS ([Official API - WKWebView.title](https://developer.apple.com/documentation/webkit/wkwebview/1415015-title))
///- Web
Future<String?> getTitle() async { Future<String?> getTitle() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getTitle', args); return await _channel.invokeMethod('getTitle', args);
@ -1222,6 +1241,7 @@ class InAppWebViewController
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<String?> getHtml() async { Future<String?> getHtml() async {
String? html; String? html;
@ -1245,13 +1265,13 @@ class InAppWebViewController
html = utf8.decode(bytes.buffer.asUint8List()); html = utf8.decode(bytes.buffer.asUint8List());
} catch (e) {} } catch (e) {}
} else { } else {
HttpClient client = new HttpClient();
try { try {
HttpClient client = HttpClient();
var htmlRequest = await client.getUrl(webviewUrl); var htmlRequest = await client.getUrl(webviewUrl);
html = html =
await (await htmlRequest.close()).transform(Utf8Decoder()).join(); await (await htmlRequest.close()).transform(Utf8Decoder()).join();
} catch (e) { } 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. ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<List<Favicon>> getFavicons() async { Future<List<Favicon>> getFavicons() async {
List<Favicon> favicons = []; List<Favicon> favicons = [];
HttpClient client = new HttpClient();
var webviewUrl = await getUrl(); var webviewUrl = await getUrl();
if (webviewUrl == null) { if (webviewUrl == null) {
@ -1336,6 +1358,7 @@ class InAppWebViewController
// try to get /favicon.ico // try to get /favicon.ico
try { try {
HttpClient client = HttpClient();
var faviconUrl = var faviconUrl =
webviewUrl.scheme + "://" + webviewUrl.host + "/favicon.ico"; webviewUrl.scheme + "://" + webviewUrl.host + "/favicon.ico";
var faviconUri = Uri.parse(faviconUrl); var faviconUri = Uri.parse(faviconUrl);
@ -1345,8 +1368,8 @@ class InAppWebViewController
favicons.add(Favicon(url: faviconUri, rel: "shortcut icon")); favicons.add(Favicon(url: faviconUri, rel: "shortcut icon"));
} }
} catch (e) { } catch (e) {
print("/favicon.ico file not found: " + e.toString()); developer.log("/favicon.ico file not found: " + e.toString(),
// print(stacktrace); name: this.runtimeType.toString());
} }
// try to get the manifest file // try to get the manifest file
@ -1358,13 +1381,14 @@ class InAppWebViewController
webviewUrl.scheme + "://" + webviewUrl.host + "/manifest.json"; webviewUrl.scheme + "://" + webviewUrl.host + "/manifest.json";
} }
try { try {
HttpClient client = HttpClient();
manifestRequest = await client.getUrl(Uri.parse(manifestUrl)); manifestRequest = await client.getUrl(Uri.parse(manifestUrl));
manifestResponse = await manifestRequest.close(); manifestResponse = await manifestRequest.close();
manifestFound = manifestResponse.statusCode == 200 && manifestFound = manifestResponse.statusCode == 200 &&
manifestResponse.headers.contentType?.mimeType == "application/json"; manifestResponse.headers.contentType?.mimeType == "application/json";
} catch (e) { } catch (e) {
print("Manifest file not found: " + e.toString()); developer.log("Manifest file not found: " + e.toString(),
// print(stacktrace); name: this.runtimeType.toString());
} }
if (manifestFound) { if (manifestFound) {
@ -1470,9 +1494,12 @@ class InAppWebViewController
///controller.postUrl(url: Uri.parse("https://www.example.com/"), postData: postData); ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.postUrl](https://developer.android.com/reference/android/webkit/WebView#postUrl(java.lang.String,%20byte[]))) ///- Android native WebView ([Official API - WebView.postUrl](https://developer.android.com/reference/android/webkit/WebView#postUrl(java.lang.String,%20byte[])))
///- iOS ///- iOS
///- Web
Future<void> postUrl({required Uri url, required Uint8List postData}) async { Future<void> postUrl({required Uri url, required Uint8List postData}) async {
assert(url.toString().isNotEmpty); assert(url.toString().isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
@ -1745,9 +1772,12 @@ class InAppWebViewController
///Instead, you should call this method, for example, inside the [WebView.onLoadStop] event or in any other events ///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". ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<void> injectJavascriptFileFromUrl( Future<void> injectJavascriptFileFromUrl(
{required Uri urlFile, {required Uri urlFile,
ScriptHtmlTagAttributes? scriptHtmlTagAttributes}) async { 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 ///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". ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<dynamic> injectJavascriptFileFromAsset( Future<dynamic> injectJavascriptFileFromAsset(
{required String assetFilePath}) async { {required String assetFilePath}) async {
String source = await rootBundle.loadString(assetFilePath); 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 ///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". ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<void> injectCSSCode({required String source}) async { Future<void> injectCSSCode({required String source}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('source', () => source); 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 ///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". ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<void> injectCSSFileFromUrl( Future<void> injectCSSFileFromUrl(
{required Uri urlFile, {required Uri urlFile,
CSSLinkHtmlTagAttributes? cssLinkHtmlTagAttributes}) async { 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 ///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". ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<void> injectCSSFileFromAsset({required String assetFilePath}) async { Future<void> injectCSSFileFromAsset({required String assetFilePath}) async {
String source = await rootBundle.loadString(assetFilePath); String source = await rootBundle.loadString(assetFilePath);
await injectCSSCode(source: source); await injectCSSCode(source: source);
@ -2076,9 +2118,12 @@ class InAppWebViewController
/// ///
///[animated] `true` to animate the scroll transition, `false` to make the scoll transition immediate. ///[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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - View.scrollTo](https://developer.android.com/reference/android/view/View#scrollTo(int,%20int))) ///- 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)) ///- 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<void> scrollTo( Future<void> scrollTo(
{required int x, required int y, bool animated = false}) async { {required int x, required int y, bool animated = false}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
@ -2096,9 +2141,12 @@ class InAppWebViewController
/// ///
///[animated] `true` to animate the scroll transition, `false` to make the scoll transition immediate. ///[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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - View.scrollBy](https://developer.android.com/reference/android/view/View#scrollBy(int,%20int))) ///- 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)) ///- 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<void> scrollBy( Future<void> scrollBy(
{required int x, required int y, bool animated = false}) async { {required int x, required int y, bool animated = false}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
@ -2137,9 +2185,12 @@ class InAppWebViewController
/// ///
///**NOTE**: available on Android 21+. ///**NOTE**: available on Android 21+.
/// ///
///**NOTE for Web**: this method will have effect only if the iframe has the same origin.
///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - PrintManager](https://developer.android.com/reference/android/print/PrintManager)) ///- 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)) ///- 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<void> printCurrentPage() async { Future<void> printCurrentPage() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('printCurrentPage', args); await _channel.invokeMethod('printCurrentPage', args);
@ -2147,9 +2198,12 @@ class InAppWebViewController
///Gets the height of the HTML content. ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.getContentHeight](https://developer.android.com/reference/android/webkit/WebView#getContentHeight())) ///- 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)) ///- 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<int?> getContentHeight() async { Future<int?> getContentHeight() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getContentHeight', 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, ///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. ///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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.getOriginalUrl](https://developer.android.com/reference/android/webkit/WebView#getOriginalUrl())) ///- Android native WebView ([Official API - WebView.getOriginalUrl](https://developer.android.com/reference/android/webkit/WebView#getOriginalUrl()))
///- iOS ///- iOS
///- Web
Future<Uri?> getOriginalUrl() async { Future<Uri?> getOriginalUrl() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
String? url = await _channel.invokeMethod('getOriginalUrl', 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 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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<String?> getSelectedText() async { Future<String?> getSelectedText() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getSelectedText', args); return await _channel.invokeMethod('getSelectedText', args);
@ -2314,9 +2374,12 @@ class InAppWebViewController
/// ///
///**NOTE**: It is implemented using JavaScript. ///**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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<List<MetaTag>> getMetaTags() async { Future<List<MetaTag>> getMetaTags() async {
List<MetaTag> metaTags = []; List<MetaTag> metaTags = [];
@ -2379,11 +2442,14 @@ class InAppWebViewController
///Returns an instance of [Color] representing the `content` value of the ///Returns an instance of [Color] representing the `content` value of the
///`<meta name="theme-color" content="">` tag of the current WebView, if available, otherwise `null`. ///`<meta name="theme-color" content="">` 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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ([Official API - WKWebView.themeColor](https://developer.apple.com/documentation/webkit/wkwebview/3794258-themecolor)) ///- iOS ([Official API - WKWebView.themeColor](https://developer.apple.com/documentation/webkit/wkwebview/3794258-themecolor))
///- Web
Future<Color?> getMetaThemeColor() async { Future<Color?> getMetaThemeColor() async {
Color? themeColor; Color? themeColor;
@ -2391,16 +2457,12 @@ class InAppWebViewController
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
themeColor = UtilColor.fromStringRepresentation( themeColor = UtilColor.fromStringRepresentation(
await _channel.invokeMethod('getMetaThemeColor', args)); await _channel.invokeMethod('getMetaThemeColor', args));
return themeColor;
} catch (e) { } catch (e) {
// not implemented // not implemented
} }
if (themeColor != null) {
return themeColor;
}
// try using javascript // try using javascript
var metaTags = await getMetaTags(); var metaTags = await getMetaTags();
MetaTag? metaTagThemeColor; MetaTag? metaTagThemeColor;

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'dart:html'; import 'dart:html';
import 'dart:js' as js; import 'dart:js' as js;
@ -49,7 +50,7 @@ class InAppWebViewWebElement {
Future<dynamic> handleMethodCall(MethodCall call) async { Future<dynamic> handleMethodCall(MethodCall call) async {
switch (call.method) { switch (call.method) {
case "getIFrameId": case "getIFrameId":
return iframe.id; return getIFrameId();
case "loadUrl": case "loadUrl":
URLRequest urlRequest = URLRequest.fromMap( URLRequest urlRequest = URLRequest.fromMap(
call.arguments["urlRequest"].cast<String, dynamic>())!; call.arguments["urlRequest"].cast<String, dynamic>())!;
@ -90,10 +91,51 @@ class InAppWebViewWebElement {
case "setSettings": case "setSettings":
InAppWebViewSettings newSettings = InAppWebViewSettings.fromMap( InAppWebViewSettings newSettings = InAppWebViewSettings.fromMap(
call.arguments["settings"].cast<String, dynamic>()); call.arguments["settings"].cast<String, dynamic>());
setSettings(newSettings); await setSettings(newSettings);
break; break;
case "getUrl": 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<String, dynamic> scriptHtmlTagAttributes = call.arguments["scriptHtmlTagAttributes"].cast<String, dynamic>();
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<String, dynamic> cssLinkHtmlTagAttributes = call.arguments["cssLinkHtmlTagAttributes"].cast<String, dynamic>();
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": case "dispose":
dispose(); dispose();
break; break;
@ -177,6 +219,10 @@ class InAppWebViewWebElement {
Uri.encodeFull(httpRequest.responseText ?? ''); Uri.encodeFull(httpRequest.responseText ?? '');
} }
String getIFrameId() {
return iframe.id;
}
Future<void> loadUrl({required URLRequest urlRequest}) async { Future<void> loadUrl({required URLRequest urlRequest}) async {
if ((urlRequest.method == null || urlRequest.method == "GET") && if ((urlRequest.method == null || urlRequest.method == "GET") &&
(urlRequest.headers == null || urlRequest.headers!.isEmpty)) { (urlRequest.headers == null || urlRequest.headers!.isEmpty)) {
@ -227,6 +273,56 @@ class InAppWebViewWebElement {
return url; return url;
} }
Future<String?> getTitle() async {
return _callMethod("getTitle");
}
Future<void> postUrl({required String url, required Uint8List postData}) async {
await loadUrl(urlRequest: URLRequest(url: Uri.parse(url), method: "POST", body: postData));
}
Future<void> injectJavascriptFileFromUrl({required String urlFile,
Map<String, dynamic>? scriptHtmlTagAttributes}) async {
_callMethod("injectJavascriptFileFromUrl",
[urlFile, scriptHtmlTagAttributes != null ?
js.JsObject.jsify(scriptHtmlTagAttributes) : null]);
}
Future<void> injectCSSCode({required String source}) async {
_callMethod("injectCSSCode", [source]);
}
Future<void> injectCSSFileFromUrl({required String urlFile,
Map<String, dynamic>? cssLinkHtmlTagAttributes}) async {
_callMethod("injectCSSFileFromUrl",
[urlFile, cssLinkHtmlTagAttributes != null ?
js.JsObject.jsify(cssLinkHtmlTagAttributes) : null]);
}
Future<void> scrollTo({required int x, required int y, bool animated = false}) async {
_callMethod('scrollTo', [x, y, animated]);
}
Future<void> scrollBy({required int x, required int y, bool animated = false}) async {
_callMethod('scrollBy', [x, y, animated]);
}
Future<void> printCurrentPage() async {
_callMethod('printCurrentPage');
}
Future<int?> getContentHeight() async {
return _callMethod('getContentHeight');
}
Future<String?> getOriginalUrl() async {
return iframe.src;
}
Future<String?> getSelectedText() async {
return _callMethod('getSelectedText');
}
Set<Sandbox> getSandbox() { Set<Sandbox> getSandbox() {
var sandbox = iframe.sandbox; var sandbox = iframe.sandbox;
Set<Sandbox> values = Set(); Set<Sandbox> values = Set();
@ -390,6 +486,14 @@ class InAppWebViewWebElement {
await _channel?.invokeMethod("onZoomScaleChanged", obj); 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() { void dispose() {
_channel?.setMethodCallHandler(null); _channel?.setMethodCallHandler(null);
_channel = null; _channel = null;

View File

@ -48,32 +48,32 @@ Future<dynamic> _dartNativeCommunication(String method, dynamic viewId,
WebPlatformManager.webViews[viewId] as InAppWebViewWebElement; WebPlatformManager.webViews[viewId] as InAppWebViewWebElement;
switch (method) { switch (method) {
case 'onLoadStart': case 'onLoadStart':
var url = args![0] as String; String url = args![0];
webViewHtmlElement.onLoadStart(url); webViewHtmlElement.onLoadStart(url);
break; break;
case 'onLoadStop': case 'onLoadStop':
var url = args![0] as String; String url = args![0];
webViewHtmlElement.onLoadStop(url); webViewHtmlElement.onLoadStop(url);
break; break;
case 'onUpdateVisitedHistory': case 'onUpdateVisitedHistory':
var url = args![0] as String; String url = args![0];
webViewHtmlElement.onUpdateVisitedHistory(url); webViewHtmlElement.onUpdateVisitedHistory(url);
break; break;
case 'onScrollChanged': case 'onScrollChanged':
var x = (args![0] as double).toInt(); int x = (args![0] as double).toInt();
var y = (args[1] as double).toInt(); int y = (args[1] as double).toInt();
webViewHtmlElement.onScrollChanged(x, y); webViewHtmlElement.onScrollChanged(x, y);
break; break;
case 'onConsoleMessage': case 'onConsoleMessage':
var type = args![0] as String; String type = args![0];
var message = args[1] as String?; String? message = args[1];
webViewHtmlElement.onConsoleMessage(type, message); webViewHtmlElement.onConsoleMessage(type, message);
break; break;
case 'onCreateWindow': case 'onCreateWindow':
var windowId = args![0] as int; int windowId = args![0];
var url = args[1] as String? ?? 'about:blank'; String url = args[1] ?? 'about:blank';
var target = args[2] as String?; String? target = args[2];
var windowFeatures = args[3] as String?; String? windowFeatures = args[3];
return await webViewHtmlElement.onCreateWindow( return await webViewHtmlElement.onCreateWindow(
windowId, url, target, windowFeatures); windowId, url, target, windowFeatures);
case 'onWindowFocus': case 'onWindowFocus':
@ -83,7 +83,7 @@ Future<dynamic> _dartNativeCommunication(String method, dynamic viewId,
webViewHtmlElement.onWindowBlur(); webViewHtmlElement.onWindowBlur();
break; break;
case 'onPrint': case 'onPrint':
var url = args![0] as String?; String? url = args![0];
webViewHtmlElement.onPrint(url); webViewHtmlElement.onPrint(url);
break; break;
case 'onEnterFullscreen': case 'onEnterFullscreen':
@ -93,14 +93,22 @@ Future<dynamic> _dartNativeCommunication(String method, dynamic viewId,
webViewHtmlElement.onExitFullscreen(); webViewHtmlElement.onExitFullscreen();
break; break;
case 'onTitleChanged': case 'onTitleChanged':
var title = args![0] as String?; String? title = args![0];
webViewHtmlElement.onTitleChanged(title); webViewHtmlElement.onTitleChanged(title);
break; break;
case 'onZoomScaleChanged': case 'onZoomScaleChanged':
var oldScale = args![0] as double; double oldScale = args![0];
var newScale = args[1] as double; double newScale = args[1];
webViewHtmlElement.onZoomScaleChanged(oldScale, newScale); webViewHtmlElement.onZoomScaleChanged(oldScale, newScale);
break; break;
case 'onInjectedScriptLoaded':
String id = args![0];
webViewHtmlElement.onInjectedScriptLoaded(id);
break;
case 'onInjectedScriptError':
String id = args![0];
webViewHtmlElement.onInjectedScriptError(id);
break;
} }
} }
} }

View File

@ -10,8 +10,13 @@ function error() {
# on macOS local IP can be found using something like $(ipconfig getifaddr en0) # 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}') # 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 export NODE_SERVER_IP=$1
PLATFORM=$2
FAILED=0 FAILED=0
if [ $PLATFORM = "web" ]; then
$PROJECT_DIR/tool/chromedriver --port=4444 &
fi
dart $PROJECT_DIR/tool/env.dart dart $PROJECT_DIR/tool/env.dart
cd $PROJECT_DIR/nodejs_server_test_auth_basic_and_ssl cd $PROJECT_DIR/nodejs_server_test_auth_basic_and_ssl
@ -21,7 +26,11 @@ flutter --version
flutter clean flutter clean
cd $PROJECT_DIR/example cd $PROJECT_DIR/example
flutter clean flutter clean
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 flutter driver --driver=test_driver/integration_test.dart --target=integration_test/webview_flutter_test.dart
fi
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo "Integration tests passed successfully." echo "Integration tests passed successfully."

View File

@ -3,6 +3,6 @@
readonly SCRIPT_PATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" readonly SCRIPT_PATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
readonly PROJECT_DIR="$(dirname $SCRIPT_PATH)" 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) kill $(jobs -p)