added headless and cookie manager web support

This commit is contained in:
Lorenzo Pichilli 2022-04-25 22:36:21 +02:00
parent d1a4ff1829
commit bb33ec2362
20 changed files with 762 additions and 410 deletions

View File

@ -24,8 +24,6 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
allowsInlineMediaPlayback: true, allowsInlineMediaPlayback: true,
iframeAllow: "camera; microphone", iframeAllow: "camera; microphone",
iframeAllowFullscreen: true, iframeAllowFullscreen: true,
disableVerticalScroll: true,
disableHorizontalScroll: true,
); );
PullToRefreshController? pullToRefreshController; PullToRefreshController? pullToRefreshController;
@ -123,7 +121,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
initialUserScripts: UnmodifiableListView<UserScript>([]), initialUserScripts: UnmodifiableListView<UserScript>([]),
initialSettings: settings, initialSettings: settings,
pullToRefreshController: pullToRefreshController, pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) { onWebViewCreated: (controller) async {
webViewController = controller; webViewController = controller;
}, },
onLoadStart: (controller, url) async { onLoadStart: (controller, url) async {
@ -162,17 +160,11 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
return NavigationActionPolicy.ALLOW; return NavigationActionPolicy.ALLOW;
}, },
onLoadStop: (controller, url) async { onLoadStop: (controller, url) async {
print("onLoadStop");
pullToRefreshController?.endRefreshing(); pullToRefreshController?.endRefreshing();
setState(() { setState(() {
this.url = url.toString(); this.url = url.toString();
urlController.text = this.url; urlController.text = this.url;
}); });
await Future.delayed(Duration(seconds: 2));
await controller.setSettings(settings: settings.copy()
..disableVerticalScroll = false
..disableHorizontalScroll = false
);
}, },
onLoadError: (controller, url, code, message) { onLoadError: (controller, url, code, message) {
pullToRefreshController?.endRefreshing(); pullToRefreshController?.endRefreshing();

View File

@ -1,6 +1,9 @@
window.flutter_inappwebview = { window.flutter_inappwebview = {
viewId: null, webViews: {},
iframeId: null, createFlutterInAppWebView: function(viewId, iframeId) {
var webView = {
viewId: viewId,
iframeId: iframeId,
iframe: null, iframe: null,
windowAutoincrementId: 0, windowAutoincrementId: 0,
windows: {}, windows: {},
@ -8,30 +11,35 @@ window.flutter_inappwebview = {
documentTitle: null, documentTitle: null,
functionMap: {}, functionMap: {},
settings: {}, settings: {},
disableContextMenuHandler: function(event) {
event.preventDefault();
event.stopPropagation();
return false;
},
prepare: function (settings) { prepare: function (settings) {
window.flutter_inappwebview.settings = settings; webView.settings = settings;
var iframe = document.getElementById(window.flutter_inappwebview.iframeId); var iframe = document.getElementById(iframeId);
document.addEventListener('fullscreenchange', function(event) { document.addEventListener('fullscreenchange', function(event) {
// document.fullscreenElement will point to the element that // document.fullscreenElement will point to the element that
// is in fullscreen mode if there is one. If there isn't one, // is in fullscreen mode if there is one. If there isn't one,
// the value of the property is null. // the value of the property is null.
if (document.fullscreenElement && document.fullscreenElement.id == window.flutter_inappwebview.iframeId) { if (document.fullscreenElement && document.fullscreenElement.id == iframeId) {
window.flutter_inappwebview.isFullscreen = true; webView.isFullscreen = true;
window.flutter_inappwebview.nativeCommunication('onEnterFullscreen', window.flutter_inappwebview.viewId); window.flutter_inappwebview.nativeCommunication('onEnterFullscreen', viewId);
} else if (!document.fullscreenElement && window.flutter_inappwebview.isFullscreen) { } else if (!document.fullscreenElement && webView.isFullscreen) {
window.flutter_inappwebview.isFullscreen = false; webView.isFullscreen = false;
window.flutter_inappwebview.nativeCommunication('onExitFullscreen', window.flutter_inappwebview.viewId); window.flutter_inappwebview.nativeCommunication('onExitFullscreen', viewId);
} else { } else {
window.flutter_inappwebview.isFullscreen = false; webView.isFullscreen = false;
} }
}); });
if (iframe != null) { if (iframe != null) {
window.flutter_inappwebview.iframe = iframe; webView.iframe = iframe;
iframe.addEventListener('load', function (event) { iframe.addEventListener('load', function (event) {
window.flutter_inappwebview.windowAutoincrementId = 0; webView.windowAutoincrementId = 0;
window.flutter_inappwebview.windows = {}; webView.windows = {};
var url = iframe.src; var url = iframe.src;
try { try {
@ -39,7 +47,7 @@ window.flutter_inappwebview = {
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
window.flutter_inappwebview.nativeCommunication('onLoadStart', window.flutter_inappwebview.viewId, [url]); window.flutter_inappwebview.nativeCommunication('onLoadStart', viewId, [url]);
try { try {
var oldLogs = { var oldLogs = {
@ -61,7 +69,7 @@ window.flutter_inappwebview = {
} }
} }
oldLogs[oldLog].call(iframe.contentWindow.console, ...arguments); oldLogs[oldLog].call(iframe.contentWindow.console, ...arguments);
window.flutter_inappwebview.nativeCommunication('onConsoleMessage', window.flutter_inappwebview.viewId, [oldLog, message]); window.flutter_inappwebview.nativeCommunication('onConsoleMessage', viewId, [oldLog, message]);
} }
})(k); })(k);
} }
@ -79,7 +87,7 @@ window.flutter_inappwebview = {
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]); window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', viewId, [iframeUrl]);
}; };
var originalReplaceState = iframe.contentWindow.history.replaceState; var originalReplaceState = iframe.contentWindow.history.replaceState;
@ -91,16 +99,16 @@ window.flutter_inappwebview = {
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]); window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', viewId, [iframeUrl]);
}; };
var originalOpen = iframe.contentWindow.open; var originalOpen = iframe.contentWindow.open;
iframe.contentWindow.open = function (url, target, windowFeatures) { iframe.contentWindow.open = function (url, target, windowFeatures) {
var newWindow = originalOpen.call(iframe.contentWindow, ...arguments); var newWindow = originalOpen.call(iframe.contentWindow, ...arguments);
var windowId = window.flutter_inappwebview.windowAutoincrementId; var windowId = webView.windowAutoincrementId;
window.flutter_inappwebview.windowAutoincrementId++; webView.windowAutoincrementId++;
window.flutter_inappwebview.windows[windowId] = newWindow; webView.windows[windowId] = newWindow;
window.flutter_inappwebview.nativeCommunication('onCreateWindow', window.flutter_inappwebview.viewId, [windowId, url, target, windowFeatures]).then(function(){}, function(handledByClient) { window.flutter_inappwebview.nativeCommunication('onCreateWindow', viewId, [windowId, url, target, windowFeatures]).then(function(){}, function(handledByClient) {
if (handledByClient) { if (handledByClient) {
newWindow.close(); newWindow.close();
} }
@ -116,11 +124,11 @@ window.flutter_inappwebview = {
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
window.flutter_inappwebview.nativeCommunication('onPrint', window.flutter_inappwebview.viewId, [iframeUrl]); window.flutter_inappwebview.nativeCommunication('onPrint', viewId, [iframeUrl]);
originalPrint.call(iframe.contentWindow); originalPrint.call(iframe.contentWindow);
}; };
window.flutter_inappwebview.functionMap = { webView.functionMap = {
"window.open": iframe.contentWindow.open, "window.open": iframe.contentWindow.open,
"window.print": iframe.contentWindow.print, "window.print": iframe.contentWindow.print,
"window.history.pushState": iframe.contentWindow.history.pushState, "window.history.pushState": iframe.contentWindow.history.pushState,
@ -128,13 +136,13 @@ window.flutter_inappwebview = {
} }
var initialTitle = iframe.contentDocument.title; var initialTitle = iframe.contentDocument.title;
window.flutter_inappwebview.documentTitle = initialTitle; webView.documentTitle = initialTitle;
window.flutter_inappwebview.nativeCommunication('onTitleChanged', window.flutter_inappwebview.viewId, [initialTitle]); window.flutter_inappwebview.nativeCommunication('onTitleChanged', viewId, [initialTitle]);
new MutationObserver(function(mutations) { new MutationObserver(function(mutations) {
var title = mutations[0].target.nodeValue; var title = mutations[0].target.nodeValue;
if (title != window.flutter_inappwebview.documentTitle) { if (title != webView.documentTitle) {
window.flutter_inappwebview.documentTitle = title; webView.documentTitle = title;
window.flutter_inappwebview.nativeCommunication('onTitleChanged', window.flutter_inappwebview.viewId, [title]); window.flutter_inappwebview.nativeCommunication('onTitleChanged', viewId, [title]);
} }
}).observe( }).observe(
iframe.contentDocument.querySelector('title'), iframe.contentDocument.querySelector('title'),
@ -145,7 +153,7 @@ window.flutter_inappwebview = {
iframe.contentWindow.addEventListener('resize', function (e) { iframe.contentWindow.addEventListener('resize', function (e) {
var newPixelRatio = iframe.contentWindow.devicePixelRatio; var newPixelRatio = iframe.contentWindow.devicePixelRatio;
if(newPixelRatio !== oldPixelRatio){ if(newPixelRatio !== oldPixelRatio){
window.flutter_inappwebview.nativeCommunication('onZoomScaleChanged', window.flutter_inappwebview.viewId, [oldPixelRatio, newPixelRatio]); window.flutter_inappwebview.nativeCommunication('onZoomScaleChanged', viewId, [oldPixelRatio, newPixelRatio]);
oldPixelRatio = newPixelRatio; oldPixelRatio = newPixelRatio;
} }
}); });
@ -157,7 +165,7 @@ window.flutter_inappwebview = {
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]); window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', viewId, [iframeUrl]);
}); });
iframe.contentWindow.addEventListener('scroll', function (event) { iframe.contentWindow.addEventListener('scroll', function (event) {
@ -169,15 +177,15 @@ window.flutter_inappwebview = {
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
window.flutter_inappwebview.nativeCommunication('onScrollChanged', window.flutter_inappwebview.viewId, [x, y]); window.flutter_inappwebview.nativeCommunication('onScrollChanged', viewId, [x, y]);
}); });
iframe.contentWindow.addEventListener('focus', function (event) { iframe.contentWindow.addEventListener('focus', function (event) {
window.flutter_inappwebview.nativeCommunication('onWindowFocus', window.flutter_inappwebview.viewId); window.flutter_inappwebview.nativeCommunication('onWindowFocus', viewId);
}); });
iframe.contentWindow.addEventListener('blur', function (event) { iframe.contentWindow.addEventListener('blur', function (event) {
window.flutter_inappwebview.nativeCommunication('onWindowBlur', window.flutter_inappwebview.viewId); window.flutter_inappwebview.nativeCommunication('onWindowBlur', viewId);
}); });
} catch (e) { } catch (e) {
console.log(e); console.log(e);
@ -185,55 +193,59 @@ window.flutter_inappwebview = {
try { try {
if (!window.flutter_inappwebview.settings.javaScriptCanOpenWindowsAutomatically) { if (!webView.settings.javaScriptCanOpenWindowsAutomatically) {
iframe.contentWindow.open = function () { iframe.contentWindow.open = function () {
throw new Error('JavaScript cannot open windows automatically'); throw new Error('JavaScript cannot open windows automatically');
}; };
} }
if (!window.flutter_inappwebview.settings.verticalScrollBarEnabled && !window.flutter_inappwebview.settings.horizontalScrollBarEnabled) { if (!webView.settings.verticalScrollBarEnabled && !webView.settings.horizontalScrollBarEnabled) {
var style = iframe.contentDocument.createElement('style'); var style = iframe.contentDocument.createElement('style');
style.id = "settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled"; style.id = "settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled";
style.innerHTML = "body::-webkit-scrollbar { width: 0px; height: 0px; }"; style.innerHTML = "body::-webkit-scrollbar { width: 0px; height: 0px; }";
iframe.contentDocument.head.append(style); iframe.contentDocument.head.append(style);
} }
if (window.flutter_inappwebview.settings.disableVerticalScroll) { if (webView.settings.disableVerticalScroll) {
var style = iframe.contentDocument.createElement('style'); var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableVerticalScroll"; style.id = "settings.disableVerticalScroll";
style.innerHTML = "body { overflow-y: hidden; }"; style.innerHTML = "body { overflow-y: hidden; }";
iframe.contentDocument.head.append(style); iframe.contentDocument.head.append(style);
} }
if (window.flutter_inappwebview.settings.disableHorizontalScroll) { if (webView.settings.disableHorizontalScroll) {
var style = iframe.contentDocument.createElement('style'); var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableHorizontalScroll"; style.id = "settings.disableHorizontalScroll";
style.innerHTML = "body { overflow-x: hidden; }"; style.innerHTML = "body { overflow-x: hidden; }";
iframe.contentDocument.head.append(style); iframe.contentDocument.head.append(style);
} }
if (webView.settings.disableContextMenu) {
iframe.contentWindow.addEventListener('contextmenu', webView.disableContextMenuHandler);
}
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
window.flutter_inappwebview.nativeCommunication('onLoadStop', window.flutter_inappwebview.viewId, [url]); window.flutter_inappwebview.nativeCommunication('onLoadStop', viewId, [url]);
}); });
} }
}, },
setSettings: function (newSettings) { setSettings: function (newSettings) {
var iframe = window.flutter_inappwebview.iframe; var iframe = webView.iframe;
try { try {
if (window.flutter_inappwebview.settings.javaScriptCanOpenWindowsAutomatically != newSettings.javaScriptCanOpenWindowsAutomatically) { if (webView.settings.javaScriptCanOpenWindowsAutomatically != newSettings.javaScriptCanOpenWindowsAutomatically) {
if (!newSettings.javaScriptCanOpenWindowsAutomatically) { if (!newSettings.javaScriptCanOpenWindowsAutomatically) {
iframe.contentWindow.open = function () { iframe.contentWindow.open = function () {
throw new Error('JavaScript cannot open windows automatically'); throw new Error('JavaScript cannot open windows automatically');
}; };
} else { } else {
iframe.contentWindow.open = window.flutter_inappwebview.functionMap["window.open"]; iframe.contentWindow.open = webView.functionMap["window.open"];
} }
} }
if (window.flutter_inappwebview.settings.verticalScrollBarEnabled != newSettings.verticalScrollBarEnabled && if (webView.settings.verticalScrollBarEnabled != newSettings.verticalScrollBarEnabled &&
window.flutter_inappwebview.settings.horizontalScrollBarEnabled != newSettings.horizontalScrollBarEnabled) { webView.settings.horizontalScrollBarEnabled != newSettings.horizontalScrollBarEnabled) {
if (!newSettings.verticalScrollBarEnabled && !newSettings.horizontalScrollBarEnabled) { if (!newSettings.verticalScrollBarEnabled && !newSettings.horizontalScrollBarEnabled) {
var style = iframe.contentDocument.createElement('style'); var style = iframe.contentDocument.createElement('style');
style.id = "settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled"; style.id = "settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled";
@ -245,7 +257,7 @@ window.flutter_inappwebview = {
} }
} }
if (window.flutter_inappwebview.settings.disableVerticalScroll != newSettings.disableVerticalScroll) { if (webView.settings.disableVerticalScroll != newSettings.disableVerticalScroll) {
if (newSettings.disableVerticalScroll) { if (newSettings.disableVerticalScroll) {
var style = iframe.contentDocument.createElement('style'); var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableVerticalScroll"; style.id = "settings.disableVerticalScroll";
@ -257,7 +269,7 @@ window.flutter_inappwebview = {
} }
} }
if (window.flutter_inappwebview.settings.disableHorizontalScroll != newSettings.disableHorizontalScroll) { if (webView.settings.disableHorizontalScroll != newSettings.disableHorizontalScroll) {
if (newSettings.disableHorizontalScroll) { if (newSettings.disableHorizontalScroll) {
var style = iframe.contentDocument.createElement('style'); var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableHorizontalScroll"; style.id = "settings.disableHorizontalScroll";
@ -268,13 +280,22 @@ window.flutter_inappwebview = {
if (styleElement) { styleElement.remove() } if (styleElement) { styleElement.remove() }
} }
} }
if (webView.settings.disableContextMenu != newSettings.disableContextMenu) {
if (newSettings.disableContextMenu) {
iframe.contentWindow.addEventListener('contextmenu', webView.disableContextMenuHandler);
} else {
iframe.contentWindow.removeEventListener('contextmenu', webView.disableContextMenuHandler);
}
}
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
window.flutter_inappwebview.settings = newSettings;
webView.settings = newSettings;
}, },
reload: function () { reload: function () {
var iframe = window.flutter_inappwebview.iframe; var iframe = webView.iframe;
if (iframe != null && iframe.contentWindow != null) { if (iframe != null && iframe.contentWindow != null) {
try { try {
iframe.contentWindow.location.reload(); iframe.contentWindow.location.reload();
@ -285,7 +306,7 @@ window.flutter_inappwebview = {
} }
}, },
goBack: function () { goBack: function () {
var iframe = window.flutter_inappwebview.iframe; var iframe = webView.iframe;
if (iframe != null) { if (iframe != null) {
try { try {
iframe.contentWindow.history.back(); iframe.contentWindow.history.back();
@ -295,7 +316,7 @@ window.flutter_inappwebview = {
} }
}, },
goForward: function () { goForward: function () {
var iframe = window.flutter_inappwebview.iframe; var iframe = webView.iframe;
if (iframe != null) { if (iframe != null) {
try { try {
iframe.contentWindow.history.forward(); iframe.contentWindow.history.forward();
@ -305,7 +326,7 @@ window.flutter_inappwebview = {
} }
}, },
goForwardOrForward: function (steps) { goForwardOrForward: function (steps) {
var iframe = window.flutter_inappwebview.iframe; var iframe = webView.iframe;
if (iframe != null) { if (iframe != null) {
try { try {
iframe.contentWindow.history.go(steps); iframe.contentWindow.history.go(steps);
@ -315,7 +336,7 @@ window.flutter_inappwebview = {
} }
}, },
evaluateJavascript: function (source) { evaluateJavascript: function (source) {
var iframe = window.flutter_inappwebview.iframe; var iframe = webView.iframe;
var result = null; var result = null;
if (iframe != null) { if (iframe != null) {
try { try {
@ -325,7 +346,7 @@ window.flutter_inappwebview = {
return result; return result;
}, },
stopLoading: function (steps) { stopLoading: function (steps) {
var iframe = window.flutter_inappwebview.iframe; var iframe = webView.iframe;
if (iframe != null) { if (iframe != null) {
try { try {
iframe.contentWindow.stop(); iframe.contentWindow.stop();
@ -335,3 +356,10 @@ window.flutter_inappwebview = {
} }
} }
}; };
return webView;
},
getCookieExpirationDate: function(timestamp) {
return (new Date(timestamp)).toUTCString();
}
};

View File

@ -36,6 +36,10 @@ class ChromeSafariBrowserNotOpenedException implements Exception {
///**NOTE**: If you want to use the `ChromeSafariBrowser` class on Android 11+ you need to specify your app querying for ///**NOTE**: If you want to use the `ChromeSafariBrowser` class on Android 11+ you need to specify your app querying for
///`android.support.customtabs.action.CustomTabsService` in your `AndroidManifest.xml` ///`android.support.customtabs.action.CustomTabsService` in your `AndroidManifest.xml`
///(you can read more about it here: https://developers.google.com/web/android/custom-tabs/best-practices#applications_targeting_android_11_api_level_30_or_above). ///(you can read more about it here: https://developers.google.com/web/android/custom-tabs/best-practices#applications_targeting_android_11_api_level_30_or_above).
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class ChromeSafariBrowser { class ChromeSafariBrowser {
///View ID used internally. ///View ID used internally.
late final String id; late final String id;

View File

@ -6,6 +6,10 @@ import 'types.dart';
///Class that represents the WebView context menu. It used by [WebView.contextMenu]. ///Class that represents the WebView context menu. It used by [WebView.contextMenu].
/// ///
///**NOTE**: To make it work properly on Android, JavaScript should be enabled! ///**NOTE**: To make it work properly on Android, JavaScript should be enabled!
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class ContextMenu { class ContextMenu {
///Event fired when the context menu for this WebView is being built. ///Event fired when the context menu for this WebView is being built.
/// ///

View File

@ -17,6 +17,15 @@ import 'types.dart';
///**NOTE for iOS below 11.0 (LIMITED SUPPORT!)**: in this case, almost all of the methods ([CookieManager.deleteAllCookies] and [CookieManager.getAllCookies] are not supported!) ///**NOTE for iOS below 11.0 (LIMITED SUPPORT!)**: in this case, almost all of the methods ([CookieManager.deleteAllCookies] and [CookieManager.getAllCookies] are not supported!)
///has been implemented using JavaScript because there is no other way to work with them on iOS below 11.0. ///has been implemented using JavaScript because there is no other way to work with them on iOS below 11.0.
///See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies for JavaScript restrictions. ///See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies for JavaScript restrictions.
///
///**NOTE for Web (LIMITED SUPPORT!)**: in this case, almost all of the methods ([CookieManager.deleteAllCookies] and [CookieManager.getAllCookies] are not supported!)
///has been implemented using JavaScript, so all methods will have effect only if the iframe has the same origin.
///See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies for JavaScript restrictions.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
class CookieManager { class CookieManager {
static CookieManager? _instance; static CookieManager? _instance;
static const MethodChannel _channel = const MethodChannel( static const MethodChannel _channel = const MethodChannel(
@ -48,15 +57,20 @@ class CookieManager {
///The default value of [path] is `"/"`. ///The default value of [path] is `"/"`.
///If [domain] is `null`, its default value will be the domain name of [url]. ///If [domain] is `null`, its default value will be the domain name of [url].
/// ///
///[iosBelow11WebViewController] could be used if you need to set a session-only cookie using JavaScript (so [isHttpOnly] cannot be set, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) ///[webViewController] could be used if you need to set a session-only cookie using JavaScript (so [isHttpOnly] cannot be set, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
///on the current URL of the [WebView] managed by that controller when you need to target iOS below 11. In this case the [url] parameter is ignored. ///on the current URL of the [WebView] managed by that controller when you need to target iOS below 11 and Web platform. In this case the [url] parameter is ignored.
/// ///
///**NOTE for iOS below 11.0**: If [iosBelow11WebViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView] ///**NOTE for iOS below 11.0**: If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to set the cookie (session-only cookie won't work! In that case, you should set also [expiresDate] or [maxAge]).
///
///**NOTE for Web**: this method will have effect only if the iframe has the same origin.
///If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to set the cookie (session-only cookie won't work! In that case, you should set also [expiresDate] or [maxAge]). ///to set the cookie (session-only cookie won't work! In that case, you should set also [expiresDate] or [maxAge]).
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - CookieManager.setCookie](https://developer.android.com/reference/android/webkit/CookieManager#setCookie(java.lang.String,%20java.lang.String,%20android.webkit.ValueCallback%3Cjava.lang.Boolean%3E))) ///- Android native WebView ([Official API - CookieManager.setCookie](https://developer.android.com/reference/android/webkit/CookieManager#setCookie(java.lang.String,%20java.lang.String,%20android.webkit.ValueCallback%3Cjava.lang.Boolean%3E)))
///- iOS ([Official API - WKHTTPCookieStore.setCookie](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882007-setcookie)) ///- iOS ([Official API - WKHTTPCookieStore.setCookie](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882007-setcookie))
///- Web
Future<void> setCookie( Future<void> setCookie(
{required Uri url, {required Uri url,
required String name, required String name,
@ -68,19 +82,26 @@ class CookieManager {
bool? isSecure, bool? isSecure,
bool? isHttpOnly, bool? isHttpOnly,
HTTPCookieSameSitePolicy? sameSite, HTTPCookieSameSitePolicy? sameSite,
InAppWebViewController? iosBelow11WebViewController}) async { @Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
if (domain == null) domain = _getDomainName(url); if (domain == null) domain = _getDomainName(url);
webViewController = webViewController ?? iosBelow11WebViewController;
assert(url.toString().isNotEmpty); assert(url.toString().isNotEmpty);
assert(name.isNotEmpty); assert(name.isNotEmpty);
assert(value.isNotEmpty); assert(value.isNotEmpty);
assert(domain.isNotEmpty); assert(domain.isNotEmpty);
assert(path.isNotEmpty); assert(path.isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) { if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var platformUtil = PlatformUtil(); var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion()); var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) { shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
await _setCookieWithJavaScript( await _setCookieWithJavaScript(
url: url, url: url,
name: name, name: name,
@ -91,7 +112,7 @@ class CookieManager {
maxAge: maxAge, maxAge: maxAge,
isSecure: isSecure, isSecure: isSecure,
sameSite: sameSite, sameSite: sameSite,
webViewController: iosBelow11WebViewController); webViewController: webViewController);
return; return;
} }
} }
@ -161,28 +182,40 @@ class CookieManager {
///Gets all the cookies for the given [url]. ///Gets all the cookies for the given [url].
/// ///
///[iosBelow11WebViewController] is used for getting the cookies (also session-only cookies) using JavaScript (cookies with `isHttpOnly` enabled cannot be found, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) ///[webViewController] is used for getting the cookies (also session-only cookies) using JavaScript (cookies with `isHttpOnly` enabled cannot be found, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
///from the current context of the [WebView] managed by that controller when you need to target iOS below 11. JavaScript must be enabled in order to work. ///from the current context of the [WebView] managed by that controller when you need to target iOS below 11 and Web platform. JavaScript must be enabled in order to work.
///In this case the [url] parameter is ignored. ///In this case the [url] parameter is ignored.
/// ///
///**NOTE for iOS below 11.0**: All the cookies returned this way will have all the properties to `null` except for [Cookie.name] and [Cookie.value]. ///**NOTE for iOS below 11.0**: All the cookies returned this way will have all the properties to `null` except for [Cookie.name] and [Cookie.value].
///If [iosBelow11WebViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView] ///If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to get the cookies (session-only cookies and cookies with `isHttpOnly` enabled won't be found!).
///
///**NOTE for Web**: this method will have effect only if the iframe has the same origin.
///If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to get the cookies (session-only cookies and cookies with `isHttpOnly` enabled won't be found!). ///to get the cookies (session-only cookies and cookies with `isHttpOnly` enabled won't be found!).
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - CookieManager.getCookie](https://developer.android.com/reference/android/webkit/CookieManager#getCookie(java.lang.String))) ///- Android native WebView ([Official API - CookieManager.getCookie](https://developer.android.com/reference/android/webkit/CookieManager#getCookie(java.lang.String)))
///- iOS ([Official API - WKHTTPCookieStore.getAllCookies](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882005-getallcookies)) ///- iOS ([Official API - WKHTTPCookieStore.getAllCookies](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882005-getallcookies))
///- Web
Future<List<Cookie>> getCookies( Future<List<Cookie>> getCookies(
{required Uri url, {required Uri url,
InAppWebViewController? iosBelow11WebViewController}) async { @Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
assert(url.toString().isNotEmpty); assert(url.toString().isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) { webViewController = webViewController ?? iosBelow11WebViewController;
var platformUtil = PlatformUtil();
if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion()); var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) { shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
return await _getCookiesWithJavaScript( return await _getCookiesWithJavaScript(
url: url, webViewController: iosBelow11WebViewController); url: url, webViewController: webViewController);
} }
} }
@ -225,10 +258,12 @@ class CookieManager {
.toList(); .toList();
documentCookies.forEach((documentCookie) { documentCookies.forEach((documentCookie) {
List<String> cookie = documentCookie.split('='); List<String> cookie = documentCookie.split('=');
if (cookie.length > 1) {
cookies.add(Cookie( cookies.add(Cookie(
name: cookie[0], name: cookie[0],
value: cookie[1], value: cookie[1],
)); ));
}
}); });
return cookies; return cookies;
} }
@ -251,10 +286,12 @@ class CookieManager {
.toList(); .toList();
documentCookies.forEach((documentCookie) { documentCookies.forEach((documentCookie) {
List<String> cookie = documentCookie.split('='); List<String> cookie = documentCookie.split('=');
if (cookie.length > 1) {
cookies.add(Cookie( cookies.add(Cookie(
name: cookie[0], name: cookie[0],
value: cookie[1], value: cookie[1],
)); ));
}
}); });
await headlessWebView.dispose(); await headlessWebView.dispose();
return cookies; return cookies;
@ -262,30 +299,42 @@ class CookieManager {
///Gets a cookie by its [name] for the given [url]. ///Gets a cookie by its [name] for the given [url].
/// ///
///[iosBelow11WebViewController] is used for getting the cookie (also session-only cookie) using JavaScript (cookie with `isHttpOnly` enabled cannot be found, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) ///[webViewController] is used for getting the cookie (also session-only cookie) using JavaScript (cookie with `isHttpOnly` enabled cannot be found, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
///from the current context of the [WebView] managed by that controller when you need to target iOS below 11. JavaScript must be enabled in order to work. ///from the current context of the [WebView] managed by that controller when you need to target iOS below 11 and Web platform. JavaScript must be enabled in order to work.
///In this case the [url] parameter is ignored. ///In this case the [url] parameter is ignored.
/// ///
///**NOTE for iOS below 11.0**: All the cookies returned this way will have all the properties to `null` except for [Cookie.name] and [Cookie.value]. ///**NOTE for iOS below 11.0**: All the cookies returned this way will have all the properties to `null` except for [Cookie.name] and [Cookie.value].
///If [iosBelow11WebViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView] ///If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to get the cookie (session-only cookie and cookie with `isHttpOnly` enabled won't be found!).
///
///**NOTE for Web**: this method will have effect only if the iframe has the same origin.
///If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to get the cookie (session-only cookie and cookie with `isHttpOnly` enabled won't be found!). ///to get the cookie (session-only cookie and cookie with `isHttpOnly` enabled won't be found!).
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<Cookie?> getCookie( Future<Cookie?> getCookie(
{required Uri url, {required Uri url,
required String name, required String name,
InAppWebViewController? iosBelow11WebViewController}) async { @Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
assert(url.toString().isNotEmpty); assert(url.toString().isNotEmpty);
assert(name.isNotEmpty); assert(name.isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) { webViewController = webViewController ?? iosBelow11WebViewController;
var platformUtil = PlatformUtil();
if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion()); var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) { shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
List<Cookie> cookies = await _getCookiesWithJavaScript( List<Cookie> cookies = await _getCookiesWithJavaScript(
url: url, webViewController: iosBelow11WebViewController); url: url, webViewController: webViewController);
return cookies return cookies
.cast<Cookie?>() .cast<Cookie?>()
.firstWhere((cookie) => cookie!.name == name, orElse: () => null); .firstWhere((cookie) => cookie!.name == name, orElse: () => null);
@ -319,31 +368,43 @@ class CookieManager {
///The default value of [path] is `"/"`. ///The default value of [path] is `"/"`.
///If [domain] is empty, its default value will be the domain name of [url]. ///If [domain] is empty, its default value will be the domain name of [url].
/// ///
///[iosBelow11WebViewController] is used for deleting the cookie (also session-only cookie) using JavaScript (cookie with `isHttpOnly` enabled cannot be deleted, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) ///[webViewController] is used for deleting the cookie (also session-only cookie) using JavaScript (cookie with `isHttpOnly` enabled cannot be deleted, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
///from the current context of the [WebView] managed by that controller when you need to target iOS below 11. JavaScript must be enabled in order to work. ///from the current context of the [WebView] managed by that controller when you need to target iOS below 11 and Web platform. JavaScript must be enabled in order to work.
///In this case the [url] parameter is ignored. ///In this case the [url] parameter is ignored.
/// ///
///**NOTE for iOS below 11.0**: If [iosBelow11WebViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView] ///**NOTE for iOS below 11.0**: If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to delete the cookie (session-only cookie and cookie with `isHttpOnly` enabled won't be deleted!).
///
///**NOTE for Web**: this method will have effect only if the iframe has the same origin.
///If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to delete the cookie (session-only cookie and cookie with `isHttpOnly` enabled won't be deleted!). ///to delete the cookie (session-only cookie and cookie with `isHttpOnly` enabled won't be deleted!).
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ([Official API - WKHTTPCookieStore.delete](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882009-delete) ///- iOS ([Official API - WKHTTPCookieStore.delete](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882009-delete)
///- Web
Future<void> deleteCookie( Future<void> deleteCookie(
{required Uri url, {required Uri url,
required String name, required String name,
String domain = "", String domain = "",
String path = "/", String path = "/",
InAppWebViewController? iosBelow11WebViewController}) async { @Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
if (domain.isEmpty) domain = _getDomainName(url); if (domain.isEmpty) domain = _getDomainName(url);
assert(url.toString().isNotEmpty); assert(url.toString().isNotEmpty);
assert(name.isNotEmpty); assert(name.isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) { webViewController = webViewController ?? iosBelow11WebViewController;
var platformUtil = PlatformUtil();
if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion()); var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) { shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
await _setCookieWithJavaScript( await _setCookieWithJavaScript(
url: url, url: url,
name: name, name: name,
@ -351,7 +412,7 @@ class CookieManager {
path: path, path: path,
domain: domain, domain: domain,
maxAge: -1, maxAge: -1,
webViewController: iosBelow11WebViewController); webViewController: webViewController);
return; return;
} }
} }
@ -369,31 +430,43 @@ class CookieManager {
///The default value of [path] is `"/"`. ///The default value of [path] is `"/"`.
///If [domain] is empty, its default value will be the domain name of [url]. ///If [domain] is empty, its default value will be the domain name of [url].
/// ///
///[iosBelow11WebViewController] is used for deleting the cookies (also session-only cookies) using JavaScript (cookies with `isHttpOnly` enabled cannot be deleted, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) ///[webViewController] is used for deleting the cookies (also session-only cookies) using JavaScript (cookies with `isHttpOnly` enabled cannot be deleted, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
///from the current context of the [WebView] managed by that controller when you need to target iOS below 11. JavaScript must be enabled in order to work. ///from the current context of the [WebView] managed by that controller when you need to target iOS below 11 and Web platform. JavaScript must be enabled in order to work.
///In this case the [url] parameter is ignored. ///In this case the [url] parameter is ignored.
/// ///
///**NOTE for iOS below 11.0**: If [iosBelow11WebViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView] ///**NOTE for iOS below 11.0**: If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to delete the cookies (session-only cookies and cookies with `isHttpOnly` enabled won't be deleted!).
///
///**NOTE for Web**: this method will have effect only if the iframe has the same origin.
///If [webViewController] is `null` or JavaScript is disabled for it, it will try to use a [HeadlessInAppWebView]
///to delete the cookies (session-only cookies and cookies with `isHttpOnly` enabled won't be deleted!). ///to delete the cookies (session-only cookies and cookies with `isHttpOnly` enabled won't be deleted!).
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
///- iOS ///- iOS
///- Web
Future<void> deleteCookies( Future<void> deleteCookies(
{required Uri url, {required Uri url,
String domain = "", String domain = "",
String path = "/", String path = "/",
InAppWebViewController? iosBelow11WebViewController}) async { @Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
if (domain.isEmpty) domain = _getDomainName(url); if (domain.isEmpty) domain = _getDomainName(url);
assert(url.toString().isNotEmpty); assert(url.toString().isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) { webViewController = webViewController ?? iosBelow11WebViewController;
var platformUtil = PlatformUtil();
if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion()); var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) { shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
List<Cookie> cookies = await _getCookiesWithJavaScript( List<Cookie> cookies = await _getCookiesWithJavaScript(
url: url, webViewController: iosBelow11WebViewController); url: url, webViewController: webViewController);
for (var i = 0; i < cookies.length; i++) { for (var i = 0; i < cookies.length; i++) {
await _setCookieWithJavaScript( await _setCookieWithJavaScript(
url: url, url: url,
@ -402,7 +475,7 @@ class CookieManager {
path: path, path: path,
domain: domain, domain: domain,
maxAge: -1, maxAge: -1,
webViewController: iosBelow11WebViewController); webViewController: webViewController);
} }
return; return;
} }
@ -462,13 +535,15 @@ class CookieManager {
} }
Future<String> _getCookieExpirationDate(int expiresDate) async { Future<String> _getCookieExpirationDate(int expiresDate) async {
var platformUtil = PlatformUtil(); var platformUtil = PlatformUtil.instance();
var dateTime = DateTime.fromMillisecondsSinceEpoch(expiresDate).toUtc(); var dateTime = DateTime.fromMillisecondsSinceEpoch(expiresDate).toUtc();
return await platformUtil.formatDate( return !kIsWeb ?
await platformUtil.formatDate(
date: dateTime, date: dateTime,
format: 'EEE, dd MMM yyyy hh:mm:ss z', format: 'EEE, dd MMM yyyy hh:mm:ss z',
locale: 'en_US', locale: 'en_US',
timezone: 'GMT'); timezone: 'GMT') :
await platformUtil.getWebCookieExpirationDate(date: dateTime);
} }
} }

View File

@ -8,6 +8,10 @@ import 'package:flutter/services.dart';
///On Android, this class has a custom implementation using `android.database.sqlite.SQLiteDatabase` because ///On Android, this class has a custom implementation using `android.database.sqlite.SQLiteDatabase` because
///[WebViewDatabase](https://developer.android.com/reference/android/webkit/WebViewDatabase) ///[WebViewDatabase](https://developer.android.com/reference/android/webkit/WebViewDatabase)
///doesn't offer the same functionalities as iOS `URLCredentialStorage`. ///doesn't offer the same functionalities as iOS `URLCredentialStorage`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class HttpAuthCredentialDatabase { class HttpAuthCredentialDatabase {
static HttpAuthCredentialDatabase? _instance; static HttpAuthCredentialDatabase? _instance;
static const MethodChannel _channel = const MethodChannel( static const MethodChannel _channel = const MethodChannel(

View File

@ -40,6 +40,10 @@ class InAppBrowserNotOpenedException implements Exception {
///This class uses the native WebView of the platform. ///This class uses the native WebView of the platform.
///The [webViewController] field can be used to access the [InAppWebViewController] API. ///The [webViewController] field can be used to access the [InAppWebViewController] API.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class InAppBrowser { class InAppBrowser {
///View ID used internally. ///View ID used internally.
late final String id; late final String id;

View File

@ -7,6 +7,10 @@ import 'package:flutter/services.dart' show rootBundle;
import 'mime_type_resolver.dart'; import 'mime_type_resolver.dart';
///This class allows you to create a simple server on `http://localhost:[port]/` in order to be able to load your assets file on a server. The default [port] value is `8080`. ///This class allows you to create a simple server on `http://localhost:[port]/` in order to be able to load your assets file on a server. The default [port] value is `8080`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class InAppLocalhostServer { class InAppLocalhostServer {
bool _started = false; bool _started = false;
HttpServer? _server; HttpServer? _server;

View File

@ -18,6 +18,11 @@ import '../util.dart';
///It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree. ///It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree.
/// ///
///Remember to dispose it when you don't need it anymore. ///Remember to dispose it when you don't need it anymore.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
class HeadlessInAppWebView implements WebView { class HeadlessInAppWebView implements WebView {
///View ID. ///View ID.
late final String id; late final String id;

View File

@ -20,6 +20,11 @@ import 'in_app_webview_settings.dart';
import '../pull_to_refresh/main.dart'; import '../pull_to_refresh/main.dart';
///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree. ///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
class InAppWebView extends StatefulWidget implements WebView { class InAppWebView extends StatefulWidget implements WebView {
/// `gestureRecognizers` specifies which gestures should be consumed by the WebView. /// `gestureRecognizers` specifies which gestures should be consumed by the WebView.
/// It is possible for other gesture recognizers to be competing with the web view on pointer /// It is possible for other gesture recognizers to be competing with the web view on pointer

View File

@ -228,9 +228,12 @@ class InAppWebViewSettings
///Set to `true` to disable context menu. The default value is `false`. ///Set to `true` to disable context menu. The default value is `false`.
/// ///
///**NOTE for Web**: this setting 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
bool disableContextMenu; bool disableContextMenu;
///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`. ///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.

View File

@ -1,10 +1,12 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
///Platform native utilities
class PlatformUtil { class PlatformUtil {
static PlatformUtil? _instance; static PlatformUtil? _instance;
static const MethodChannel _channel = const MethodChannel( static const MethodChannel _channel = const MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_platformutil'); 'com.pichillilorenzo/flutter_inappwebview_platformutil');
///Get [PlatformUtil] instance.
static PlatformUtil instance() { static PlatformUtil instance() {
return (_instance != null) ? _instance! : _init(); return (_instance != null) ? _instance! : _init();
} }
@ -18,6 +20,8 @@ class PlatformUtil {
static Future<dynamic> _handleMethod(MethodCall call) async {} static Future<dynamic> _handleMethod(MethodCall call) async {}
String? _cachedSystemVersion; String? _cachedSystemVersion;
///Get current platform system version.
Future<String> getSystemVersion() async { Future<String> getSystemVersion() async {
if (_cachedSystemVersion != null) { if (_cachedSystemVersion != null) {
return _cachedSystemVersion!; return _cachedSystemVersion!;
@ -28,6 +32,7 @@ class PlatformUtil {
return _cachedSystemVersion!; return _cachedSystemVersion!;
} }
///Format date.
Future<String> formatDate( Future<String> formatDate(
{required DateTime date, {required DateTime date,
required String format, required String format,
@ -40,4 +45,12 @@ class PlatformUtil {
args.putIfAbsent('timezone', () => timezone); args.putIfAbsent('timezone', () => timezone);
return await _channel.invokeMethod('formatDate', args); return await _channel.invokeMethod('formatDate', args);
} }
///Get cookie expiration date used by Web platform.
Future<String> getWebCookieExpirationDate(
{required DateTime date}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('date', () => date.millisecondsSinceEpoch);
return await _channel.invokeMethod('getWebCookieExpirationDate', args);
}
} }

View File

@ -15,6 +15,10 @@ import 'pull_to_refresh_settings.dart';
///(for example [WebView.onWebViewCreated] or [InAppBrowser.onBrowserCreated]). ///(for example [WebView.onWebViewCreated] or [InAppBrowser.onBrowserCreated]).
/// ///
///**NOTE for Android**: to be able to use the "pull-to-refresh" feature, [InAppWebViewSettings.useHybridComposition] must be `true`. ///**NOTE for Android**: to be able to use the "pull-to-refresh" feature, [InAppWebViewSettings.useHybridComposition] must be `true`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class PullToRefreshController { class PullToRefreshController {
@Deprecated("Use settings instead") @Deprecated("Use settings instead")
// ignore: deprecated_member_use_from_same_package // ignore: deprecated_member_use_from_same_package

View File

@ -184,6 +184,20 @@ class InAppWebViewInitialData {
this.androidHistoryUrl = this.historyUrl; this.androidHistoryUrl = this.historyUrl;
} }
static InAppWebViewInitialData? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
return InAppWebViewInitialData(
data: map["data"],
mimeType: map["mimeType"],
encoding: map["encoding"],
baseUrl: map["baseUrl"],
// ignore: deprecated_member_use_from_same_package
androidHistoryUrl: map["androidHistoryUrl"],
historyUrl: map["historyUrl"]);
}
Map<String, String> toMap() { Map<String, String> toMap() {
return { return {
"data": data, "data": data,

View File

@ -0,0 +1,67 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'in_app_web_view_web_element.dart';
import '../util.dart';
class HeadlessInAppWebViewWebElement {
String id;
late BinaryMessenger _messenger;
InAppWebViewWebElement? webView;
late MethodChannel? _channel;
HeadlessInAppWebViewWebElement({required this.id, required BinaryMessenger messenger,
required this.webView}) {
this._messenger = messenger;
_channel = MethodChannel(
'com.pichillilorenzo/flutter_headless_inappwebview_${this.id}',
const StandardMethodCodec(),
_messenger,
);
this._channel?.setMethodCallHandler(handleMethodCall);
}
Future<dynamic> handleMethodCall(MethodCall call) async {
switch (call.method) {
case "dispose":
dispose();
break;
case "setSize":
Size size = MapSize.fromMap(call.arguments['size'])!;
setSize(size);
break;
case "getSize":
return getSize().toMap();
default:
throw PlatformException(
code: 'Unimplemented',
details: 'flutter_inappwebview for web doesn\'t implement \'${call.method}\'',
);
}
}
void onWebViewCreated() async {
await _channel?.invokeMethod("onWebViewCreated");
}
void setSize(Size size) {
webView?.iframe.style.width = size.width.toString() + "px";
webView?.iframe.style.height = size.height.toString() + "px";
}
Size getSize() {
var width = webView?.iframe.getBoundingClientRect().width.toDouble() ?? 0.0;
var height = webView?.iframe.getBoundingClientRect().height.toDouble() ?? 0.0;
return Size(width, height);
}
void dispose() {
_channel?.setMethodCallHandler(null);
_channel = null;
webView?.dispose();
webView = null;
}
}

View File

@ -0,0 +1,62 @@
import 'dart:html';
import 'package:flutter/services.dart';
import 'web_platform_manager.dart';
import '../in_app_webview/in_app_webview_settings.dart';
import 'in_app_web_view_web_element.dart';
import 'headless_in_app_web_view_web_element.dart';
import '../types.dart';
class HeadlessInAppWebViewManager {
static late MethodChannel _sharedChannel;
late BinaryMessenger _messenger;
HeadlessInAppWebViewManager({required BinaryMessenger messenger}) {
this._messenger = messenger;
HeadlessInAppWebViewManager._sharedChannel = MethodChannel(
'com.pichillilorenzo/flutter_headless_inappwebview',
const StandardMethodCodec(),
_messenger,
);
HeadlessInAppWebViewManager._sharedChannel.setMethodCallHandler(handleMethod);
}
Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) {
case "run":
String id = call.arguments["id"];
Map<String, dynamic> params = call.arguments["params"].cast<String, dynamic>();
run(id, params);
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
return null;
}
void run(String id, Map<String, dynamic> params) {
var webView = InAppWebViewWebElement(viewId: id, messenger: _messenger);
var headlessWebView = HeadlessInAppWebViewWebElement(
id: id,
messenger: _messenger,
webView: webView
);
WebPlatformManager.webViews.putIfAbsent(id, () => webView);
webView.iframe.style.display = 'none';
Map<String, dynamic> initialSettings = params["initialSettings"].cast<String, dynamic>();
if (initialSettings.isEmpty) {
webView.initialSettings = InAppWebViewSettings();
} else {
webView.initialSettings = InAppWebViewSettings.fromMap(initialSettings);
}
webView.initialUrlRequest = URLRequest.fromMap(params["initialUrlRequest"]?.cast<String, dynamic>());
webView.initialFile = params["initialFile"];
webView.initialData = InAppWebViewInitialData.fromMap(params["initialData"]?.cast<String, dynamic>());
document.body?.append(webView.iframe);
webView.prepare();
headlessWebView.onWebViewCreated();
webView.makeInitialLoad();
}
}

View File

@ -3,14 +3,15 @@ import 'package:flutter/services.dart';
import 'dart:html'; import 'dart:html';
import 'dart:js' as js; import 'dart:js' as js;
import 'web_platform_manager.dart';
import '../in_app_webview/in_app_webview_settings.dart'; import '../in_app_webview/in_app_webview_settings.dart';
import '../types.dart'; import '../types.dart';
class InAppWebViewWebElement { class InAppWebViewWebElement {
late int _viewId; late dynamic _viewId;
late BinaryMessenger _messenger; late BinaryMessenger _messenger;
late IFrameElement iframe; late IFrameElement iframe;
late MethodChannel _channel; late MethodChannel? _channel;
InAppWebViewSettings? initialSettings; InAppWebViewSettings? initialSettings;
URLRequest? initialUrlRequest; URLRequest? initialUrlRequest;
InAppWebViewInitialData? initialData; InAppWebViewInitialData? initialData;
@ -20,7 +21,7 @@ class InAppWebViewWebElement {
late js.JsObject bridgeJsObject; late js.JsObject bridgeJsObject;
bool isLoading = false; bool isLoading = false;
InAppWebViewWebElement({required int viewId, required BinaryMessenger messenger}) { InAppWebViewWebElement({required dynamic viewId, required BinaryMessenger messenger}) {
this._viewId = viewId; this._viewId = viewId;
this._messenger = messenger; this._messenger = messenger;
iframe = IFrameElement() iframe = IFrameElement()
@ -35,11 +36,10 @@ class InAppWebViewWebElement {
_messenger, _messenger,
); );
this._channel.setMethodCallHandler(handleMethodCall); this._channel?.setMethodCallHandler(handleMethodCall);
bridgeJsObject = js.JsObject.fromBrowserObject(js.context['flutter_inappwebview']); bridgeJsObject = js.JsObject.fromBrowserObject(js.context[WebPlatformManager.BRIDGE_JS_OBJECT_NAME]);
bridgeJsObject['viewId'] = _viewId; bridgeJsObject['webViews'][_viewId] = bridgeJsObject.callMethod("createFlutterInAppWebView", [_viewId, iframe.id]);
bridgeJsObject['iframeId'] = iframe.id;
} }
/// Handles method calls over the MethodChannel of this plugin. /// Handles method calls over the MethodChannel of this plugin.
@ -87,6 +87,9 @@ class InAppWebViewWebElement {
InAppWebViewSettings newSettings = InAppWebViewSettings.fromMap(call.arguments["settings"].cast<String, dynamic>()); InAppWebViewSettings newSettings = InAppWebViewSettings.fromMap(call.arguments["settings"].cast<String, dynamic>());
setSettings(newSettings); setSettings(newSettings);
break; break;
case "dispose":
dispose();
break;
default: default:
throw PlatformException( throw PlatformException(
code: 'Unimplemented', code: 'Unimplemented',
@ -118,7 +121,16 @@ class InAppWebViewWebElement {
iframe.setAttribute("sandbox", sandbox.map((e) => e.toValue()).join(" ")); iframe.setAttribute("sandbox", sandbox.map((e) => e.toValue()).join(" "));
} }
bridgeJsObject.callMethod("prepare", [js.JsObject.jsify(settings.toMap())]); _callMethod("prepare", [js.JsObject.jsify(settings.toMap())]);
}
dynamic _callMethod(Object method, [List? args]) {
var webViews = bridgeJsObject['webViews'] as js.JsObject;
if (webViews.hasProperty(_viewId)) {
var webview = bridgeJsObject['webViews'][_viewId] as js.JsObject;
return webview.callMethod(method, args);
}
return null;
} }
void makeInitialLoad() async { void makeInitialLoad() async {
@ -168,27 +180,27 @@ class InAppWebViewWebElement {
} }
Future<void> reload() async { Future<void> reload() async {
bridgeJsObject.callMethod("reload"); _callMethod("reload");
} }
Future<void> goBack() async { Future<void> goBack() async {
bridgeJsObject.callMethod("goBack"); _callMethod("goBack");
} }
Future<void> goForward() async { Future<void> goForward() async {
bridgeJsObject.callMethod("goForward"); _callMethod("goForward");
} }
Future<void> goBackOrForward({required int steps}) async { Future<void> goBackOrForward({required int steps}) async {
bridgeJsObject.callMethod("goBackOrForward", [steps]); _callMethod("goBackOrForward", [steps]);
} }
Future<dynamic> evaluateJavascript({required String source}) async { Future<dynamic> evaluateJavascript({required String source}) async {
return bridgeJsObject.callMethod("evaluateJavascript", [source]); return _callMethod("evaluateJavascript", [source]);
} }
Future<void> stopLoading() async { Future<void> stopLoading() async {
bridgeJsObject.callMethod("stopLoading"); _callMethod("stopLoading");
} }
Set<Sandbox> getSandbox() { Set<Sandbox> getSandbox() {
@ -243,7 +255,7 @@ class InAppWebViewWebElement {
iframe.setAttribute("sandbox", sandbox.map((e) => e.toValue()).join(" ")); iframe.setAttribute("sandbox", sandbox.map((e) => e.toValue()).join(" "));
} }
bridgeJsObject.callMethod("setSettings", [js.JsObject.jsify(newSettings.toMap())]); _callMethod("setSettings", [js.JsObject.jsify(newSettings.toMap())]);
settings = newSettings; settings = newSettings;
} }
@ -254,7 +266,7 @@ class InAppWebViewWebElement {
var obj = { var obj = {
"url": url "url": url
}; };
await _channel.invokeMethod("onLoadStart", obj); await _channel?.invokeMethod("onLoadStart", obj);
} }
void onLoadStop(String url) async { void onLoadStop(String url) async {
@ -263,14 +275,14 @@ class InAppWebViewWebElement {
var obj = { var obj = {
"url": url "url": url
}; };
await _channel.invokeMethod("onLoadStop", obj); await _channel?.invokeMethod("onLoadStop", obj);
} }
void onUpdateVisitedHistory(String url) async { void onUpdateVisitedHistory(String url) async {
var obj = { var obj = {
"url": url "url": url
}; };
await _channel.invokeMethod("onUpdateVisitedHistory", obj); await _channel?.invokeMethod("onUpdateVisitedHistory", obj);
} }
void onScrollChanged(int x, int y) async { void onScrollChanged(int x, int y) async {
@ -278,7 +290,7 @@ class InAppWebViewWebElement {
"x": x, "x": x,
"y": y "y": y
}; };
await _channel.invokeMethod("onScrollChanged", obj); await _channel?.invokeMethod("onScrollChanged", obj);
} }
void onConsoleMessage(String type, String? message) async { void onConsoleMessage(String type, String? message) async {
@ -302,7 +314,7 @@ class InAppWebViewWebElement {
"messageLevel": messageLevel, "messageLevel": messageLevel,
"message": message "message": message
}; };
await _channel.invokeMethod("onConsoleMessage", obj); await _channel?.invokeMethod("onConsoleMessage", obj);
} }
Future<bool?> onCreateWindow(int windowId, String url, String? target, String? windowFeatures) async { Future<bool?> onCreateWindow(int windowId, String url, String? target, String? windowFeatures) async {
@ -330,15 +342,15 @@ class InAppWebViewWebElement {
}, },
"windowFeatures": windowFeaturesMap "windowFeatures": windowFeaturesMap
}; };
return await _channel.invokeMethod("onCreateWindow", obj); return await _channel?.invokeMethod("onCreateWindow", obj);
} }
void onWindowFocus() async { void onWindowFocus() async {
await _channel.invokeMethod("onWindowFocus"); await _channel?.invokeMethod("onWindowFocus");
} }
void onWindowBlur() async { void onWindowBlur() async {
await _channel.invokeMethod("onWindowBlur"); await _channel?.invokeMethod("onWindowBlur");
} }
void onPrint(String? url) async { void onPrint(String? url) async {
@ -346,15 +358,15 @@ class InAppWebViewWebElement {
"url": url "url": url
}; };
await _channel.invokeMethod("onPrint", obj); await _channel?.invokeMethod("onPrint", obj);
} }
void onEnterFullscreen() async { void onEnterFullscreen() async {
await _channel.invokeMethod("onEnterFullscreen"); await _channel?.invokeMethod("onEnterFullscreen");
} }
void onExitFullscreen() async { void onExitFullscreen() async {
await _channel.invokeMethod("onExitFullscreen"); await _channel?.invokeMethod("onExitFullscreen");
} }
void onTitleChanged(String? title) async { void onTitleChanged(String? title) async {
@ -362,7 +374,7 @@ class InAppWebViewWebElement {
"title": title "title": title
}; };
await _channel.invokeMethod("onTitleChanged", obj); await _channel?.invokeMethod("onTitleChanged", obj);
} }
void onZoomScaleChanged(double oldScale, double newScale) async { void onZoomScaleChanged(double oldScale, double newScale) async {
@ -371,6 +383,20 @@ class InAppWebViewWebElement {
"newScale": newScale "newScale": newScale
}; };
await _channel.invokeMethod("onZoomScaleChanged", obj); await _channel?.invokeMethod("onZoomScaleChanged", obj);
}
void dispose() {
_channel?.setMethodCallHandler(null);
_channel = null;
iframe.remove();
if (WebPlatformManager.webViews.containsKey(_viewId)) {
WebPlatformManager.webViews.remove(_viewId);
}
bridgeJsObject = js.JsObject.fromBrowserObject(js.context[WebPlatformManager.BRIDGE_JS_OBJECT_NAME]);
var webViews = bridgeJsObject['webViews'] as js.JsObject;
if (webViews.hasProperty(_viewId)) {
webViews.deleteProperty(_viewId);
}
} }
} }

View File

@ -0,0 +1,46 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:js' as js;
import 'web_platform_manager.dart';
class PlatformUtil {
late BinaryMessenger _messenger;
late MethodChannel? _channel;
PlatformUtil({required BinaryMessenger messenger}) {
this._messenger = messenger;
_channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_platformutil',
const StandardMethodCodec(),
_messenger,
);
this._channel?.setMethodCallHandler(handleMethodCall);
}
Future<dynamic> handleMethodCall(MethodCall call) async {
switch (call.method) {
case "getWebCookieExpirationDate":
int timestamp = call.arguments['date'];
return getWebCookieExpirationDate(timestamp);
default:
throw PlatformException(
code: 'Unimplemented',
details: 'flutter_inappwebview for web doesn\'t implement \'${call.method}\'',
);
}
}
String getWebCookieExpirationDate(int timestamp) {
var bridgeJsObject = js.JsObject.fromBrowserObject(js.context[WebPlatformManager.BRIDGE_JS_OBJECT_NAME]);
return bridgeJsObject.callMethod("getCookieExpirationDate", [timestamp]);
}
void dispose() {
_channel?.setMethodCallHandler(null);
_channel = null;
}
}

View File

@ -1,11 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'headless_inappwebview_manager.dart';
import 'web_platform_manager.dart'; import 'web_platform_manager.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'shims/dart_ui.dart' as ui; import 'shims/dart_ui.dart' as ui;
import 'in_app_web_view_web_element.dart'; import 'in_app_web_view_web_element.dart';
import 'platform_util.dart';
import 'package:js/js.dart'; import 'package:js/js.dart';
/// Builds an iframe based WebView. /// Builds an iframe based WebView.
@ -26,30 +26,21 @@ class FlutterInAppWebViewWebPlatform {
static void registerWith(Registrar registrar) { static void registerWith(Registrar registrar) {
final pluginInstance = FlutterInAppWebViewWebPlatform(registrar); final pluginInstance = FlutterInAppWebViewWebPlatform(registrar);
final platformUtil = PlatformUtil(messenger: registrar);
final headlessManager = HeadlessInAppWebViewManager(messenger: registrar);
_nativeCommunication = allowInterop(_dartNativeCommunication); _nativeCommunication = allowInterop(_dartNativeCommunication);
} }
/// Handles method calls over the MethodChannel of this plugin.
Future<dynamic> handleMethodCall(MethodCall call) async {
switch (call.method) {
default:
throw PlatformException(
code: 'Unimplemented',
details: 'flutter_inappwebview for web doesn\'t implement \'${call.method}\'',
);
}
}
} }
/// Allows assigning a function to be callable from `window.flutter_inappwebview.nativeCommunication()` /// Allows assigning a function to be callable from `window.flutter_inappwebview.nativeCommunication()`
@JS('flutter_inappwebview.nativeCommunication') @JS('flutter_inappwebview.nativeCommunication')
external set _nativeCommunication(Future<dynamic> Function(String method, int viewId, [List? args]) f); external set _nativeCommunication(Future<dynamic> Function(String method, dynamic viewId, [List? args]) f);
/// Allows calling the assigned function from Dart as well. /// Allows calling the assigned function from Dart as well.
@JS() @JS()
external Future<dynamic> nativeCommunication(String method, int viewId, [List? args]); external Future<dynamic> nativeCommunication(String method, dynamic viewId, [List? args]);
Future<dynamic> _dartNativeCommunication(String method, int viewId, [List? args]) async { Future<dynamic> _dartNativeCommunication(String method, dynamic viewId, [List? args]) async {
if (WebPlatformManager.webViews.containsKey(viewId)) { if (WebPlatformManager.webViews.containsKey(viewId)) {
var webViewHtmlElement = WebPlatformManager.webViews[viewId] as InAppWebViewWebElement; var webViewHtmlElement = WebPlatformManager.webViews[viewId] as InAppWebViewWebElement;
switch (method) { switch (method) {

View File

@ -1,3 +1,4 @@
abstract class WebPlatformManager { abstract class WebPlatformManager {
static final Map<int, dynamic> webViews = {}; static final String BRIDGE_JS_OBJECT_NAME = "flutter_inappwebview";
static final Map<dynamic, dynamic> webViews = {};
} }