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,
iframeAllow: "camera; microphone",
iframeAllowFullscreen: true,
disableVerticalScroll: true,
disableHorizontalScroll: true,
);
PullToRefreshController? pullToRefreshController;
@ -123,7 +121,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
initialUserScripts: UnmodifiableListView<UserScript>([]),
initialSettings: settings,
pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) {
onWebViewCreated: (controller) async {
webViewController = controller;
},
onLoadStart: (controller, url) async {
@ -162,17 +160,11 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
return NavigationActionPolicy.ALLOW;
},
onLoadStop: (controller, url) async {
print("onLoadStop");
pullToRefreshController?.endRefreshing();
setState(() {
this.url = url.toString();
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) {
pullToRefreshController?.endRefreshing();

View File

@ -1,337 +1,365 @@
window.flutter_inappwebview = {
viewId: null,
iframeId: null,
iframe: null,
windowAutoincrementId: 0,
windows: {},
isFullscreen: false,
documentTitle: null,
functionMap: {},
settings: {},
prepare: function (settings) {
window.flutter_inappwebview.settings = settings;
var iframe = document.getElementById(window.flutter_inappwebview.iframeId);
webViews: {},
createFlutterInAppWebView: function(viewId, iframeId) {
var webView = {
viewId: viewId,
iframeId: iframeId,
iframe: null,
windowAutoincrementId: 0,
windows: {},
isFullscreen: false,
documentTitle: null,
functionMap: {},
settings: {},
disableContextMenuHandler: function(event) {
event.preventDefault();
event.stopPropagation();
return false;
},
prepare: function (settings) {
webView.settings = settings;
var iframe = document.getElementById(iframeId);
document.addEventListener('fullscreenchange', function(event) {
// document.fullscreenElement will point to the element that
// is in fullscreen mode if there is one. If there isn't one,
// the value of the property is null.
if (document.fullscreenElement && document.fullscreenElement.id == window.flutter_inappwebview.iframeId) {
window.flutter_inappwebview.isFullscreen = true;
window.flutter_inappwebview.nativeCommunication('onEnterFullscreen', window.flutter_inappwebview.viewId);
} else if (!document.fullscreenElement && window.flutter_inappwebview.isFullscreen) {
window.flutter_inappwebview.isFullscreen = false;
window.flutter_inappwebview.nativeCommunication('onExitFullscreen', window.flutter_inappwebview.viewId);
} else {
window.flutter_inappwebview.isFullscreen = false;
}
});
document.addEventListener('fullscreenchange', function(event) {
// document.fullscreenElement will point to the element that
// is in fullscreen mode if there is one. If there isn't one,
// the value of the property is null.
if (document.fullscreenElement && document.fullscreenElement.id == iframeId) {
webView.isFullscreen = true;
window.flutter_inappwebview.nativeCommunication('onEnterFullscreen', viewId);
} else if (!document.fullscreenElement && webView.isFullscreen) {
webView.isFullscreen = false;
window.flutter_inappwebview.nativeCommunication('onExitFullscreen', viewId);
} else {
webView.isFullscreen = false;
}
});
if (iframe != null) {
window.flutter_inappwebview.iframe = iframe;
iframe.addEventListener('load', function (event) {
window.flutter_inappwebview.windowAutoincrementId = 0;
window.flutter_inappwebview.windows = {};
if (iframe != null) {
webView.iframe = iframe;
iframe.addEventListener('load', function (event) {
webView.windowAutoincrementId = 0;
webView.windows = {};
var url = iframe.src;
try {
url = iframe.contentWindow.location.href;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onLoadStart', window.flutter_inappwebview.viewId, [url]);
var url = iframe.src;
try {
url = iframe.contentWindow.location.href;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onLoadStart', viewId, [url]);
try {
var oldLogs = {
'log': iframe.contentWindow.console.log,
'debug': iframe.contentWindow.console.debug,
'error': iframe.contentWindow.console.error,
'info': iframe.contentWindow.console.info,
'warn': iframe.contentWindow.console.warn
};
for (var k in oldLogs) {
(function(oldLog) {
iframe.contentWindow.console[oldLog] = function() {
var message = '';
for (var i in arguments) {
if (message == '') {
message += arguments[i];
} else {
message += ' ' + arguments[i];
try {
var oldLogs = {
'log': iframe.contentWindow.console.log,
'debug': iframe.contentWindow.console.debug,
'error': iframe.contentWindow.console.error,
'info': iframe.contentWindow.console.info,
'warn': iframe.contentWindow.console.warn
};
for (var k in oldLogs) {
(function(oldLog) {
iframe.contentWindow.console[oldLog] = function() {
var message = '';
for (var i in arguments) {
if (message == '') {
message += arguments[i];
} else {
message += ' ' + arguments[i];
}
}
oldLogs[oldLog].call(iframe.contentWindow.console, ...arguments);
window.flutter_inappwebview.nativeCommunication('onConsoleMessage', viewId, [oldLog, message]);
}
})(k);
}
} catch (e) {
console.log(e);
}
try {
var originalPushState = iframe.contentWindow.history.pushState;
iframe.contentWindow.history.pushState = function (state, unused, url) {
originalPushState.call(iframe.contentWindow.history, state, unused, url);
var iframeUrl = iframe.src;
try {
iframeUrl = iframe.contentWindow.location.href;
} catch (e) {
console.log(e);
}
oldLogs[oldLog].call(iframe.contentWindow.console, ...arguments);
window.flutter_inappwebview.nativeCommunication('onConsoleMessage', window.flutter_inappwebview.viewId, [oldLog, message]);
window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', viewId, [iframeUrl]);
};
var originalReplaceState = iframe.contentWindow.history.replaceState;
iframe.contentWindow.history.replaceState = function (state, unused, url) {
originalReplaceState.call(iframe.contentWindow.history, state, unused, url);
var iframeUrl = iframe.src;
try {
iframeUrl = iframe.contentWindow.location.href;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', viewId, [iframeUrl]);
};
var originalOpen = iframe.contentWindow.open;
iframe.contentWindow.open = function (url, target, windowFeatures) {
var newWindow = originalOpen.call(iframe.contentWindow, ...arguments);
var windowId = webView.windowAutoincrementId;
webView.windowAutoincrementId++;
webView.windows[windowId] = newWindow;
window.flutter_inappwebview.nativeCommunication('onCreateWindow', viewId, [windowId, url, target, windowFeatures]).then(function(){}, function(handledByClient) {
if (handledByClient) {
newWindow.close();
}
});
return newWindow;
};
var originalPrint = iframe.contentWindow.print;
iframe.contentWindow.print = function () {
var iframeUrl = iframe.src;
try {
iframeUrl = iframe.contentWindow.location.href;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onPrint', viewId, [iframeUrl]);
originalPrint.call(iframe.contentWindow);
};
webView.functionMap = {
"window.open": iframe.contentWindow.open,
"window.print": iframe.contentWindow.print,
"window.history.pushState": iframe.contentWindow.history.pushState,
"window.history.replaceState": iframe.contentWindow.history.replaceState,
}
})(k);
}
} catch (e) {
console.log(e);
}
try {
var originalPushState = iframe.contentWindow.history.pushState;
iframe.contentWindow.history.pushState = function (state, unused, url) {
originalPushState.call(iframe.contentWindow.history, state, unused, url);
var iframeUrl = iframe.src;
try {
iframeUrl = iframe.contentWindow.location.href;
var initialTitle = iframe.contentDocument.title;
webView.documentTitle = initialTitle;
window.flutter_inappwebview.nativeCommunication('onTitleChanged', viewId, [initialTitle]);
new MutationObserver(function(mutations) {
var title = mutations[0].target.nodeValue;
if (title != webView.documentTitle) {
webView.documentTitle = title;
window.flutter_inappwebview.nativeCommunication('onTitleChanged', viewId, [title]);
}
}).observe(
iframe.contentDocument.querySelector('title'),
{ subtree: true, characterData: true, childList: true }
);
var oldPixelRatio = iframe.contentWindow.devicePixelRatio;
iframe.contentWindow.addEventListener('resize', function (e) {
var newPixelRatio = iframe.contentWindow.devicePixelRatio;
if(newPixelRatio !== oldPixelRatio){
window.flutter_inappwebview.nativeCommunication('onZoomScaleChanged', viewId, [oldPixelRatio, newPixelRatio]);
oldPixelRatio = newPixelRatio;
}
});
iframe.contentWindow.addEventListener('popstate', function (event) {
var iframeUrl = iframe.src;
try {
iframeUrl = iframe.contentWindow.location.href;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', viewId, [iframeUrl]);
});
iframe.contentWindow.addEventListener('scroll', function (event) {
var x = 0;
var y = 0;
try {
x = iframe.contentWindow.scrollX;
y = iframe.contentWindow.scrollY;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onScrollChanged', viewId, [x, y]);
});
iframe.contentWindow.addEventListener('focus', function (event) {
window.flutter_inappwebview.nativeCommunication('onWindowFocus', viewId);
});
iframe.contentWindow.addEventListener('blur', function (event) {
window.flutter_inappwebview.nativeCommunication('onWindowBlur', viewId);
});
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]);
};
var originalReplaceState = iframe.contentWindow.history.replaceState;
iframe.contentWindow.history.replaceState = function (state, unused, url) {
originalReplaceState.call(iframe.contentWindow.history, state, unused, url);
var iframeUrl = iframe.src;
try {
iframeUrl = iframe.contentWindow.location.href;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]);
};
var originalOpen = iframe.contentWindow.open;
iframe.contentWindow.open = function (url, target, windowFeatures) {
var newWindow = originalOpen.call(iframe.contentWindow, ...arguments);
var windowId = window.flutter_inappwebview.windowAutoincrementId;
window.flutter_inappwebview.windowAutoincrementId++;
window.flutter_inappwebview.windows[windowId] = newWindow;
window.flutter_inappwebview.nativeCommunication('onCreateWindow', window.flutter_inappwebview.viewId, [windowId, url, target, windowFeatures]).then(function(){}, function(handledByClient) {
if (handledByClient) {
newWindow.close();
if (!webView.settings.javaScriptCanOpenWindowsAutomatically) {
iframe.contentWindow.open = function () {
throw new Error('JavaScript cannot open windows automatically');
};
}
});
return newWindow;
};
var originalPrint = iframe.contentWindow.print;
iframe.contentWindow.print = function () {
var iframeUrl = iframe.src;
try {
iframeUrl = iframe.contentWindow.location.href;
if (!webView.settings.verticalScrollBarEnabled && !webView.settings.horizontalScrollBarEnabled) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled";
style.innerHTML = "body::-webkit-scrollbar { width: 0px; height: 0px; }";
iframe.contentDocument.head.append(style);
}
if (webView.settings.disableVerticalScroll) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableVerticalScroll";
style.innerHTML = "body { overflow-y: hidden; }";
iframe.contentDocument.head.append(style);
}
if (webView.settings.disableHorizontalScroll) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableHorizontalScroll";
style.innerHTML = "body { overflow-x: hidden; }";
iframe.contentDocument.head.append(style);
}
if (webView.settings.disableContextMenu) {
iframe.contentWindow.addEventListener('contextmenu', webView.disableContextMenuHandler);
}
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onPrint', window.flutter_inappwebview.viewId, [iframeUrl]);
originalPrint.call(iframe.contentWindow);
};
window.flutter_inappwebview.functionMap = {
"window.open": iframe.contentWindow.open,
"window.print": iframe.contentWindow.print,
"window.history.pushState": iframe.contentWindow.history.pushState,
"window.history.replaceState": iframe.contentWindow.history.replaceState,
}
var initialTitle = iframe.contentDocument.title;
window.flutter_inappwebview.documentTitle = initialTitle;
window.flutter_inappwebview.nativeCommunication('onTitleChanged', window.flutter_inappwebview.viewId, [initialTitle]);
new MutationObserver(function(mutations) {
var title = mutations[0].target.nodeValue;
if (title != window.flutter_inappwebview.documentTitle) {
window.flutter_inappwebview.documentTitle = title;
window.flutter_inappwebview.nativeCommunication('onTitleChanged', window.flutter_inappwebview.viewId, [title]);
}
}).observe(
iframe.contentDocument.querySelector('title'),
{ subtree: true, characterData: true, childList: true }
);
var oldPixelRatio = iframe.contentWindow.devicePixelRatio;
iframe.contentWindow.addEventListener('resize', function (e) {
var newPixelRatio = iframe.contentWindow.devicePixelRatio;
if(newPixelRatio !== oldPixelRatio){
window.flutter_inappwebview.nativeCommunication('onZoomScaleChanged', window.flutter_inappwebview.viewId, [oldPixelRatio, newPixelRatio]);
oldPixelRatio = newPixelRatio;
}
window.flutter_inappwebview.nativeCommunication('onLoadStop', viewId, [url]);
});
iframe.contentWindow.addEventListener('popstate', function (event) {
var iframeUrl = iframe.src;
try {
iframeUrl = iframe.contentWindow.location.href;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onUpdateVisitedHistory', window.flutter_inappwebview.viewId, [iframeUrl]);
});
iframe.contentWindow.addEventListener('scroll', function (event) {
var x = 0;
var y = 0;
try {
x = iframe.contentWindow.scrollX;
y = iframe.contentWindow.scrollY;
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onScrollChanged', window.flutter_inappwebview.viewId, [x, y]);
});
iframe.contentWindow.addEventListener('focus', function (event) {
window.flutter_inappwebview.nativeCommunication('onWindowFocus', window.flutter_inappwebview.viewId);
});
iframe.contentWindow.addEventListener('blur', function (event) {
window.flutter_inappwebview.nativeCommunication('onWindowBlur', window.flutter_inappwebview.viewId);
});
} catch (e) {
console.log(e);
}
},
setSettings: function (newSettings) {
var iframe = webView.iframe;
try {
if (!window.flutter_inappwebview.settings.javaScriptCanOpenWindowsAutomatically) {
iframe.contentWindow.open = function () {
throw new Error('JavaScript cannot open windows automatically');
};
if (webView.settings.javaScriptCanOpenWindowsAutomatically != newSettings.javaScriptCanOpenWindowsAutomatically) {
if (!newSettings.javaScriptCanOpenWindowsAutomatically) {
iframe.contentWindow.open = function () {
throw new Error('JavaScript cannot open windows automatically');
};
} else {
iframe.contentWindow.open = webView.functionMap["window.open"];
}
}
if (!window.flutter_inappwebview.settings.verticalScrollBarEnabled && !window.flutter_inappwebview.settings.horizontalScrollBarEnabled) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled";
style.innerHTML = "body::-webkit-scrollbar { width: 0px; height: 0px; }";
iframe.contentDocument.head.append(style);
if (webView.settings.verticalScrollBarEnabled != newSettings.verticalScrollBarEnabled &&
webView.settings.horizontalScrollBarEnabled != newSettings.horizontalScrollBarEnabled) {
if (!newSettings.verticalScrollBarEnabled && !newSettings.horizontalScrollBarEnabled) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled";
style.innerHTML = "body::-webkit-scrollbar { width: 0px; height: 0px; }";
iframe.contentDocument.head.append(style);
} else {
var styleElement = iframe.contentDocument.getElementById("settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled");
if (styleElement) { styleElement.remove() }
}
}
if (window.flutter_inappwebview.settings.disableVerticalScroll) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableVerticalScroll";
style.innerHTML = "body { overflow-y: hidden; }";
iframe.contentDocument.head.append(style);
if (webView.settings.disableVerticalScroll != newSettings.disableVerticalScroll) {
if (newSettings.disableVerticalScroll) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableVerticalScroll";
style.innerHTML = "body { overflow-y: hidden; }";
iframe.contentDocument.head.append(style);
} else {
var styleElement = iframe.contentDocument.getElementById("settings.disableVerticalScroll");
if (styleElement) { styleElement.remove() }
}
}
if (window.flutter_inappwebview.settings.disableHorizontalScroll) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableHorizontalScroll";
style.innerHTML = "body { overflow-x: hidden; }";
iframe.contentDocument.head.append(style);
if (webView.settings.disableHorizontalScroll != newSettings.disableHorizontalScroll) {
if (newSettings.disableHorizontalScroll) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableHorizontalScroll";
style.innerHTML = "body { overflow-x: hidden; }";
iframe.contentDocument.head.append(style);
} else {
var styleElement = iframe.contentDocument.getElementById("settings.disableHorizontalScroll");
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) {
console.log(e);
}
window.flutter_inappwebview.nativeCommunication('onLoadStop', window.flutter_inappwebview.viewId, [url]);
});
}
},
setSettings: function (newSettings) {
var iframe = window.flutter_inappwebview.iframe;
try {
if (window.flutter_inappwebview.settings.javaScriptCanOpenWindowsAutomatically != newSettings.javaScriptCanOpenWindowsAutomatically) {
if (!newSettings.javaScriptCanOpenWindowsAutomatically) {
iframe.contentWindow.open = function () {
throw new Error('JavaScript cannot open windows automatically');
};
} else {
iframe.contentWindow.open = window.flutter_inappwebview.functionMap["window.open"];
webView.settings = newSettings;
},
reload: function () {
var iframe = webView.iframe;
if (iframe != null && iframe.contentWindow != null) {
try {
iframe.contentWindow.location.reload();
} catch (e) {
console.log(e);
iframe.contentWindow.location.href = iframe.src;
}
}
},
goBack: function () {
var iframe = webView.iframe;
if (iframe != null) {
try {
iframe.contentWindow.history.back();
} catch (e) {
console.log(e);
}
}
},
goForward: function () {
var iframe = webView.iframe;
if (iframe != null) {
try {
iframe.contentWindow.history.forward();
} catch (e) {
console.log(e);
}
}
},
goForwardOrForward: function (steps) {
var iframe = webView.iframe;
if (iframe != null) {
try {
iframe.contentWindow.history.go(steps);
} catch (e) {
console.log(e);
}
}
},
evaluateJavascript: function (source) {
var iframe = webView.iframe;
var result = null;
if (iframe != null) {
try {
result = JSON.stringify(iframe.contentWindow.eval(source));
} catch (e) {}
}
return result;
},
stopLoading: function (steps) {
var iframe = webView.iframe;
if (iframe != null) {
try {
iframe.contentWindow.stop();
} catch (e) {
console.log(e);
}
}
}
};
if (window.flutter_inappwebview.settings.verticalScrollBarEnabled != newSettings.verticalScrollBarEnabled &&
window.flutter_inappwebview.settings.horizontalScrollBarEnabled != newSettings.horizontalScrollBarEnabled) {
if (!newSettings.verticalScrollBarEnabled && !newSettings.horizontalScrollBarEnabled) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled";
style.innerHTML = "body::-webkit-scrollbar { width: 0px; height: 0px; }";
iframe.contentDocument.head.append(style);
} else {
var styleElement = iframe.contentDocument.getElementById("settings.verticalScrollBarEnabled-settings.horizontalScrollBarEnabled");
if (styleElement) { styleElement.remove() }
}
}
if (window.flutter_inappwebview.settings.disableVerticalScroll != newSettings.disableVerticalScroll) {
if (newSettings.disableVerticalScroll) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableVerticalScroll";
style.innerHTML = "body { overflow-y: hidden; }";
iframe.contentDocument.head.append(style);
} else {
var styleElement = iframe.contentDocument.getElementById("settings.disableVerticalScroll");
if (styleElement) { styleElement.remove() }
}
}
if (window.flutter_inappwebview.settings.disableHorizontalScroll != newSettings.disableHorizontalScroll) {
if (newSettings.disableHorizontalScroll) {
var style = iframe.contentDocument.createElement('style');
style.id = "settings.disableHorizontalScroll";
style.innerHTML = "body { overflow-x: hidden; }";
iframe.contentDocument.head.append(style);
} else {
var styleElement = iframe.contentDocument.getElementById("settings.disableHorizontalScroll");
if (styleElement) { styleElement.remove() }
}
}
} catch (e) {
console.log(e);
}
window.flutter_inappwebview.settings = newSettings;
return webView;
},
reload: function () {
var iframe = window.flutter_inappwebview.iframe;
if (iframe != null && iframe.contentWindow != null) {
try {
iframe.contentWindow.location.reload();
} catch (e) {
console.log(e);
iframe.contentWindow.location.href = iframe.src;
}
}
},
goBack: function () {
var iframe = window.flutter_inappwebview.iframe;
if (iframe != null) {
try {
iframe.contentWindow.history.back();
} catch (e) {
console.log(e);
}
}
},
goForward: function () {
var iframe = window.flutter_inappwebview.iframe;
if (iframe != null) {
try {
iframe.contentWindow.history.forward();
} catch (e) {
console.log(e);
}
}
},
goForwardOrForward: function (steps) {
var iframe = window.flutter_inappwebview.iframe;
if (iframe != null) {
try {
iframe.contentWindow.history.go(steps);
} catch (e) {
console.log(e);
}
}
},
evaluateJavascript: function (source) {
var iframe = window.flutter_inappwebview.iframe;
var result = null;
if (iframe != null) {
try {
result = JSON.stringify(iframe.contentWindow.eval(source));
} catch (e) {}
}
return result;
},
stopLoading: function (steps) {
var iframe = window.flutter_inappwebview.iframe;
if (iframe != null) {
try {
iframe.contentWindow.stop();
} catch (e) {
console.log(e);
}
}
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
///`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).
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class ChromeSafariBrowser {
///View ID used internally.
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].
///
///**NOTE**: To make it work properly on Android, JavaScript should be enabled!
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class ContextMenu {
///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!)
///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.
///
///**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 {
static CookieManager? _instance;
static const MethodChannel _channel = const MethodChannel(
@ -48,15 +57,20 @@ class CookieManager {
///The default value of [path] is `"/"`.
///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)
///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.
///[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 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]).
///
///**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)))
///- iOS ([Official API - WKHTTPCookieStore.setCookie](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882007-setcookie))
///- Web
Future<void> setCookie(
{required Uri url,
required String name,
@ -68,19 +82,26 @@ class CookieManager {
bool? isSecure,
bool? isHttpOnly,
HTTPCookieSameSitePolicy? sameSite,
InAppWebViewController? iosBelow11WebViewController}) async {
@Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
if (domain == null) domain = _getDomainName(url);
webViewController = webViewController ?? iosBelow11WebViewController;
assert(url.toString().isNotEmpty);
assert(name.isNotEmpty);
assert(value.isNotEmpty);
assert(domain.isNotEmpty);
assert(path.isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) {
var platformUtil = PlatformUtil();
var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) {
if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion());
shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
await _setCookieWithJavaScript(
url: url,
name: name,
@ -91,7 +112,7 @@ class CookieManager {
maxAge: maxAge,
isSecure: isSecure,
sameSite: sameSite,
webViewController: iosBelow11WebViewController);
webViewController: webViewController);
return;
}
}
@ -161,28 +182,40 @@ class CookieManager {
///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)
///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.
///[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 and Web platform. JavaScript must be enabled in order to work.
///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].
///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!).
///
///**Supported Platforms/Implementations**:
///- 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))
///- Web
Future<List<Cookie>> getCookies(
{required Uri url,
InAppWebViewController? iosBelow11WebViewController}) async {
@Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
assert(url.toString().isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) {
var platformUtil = PlatformUtil();
var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) {
webViewController = webViewController ?? iosBelow11WebViewController;
if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion());
shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
return await _getCookiesWithJavaScript(
url: url, webViewController: iosBelow11WebViewController);
url: url, webViewController: webViewController);
}
}
@ -225,10 +258,12 @@ class CookieManager {
.toList();
documentCookies.forEach((documentCookie) {
List<String> cookie = documentCookie.split('=');
cookies.add(Cookie(
name: cookie[0],
value: cookie[1],
));
if (cookie.length > 1) {
cookies.add(Cookie(
name: cookie[0],
value: cookie[1],
));
}
});
return cookies;
}
@ -251,10 +286,12 @@ class CookieManager {
.toList();
documentCookies.forEach((documentCookie) {
List<String> cookie = documentCookie.split('=');
cookies.add(Cookie(
name: cookie[0],
value: cookie[1],
));
if (cookie.length > 1) {
cookies.add(Cookie(
name: cookie[0],
value: cookie[1],
));
}
});
await headlessWebView.dispose();
return cookies;
@ -262,30 +299,42 @@ class CookieManager {
///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)
///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.
///[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 and Web platform. JavaScript must be enabled in order to work.
///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].
///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!).
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
Future<Cookie?> getCookie(
{required Uri url,
required String name,
InAppWebViewController? iosBelow11WebViewController}) async {
@Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
assert(url.toString().isNotEmpty);
assert(name.isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) {
var platformUtil = PlatformUtil();
var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) {
webViewController = webViewController ?? iosBelow11WebViewController;
if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion());
shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
List<Cookie> cookies = await _getCookiesWithJavaScript(
url: url, webViewController: iosBelow11WebViewController);
url: url, webViewController: webViewController);
return cookies
.cast<Cookie?>()
.firstWhere((cookie) => cookie!.name == name, orElse: () => null);
@ -319,31 +368,43 @@ class CookieManager {
///The default value of [path] is `"/"`.
///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)
///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.
///[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 and Web platform. JavaScript must be enabled in order to work.
///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!).
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS ([Official API - WKHTTPCookieStore.delete](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882009-delete)
///- Web
Future<void> deleteCookie(
{required Uri url,
required String name,
String domain = "",
String path = "/",
InAppWebViewController? iosBelow11WebViewController}) async {
@Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
if (domain.isEmpty) domain = _getDomainName(url);
assert(url.toString().isNotEmpty);
assert(name.isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) {
var platformUtil = PlatformUtil();
var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) {
webViewController = webViewController ?? iosBelow11WebViewController;
if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion());
shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
await _setCookieWithJavaScript(
url: url,
name: name,
@ -351,7 +412,7 @@ class CookieManager {
path: path,
domain: domain,
maxAge: -1,
webViewController: iosBelow11WebViewController);
webViewController: webViewController);
return;
}
}
@ -369,31 +430,43 @@ class CookieManager {
///The default value of [path] is `"/"`.
///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)
///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.
///[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 and Web platform. JavaScript must be enabled in order to work.
///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!).
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
Future<void> deleteCookies(
{required Uri url,
String domain = "",
String path = "/",
InAppWebViewController? iosBelow11WebViewController}) async {
@Deprecated("Use webViewController instead") InAppWebViewController? iosBelow11WebViewController,
InAppWebViewController? webViewController}) async {
if (domain.isEmpty) domain = _getDomainName(url);
assert(url.toString().isNotEmpty);
if (defaultTargetPlatform == TargetPlatform.iOS) {
var platformUtil = PlatformUtil();
var version = double.tryParse(await platformUtil.getSystemVersion());
if (version != null && version < 11.0) {
webViewController = webViewController ?? iosBelow11WebViewController;
if (defaultTargetPlatform == TargetPlatform.iOS || kIsWeb) {
var shouldUseJavascript = kIsWeb;
if (defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb) {
var platformUtil = PlatformUtil.instance();
var version = double.tryParse(await platformUtil.getSystemVersion());
shouldUseJavascript = version != null && version < 11.0;
}
if (shouldUseJavascript) {
List<Cookie> cookies = await _getCookiesWithJavaScript(
url: url, webViewController: iosBelow11WebViewController);
url: url, webViewController: webViewController);
for (var i = 0; i < cookies.length; i++) {
await _setCookieWithJavaScript(
url: url,
@ -402,7 +475,7 @@ class CookieManager {
path: path,
domain: domain,
maxAge: -1,
webViewController: iosBelow11WebViewController);
webViewController: webViewController);
}
return;
}
@ -462,13 +535,15 @@ class CookieManager {
}
Future<String> _getCookieExpirationDate(int expiresDate) async {
var platformUtil = PlatformUtil();
var platformUtil = PlatformUtil.instance();
var dateTime = DateTime.fromMillisecondsSinceEpoch(expiresDate).toUtc();
return await platformUtil.formatDate(
date: dateTime,
format: 'EEE, dd MMM yyyy hh:mm:ss z',
locale: 'en_US',
timezone: 'GMT');
return !kIsWeb ?
await platformUtil.formatDate(
date: dateTime,
format: 'EEE, dd MMM yyyy hh:mm:ss z',
locale: 'en_US',
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
///[WebViewDatabase](https://developer.android.com/reference/android/webkit/WebViewDatabase)
///doesn't offer the same functionalities as iOS `URLCredentialStorage`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class HttpAuthCredentialDatabase {
static HttpAuthCredentialDatabase? _instance;
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.
///The [webViewController] field can be used to access the [InAppWebViewController] API.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class InAppBrowser {
///View ID used internally.
late final String id;

View File

@ -7,6 +7,10 @@ import 'package:flutter/services.dart' show rootBundle;
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`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class InAppLocalhostServer {
bool _started = false;
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.
///
///Remember to dispose it when you don't need it anymore.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
class HeadlessInAppWebView implements WebView {
///View ID.
late final String id;

View File

@ -20,6 +20,11 @@ import 'in_app_webview_settings.dart';
import '../pull_to_refresh/main.dart';
///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 {
/// `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

View File

@ -228,9 +228,12 @@ class InAppWebViewSettings
///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**:
///- Android native WebView
///- iOS
///- Web
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`.

View File

@ -1,10 +1,12 @@
import 'package:flutter/services.dart';
///Platform native utilities
class PlatformUtil {
static PlatformUtil? _instance;
static const MethodChannel _channel = const MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_platformutil');
///Get [PlatformUtil] instance.
static PlatformUtil instance() {
return (_instance != null) ? _instance! : _init();
}
@ -18,6 +20,8 @@ class PlatformUtil {
static Future<dynamic> _handleMethod(MethodCall call) async {}
String? _cachedSystemVersion;
///Get current platform system version.
Future<String> getSystemVersion() async {
if (_cachedSystemVersion != null) {
return _cachedSystemVersion!;
@ -28,6 +32,7 @@ class PlatformUtil {
return _cachedSystemVersion!;
}
///Format date.
Future<String> formatDate(
{required DateTime date,
required String format,
@ -40,4 +45,12 @@ class PlatformUtil {
args.putIfAbsent('timezone', () => timezone);
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]).
///
///**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 {
@Deprecated("Use settings instead")
// ignore: deprecated_member_use_from_same_package

View File

@ -184,6 +184,20 @@ class InAppWebViewInitialData {
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() {
return {
"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:js' as js;
import 'web_platform_manager.dart';
import '../in_app_webview/in_app_webview_settings.dart';
import '../types.dart';
class InAppWebViewWebElement {
late int _viewId;
late dynamic _viewId;
late BinaryMessenger _messenger;
late IFrameElement iframe;
late MethodChannel _channel;
late MethodChannel? _channel;
InAppWebViewSettings? initialSettings;
URLRequest? initialUrlRequest;
InAppWebViewInitialData? initialData;
@ -20,7 +21,7 @@ class InAppWebViewWebElement {
late js.JsObject bridgeJsObject;
bool isLoading = false;
InAppWebViewWebElement({required int viewId, required BinaryMessenger messenger}) {
InAppWebViewWebElement({required dynamic viewId, required BinaryMessenger messenger}) {
this._viewId = viewId;
this._messenger = messenger;
iframe = IFrameElement()
@ -35,11 +36,10 @@ class InAppWebViewWebElement {
_messenger,
);
this._channel.setMethodCallHandler(handleMethodCall);
this._channel?.setMethodCallHandler(handleMethodCall);
bridgeJsObject = js.JsObject.fromBrowserObject(js.context['flutter_inappwebview']);
bridgeJsObject['viewId'] = _viewId;
bridgeJsObject['iframeId'] = iframe.id;
bridgeJsObject = js.JsObject.fromBrowserObject(js.context[WebPlatformManager.BRIDGE_JS_OBJECT_NAME]);
bridgeJsObject['webViews'][_viewId] = bridgeJsObject.callMethod("createFlutterInAppWebView", [_viewId, iframe.id]);
}
/// 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>());
setSettings(newSettings);
break;
case "dispose":
dispose();
break;
default:
throw PlatformException(
code: 'Unimplemented',
@ -118,7 +121,16 @@ class InAppWebViewWebElement {
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 {
@ -168,27 +180,27 @@ class InAppWebViewWebElement {
}
Future<void> reload() async {
bridgeJsObject.callMethod("reload");
_callMethod("reload");
}
Future<void> goBack() async {
bridgeJsObject.callMethod("goBack");
_callMethod("goBack");
}
Future<void> goForward() async {
bridgeJsObject.callMethod("goForward");
_callMethod("goForward");
}
Future<void> goBackOrForward({required int steps}) async {
bridgeJsObject.callMethod("goBackOrForward", [steps]);
_callMethod("goBackOrForward", [steps]);
}
Future<dynamic> evaluateJavascript({required String source}) async {
return bridgeJsObject.callMethod("evaluateJavascript", [source]);
return _callMethod("evaluateJavascript", [source]);
}
Future<void> stopLoading() async {
bridgeJsObject.callMethod("stopLoading");
_callMethod("stopLoading");
}
Set<Sandbox> getSandbox() {
@ -243,7 +255,7 @@ class InAppWebViewWebElement {
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;
}
@ -254,7 +266,7 @@ class InAppWebViewWebElement {
var obj = {
"url": url
};
await _channel.invokeMethod("onLoadStart", obj);
await _channel?.invokeMethod("onLoadStart", obj);
}
void onLoadStop(String url) async {
@ -263,14 +275,14 @@ class InAppWebViewWebElement {
var obj = {
"url": url
};
await _channel.invokeMethod("onLoadStop", obj);
await _channel?.invokeMethod("onLoadStop", obj);
}
void onUpdateVisitedHistory(String url) async {
var obj = {
"url": url
};
await _channel.invokeMethod("onUpdateVisitedHistory", obj);
await _channel?.invokeMethod("onUpdateVisitedHistory", obj);
}
void onScrollChanged(int x, int y) async {
@ -278,7 +290,7 @@ class InAppWebViewWebElement {
"x": x,
"y": y
};
await _channel.invokeMethod("onScrollChanged", obj);
await _channel?.invokeMethod("onScrollChanged", obj);
}
void onConsoleMessage(String type, String? message) async {
@ -302,7 +314,7 @@ class InAppWebViewWebElement {
"messageLevel": messageLevel,
"message": message
};
await _channel.invokeMethod("onConsoleMessage", obj);
await _channel?.invokeMethod("onConsoleMessage", obj);
}
Future<bool?> onCreateWindow(int windowId, String url, String? target, String? windowFeatures) async {
@ -330,15 +342,15 @@ class InAppWebViewWebElement {
},
"windowFeatures": windowFeaturesMap
};
return await _channel.invokeMethod("onCreateWindow", obj);
return await _channel?.invokeMethod("onCreateWindow", obj);
}
void onWindowFocus() async {
await _channel.invokeMethod("onWindowFocus");
await _channel?.invokeMethod("onWindowFocus");
}
void onWindowBlur() async {
await _channel.invokeMethod("onWindowBlur");
await _channel?.invokeMethod("onWindowBlur");
}
void onPrint(String? url) async {
@ -346,15 +358,15 @@ class InAppWebViewWebElement {
"url": url
};
await _channel.invokeMethod("onPrint", obj);
await _channel?.invokeMethod("onPrint", obj);
}
void onEnterFullscreen() async {
await _channel.invokeMethod("onEnterFullscreen");
await _channel?.invokeMethod("onEnterFullscreen");
}
void onExitFullscreen() async {
await _channel.invokeMethod("onExitFullscreen");
await _channel?.invokeMethod("onExitFullscreen");
}
void onTitleChanged(String? title) async {
@ -362,7 +374,7 @@ class InAppWebViewWebElement {
"title": title
};
await _channel.invokeMethod("onTitleChanged", obj);
await _channel?.invokeMethod("onTitleChanged", obj);
}
void onZoomScaleChanged(double oldScale, double newScale) async {
@ -371,6 +383,20 @@ class InAppWebViewWebElement {
"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 'package:flutter/services.dart';
import 'headless_inappwebview_manager.dart';
import 'web_platform_manager.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'shims/dart_ui.dart' as ui;
import 'in_app_web_view_web_element.dart';
import 'platform_util.dart';
import 'package:js/js.dart';
/// Builds an iframe based WebView.
@ -26,30 +26,21 @@ class FlutterInAppWebViewWebPlatform {
static void registerWith(Registrar registrar) {
final pluginInstance = FlutterInAppWebViewWebPlatform(registrar);
final platformUtil = PlatformUtil(messenger: registrar);
final headlessManager = HeadlessInAppWebViewManager(messenger: registrar);
_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()`
@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.
@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)) {
var webViewHtmlElement = WebPlatformManager.webViews[viewId] as InAppWebViewWebElement;
switch (method) {

View File

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