Isolate extension WebView profiles and follow host proxy settings

This commit is contained in:
Levi committed 2026-09-14 00:30:52 +08:00
1 parent cee5f3336e
commit a0e84a75b1
556 files changed
+71097 -31

No files matched your search

@@ -0,0 +1,83 @@
## 1.1.2
- Updated flutter_inappwebview_platform_interface version to ^1.3.0
## 1.1.1
- Updated flutter_inappwebview_platform_interface version to ^1.2.0
## 1.1.0+3
- Updated flutter_inappwebview_platform_interface version
## ## 1.1.0+2
- Updated pubspec.yaml
## 1.1.0+1
- Fixed "v6.1.0 fails to compile on Xcode 15" [#2288](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2288)
## 1.1.0
- Added `InAppWebView` support
- Added privacy manifest
- Updates minimum supported SDK version to Flutter 3.24/Dart 3.5.
- Fixed "[MACOS] launching InAppBrowser with 'hidden: true' calls onExit immediately" [#1939](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1939)
- Fixed XCode 16 build
## 1.0.11
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.10`
## 1.0.10
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.9`
- Fix typos and other code improvements (thanks to [michalsrutek](https://github.com/michalsrutek))
- Fixed "runtime issue of SecTrustCopyExceptions 'This method should not be called on the main thread as it may lead to UI unresponsiveness.' when using onReceivedServerTrustAuthRequest" [#1924](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1924)
## 1.0.9
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.8`
## 1.0.8
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.7`
- Implemented `InAppBrowser.onMainWindowWillClose` event
## 1.0.7
- Implemented `InAppWebViewSettings.interceptOnlyAsyncAjaxRequests`
- Updated `useShouldInterceptAjaxRequest` automatic infer logic
- Updated `CookieManager` methods return value
- Fixed "iOS crash at public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)" [#1912](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1912)
## 1.0.6
- Fixed error in InterceptAjaxRequestJS 'Failed to set responseType property'
- Fixed shouldInterceptAjaxRequest javascript code when overriding XMLHttpRequest.open method parameters
## 1.0.5
- Fixed "getFavicons: _TypeError: type '_Map<String, dynamic>' is not a subtype of type 'Iterable<dynamic>'" [#1897](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1897)
## 1.0.4
- Possible fix for "iOS Fatal Crash" [#1894](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1894)
## 1.0.3
- Call `super.dispose();` on `InAppBrowser` implementation
## 1.0.2
- Fixed "Cloudflare Turnstile failure" [#1738](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738)
## 1.0.1
- Added `PlatformPrintJobController.onComplete` setter
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.2`
## 1.0.0
Initial release.
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2023 Lorenzo Pichilli
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,13 @@
# flutter\_inappwebview\_macos
The Apple macOS WKWebView implementation of [`flutter_inappwebview`](https://pub.dev/packages/flutter_inappwebview).
## Usage
This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin),
which means you can simply use `flutter_inappwebview`
normally. This package will be automatically included in your app when you do,
so you do not need to add it to your `pubspec.yaml`.
However, if you `import` this package to use any of its APIs directly, you
should add it to your `pubspec.yaml` as usual.
@@ -0,0 +1,3 @@
library flutter_inappwebview_macos;
export 'src/main.dart';
@@ -0,0 +1,433 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
import 'in_app_webview/headless_in_app_webview.dart';
import 'platform_util.dart';
/// Object specifying creation parameters for creating a [MacOSCookieManager].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformCookieManagerCreationParams] for
/// more information.
@immutable
class MacOSCookieManagerCreationParams
extends PlatformCookieManagerCreationParams {
/// Creates a new [MacOSCookieManagerCreationParams] instance.
const MacOSCookieManagerCreationParams(
// This parameter prevents breaking changes later.
// ignore: avoid_unused_constructor_parameters
PlatformCookieManagerCreationParams params,
) : super();
/// Creates a [MacOSCookieManagerCreationParams] instance based on [PlatformCookieManagerCreationParams].
factory MacOSCookieManagerCreationParams.fromPlatformCookieManagerCreationParams(
PlatformCookieManagerCreationParams params) {
return MacOSCookieManagerCreationParams(params);
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager}
class MacOSCookieManager extends PlatformCookieManager with ChannelController {
/// Creates a new [MacOSCookieManager].
MacOSCookieManager(PlatformCookieManagerCreationParams params)
: super.implementation(
params is MacOSCookieManagerCreationParams
? params
: MacOSCookieManagerCreationParams
.fromPlatformCookieManagerCreationParams(params),
) {
channel = const MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_cookiemanager');
handler = handleMethod;
initMethodCallHandler();
}
static MacOSCookieManager? _instance;
///Gets the [MacOSCookieManager] shared instance.
static MacOSCookieManager instance() {
return (_instance != null) ? _instance! : _init();
}
static MacOSCookieManager _init() {
_instance = MacOSCookieManager(MacOSCookieManagerCreationParams(
const PlatformCookieManagerCreationParams()));
return _instance!;
}
Future<dynamic> _handleMethod(MethodCall call) async {}
@override
Future<bool> setCookie(
{required WebUri url,
required String name,
required String value,
String path = "/",
String? domain,
int? expiresDate,
int? maxAge,
bool? isSecure,
bool? isHttpOnly,
HTTPCookieSameSitePolicy? sameSite,
@Deprecated("Use webViewController instead")
PlatformInAppWebViewController? iosBelow11WebViewController,
PlatformInAppWebViewController? webViewController}) async {
webViewController = webViewController ?? iosBelow11WebViewController;
assert(url.toString().isNotEmpty);
assert(name.isNotEmpty);
assert(value.isNotEmpty);
assert(path.isNotEmpty);
if (await _shouldUseJavascript()) {
await _setCookieWithJavaScript(
url: url,
name: name,
value: value,
domain: domain,
path: path,
expiresDate: expiresDate,
maxAge: maxAge,
isSecure: isSecure,
sameSite: sameSite,
webViewController: webViewController);
return true;
}
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url.toString());
args.putIfAbsent('name', () => name);
args.putIfAbsent('value', () => value);
args.putIfAbsent('domain', () => domain);
args.putIfAbsent('path', () => path);
args.putIfAbsent('expiresDate', () => expiresDate?.toString());
args.putIfAbsent('maxAge', () => maxAge);
args.putIfAbsent('isSecure', () => isSecure);
args.putIfAbsent('isHttpOnly', () => isHttpOnly);
args.putIfAbsent('sameSite', () => sameSite?.toNativeValue());
return await channel?.invokeMethod<bool>('setCookie', args) ?? false;
}
Future<void> _setCookieWithJavaScript(
{required WebUri url,
required String name,
required String value,
String path = "/",
String? domain,
int? expiresDate,
int? maxAge,
bool? isSecure,
HTTPCookieSameSitePolicy? sameSite,
PlatformInAppWebViewController? webViewController}) async {
var cookieValue = name + "=" + value + "; Path=" + path;
if (domain != null) cookieValue += "; Domain=" + domain;
if (expiresDate != null)
cookieValue += "; Expires=" + await _getCookieExpirationDate(expiresDate);
if (maxAge != null) cookieValue += "; Max-Age=" + maxAge.toString();
if (isSecure != null && isSecure) cookieValue += "; Secure";
if (sameSite != null)
cookieValue += "; SameSite=" + sameSite.toNativeValue();
cookieValue += ";";
if (webViewController != null) {
final javaScriptEnabled =
(await webViewController.getSettings())?.javaScriptEnabled ?? false;
if (javaScriptEnabled) {
await webViewController.evaluateJavascript(
source: 'document.cookie="$cookieValue"');
return;
}
}
final setCookieCompleter = Completer<void>();
final headlessWebView =
MacOSHeadlessInAppWebView(MacOSHeadlessInAppWebViewCreationParams(
initialUrlRequest: URLRequest(url: url),
onLoadStop: (controller, url) async {
await controller.evaluateJavascript(
source: 'document.cookie="$cookieValue"');
setCookieCompleter.complete();
}));
await headlessWebView.run();
await setCookieCompleter.future;
await headlessWebView.dispose();
}
@override
Future<List<Cookie>> getCookies(
{required WebUri url,
@Deprecated("Use webViewController instead")
PlatformInAppWebViewController? iosBelow11WebViewController,
PlatformInAppWebViewController? webViewController}) async {
assert(url.toString().isNotEmpty);
webViewController = webViewController ?? iosBelow11WebViewController;
if (await _shouldUseJavascript()) {
return await _getCookiesWithJavaScript(
url: url, webViewController: webViewController);
}
List<Cookie> cookies = [];
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url.toString());
List<dynamic> cookieListMap =
await channel?.invokeMethod<List>('getCookies', args) ?? [];
cookieListMap = cookieListMap.cast<Map<dynamic, dynamic>>();
cookieListMap.forEach((cookieMap) {
cookies.add(Cookie(
name: cookieMap["name"],
value: cookieMap["value"],
expiresDate: cookieMap["expiresDate"],
isSessionOnly: cookieMap["isSessionOnly"],
domain: cookieMap["domain"],
sameSite:
HTTPCookieSameSitePolicy.fromNativeValue(cookieMap["sameSite"]),
isSecure: cookieMap["isSecure"],
isHttpOnly: cookieMap["isHttpOnly"],
path: cookieMap["path"]));
});
return cookies;
}
Future<List<Cookie>> _getCookiesWithJavaScript(
{required WebUri url,
PlatformInAppWebViewController? webViewController}) async {
assert(url.toString().isNotEmpty);
List<Cookie> cookies = [];
if (webViewController != null) {
final javaScriptEnabled =
(await webViewController.getSettings())?.javaScriptEnabled ?? false;
if (javaScriptEnabled) {
List<String> documentCookies = (await webViewController
.evaluateJavascript(source: 'document.cookie') as String)
.split(';')
.map((documentCookie) => documentCookie.trim())
.toList();
documentCookies.forEach((documentCookie) {
List<String> cookie = documentCookie.split('=');
if (cookie.length > 1) {
cookies.add(Cookie(
name: cookie[0],
value: cookie[1],
));
}
});
return cookies;
}
}
final pageLoaded = Completer<void>();
final headlessWebView =
MacOSHeadlessInAppWebView(MacOSHeadlessInAppWebViewCreationParams(
initialUrlRequest: URLRequest(url: url),
onLoadStop: (controller, url) async {
pageLoaded.complete();
},
));
await headlessWebView.run();
await pageLoaded.future;
List<String> documentCookies = (await headlessWebView.webViewController!
.evaluateJavascript(source: 'document.cookie') as String)
.split(';')
.map((documentCookie) => documentCookie.trim())
.toList();
documentCookies.forEach((documentCookie) {
List<String> cookie = documentCookie.split('=');
if (cookie.length > 1) {
cookies.add(Cookie(
name: cookie[0],
value: cookie[1],
));
}
});
await headlessWebView.dispose();
return cookies;
}
@override
Future<Cookie?> getCookie(
{required WebUri url,
required String name,
@Deprecated("Use webViewController instead")
PlatformInAppWebViewController? iosBelow11WebViewController,
PlatformInAppWebViewController? webViewController}) async {
assert(url.toString().isNotEmpty);
assert(name.isNotEmpty);
webViewController = webViewController ?? iosBelow11WebViewController;
if (await _shouldUseJavascript()) {
List<Cookie> cookies = await _getCookiesWithJavaScript(
url: url, webViewController: webViewController);
return cookies
.cast<Cookie?>()
.firstWhere((cookie) => cookie!.name == name, orElse: () => null);
}
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url.toString());
List<dynamic> cookies =
await channel?.invokeMethod<List>('getCookies', args) ?? [];
cookies = cookies.cast<Map<dynamic, dynamic>>();
for (var i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].cast<String, dynamic>();
if (cookies[i]["name"] == name)
return Cookie(
name: cookies[i]["name"],
value: cookies[i]["value"],
expiresDate: cookies[i]["expiresDate"],
isSessionOnly: cookies[i]["isSessionOnly"],
domain: cookies[i]["domain"],
sameSite: HTTPCookieSameSitePolicy.fromNativeValue(
cookies[i]["sameSite"]),
isSecure: cookies[i]["isSecure"],
isHttpOnly: cookies[i]["isHttpOnly"],
path: cookies[i]["path"]);
}
return null;
}
@override
Future<bool> deleteCookie(
{required WebUri url,
required String name,
String path = "/",
String? domain,
@Deprecated("Use webViewController instead")
PlatformInAppWebViewController? iosBelow11WebViewController,
PlatformInAppWebViewController? webViewController}) async {
assert(url.toString().isNotEmpty);
assert(name.isNotEmpty);
webViewController = webViewController ?? iosBelow11WebViewController;
if (await _shouldUseJavascript()) {
await _setCookieWithJavaScript(
url: url,
name: name,
value: "",
path: path,
domain: domain,
maxAge: -1,
webViewController: webViewController);
return true;
}
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url.toString());
args.putIfAbsent('name', () => name);
args.putIfAbsent('domain', () => domain);
args.putIfAbsent('path', () => path);
return await channel?.invokeMethod<bool>('deleteCookie', args) ?? false;
}
@override
Future<bool> deleteCookies(
{required WebUri url,
String path = "/",
String? domain,
@Deprecated("Use webViewController instead")
PlatformInAppWebViewController? iosBelow11WebViewController,
PlatformInAppWebViewController? webViewController}) async {
assert(url.toString().isNotEmpty);
webViewController = webViewController ?? iosBelow11WebViewController;
if (await _shouldUseJavascript()) {
List<Cookie> cookies = await _getCookiesWithJavaScript(
url: url, webViewController: webViewController);
for (var i = 0; i < cookies.length; i++) {
await _setCookieWithJavaScript(
url: url,
name: cookies[i].name,
value: "",
path: path,
domain: domain,
maxAge: -1,
webViewController: webViewController);
}
return true;
}
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url.toString());
args.putIfAbsent('domain', () => domain);
args.putIfAbsent('path', () => path);
return await channel?.invokeMethod<bool>('deleteCookies', args) ?? false;
}
@override
Future<bool> deleteAllCookies() async {
Map<String, dynamic> args = <String, dynamic>{};
return await channel?.invokeMethod<bool>('deleteAllCookies', args) ?? false;
}
@override
Future<List<Cookie>> getAllCookies() async {
List<Cookie> cookies = [];
Map<String, dynamic> args = <String, dynamic>{};
List<dynamic> cookieListMap =
await channel?.invokeMethod<List>('getAllCookies', args) ?? [];
cookieListMap = cookieListMap.cast<Map<dynamic, dynamic>>();
cookieListMap.forEach((cookieMap) {
cookies.add(Cookie(
name: cookieMap["name"],
value: cookieMap["value"],
expiresDate: cookieMap["expiresDate"],
isSessionOnly: cookieMap["isSessionOnly"],
domain: cookieMap["domain"],
sameSite:
HTTPCookieSameSitePolicy.fromNativeValue(cookieMap["sameSite"]),
isSecure: cookieMap["isSecure"],
isHttpOnly: cookieMap["isHttpOnly"],
path: cookieMap["path"]));
});
return cookies;
}
Future<String> _getCookieExpirationDate(int expiresDate) async {
var platformUtil = PlatformUtil.instance();
var dateTime = DateTime.fromMillisecondsSinceEpoch(expiresDate).toUtc();
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);
}
Future<bool> _shouldUseJavascript() async {
final platformUtil = PlatformUtil.instance();
final systemVersion = await platformUtil.getSystemVersion();
return systemVersion.compareTo("11") == -1;
}
@override
void dispose() {
// empty
}
}
extension InternalCookieManager on MacOSCookieManager {
get handleMethod => _handleMethod;
}
@@ -0,0 +1,124 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
/// Object specifying creation parameters for creating a [MacOSFindInteractionController].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformFindInteractionControllerCreationParams] for
/// more information.
@immutable
class MacOSFindInteractionControllerCreationParams
extends PlatformFindInteractionControllerCreationParams {
/// Creates a new [MacOSFindInteractionControllerCreationParams] instance.
const MacOSFindInteractionControllerCreationParams(
{super.onFindResultReceived});
/// Creates a [MacOSFindInteractionControllerCreationParams] instance based on [PlatformFindInteractionControllerCreationParams].
factory MacOSFindInteractionControllerCreationParams.fromPlatformFindInteractionControllerCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformFindInteractionControllerCreationParams params) {
return MacOSFindInteractionControllerCreationParams(
onFindResultReceived: params.onFindResultReceived);
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController}
class MacOSFindInteractionController extends PlatformFindInteractionController
with ChannelController {
/// Constructs a [MacOSFindInteractionController].
MacOSFindInteractionController(
PlatformFindInteractionControllerCreationParams params)
: super.implementation(
params is MacOSFindInteractionControllerCreationParams
? params
: MacOSFindInteractionControllerCreationParams
.fromPlatformFindInteractionControllerCreationParams(params),
);
_debugLog(String method, dynamic args) {
debugLog(
className: this.runtimeType.toString(),
debugLoggingSettings:
PlatformFindInteractionController.debugLoggingSettings,
method: method,
args: args);
}
Future<dynamic> _handleMethod(MethodCall call) async {
_debugLog(call.method, call.arguments);
switch (call.method) {
case "onFindResultReceived":
if (onFindResultReceived != null) {
int activeMatchOrdinal = call.arguments["activeMatchOrdinal"];
int numberOfMatches = call.arguments["numberOfMatches"];
bool isDoneCounting = call.arguments["isDoneCounting"];
onFindResultReceived!(
this, activeMatchOrdinal, numberOfMatches, isDoneCounting);
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
return null;
}
///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.findAll}
Future<void> findAll({String? find}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('find', () => find);
await channel?.invokeMethod('findAll', args);
}
///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.findNext}
Future<void> findNext({bool forward = true}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('forward', () => forward);
await channel?.invokeMethod('findNext', args);
}
///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.clearMatches}
Future<void> clearMatches() async {
Map<String, dynamic> args = <String, dynamic>{};
await channel?.invokeMethod('clearMatches', args);
}
///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.setSearchText}
Future<void> setSearchText(String? searchText) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('searchText', () => searchText);
await channel?.invokeMethod('setSearchText', args);
}
///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.getSearchText}
Future<String?> getSearchText() async {
Map<String, dynamic> args = <String, dynamic>{};
return await channel?.invokeMethod<String?>('getSearchText', args);
}
///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.getActiveFindSession}
Future<FindSession?> getActiveFindSession() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? result =
(await channel?.invokeMethod('getActiveFindSession', args))
?.cast<String, dynamic>();
return FindSession.fromMap(result);
}
///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.dispose}
@override
void dispose({bool isKeepAlive = false}) {
disposeChannel(removeMethodCallHandler: !isKeepAlive);
}
}
extension InternalFindInteractionController on MacOSFindInteractionController {
void init(dynamic id) {
channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_find_interaction_$id');
handler = _handleMethod;
initMethodCallHandler();
}
}
@@ -0,0 +1,2 @@
export 'find_interaction_controller.dart'
hide InternalFindInteractionController;
@@ -0,0 +1,155 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
/// Object specifying creation parameters for creating a [MacOSHttpAuthCredentialDatabase].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformHttpAuthCredentialDatabaseCreationParams] for
/// more information.
@immutable
class MacOSHttpAuthCredentialDatabaseCreationParams
extends PlatformHttpAuthCredentialDatabaseCreationParams {
/// Creates a new [MacOSHttpAuthCredentialDatabaseCreationParams] instance.
const MacOSHttpAuthCredentialDatabaseCreationParams(
// This parameter prevents breaking changes later.
// ignore: avoid_unused_constructor_parameters
PlatformHttpAuthCredentialDatabaseCreationParams params,
) : super();
/// Creates a [MacOSHttpAuthCredentialDatabaseCreationParams] instance based on [PlatformHttpAuthCredentialDatabaseCreationParams].
factory MacOSHttpAuthCredentialDatabaseCreationParams.fromPlatformHttpAuthCredentialDatabaseCreationParams(
PlatformHttpAuthCredentialDatabaseCreationParams params) {
return MacOSHttpAuthCredentialDatabaseCreationParams(params);
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformHttpAuthCredentialDatabase}
class MacOSHttpAuthCredentialDatabase extends PlatformHttpAuthCredentialDatabase
with ChannelController {
/// Creates a new [MacOSHttpAuthCredentialDatabase].
MacOSHttpAuthCredentialDatabase(
PlatformHttpAuthCredentialDatabaseCreationParams params)
: super.implementation(
params is MacOSHttpAuthCredentialDatabaseCreationParams
? params
: MacOSHttpAuthCredentialDatabaseCreationParams
.fromPlatformHttpAuthCredentialDatabaseCreationParams(params),
) {
channel = const MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_credential_database');
handler = handleMethod;
initMethodCallHandler();
}
static MacOSHttpAuthCredentialDatabase? _instance;
///Gets the database shared instance.
static MacOSHttpAuthCredentialDatabase instance() {
return (_instance != null) ? _instance! : _init();
}
static MacOSHttpAuthCredentialDatabase _init() {
_instance = MacOSHttpAuthCredentialDatabase(
MacOSHttpAuthCredentialDatabaseCreationParams(
const PlatformHttpAuthCredentialDatabaseCreationParams()));
return _instance!;
}
Future<dynamic> _handleMethod(MethodCall call) async {}
@override
Future<List<URLProtectionSpaceHttpAuthCredentials>>
getAllAuthCredentials() async {
Map<String, dynamic> args = <String, dynamic>{};
List<dynamic> allCredentials =
await channel?.invokeMethod<List>('getAllAuthCredentials', args) ?? [];
List<URLProtectionSpaceHttpAuthCredentials> result = [];
for (Map<dynamic, dynamic> map in allCredentials) {
var element = URLProtectionSpaceHttpAuthCredentials.fromMap(
map.cast<String, dynamic>());
if (element != null) {
result.add(element);
}
}
return result;
}
@override
Future<List<URLCredential>> getHttpAuthCredentials(
{required URLProtectionSpace protectionSpace}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("host", () => protectionSpace.host);
args.putIfAbsent("protocol", () => protectionSpace.protocol);
args.putIfAbsent("realm", () => protectionSpace.realm);
args.putIfAbsent("port", () => protectionSpace.port);
List<dynamic> credentialList =
await channel?.invokeMethod<List>('getHttpAuthCredentials', args) ?? [];
List<URLCredential> credentials = [];
for (Map<dynamic, dynamic> map in credentialList) {
var credential = URLCredential.fromMap(map.cast<String, dynamic>());
if (credential != null) {
credentials.add(credential);
}
}
return credentials;
}
@override
Future<void> setHttpAuthCredential(
{required URLProtectionSpace protectionSpace,
required URLCredential credential}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("host", () => protectionSpace.host);
args.putIfAbsent("protocol", () => protectionSpace.protocol);
args.putIfAbsent("realm", () => protectionSpace.realm);
args.putIfAbsent("port", () => protectionSpace.port);
args.putIfAbsent("username", () => credential.username);
args.putIfAbsent("password", () => credential.password);
await channel?.invokeMethod('setHttpAuthCredential', args);
}
@override
Future<void> removeHttpAuthCredential(
{required URLProtectionSpace protectionSpace,
required URLCredential credential}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("host", () => protectionSpace.host);
args.putIfAbsent("protocol", () => protectionSpace.protocol);
args.putIfAbsent("realm", () => protectionSpace.realm);
args.putIfAbsent("port", () => protectionSpace.port);
args.putIfAbsent("username", () => credential.username);
args.putIfAbsent("password", () => credential.password);
await channel?.invokeMethod('removeHttpAuthCredential', args);
}
@override
Future<void> removeHttpAuthCredentials(
{required URLProtectionSpace protectionSpace}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("host", () => protectionSpace.host);
args.putIfAbsent("protocol", () => protectionSpace.protocol);
args.putIfAbsent("realm", () => protectionSpace.realm);
args.putIfAbsent("port", () => protectionSpace.port);
await channel?.invokeMethod('removeHttpAuthCredentials', args);
}
@override
Future<void> clearAllAuthCredentials() async {
Map<String, dynamic> args = <String, dynamic>{};
await channel?.invokeMethod('clearAllAuthCredentials', args);
}
@override
void dispose() {
// empty
}
}
extension InternalHttpAuthCredentialDatabase
on MacOSHttpAuthCredentialDatabase {
get handleMethod => _handleMethod;
}
@@ -0,0 +1,373 @@
import 'dart:async';
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
import '../find_interaction/find_interaction_controller.dart';
import '../in_app_webview/in_app_webview_controller.dart';
/// Object specifying creation parameters for creating a [MacOSInAppBrowser].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformInAppBrowserCreationParams] for
/// more information.
class MacOSInAppBrowserCreationParams
extends PlatformInAppBrowserCreationParams {
/// Creates a new [MacOSInAppBrowserCreationParams] instance.
MacOSInAppBrowserCreationParams(
{super.contextMenu,
super.pullToRefreshController,
this.findInteractionController,
super.initialUserScripts,
super.windowId});
/// Creates a [MacOSInAppBrowserCreationParams] instance based on [PlatformInAppBrowserCreationParams].
factory MacOSInAppBrowserCreationParams.fromPlatformInAppBrowserCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformInAppBrowserCreationParams params) {
return MacOSInAppBrowserCreationParams(
contextMenu: params.contextMenu,
pullToRefreshController: params.pullToRefreshController,
findInteractionController:
params.findInteractionController as MacOSFindInteractionController?,
initialUserScripts: params.initialUserScripts,
windowId: params.windowId);
}
@override
final MacOSFindInteractionController? findInteractionController;
}
///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser}
class MacOSInAppBrowser extends PlatformInAppBrowser with ChannelController {
@override
final String id = IdGenerator.generate();
/// Constructs a [MacOSInAppBrowser].
MacOSInAppBrowser(PlatformInAppBrowserCreationParams params)
: super.implementation(
params is MacOSInAppBrowserCreationParams
? params
: MacOSInAppBrowserCreationParams
.fromPlatformInAppBrowserCreationParams(params),
) {
_contextMenu = params.contextMenu;
}
static final MacOSInAppBrowser _staticValue =
MacOSInAppBrowser(MacOSInAppBrowserCreationParams());
/// Provide static access.
factory MacOSInAppBrowser.static() {
return _staticValue;
}
MacOSInAppBrowserCreationParams get _macosParams =>
params as MacOSInAppBrowserCreationParams;
static const MethodChannel _staticChannel =
const MethodChannel('com.pichillilorenzo/flutter_inappbrowser');
ContextMenu? _contextMenu;
@override
ContextMenu? get contextMenu => _contextMenu;
Map<int, InAppBrowserMenuItem> _menuItems = HashMap();
bool _isOpened = false;
MacOSInAppWebViewController? _webViewController;
@override
MacOSInAppWebViewController? get webViewController {
return _isOpened ? _webViewController : null;
}
_init() {
channel = MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id');
handler = _handleMethod;
initMethodCallHandler();
_webViewController = MacOSInAppWebViewController.fromInAppBrowser(
MacOSInAppWebViewControllerCreationParams(id: id),
channel!,
this,
this.initialUserScripts);
_macosParams.findInteractionController?.init(id);
}
_debugLog(String method, dynamic args) {
debugLog(
className: this.runtimeType.toString(),
id: id,
debugLoggingSettings: PlatformInAppBrowser.debugLoggingSettings,
method: method,
args: args);
}
Future<dynamic> _handleMethod(MethodCall call) async {
switch (call.method) {
case "onBrowserCreated":
_debugLog(call.method, call.arguments);
eventHandler?.onBrowserCreated();
break;
case "onMenuItemClicked":
_debugLog(call.method, call.arguments);
int id = call.arguments["id"].toInt();
if (this._menuItems[id] != null) {
if (this._menuItems[id]?.onClick != null) {
this._menuItems[id]?.onClick!();
}
}
break;
case "onMainWindowWillClose":
_debugLog(call.method, call.arguments);
eventHandler?.onMainWindowWillClose();
break;
case "onExit":
_debugLog(call.method, call.arguments);
_isOpened = false;
final onExit = eventHandler?.onExit;
dispose();
onExit?.call();
break;
default:
return _webViewController?.handleMethod(call);
}
}
Map<String, dynamic> _prepareOpenRequest(
{@Deprecated('Use settings instead') InAppBrowserClassOptions? options,
InAppBrowserClassSettings? settings}) {
assert(!_isOpened, 'The browser is already opened.');
_isOpened = true;
_init();
var initialSettings = settings?.toMap() ??
options?.toMap() ??
InAppBrowserClassSettings().toMap();
Map<String, dynamic> pullToRefreshSettings =
pullToRefreshController?.settings.toMap() ??
pullToRefreshController?.options.toMap() ??
PullToRefreshSettings(enabled: false).toMap();
List<Map<String, dynamic>> menuItemList = [];
_menuItems.forEach((key, value) {
menuItemList.add(value.toMap());
});
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('id', () => id);
args.putIfAbsent('settings', () => initialSettings);
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
args.putIfAbsent('windowId', () => windowId);
args.putIfAbsent('initialUserScripts',
() => initialUserScripts?.map((e) => e.toMap()).toList() ?? []);
args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings);
args.putIfAbsent('menuItems', () => menuItemList);
return args;
}
@override
Future<void> openUrlRequest(
{required URLRequest urlRequest,
// ignore: deprecated_member_use_from_same_package
@Deprecated('Use settings instead') InAppBrowserClassOptions? options,
InAppBrowserClassSettings? settings}) async {
assert(urlRequest.url != null && urlRequest.url.toString().isNotEmpty);
Map<String, dynamic> args =
_prepareOpenRequest(options: options, settings: settings);
args.putIfAbsent('urlRequest', () => urlRequest.toMap());
await _staticChannel.invokeMethod('open', args);
}
@override
Future<void> openFile(
{required String assetFilePath,
// ignore: deprecated_member_use_from_same_package
@Deprecated('Use settings instead') InAppBrowserClassOptions? options,
InAppBrowserClassSettings? settings}) async {
assert(assetFilePath.isNotEmpty);
Map<String, dynamic> args =
_prepareOpenRequest(options: options, settings: settings);
args.putIfAbsent('assetFilePath', () => assetFilePath);
await _staticChannel.invokeMethod('open', args);
}
@override
Future<void> openData(
{required String data,
String mimeType = "text/html",
String encoding = "utf8",
WebUri? baseUrl,
@Deprecated("Use historyUrl instead") Uri? androidHistoryUrl,
WebUri? historyUrl,
// ignore: deprecated_member_use_from_same_package
@Deprecated('Use settings instead') InAppBrowserClassOptions? options,
InAppBrowserClassSettings? settings}) async {
Map<String, dynamic> args =
_prepareOpenRequest(options: options, settings: settings);
args.putIfAbsent('data', () => data);
args.putIfAbsent('mimeType', () => mimeType);
args.putIfAbsent('encoding', () => encoding);
args.putIfAbsent('baseUrl', () => baseUrl?.toString() ?? "about:blank");
args.putIfAbsent('historyUrl',
() => (historyUrl ?? androidHistoryUrl)?.toString() ?? "about:blank");
await _staticChannel.invokeMethod('open', args);
}
@override
Future<void> openWithSystemBrowser({required WebUri url}) async {
assert(url.toString().isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url.toString());
return await _staticChannel.invokeMethod('openWithSystemBrowser', args);
}
@override
void addMenuItem(InAppBrowserMenuItem menuItem) {
_menuItems[menuItem.id] = menuItem;
}
@override
void addMenuItems(List<InAppBrowserMenuItem> menuItems) {
menuItems.forEach((menuItem) {
_menuItems[menuItem.id] = menuItem;
});
}
@override
bool removeMenuItem(InAppBrowserMenuItem menuItem) {
return _menuItems.remove(menuItem.id) != null;
}
@override
void removeMenuItems(List<InAppBrowserMenuItem> menuItems) {
for (final menuItem in menuItems) {
removeMenuItem(menuItem);
}
}
@override
void removeAllMenuItem() {
_menuItems.clear();
}
@override
bool hasMenuItem(InAppBrowserMenuItem menuItem) {
return _menuItems.containsKey(menuItem.id);
}
@override
Future<void> show() async {
assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{};
await channel?.invokeMethod('show', args);
}
@override
Future<void> hide() async {
assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{};
await channel?.invokeMethod('hide', args);
}
@override
Future<void> close() async {
assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{};
await channel?.invokeMethod('close', args);
}
@override
Future<bool> isHidden() async {
assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{};
return await channel?.invokeMethod<bool>('isHidden', args) ?? false;
}
@override
@Deprecated('Use setSettings instead')
Future<void> setOptions({required InAppBrowserClassOptions options}) async {
assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('settings', () => options.toMap());
await channel?.invokeMethod('setSettings', args);
}
@override
@Deprecated('Use getSettings instead')
Future<InAppBrowserClassOptions?> getOptions() async {
assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? options =
await channel?.invokeMethod('getSettings', args);
if (options != null) {
options = options.cast<String, dynamic>();
return InAppBrowserClassOptions.fromMap(options as Map<String, dynamic>);
}
return null;
}
@override
Future<void> setSettings(
{required InAppBrowserClassSettings settings}) async {
assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('settings', () => settings.toMap());
await channel?.invokeMethod('setSettings', args);
}
@override
Future<InAppBrowserClassSettings?> getSettings() async {
assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? settings =
await channel?.invokeMethod('getSettings', args);
if (settings != null) {
settings = settings.cast<String, dynamic>();
return InAppBrowserClassSettings.fromMap(
settings as Map<String, dynamic>);
}
return null;
}
@override
bool isOpened() {
return this._isOpened;
}
@override
@mustCallSuper
void dispose() {
super.dispose();
disposeChannel();
_webViewController?.dispose();
_webViewController = null;
pullToRefreshController?.dispose();
findInteractionController?.dispose();
}
}
extension InternalInAppBrowser on MacOSInAppBrowser {
void setContextMenu(ContextMenu? contextMenu) {
_contextMenu = contextMenu;
}
}
@@ -0,0 +1 @@
export 'in_app_browser.dart' hide InternalInAppBrowser;
@@ -0,0 +1,4 @@
import 'package:flutter/services.dart';
const IN_APP_WEBVIEW_STATIC_CHANNEL =
MethodChannel('com.pichillilorenzo/flutter_inappwebview_manager');
@@ -0,0 +1,433 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
import '../find_interaction/find_interaction_controller.dart';
import 'in_app_webview_controller.dart';
/// Object specifying creation parameters for creating a [MacOSHeadlessInAppWebView].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformHeadlessInAppWebViewCreationParams] for
/// more information.
@immutable
class MacOSHeadlessInAppWebViewCreationParams
extends PlatformHeadlessInAppWebViewCreationParams {
/// Creates a new [MacOSHeadlessInAppWebViewCreationParams] instance.
MacOSHeadlessInAppWebViewCreationParams(
{super.controllerFromPlatform,
super.initialSize,
super.windowId,
super.onWebViewCreated,
super.onLoadStart,
super.onLoadStop,
@Deprecated('Use onReceivedError instead') super.onLoadError,
super.onReceivedError,
@Deprecated("Use onReceivedHttpError instead") super.onLoadHttpError,
super.onReceivedHttpError,
super.onProgressChanged,
super.onConsoleMessage,
super.shouldOverrideUrlLoading,
super.onLoadResource,
super.onScrollChanged,
@Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart,
super.onDownloadStartRequest,
@Deprecated('Use onLoadResourceWithCustomScheme instead')
super.onLoadResourceCustomScheme,
super.onLoadResourceWithCustomScheme,
super.onCreateWindow,
super.onCloseWindow,
super.onJsAlert,
super.onJsConfirm,
super.onJsPrompt,
super.onReceivedHttpAuthRequest,
super.onReceivedServerTrustAuthRequest,
super.onReceivedClientCertRequest,
@Deprecated('Use FindInteractionController.onFindResultReceived instead')
super.onFindResultReceived,
super.shouldInterceptAjaxRequest,
super.onAjaxReadyStateChange,
super.onAjaxProgress,
super.shouldInterceptFetchRequest,
super.onUpdateVisitedHistory,
@Deprecated("Use onPrintRequest instead") super.onPrint,
super.onPrintRequest,
super.onLongPressHitTestResult,
super.onEnterFullscreen,
super.onExitFullscreen,
super.onPageCommitVisible,
super.onTitleChanged,
super.onWindowFocus,
super.onWindowBlur,
super.onOverScrolled,
super.onZoomScaleChanged,
@Deprecated('Use onSafeBrowsingHit instead')
super.androidOnSafeBrowsingHit,
super.onSafeBrowsingHit,
@Deprecated('Use onPermissionRequest instead')
super.androidOnPermissionRequest,
super.onPermissionRequest,
@Deprecated('Use onGeolocationPermissionsShowPrompt instead')
super.androidOnGeolocationPermissionsShowPrompt,
super.onGeolocationPermissionsShowPrompt,
@Deprecated('Use onGeolocationPermissionsHidePrompt instead')
super.androidOnGeolocationPermissionsHidePrompt,
super.onGeolocationPermissionsHidePrompt,
@Deprecated('Use shouldInterceptRequest instead')
super.androidShouldInterceptRequest,
super.shouldInterceptRequest,
@Deprecated('Use onRenderProcessGone instead')
super.androidOnRenderProcessGone,
super.onRenderProcessGone,
@Deprecated('Use onRenderProcessResponsive instead')
super.androidOnRenderProcessResponsive,
super.onRenderProcessResponsive,
@Deprecated('Use onRenderProcessUnresponsive instead')
super.androidOnRenderProcessUnresponsive,
super.onRenderProcessUnresponsive,
@Deprecated('Use onFormResubmission instead')
super.androidOnFormResubmission,
super.onFormResubmission,
@Deprecated('Use onZoomScaleChanged instead') super.androidOnScaleChanged,
@Deprecated('Use onReceivedIcon instead') super.androidOnReceivedIcon,
super.onReceivedIcon,
@Deprecated('Use onReceivedTouchIconUrl instead')
super.androidOnReceivedTouchIconUrl,
super.onReceivedTouchIconUrl,
@Deprecated('Use onJsBeforeUnload instead') super.androidOnJsBeforeUnload,
super.onJsBeforeUnload,
@Deprecated('Use onReceivedLoginRequest instead')
super.androidOnReceivedLoginRequest,
super.onReceivedLoginRequest,
super.onPermissionRequestCanceled,
super.onRequestFocus,
@Deprecated('Use onWebContentProcessDidTerminate instead')
super.iosOnWebContentProcessDidTerminate,
super.onWebContentProcessDidTerminate,
@Deprecated(
'Use onDidReceiveServerRedirectForProvisionalNavigation instead')
super.iosOnDidReceiveServerRedirectForProvisionalNavigation,
super.onDidReceiveServerRedirectForProvisionalNavigation,
@Deprecated('Use onNavigationResponse instead')
super.iosOnNavigationResponse,
super.onNavigationResponse,
@Deprecated('Use shouldAllowDeprecatedTLS instead')
super.iosShouldAllowDeprecatedTLS,
super.shouldAllowDeprecatedTLS,
super.onCameraCaptureStateChanged,
super.onMicrophoneCaptureStateChanged,
super.onContentSizeChanged,
super.initialUrlRequest,
super.initialFile,
super.initialData,
@Deprecated('Use initialSettings instead') super.initialOptions,
super.initialSettings,
super.contextMenu,
super.initialUserScripts,
super.pullToRefreshController,
this.findInteractionController});
/// Creates a [MacOSHeadlessInAppWebViewCreationParams] instance based on [PlatformHeadlessInAppWebViewCreationParams].
MacOSHeadlessInAppWebViewCreationParams.fromPlatformHeadlessInAppWebViewCreationParams(
PlatformHeadlessInAppWebViewCreationParams params)
: this(
controllerFromPlatform: params.controllerFromPlatform,
initialSize: params.initialSize,
windowId: params.windowId,
onWebViewCreated: params.onWebViewCreated,
onLoadStart: params.onLoadStart,
onLoadStop: params.onLoadStop,
onLoadError: params.onLoadError,
onReceivedError: params.onReceivedError,
onLoadHttpError: params.onLoadHttpError,
onReceivedHttpError: params.onReceivedHttpError,
onProgressChanged: params.onProgressChanged,
onConsoleMessage: params.onConsoleMessage,
shouldOverrideUrlLoading: params.shouldOverrideUrlLoading,
onLoadResource: params.onLoadResource,
onScrollChanged: params.onScrollChanged,
onDownloadStart: params.onDownloadStart,
onDownloadStartRequest: params.onDownloadStartRequest,
onLoadResourceCustomScheme: params.onLoadResourceCustomScheme,
onLoadResourceWithCustomScheme:
params.onLoadResourceWithCustomScheme,
onCreateWindow: params.onCreateWindow,
onCloseWindow: params.onCloseWindow,
onJsAlert: params.onJsAlert,
onJsConfirm: params.onJsConfirm,
onJsPrompt: params.onJsPrompt,
onReceivedHttpAuthRequest: params.onReceivedHttpAuthRequest,
onReceivedServerTrustAuthRequest:
params.onReceivedServerTrustAuthRequest,
onReceivedClientCertRequest: params.onReceivedClientCertRequest,
onFindResultReceived: params.onFindResultReceived,
shouldInterceptAjaxRequest: params.shouldInterceptAjaxRequest,
onAjaxReadyStateChange: params.onAjaxReadyStateChange,
onAjaxProgress: params.onAjaxProgress,
shouldInterceptFetchRequest: params.shouldInterceptFetchRequest,
onUpdateVisitedHistory: params.onUpdateVisitedHistory,
onPrint: params.onPrint,
onPrintRequest: params.onPrintRequest,
onLongPressHitTestResult: params.onLongPressHitTestResult,
onEnterFullscreen: params.onEnterFullscreen,
onExitFullscreen: params.onExitFullscreen,
onPageCommitVisible: params.onPageCommitVisible,
onTitleChanged: params.onTitleChanged,
onWindowFocus: params.onWindowFocus,
onWindowBlur: params.onWindowBlur,
onOverScrolled: params.onOverScrolled,
onZoomScaleChanged: params.onZoomScaleChanged,
androidOnSafeBrowsingHit: params.androidOnSafeBrowsingHit,
onSafeBrowsingHit: params.onSafeBrowsingHit,
androidOnPermissionRequest: params.androidOnPermissionRequest,
onPermissionRequest: params.onPermissionRequest,
androidOnGeolocationPermissionsShowPrompt:
params.androidOnGeolocationPermissionsShowPrompt,
onGeolocationPermissionsShowPrompt:
params.onGeolocationPermissionsShowPrompt,
androidOnGeolocationPermissionsHidePrompt:
params.androidOnGeolocationPermissionsHidePrompt,
onGeolocationPermissionsHidePrompt:
params.onGeolocationPermissionsHidePrompt,
androidShouldInterceptRequest: params.androidShouldInterceptRequest,
shouldInterceptRequest: params.shouldInterceptRequest,
androidOnRenderProcessGone: params.androidOnRenderProcessGone,
onRenderProcessGone: params.onRenderProcessGone,
androidOnRenderProcessResponsive:
params.androidOnRenderProcessResponsive,
onRenderProcessResponsive: params.onRenderProcessResponsive,
androidOnRenderProcessUnresponsive:
params.androidOnRenderProcessUnresponsive,
onRenderProcessUnresponsive: params.onRenderProcessUnresponsive,
androidOnFormResubmission: params.androidOnFormResubmission,
onFormResubmission: params.onFormResubmission,
androidOnScaleChanged: params.androidOnScaleChanged,
androidOnReceivedIcon: params.androidOnReceivedIcon,
onReceivedIcon: params.onReceivedIcon,
androidOnReceivedTouchIconUrl: params.androidOnReceivedTouchIconUrl,
onReceivedTouchIconUrl: params.onReceivedTouchIconUrl,
androidOnJsBeforeUnload: params.androidOnJsBeforeUnload,
onJsBeforeUnload: params.onJsBeforeUnload,
androidOnReceivedLoginRequest: params.androidOnReceivedLoginRequest,
onReceivedLoginRequest: params.onReceivedLoginRequest,
onPermissionRequestCanceled: params.onPermissionRequestCanceled,
onRequestFocus: params.onRequestFocus,
iosOnWebContentProcessDidTerminate:
params.iosOnWebContentProcessDidTerminate,
onWebContentProcessDidTerminate:
params.onWebContentProcessDidTerminate,
iosOnDidReceiveServerRedirectForProvisionalNavigation:
params.iosOnDidReceiveServerRedirectForProvisionalNavigation,
onDidReceiveServerRedirectForProvisionalNavigation:
params.onDidReceiveServerRedirectForProvisionalNavigation,
iosOnNavigationResponse: params.iosOnNavigationResponse,
onNavigationResponse: params.onNavigationResponse,
iosShouldAllowDeprecatedTLS: params.iosShouldAllowDeprecatedTLS,
shouldAllowDeprecatedTLS: params.shouldAllowDeprecatedTLS,
onCameraCaptureStateChanged: params.onCameraCaptureStateChanged,
onMicrophoneCaptureStateChanged:
params.onMicrophoneCaptureStateChanged,
onContentSizeChanged: params.onContentSizeChanged,
initialUrlRequest: params.initialUrlRequest,
initialFile: params.initialFile,
initialData: params.initialData,
initialOptions: params.initialOptions,
initialSettings: params.initialSettings,
contextMenu: params.contextMenu,
initialUserScripts: params.initialUserScripts,
pullToRefreshController: params.pullToRefreshController,
findInteractionController: params.findInteractionController
as MacOSFindInteractionController?);
@override
final MacOSFindInteractionController? findInteractionController;
}
///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView}
class MacOSHeadlessInAppWebView extends PlatformHeadlessInAppWebView
with ChannelController {
@override
late final String id;
bool _started = false;
bool _running = false;
static const MethodChannel _sharedChannel =
const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview');
MacOSInAppWebViewController? _webViewController;
/// Constructs a [MacOSHeadlessInAppWebView].
MacOSHeadlessInAppWebView(PlatformHeadlessInAppWebViewCreationParams params)
: super.implementation(
params is MacOSHeadlessInAppWebViewCreationParams
? params
: MacOSHeadlessInAppWebViewCreationParams
.fromPlatformHeadlessInAppWebViewCreationParams(params),
) {
id = IdGenerator.generate();
}
@override
MacOSInAppWebViewController? get webViewController => _webViewController;
dynamic _controllerFromPlatform;
MacOSHeadlessInAppWebViewCreationParams get _macosParams =>
params as MacOSHeadlessInAppWebViewCreationParams;
_init() {
_webViewController = MacOSInAppWebViewController(
MacOSInAppWebViewControllerCreationParams(id: id, webviewParams: params),
);
_controllerFromPlatform =
params.controllerFromPlatform?.call(_webViewController!) ??
_webViewController!;
_macosParams.findInteractionController?.init(id);
channel =
MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview_$id');
handler = _handleMethod;
initMethodCallHandler();
}
Future<dynamic> _handleMethod(MethodCall call) async {
switch (call.method) {
case "onWebViewCreated":
if (params.onWebViewCreated != null && _webViewController != null) {
params.onWebViewCreated!(_controllerFromPlatform);
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
return null;
}
Future<void> run() async {
if (_started) {
return;
}
_started = true;
_init();
final initialSettings = params.initialSettings ?? InAppWebViewSettings();
_inferInitialSettings(initialSettings);
Map<String, dynamic> settingsMap =
(params.initialSettings != null ? initialSettings.toMap() : null) ??
params.initialOptions?.toMap() ??
initialSettings.toMap();
Map<String, dynamic> pullToRefreshSettings =
_macosParams.pullToRefreshController?.params.settings.toMap() ??
_macosParams.pullToRefreshController?.params.options.toMap() ??
PullToRefreshSettings(enabled: false).toMap();
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('id', () => id);
args.putIfAbsent(
'params',
() => <String, dynamic>{
'initialUrlRequest': params.initialUrlRequest?.toMap(),
'initialFile': params.initialFile,
'initialData': params.initialData?.toMap(),
'initialSettings': settingsMap,
'contextMenu': params.contextMenu?.toMap() ?? {},
'windowId': params.windowId,
'initialUserScripts':
params.initialUserScripts?.map((e) => e.toMap()).toList() ??
[],
'pullToRefreshSettings': pullToRefreshSettings,
'initialSize': params.initialSize.toMap()
});
await _sharedChannel.invokeMethod('run', args);
_running = true;
}
void _inferInitialSettings(InAppWebViewSettings settings) {
if (params.shouldOverrideUrlLoading != null &&
settings.useShouldOverrideUrlLoading == null) {
settings.useShouldOverrideUrlLoading = true;
}
if (params.onLoadResource != null && settings.useOnLoadResource == null) {
settings.useOnLoadResource = true;
}
if (params.onDownloadStartRequest != null &&
settings.useOnDownloadStart == null) {
settings.useOnDownloadStart = true;
}
if (params.shouldInterceptAjaxRequest != null &&
settings.useShouldInterceptAjaxRequest == null) {
settings.useShouldInterceptAjaxRequest = true;
}
if (params.shouldInterceptFetchRequest != null &&
settings.useShouldInterceptFetchRequest == null) {
settings.useShouldInterceptFetchRequest = true;
}
if (params.shouldInterceptRequest != null &&
settings.useShouldInterceptRequest == null) {
settings.useShouldInterceptRequest = true;
}
if (params.onRenderProcessGone != null &&
settings.useOnRenderProcessGone == null) {
settings.useOnRenderProcessGone = true;
}
if (params.onNavigationResponse != null &&
settings.useOnNavigationResponse == null) {
settings.useOnNavigationResponse = true;
}
}
@override
bool isRunning() {
return _running;
}
@override
Future<void> setSize(Size size) async {
if (!_running) {
return;
}
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('size', () => size.toMap());
await channel?.invokeMethod('setSize', args);
}
@override
Future<Size?> getSize() async {
if (!_running) {
return null;
}
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic> sizeMap =
(await channel?.invokeMethod('getSize', args))?.cast<String, dynamic>();
return MapSize.fromMap(sizeMap);
}
@override
Future<void> dispose() async {
if (!_running) {
return;
}
Map<String, dynamic> args = <String, dynamic>{};
await channel?.invokeMethod('dispose', args);
disposeChannel();
_started = false;
_running = false;
_webViewController?.dispose();
_webViewController = null;
_controllerFromPlatform = null;
_macosParams.pullToRefreshController?.dispose();
_macosParams.findInteractionController?.dispose();
}
}
extension InternalHeadlessInAppWebView on MacOSHeadlessInAppWebView {
Future<void> internalDispose() async {
_started = false;
_running = false;
}
}
@@ -0,0 +1,408 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
import 'headless_in_app_webview.dart';
import '../find_interaction/find_interaction_controller.dart';
import 'in_app_webview_controller.dart';
/// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget].
///
/// Platform specific implementations can add additional fields by extending
/// this class.
class MacOSInAppWebViewWidgetCreationParams
extends PlatformInAppWebViewWidgetCreationParams {
MacOSInAppWebViewWidgetCreationParams(
{super.controllerFromPlatform,
super.key,
super.layoutDirection,
super.gestureRecognizers,
super.headlessWebView,
super.keepAlive,
super.preventGestureDelay,
super.windowId,
super.onWebViewCreated,
super.onLoadStart,
super.onLoadStop,
@Deprecated('Use onReceivedError instead') super.onLoadError,
super.onReceivedError,
@Deprecated("Use onReceivedHttpError instead") super.onLoadHttpError,
super.onReceivedHttpError,
super.onProgressChanged,
super.onConsoleMessage,
super.shouldOverrideUrlLoading,
super.onLoadResource,
super.onScrollChanged,
@Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart,
super.onDownloadStartRequest,
@Deprecated('Use onLoadResourceWithCustomScheme instead')
super.onLoadResourceCustomScheme,
super.onLoadResourceWithCustomScheme,
super.onCreateWindow,
super.onCloseWindow,
super.onJsAlert,
super.onJsConfirm,
super.onJsPrompt,
super.onReceivedHttpAuthRequest,
super.onReceivedServerTrustAuthRequest,
super.onReceivedClientCertRequest,
@Deprecated('Use FindInteractionController.onFindResultReceived instead')
super.onFindResultReceived,
super.shouldInterceptAjaxRequest,
super.onAjaxReadyStateChange,
super.onAjaxProgress,
super.shouldInterceptFetchRequest,
super.onUpdateVisitedHistory,
@Deprecated("Use onPrintRequest instead") super.onPrint,
super.onPrintRequest,
super.onLongPressHitTestResult,
super.onEnterFullscreen,
super.onExitFullscreen,
super.onPageCommitVisible,
super.onTitleChanged,
super.onWindowFocus,
super.onWindowBlur,
super.onOverScrolled,
super.onZoomScaleChanged,
@Deprecated('Use onSafeBrowsingHit instead')
super.androidOnSafeBrowsingHit,
super.onSafeBrowsingHit,
@Deprecated('Use onPermissionRequest instead')
super.androidOnPermissionRequest,
super.onPermissionRequest,
@Deprecated('Use onGeolocationPermissionsShowPrompt instead')
super.androidOnGeolocationPermissionsShowPrompt,
super.onGeolocationPermissionsShowPrompt,
@Deprecated('Use onGeolocationPermissionsHidePrompt instead')
super.androidOnGeolocationPermissionsHidePrompt,
super.onGeolocationPermissionsHidePrompt,
@Deprecated('Use shouldInterceptRequest instead')
super.androidShouldInterceptRequest,
super.shouldInterceptRequest,
@Deprecated('Use onRenderProcessGone instead')
super.androidOnRenderProcessGone,
super.onRenderProcessGone,
@Deprecated('Use onRenderProcessResponsive instead')
super.androidOnRenderProcessResponsive,
super.onRenderProcessResponsive,
@Deprecated('Use onRenderProcessUnresponsive instead')
super.androidOnRenderProcessUnresponsive,
super.onRenderProcessUnresponsive,
@Deprecated('Use onFormResubmission instead')
super.androidOnFormResubmission,
super.onFormResubmission,
@Deprecated('Use onZoomScaleChanged instead') super.androidOnScaleChanged,
@Deprecated('Use onReceivedIcon instead') super.androidOnReceivedIcon,
super.onReceivedIcon,
@Deprecated('Use onReceivedTouchIconUrl instead')
super.androidOnReceivedTouchIconUrl,
super.onReceivedTouchIconUrl,
@Deprecated('Use onJsBeforeUnload instead') super.androidOnJsBeforeUnload,
super.onJsBeforeUnload,
@Deprecated('Use onReceivedLoginRequest instead')
super.androidOnReceivedLoginRequest,
super.onReceivedLoginRequest,
super.onPermissionRequestCanceled,
super.onRequestFocus,
@Deprecated('Use onWebContentProcessDidTerminate instead')
super.iosOnWebContentProcessDidTerminate,
super.onWebContentProcessDidTerminate,
@Deprecated(
'Use onDidReceiveServerRedirectForProvisionalNavigation instead')
super.iosOnDidReceiveServerRedirectForProvisionalNavigation,
super.onDidReceiveServerRedirectForProvisionalNavigation,
@Deprecated('Use onNavigationResponse instead')
super.iosOnNavigationResponse,
super.onNavigationResponse,
@Deprecated('Use shouldAllowDeprecatedTLS instead')
super.iosShouldAllowDeprecatedTLS,
super.shouldAllowDeprecatedTLS,
super.onCameraCaptureStateChanged,
super.onMicrophoneCaptureStateChanged,
super.onContentSizeChanged,
super.initialUrlRequest,
super.initialFile,
super.initialData,
@Deprecated('Use initialSettings instead') super.initialOptions,
super.initialSettings,
super.contextMenu,
super.initialUserScripts,
super.pullToRefreshController,
this.findInteractionController});
/// Constructs a [MacOSInAppWebViewWidgetCreationParams] using a
/// [PlatformInAppWebViewWidgetCreationParams].
MacOSInAppWebViewWidgetCreationParams.fromPlatformInAppWebViewWidgetCreationParams(
PlatformInAppWebViewWidgetCreationParams params)
: this(
controllerFromPlatform: params.controllerFromPlatform,
key: params.key,
layoutDirection: params.layoutDirection,
gestureRecognizers: params.gestureRecognizers,
headlessWebView: params.headlessWebView,
keepAlive: params.keepAlive,
preventGestureDelay: params.preventGestureDelay,
windowId: params.windowId,
onWebViewCreated: params.onWebViewCreated,
onLoadStart: params.onLoadStart,
onLoadStop: params.onLoadStop,
onLoadError: params.onLoadError,
onReceivedError: params.onReceivedError,
onLoadHttpError: params.onLoadHttpError,
onReceivedHttpError: params.onReceivedHttpError,
onProgressChanged: params.onProgressChanged,
onConsoleMessage: params.onConsoleMessage,
shouldOverrideUrlLoading: params.shouldOverrideUrlLoading,
onLoadResource: params.onLoadResource,
onScrollChanged: params.onScrollChanged,
onDownloadStart: params.onDownloadStart,
onDownloadStartRequest: params.onDownloadStartRequest,
onLoadResourceCustomScheme: params.onLoadResourceCustomScheme,
onLoadResourceWithCustomScheme:
params.onLoadResourceWithCustomScheme,
onCreateWindow: params.onCreateWindow,
onCloseWindow: params.onCloseWindow,
onJsAlert: params.onJsAlert,
onJsConfirm: params.onJsConfirm,
onJsPrompt: params.onJsPrompt,
onReceivedHttpAuthRequest: params.onReceivedHttpAuthRequest,
onReceivedServerTrustAuthRequest:
params.onReceivedServerTrustAuthRequest,
onReceivedClientCertRequest: params.onReceivedClientCertRequest,
onFindResultReceived: params.onFindResultReceived,
shouldInterceptAjaxRequest: params.shouldInterceptAjaxRequest,
onAjaxReadyStateChange: params.onAjaxReadyStateChange,
onAjaxProgress: params.onAjaxProgress,
shouldInterceptFetchRequest: params.shouldInterceptFetchRequest,
onUpdateVisitedHistory: params.onUpdateVisitedHistory,
onPrint: params.onPrint,
onPrintRequest: params.onPrintRequest,
onLongPressHitTestResult: params.onLongPressHitTestResult,
onEnterFullscreen: params.onEnterFullscreen,
onExitFullscreen: params.onExitFullscreen,
onPageCommitVisible: params.onPageCommitVisible,
onTitleChanged: params.onTitleChanged,
onWindowFocus: params.onWindowFocus,
onWindowBlur: params.onWindowBlur,
onOverScrolled: params.onOverScrolled,
onZoomScaleChanged: params.onZoomScaleChanged,
androidOnSafeBrowsingHit: params.androidOnSafeBrowsingHit,
onSafeBrowsingHit: params.onSafeBrowsingHit,
androidOnPermissionRequest: params.androidOnPermissionRequest,
onPermissionRequest: params.onPermissionRequest,
androidOnGeolocationPermissionsShowPrompt:
params.androidOnGeolocationPermissionsShowPrompt,
onGeolocationPermissionsShowPrompt:
params.onGeolocationPermissionsShowPrompt,
androidOnGeolocationPermissionsHidePrompt:
params.androidOnGeolocationPermissionsHidePrompt,
onGeolocationPermissionsHidePrompt:
params.onGeolocationPermissionsHidePrompt,
androidShouldInterceptRequest: params.androidShouldInterceptRequest,
shouldInterceptRequest: params.shouldInterceptRequest,
androidOnRenderProcessGone: params.androidOnRenderProcessGone,
onRenderProcessGone: params.onRenderProcessGone,
androidOnRenderProcessResponsive:
params.androidOnRenderProcessResponsive,
onRenderProcessResponsive: params.onRenderProcessResponsive,
androidOnRenderProcessUnresponsive:
params.androidOnRenderProcessUnresponsive,
onRenderProcessUnresponsive: params.onRenderProcessUnresponsive,
androidOnFormResubmission: params.androidOnFormResubmission,
onFormResubmission: params.onFormResubmission,
androidOnScaleChanged: params.androidOnScaleChanged,
androidOnReceivedIcon: params.androidOnReceivedIcon,
onReceivedIcon: params.onReceivedIcon,
androidOnReceivedTouchIconUrl: params.androidOnReceivedTouchIconUrl,
onReceivedTouchIconUrl: params.onReceivedTouchIconUrl,
androidOnJsBeforeUnload: params.androidOnJsBeforeUnload,
onJsBeforeUnload: params.onJsBeforeUnload,
androidOnReceivedLoginRequest: params.androidOnReceivedLoginRequest,
onReceivedLoginRequest: params.onReceivedLoginRequest,
onPermissionRequestCanceled: params.onPermissionRequestCanceled,
onRequestFocus: params.onRequestFocus,
iosOnWebContentProcessDidTerminate:
params.iosOnWebContentProcessDidTerminate,
onWebContentProcessDidTerminate:
params.onWebContentProcessDidTerminate,
iosOnDidReceiveServerRedirectForProvisionalNavigation:
params.iosOnDidReceiveServerRedirectForProvisionalNavigation,
onDidReceiveServerRedirectForProvisionalNavigation:
params.onDidReceiveServerRedirectForProvisionalNavigation,
iosOnNavigationResponse: params.iosOnNavigationResponse,
onNavigationResponse: params.onNavigationResponse,
iosShouldAllowDeprecatedTLS: params.iosShouldAllowDeprecatedTLS,
shouldAllowDeprecatedTLS: params.shouldAllowDeprecatedTLS,
onCameraCaptureStateChanged: params.onCameraCaptureStateChanged,
onMicrophoneCaptureStateChanged:
params.onMicrophoneCaptureStateChanged,
onContentSizeChanged: params.onContentSizeChanged,
initialUrlRequest: params.initialUrlRequest,
initialFile: params.initialFile,
initialData: params.initialData,
initialOptions: params.initialOptions,
initialSettings: params.initialSettings,
contextMenu: params.contextMenu,
initialUserScripts: params.initialUserScripts,
pullToRefreshController: params.pullToRefreshController,
findInteractionController: params.findInteractionController
as MacOSFindInteractionController?);
@override
final MacOSFindInteractionController? findInteractionController;
}
///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget}
class MacOSInAppWebViewWidget extends PlatformInAppWebViewWidget {
/// Constructs a [MacOSInAppWebViewWidget].
///
///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget}
MacOSInAppWebViewWidget(PlatformInAppWebViewWidgetCreationParams params)
: super.implementation(
params is MacOSInAppWebViewWidgetCreationParams
? params
: MacOSInAppWebViewWidgetCreationParams
.fromPlatformInAppWebViewWidgetCreationParams(params),
);
MacOSInAppWebViewWidgetCreationParams get _macosParams =>
params as MacOSInAppWebViewWidgetCreationParams;
MacOSInAppWebViewController? _controller;
MacOSHeadlessInAppWebView? get _macosHeadlessInAppWebView =>
params.headlessWebView as MacOSHeadlessInAppWebView?;
@override
Widget build(BuildContext context) {
final initialSettings = params.initialSettings ?? InAppWebViewSettings();
_inferInitialSettings(initialSettings);
Map<String, dynamic> settingsMap =
(params.initialSettings != null ? initialSettings.toMap() : null) ??
// ignore: deprecated_member_use_from_same_package
params.initialOptions?.toMap() ??
initialSettings.toMap();
Map<String, dynamic> pullToRefreshSettings =
params.pullToRefreshController?.params.settings.toMap() ??
// ignore: deprecated_member_use_from_same_package
params.pullToRefreshController?.params.options.toMap() ??
PullToRefreshSettings(enabled: false).toMap();
if ((params.headlessWebView?.isRunning() ?? false) &&
params.keepAlive != null) {
final headlessId = params.headlessWebView?.id;
if (headlessId != null) {
// force keep alive id to match headless webview id
params.keepAlive?.id = headlessId;
}
}
return AppKitView(
viewType: 'com.pichillilorenzo/flutter_inappwebview',
onPlatformViewCreated: _onPlatformViewCreated,
gestureRecognizers: params.gestureRecognizers,
creationParams: <String, dynamic>{
'initialUrlRequest': params.initialUrlRequest?.toMap(),
'initialFile': params.initialFile,
'initialData': params.initialData?.toMap(),
'initialSettings': settingsMap,
'contextMenu': params.contextMenu?.toMap() ?? {},
'windowId': params.windowId,
'headlessWebViewId': params.headlessWebView?.isRunning() ?? false
? params.headlessWebView?.id
: null,
'initialUserScripts':
params.initialUserScripts?.map((e) => e.toMap()).toList() ?? [],
'pullToRefreshSettings': pullToRefreshSettings,
'keepAliveId': params.keepAlive?.id,
'preventGestureDelay': params.preventGestureDelay
},
creationParamsCodec: const StandardMessageCodec(),
);
}
void _onPlatformViewCreated(int id) {
dynamic viewId = id;
if (params.headlessWebView?.isRunning() ?? false) {
viewId = params.headlessWebView?.id;
}
viewId = params.keepAlive?.id ?? viewId ?? id;
_macosHeadlessInAppWebView?.internalDispose();
_controller = MacOSInAppWebViewController(
PlatformInAppWebViewControllerCreationParams(
id: viewId, webviewParams: params));
_macosParams.findInteractionController?.init(viewId);
debugLog(
className: runtimeType.toString(),
id: viewId?.toString(),
debugLoggingSettings:
PlatformInAppWebViewController.debugLoggingSettings,
method: "onWebViewCreated",
args: []);
if (params.onWebViewCreated != null) {
params.onWebViewCreated!(
params.controllerFromPlatform?.call(_controller!) ?? _controller!);
}
}
void _inferInitialSettings(InAppWebViewSettings settings) {
if (params.shouldOverrideUrlLoading != null &&
settings.useShouldOverrideUrlLoading == null) {
settings.useShouldOverrideUrlLoading = true;
}
if (params.onLoadResource != null && settings.useOnLoadResource == null) {
settings.useOnLoadResource = true;
}
if (params.onDownloadStartRequest != null &&
settings.useOnDownloadStart == null) {
settings.useOnDownloadStart = true;
}
if ((params.shouldInterceptAjaxRequest != null ||
params.onAjaxProgress != null ||
params.onAjaxReadyStateChange != null) &&
settings.useShouldInterceptAjaxRequest == null) {
settings.useShouldInterceptAjaxRequest = true;
}
if (params.shouldInterceptFetchRequest != null &&
settings.useShouldInterceptFetchRequest == null) {
settings.useShouldInterceptFetchRequest = true;
}
if (params.shouldInterceptRequest != null &&
settings.useShouldInterceptRequest == null) {
settings.useShouldInterceptRequest = true;
}
if (params.onRenderProcessGone != null &&
settings.useOnRenderProcessGone == null) {
settings.useOnRenderProcessGone = true;
}
if (params.onNavigationResponse != null &&
settings.useOnNavigationResponse == null) {
settings.useOnNavigationResponse = true;
}
}
@override
void dispose() {
dynamic viewId = _controller?.getViewId();
debugLog(
className: runtimeType.toString(),
id: viewId?.toString(),
debugLoggingSettings:
PlatformInAppWebViewController.debugLoggingSettings,
method: "dispose",
args: []);
final isKeepAlive = params.keepAlive != null;
_controller?.dispose(isKeepAlive: isKeepAlive);
_controller = null;
params.findInteractionController?.dispose(isKeepAlive: isKeepAlive);
}
@override
T controllerFromPlatform<T>(PlatformInAppWebViewController controller) {
// unused
throw UnimplementedError();
}
}
@@ -0,0 +1,3 @@
export 'in_app_webview_controller.dart' hide InternalInAppWebViewController;
export 'in_app_webview.dart';
export 'headless_in_app_webview.dart' hide InternalHeadlessInAppWebView;
@@ -0,0 +1,240 @@
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
import 'cookie_manager.dart';
import 'http_auth_credentials_database.dart';
import 'find_interaction/main.dart';
import 'in_app_browser/in_app_browser.dart';
import 'in_app_webview/main.dart';
import 'print_job/main.dart';
import 'web_message/main.dart';
import 'web_storage/main.dart';
import 'web_authentication_session/main.dart';
/// Implementation of [InAppWebViewPlatform] using the WebKit API.
class MacOSInAppWebViewPlatform extends InAppWebViewPlatform {
/// Registers this class as the default instance of [InAppWebViewPlatform].
static void registerWith() {
InAppWebViewPlatform.instance = MacOSInAppWebViewPlatform();
}
/// Creates a new [MacOSCookieManager].
///
/// This function should only be called by the app-facing package.
/// Look at using [CookieManager] in `flutter_inappwebview` instead.
@override
MacOSCookieManager createPlatformCookieManager(
PlatformCookieManagerCreationParams params,
) {
return MacOSCookieManager(params);
}
/// Creates a new [MacOSInAppWebViewController].
///
/// This function should only be called by the app-facing package.
/// Look at using [InAppWebViewController] in `flutter_inappwebview` instead.
@override
MacOSInAppWebViewController createPlatformInAppWebViewController(
PlatformInAppWebViewControllerCreationParams params,
) {
return MacOSInAppWebViewController(params);
}
/// Creates a new empty [MacOSInAppWebViewController] to access static methods.
///
/// This function should only be called by the app-facing package.
/// Look at using [InAppWebViewController] in `flutter_inappwebview` instead.
@override
MacOSInAppWebViewController createPlatformInAppWebViewControllerStatic() {
return MacOSInAppWebViewController.static();
}
/// Creates a new [MacOSInAppWebViewWidget].
///
/// This function should only be called by the app-facing package.
/// Look at using [InAppWebView] in `flutter_inappwebview` instead.
@override
MacOSInAppWebViewWidget createPlatformInAppWebViewWidget(
PlatformInAppWebViewWidgetCreationParams params,
) {
return MacOSInAppWebViewWidget(params);
}
/// Creates a new [MacOSFindInteractionController].
///
/// This function should only be called by the app-facing package.
/// Look at using [FindInteractionController] in `flutter_inappwebview` instead.
@override
MacOSFindInteractionController createPlatformFindInteractionController(
PlatformFindInteractionControllerCreationParams params,
) {
return MacOSFindInteractionController(params);
}
/// Creates a new [MacOSPrintJobController].
///
/// This function should only be called by the app-facing package.
/// Look at using [PrintJobController] in `flutter_inappwebview` instead.
@override
MacOSPrintJobController createPlatformPrintJobController(
PlatformPrintJobControllerCreationParams params,
) {
return MacOSPrintJobController(params);
}
/// Creates a new [MacOSWebMessageChannel].
///
/// This function should only be called by the app-facing package.
/// Look at using [WebMessageChannel] in `flutter_inappwebview` instead.
@override
MacOSWebMessageChannel createPlatformWebMessageChannel(
PlatformWebMessageChannelCreationParams params,
) {
return MacOSWebMessageChannel(params);
}
/// Creates a new empty [MacOSWebMessageChannel] to access static methods.
///
/// This function should only be called by the app-facing package.
/// Look at using [WebMessageChannel] in `flutter_inappwebview` instead.
@override
MacOSWebMessageChannel createPlatformWebMessageChannelStatic() {
return MacOSWebMessageChannel.static();
}
/// Creates a new [MacOSWebMessageListener].
///
/// This function should only be called by the app-facing package.
/// Look at using [WebMessageListener] in `flutter_inappwebview` instead.
@override
MacOSWebMessageListener createPlatformWebMessageListener(
PlatformWebMessageListenerCreationParams params,
) {
return MacOSWebMessageListener(params);
}
/// Creates a new [MacOSJavaScriptReplyProxy].
///
/// This function should only be called by the app-facing package.
/// Look at using [JavaScriptReplyProxy] in `flutter_inappwebview` instead.
@override
MacOSJavaScriptReplyProxy createPlatformJavaScriptReplyProxy(
PlatformJavaScriptReplyProxyCreationParams params,
) {
return MacOSJavaScriptReplyProxy(params);
}
/// Creates a new [MacOSWebMessagePort].
///
/// This function should only be called by the app-facing package.
/// Look at using [WebMessagePort] in `flutter_inappwebview` instead.
@override
MacOSWebMessagePort createPlatformWebMessagePort(
PlatformWebMessagePortCreationParams params,
) {
return MacOSWebMessagePort(params);
}
/// Creates a new [MacOSWebStorage].
///
/// This function should only be called by the app-facing package.
/// Look at using [WebStorage] in `flutter_inappwebview` instead.
@override
MacOSWebStorage createPlatformWebStorage(
PlatformWebStorageCreationParams params,
) {
return MacOSWebStorage(params);
}
/// Creates a new [MacOSLocalStorage].
///
/// This function should only be called by the app-facing package.
/// Look at using [LocalStorage] in `flutter_inappwebview` instead.
@override
MacOSLocalStorage createPlatformLocalStorage(
PlatformLocalStorageCreationParams params,
) {
return MacOSLocalStorage(params);
}
/// Creates a new [MacOSSessionStorage].
///
/// This function should only be called by the app-facing package.
/// Look at using [SessionStorage] in `flutter_inappwebview` instead.
@override
MacOSSessionStorage createPlatformSessionStorage(
PlatformSessionStorageCreationParams params,
) {
return MacOSSessionStorage(params);
}
/// Creates a new [MacOSHeadlessInAppWebView].
///
/// This function should only be called by the app-facing package.
/// Look at using [HeadlessInAppWebView] in `flutter_inappwebview` instead.
@override
MacOSHeadlessInAppWebView createPlatformHeadlessInAppWebView(
PlatformHeadlessInAppWebViewCreationParams params,
) {
return MacOSHeadlessInAppWebView(params);
}
/// Creates a new [MacOSHttpAuthCredentialDatabase].
///
/// This function should only be called by the app-facing package.
/// Look at using [HttpAuthCredentialDatabase] in `flutter_inappwebview` instead.
@override
MacOSHttpAuthCredentialDatabase createPlatformHttpAuthCredentialDatabase(
PlatformHttpAuthCredentialDatabaseCreationParams params,
) {
return MacOSHttpAuthCredentialDatabase(params);
}
/// Creates a new [MacOSInAppBrowser].
///
/// This function should only be called by the app-facing package.
/// Look at using [InAppBrowser] in `flutter_inappwebview` instead.
@override
MacOSInAppBrowser createPlatformInAppBrowser(
PlatformInAppBrowserCreationParams params,
) {
return MacOSInAppBrowser(params);
}
/// Creates a new empty [MacOSInAppBrowser] to access static methods.
///
/// This function should only be called by the app-facing package.
/// Look at using [InAppBrowser] in `flutter_inappwebview` instead.
@override
MacOSInAppBrowser createPlatformInAppBrowserStatic() {
return MacOSInAppBrowser.static();
}
/// Creates a new empty [MacOSWebStorageManager] to access static methods.
///
/// This function should only be called by the app-facing package.
/// Look at using [WebStorageManager] in `flutter_inappwebview` instead.
@override
MacOSWebStorageManager createPlatformWebStorageManager(
PlatformWebStorageManagerCreationParams params) {
return MacOSWebStorageManager(params);
}
/// Creates a new [MacOSWebAuthenticationSession].
///
/// This function should only be called by the app-facing package.
/// Look at using [WebAuthenticationSession] in `flutter_inappwebview` instead.
@override
MacOSWebAuthenticationSession createPlatformWebAuthenticationSession(
PlatformWebAuthenticationSessionCreationParams params) {
return MacOSWebAuthenticationSession(params);
}
/// Creates a new empty [MacOSWebAuthenticationSession] to access static methods.
///
/// This function should only be called by the app-facing package.
/// Look at using [WebAuthenticationSession] in `flutter_inappwebview` instead.
@override
MacOSWebAuthenticationSession createPlatformWebAuthenticationSessionStatic() {
return MacOSWebAuthenticationSession.static();
}
}
@@ -0,0 +1,11 @@
export 'inappwebview_platform.dart';
export 'in_app_webview/main.dart';
export 'in_app_browser/main.dart';
export 'web_storage/main.dart';
export 'cookie_manager.dart' hide InternalCookieManager;
export 'http_auth_credentials_database.dart'
hide InternalHttpAuthCredentialDatabase;
export 'web_message/main.dart';
export 'print_job/main.dart';
export 'find_interaction/main.dart';
export 'web_authentication_session/main.dart';
@@ -0,0 +1,64 @@
import 'package:flutter/services.dart';
///Platform native utilities
class PlatformUtil {
static PlatformUtil? _instance;
static const MethodChannel _channel =
MethodChannel('com.pichillilorenzo/flutter_inappwebview_platformutil');
PlatformUtil._();
///Get [PlatformUtil] instance.
static PlatformUtil instance() {
return (_instance != null) ? _instance! : _init();
}
static PlatformUtil _init() {
_channel.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
print(e);
print(e.stackTrace);
}
});
_instance = PlatformUtil._();
return _instance!;
}
static Future<dynamic> _handleMethod(MethodCall call) async {}
String? _cachedSystemVersion;
///Get current platform system version.
Future<String> getSystemVersion() async {
if (_cachedSystemVersion != null) {
return _cachedSystemVersion!;
}
Map<String, dynamic> args = <String, dynamic>{};
_cachedSystemVersion =
await _channel.invokeMethod('getSystemVersion', args);
return _cachedSystemVersion!;
}
///Format date.
Future<String> formatDate(
{required DateTime date,
required String format,
String locale = "en_US",
String timezone = "UTC"}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('date', () => date.millisecondsSinceEpoch);
args.putIfAbsent('format', () => format);
args.putIfAbsent('locale', () => locale);
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);
}
}
@@ -0,0 +1 @@
export 'print_job_controller.dart';
@@ -0,0 +1,73 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
/// Object specifying creation parameters for creating a [MacOSPrintJobController].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformPrintJobControllerCreationParams] for
/// more information.
@immutable
class MacOSPrintJobControllerCreationParams
extends PlatformPrintJobControllerCreationParams {
/// Creates a new [MacOSPrintJobControllerCreationParams] instance.
const MacOSPrintJobControllerCreationParams(
{required super.id, super.onComplete});
/// Creates a [MacOSPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams].
factory MacOSPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformPrintJobControllerCreationParams params) {
return MacOSPrintJobControllerCreationParams(
id: params.id, onComplete: params.onComplete);
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformPrintJobController}
class MacOSPrintJobController extends PlatformPrintJobController
with ChannelController {
/// Constructs a [MacOSPrintJobController].
MacOSPrintJobController(PlatformPrintJobControllerCreationParams params)
: super.implementation(
params is MacOSPrintJobControllerCreationParams
? params
: MacOSPrintJobControllerCreationParams
.fromPlatformPrintJobControllerCreationParams(params),
) {
onComplete = params.onComplete;
channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_${params.id}');
handler = _handleMethod;
initMethodCallHandler();
}
Future<dynamic> _handleMethod(MethodCall call) async {
switch (call.method) {
case "onComplete":
bool completed = call.arguments["completed"];
String? error = call.arguments["error"];
if (onComplete != null) {
onComplete!(completed, error);
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
}
@override
Future<PrintJobInfo?> getInfo() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? infoMap =
(await channel?.invokeMethod('getInfo', args))?.cast<String, dynamic>();
return PrintJobInfo.fromMap(infoMap);
}
@override
Future<void> dispose() async {
Map<String, dynamic> args = <String, dynamic>{};
await channel?.invokeMethod('dispose', args);
disposeChannel();
}
}
@@ -0,0 +1 @@
export 'web_authenticate_session.dart';
@@ -0,0 +1,164 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
/// Object specifying creation parameters for creating a [MacOSWebAuthenticationSession].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformWebAuthenticationSessionCreationParams] for
/// more information.
class MacOSWebAuthenticationSessionCreationParams
extends PlatformWebAuthenticationSessionCreationParams {
/// Creates a new [MacOSWebAuthenticationSessionCreationParams] instance.
const MacOSWebAuthenticationSessionCreationParams();
/// Creates a [MacOSWebAuthenticationSessionCreationParams] instance based on [PlatformWebAuthenticationSessionCreationParams].
factory MacOSWebAuthenticationSessionCreationParams.fromPlatformWebAuthenticationSessionCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformWebAuthenticationSessionCreationParams params) {
return MacOSWebAuthenticationSessionCreationParams();
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformWebAuthenticationSession}
class MacOSWebAuthenticationSession extends PlatformWebAuthenticationSession
with ChannelController {
/// Constructs a [MacOSWebAuthenticationSession].
MacOSWebAuthenticationSession(
PlatformWebAuthenticationSessionCreationParams params)
: super.implementation(
params is MacOSWebAuthenticationSessionCreationParams
? params
: MacOSWebAuthenticationSessionCreationParams
.fromPlatformWebAuthenticationSessionCreationParams(params),
);
static final MacOSWebAuthenticationSession _staticValue =
MacOSWebAuthenticationSession(
MacOSWebAuthenticationSessionCreationParams());
/// Provide static access.
factory MacOSWebAuthenticationSession.static() {
return _staticValue;
}
@override
final String id = IdGenerator.generate();
@override
late final WebUri url;
@override
late final String? callbackURLScheme;
@override
late final WebAuthenticationSessionSettings? initialSettings;
@override
late final WebAuthenticationSessionCompletionHandler onComplete;
static const MethodChannel _staticChannel = const MethodChannel(
'com.pichillilorenzo/flutter_webauthenticationsession');
@override
Future<MacOSWebAuthenticationSession> create(
{required WebUri url,
String? callbackURLScheme,
WebAuthenticationSessionCompletionHandler onComplete,
WebAuthenticationSessionSettings? initialSettings}) async {
var session = MacOSWebAuthenticationSession._create(
url: url,
callbackURLScheme: callbackURLScheme,
onComplete: onComplete,
initialSettings: initialSettings);
initialSettings =
session.initialSettings ?? WebAuthenticationSessionSettings();
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("id", () => session.id);
args.putIfAbsent("url", () => session.url.toString());
args.putIfAbsent("callbackURLScheme", () => session.callbackURLScheme);
args.putIfAbsent("initialSettings", () => initialSettings?.toMap());
await _staticChannel.invokeMethod('create', args);
return session;
}
MacOSWebAuthenticationSession._create(
{required this.url,
this.callbackURLScheme,
this.onComplete,
WebAuthenticationSessionSettings? initialSettings})
: super.implementation(MacOSWebAuthenticationSessionCreationParams()) {
assert(url.toString().isNotEmpty);
assert(['http', 'https'].contains(url.scheme),
'The specified URL has an unsupported scheme. Only HTTP and HTTPS URLs are supported on iOS.');
this.initialSettings =
initialSettings ?? WebAuthenticationSessionSettings();
channel = MethodChannel(
'com.pichillilorenzo/flutter_webauthenticationsession_$id');
handler = _handleMethod;
initMethodCallHandler();
}
_debugLog(String method, dynamic args) {
debugLog(
className: this.runtimeType.toString(),
debugLoggingSettings:
PlatformWebAuthenticationSession.debugLoggingSettings,
id: id,
method: method,
args: args);
}
Future<dynamic> _handleMethod(MethodCall call) async {
_debugLog(call.method, call.arguments);
switch (call.method) {
case "onComplete":
String? url = call.arguments["url"];
WebUri? uri = url != null ? WebUri(url) : null;
var error = WebAuthenticationSessionError.fromNativeValue(
call.arguments["errorCode"]);
if (onComplete != null) {
onComplete!(uri, error);
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
}
@override
Future<bool> canStart() async {
Map<String, dynamic> args = <String, dynamic>{};
return await channel?.invokeMethod<bool>('canStart', args) ?? false;
}
@override
Future<bool> start() async {
Map<String, dynamic> args = <String, dynamic>{};
return await channel?.invokeMethod<bool>('start', args) ?? false;
}
@override
Future<void> cancel() async {
Map<String, dynamic> args = <String, dynamic>{};
await channel?.invokeMethod("cancel", args);
}
@override
Future<void> dispose() async {
Map<String, dynamic> args = <String, dynamic>{};
await channel?.invokeMethod("dispose", args);
disposeChannel();
}
@override
Future<bool> isAvailable() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _staticChannel.invokeMethod<bool>("isAvailable", args) ??
false;
}
}
@@ -0,0 +1,3 @@
export 'web_message_port.dart' hide InternalWebMessagePort;
export 'web_message_channel.dart' hide InternalWebMessageChannel;
export 'web_message_listener.dart';
@@ -0,0 +1,120 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
import 'web_message_port.dart';
/// Object specifying creation parameters for creating a [MacOSWebMessageChannel].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformWebMessageChannelCreationParams] for
/// more information.
@immutable
class MacOSWebMessageChannelCreationParams
extends PlatformWebMessageChannelCreationParams {
/// Creates a new [MacOSWebMessageChannelCreationParams] instance.
const MacOSWebMessageChannelCreationParams(
{required super.id, required super.port1, required super.port2});
/// Creates a [MacOSWebMessageChannelCreationParams] instance based on [PlatformWebMessageChannelCreationParams].
factory MacOSWebMessageChannelCreationParams.fromPlatformWebMessageChannelCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformWebMessageChannelCreationParams params) {
return MacOSWebMessageChannelCreationParams(
id: params.id, port1: params.port1, port2: params.port2);
}
@override
String toString() {
return 'MacOSWebMessageChannelCreationParams{id: $id, port1: $port1, port2: $port2}';
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformWebMessageChannel}
class MacOSWebMessageChannel extends PlatformWebMessageChannel
with ChannelController {
/// Constructs a [MacOSWebMessageChannel].
MacOSWebMessageChannel(PlatformWebMessageChannelCreationParams params)
: super.implementation(
params is MacOSWebMessageChannelCreationParams
? params
: MacOSWebMessageChannelCreationParams
.fromPlatformWebMessageChannelCreationParams(params),
) {
channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_web_message_channel_${params.id}');
handler = _handleMethod;
initMethodCallHandler();
}
static final MacOSWebMessageChannel _staticValue = MacOSWebMessageChannel(
MacOSWebMessageChannelCreationParams(
id: '',
port1:
MacOSWebMessagePort(MacOSWebMessagePortCreationParams(index: 0)),
port2: MacOSWebMessagePort(
MacOSWebMessagePortCreationParams(index: 1))));
/// Provide static access.
factory MacOSWebMessageChannel.static() {
return _staticValue;
}
MacOSWebMessagePort get _macosPort1 => port1 as MacOSWebMessagePort;
MacOSWebMessagePort get _macosPort2 => port2 as MacOSWebMessagePort;
static MacOSWebMessageChannel? _fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
var webMessageChannel = MacOSWebMessageChannel(
MacOSWebMessageChannelCreationParams(
id: map["id"],
port1: MacOSWebMessagePort(
MacOSWebMessagePortCreationParams(index: 0)),
port2: MacOSWebMessagePort(
MacOSWebMessagePortCreationParams(index: 1))));
webMessageChannel._macosPort1.webMessageChannel = webMessageChannel;
webMessageChannel._macosPort2.webMessageChannel = webMessageChannel;
return webMessageChannel;
}
Future<dynamic> _handleMethod(MethodCall call) async {
switch (call.method) {
case "onMessage":
int index = call.arguments["index"];
var port = index == 0 ? _macosPort1 : _macosPort2;
if (port.onMessage != null) {
WebMessage? message = call.arguments["message"] != null
? WebMessage.fromMap(
call.arguments["message"].cast<String, dynamic>())
: null;
port.onMessage!(message);
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
return null;
}
@override
MacOSWebMessageChannel? fromMap(Map<String, dynamic>? map) {
return _fromMap(map);
}
@override
void dispose() {
disposeChannel();
}
@override
String toString() {
return 'MacOSWebMessageChannel{id: $id, port1: $port1, port2: $port2}';
}
}
extension InternalWebMessageChannel on MacOSWebMessageChannel {
MethodChannel? get internalChannel => channel;
}
@@ -0,0 +1,164 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
/// Object specifying creation parameters for creating a [MacOSWebMessageListener].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformWebMessageListenerCreationParams] for
/// more information.
@immutable
class MacOSWebMessageListenerCreationParams
extends PlatformWebMessageListenerCreationParams {
/// Creates a new [MacOSWebMessageListenerCreationParams] instance.
const MacOSWebMessageListenerCreationParams(
{required this.allowedOriginRules,
required super.jsObjectName,
super.onPostMessage});
/// Creates a [MacOSWebMessageListenerCreationParams] instance based on [PlatformWebMessageListenerCreationParams].
factory MacOSWebMessageListenerCreationParams.fromPlatformWebMessageListenerCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformWebMessageListenerCreationParams params) {
return MacOSWebMessageListenerCreationParams(
allowedOriginRules: params.allowedOriginRules ?? Set.from(["*"]),
jsObjectName: params.jsObjectName,
onPostMessage: params.onPostMessage);
}
@override
final Set<String> allowedOriginRules;
@override
String toString() {
return 'MacOSWebMessageListenerCreationParams{jsObjectName: $jsObjectName, allowedOriginRules: $allowedOriginRules, onPostMessage: $onPostMessage}';
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformWebMessageListener}
class MacOSWebMessageListener extends PlatformWebMessageListener
with ChannelController {
/// Constructs a [MacOSWebMessageListener].
MacOSWebMessageListener(PlatformWebMessageListenerCreationParams params)
: super.implementation(
params is MacOSWebMessageListenerCreationParams
? params
: MacOSWebMessageListenerCreationParams
.fromPlatformWebMessageListenerCreationParams(params),
) {
assert(!this._macosParams.allowedOriginRules.contains(""),
"allowedOriginRules cannot contain empty strings");
channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_web_message_listener_${_id}_${params.jsObjectName}');
handler = _handleMethod;
initMethodCallHandler();
}
///Message Listener ID used internally.
final String _id = IdGenerator.generate();
MacOSJavaScriptReplyProxy? _replyProxy;
MacOSWebMessageListenerCreationParams get _macosParams =>
params as MacOSWebMessageListenerCreationParams;
Future<dynamic> _handleMethod(MethodCall call) async {
switch (call.method) {
case "onPostMessage":
if (_replyProxy == null) {
_replyProxy = MacOSJavaScriptReplyProxy(
PlatformJavaScriptReplyProxyCreationParams(
webMessageListener: this));
}
if (onPostMessage != null) {
WebMessage? message = call.arguments["message"] != null
? WebMessage.fromMap(
call.arguments["message"].cast<String, dynamic>())
: null;
WebUri? sourceOrigin = call.arguments["sourceOrigin"] != null
? WebUri(call.arguments["sourceOrigin"])
: null;
bool isMainFrame = call.arguments["isMainFrame"];
onPostMessage!(message, sourceOrigin, isMainFrame, _replyProxy!);
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
return null;
}
@override
void dispose() {
disposeChannel();
}
@override
Map<String, dynamic> toMap() {
return {
"id": _id,
"jsObjectName": params.jsObjectName,
"allowedOriginRules": _macosParams.allowedOriginRules.toList(),
};
}
@override
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return 'MacOSWebMessageListener{id: ${_id}, jsObjectName: ${params.jsObjectName}, allowedOriginRules: ${params.allowedOriginRules}, replyProxy: $_replyProxy}';
}
}
/// Object specifying creation parameters for creating a [MacOSJavaScriptReplyProxy].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformJavaScriptReplyProxyCreationParams] for
/// more information.
@immutable
class MacOSJavaScriptReplyProxyCreationParams
extends PlatformJavaScriptReplyProxyCreationParams {
/// Creates a new [MacOSJavaScriptReplyProxyCreationParams] instance.
const MacOSJavaScriptReplyProxyCreationParams(
{required super.webMessageListener});
/// Creates a [MacOSJavaScriptReplyProxyCreationParams] instance based on [PlatformJavaScriptReplyProxyCreationParams].
factory MacOSJavaScriptReplyProxyCreationParams.fromPlatformJavaScriptReplyProxyCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformJavaScriptReplyProxyCreationParams params) {
return MacOSJavaScriptReplyProxyCreationParams(
webMessageListener: params.webMessageListener);
}
}
///{@macro flutter_inappwebview_platform_interface.JavaScriptReplyProxy}
class MacOSJavaScriptReplyProxy extends PlatformJavaScriptReplyProxy {
/// Constructs a [MacOSWebMessageListener].
MacOSJavaScriptReplyProxy(PlatformJavaScriptReplyProxyCreationParams params)
: super.implementation(
params is MacOSJavaScriptReplyProxyCreationParams
? params
: MacOSJavaScriptReplyProxyCreationParams
.fromPlatformJavaScriptReplyProxyCreationParams(params),
);
MacOSWebMessageListener get _macosWebMessageListener =>
params.webMessageListener as MacOSWebMessageListener;
@override
Future<void> postMessage(WebMessage message) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('message', () => message.toMap());
await _macosWebMessageListener.channel?.invokeMethod('postMessage', args);
}
@override
String toString() {
return 'MacOSJavaScriptReplyProxy{}';
}
}
@@ -0,0 +1,95 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
import 'web_message_channel.dart';
/// Object specifying creation parameters for creating a [MacOSWebMessagePort].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformWebMessagePortCreationParams] for
/// more information.
@immutable
class MacOSWebMessagePortCreationParams
extends PlatformWebMessagePortCreationParams {
/// Creates a new [MacOSWebMessagePortCreationParams] instance.
const MacOSWebMessagePortCreationParams({required super.index});
/// Creates a [MacOSWebMessagePortCreationParams] instance based on [PlatformWebMessagePortCreationParams].
factory MacOSWebMessagePortCreationParams.fromPlatformWebMessagePortCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformWebMessagePortCreationParams params) {
return MacOSWebMessagePortCreationParams(index: params.index);
}
@override
String toString() {
return 'MacOSWebMessagePortCreationParams{index: $index}';
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformWebMessagePort}
class MacOSWebMessagePort extends PlatformWebMessagePort {
WebMessageCallback? _onMessage;
late MacOSWebMessageChannel _webMessageChannel;
/// Constructs a [MacOSWebMessagePort].
MacOSWebMessagePort(PlatformWebMessagePortCreationParams params)
: super.implementation(
params is MacOSWebMessagePortCreationParams
? params
: MacOSWebMessagePortCreationParams
.fromPlatformWebMessagePortCreationParams(params),
);
@override
Future<void> setWebMessageCallback(WebMessageCallback? onMessage) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => params.index);
await _webMessageChannel.internalChannel
?.invokeMethod('setWebMessageCallback', args);
this._onMessage = onMessage;
}
@override
Future<void> postMessage(WebMessage message) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => params.index);
args.putIfAbsent('message', () => message.toMap());
await _webMessageChannel.internalChannel?.invokeMethod('postMessage', args);
}
@override
Future<void> close() async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => params.index);
await _webMessageChannel.internalChannel?.invokeMethod('close', args);
}
@override
Map<String, dynamic> toMap() {
return {
"index": params.index,
"webMessageChannelId": this._webMessageChannel.params.id
};
}
@override
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return 'MacOSWebMessagePort{index: ${params.index}}';
}
}
extension InternalWebMessagePort on MacOSWebMessagePort {
WebMessageCallback? get onMessage => _onMessage;
void set onMessage(WebMessageCallback? value) => _onMessage = value;
MacOSWebMessageChannel get webMessageChannel => _webMessageChannel;
void set webMessageChannel(MacOSWebMessageChannel value) =>
_webMessageChannel = value;
}
@@ -0,0 +1,2 @@
export 'web_storage.dart';
export 'web_storage_manager.dart' hide InternalWebStorageManager;
@@ -0,0 +1,257 @@
import 'dart:convert';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
import '../in_app_webview/in_app_webview_controller.dart';
/// Object specifying creation parameters for creating a [MacOSWebStorage].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformWebStorageCreationParams] for
/// more information.
class MacOSWebStorageCreationParams extends PlatformWebStorageCreationParams {
/// Creates a new [MacOSWebStorageCreationParams] instance.
MacOSWebStorageCreationParams(
{required super.localStorage, required super.sessionStorage});
/// Creates a [MacOSWebStorageCreationParams] instance based on [PlatformWebStorageCreationParams].
factory MacOSWebStorageCreationParams.fromPlatformWebStorageCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformWebStorageCreationParams params) {
return MacOSWebStorageCreationParams(
localStorage: params.localStorage,
sessionStorage: params.sessionStorage);
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformWebStorage}
class MacOSWebStorage extends PlatformWebStorage {
/// Constructs a [MacOSWebStorage].
MacOSWebStorage(PlatformWebStorageCreationParams params)
: super.implementation(
params is MacOSWebStorageCreationParams
? params
: MacOSWebStorageCreationParams
.fromPlatformWebStorageCreationParams(params),
);
@override
PlatformLocalStorage get localStorage => params.localStorage;
@override
PlatformSessionStorage get sessionStorage => params.sessionStorage;
@override
void dispose() {
localStorage.dispose();
sessionStorage.dispose();
}
}
/// Object specifying creation parameters for creating a [MacOSStorage].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformStorageCreationParams] for
/// more information.
class MacOSStorageCreationParams extends PlatformStorageCreationParams {
/// Creates a new [MacOSStorageCreationParams] instance.
MacOSStorageCreationParams(
{required super.controller, required super.webStorageType});
/// Creates a [MacOSStorageCreationParams] instance based on [PlatformStorageCreationParams].
factory MacOSStorageCreationParams.fromPlatformStorageCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformStorageCreationParams params) {
return MacOSStorageCreationParams(
controller: params.controller, webStorageType: params.webStorageType);
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformStorage}
abstract mixin class MacOSStorage implements PlatformStorage {
@override
MacOSInAppWebViewController? controller;
@override
Future<int?> length() async {
var result = await controller?.evaluateJavascript(source: """
window.$webStorageType.length;
""");
return result != null ? int.parse(json.decode(result)) : null;
}
@override
Future<void> setItem({required String key, required dynamic value}) async {
var encodedValue = json.encode(value);
await controller?.evaluateJavascript(source: """
window.$webStorageType.setItem("$key", ${value is String ? encodedValue : "JSON.stringify($encodedValue)"});
""");
}
@override
Future<dynamic> getItem({required String key}) async {
var itemValue = await controller?.evaluateJavascript(source: """
window.$webStorageType.getItem("$key");
""");
if (itemValue == null) {
return null;
}
try {
return json.decode(itemValue);
} catch (e) {}
return itemValue;
}
@override
Future<void> removeItem({required String key}) async {
await controller?.evaluateJavascript(source: """
window.$webStorageType.removeItem("$key");
""");
}
@override
Future<List<WebStorageItem>> getItems() async {
var webStorageItems = <WebStorageItem>[];
List<Map<dynamic, dynamic>>? items =
(await controller?.evaluateJavascript(source: """
(function() {
var webStorageItems = [];
for(var i = 0; i < window.$webStorageType.length; i++){
var key = window.$webStorageType.key(i);
webStorageItems.push(
{
key: key,
value: window.$webStorageType.getItem(key)
}
);
}
return webStorageItems;
})();
"""))?.cast<Map<dynamic, dynamic>>();
if (items == null) {
return webStorageItems;
}
for (var item in items) {
webStorageItems
.add(WebStorageItem(key: item["key"], value: item["value"]));
}
return webStorageItems;
}
@override
Future<void> clear() async {
await controller?.evaluateJavascript(source: """
window.$webStorageType.clear();
""");
}
@override
Future<String> key({required int index}) async {
var result = await controller?.evaluateJavascript(source: """
window.$webStorageType.key($index);
""");
return result != null ? json.decode(result) : null;
}
@override
void dispose() {
controller = null;
}
}
/// Object specifying creation parameters for creating a [MacOSLocalStorage].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformLocalStorageCreationParams] for
/// more information.
class MacOSLocalStorageCreationParams
extends PlatformLocalStorageCreationParams {
/// Creates a new [MacOSLocalStorageCreationParams] instance.
MacOSLocalStorageCreationParams(super.params);
/// Creates a [MacOSLocalStorageCreationParams] instance based on [PlatformLocalStorageCreationParams].
factory MacOSLocalStorageCreationParams.fromPlatformLocalStorageCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformLocalStorageCreationParams params) {
return MacOSLocalStorageCreationParams(params);
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformLocalStorage}
class MacOSLocalStorage extends PlatformLocalStorage with MacOSStorage {
/// Constructs a [MacOSLocalStorage].
MacOSLocalStorage(PlatformLocalStorageCreationParams params)
: super.implementation(
params is MacOSLocalStorageCreationParams
? params
: MacOSLocalStorageCreationParams
.fromPlatformLocalStorageCreationParams(params),
);
/// Default storage
factory MacOSLocalStorage.defaultStorage(
{required PlatformInAppWebViewController? controller}) {
return MacOSLocalStorage(MacOSLocalStorageCreationParams(
PlatformLocalStorageCreationParams(PlatformStorageCreationParams(
controller: controller,
webStorageType: WebStorageType.LOCAL_STORAGE))));
}
@override
MacOSInAppWebViewController? get controller =>
params.controller as MacOSInAppWebViewController?;
}
/// Object specifying creation parameters for creating a [MacOSSessionStorage].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformSessionStorageCreationParams] for
/// more information.
class MacOSSessionStorageCreationParams
extends PlatformSessionStorageCreationParams {
/// Creates a new [MacOSSessionStorageCreationParams] instance.
MacOSSessionStorageCreationParams(super.params);
/// Creates a [MacOSSessionStorageCreationParams] instance based on [PlatformSessionStorageCreationParams].
factory MacOSSessionStorageCreationParams.fromPlatformSessionStorageCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformSessionStorageCreationParams params) {
return MacOSSessionStorageCreationParams(params);
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformSessionStorage}
class MacOSSessionStorage extends PlatformSessionStorage with MacOSStorage {
/// Constructs a [MacOSSessionStorage].
MacOSSessionStorage(PlatformSessionStorageCreationParams params)
: super.implementation(
params is MacOSSessionStorageCreationParams
? params
: MacOSSessionStorageCreationParams
.fromPlatformSessionStorageCreationParams(params),
);
/// Default storage
factory MacOSSessionStorage.defaultStorage(
{required PlatformInAppWebViewController? controller}) {
return MacOSSessionStorage(MacOSSessionStorageCreationParams(
PlatformSessionStorageCreationParams(PlatformStorageCreationParams(
controller: controller,
webStorageType: WebStorageType.SESSION_STORAGE))));
}
@override
MacOSInAppWebViewController? get controller =>
params.controller as MacOSInAppWebViewController?;
}
@@ -0,0 +1,134 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';
/// Object specifying creation parameters for creating a [MacOSWebStorageManager].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformWebStorageManagerCreationParams] for
/// more information.
@immutable
class MacOSWebStorageManagerCreationParams
extends PlatformWebStorageManagerCreationParams {
/// Creates a new [MacOSWebStorageManagerCreationParams] instance.
const MacOSWebStorageManagerCreationParams(
// This parameter prevents breaking changes later.
// ignore: avoid_unused_constructor_parameters
PlatformWebStorageManagerCreationParams params,
) : super();
/// Creates a [MacOSWebStorageManagerCreationParams] instance based on [PlatformWebStorageManagerCreationParams].
factory MacOSWebStorageManagerCreationParams.fromPlatformWebStorageManagerCreationParams(
PlatformWebStorageManagerCreationParams params) {
return MacOSWebStorageManagerCreationParams(params);
}
}
///{@macro flutter_inappwebview_platform_interface.PlatformWebStorageManager}
class MacOSWebStorageManager extends PlatformWebStorageManager
with ChannelController {
/// Creates a new [MacOSWebStorageManager].
MacOSWebStorageManager(PlatformWebStorageManagerCreationParams params)
: super.implementation(
params is MacOSWebStorageManagerCreationParams
? params
: MacOSWebStorageManagerCreationParams
.fromPlatformWebStorageManagerCreationParams(params),
) {
channel = const MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_webstoragemanager');
handler = handleMethod;
initMethodCallHandler();
}
static MacOSWebStorageManager? _instance;
///Gets the WebStorage manager shared instance.
static MacOSWebStorageManager instance() {
return (_instance != null) ? _instance! : _init();
}
static MacOSWebStorageManager _init() {
_instance = MacOSWebStorageManager(MacOSWebStorageManagerCreationParams(
const PlatformWebStorageManagerCreationParams()));
return _instance!;
}
Future<dynamic> _handleMethod(MethodCall call) async {}
@override
Future<List<WebsiteDataRecord>> fetchDataRecords(
{required Set<WebsiteDataType> dataTypes}) async {
List<WebsiteDataRecord> recordList = [];
List<String> dataTypesList = [];
for (var dataType in dataTypes) {
dataTypesList.add(dataType.toNativeValue());
}
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("dataTypes", () => dataTypesList);
List<Map<dynamic, dynamic>> records =
(await channel?.invokeMethod<List>('fetchDataRecords', args))
?.cast<Map<dynamic, dynamic>>() ??
[];
for (var record in records) {
List<String> dataTypesString = record["dataTypes"].cast<String>();
Set<WebsiteDataType> dataTypes = Set();
for (var dataTypeValue in dataTypesString) {
var dataType = WebsiteDataType.fromNativeValue(dataTypeValue);
if (dataType != null) {
dataTypes.add(dataType);
}
}
recordList.add(WebsiteDataRecord(
displayName: record["displayName"], dataTypes: dataTypes));
}
return recordList;
}
@override
Future<void> removeDataFor(
{required Set<WebsiteDataType> dataTypes,
required List<WebsiteDataRecord> dataRecords}) async {
List<String> dataTypesList = [];
for (var dataType in dataTypes) {
dataTypesList.add(dataType.toNativeValue());
}
List<Map<String, dynamic>> recordList = [];
for (var record in dataRecords) {
recordList.add(record.toMap());
}
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("dataTypes", () => dataTypesList);
args.putIfAbsent("recordList", () => recordList);
await channel?.invokeMethod('removeDataFor', args);
}
@override
Future<void> removeDataModifiedSince(
{required Set<WebsiteDataType> dataTypes, required DateTime date}) async {
List<String> dataTypesList = [];
for (var dataType in dataTypes) {
dataTypesList.add(dataType.toNativeValue());
}
var timestamp = date.millisecondsSinceEpoch;
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("dataTypes", () => dataTypesList);
args.putIfAbsent("timestamp", () => timestamp);
await channel?.invokeMethod('removeDataModifiedSince', args);
}
@override
void dispose() {
// empty
}
}
extension InternalWebStorageManager on MacOSWebStorageManager {
get handleMethod => _handleMethod;
}
@@ -0,0 +1,172 @@
//
// CredentialDatabase.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 29/10/2019.
//
import Foundation
import FlutterMacOS
public class CredentialDatabase: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_credential_database"
var plugin: InAppWebViewFlutterPlugin?
static var credentialStore = URLCredentialStorage.shared
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getAllAuthCredentials":
var allCredentials: [[String: Any?]] = []
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
var crendentials: [[String: Any?]] = []
for c in credentials {
let credential: [String: Any?] = c.value.toMap()
crendentials.append(credential)
}
if crendentials.count > 0 {
let dict: [String : Any] = [
"protectionSpace": protectionSpace.toMap(),
"credentials": crendentials
]
allCredentials.append(dict)
}
}
result(allCredentials)
break
case "getHttpAuthCredentials":
var crendentials: [[String: Any?]] = []
let host = arguments!["host"] as! String
let urlProtocol = arguments!["protocol"] as? String
let urlPort = arguments!["port"] as? Int ?? 0
var realm = arguments!["realm"] as? String;
if let r = realm, r.isEmpty {
realm = nil
}
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
if protectionSpace.host == host && protectionSpace.realm == realm &&
protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort {
for c in credentials {
crendentials.append(c.value.toMap())
}
break
}
}
result(crendentials)
break
case "setHttpAuthCredential":
let host = arguments!["host"] as! String
let urlProtocol = arguments!["protocol"] as? String
let urlPort = arguments!["port"] as? Int ?? 0
var realm = arguments!["realm"] as? String;
if let r = realm, r.isEmpty {
realm = nil
}
let username = arguments!["username"] as! String
let password = arguments!["password"] as! String
let credential = URLCredential(user: username, password: password, persistence: .permanent)
CredentialDatabase.credentialStore.set(credential,
for: URLProtectionSpace(host: host, port: urlPort, protocol: urlProtocol,
realm: realm, authenticationMethod: NSURLAuthenticationMethodHTTPBasic))
result(true)
break
case "removeHttpAuthCredential":
let host = arguments!["host"] as! String
let urlProtocol = arguments!["protocol"] as? String
let urlPort = arguments!["port"] as? Int ?? 0
var realm = arguments!["realm"] as? String;
if let r = realm, r.isEmpty {
realm = nil
}
let username = arguments!["username"] as! String
let password = arguments!["password"] as! String
var credential: URLCredential? = nil;
var protectionSpaceCredential: URLProtectionSpace? = nil
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
if protectionSpace.host == host && protectionSpace.realm == realm &&
protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort {
for c in credentials {
if c.value.user == username, c.value.password == password {
credential = c.value
protectionSpaceCredential = protectionSpace
break
}
}
}
if credential != nil {
break
}
}
if let c = credential, let protectionSpace = protectionSpaceCredential {
CredentialDatabase.credentialStore.remove(c, for: protectionSpace)
}
result(true)
break
case "removeHttpAuthCredentials":
let host = arguments!["host"] as! String
let urlProtocol = arguments!["protocol"] as? String
let urlPort = arguments!["port"] as? Int ?? 0
var realm = arguments!["realm"] as? String;
if let r = realm, r.isEmpty {
realm = nil
}
var credentialsToRemove: [URLCredential] = [];
var protectionSpaceCredential: URLProtectionSpace? = nil
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
if protectionSpace.host == host && protectionSpace.realm == realm &&
protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort {
protectionSpaceCredential = protectionSpace
for c in credentials {
if let _ = c.value.user, let _ = c.value.password {
credentialsToRemove.append(c.value)
}
}
break
}
}
if let protectionSpace = protectionSpaceCredential {
for credential in credentialsToRemove {
CredentialDatabase.credentialStore.remove(credential, for: protectionSpace)
}
}
result(true)
break
case "clearAllAuthCredentials":
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
for credential in credentials {
CredentialDatabase.credentialStore.remove(credential.value, for: protectionSpace)
}
}
result(true)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,112 @@
//
// FindInteractionChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
import FlutterMacOS
public class FindInteractionChannelDelegate: ChannelDelegate {
private weak var findInteractionController: FindInteractionController?
public init(findInteractionController: FindInteractionController, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.findInteractionController = findInteractionController
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "findAll":
if let findInteractionController = findInteractionController {
let find = arguments!["find"] as! String
findInteractionController.findAll(find: find, completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "FindInteractionChannelDelegate", message: error?.localizedDescription, details: nil))
return
}
result(true)
})
} else {
result(false)
}
break
case "findNext":
if let findInteractionController = findInteractionController {
let forward = arguments!["forward"] as! Bool
findInteractionController.findNext(forward: forward, completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "FindInteractionChannelDelegate", message: error?.localizedDescription, details: nil))
return
}
result(true)
})
} else {
result(false)
}
break
case "clearMatches":
if let findInteractionController = findInteractionController {
findInteractionController.clearMatches(completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "FindInteractionChannelDelegate", message: error?.localizedDescription, details: nil))
return
}
result(true)
})
} else {
result(false)
}
break
case "setSearchText":
if let findInteractionController = findInteractionController {
let searchText = arguments!["searchText"] as? String
findInteractionController.searchText = searchText
result(true)
} else {
result(false)
}
break
case "getSearchText":
result(findInteractionController?.searchText)
break
case "getActiveFindSession":
if let findInteractionController = findInteractionController {
result(findInteractionController.activeFindSession?.toMap())
} else {
result(nil)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onFindResultReceived(activeMatchOrdinal: Int, numberOfMatches: Int, isDoneCounting: Bool) {
if isDoneCounting, let findInteractionController = findInteractionController {
findInteractionController.activeFindSession = FindSession(resultCount: numberOfMatches,
highlightedResultIndex: activeMatchOrdinal,
searchResultDisplayStyle: 2) // matches UIFindSession.SearchResultDisplayStyle.none
}
let arguments: [String : Any?] = [
"activeMatchOrdinal": activeMatchOrdinal,
"numberOfMatches": numberOfMatches,
"isDoneCounting": isDoneCounting
]
channel?.invokeMethod("onFindResultReceived", arguments: arguments)
}
public override func dispose() {
super.dispose()
findInteractionController = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,101 @@
//
// FindInteractionController.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
import FlutterMacOS
public class FindInteractionController: NSObject, Disposable {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_";
var plugin: InAppWebViewFlutterPlugin?
var webView: InAppWebView?
var channelDelegate: FindInteractionChannelDelegate?
var settings: FindInteractionSettings?
var shouldCallOnRefresh = false
var searchText: String?
var activeFindSession: FindSession?
public init(plugin: InAppWebViewFlutterPlugin, id: Any, webView: InAppWebView, settings: FindInteractionSettings?) {
super.init()
self.plugin = plugin
self.webView = webView
self.settings = settings
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id),
binaryMessenger: registrar.messenger)
self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel)
}
}
public func prepare() {
// if let settings = settings {
//
// }
}
public func findAll(find: String?, completionHandler: ((Any?, Error?) -> Void)?) {
guard let webView else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
var find = find
if find == nil {
find = searchText
} else {
// updated searchText
searchText = find
}
guard let find else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
if find != "" {
let startSearch = "window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync('\(find)');"
webView.evaluateJavaScript(startSearch, completionHandler: completionHandler)
}
}
public func findNext(forward: Bool, completionHandler: ((Any?, Error?) -> Void)?) {
guard let webView else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler)
}
public func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) {
guard let webView else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();", completionHandler: completionHandler)
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
webView = nil
activeFindSession = nil
plugin = nil
}
deinit {
debugPrint("FindInteractionControl - dealloc")
dispose()
}
}
@@ -0,0 +1,25 @@
//
// FindInteractionSettings.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
public class FindInteractionSettings: ISettings<FindInteractionController> {
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> FindInteractionSettings {
let _ = super.parse(settings: settings)
return self
}
override func getRealSettings(obj: FindInteractionController?) -> [String: Any?] {
let realSettings: [String: Any?] = toMap()
return realSettings
}
}
@@ -0,0 +1,29 @@
// Internal Gopeed host bridge. No extension-facing API.
import Foundation
import WebKit
import Network
import FlutterMacOS
final class GopeedProfiles {
private static var stores: [String: WKWebsiteDataStore] = [:]
static func store(_ identifier: String) -> WKWebsiteDataStore? { stores[identifier] }
static func prepare(arguments: NSDictionary?, result: @escaping FlutterResult) {
guard #available(macOS 14.0, iOS 17.0, *),
let identifier = arguments?["gopeedProfileId"] as? String,
let uuid = UUID(uuidString: identifier),
let proxyURL = arguments?["proxyUrl"] as? String,
let url = URL(string: proxyURL), url.scheme == "socks5",
let host = url.host, host == "127.0.0.1",
let port = url.port, port > 0 && port <= 65535 else {
result(FlutterError(code: "UNAVAILABLE", message: "Isolated WebView profiles and proxy require macOS 14 / iOS 17 or newer and valid host configuration", details: nil))
return
}
let store = stores[identifier] ?? WKWebsiteDataStore(forIdentifier: uuid)
let endpoint = nw_endpoint_create_host(host, String(port))
let proxy = nw_proxy_config_create_socksv5(endpoint)
store.__proxyConfigurations = [proxy]
stores[identifier] = store
result(true)
}
}
@@ -0,0 +1,100 @@
//
// HeadlessInAppWebView.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 26/03/21.
//
import Foundation
import FlutterMacOS
public class HeadlessInAppWebView: Disposable {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_headless_inappwebview_"
var id: String
var channelDelegate: HeadlessWebViewChannelDelegate?
var flutterWebView: FlutterWebViewController?
var plugin: InAppWebViewFlutterPlugin?
public init(plugin: InAppWebViewFlutterPlugin, id: String, flutterWebView: FlutterWebViewController) {
self.id = id
self.flutterWebView = flutterWebView
self.plugin = plugin
let channel = FlutterMethodChannel(name: HeadlessInAppWebView.METHOD_CHANNEL_NAME_PREFIX + id,
binaryMessenger: plugin.registrar!.messenger)
self.channelDelegate = HeadlessWebViewChannelDelegate(headlessWebView: self, channel: channel)
}
public func onWebViewCreated() {
channelDelegate?.onWebViewCreated();
}
public func prepare(params: NSDictionary) {
if let view = flutterWebView?.view() {
view.alphaValue = 0.01
let initialSize = params["initialSize"] as? [String: Any?]
if let size = Size2D.fromMap(map: initialSize) {
setSize(size: size)
} else {
view.frame = CGRect(x: 0.0, y: 0.0, width: NSApplication.shared.mainWindow?.contentView?.bounds.width ?? 0.0,
height: NSApplication.shared.mainWindow?.contentView?.bounds.height ?? 0.0)
}
/// Note: The WKWebView behaves very unreliable when rendering offscreen
/// on a device. This is especially true with JavaScript, which simply
/// won't be executed sometimes.
/// So, add the headless WKWebView to the view hierarchy.
/// This way is also possible to take screenshots.
let wrapperView = NSView() // wrapper view with frame zero
wrapperView.addSubview(view, positioned: .below, relativeTo: nil)
NSApplication.shared.mainWindow?.contentView?.addSubview(wrapperView, positioned: .below, relativeTo: nil)
}
}
public func setSize(size: Size2D) {
if let view = flutterWebView?.view() {
let width = size.width == -1.0 ? NSApplication.shared.mainWindow?.contentView?.bounds.width ?? 0.0 : CGFloat(size.width)
let height = size.height == -1.0 ? NSApplication.shared.mainWindow?.contentView?.bounds.height ?? 0.0 : CGFloat(size.height)
view.frame = CGRect(x: 0.0, y: 0.0, width: width, height: height)
}
}
public func getSize() -> Size2D? {
if let view = flutterWebView?.view() {
return Size2D(width: Double(view.frame.width), height: Double(view.frame.height))
}
return nil
}
public func disposeAndGetFlutterWebView(withFrame frame: CGRect) -> FlutterWebViewController? {
let newFlutterWebView = flutterWebView
if let view = flutterWebView?.view() {
// restore WebView frame and alpha
view.frame = frame
view.alphaValue = 1.0
// remove from parent
view.removeFromSuperview()
dispose(disposeWebView: false)
}
return newFlutterWebView
}
public func dispose(disposeWebView: Bool) {
channelDelegate?.dispose()
channelDelegate = nil
plugin?.headlessInAppWebViewManager?.webViews[id] = nil
if disposeWebView {
flutterWebView?.dispose(removeFromSuperview: true)
}
flutterWebView = nil
plugin = nil
}
public func dispose() {
dispose(disposeWebView: true)
}
deinit {
debugPrint("HeadlessInAppWebView - dealloc")
dispose()
}
}
@@ -0,0 +1,71 @@
//
// HeadlessInAppWebViewManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/2020.
//
import Foundation
import FlutterMacOS
import AppKit
import WebKit
import Foundation
import AVFoundation
public class HeadlessInAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview"
var plugin: InAppWebViewFlutterPlugin?
var webViews: [String: HeadlessInAppWebView?] = [:]
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
let id: String = arguments!["id"] as! String
switch call.method {
case "run":
let params = arguments!["params"] as! [String: Any?]
run(id: id, params: params)
result(true)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func run(id: String, params: [String: Any?]) {
guard let plugin = plugin else {
return
}
let flutterWebView = FlutterWebViewController(plugin: plugin,
withFrame: CGRect.zero,
viewIdentifier: id,
params: params as NSDictionary)
let headlessInAppWebView = HeadlessInAppWebView(plugin: plugin, id: id, flutterWebView: flutterWebView)
webViews[id] = headlessInAppWebView
headlessInAppWebView.prepare(params: params as NSDictionary)
headlessInAppWebView.onWebViewCreated()
flutterWebView.makeInitialLoad(params: params as NSDictionary)
}
public override func dispose() {
super.dispose()
let headlessWebViews = webViews.values
headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView?) in
headlessWebView?.dispose()
}
webViews.removeAll()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,64 @@
//
// HeadlessWebViewChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/05/22.
//
import Foundation
import FlutterMacOS
public class HeadlessWebViewChannelDelegate: ChannelDelegate {
private weak var headlessWebView: HeadlessInAppWebView?
public init(headlessWebView: HeadlessInAppWebView, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.headlessWebView = headlessWebView
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "dispose":
if let headlessWebView = headlessWebView {
headlessWebView.dispose()
result(true)
} else {
result(false)
}
break
case "setSize":
if let headlessWebView = headlessWebView {
let sizeMap = arguments!["size"] as? [String: Any?]
if let size = Size2D.fromMap(map: sizeMap) {
headlessWebView.setSize(size: size)
}
result(true)
} else {
result(false)
}
break
case "getSize":
result(headlessWebView?.getSize()?.toMap())
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onWebViewCreated() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onWebViewCreated", arguments: arguments)
}
public override func dispose() {
super.dispose()
headlessWebView = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,45 @@
//
// Options.swift
// flutter_inappwebview
//
// Created by Lorenzo on 26/09/18.
//
import Foundation
@objcMembers
public class ISettings<T>: NSObject {
override init(){
super.init()
}
func parse(settings: [String: Any?]) -> ISettings<T> {
for (key, value) in settings {
if !(value is NSNull), value != nil {
if self.responds(to: Selector(key)) {
self.setValue(value, forKey: key)
} else if self.responds(to: Selector("_" + key)) {
self.setValue(value, forKey: "_" + key)
}
}
}
return self
}
func toMap() -> [String: Any?] {
var settings: [String: Any?] = [:]
let mirrored_object = Mirror(reflecting: self)
for (_, attr) in mirrored_object.children.enumerated() {
if let property_name = attr.label as String? {
settings[property_name] = attr.value
}
}
return settings
}
func getRealSettings(obj: T?) -> [String: Any?] {
let realSettings: [String: Any?] = toMap()
return realSettings
}
}
@@ -0,0 +1,41 @@
//
// InAppBrowserChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/05/22.
//
import Foundation
import FlutterMacOS
public class InAppBrowserChannelDelegate: ChannelDelegate {
public override init(channel: FlutterMethodChannel) {
super.init(channel: channel)
}
public func onBrowserCreated() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onBrowserCreated", arguments: arguments)
}
public func onMenuItemClicked(menuItem: InAppBrowserMenuItem) {
let arguments: [String: Any?] = [
"id": menuItem.id
]
channel?.invokeMethod("onMenuItemClicked", arguments: arguments)
}
public func onMainWindowWillClose() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onMainWindowWillClose", arguments: arguments)
}
public func onExit() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onExit", arguments: arguments)
}
deinit {
dispose()
}
}
@@ -0,0 +1,17 @@
//
// InAppBrowserDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 14/02/21.
//
import Foundation
public protocol InAppBrowserDelegate {
func didChangeTitle(title: String?)
func didStartNavigation(url: URL?)
func didUpdateVisitedHistory(url: URL?)
func didFinishNavigation(url: URL?)
func didFailNavigation(url: URL?, error: Error)
func didChangeProgress(progress: Double)
}
@@ -0,0 +1,122 @@
//
// InAppBrowserManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 18/12/2019.
//
import FlutterMacOS
import AppKit
import WebKit
import Foundation
import AVFoundation
public class InAppBrowserManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappbrowser"
static let WEBVIEW_STORYBOARD = "WebView"
static let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController"
static let NAV_STORYBOARD_CONTROLLER_ID = "navController"
var plugin: InAppWebViewFlutterPlugin?
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "open":
open(arguments: arguments!)
result(true)
break
case "openWithSystemBrowser":
let url = arguments!["url"] as! String
openWithSystemBrowser(url: url, result: result)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func open(arguments: NSDictionary) {
let id = arguments["id"] as! String
let urlRequest = arguments["urlRequest"] as? [String:Any?]
let assetFilePath = arguments["assetFilePath"] as? String
let data = arguments["data"] as? String
let mimeType = arguments["mimeType"] as? String
let encoding = arguments["encoding"] as? String
let baseUrl = arguments["baseUrl"] as? String
let settings = arguments["settings"] as! [String: Any?]
let windowId = arguments["windowId"] as? Int64
let initialUserScripts = arguments["initialUserScripts"] as? [[String: Any]]
let menuItems = arguments["menuItems"] as! [[String: Any?]]
let browserSettings = InAppBrowserSettings()
let _ = browserSettings.parse(settings: settings)
let webViewSettings = InAppWebViewSettings()
let _ = webViewSettings.parse(settings: settings)
let webViewController = InAppBrowserWebViewController()
webViewController.plugin = plugin
webViewController.browserSettings = browserSettings
webViewController.webViewSettings = webViewSettings
webViewController.id = id
webViewController.initialUrlRequest = urlRequest != nil ? URLRequest.init(fromPluginMap: urlRequest!) : nil
webViewController.initialFile = assetFilePath
webViewController.initialData = data
webViewController.initialMimeType = mimeType
webViewController.initialEncoding = encoding
webViewController.initialBaseUrl = baseUrl
webViewController.windowId = windowId
webViewController.initialUserScripts = initialUserScripts ?? []
webViewController.isHidden = browserSettings.hidden
let window = InAppBrowserWindow(contentViewController: webViewController)
window.browserSettings = browserSettings
window.contentViewController = webViewController
for menuItem in menuItems {
window.menuItems.append(InAppBrowserMenuItem.fromMap(map: menuItem)!)
}
window.prepare()
if #available(macOS 10.12, *), browserSettings.windowType == .tabbed {
NSApplication.shared.mainWindow?.addTabbedWindow(window, ordered: .above)
} else if browserSettings.windowType == .child {
NSApplication.shared.mainWindow?.addChildWindow(window, ordered: .above)
} else {
window.windowController?.showWindow(self)
}
window.makeKeyAndOrderFront(self)
if browserSettings.hidden {
// https://github.com/pichillilorenzo/flutter_inappwebview/issues/1939
// without calling first window.makeKeyAndOrderFront(self)
// window.hide() would deallocate and dispose the InAppBrowserWindow
window.hide()
NSApplication.shared.mainWindow?.makeKeyAndOrderFront(self)
}
}
public func openWithSystemBrowser(url: String, result: @escaping FlutterResult) {
let absoluteUrl = URL(string: url)!.absoluteURL
if !NSWorkspace.shared.open(absoluteUrl) {
result(FlutterError(code: "InAppBrowserManager", message: url + " cannot be opened!", details: nil))
return
}
result(true)
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,91 @@
//
// InAppBrowserOptions.swift
// flutter_inappwebview
//
// Created by Lorenzo on 17/09/18.
//
import Foundation
@objcMembers
public class InAppBrowserSettings: ISettings<InAppBrowserWebViewController> {
var hidden = false
var hideToolbarTop = true
var toolbarTopBackgroundColor: String?
var hideUrlBar = false
var hideProgressBar = false
var toolbarTopFixedTitle: String?
var windowType = InAppBrowserWindowType.window
var windowAlphaValue = 1.0
var _windowStyleMask: NSNumber?
var windowStyleMask: NSWindow.StyleMask? {
get {
return _windowStyleMask != nil ?
NSWindow.StyleMask.init(rawValue: _windowStyleMask!.uintValue) :
nil
}
set {
if let newValue = newValue {
_windowStyleMask = NSNumber.init(value: newValue.rawValue)
} else {
_windowStyleMask = nil
}
}
}
var _windowTitlebarSeparatorStyle: NSNumber?
@available(macOS 11.0, *)
var windowTitlebarSeparatorStyle: NSTitlebarSeparatorStyle? {
get {
return _windowTitlebarSeparatorStyle != nil ?
NSTitlebarSeparatorStyle.init(rawValue: _windowTitlebarSeparatorStyle!.intValue) :
nil
}
set {
if let newValue = newValue {
_windowTitlebarSeparatorStyle = NSNumber.init(value: newValue.rawValue)
} else {
_windowTitlebarSeparatorStyle = nil
}
}
}
var windowFrame: NSRect?
var hideDefaultMenuItems = false
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> InAppBrowserSettings {
let _ = super.parse(settings: settings)
if let windowType = settings["windowType"] as? String {
self.windowType = InAppBrowserWindowType.init(rawValue: windowType) ?? InAppBrowserWindowType.child
}
if let windowFrame = settings["windowFrame"] as? [String:Double] {
self.windowFrame = NSRect(x: windowFrame["x"]!,
y: windowFrame["y"]!,
width: windowFrame["width"]!,
height: windowFrame["height"]!)
}
return self
}
override func getRealSettings(obj: InAppBrowserWebViewController?) -> [String: Any?] {
var realOptions: [String: Any?] = toMap()
realOptions["windowType"] = windowType.rawValue
if let inAppBrowserWebViewController = obj {
realOptions["hidden"] = inAppBrowserWebViewController.isHidden
realOptions["hideUrlBar"] = inAppBrowserWebViewController.window?.searchBar?.isHidden
realOptions["hideProgressBar"] = inAppBrowserWebViewController.progressBar.isHidden
realOptions["hideToolbarTop"] = !(inAppBrowserWebViewController.window?.toolbar?.isVisible ?? true)
realOptions["toolbarTopBackgroundColor"] = inAppBrowserWebViewController.window?.backgroundColor.hexString
realOptions["windowAlphaValue"] = inAppBrowserWebViewController.window?.alphaValue
realOptions["windowStyleMask"] = inAppBrowserWebViewController.window?.styleMask.rawValue
if #available(macOS 11.0, *) {
realOptions["windowTitlebarSeparatorStyle"] = inAppBrowserWebViewController.window?.titlebarSeparatorStyle.rawValue
}
realOptions["windowFrame"] = inAppBrowserWebViewController.window?.frame.toMap()
}
return realOptions
}
}
@@ -0,0 +1,339 @@
//
// InAppBrowserWebViewController.swift
// flutter_inappwebview
//
// Created by Lorenzo on 17/09/18.
//
import FlutterMacOS
import AppKit
import WebKit
import Foundation
public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelegate, Disposable {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_";
var progressBar: NSProgressIndicator!
var window: InAppBrowserWindow?
var id: String = ""
var plugin: InAppWebViewFlutterPlugin?
var windowId: Int64?
var webView: InAppWebView?
var channelDelegate: InAppBrowserChannelDelegate?
var initialUrlRequest: URLRequest?
var initialFile: String?
var browserSettings: InAppBrowserSettings?
var webViewSettings: InAppWebViewSettings?
var initialData: String?
var initialMimeType: String?
var initialEncoding: String?
var initialBaseUrl: String?
var initialUserScripts: [[String: Any]] = []
var isHidden = false
public override func loadView() {
guard let plugin = plugin, let registrar = plugin.registrar else {
return
}
let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: registrar.messenger)
channelDelegate = InAppBrowserChannelDelegate(channel: channel)
var userScripts: [UserScript] = []
for initialUserScript in initialUserScripts {
userScripts.append(UserScript.fromMap(map: initialUserScript, windowId: windowId)!)
}
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: webViewSettings)
if let wId = windowId, let webViewTransport = plugin.inAppWebViewManager?.windowWebViews[wId] {
webView = webViewTransport.webView
webView!.initialUserScripts = userScripts
} else {
webView = InAppWebView(id: nil,
plugin: nil,
frame: .zero,
configuration: preWebviewConfiguration,
userScripts: userScripts)
}
guard let webView = webView else {
return
}
webView.inAppBrowserDelegate = self
webView.id = id
webView.plugin = plugin
webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel)
let findInteractionController = FindInteractionController(
plugin: plugin,
id: id, webView: webView, settings: nil)
webView.findInteractionController = findInteractionController
findInteractionController.prepare()
prepareWebView()
webView.windowCreated = true
progressBar = NSProgressIndicator()
progressBar.style = .bar
progressBar.isIndeterminate = false
progressBar.startAnimation(self)
view = NSView(frame: NSApplication.shared.mainWindow?.frame ?? .zero)
view.addSubview(webView)
view.addSubview(progressBar, positioned: .above, relativeTo: webView)
}
public override func viewDidLoad() {
super.viewDidLoad()
webView?.translatesAutoresizingMaskIntoConstraints = false
progressBar.translatesAutoresizingMaskIntoConstraints = false
webView?.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0).isActive = true
webView?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0).isActive = true
webView?.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
webView?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
progressBar.topAnchor.constraint(equalTo: self.view.topAnchor, constant: -6.0).isActive = true
progressBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
progressBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
if let wId = windowId {
channelDelegate?.onBrowserCreated()
webView?.runWindowBeforeCreatedCallbacks()
} else {
if #available(macOS 10.13, *) {
if let contentBlockers = webView?.settings?.contentBlockers, contentBlockers.count > 0 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
let blockRules = String(data: jsonData, encoding: .utf8)
WKContentRuleListStore.default().compileContentRuleList(
forIdentifier: "ContentBlockingRules",
encodedContentRuleList: blockRules) { (contentRuleList, error) in
if let error = error {
print(error.localizedDescription)
return
}
let configuration = self.webView!.configuration
configuration.userContentController.add(contentRuleList!)
self.initLoad()
}
return
} catch {
print(error.localizedDescription)
}
}
}
initLoad()
}
}
public override func viewDidAppear() {
super.viewDidAppear()
window = view.window as? InAppBrowserWindow
}
public func initLoad() {
if let initialFile = initialFile {
do {
try webView?.loadFile(assetFilePath: initialFile)
}
catch let error as NSError {
dump(error)
}
}
else if let initialData = initialData {
let baseUrl = URL(string: initialBaseUrl ?? "about:blank")!
var allowingReadAccessToURL: URL? = nil
if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, baseUrl.scheme == "file" {
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
if allowingReadAccessToURL?.scheme != "file" {
allowingReadAccessToURL = nil
}
}
webView?.loadData(data: initialData, mimeType: initialMimeType!, encoding: initialEncoding!, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL)
}
else if let initialUrlRequest = initialUrlRequest {
var allowingReadAccessToURL: URL? = nil
if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, let url = initialUrlRequest.url, url.scheme == "file" {
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
if allowingReadAccessToURL?.scheme != "file" {
allowingReadAccessToURL = nil
}
}
webView?.loadUrl(urlRequest: initialUrlRequest, allowingReadAccessTo: allowingReadAccessToURL)
}
channelDelegate?.onBrowserCreated()
}
public func prepareWebView() {
webView?.settings = webViewSettings
webView?.prepare()
if let browserSettings = browserSettings {
if browserSettings.hideProgressBar {
progressBar.isHidden = true
}
}
}
public func didChangeTitle(title: String?) {
guard let title = title else {
return
}
if let browserSettings = browserSettings,
let toolbarTopFixedTitle = browserSettings.toolbarTopFixedTitle {
window?.title = toolbarTopFixedTitle
} else {
window?.title = title
}
window?.update()
}
public func didStartNavigation(url: URL?) {
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
window?.backButton?.isEnabled = webView?.canGoBack ?? false
progressBar.doubleValue = 0.0
progressBar.isHidden = false
guard let url = url else {
return
}
window?.searchBar?.stringValue = url.absoluteString
}
public func didUpdateVisitedHistory(url: URL?) {
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
window?.backButton?.isEnabled = webView?.canGoBack ?? false
guard let url = url else {
return
}
window?.searchBar?.stringValue = url.absoluteString
}
public func didFinishNavigation(url: URL?) {
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
window?.backButton?.isEnabled = webView?.canGoBack ?? false
progressBar.doubleValue = 0.0
progressBar.isHidden = true
guard let url = url else {
return
}
window?.searchBar?.stringValue = url.absoluteString
}
public func didFailNavigation(url: URL?, error: Error) {
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
window?.backButton?.isEnabled = webView?.canGoBack ?? false
progressBar.doubleValue = 0.0
progressBar.isHidden = true
}
public func didChangeProgress(progress: Double) {
progressBar.isHidden = false
progressBar.doubleValue = progress * 100
if progress == 100.0 {
progressBar.isHidden = true
}
}
@objc public func reload() {
webView?.reload()
didUpdateVisitedHistory(url: webView?.url)
}
@objc public func goBack() {
if let webView = webView, webView.canGoBack {
webView.goBack()
}
}
@objc public func goForward() {
if let webView = webView, webView.canGoForward {
webView.goForward()
}
}
@objc public func goBackOrForward(steps: Int) {
webView?.goBackOrForward(steps: steps)
}
@objc public func onMenuItemClicked(sender: Any?) {
var identifier: String?
if let sender = sender as? NSMenuItem {
identifier = sender.identifier?.rawValue
}
if identifier == nil, let sender = sender as? NSButton {
identifier = sender.identifier?.rawValue
}
if let identifier = identifier, let window = window {
let menuItem = window.menuItems.first { item in
return item.id == Int64(identifier)
}
if let menuItem = menuItem {
channelDelegate?.onMenuItemClicked(menuItem: menuItem)
}
}
}
public func setSettings(newSettings: InAppBrowserSettings, newSettingsMap: [String: Any]) {
window?.setSettings(newSettings: newSettings, newSettingsMap: newSettingsMap)
let newInAppWebViewSettings = InAppWebViewSettings()
let _ = newInAppWebViewSettings.parse(settings: newSettingsMap)
webView?.setSettings(newSettings: newInAppWebViewSettings, newSettingsMap: newSettingsMap)
if newSettingsMap["hideProgressBar"] != nil, browserSettings?.hideProgressBar != newSettings.hideProgressBar {
progressBar.isHidden = newSettings.hideProgressBar
}
browserSettings = newSettings
webViewSettings = newInAppWebViewSettings
}
public func getSettings() -> [String: Any?]? {
let webViewSettingsMap = webView?.getSettings()
if (self.browserSettings == nil || webViewSettingsMap == nil) {
return nil
}
var settingsMap = self.browserSettings!.getRealSettings(obj: self)
settingsMap.merge(webViewSettingsMap!, uniquingKeysWith: { (current, _) in current })
return settingsMap
}
public func hide() {
isHidden = true
window?.hide()
}
public func show() {
isHidden = false
window?.show()
}
public func close() {
window?.close()
}
public func dispose() {
channelDelegate?.onExit()
channelDelegate?.dispose()
channelDelegate = nil
webView?.dispose()
webView?.removeFromSuperview()
webView = nil
window = nil
plugin = nil
}
deinit {
debugPrint("InAppBrowserWebViewController - dealloc")
dispose()
}
}
@@ -0,0 +1,387 @@
//
// InAppBrowserNavigationController.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 14/02/21.
//
import Foundation
struct ToolbarIdentifiers {
static let searchBar = NSToolbarItem.Identifier(rawValue: "SearchBar")
static let backButton = NSToolbarItem.Identifier(rawValue: "BackButton")
static let forwardButton = NSToolbarItem.Identifier(rawValue: "ForwardButton")
static let reloadButton = NSToolbarItem.Identifier(rawValue: "ReloadButton")
static let menuButton = NSToolbarItem.Identifier(rawValue: "MenuButton")
}
public class InAppBrowserWindow: NSWindow, NSWindowDelegate, NSToolbarDelegate, NSSearchFieldDelegate {
var searchItem: NSToolbarItem?
var backItem: NSToolbarItem?
var forwardItem: NSToolbarItem?
var reloadItem: NSToolbarItem?
var menuItem: NSToolbarItem?
var actionItems: [NSToolbarItem] = []
var reloadButton: NSButton? {
get {
return reloadItem?.view as? NSButton
}
}
var backButton: NSButton? {
get {
return backItem?.view as? NSButton
}
}
var forwardButton: NSButton? {
get {
return forwardItem?.view as? NSButton
}
}
var searchBar: NSSearchField? {
get {
if #available(macOS 11.0, *), let searchItem = searchItem as? NSSearchToolbarItem {
return searchItem.searchField
} else {
return searchItem?.view as? NSSearchField
}
}
}
var menuButton: NSPopUpButton? {
get {
return menuItem?.view as? NSPopUpButton
}
}
var browserSettings: InAppBrowserSettings?
var menuItems: [InAppBrowserMenuItem] = []
public func prepare() {
title = ""
collectionBehavior = .fullScreenPrimary
delegate = self
NotificationCenter.default.addObserver(self,
selector: #selector(onMainWindowWillClose(_:)),
name: NSWindow.willCloseNotification,
object: NSApplication.shared.mainWindow)
if #available(macOS 10.13, *) {
let windowToolbar = NSToolbar()
windowToolbar.delegate = self
if #available(macOS 11.0, *) {
searchItem = NSSearchToolbarItem(itemIdentifier: ToolbarIdentifiers.searchBar)
(searchItem as! NSSearchToolbarItem).searchField.delegate = self
toolbarStyle = .expanded
} else {
searchItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.searchBar)
let textField = NSSearchField()
textField.usesSingleLineMode = true
textField.delegate = self
searchItem?.view = textField
}
searchItem?.label = ""
windowToolbar.displayMode = .default
backItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.backButton)
backItem?.label = ""
if let webViewController = contentViewController as? InAppBrowserWebViewController {
if #available(macOS 11.0, *) {
backItem?.view = NSButton(image: NSImage(systemSymbolName: "chevron.left",
accessibilityDescription: "Go Back")!,
target: webViewController,
action: #selector(InAppBrowserWebViewController.goBack))
} else {
backItem?.view = NSButton(title: "\u{2039}",
target: webViewController,
action: #selector(InAppBrowserWebViewController.goBack))
}
}
forwardItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.forwardButton)
forwardItem?.label = ""
if let webViewController = contentViewController as? InAppBrowserWebViewController {
if #available(macOS 11.0, *) {
forwardItem?.view = NSButton(image: NSImage(systemSymbolName: "chevron.right",
accessibilityDescription: "Go Forward")!,
target: webViewController,
action: #selector(InAppBrowserWebViewController.goForward))
} else {
forwardItem?.view = NSButton(title: "\u{203A}",
target: webViewController,
action: #selector(InAppBrowserWebViewController.goForward))
}
}
reloadItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.reloadButton)
reloadItem?.label = ""
if let webViewController = contentViewController as? InAppBrowserWebViewController {
if #available(macOS 11.0, *) {
reloadItem?.view = NSButton(image: NSImage(systemSymbolName: "arrow.counterclockwise",
accessibilityDescription: "Reload")!,
target: webViewController,
action: #selector(InAppBrowserWebViewController.reload))
} else {
reloadItem?.view = NSButton(title: "Reload",
target: webViewController,
action: #selector(InAppBrowserWebViewController.reload))
}
}
if #available(macOS 10.15, *), !menuItems.isEmpty {
menuItem = NSMenuToolbarItem(itemIdentifier: ToolbarIdentifiers.menuButton)
if let menuItem = menuItem as? NSMenuToolbarItem {
menuItem.label = ""
if #available(macOS 11.0, *) {
menuItem.image = NSImage(systemSymbolName: "ellipsis.circle",
accessibilityDescription: "Options")
menuItem.showsIndicator = true
menuItem.isBordered = true
} else {
menuItem.title = "Options"
}
let menu = NSMenu()
menuItems = menuItems.sorted(by: {$0.order ?? 0 < $1.order ?? 0})
for item in menuItems {
if !item.showAsAction {
let nsItem = NSMenuItem(title: item.title, action: #selector(InAppBrowserWebViewController.onMenuItemClicked), keyEquivalent: "")
nsItem.identifier = NSUserInterfaceItemIdentifier.init(String(item.id))
nsItem.image = item.icon
menu.addItem(nsItem)
} else {
let actionItem = NSMenuToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: String(item.id)))
actionItem.label = ""
if let webViewController = contentViewController as? InAppBrowserWebViewController {
let actionButton = NSButton(title: item.title,
target: webViewController,
action: #selector(InAppBrowserWebViewController.onMenuItemClicked))
actionButton.identifier = NSUserInterfaceItemIdentifier.init(String(item.id))
actionButton.image = item.icon
actionItem.view = actionButton
}
actionItems.append(actionItem)
}
}
menuItem.menu = menu
}
}
if #available(macOS 10.14, *) {
windowToolbar.centeredItemIdentifier = ToolbarIdentifiers.searchBar
}
toolbar = windowToolbar
}
forwardButton?.isEnabled = false
backButton?.isEnabled = false
if let browserSettings = browserSettings {
if let toolbarTopFixedTitle = browserSettings.toolbarTopFixedTitle {
title = toolbarTopFixedTitle
}
if !browserSettings.hideToolbarTop {
toolbar?.isVisible = true
if browserSettings.hideUrlBar {
if #available(macOS 11.0, *) {
(searchItem as! NSSearchToolbarItem).searchField.isHidden = true
} else {
searchItem?.view?.isHidden = true
}
}
if let bgColor = browserSettings.toolbarTopBackgroundColor, !bgColor.isEmpty {
backgroundColor = NSColor(hexString: bgColor)
}
}
else {
toolbar?.isVisible = false
}
if #available(macOS 11.0, *), let windowTitlebarSeparatorStyle = browserSettings.windowTitlebarSeparatorStyle {
titlebarSeparatorStyle = windowTitlebarSeparatorStyle
}
alphaValue = browserSettings.windowAlphaValue
if let windowStyleMask = browserSettings.windowStyleMask {
styleMask = windowStyleMask
}
if let windowFrame = browserSettings.windowFrame {
setFrame(windowFrame, display: true)
}
reloadItem?.view?.isHidden = browserSettings.hideDefaultMenuItems
backItem?.view?.isHidden = browserSettings.hideDefaultMenuItems
forwardItem?.view?.isHidden = browserSettings.hideDefaultMenuItems
}
}
public func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [[ ToolbarIdentifiers.menuButton,
ToolbarIdentifiers.searchBar,
ToolbarIdentifiers.backButton,
ToolbarIdentifiers.forwardButton,
ToolbarIdentifiers.reloadButton,
.flexibleSpace ], actionItems.compactMap({ item in
return item.itemIdentifier
})].flatMap { $0 }
}
public func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [[.flexibleSpace,
ToolbarIdentifiers.searchBar,
.flexibleSpace,
ToolbarIdentifiers.reloadButton,
ToolbarIdentifiers.backButton,
ToolbarIdentifiers.forwardButton],
actionItems.compactMap({ item in
return item.itemIdentifier
}),
[ToolbarIdentifiers.menuButton]].flatMap { $0 }
}
public func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
switch(itemIdentifier) {
case ToolbarIdentifiers.searchBar:
return searchItem
case ToolbarIdentifiers.backButton:
return backItem
case ToolbarIdentifiers.forwardButton:
return forwardItem
case ToolbarIdentifiers.reloadButton:
return reloadItem
case ToolbarIdentifiers.menuButton:
return menuItem
default:
let actionItem = actionItems.first { item in
return item.itemIdentifier == itemIdentifier
}
return actionItem
}
}
public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
// ENTER key
var searchField: NSSearchField? = nil
if #available(macOS 11.0, *), let searchBar = searchItem as? NSSearchToolbarItem {
searchField = searchBar.searchField
} else if let searchBar = searchItem {
searchField = searchBar.view as? NSSearchField
}
guard let searchField,
let urlEncoded = searchField.stringValue.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: urlEncoded) else {
return false
}
let request = URLRequest(url: url)
(contentViewController as? InAppBrowserWebViewController)?.webView?.load(request)
return true
}
return false
}
public func hide() {
orderOut(self)
}
public func show() {
let mainWindow = parent ?? NSApplication.shared.mainWindow
if #available(macOS 10.12, *),
!(mainWindow?.tabbedWindows?.contains(self) ?? false),
browserSettings?.windowType == .tabbed {
mainWindow?.addTabbedWindow(self, ordered: .above)
} else if !(mainWindow?.childWindows?.contains(self) ?? false) {
mainWindow?.addChildWindow(self, ordered: .above)
}
makeKeyAndOrderFront(self)
NSApplication.shared.activate(ignoringOtherApps: true)
}
public func setSettings(newSettings: InAppBrowserSettings, newSettingsMap: [String: Any]) {
if newSettingsMap["hidden"] != nil, browserSettings?.hidden != newSettings.hidden {
if newSettings.hidden {
hide()
}
else {
show()
}
}
if newSettingsMap["hideUrlBar"] != nil, browserSettings?.hideUrlBar != newSettings.hideUrlBar {
searchBar?.isHidden = newSettings.hideUrlBar
}
if newSettingsMap["hideToolbarTop"] != nil, browserSettings?.hideToolbarTop != newSettings.hideToolbarTop {
toolbar?.isVisible = !newSettings.hideToolbarTop
}
if newSettingsMap["toolbarTopBackgroundColor"] != nil, browserSettings?.toolbarTopBackgroundColor != newSettings.toolbarTopBackgroundColor {
if let bgColor = newSettings.toolbarTopBackgroundColor, !bgColor.isEmpty {
backgroundColor = NSColor(hexString: bgColor)
} else {
backgroundColor = nil
}
}
if #available(macOS 11.0, *), newSettingsMap["windowTitlebarSeparatorStyle"] != nil,
browserSettings?.windowTitlebarSeparatorStyle != newSettings.windowTitlebarSeparatorStyle {
titlebarSeparatorStyle = newSettings.windowTitlebarSeparatorStyle!
}
if newSettingsMap["windowAlphaValue"] != nil, browserSettings?.windowAlphaValue != newSettings.windowAlphaValue {
alphaValue = newSettings.windowAlphaValue
}
if newSettingsMap["windowStyleMask"] != nil, browserSettings?.windowStyleMask != newSettings.windowStyleMask {
styleMask = newSettings.windowStyleMask!
}
if newSettingsMap["windowFrame"] != nil, browserSettings?.windowFrame != newSettings.windowFrame {
setFrame(newSettings.windowFrame!, display: true)
}
if newSettingsMap["hideDefaultMenuItems"] != nil, browserSettings?.hideDefaultMenuItems != newSettings.hideDefaultMenuItems {
reloadItem?.view?.isHidden = newSettings.hideDefaultMenuItems
backItem?.view?.isHidden = newSettings.hideDefaultMenuItems
forwardItem?.view?.isHidden = newSettings.hideDefaultMenuItems
}
browserSettings = newSettings
}
public func windowWillClose(_ notification: Notification) {
dispose()
}
@objc func onMainWindowWillClose(_ notification: Notification) {
if let webViewController = contentViewController as? InAppBrowserWebViewController {
webViewController.channelDelegate?.onMainWindowWillClose()
}
}
public func dispose() {
delegate = nil
NotificationCenter.default.removeObserver(self,
name: NSWindow.willCloseNotification,
object: NSApplication.shared.mainWindow)
if let webViewController = contentViewController as? InAppBrowserWebViewController {
webViewController.dispose()
}
if #available(macOS 11.0, *) {
(searchItem as? NSSearchToolbarItem)?.searchField.delegate = nil
} else {
(searchItem?.view as? NSTextField)?.delegate = nil
searchItem?.view = nil
}
searchItem = nil
(backItem?.view as? NSButton)?.target = nil
backItem?.view = nil
backItem = nil
(forwardItem?.view as? NSButton)?.target = nil
forwardItem?.view = nil
forwardItem = nil
(reloadItem?.view as? NSButton)?.target = nil
reloadItem?.view = nil
reloadItem = nil
}
deinit {
debugPrint("InAppBrowserWindow - dealloc")
dispose()
}
}
@@ -0,0 +1,17 @@
//
// ContextMenuOptions.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 30/05/2020.
//
import Foundation
public class ContextMenuSettings: ISettings<NSObject> {
var hideDefaultSystemContextMenuItems = false;
override init(){
super.init()
}
}
@@ -0,0 +1,45 @@
//
// CustomSchemeHandler.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 25/10/2019.
//
import FlutterMacOS
import Foundation
import WebKit
@available(macOS 10.13, *)
public class CustomSchemeHandler: NSObject, WKURLSchemeHandler {
var schemeHandlers: [Int: WKURLSchemeTask] = [:]
public func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
schemeHandlers[urlSchemeTask.hash] = urlSchemeTask
let inAppWebView = webView as! InAppWebView
let request = WebResourceRequest.init(fromURLRequest: urlSchemeTask.request)
let callback = WebViewChannelDelegate.LoadResourceWithCustomSchemeCallback()
callback.nonNullSuccess = { (response: CustomSchemeResponse) in
if (self.schemeHandlers[urlSchemeTask.hash] != nil) {
let urlResponse = URLResponse(url: request.url, mimeType: response.contentType, expectedContentLength: -1, textEncodingName: response.contentEncoding)
urlSchemeTask.didReceive(urlResponse)
urlSchemeTask.didReceive(response.data)
urlSchemeTask.didFinish()
self.schemeHandlers.removeValue(forKey: urlSchemeTask.hash)
}
return false
}
callback.error = { (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? ""))
}
if let channelDelegate = inAppWebView.channelDelegate {
channelDelegate.onLoadResourceWithCustomScheme(request: request, callback: callback)
} else {
callback.defaultBehaviour(nil)
}
}
public func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
schemeHandlers.removeValue(forKey: urlSchemeTask.hash)
}
}
@@ -0,0 +1,202 @@
//
// FlutterWebViewController.swift
// flutter_inappwebview
//
// Created by Lorenzo on 13/11/18.
//
import Foundation
import WebKit
import FlutterMacOS
public class FlutterWebViewController: NSView, Disposable {
var keepAliveId: String?
init(plugin: InAppWebViewFlutterPlugin, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) {
super.init(frame: frame)
keepAliveId = params["keepAliveId"] as? String
let initialSettings = params["initialSettings"] as! [String: Any?]
let windowId = params["windowId"] as? Int64
let initialUserScripts = params["initialUserScripts"] as? [[String: Any]]
var userScripts: [UserScript] = []
if let initialUserScripts = initialUserScripts {
for initialUserScript in initialUserScripts {
userScripts.append(UserScript.fromMap(map: initialUserScript, windowId: windowId)!)
}
}
let settings = InAppWebViewSettings()
let _ = settings.parse(settings: initialSettings)
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: settings)
var webView: InAppWebView?
if let wId = windowId, let webViewTransport = plugin.inAppWebViewManager?.windowWebViews[wId] {
webView = webViewTransport.webView
webView!.id = viewId
webView!.plugin = plugin
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId),
binaryMessenger: registrar.messenger)
webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel)
}
webView!.frame = self.bounds
webView!.initialUserScripts = userScripts
} else {
webView = InAppWebView(id: viewId,
plugin: plugin,
frame: self.bounds,
configuration: preWebviewConfiguration,
userScripts: userScripts)
}
let findInteractionController = FindInteractionController(
plugin: plugin,
id: viewId, webView: webView!, settings: nil)
webView!.findInteractionController = findInteractionController
findInteractionController.prepare()
webView!.autoresizingMask = [.width, .height]
self.autoresizesSubviews = true
self.autoresizingMask = [.width, .height]
self.addSubview(webView!)
webView!.settings = settings
webView!.prepare()
webView!.windowCreated = true
}
required init?(coder nsCoder: NSCoder) {
super.init(coder: nsCoder)
}
public func webView() -> InAppWebView? {
for subview in self.subviews
{
if let item = subview as? InAppWebView
{
return item
}
}
return nil
}
public func view() -> NSView {
return self
}
public func makeInitialLoad(params: NSDictionary) {
guard let webView = webView() else {
return
}
let windowId = params["windowId"] as? Int64
let initialUrlRequest = params["initialUrlRequest"] as? [String: Any?]
let initialFile = params["initialFile"] as? String
let initialData = params["initialData"] as? [String: String?]
if windowId == nil {
if #available(macOS 10.13, *) {
webView.configuration.userContentController.removeAllContentRuleLists()
if let contentBlockers = webView.settings?.contentBlockers, contentBlockers.count > 0 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
let blockRules = String(data: jsonData, encoding: .utf8)
WKContentRuleListStore.default().compileContentRuleList(
forIdentifier: "ContentBlockingRules",
encodedContentRuleList: blockRules) { (contentRuleList, error) in
if let error = error {
print(error.localizedDescription)
return
}
let configuration = webView.configuration
configuration.userContentController.add(contentRuleList!)
self.load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData)
}
return
} catch {
print(error.localizedDescription)
}
}
}
load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData)
}
else if let wId = windowId {
webView.runWindowBeforeCreatedCallbacks()
}
}
func load(initialUrlRequest: [String:Any?]?, initialFile: String?, initialData: [String: String?]?) {
guard let webView = webView() else {
return
}
if let initialFile = initialFile {
do {
try webView.loadFile(assetFilePath: initialFile)
}
catch let error as NSError {
dump(error)
}
}
else if let initialData = initialData, let data = initialData["data"]!,
let mimeType = initialData["mimeType"]!, let encoding = initialData["encoding"]!,
let baseUrl = URL(string: initialData["baseUrl"]! ?? "about:blank") {
var allowingReadAccessToURL: URL? = nil
if let allowingReadAccessTo = webView.settings?.allowingReadAccessTo, baseUrl.scheme == "file" {
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
if allowingReadAccessToURL?.scheme != "file" {
allowingReadAccessToURL = nil
}
}
webView.loadData(data: data,
mimeType: mimeType,
encoding: encoding,
baseUrl: baseUrl,
allowingReadAccessTo: allowingReadAccessToURL)
}
else if let initialUrlRequest = initialUrlRequest {
let urlRequest = URLRequest.init(fromPluginMap: initialUrlRequest)
var allowingReadAccessToURL: URL? = nil
if let allowingReadAccessTo = webView.settings?.allowingReadAccessTo, let url = urlRequest.url, url.scheme == "file" {
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
if allowingReadAccessToURL?.scheme != "file" {
allowingReadAccessToURL = nil
}
}
webView.loadUrl(urlRequest: urlRequest, allowingReadAccessTo: allowingReadAccessToURL)
}
}
// method added to fix:
// https://github.com/pichillilorenzo/flutter_inappwebview/issues/1837
public func dispose(removeFromSuperview: Bool) {
if keepAliveId == nil {
if let webView = webView() {
webView.dispose()
if removeFromSuperview {
webView.removeFromSuperview()
}
}
if removeFromSuperview {
self.removeFromSuperview()
}
}
}
public func dispose() {
dispose(removeFromSuperview: false)
}
deinit {
debugPrint("FlutterWebViewController - dealloc")
dispose()
}
}
@@ -0,0 +1,72 @@
//
// FlutterWebViewFactory.swift
// flutter_inappwebview
//
// Created by Lorenzo on 13/11/18.
//
import SwiftUI
import Cocoa
import FlutterMacOS
import Foundation
public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
static let VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview"
private var plugin: InAppWebViewFlutterPlugin
init(plugin: InAppWebViewFlutterPlugin) {
self.plugin = plugin
super.init()
}
public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? {
return FlutterStandardMessageCodec.sharedInstance()
}
public func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView {
let arguments = args as? NSDictionary
var flutterWebView: FlutterWebViewController?
var id: Any = viewId
let keepAliveId = arguments?["keepAliveId"] as? String
let headlessWebViewId = arguments?["headlessWebViewId"] as? String
if let headlessWebViewId = headlessWebViewId,
let headlessWebView = plugin.headlessInAppWebViewManager?.webViews[headlessWebViewId],
let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: .zero) {
flutterWebView = platformView
flutterWebView?.keepAliveId = keepAliveId
}
if let keepAliveId = keepAliveId,
flutterWebView == nil,
let keepAliveWebView = plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] {
flutterWebView = keepAliveWebView
if let view = flutterWebView?.view() {
// remove from parent
view.removeFromSuperview()
}
}
let shouldMakeInitialLoad = flutterWebView == nil
if flutterWebView == nil {
if let keepAliveId = keepAliveId {
id = keepAliveId
}
flutterWebView = FlutterWebViewController(plugin: plugin,
withFrame: .zero,
viewIdentifier: id,
params: arguments!)
}
if let keepAliveId = keepAliveId {
plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] = flutterWebView!
}
if shouldMakeInitialLoad {
flutterWebView?.makeInitialLoad(params: arguments!)
}
return flutterWebView!
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,124 @@
//
// InAppWebViewManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 08/12/2019.
//
import Foundation
import WebKit
import FlutterMacOS
public class InAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"
var plugin: InAppWebViewFlutterPlugin?
var webViewForUserAgent: WKWebView?
var defaultUserAgent: String?
var keepAliveWebViews: [String:FlutterWebViewController?] = [:]
var windowWebViews: [Int64:WebViewTransport] = [:]
var windowAutoincrementId: Int64 = 0
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getDefaultUserAgent":
getDefaultUserAgent(completionHandler: { (value) in
result(value)
})
break
case "handlesURLScheme":
let urlScheme = arguments!["urlScheme"] as! String
if #available(macOS 10.13, *) {
result(WKWebView.handlesURLScheme(urlScheme))
} else {
result(false)
}
break
case "disposeKeepAlive":
let keepAliveId = arguments!["keepAliveId"] as! String
disposeKeepAlive(keepAliveId: keepAliveId)
result(true)
break
case "clearAllCache":
let includeDiskFiles = arguments!["includeDiskFiles"] as! Bool
clearAllCache(includeDiskFiles: includeDiskFiles, completionHandler: {
result(true)
})
default:
result(FlutterMethodNotImplemented)
break
}
}
public func getDefaultUserAgent(completionHandler: @escaping (_ value: String?) -> Void) {
if defaultUserAgent == nil {
webViewForUserAgent = WKWebView()
webViewForUserAgent?.evaluateJavaScript("navigator.userAgent") { (value, error) in
if error != nil {
print("Error occurred to get userAgent")
self.webViewForUserAgent = nil
completionHandler(nil)
return
}
if let unwrappedUserAgent = value as? String {
self.defaultUserAgent = unwrappedUserAgent
completionHandler(self.defaultUserAgent)
} else {
print("Failed to get userAgent")
}
self.webViewForUserAgent = nil
}
} else {
completionHandler(defaultUserAgent)
}
}
public func disposeKeepAlive(keepAliveId: String) {
if let flutterWebView = keepAliveWebViews[keepAliveId] as? FlutterWebViewController {
flutterWebView.keepAliveId = nil
flutterWebView.dispose(removeFromSuperview: true)
keepAliveWebViews[keepAliveId] = nil
}
}
public func clearAllCache(includeDiskFiles: Bool, completionHandler: @escaping () -> Void) {
var websiteDataTypes = Set([WKWebsiteDataTypeMemoryCache])
if includeDiskFiles {
websiteDataTypes.insert(WKWebsiteDataTypeDiskCache)
if #available(macOS 10.13.4, *) {
websiteDataTypes.insert(WKWebsiteDataTypeFetchCache)
}
websiteDataTypes.insert(WKWebsiteDataTypeOfflineWebApplicationCache)
}
let date = NSDate(timeIntervalSince1970: 0)
WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes, modifiedSince: date as Date, completionHandler: completionHandler)
}
public override func dispose() {
super.dispose()
let keepAliveWebViewValues = keepAliveWebViews.values
keepAliveWebViewValues.forEach {(keepAliveWebView: FlutterWebViewController?) in
if let keepAliveId = keepAliveWebView?.keepAliveId {
disposeKeepAlive(keepAliveId: keepAliveId)
}
}
keepAliveWebViews.removeAll()
windowWebViews.removeAll()
webViewForUserAgent = nil
defaultUserAgent = nil
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,118 @@
//
// InAppWebViewSettings.swift
// flutter_inappwebview
//
// Created by Lorenzo on 21/10/18.
//
import Foundation
import WebKit
@objcMembers
public class InAppWebViewSettings: ISettings<InAppWebView> {
var gopeedProfileId = ""
var useShouldOverrideUrlLoading = false
var useOnLoadResource = false
var useOnDownloadStart = false
@available(*, deprecated, message: "Use InAppWebViewManager.clearAllCache instead.")
var clearCache = false
var userAgent = ""
var applicationNameForUserAgent = ""
var javaScriptEnabled = true
var javaScriptCanOpenWindowsAutomatically = false
var mediaPlaybackRequiresUserGesture = true
var resourceCustomSchemes: [String] = []
var contentBlockers: [[String: [String : Any]]] = []
var minimumFontSize = 0
var useShouldInterceptAjaxRequest = false
var interceptOnlyAsyncAjaxRequests = true
var useShouldInterceptFetchRequest = false
var incognito = false
var cacheEnabled = true
var transparentBackground = false
var supportZoom = true
var allowUniversalAccessFromFileURLs = false
var allowFileAccessFromFileURLs = false
var enableViewportScale = false
var suppressesIncrementalRendering = false
var allowsAirPlayForMediaPlayback = true
var allowsBackForwardNavigationGestures = true
var allowsLinkPreview = true
var isFraudulentWebsiteWarningEnabled = true
var preferredContentMode = 0
var sharedCookiesEnabled = false
var mediaType: String? = nil
var pageZoom = 1.0
var limitsNavigationsToAppBoundDomains = false
var useOnNavigationResponse = false
var applePayAPIEnabled = false
var allowingReadAccessTo: String? = nil
var underPageBackgroundColor: String?
var isTextInteractionEnabled = true
var isSiteSpecificQuirksModeEnabled = true
var upgradeKnownHostsToHTTPS = true
var isElementFullscreenEnabled = true
var isInspectable = false
var shouldPrintBackgrounds = false
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> InAppWebViewSettings {
let _ = super.parse(settings: settings)
if #available(macOS 10.15, *) {} else {
applePayAPIEnabled = false
}
return self
}
override func getRealSettings(obj: InAppWebView?) -> [String: Any?] {
var realSettings: [String: Any?] = toMap()
if let webView = obj {
let configuration = webView.configuration
realSettings["userAgent"] = webView.customUserAgent
realSettings["applicationNameForUserAgent"] = configuration.applicationNameForUserAgent
realSettings["allowsAirPlayForMediaPlayback"] = configuration.allowsAirPlayForMediaPlayback
realSettings["allowsLinkPreview"] = webView.allowsLinkPreview
realSettings["javaScriptCanOpenWindowsAutomatically"] = configuration.preferences.javaScriptCanOpenWindowsAutomatically
if #available(macOS 10.12, *) {
realSettings["mediaPlaybackRequiresUserGesture"] = configuration.mediaTypesRequiringUserActionForPlayback == .all
}
realSettings["minimumFontSize"] = Int(configuration.preferences.minimumFontSize)
realSettings["suppressesIncrementalRendering"] = configuration.suppressesIncrementalRendering
realSettings["allowsBackForwardNavigationGestures"] = webView.allowsBackForwardNavigationGestures
if #available(macOS 10.15, *) {
realSettings["isFraudulentWebsiteWarningEnabled"] = configuration.preferences.isFraudulentWebsiteWarningEnabled
realSettings["preferredContentMode"] = configuration.defaultWebpagePreferences.preferredContentMode.rawValue
}
realSettings["allowUniversalAccessFromFileURLs"] = configuration.value(forKey: "allowUniversalAccessFromFileURLs") as? Bool
realSettings["allowFileAccessFromFileURLs"] = configuration.preferences.value(forKey: "allowFileAccessFromFileURLs") as? Bool
realSettings["javaScriptEnabled"] = configuration.preferences.javaScriptEnabled
if #available(macOS 11.0, *) {
realSettings["mediaType"] = webView.mediaType
realSettings["pageZoom"] = Float(webView.pageZoom)
realSettings["limitsNavigationsToAppBoundDomains"] = configuration.limitsNavigationsToAppBoundDomains
realSettings["javaScriptEnabled"] = configuration.defaultWebpagePreferences.allowsContentJavaScript
}
if #available(macOS 11.3, *) {
realSettings["isTextInteractionEnabled"] = configuration.preferences.isTextInteractionEnabled
realSettings["upgradeKnownHostsToHTTPS"] = configuration.upgradeKnownHostsToHTTPS
}
if #available(macOS 12.0, *) {
realSettings["underPageBackgroundColor"] = webView.underPageBackgroundColor.hexString
}
if #available(macOS 12.3, *) {
realSettings["isSiteSpecificQuirksModeEnabled"] = configuration.preferences.isSiteSpecificQuirksModeEnabled
realSettings["isElementFullscreenEnabled"] = configuration.preferences.isElementFullscreenEnabled
}
if #available(macOS 13.3, *) {
realSettings["isInspectable"] = webView.isInspectable
realSettings["shouldPrintBackgrounds"] = configuration.preferences.shouldPrintBackgrounds
}
}
return realSettings
}
}
@@ -0,0 +1,80 @@
//
// WebMessageChannel.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
import FlutterMacOS
public class WebMessageChannel: FlutterMethodCallDelegate {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_"
var id: String
var plugin: InAppWebViewFlutterPlugin?
var channelDelegate: WebMessageChannelChannelDelegate?
weak var webView: InAppWebView?
var ports: [WebMessagePort] = []
public init(plugin: InAppWebViewFlutterPlugin, id: String) {
self.id = id
self.plugin = plugin
super.init()
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id,
binaryMessenger: registrar.messenger)
self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel)
}
self.ports = [
WebMessagePort(name: "port1", index: 0, webMessageChannelId: self.id, webMessageChannel: self),
WebMessagePort(name: "port2", index: 1, webMessageChannelId: self.id, webMessageChannel: self)
]
}
public func initJsInstance(webView: InAppWebView, completionHandler: ((WebMessageChannel?) -> Void)? = nil) {
self.webView = webView
if let webView = self.webView {
webView.evaluateJavascript(source: """
(function() {
\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel();
})();
""") { (_) in
completionHandler?(self)
}
} else {
completionHandler?(nil)
}
}
public func toMap() -> [String:Any?] {
return [
"id": id
]
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
for port in ports {
port.dispose()
}
ports.removeAll()
webView?.evaluateJavascript(source: """
(function() {
var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
if (webMessageChannel != null) {
webMessageChannel.port1.close();
webMessageChannel.port2.close();
delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
}
})();
""")
webView = nil
plugin = nil
}
deinit {
debugPrint("WebMessageChannel - dealloc")
dispose()
}
}
@@ -0,0 +1,103 @@
//
// WebMessageChannelChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
import FlutterMacOS
public class WebMessageChannelChannelDelegate: ChannelDelegate {
private weak var webMessageChannel: WebMessageChannel?
public init(webMessageChannel: WebMessageChannel, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.webMessageChannel = webMessageChannel
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "setWebMessageCallback":
if let _ = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
let index = arguments!["index"] as! Int
let port = ports[index]
do {
try port.setWebMessageCallback { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
}
} else {
result(true)
}
break
case "postMessage":
if let webView = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
let index = arguments!["index"] as! Int
let port = ports[index]
var message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
var ports: [WebMessagePort] = []
if let notConnectedPorts = message.ports {
for notConnectedPort in notConnectedPorts {
if let webMessageChannel = webView.webMessageChannels[notConnectedPort.webMessageChannelId] {
ports.append(webMessageChannel.ports[Int(notConnectedPort.index)])
}
}
}
message.ports = ports
do {
try port.postMessage(message: message) { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
}
} else {
result(true)
}
break
case "close":
if let _ = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
let index = arguments!["index"] as! Int
let port = ports[index]
do {
try port.close { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
}
} else {
result(true)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onMessage(index: Int64, message: WebMessage?) {
let arguments: [String:Any?] = [
"index": index,
"message": message?.toMap()
]
channel?.invokeMethod("onMessage", arguments: arguments)
}
public override func dispose() {
super.dispose()
webMessageChannel = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,193 @@
//
// WebMessageListener.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
import WebKit
import FlutterMacOS
public class WebMessageListener: FlutterMethodCallDelegate {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_listener_"
var id: String
var jsObjectName: String
var allowedOriginRules: Set<String>
var channelDelegate: WebMessageListenerChannelDelegate?
weak var webView: InAppWebView?
var plugin: InAppWebViewFlutterPlugin?
public init(plugin: InAppWebViewFlutterPlugin, id: String, jsObjectName: String, allowedOriginRules: Set<String>) {
self.id = id
self.plugin = plugin
self.jsObjectName = jsObjectName
self.allowedOriginRules = allowedOriginRules
super.init()
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName,
binaryMessenger: registrar.messenger)
self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel)
}
}
public func assertOriginRulesValid() throws {
for (index, originRule) in allowedOriginRules.enumerated() {
if originRule.isEmpty {
throw NSError(domain: "allowedOriginRules[\(index)] is empty", code: 0)
}
if originRule == "*" {
continue
}
if let url = URL(string: originRule) {
guard let scheme = url.scheme else {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if scheme == "http" || scheme == "https", url.host == nil || url.host!.isEmpty {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if scheme != "http", scheme != "https", url.host != nil || url.port != nil {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if url.host == nil || url.host!.isEmpty, url.port != nil {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if !url.path.isEmpty {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if let hostname = url.host {
if let firstIndex = hostname.firstIndex(of: "*") {
let distance = hostname.distance(from: hostname.startIndex, to: firstIndex)
if distance != 0 || (distance == 0 && hostname.prefix(2) != "*.") {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
}
if hostname.hasPrefix("[") {
if !hostname.hasSuffix("]") {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
let ipv6 = String(hostname[indexRange])
if !Util.isIPv6(address: ipv6) {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
}
}
} else {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
}
}
public func initJsInstance(webView: InAppWebView) {
self.webView = webView
if let webView = self.webView {
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
let allowedOriginRulesString = allowedOriginRules.map { (allowedOriginRule) -> String in
if allowedOriginRule == "*" {
return "'*'"
}
let rule = URL(string: allowedOriginRule)!
let host = rule.host != nil ? "'" + rule.host!.replacingOccurrences(of: "\'", with: "\\'") + "'" : "null"
return """
{scheme: '\(rule.scheme!)', host: \(host), port: \(rule.port != nil ? String(rule.port!) : "null")}
"""
}.joined(separator: ", ")
let source = """
(function() {
var allowedOriginRules = [\(allowedOriginRulesString)];
var isPageBlank = window.location.href === "about:blank";
var scheme = !isPageBlank ? window.location.protocol.replace(":", "") : null;
var host = !isPageBlank ? window.location.hostname : null;
var port = !isPageBlank ? window.location.port : null;
if (window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed(allowedOriginRules, scheme, host, port)) {
window['\(jsObjectNameEscaped)'] = new FlutterInAppWebViewWebMessageListener('\(jsObjectNameEscaped)');
}
})();
"""
webView.configuration.userContentController.addPluginScript(PluginScript(
groupName: "WebMessageListener-" + id + "-" + jsObjectName,
source: source,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: false,
messageHandlerNames: ["onWebMessageListenerPostMessageReceived"]
))
webView.configuration.userContentController.sync(scriptMessageHandler: webView)
}
}
public static func fromMap(plugin: InAppWebViewFlutterPlugin, map: [String:Any?]?) -> WebMessageListener? {
guard let map = map else {
return nil
}
return WebMessageListener(
plugin: plugin,
id: map["id"] as! String,
jsObjectName: map["jsObjectName"] as! String,
allowedOriginRules: Set(map["allowedOriginRules"] as! [String])
)
}
public func isOriginAllowed(scheme: String?, host: String?, port: Int?) -> Bool {
for allowedOriginRule in allowedOriginRules {
if allowedOriginRule == "*" {
return true
}
if scheme == nil || scheme!.isEmpty {
continue
}
if scheme == nil || scheme!.isEmpty, host == nil || host!.isEmpty, port == nil || port == 0 {
continue
}
if let rule = URL(string: allowedOriginRule) {
let rulePort = rule.port == nil || rule.port == 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port!
let currentPort = port == nil || port == 0 ? (scheme == "https" ? 443 : 80) : port!
var IPv6: String? = nil
if let hostname = rule.host, hostname.hasPrefix("[") {
let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
do {
IPv6 = try Util.normalizeIPv6(address: String(hostname[indexRange]))
} catch {}
}
var hostIPv6: String? = nil
if let host = host, Util.isIPv6(address: host) {
do {
hostIPv6 = try Util.normalizeIPv6(address: host)
} catch {}
}
let schemeAllowed = scheme != nil && !scheme!.isEmpty && scheme == rule.scheme
let hostAllowed = rule.host == nil ||
rule.host!.isEmpty ||
host == rule.host ||
(rule.host!.hasPrefix("*") && host != nil && host!.hasSuffix(rule.host!.split(separator: "*", omittingEmptySubsequences: false)[1])) ||
(hostIPv6 != nil && IPv6 != nil && hostIPv6 == IPv6)
let portAllowed = rulePort == currentPort
if schemeAllowed, hostAllowed, portAllowed {
return true
}
}
}
return false
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
webView = nil
plugin = nil
}
deinit {
debugPrint("WebMessageListener - dealloc")
dispose()
}
}
@@ -0,0 +1,73 @@
//
// WebMessageListenerChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
import FlutterMacOS
public class WebMessageListenerChannelDelegate: ChannelDelegate {
private weak var webMessageListener: WebMessageListener?
public init(webMessageListener: WebMessageListener, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.webMessageListener = webMessageListener
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "postMessage":
if let webView = webMessageListener?.webView, let jsObjectName = webMessageListener?.jsObjectName {
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
let message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
let source = """
(function() {
var webMessageListener = window['\(jsObjectNameEscaped)'];
if (webMessageListener != null) {
var event = {data: \(message.jsData)};
if (webMessageListener.onmessage != null) {
webMessageListener.onmessage(event);
}
for (var listener of webMessageListener.listeners) {
listener(event);
}
}
})();
"""
webView.evaluateJavascript(source: source) { (_) in
result(true)
}
} else {
result(true)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onPostMessage(message: WebMessage?, sourceOrigin: URL?, isMainFrame: Bool) {
let arguments: [String:Any?] = [
"message": message?.toMap(),
"sourceOrigin": sourceOrigin?.absoluteString,
"isMainFrame": isMainFrame
]
channel?.invokeMethod("onPostMessage", arguments: arguments)
}
public override func dispose() {
super.dispose()
webMessageListener = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,87 @@
//
// WebViewChannelDelegateMethods.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 08/10/22.
//
import Foundation
public enum WebViewChannelDelegateMethods: String {
case getUrl = "getUrl"
case getTitle = "getTitle"
case getProgress = "getProgress"
case loadUrl = "loadUrl"
case postUrl = "postUrl"
case loadData = "loadData"
case loadFile = "loadFile"
case evaluateJavascript = "evaluateJavascript"
case injectJavascriptFileFromUrl = "injectJavascriptFileFromUrl"
case injectCSSCode = "injectCSSCode"
case injectCSSFileFromUrl = "injectCSSFileFromUrl"
case reload = "reload"
case goBack = "goBack"
case canGoBack = "canGoBack"
case goForward = "goForward"
case canGoForward = "canGoForward"
case goBackOrForward = "goBackOrForward"
case canGoBackOrForward = "canGoBackOrForward"
case stopLoading = "stopLoading"
case isLoading = "isLoading"
case takeScreenshot = "takeScreenshot"
case setSettings = "setSettings"
case getSettings = "getSettings"
case close = "close"
case show = "show"
case hide = "hide"
case isHidden = "isHidden"
case getCopyBackForwardList = "getCopyBackForwardList"
@available(*, deprecated, message: "Use FindInteractionController.findAll instead.")
case findAll = "findAll"
@available(*, deprecated, message: "Use FindInteractionController.findNext instead.")
case findNext = "findNext"
@available(*, deprecated, message: "Use FindInteractionController.clearMatches instead.")
case clearMatches = "clearMatches"
@available(*, deprecated, message: "Use InAppWebViewManager.clearAllCache instead.")
case clearCache = "clearCache"
case scrollTo = "scrollTo"
case scrollBy = "scrollBy"
case pauseTimers = "pauseTimers"
case resumeTimers = "resumeTimers"
case printCurrentPage = "printCurrentPage"
case getContentHeight = "getContentHeight"
case getContentWidth = "getContentWidth"
case reloadFromOrigin = "reloadFromOrigin"
case getOriginalUrl = "getOriginalUrl"
case getZoomScale = "getZoomScale"
case hasOnlySecureContent = "hasOnlySecureContent"
case getSelectedText = "getSelectedText"
case getScrollX = "getScrollX"
case getScrollY = "getScrollY"
case getCertificate = "getCertificate"
case addUserScript = "addUserScript"
case removeUserScript = "removeUserScript"
case removeUserScriptsByGroupName = "removeUserScriptsByGroupName"
case removeAllUserScripts = "removeAllUserScripts"
case callAsyncJavaScript = "callAsyncJavaScript"
case createPdf = "createPdf"
case createWebArchiveData = "createWebArchiveData"
case saveWebArchive = "saveWebArchive"
case isSecureContext = "isSecureContext"
case createWebMessageChannel = "createWebMessageChannel"
case postWebMessage = "postWebMessage"
case addWebMessageListener = "addWebMessageListener"
case canScrollVertically = "canScrollVertically"
case canScrollHorizontally = "canScrollHorizontally"
case pauseAllMediaPlayback = "pauseAllMediaPlayback"
case setAllMediaPlaybackSuspended = "setAllMediaPlaybackSuspended"
case closeAllMediaPresentations = "closeAllMediaPresentations"
case requestMediaPlaybackState = "requestMediaPlaybackState"
case getMetaThemeColor = "getMetaThemeColor"
case isInFullscreen = "isInFullscreen"
case getCameraCaptureState = "getCameraCaptureState"
case setCameraCaptureState = "setCameraCaptureState"
case getMicrophoneCaptureState = "getMicrophoneCaptureState"
case setMicrophoneCaptureState = "setMicrophoneCaptureState"
case loadSimulatedRequest = "loadSimulatedRequest"
}
@@ -0,0 +1,86 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
import Cocoa
import FlutterMacOS
import AppKit
import WebKit
import Foundation
import AVFoundation
import SafariServices
public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin {
var registrar: FlutterPluginRegistrar?
var platformUtil: PlatformUtil?
var inAppWebViewManager: InAppWebViewManager?
var myCookieManager: Any?
var myWebStorageManager: MyWebStorageManager?
var credentialDatabase: CredentialDatabase?
var inAppBrowserManager: InAppBrowserManager?
var headlessInAppWebViewManager: HeadlessInAppWebViewManager?
var webAuthenticationSessionManager: WebAuthenticationSessionManager?
var printJobManager: PrintJobManager?
var webViewControllers: [String: InAppBrowserWebViewController?] = [:]
var safariViewControllers: [String: Any?] = [:]
public init(with registrar: FlutterPluginRegistrar) {
super.init()
self.registrar = registrar
registrar.register(FlutterWebViewFactory(plugin: self) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID)
platformUtil = PlatformUtil(plugin: self)
inAppBrowserManager = InAppBrowserManager(plugin: self)
headlessInAppWebViewManager = HeadlessInAppWebViewManager(plugin: self)
inAppWebViewManager = InAppWebViewManager(plugin: self)
credentialDatabase = CredentialDatabase(plugin: self)
if #available(macOS 10.13, *) {
myCookieManager = MyCookieManager(plugin: self)
}
myWebStorageManager = MyWebStorageManager(plugin: self)
webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self)
printJobManager = PrintJobManager(plugin: self)
}
public static func register(with registrar: FlutterPluginRegistrar) {
let _ = InAppWebViewFlutterPlugin(with: registrar)
}
public func detachFromEngine(for registrar: FlutterPluginRegistrar) {
platformUtil?.dispose()
platformUtil = nil
inAppBrowserManager?.dispose()
inAppBrowserManager = nil
headlessInAppWebViewManager?.dispose()
headlessInAppWebViewManager = nil
inAppWebViewManager?.dispose()
inAppWebViewManager = nil
credentialDatabase?.dispose()
credentialDatabase = nil
if #available(macOS 10.13, *) {
(myCookieManager as! MyCookieManager?)?.dispose()
myCookieManager = nil
}
myWebStorageManager?.dispose()
myWebStorageManager = nil
webAuthenticationSessionManager?.dispose()
webAuthenticationSessionManager = nil
printJobManager?.dispose()
printJobManager = nil
}
}
@@ -0,0 +1,26 @@
//
// LeakAvoider.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/12/2019.
//
import Foundation
import FlutterMacOS
public class LeakAvoider: NSObject {
weak var delegate : FlutterMethodCallDelegate?
init(delegate: FlutterMethodCallDelegate) {
super.init()
self.delegate = delegate
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
self.delegate?.handle(call, result: result)
}
deinit {
debugPrint("LeakAvoider - dealloc")
}
}
@@ -0,0 +1,305 @@
//
// MyCookieManager.swift
// flutter_inappwebview
//
// Created by Lorenzo on 26/10/18.
//
import Foundation
import WebKit
import FlutterMacOS
@available(macOS 10.13, *)
public class MyCookieManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_cookiemanager"
var plugin: InAppWebViewFlutterPlugin?
static var httpCookieStore = WKWebsiteDataStore.default().httpCookieStore
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
if call.method == "gopeed.prepareProfile" {
GopeedProfiles.prepare(arguments: arguments, result: result)
return
}
let profileID = arguments?["gopeedProfileId"] as? String ?? ""
let selectedStore = GopeedProfiles.store(profileID)
if !profileID.isEmpty && selectedStore == nil {
result(FlutterError(code: "UNAVAILABLE", message: "WebView profile is not initialized", details: nil))
return
}
let cookieStore = (selectedStore ?? WKWebsiteDataStore.default()).httpCookieStore
switch call.method {
case "setCookie":
let url = arguments!["url"] as! String
let name = arguments!["name"] as! String
let value = arguments!["value"] as! String
let path = arguments!["path"] as! String
var expiresDate: Int64?
if let expiresDateString = arguments!["expiresDate"] as? String {
expiresDate = Int64(expiresDateString)
}
let maxAge = arguments!["maxAge"] as? Int64
let isSecure = arguments!["isSecure"] as? Bool
let isHttpOnly = arguments!["isHttpOnly"] as? Bool
let sameSite = arguments!["sameSite"] as? String
let domain = arguments!["domain"] as? String
MyCookieManager.setCookie(url: url,
name: name,
value: value,
path: path,
domain: domain,
expiresDate: expiresDate,
maxAge: maxAge,
isSecure: isSecure,
isHttpOnly: isHttpOnly,
sameSite: sameSite,
result: result, cookieStore: cookieStore)
break
case "getCookies":
let url = arguments!["url"] as! String
MyCookieManager.getCookies(url: url, result: result, cookieStore: cookieStore)
break
case "getAllCookies":
MyCookieManager.getAllCookies(result: result, cookieStore: cookieStore)
break
case "deleteCookie":
let url = arguments!["url"] as! String
let name = arguments!["name"] as! String
let path = arguments!["path"] as! String
let domain = arguments!["domain"] as? String
MyCookieManager.deleteCookie(url: url, name: name, path: path, domain: domain, result: result, cookieStore: cookieStore)
break;
case "deleteCookies":
let url = arguments!["url"] as! String
let path = arguments!["path"] as! String
let domain = arguments!["domain"] as? String
MyCookieManager.deleteCookies(url: url, path: path, domain: domain, result: result, cookieStore: cookieStore)
break;
case "deleteAllCookies":
MyCookieManager.deleteAllCookies(result: result, cookieStore: cookieStore)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public static func setCookie(url: String,
name: String,
value: String,
path: String,
domain: String?,
expiresDate: Int64?,
maxAge: Int64?,
isSecure: Bool?,
isHttpOnly: Bool?,
sameSite: String?,
result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var properties: [HTTPCookiePropertyKey: Any] = [:]
properties[.originURL] = url
properties[.name] = name
properties[.value] = value
properties[.path] = path
if domain != nil {
properties[.domain] = domain
}
if expiresDate != nil {
// convert from milliseconds
properties[.expires] = Date(timeIntervalSince1970: TimeInterval(Double(expiresDate!)/1000))
}
if maxAge != nil {
properties[.maximumAge] = String(maxAge!)
}
if isSecure != nil && isSecure! {
properties[.secure] = "TRUE"
}
if isHttpOnly != nil && isHttpOnly! {
properties[.init("HttpOnly")] = "YES"
}
if sameSite != nil {
if #available(macOS 10.15, *) {
var sameSiteValue = HTTPCookieStringPolicy(rawValue: "None")
switch sameSite {
case "Lax":
sameSiteValue = HTTPCookieStringPolicy.sameSiteLax
case "Strict":
sameSiteValue = HTTPCookieStringPolicy.sameSiteStrict
default:
break
}
properties[.sameSitePolicy] = sameSiteValue
} else {
properties[.init("SameSite")] = sameSite
}
}
if let cookie = HTTPCookie(properties: properties) {
cookieStore.setCookie(cookie, completionHandler: {() in
result(true)
})
} else {
result(false)
}
}
public static func getCookies(url: String, result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var cookieList: [[String: Any?]] = []
if let urlHost = URL(string: url)?.host {
cookieStore.getAllCookies { (cookies) in
for cookie in cookies {
if urlHost.hasSuffix(cookie.domain) || ".\(urlHost)".hasSuffix(cookie.domain) {
var sameSite: String? = nil
if #available(macOS 10.15, *) {
if let sameSiteValue = cookie.sameSitePolicy?.rawValue {
sameSite = sameSiteValue.prefix(1).capitalized + sameSiteValue.dropFirst()
}
}
var expiresDateTimestamp: Int64 = -1
if let expiresDate = cookie.expiresDate?.timeIntervalSince1970 {
// convert to milliseconds
expiresDateTimestamp = Int64(expiresDate * 1000)
}
cookieList.append([
"name": cookie.name,
"value": cookie.value,
"expiresDate": expiresDateTimestamp != -1 ? expiresDateTimestamp : nil,
"isSessionOnly": cookie.isSessionOnly,
"domain": cookie.domain,
"sameSite": sameSite,
"isSecure": cookie.isSecure,
"isHttpOnly": cookie.isHTTPOnly,
"path": cookie.path,
])
}
}
result(cookieList)
}
return
} else {
print("Cannot get WebView cookies. No HOST found for URL: \(url)")
}
result(cookieList)
}
public static func getAllCookies(result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var cookieList: [[String: Any?]] = []
cookieStore.getAllCookies { (cookies) in
for cookie in cookies {
var sameSite: String? = nil
if #available(macOS 10.15, *) {
if let sameSiteValue = cookie.sameSitePolicy?.rawValue {
sameSite = sameSiteValue.prefix(1).capitalized + sameSiteValue.dropFirst()
}
}
var expiresDateTimestamp: Int64 = -1
if let expiresDate = cookie.expiresDate?.timeIntervalSince1970 {
// convert to milliseconds
expiresDateTimestamp = Int64(expiresDate * 1000)
}
cookieList.append([
"name": cookie.name,
"value": cookie.value,
"expiresDate": expiresDateTimestamp != -1 ? expiresDateTimestamp : nil,
"isSessionOnly": cookie.isSessionOnly,
"domain": cookie.domain,
"sameSite": sameSite,
"isSecure": cookie.isSecure,
"isHttpOnly": cookie.isHTTPOnly,
"path": cookie.path,
])
}
result(cookieList)
}
}
public static func deleteCookie(url: String, name: String, path: String, domain: String?, result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var domain = domain
cookieStore.getAllCookies { (cookies) in
for cookie in cookies {
var originURL = url
if cookie.properties![.originURL] is String {
originURL = cookie.properties![.originURL] as! String
}
else if cookie.properties![.originURL] is URL {
originURL = (cookie.properties![.originURL] as! URL).absoluteString
}
if domain == nil, let domainUrl = URL(string: originURL) {
domain = domainUrl.host
}
if let domain = domain, cookie.domain == domain, cookie.name == name, cookie.path == path {
cookieStore.delete(cookie, completionHandler: {
result(true)
})
return
}
}
result(false)
}
}
public static func deleteCookies(url: String, path: String, domain: String?, result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var domain = domain
let dispatchGroup = DispatchGroup()
cookieStore.getAllCookies { (cookies) in
for cookie in cookies {
var originURL = url
if cookie.properties![.originURL] is String {
originURL = cookie.properties![.originURL] as! String
}
else if cookie.properties![.originURL] is URL {
originURL = (cookie.properties![.originURL] as! URL).absoluteString
}
if domain == nil, let domainUrl = URL(string: originURL) {
domain = domainUrl.host
}
if let domain = domain, cookie.domain == domain, cookie.path == path {
dispatchGroup.enter()
cookieStore.delete(cookie) {
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
result(true)
}
}
}
public static func deleteAllCookies(result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
cookieStore.getAllCookies { cookies in
let group = DispatchGroup()
for cookie in cookies {
group.enter()
cookieStore.delete(cookie) { group.leave() }
}
group.notify(queue: .main) { result(true) }
}
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,95 @@
//
// MyWebStorageManager.swift
// connectivity
//
// Created by Lorenzo Pichilli on 16/12/2019.
//
import Foundation
import WebKit
import FlutterMacOS
public class MyWebStorageManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_webstoragemanager"
var plugin: InAppWebViewFlutterPlugin?
static var websiteDataStore = WKWebsiteDataStore.default()
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "fetchDataRecords":
let dataTypes = Set(arguments!["dataTypes"] as! [String])
MyWebStorageManager.fetchDataRecords(dataTypes: dataTypes, result: result)
break
case "removeDataFor":
let dataTypes = Set(arguments!["dataTypes"] as! [String])
let recordList = arguments!["recordList"] as! [[String: Any?]]
MyWebStorageManager.removeDataFor(dataTypes: dataTypes, recordList: recordList, result: result)
break
case "removeDataModifiedSince":
let dataTypes = Set(arguments!["dataTypes"] as! [String])
let timestamp = arguments!["timestamp"] as! Int64
MyWebStorageManager.removeDataModifiedSince(dataTypes: dataTypes, timestamp: timestamp, result: result)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public static func fetchDataRecords(dataTypes: Set<String>, result: @escaping FlutterResult) {
var recordList: [[String: Any?]] = []
MyWebStorageManager.websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in
for record in data {
recordList.append([
"displayName": record.displayName,
"dataTypes": record.dataTypes.map({ (dataType) -> String in
return dataType
})
])
}
result(recordList)
}
}
public static func removeDataFor(dataTypes: Set<String>, recordList: [[String: Any?]], result: @escaping FlutterResult) {
var records: [WKWebsiteDataRecord] = []
MyWebStorageManager.websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in
for record in data {
for r in recordList {
let displayName = r["displayName"] as! String
if (record.displayName == displayName) {
records.append(record)
break
}
}
}
websiteDataStore.removeData(ofTypes: dataTypes, for: records) {
result(true)
}
}
}
public static func removeDataModifiedSince(dataTypes: Set<String>, timestamp: Int64, result: @escaping FlutterResult) {
let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp))
MyWebStorageManager.websiteDataStore.removeData(ofTypes: dataTypes, modifiedSince: date as Date) {
result(true)
}
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,67 @@
//
// PlatformUtil.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 01/03/21.
//
import Foundation
import FlutterMacOS
public class PlatformUtil: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_platformutil"
var plugin: InAppWebViewFlutterPlugin?
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getSystemVersion":
let version = ProcessInfo.processInfo.operatingSystemVersion
result("\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)")
break
case "formatDate":
let date = arguments!["date"] as! Int64
let format = arguments!["format"] as! String
let locale = PlatformUtil.getLocaleFromString(locale: arguments!["locale"] as? String)
let timezone = TimeZone.init(abbreviation: arguments!["timezone"] as? String ?? "UTC")!
result(PlatformUtil.formatDate(date: date, format: format, locale: locale, timezone: timezone))
break
default:
result(FlutterMethodNotImplemented)
break
}
}
static public func getLocaleFromString(locale: String?) -> Locale {
guard let locale = locale else {
return Locale.init(identifier: "en_US")
}
return Locale.init(identifier: locale)
}
static public func getDateFromMilliseconds(date: Int64) -> Date {
return Date(timeIntervalSince1970: TimeInterval(Double(date)/1000))
}
static public func formatDate(date: Int64, format: String, locale: Locale, timezone: TimeZone) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format
formatter.timeZone = timezone
return formatter.string(from: PlatformUtil.getDateFromMilliseconds(date: date))
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,21 @@
//
// CallAsyncJavaScriptBelowIOS14WrapperJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS = """
(function(obj) {
(async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) {
\(PluginScriptsUtil.VAR_FUNCTION_BODY)
})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) {
window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': value, 'error': null, 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'});
}).catch(function(error) {
window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': null, 'error': error + '', 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'});
});
return null;
})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ));
"""
@@ -0,0 +1,52 @@
//
// ConsoleLogJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT"
let CONSOLE_LOG_JS_PLUGIN_SCRIPT = PluginScript(
groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: CONSOLE_LOG_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"])
// the message needs to be concatenated with '' in order to have the same behavior like on Android
let CONSOLE_LOG_JS_SOURCE = """
(function(console) {
function _callHandler(oldLog, args) {
var message = '';
for (var i in args) {
try {
message += message === '' ? args[i] : ' ' + args[i];
} catch(ignored) {}
}
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers[oldLog].postMessage({'message': message, '_windowId': _windowId});
}
var oldLogs = {
'consoleLog': console.log,
'consoleDebug': console.debug,
'consoleError': console.error,
'consoleInfo': console.info,
'consoleWarn': console.warn
};
for (var k in oldLogs) {
(function(oldLog) {
console[oldLog.replace('console', '').toLowerCase()] = function() {
oldLogs[oldLog].apply(null, arguments);
_callHandler(oldLog, arguments);
}
})(k);
}
})(window.console);
"""
@@ -0,0 +1,36 @@
//
// EnableViewportScaleJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT"
let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ENABLE_VIEWPORT_SCALE_JS_SOURCE,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """
(function() {
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', 'width=device-width');
document.getElementsByTagName('head')[0].appendChild(meta);
})()
"""
let NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE = """
(function() {
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent);
document.getElementsByTagName('head')[0].appendChild(meta);
})()
"""
@@ -0,0 +1,73 @@
//
// FindElementsAtPointJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT"
let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: FIND_ELEMENTS_AT_POINT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
/**
https://developer.android.com/reference/android/webkit/WebView.HitTestResult
*/
let FIND_ELEMENTS_AT_POINT_JS_SOURCE = """
window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) {
var hitTestResultType = {
UNKNOWN_TYPE: 0,
PHONE_TYPE: 2,
GEO_TYPE: 3,
EMAIL_TYPE: 4,
IMAGE_TYPE: 5,
SRC_ANCHOR_TYPE: 7,
SRC_IMAGE_ANCHOR_TYPE: 8,
EDIT_TEXT_TYPE: 9
};
var element = document.elementFromPoint(x, y);
var data = {
type: 0,
extra: null
};
while (element) {
if (element.tagName === 'IMG' && element.src) {
if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) {
data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE;
} else {
data.type = hitTestResultType.IMAGE_TYPE;
}
data.extra = element.src;
break;
} else if (element.tagName === 'A' && element.href) {
if (element.href.indexOf('mailto:') === 0) {
data.type = hitTestResultType.EMAIL_TYPE;
data.extra = element.href.replace('mailto:', '');
} else if (element.href.indexOf('tel:') === 0) {
data.type = hitTestResultType.PHONE_TYPE;
data.extra = element.href.replace('tel:', '');
} else if (element.href.indexOf('geo:') === 0) {
data.type = hitTestResultType.GEO_TYPE;
data.extra = element.href.replace('geo:', '');
} else {
data.type = hitTestResultType.SRC_ANCHOR_TYPE;
data.extra = element.href;
}
break;
} else if (
(element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) ||
element.tagName === 'TEXTAREA') {
data.type = hitTestResultType.EDIT_TEXT_TYPE
}
element = element.parentNode;
}
return data;
}
"""
@@ -0,0 +1,187 @@
//
// FindTextHighlightJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT"
let FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._searchResultCount"
let FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._currentHighlight"
let FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._isDoneCounting"
let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: FIND_TEXT_HIGHLIGHT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: ["onFindResultReceived"])
let FIND_TEXT_HIGHLIGHT_JS_SOURCE = """
\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0;
\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0;
\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false;
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement = function(element, keyword) {
if (element) {
if (element.nodeType == 3) {
// Text node
var elementTmp = element;
while (true) {
var value = elementTmp.nodeValue; // Search for keyword in text node
var idx = value.toLowerCase().indexOf(keyword);
if (idx < 0) break;
var span = document.createElement("span");
var text = document.createTextNode(value.substr(idx, keyword.length));
span.appendChild(text);
span.setAttribute(
"id",
"\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)
);
span.setAttribute("class", "\(JAVASCRIPT_BRIDGE_NAME)_Highlight");
var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) == 0 ? "#FF9732" : "#FFFF00";
span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;");
text = document.createTextNode(value.substr(idx + keyword.length));
element.deleteData(idx, value.length - idx);
var next = element.nextSibling;
element.parentNode.insertBefore(span, next);
element.parentNode.insertBefore(text, next);
element = text;
\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)++;
elementTmp = document.createTextNode(
value.substr(idx + keyword.length)
);
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
{
'findResult': {
'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE),
'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE),
'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE)
},
'_windowId': _windowId
}
);
}
} else if (element.nodeType == 1) {
// Element node
if (
element.style.display != "none" &&
element.nodeName.toLowerCase() != "select"
) {
for (var i = element.childNodes.length - 1; i >= 0; i--) {
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(
element.childNodes[element.childNodes.length - 1 - i],
keyword
);
}
}
}
}
}
// the main entry point to start the search
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync = function(keyword) {
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(document.body, keyword.toLowerCase());
\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = true;
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
{
'findResult': {
'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE),
'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE),
'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE)
},
'_windowId': _windowId
}
);
}
// helper function, recursively removes the highlights in elements and their children
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement = function(element) {
if (element) {
if (element.nodeType == 1) {
if (element.getAttribute("class") == "\(JAVASCRIPT_BRIDGE_NAME)_Highlight") {
var text = element.removeChild(element.firstChild);
element.parentNode.insertBefore(text, element);
element.parentNode.removeChild(element);
return true;
} else {
var normalize = false;
for (var i = element.childNodes.length - 1; i >= 0; i--) {
if (window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(element.childNodes[i])) {
normalize = true;
}
}
if (normalize) {
element.normalize();
}
}
}
}
return false;
}
// the main entry point to remove the highlights
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches = function() {
\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0;
\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0;
\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false;
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(document.body);
}
window.\(JAVASCRIPT_BRIDGE_NAME)._findNext = function(forward) {
if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) <= 0) return;
var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) + (forward ? +1 : -1);
idx =
idx < 0
? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - 1
: idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)
? 0
: idx;
\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = idx;
var scrollTo = document.getElementById("\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + idx);
if (scrollTo) {
var highlights = document.getElementsByClassName("\(JAVASCRIPT_BRIDGE_NAME)_Highlight");
for (var i = 0; i < highlights.length; i++) {
var span = highlights[i];
span.style.backgroundColor = "#FFFF00";
}
scrollTo.style.backgroundColor = "#FF9732";
scrollTo.scrollIntoView({
behavior: "auto",
block: "center"
});
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
{
'findResult': {
'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE),
'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE),
'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE)
},
'_windowId': _windowId
}
);
}
}
"""
@@ -0,0 +1,268 @@
//
// InterceptAjaxRequestsJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT"
let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptAjaxRequest"
let FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._interceptOnlyAsyncAjaxRequests";
let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = PluginScript(
groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: INTERCEPT_AJAX_REQUEST_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: [])
func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool) -> PluginScript {
return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) = \(onlyAsync);",
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: []
);
}
let INTERCEPT_AJAX_REQUEST_JS_SOURCE = """
\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) = true;
(function(ajax) {
var send = ajax.prototype.send;
var open = ajax.prototype.open;
var setRequestHeader = ajax.prototype.setRequestHeader;
ajax.prototype._flutter_inappwebview_url = null;
ajax.prototype._flutter_inappwebview_method = null;
ajax.prototype._flutter_inappwebview_isAsync = null;
ajax.prototype._flutter_inappwebview_user = null;
ajax.prototype._flutter_inappwebview_password = null;
ajax.prototype._flutter_inappwebview_password = null;
ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false;
ajax.prototype._flutter_inappwebview_request_headers = {};
function convertRequestResponse(request, callback) {
if (request.response != null && request.responseType != null) {
switch (request.responseType) {
case 'arraybuffer':
callback(new Uint8Array(request.response));
return;
case 'blob':
const reader = new FileReader();
reader.addEventListener('loadend', function() {
callback(new Uint8Array(reader.result));
});
reader.readAsArrayBuffer(blob);
return;
case 'document':
callback(request.response.documentElement.outerHTML);
return;
case 'json':
callback(request.response);
return;
};
}
callback(null);
};
ajax.prototype.open = function(method, url, isAsync, user, password) {
isAsync = (isAsync != null) ? isAsync : true;
this._flutter_inappwebview_url = url;
this._flutter_inappwebview_method = method;
this._flutter_inappwebview_isAsync = isAsync;
this._flutter_inappwebview_user = user;
this._flutter_inappwebview_password = password;
this._flutter_inappwebview_request_headers = {};
open.call(this, method, url, isAsync, user, password);
};
ajax.prototype.setRequestHeader = function(header, value) {
this._flutter_inappwebview_request_headers[header] = value;
setRequestHeader.call(this, header, value);
};
function handleEvent(e) {
var self = this;
if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true) {
var headers = this.getAllResponseHeaders();
var responseHeaders = {};
if (headers != null) {
var arr = headers.trim().split(/[\\r\\n]+/);
arr.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift();
var value = parts.join(': ');
responseHeaders[header] = value;
});
}
convertRequestResponse(this, function(response) {
var ajaxRequest = {
method: self._flutter_inappwebview_method,
url: self._flutter_inappwebview_url,
isAsync: self._flutter_inappwebview_isAsync,
user: self._flutter_inappwebview_user,
password: self._flutter_inappwebview_password,
withCredentials: self.withCredentials,
headers: self._flutter_inappwebview_request_headers,
readyState: self.readyState,
status: self.status,
responseURL: self.responseURL,
responseType: self.responseType,
response: response,
responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null,
responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null,
statusText: self.statusText,
responseHeaders, responseHeaders,
event: {
type: e.type,
loaded: e.loaded,
lengthComputable: e.lengthComputable,
total: e.total
}
};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxProgress', ajaxRequest).then(function(result) {
if (result != null) {
switch (result) {
case 0:
self.abort();
return;
};
}
});
});
}
};
ajax.prototype.send = function(data) {
var self = this;
var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) === false;
if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true)) {
if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) {
this._flutter_inappwebview_already_onreadystatechange_wrapped = true;
var onreadystatechange = this.onreadystatechange;
this.onreadystatechange = function() {
if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true) {
var headers = this.getAllResponseHeaders();
var responseHeaders = {};
if (headers != null) {
var arr = headers.trim().split(/[\\r\\n]+/);
arr.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift();
var value = parts.join(': ');
responseHeaders[header] = value;
});
}
convertRequestResponse(this, function(response) {
var ajaxRequest = {
method: self._flutter_inappwebview_method,
url: self._flutter_inappwebview_url,
isAsync: self._flutter_inappwebview_isAsync,
user: self._flutter_inappwebview_user,
password: self._flutter_inappwebview_password,
withCredentials: self.withCredentials,
headers: self._flutter_inappwebview_request_headers,
readyState: self.readyState,
status: self.status,
responseURL: self.responseURL,
responseType: self.responseType,
response: response,
responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null,
responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null,
statusText: self.statusText,
responseHeaders: responseHeaders
};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) {
if (result != null) {
switch (result) {
case 0:
self.abort();
return;
};
}
if (onreadystatechange != null) {
onreadystatechange();
}
});
});
} else if (onreadystatechange != null) {
onreadystatechange();
}
};
}
this.addEventListener('loadstart', handleEvent);
this.addEventListener('load', handleEvent);
this.addEventListener('loadend', handleEvent);
this.addEventListener('progress', handleEvent);
this.addEventListener('error', handleEvent);
this.addEventListener('abort', handleEvent);
this.addEventListener('timeout', handleEvent);
\(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(data).then(function(data) {
var ajaxRequest = {
data: data,
method: self._flutter_inappwebview_method,
url: self._flutter_inappwebview_url,
isAsync: self._flutter_inappwebview_isAsync,
user: self._flutter_inappwebview_user,
password: self._flutter_inappwebview_password,
withCredentials: self.withCredentials,
headers: self._flutter_inappwebview_request_headers,
responseType: self.responseType
};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {
if (result != null) {
switch (result) {
case 0:
self.abort();
return;
};
if (result.data != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) && result.data.length > 0) {
var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.data);
if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) {
var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString);
if (result.headers != null) {
result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];
} else {
result.headers = { 'Content-Type': formDataContentType };
}
}
}
if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) || result.data == null) {
data = result.data;
} else if (result.data.length > 0) {
data = new Uint8Array(result.data);
}
self.withCredentials = result.withCredentials;
if (result.responseType != null && self._flutter_inappwebview_isAsync) {
self.responseType = result.responseType;
};
if (result.headers != null) {
for (var header in result.headers) {
var value = result.headers[header];
var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header];
if (flutter_inappwebview_value == null) {
self._flutter_inappwebview_request_headers[header] = value;
} else {
self._flutter_inappwebview_request_headers[header] += ', ' + value;
}
setRequestHeader.call(self, header, value);
};
}
if ((self._flutter_inappwebview_method != result.method && result.method != null) ||
(self._flutter_inappwebview_url != result.url && result.url != null) ||
(self._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) ||
(self._flutter_inappwebview_user != result.user && result.user != null) ||
(self._flutter_inappwebview_password != result.password && result.password != null)) {
self.abort();
self.open(result.method, result.url, result.isAsync, result.user, result.password);
}
}
send.call(self, data);
});
});
} else {
send.call(this, data);
}
};
})(window.XMLHttpRequest);
"""
@@ -0,0 +1,153 @@
//
// InterceptFetchRequestsJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT"
let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptFetchRequest"
let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = PluginScript(
groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: INTERCEPT_FETCH_REQUEST_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: [])
let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """
\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) = true;
(function(fetch) {
if (fetch == null) {
return;
}
window.fetch = async function(resource, init) {
if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == true) {
var fetchRequest = {
url: null,
method: null,
headers: null,
body: null,
mode: null,
credentials: null,
cache: null,
redirect: null,
referrer: null,
referrerPolicy: null,
integrity: null,
keepalive: null
};
if (resource instanceof Request) {
fetchRequest.url = resource.url;
fetchRequest.method = resource.method;
fetchRequest.headers = resource.headers;
fetchRequest.body = resource.body;
fetchRequest.mode = resource.mode;
fetchRequest.credentials = resource.credentials;
fetchRequest.cache = resource.cache;
fetchRequest.redirect = resource.redirect;
fetchRequest.referrer = resource.referrer;
fetchRequest.referrerPolicy = resource.referrerPolicy;
fetchRequest.integrity = resource.integrity;
fetchRequest.keepalive = resource.keepalive;
} else {
fetchRequest.url = resource != null ? resource.toString() : null;
if (init != null) {
fetchRequest.method = init.method;
fetchRequest.headers = init.headers;
fetchRequest.body = init.body;
fetchRequest.mode = init.mode;
fetchRequest.credentials = init.credentials;
fetchRequest.cache = init.cache;
fetchRequest.redirect = init.redirect;
fetchRequest.referrer = init.referrer;
fetchRequest.referrerPolicy = init.referrerPolicy;
fetchRequest.integrity = init.integrity;
fetchRequest.keepalive = init.keepalive;
}
}
if (fetchRequest.headers instanceof Headers) {
fetchRequest.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertHeadersToJson(fetchRequest.headers);
}
fetchRequest.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertCredentialsToJson(fetchRequest.credentials);
return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(fetchRequest.body).then(function(body) {
fetchRequest.body = body;
return window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) {
if (result != null) {
switch (result.action) {
case 0:
var controller = new AbortController();
if (init != null) {
init.signal = controller.signal;
} else {
init = {
signal: controller.signal
};
}
controller.abort();
break;
}
if (result.body != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) && result.body.length > 0) {
var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.body);
if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) {
var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString);
if (result.headers != null) {
result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];
} else {
result.headers = { 'Content-Type': formDataContentType };
}
}
}
resource = result.url;
if (init == null) {
init = {};
}
if (result.method != null && result.method.length > 0) {
init.method = result.method;
}
if (result.headers != null && Object.keys(result.headers).length > 0) {
init.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToHeaders(result.headers);
}
if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) || result.body == null) {
init.body = result.body;
} else if (result.body.length > 0) {
init.body = new Uint8Array(result.body);
}
if (result.mode != null && result.mode.length > 0) {
init.mode = result.mode;
}
if (result.credentials != null) {
init.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToCredential(result.credentials);
}
if (result.cache != null && result.cache.length > 0) {
init.cache = result.cache;
}
if (result.redirect != null && result.redirect.length > 0) {
init.redirect = result.redirect;
}
if (result.referrer != null && result.referrer.length > 0) {
init.referrer = result.referrer;
}
if (result.referrerPolicy != null && result.referrerPolicy.length > 0) {
init.referrerPolicy = result.referrerPolicy;
}
if (result.integrity != null && result.integrity.length > 0) {
init.integrity = result.integrity;
}
if (result.keepalive != null) {
init.keepalive = result.keepalive;
}
return fetch(resource, init);
}
return fetch(resource, init);
});
});
} else {
return fetch(resource, init);
}
};
})(window.fetch);
"""
@@ -0,0 +1,243 @@
//
// javaScriptBridgeJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"
let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT"
let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = PluginScript(
groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: JAVASCRIPT_BRIDGE_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: ["callHandler"])
let JAVASCRIPT_BRIDGE_JS_SOURCE = """
window.\(JAVASCRIPT_BRIDGE_NAME) = {};
\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME) = {};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
var _callHandlerID = setTimeout(function(){});
window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1)), '_windowId': _windowId} );
return new Promise(function(resolve, reject) {
window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = {resolve: resolve, reject: reject};
});
};
\(WEB_MESSAGE_LISTENER_JS_SOURCE)
\(UTIL_JS_SOURCE)
"""
let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));";
let JAVASCRIPT_UTIL_VAR_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._Util"
/*
https://github.com/github/fetch/blob/master/fetch.js
*/
let UTIL_JS_SOURCE = """
\(JAVASCRIPT_UTIL_VAR_NAME) = {
support: {
searchParams: 'URLSearchParams' in window,
iterable: 'Symbol' in window && 'iterator' in Symbol,
blob:
'FileReader' in window &&
'Blob' in window &&
(function() {
try {
new Blob();
return true;
} catch (e) {
return false;
}
})(),
formData: 'FormData' in window,
arrayBuffer: 'ArrayBuffer' in window
},
isDataView: function(obj) {
return obj && DataView.prototype.isPrototypeOf(obj);
},
fileReaderReady: function(reader) {
return new Promise(function(resolve, reject) {
reader.onload = function() {
resolve(reader.result);
};
reader.onerror = function() {
reject(reader.error);
};
});
},
readBlobAsArrayBuffer: function(blob) {
var reader = new FileReader();
var promise = \(JAVASCRIPT_UTIL_VAR_NAME).fileReaderReady(reader);
reader.readAsArrayBuffer(blob);
return promise;
},
convertBodyToArrayBuffer: function(body) {
var viewClasses = [
'[object Int8Array]',
'[object Uint8Array]',
'[object Uint8ClampedArray]',
'[object Int16Array]',
'[object Uint16Array]',
'[object Int32Array]',
'[object Uint32Array]',
'[object Float32Array]',
'[object Float64Array]'
];
var isArrayBufferView = null;
if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer) {
isArrayBufferView =
ArrayBuffer.isView ||
function(obj) {
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
};
}
var bodyUsed = false;
this._bodyInit = body;
if (!body) {
this._bodyText = '';
} else if (typeof body === 'string') {
this._bodyText = body;
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.blob && Blob.prototype.isPrototypeOf(body)) {
this._bodyBlob = body;
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.formData && FormData.prototype.isPrototypeOf(body)) {
this._bodyFormData = body;
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this._bodyText = body.toString();
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME).isDataView(body)) {
this._bodyArrayBuffer = bufferClone(body.buffer);
this._bodyInit = new Blob([this._bodyArrayBuffer]);
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
this._bodyArrayBuffer = bufferClone(body);
} else {
this._bodyText = body = Object.prototype.toString.call(body);
}
this.blob = function () {
if (bodyUsed) {
return Promise.reject(new TypeError('Already read'));
}
bodyUsed = true;
if (this._bodyBlob) {
return Promise.resolve(this._bodyBlob);
} else if (this._bodyArrayBuffer) {
return Promise.resolve(new Blob([this._bodyArrayBuffer]));
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as blob');
} else {
return Promise.resolve(new Blob([this._bodyText]));
}
};
if (this._bodyArrayBuffer) {
if (bodyUsed) {
return Promise.reject(new TypeError('Already read'));
}
bodyUsed = true;
if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
return Promise.resolve(
this._bodyArrayBuffer.buffer.slice(
this._bodyArrayBuffer.byteOffset,
this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
)
);
} else {
return Promise.resolve(this._bodyArrayBuffer);
}
}
return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME).readBlobAsArrayBuffer);
},
isString: function(variable) {
return typeof variable === 'string' || variable instanceof String;
},
convertBodyRequest: function(body) {
if (body == null) {
return new Promise((resolve, reject) => resolve(null));
}
if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && body instanceof URLSearchParams)) {
return new Promise((resolve, reject) => resolve(body.toString()));
}
if (window.Response != null) {
return new Response(body).arrayBuffer().then(function(arrayBuffer) {
return Array.from(new Uint8Array(arrayBuffer));
});
}
return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyToArrayBuffer(body).then(function(arrayBuffer) {
return Array.from(new Uint8Array(arrayBuffer));
});
},
arrayBufferToString: function(arrayBuffer) {
var uint8Array = new Uint8Array(arrayBuffer);
return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, '');
},
isBodyFormData: function(bodyString) {
return bodyString.indexOf('------WebKitFormBoundary') >= 0;
},
getFormDataContentType: function(bodyString) {
var boundary = bodyString.substr(2, 40);
return 'multipart/form-data; boundary=' + boundary;
},
convertHeadersToJson: function(headers) {
var headersObj = {};
for (var header of headers.keys()) {
var value = headers.get(header);
headersObj[header] = value;
}
return headersObj;
},
convertJsonToHeaders: function(headersJson) {
return new Headers(headersJson);
},
convertCredentialsToJson: function(credentials) {
var credentialsObj = {};
if (window.FederatedCredential != null && credentials instanceof FederatedCredential) {
credentialsObj.type = credentials.type;
credentialsObj.id = credentials.id;
credentialsObj.name = credentials.name;
credentialsObj.protocol = credentials.protocol;
credentialsObj.provider = credentials.provider;
credentialsObj.iconURL = credentials.iconURL;
} else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) {
credentialsObj.type = credentials.type;
credentialsObj.id = credentials.id;
credentialsObj.name = credentials.name;
credentialsObj.password = credentials.password;
credentialsObj.iconURL = credentials.iconURL;
} else {
credentialsObj.type = 'default';
credentialsObj.value = credentials;
}
return credentialsObj;
},
convertJsonToCredential: function(credentialsJson) {
var credentials;
if (window.FederatedCredential != null && credentialsJson.type === 'federated') {
credentials = new FederatedCredential({
id: credentialsJson.id,
name: credentialsJson.name,
protocol: credentialsJson.protocol,
provider: credentialsJson.provider,
iconURL: credentialsJson.iconURL
});
} else if (window.PasswordCredential != null && credentialsJson.type === 'password') {
credentials = new PasswordCredential({
id: credentialsJson.id,
name: credentialsJson.name,
password: credentialsJson.password,
iconURL: credentialsJson.iconURL
});
} else {
credentials = credentialsJson.value == null ? undefined : credentialsJson.value;
}
return credentials;
}
};
"""
@@ -0,0 +1,39 @@
//
// resourceObserverJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT"
let FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useOnLoadResource"
let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ON_LOAD_RESOURCE_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ON_LOAD_RESOURCE_JS_SOURCE = """
\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) = true;
(function() {
var observer = new PerformanceObserver(function(list) {
list.getEntries().forEach(function(entry) {
if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == true) {
var resource = {
"url": entry.name,
"initiatorType": entry.initiatorType,
"startTime": entry.startTime,
"duration": entry.duration
};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onLoadResource", resource);
}
});
});
observer.observe({entryTypes: ['resource']});
})();
"""
@@ -0,0 +1,33 @@
//
// OnScrollEvent.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/10/22.
//
import Foundation
let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT"
let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ON_SCROLL_CHANGED_EVENT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: ["onScrollChanged"])
let ON_SCROLL_CHANGED_EVENT_JS_SOURCE = """
(function(){
document.addEventListener('scroll', function(e) {
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers["onScrollChanged"].postMessage(
{
x: window.scrollX,
y: window.scrollY,
_windowId: _windowId
}
);
});
})();
"""
@@ -0,0 +1,26 @@
//
// OnWindowBlurEventJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT"
let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ON_WINDOW_BLUR_EVENT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ON_WINDOW_BLUR_EVENT_JS_SOURCE = """
(function(){
window.addEventListener('blur', function(e) {
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowBlur');
});
})();
"""
@@ -0,0 +1,26 @@
//
// OnWindowFocusEventJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT"
let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ON_WINDOW_FOCUS_EVENT_JS_SOURCE,
source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ON_WINDOW_FOCUS_EVENT_JS_SOURCE = """
(function(){
window.addEventListener('focus', function(e) {
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowFocus');
});
})();
"""
@@ -0,0 +1,31 @@
//
// OriginalViewPortMetaTagContentJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT"
let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE = """
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = "";
(function() {
var metaTagNodes = document.head.getElementsByTagName('meta');
for (var i = 0; i < metaTagNodes.length; i++) {
var metaTagNode = metaTagNodes[i];
if (metaTagNode.name === "viewport") {
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content;
}
}
})();
"""
@@ -0,0 +1,31 @@
//
// PluginScripts.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
public class PluginScriptsUtil {
public static let VAR_PLACEHOLDER_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_VALUE"
public static let VAR_FUNCTION_ARGUMENT_NAMES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_NAMES"
public static let VAR_FUNCTION_ARGUMENT_VALUES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_VALUES"
public static let VAR_FUNCTION_ARGUMENTS_OBJ = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENTS_OBJ"
public static let VAR_FUNCTION_BODY = "$IN_APP_WEBVIEW_FUNCTION_BODY"
public static let VAR_RESULT_UUID = "$IN_APP_WEBVIEW_RESULT_UUID"
public static let GET_SELECTED_TEXT_JS_SOURCE = """
(function(){
var txt;
if (window.getSelection) {
txt = window.getSelection().toString();
} else if (window.document.getSelection) {
txt = window.document.getSelection().toString();
} else if (window.document.selection) {
txt = window.document.selection.createRange().text;
}
return txt;
})();
"""
}
@@ -0,0 +1,24 @@
//
// PrintJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT"
let PRINT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: PRINT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: [])
let PRINT_JS_SOURCE = """
window.print = function() {
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrintRequest", window.location.href);
}
"""
@@ -0,0 +1,27 @@
//
// PromisePolyfillJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
import WebKit
let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PROMISE_POLYFILL_JS_PLUGIN_SCRIPT"
let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT = PluginScript(
groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: PROMISE_POLYFILL_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: [])
// https://github.com/tildeio/rsvp.js
let PROMISE_POLYFILL_JS_SOURCE = """
if (window.Promise == null) {
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i<o.length;i++)(0,o[i])(r,n)}},n={instrument:!1};function o(t,e){if(2!==arguments.length)return n[t];n[t]=e}r.mixin(n);var i=[];function s(t,e,r){1===i.push({name:t,payload:{key:e._guidKey,id:e._id,eventName:t,detail:e._result,childId:r&&r._id,label:e._label,timeStamp:Date.now(),error:n["instrument-with-stack"]?new Error(e._label):null}})&&setTimeout(function(){for(var t=0;t<i.length;t++){var e=i[t],r=e.payload;r.guid=r.key+r.id,r.childGuid=r.key+r.childId,r.error&&(r.stack=r.error.stack),n.trigger(e.name,e.payload)}i.length=0},50)}function u(t,e){if(t&&"object"==typeof t&&t.constructor===this)return t;var r=new this(c,e);return m(r,t),r}function c(){}var a=void 0,f=1,l=2,h={error:null};function p(t){try{return t.then}catch(t){return h.error=t,h}}var y=void 0;function _(){try{var t=y;return y=null,t.apply(this,arguments)}catch(t){return h.error=t,h}}function v(t){return y=t,_}function d(t,e,r){if(e.constructor===t.constructor&&r===A&&t.constructor.resolve===u)!function(t,e){e._state===f?b(t,e._result):e._state===l?(e._onError=null,g(t,e._result)):j(e,void 0,function(r){e===r?b(t,r):m(t,r)},function(e){return g(t,e)})}(t,e);else if(r===h){var o=h.error;h.error=null,g(t,o)}else"function"==typeof r?function(t,e,r){n.async(function(t){var n=!1,o=v(r).call(e,function(r){n||(n=!0,e===r?b(t,r):m(t,r))},function(e){n||(n=!0,g(t,e))},"Settle: "+(t._label||" unknown promise"));if(!n&&o===h){n=!0;var i=h.error;h.error=null,g(t,i)}},t)}(t,e,r):b(t,e)}function m(t,e){var r,n;t===e?b(t,e):(n=typeof(r=e),null===r||"object"!==n&&"function"!==n?b(t,e):d(t,e,p(e)))}function w(t){t._onError&&t._onError(t._result),O(t)}function b(t,e){t._state===a&&(t._result=e,t._state=f,0===t._subscribers.length?n.instrument&&s("fulfilled",t):n.async(O,t))}function g(t,e){t._state===a&&(t._state=l,t._result=e,n.async(w,t))}function j(t,e,r,o){var i=t._subscribers,s=i.length;t._onError=null,i[s]=e,i[s+f]=r,i[s+l]=o,0===s&&t._state&&n.async(O,t)}function O(t){var e=t._subscribers,r=t._state;if(n.instrument&&s(r===f?"fulfilled":"rejected",t),0!==e.length){for(var o=void 0,i=void 0,u=t._result,c=0;c<e.length;c+=3)o=e[c],i=e[c+r],o?E(r,o,i,u):i(u);t._subscribers.length=0}}function E(t,e,r,n){var o="function"==typeof r,i=void 0;if(i=o?v(r)(n):n,e._state!==a);else if(i===e)g(e,new TypeError("A promises callback cannot return that same promise."));else if(i===h){var s=h.error;h.error=null,g(e,s)}else o?m(e,i):t===f?b(e,i):t===l&&g(e,i)}function A(t,e,r){var o=this._state;if(o===f&&!t||o===l&&!e)return n.instrument&&s("chained",this,this),this;this._onError=null;var i=new this.constructor(c,r),u=this._result;if(n.instrument&&s("chained",this,i),o===a)j(this,i,t,e);else{var h=o===f?t:e;n.async(function(){return E(o,i,h,u)})}return i}var T=function(){function t(t,e,r,n){this._instanceConstructor=t,this.promise=new t(c,n),this._abortOnReject=r,this._isUsingOwnPromise=t===k,this._isUsingOwnResolve=t.resolve===u,this._init.apply(this,arguments)}return t.prototype._init=function(t,e){var r=e.length||0;this.length=r,this._remaining=r,this._result=new Array(r),this._enumerate(e)},t.prototype._enumerate=function(t){for(var e=this.length,r=this.promise,n=0;r._state===a&&n<e;n++)this._eachEntry(t[n],n,!0);this._checkFullfillment()},t.prototype._checkFullfillment=function(){if(0===this._remaining){var t=this._result;b(this.promise,t),this._result=null}},t.prototype._settleMaybeThenable=function(t,e,r){var n=this._instanceConstructor;if(this._isUsingOwnResolve){var o=p(t);if(o===A&&t._state!==a)t._onError=null,this._settledAt(t._state,e,t._result,r);else if("function"!=typeof o)this._settledAt(f,e,t,r);else if(this._isUsingOwnPromise){var i=new n(c);d(i,t,o),this._willSettleAt(i,e,r)}else this._willSettleAt(new n(function(e){return e(t)}),e,r)}else this._willSettleAt(n.resolve(t),e,r)},t.prototype._eachEntry=function(t,e,r){null!==t&&"object"==typeof t?this._settleMaybeThenable(t,e,r):this._setResultAt(f,e,t,r)},t.prototype._settledAt=function(t,e,r,n){var o=this.promise;o._state===a&&(this._abortOnReject&&t===l?g(o,r):(this._setResultAt(t,e,r,n),this._checkFullfillment()))},t.prototype._setResultAt=function(t,e,r,n){this._remaining--,this._result[e]=r},t.prototype._willSettleAt=function(t,e,r){var n=this;j(t,void 0,function(t){return n._settledAt(f,e,t,r)},function(t){return n._sLine truncated
window.Promise = RSVP.Promise;
}
"""
@@ -0,0 +1,37 @@
//
// SupportZoomJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT"
let NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT = PluginScript(
groupName: NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: NOT_SUPPORT_ZOOM_JS_SOURCE,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let NOT_SUPPORT_ZOOM_JS_SOURCE = """
(function() {
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no');
document.getElementsByTagName('head')[0].appendChild(meta);
})()
"""
let SUPPORT_ZOOM_JS_SOURCE = """
(function() {
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent);
document.getElementsByTagName('head')[0].appendChild(meta);
})()
"""
@@ -0,0 +1,10 @@
//
// WebMessageChannelJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
let WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._webMessageChannels"
@@ -0,0 +1,116 @@
//
// WebMessageListenerJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
let WEB_MESSAGE_LISTENER_JS_SOURCE = """
function FlutterInAppWebViewWebMessageListener(jsObjectName) {
this.jsObjectName = jsObjectName;
this.listeners = [];
this.onmessage = null;
}
FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(data) {
var message = {
"data": window.ArrayBuffer != null && data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : (data != null ? data.toString() : null),
"type": window.ArrayBuffer != null && data instanceof ArrayBuffer ? 1 : 0
};
window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message});
};
FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) {
if (listener == null) {
return;
}
this.listeners.push(listener);
};
FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) {
if (listener == null) {
return;
}
var index = this.listeners.indexOf(listener);
if (index >= 0) {
this.listeners.splice(index, 1);
}
};
window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6 = function(ip_string) {
// replace ipv4 address if any
var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/);
if (ipv4) {
ip_string = ipv4[1];
ipv4 = ipv4[2].match(/[0-9]+/g);
for (var i = 0;i < 4;i ++) {
var byte = parseInt(ipv4[i],10);
ipv4[i] = ("0" + byte.toString(16)).substr(-2);
}
ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3];
}
// take care of leading and trailing ::
ip_string = ip_string.replace(/^:|:$/g, '');
var ipv6 = ip_string.split(':');
for (var i = 0; i < ipv6.length; i ++) {
var hex = ipv6[i];
if (hex != "") {
// normalize leading zeros
ipv6[i] = ("0000" + hex).substr(-4);
}
else {
// normalize grouped zeros ::
hex = [];
for (var j = ipv6.length; j <= 8; j ++) {
hex.push('0000');
}
ipv6[i] = hex.join(':');
}
}
return ipv6.join(':');
}
window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed = function(allowedOriginRules, scheme, host, port) {
for (var rule of allowedOriginRules) {
if (rule === "*") {
return true;
}
if (scheme == null || scheme === "") {
continue;
}
if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) {
continue;
}
var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port;
var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port;
var IPv6 = null;
if (rule.host != null && rule.host[0] === "[") {
try {
IPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(rule.host.substring(1, rule.host.length - 1));
} catch {}
}
var hostIPv6 = null;
try {
hostIPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(host);
} catch {}
var schemeAllowed = scheme == rule.scheme;
var hostAllowed = rule.host == null ||
rule.host === "" ||
host === rule.host ||
(rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) ||
(hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6);
var portAllowed = rulePort === currentPort;
if (schemeAllowed && hostAllowed && portAllowed) {
return true;
}
}
return false;
}
"""
@@ -0,0 +1,19 @@
//
// WindowIdJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT"
let WINDOW_ID_VARIABLE_JS_SOURCE = "window._\(JAVASCRIPT_BRIDGE_NAME)_windowId"
let WINDOW_ID_INITIALIZE_JS_SOURCE = """
(function() {
\(WINDOW_ID_VARIABLE_JS_SOURCE) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE);
return \(WINDOW_ID_VARIABLE_JS_SOURCE);
})()
"""
@@ -0,0 +1,34 @@
//
// CustomUIPrintPageRenderer.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/22.
//
import Foundation
//
//public class CustomUIPrintPageRenderer: UIPrintPageRenderer {
// private var _numberOfPages: Int?
// private var forceRenderingQuality: Int?
//
// public init(numberOfPage: Int? = nil, forceRenderingQuality: Int? = nil) {
// super.init()
// self._numberOfPages = numberOfPage
// self.forceRenderingQuality = forceRenderingQuality
// }
//
// open override var numberOfPages: Int {
// get {
// return _numberOfPages ?? super.numberOfPages
// }
// }
//
// @available(iOS 14.5, *)
// open override func currentRenderingQuality(forRequested requestedRenderingQuality: UIPrintRenderingQuality) -> UIPrintRenderingQuality {
// if let forceRenderingQuality = forceRenderingQuality,
// let quality = UIPrintRenderingQuality.init(rawValue: forceRenderingQuality) {
// return quality
// }
// return super.currentRenderingQuality(forRequested: requestedRenderingQuality)
// }
//}
@@ -0,0 +1,98 @@
//
// PrintAttributes.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/22.
//
import Foundation
public class PrintAttributes: NSObject {
var orientation: NSPrintInfo.PaperOrientation?
var margins: NSEdgeInsets?
var paperRect: CGRect?
var colorMode: String?
var duplex: Int?
var paperName: String?
var localizedPaperName: String?
var horizontalPagination: UInt?
var verticalPagination: UInt?
var jobDisposition: String?
var printableRect: NSRect?
var isHorizontallyCentered: Bool?
var isVerticallyCentered: Bool?
var isSelectionOnly: Bool?
var scalingFactor: CGFloat?
var jobSavingURL: String?
var detailedErrorReporting: Bool?
var faxNumber: String?
var headerAndFooter: Bool?
var mustCollate: Bool?
var pagesAcross: Int?
var pagesDown: Int?
var time: Int?
public init(fromPrintJobController: PrintJobController) {
super.init()
if let job = fromPrintJobController.job {
let printInfo = job.printInfo
let printInfoDictionary = printInfo.dictionary()
orientation = printInfo.orientation
margins = NSEdgeInsets(top: printInfo.topMargin,
left: printInfo.leftMargin,
bottom: printInfo.bottomMargin,
right: printInfo.rightMargin)
paperRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: printInfo.paperSize)
colorMode = printInfo.printSettings["ColorModel"] as? String
duplex = printInfo.printSettings["com_apple_print_PrintSettings_PMDuplexing"] as? Int
paperName = printInfo.paperName?.rawValue
localizedPaperName = printInfo.localizedPaperName
horizontalPagination = printInfo.horizontalPagination.rawValue
verticalPagination = printInfo.verticalPagination.rawValue
jobDisposition = printInfo.jobDisposition.rawValue
printableRect = printInfo.imageablePageBounds
isHorizontallyCentered = printInfo.isHorizontallyCentered
isVerticallyCentered = printInfo.isVerticallyCentered
isSelectionOnly = printInfo.isSelectionOnly
scalingFactor = printInfo.scalingFactor
jobSavingURL = (printInfoDictionary[NSPrintInfo.AttributeKey.jobSavingURL] as? URL)?.absoluteString
detailedErrorReporting = printInfoDictionary[NSPrintInfo.AttributeKey.detailedErrorReporting] as? Bool
faxNumber = printInfoDictionary[NSPrintInfo.AttributeKey.faxNumber] as? String
headerAndFooter = printInfoDictionary[NSPrintInfo.AttributeKey.headerAndFooter] as? Bool
mustCollate = printInfoDictionary[NSPrintInfo.AttributeKey.mustCollate] as? Bool
pagesAcross = printInfoDictionary[NSPrintInfo.AttributeKey.pagesAcross] as? Int
pagesDown = printInfoDictionary[NSPrintInfo.AttributeKey.pagesDown] as? Int
if let timestamp = (printInfoDictionary[NSPrintInfo.AttributeKey.time] as? Date)?.timeIntervalSince1970 {
time = Int(timestamp)
}
}
}
public func toMap () -> [String:Any?] {
return [
"paperRect": paperRect?.toMap(),
"margins": margins?.toMap(),
"orientation": orientation?.rawValue,
"colorMode": colorMode,
"duplex": duplex,
"paperName": paperName,
"localizedPaperName": localizedPaperName,
"horizontalPagination": horizontalPagination,
"verticalPagination": verticalPagination,
"jobDisposition": jobDisposition,
"printableRect": printableRect?.toMap(),
"isHorizontallyCentered": isHorizontallyCentered,
"isVerticallyCentered": isVerticallyCentered,
"isSelectionOnly": isSelectionOnly,
"scalingFactor": scalingFactor,
"jobSavingURL": jobSavingURL,
"detailedErrorReporting": detailedErrorReporting,
"faxNumber": faxNumber,
"headerAndFooter": headerAndFooter,
"mustCollate": mustCollate,
"pagesAcross": pagesAcross,
"pagesDown": pagesDown,
"time": time
]
}
}
@@ -0,0 +1,60 @@
//
// PrintJobChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
import FlutterMacOS
public class PrintJobChannelDelegate: ChannelDelegate {
private weak var printJobController: PrintJobController?
public init(printJobController: PrintJobController, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.printJobController = printJobController
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getInfo":
if let printJobController = printJobController {
result(printJobController.getInfo()?.toMap())
} else {
result(false)
}
break
case "dispose":
if let printJobController = printJobController {
printJobController.dispose()
result(true)
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onComplete(completed: Bool, error: Error?) {
let arguments: [String: Any?] = [
"completed": completed,
"error": error?.localizedDescription
]
channel?.invokeMethod("onComplete", arguments: arguments)
}
public override func dispose() {
super.dispose()
printJobController = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,93 @@
//
// PrintJob.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
import FlutterMacOS
public enum PrintJobState: Int {
case created = 1
case started = 3
case completed = 5
case canceled = 7
}
public class PrintJobController: NSObject, Disposable {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_"
var id: String
var plugin: InAppWebViewFlutterPlugin?
var job: NSPrintOperation?
var settings: PrintJobSettings?
var channelDelegate: PrintJobChannelDelegate?
var state = PrintJobState.created
var creationTime = Int64(Date().timeIntervalSince1970 * 1000)
private var completionHandler: PrintJobController.CompletionHandler?
public typealias CompletionHandler = (_ printOperation: NSPrintOperation,
_ success: Bool,
_ contextInfo: UnsafeMutableRawPointer?) -> Void
public init(plugin: InAppWebViewFlutterPlugin, id: String, job: NSPrintOperation? = nil, settings: PrintJobSettings? = nil) {
self.id = id
self.plugin = plugin
super.init()
self.job = job
self.settings = settings
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id,
binaryMessenger: registrar.messenger)
self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel)
}
}
public func present(parentWindow: NSWindow? = nil, completionHandler: PrintJobController.CompletionHandler? = nil) {
guard let job = job else {
return
}
state = .started
self.completionHandler = completionHandler
if let mainWindow = parentWindow ?? NSApplication.shared.mainWindow {
job.runModal(for: mainWindow, delegate: self, didRun: #selector(printOperationDidRun), contextInfo: nil)
}
}
@objc func printOperationDidRun(printOperation: NSPrintOperation,
success: Bool,
contextInfo: UnsafeMutableRawPointer?) {
state = success ? .completed : .canceled
channelDelegate?.onComplete(completed: success, error: nil)
if let completionHandler = completionHandler {
completionHandler(printOperation, success, contextInfo)
self.completionHandler = nil
}
}
public func getInfo() -> PrintJobInfo? {
guard let _ = job else {
return nil
}
return PrintJobInfo.init(fromPrintJobController: self)
}
public func disposeNoDismiss() {
channelDelegate?.dispose()
channelDelegate = nil
completionHandler = nil
job = nil
plugin?.printJobManager?.jobs[id] = nil
plugin = nil
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
completionHandler = nil
job = nil
plugin?.printJobManager?.jobs[id] = nil
plugin = nil
}
}
@@ -0,0 +1,80 @@
//
// PrintJobInfo.swift
// flutter_downloader
//
// Created by Lorenzo Pichilli on 10/05/22.
//
import Foundation
public class PrintJobInfo: NSObject {
var state: PrintJobState
var attributes: PrintAttributes
var creationTime: Int64
var numberOfPages: Int?
var copies: Int?
var label: String?
var printer: NSPrinter?
var pageOrder: Int?
var preferredRenderingQuality: Int?
var showsProgressPanel: Bool?
var showsPrintPanel: Bool?
var canSpawnSeparateThread: Bool?
var isCopyingOperation: Bool?
var currentPage: Int?
var firstPage: Int?
var lastPage: Int?
public init(fromPrintJobController: PrintJobController) {
state = fromPrintJobController.state
creationTime = fromPrintJobController.creationTime
attributes = PrintAttributes.init(fromPrintJobController: fromPrintJobController)
super.init()
if let job = fromPrintJobController.job {
let printInfo = job.printInfo
let printInfoDictionary = printInfo.dictionary()
printer = printInfo.printer
copies = printInfo.printSettings["com_apple_print_PrintSettings_PMCopies"] as? Int
label = job.jobTitle
firstPage = printInfoDictionary[NSPrintInfo.AttributeKey.firstPage] as? Int
lastPage = printInfoDictionary[NSPrintInfo.AttributeKey.lastPage] as? Int
if let firstPage = firstPage, let lastPage = lastPage {
numberOfPages = lastPage - firstPage + 1
}
if numberOfPages == nil {
numberOfPages = printInfo.printSettings["com_apple_print_PrintSettings_PMLastPage"] as? Int
if numberOfPages == nil || numberOfPages! > job.pageRange.length {
numberOfPages = job.pageRange.length
}
}
pageOrder = job.pageOrder.rawValue
preferredRenderingQuality = job.preferredRenderingQuality.rawValue
showsProgressPanel = job.showsProgressPanel
showsPrintPanel = job.showsPrintPanel
canSpawnSeparateThread = job.canSpawnSeparateThread
isCopyingOperation = job.isCopyingOperation
currentPage = job.currentPage
}
}
public func toMap () -> [String:Any?] {
return [
"state": state.rawValue,
"attributes": attributes.toMap(),
"numberOfPages": numberOfPages,
"copies": copies,
"creationTime": creationTime,
"label": label,
"printer": printer?.toMap(),
"pageOrder": pageOrder,
"preferredRenderingQuality": preferredRenderingQuality,
"showsProgressPanel": showsProgressPanel,
"showsPrintPanel": showsPrintPanel,
"canSpawnSeparateThread": canSpawnSeparateThread,
"isCopyingOperation": isCopyingOperation,
"currentPage": currentPage,
"firstPage": firstPage,
"lastPage": lastPage
]
}
}
@@ -0,0 +1,31 @@
//
// PrintJobManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
public class PrintJobManager: NSObject, Disposable {
var plugin: InAppWebViewFlutterPlugin?
var jobs: [String: PrintJobController?] = [:]
public init(plugin: InAppWebViewFlutterPlugin?) {
super.init()
self.plugin = plugin
}
public func dispose() {
let jobValues = jobs.values
jobValues.forEach { (job: PrintJobController?) in
job?.dispose()
}
jobs.removeAll()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,210 @@
//
// PrintJobSettings.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
@objcMembers
public class PrintJobSettings: ISettings<PrintJobController> {
public var handledByClient = false
public var jobName: String?
public var _orientation: NSNumber?
public var orientation: Int? {
get {
return _orientation?.intValue
}
set {
if let newValue = newValue {
_orientation = NSNumber.init(value: newValue)
} else {
_orientation = nil
}
}
}
public var _numberOfPages: NSNumber?
public var numberOfPages: Int64? {
get {
return _numberOfPages?.int64Value
}
set {
if let newValue = newValue {
_numberOfPages = NSNumber.init(value: newValue)
} else {
_numberOfPages = nil
}
}
}
public var margins: NSEdgeInsets?
public var colorMode: String?
public var showsNumberOfCopies = true
public var showsPaperOrientation = true
public var showsPaperSize = true
public var showsScaling = true
public var showsPageRange = true
public var showsPageSetupAccessory = true
public var showsPreview = true
public var showsPrintSelection = true
public var showsPrintPanel = true
public var showsProgressPanel = true
public var _scalingFactor: NSNumber?
public var scalingFactor: Double? {
get {
return _scalingFactor?.doubleValue
}
set {
if let newValue = newValue {
_scalingFactor = NSNumber.init(value: newValue)
} else {
_scalingFactor = nil
}
}
}
public var jobDisposition: String?
public var jobSavingURL: String?
public var paperName: String?
public var _horizontalPagination: NSNumber?
public var horizontalPagination: UInt? {
get {
return _horizontalPagination?.uintValue
}
set {
if let newValue = newValue {
_horizontalPagination = NSNumber.init(value: newValue)
} else {
_horizontalPagination = nil
}
}
}
public var _verticalPagination: NSNumber?
public var verticalPagination: UInt? {
get {
return _verticalPagination?.uintValue
}
set {
if let newValue = newValue {
_verticalPagination = NSNumber.init(value: newValue)
} else {
_verticalPagination = nil
}
}
}
public var isHorizontallyCentered = true
public var isVerticallyCentered = true
public var _pageOrder: NSNumber?
public var pageOrder: Int? {
get {
return _pageOrder?.intValue
}
set {
if let newValue = newValue {
_pageOrder = NSNumber.init(value: newValue)
} else {
_pageOrder = nil
}
}
}
public var canSpawnSeparateThread = true
public var copies = 1
public var _firstPage: NSNumber?
public var firstPage: Int64? {
get {
return _firstPage?.int64Value
}
set {
if let newValue = newValue {
_firstPage = NSNumber.init(value: newValue)
} else {
_firstPage = nil
}
}
}
public var _lastPage: NSNumber?
public var lastPage: Int64? {
get {
return _lastPage?.int64Value
}
set {
if let newValue = newValue {
_lastPage = NSNumber.init(value: newValue)
} else {
_lastPage = nil
}
}
}
public var detailedErrorReporting = false
public var faxNumber: String?
public var headerAndFooter = true
public var _mustCollate: NSNumber?
public var mustCollate: Bool? {
get {
return _mustCollate?.boolValue
}
set {
if let newValue = newValue {
_mustCollate = NSNumber.init(value: newValue)
} else {
_mustCollate = nil
}
}
}
public var _pagesAcross: NSNumber?
public var pagesAcross: Int64? {
get {
return _pagesAcross?.int64Value
}
set {
if let newValue = newValue {
_pagesAcross = NSNumber.init(value: newValue)
} else {
_pagesAcross = nil
}
}
}
public var _pagesDown: NSNumber?
public var pagesDown: Int64? {
get {
return _pagesDown?.int64Value
}
set {
if let newValue = newValue {
_pagesDown = NSNumber.init(value: newValue)
} else {
_pagesDown = nil
}
}
}
public var _time: NSNumber?
public var time: Int64? {
get {
return _time?.int64Value
}
set {
if let newValue = newValue {
_time = NSNumber.init(value: newValue)
} else {
_time = nil
}
}
}
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> PrintJobSettings {
let _ = super.parse(settings: settings)
if let marginsMap = settings["margins"] as? [String : Double] {
margins = NSEdgeInsets.fromMap(map: marginsMap)
}
return self
}
override func getRealSettings(obj: PrintJobController?) -> [String: Any?] {
var realOptions: [String: Any?] = toMap()
return realOptions
}
}
@@ -0,0 +1,32 @@
//
// BaseCallbackResult.swift
// shared-apple
//
// Created by Lorenzo Pichilli on 17/10/22.
//
import Foundation
public class BaseCallbackResult<T>: CallbackResult<T> {
override init() {
super.init()
self.success = { [weak self] (obj: Any?) in
let result: T? = self?.decodeResult(obj)
var shouldRunDefaultBehaviour = false
if let result = result {
shouldRunDefaultBehaviour = self?.nonNullSuccess(result) ?? shouldRunDefaultBehaviour
} else {
shouldRunDefaultBehaviour = self?.nullSuccess() ?? shouldRunDefaultBehaviour
}
if shouldRunDefaultBehaviour {
self?.defaultBehaviour(result)
}
}
self.notImplemented = { [weak self] in
self?.defaultBehaviour(nil)
}
}
}
@@ -0,0 +1,23 @@
//
// CGRect.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/22.
//
import Foundation
extension CGRect {
public static func fromMap(map: [String: Double]) -> CGRect {
return CGRect(x: map["x"]!, y: map["y"]!, width: map["width"]!, height: map["height"]!)
}
public func toMap () -> [String:Any?] {
return [
"x": minX,
"y": minY,
"width": width,
"height": height
]
}
}
@@ -0,0 +1,18 @@
//
// CallbackResult.swift
// shared-apple
//
// Created by Lorenzo Pichilli on 17/10/22.
//
import Foundation
public class CallbackResult<T>: MethodChannelResult {
public var notImplemented: () -> Void = {}
public var success: (Any?) -> Void = {_ in }
public var error: (String, String?, Any?) -> Void = {_,_,_ in }
public var nonNullSuccess: (T) -> Bool = {_ in true}
public var nullSuccess: () -> Bool = {true}
public var defaultBehaviour: (T?) -> Void = {_ in }
public var decodeResult: (Any?) -> T? = {_ in nil}
}
@@ -0,0 +1,28 @@
//
// ChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 04/05/22.
//
import Foundation
import FlutterMacOS
public class ChannelDelegate: FlutterMethodCallDelegate, Disposable {
var channel: FlutterMethodChannel?
public init(channel: FlutterMethodChannel) {
super.init()
self.channel = channel
self.channel?.setMethodCallHandler(handle)
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
}
public func dispose() {
channel?.setMethodCallHandler(nil)
channel = nil
}
}
@@ -0,0 +1,22 @@
//
// ClientCertChallenge.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/02/21.
//
import Foundation
public class ClientCertChallenge: NSObject {
var protectionSpace: URLProtectionSpace!
public init(fromChallenge: URLAuthenticationChallenge) {
protectionSpace = fromChallenge.protectionSpace
}
public func toMap () -> [String:Any?] {
return [
"protectionSpace": protectionSpace.toMap(),
]
}
}
@@ -0,0 +1,33 @@
//
// ClientCertResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
public class ClientCertResponse: NSObject {
var certificatePath: String
var certificatePassword: String?
var keyStoreType: String?
var action: Int?
public init(certificatePath: String, certificatePassword: String? = nil, keyStoreType: String? = nil, action: Int? = nil) {
self.certificatePath = certificatePath
self.certificatePassword = certificatePassword
self.keyStoreType = keyStoreType
self.action = action
}
public static func fromMap(map: [String:Any?]?) -> ClientCertResponse? {
guard let map = map else {
return nil
}
let certificatePath = map["certificatePath"] as! String
let certificatePassword = map["certificatePassword"] as? String
let keyStoreType = map["keyStoreType"] as? String
let action = map["action"] as? Int
return ClientCertResponse(certificatePath: certificatePath, certificatePassword: certificatePassword, keyStoreType: keyStoreType, action: action)
}
}
@@ -0,0 +1,31 @@
//
// CreateWindowAction.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
import WebKit
public class CreateWindowAction: NSObject {
var navigationAction: WKNavigationAction
var windowId: Int64
var windowFeatures: WKWindowFeatures
var isDialog: Bool?
public init(navigationAction: WKNavigationAction, windowId: Int64, windowFeatures: WKWindowFeatures, isDialog: Bool? = nil) {
self.navigationAction = navigationAction
self.windowId = windowId
self.windowFeatures = windowFeatures
self.isDialog = isDialog
}
public func toMap () -> [String:Any?] {
var map = navigationAction.toMap()
map["windowId"] = windowId
map["windowFeatures"] = windowFeatures.toMap()
map["isDialog"] = isDialog
return map
}
}
@@ -0,0 +1,31 @@
//
// CustomSchemeResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
import FlutterMacOS
public class CustomSchemeResponse: NSObject {
var data: Data
var contentType: String
var contentEncoding: String
public init(data: Data, contentType: String, contentEncoding: String) {
self.data = data
self.contentType = contentType
self.contentEncoding = contentEncoding
}
public static func fromMap(map: [String:Any?]?) -> CustomSchemeResponse? {
guard let map = map else {
return nil
}
let data = map["data"] as! FlutterStandardTypedData
let contentType = map["contentType"] as! String
let contentEncoding = map["contentEncoding"] as! String
return CustomSchemeResponse(data: data.data, contentType: contentType, contentEncoding: contentEncoding)
}
}
@@ -0,0 +1,12 @@
//
// Disposable.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 04/05/22.
//
import Foundation
public protocol Disposable {
func dispose() -> Void
}
@@ -0,0 +1,42 @@
//
// DownloadStartRequest.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 17/04/22.
//
import Foundation
public class DownloadStartRequest: NSObject {
var url: String
var userAgent: String?
var contentDisposition: String?
var mimeType: String?
var contentLength: Int64
var suggestedFilename: String?
var textEncodingName: String?
public init(url: String, userAgent: String?, contentDisposition: String?,
mimeType: String?, contentLength: Int64,
suggestedFilename: String?, textEncodingName: String?) {
self.url = url
self.userAgent = userAgent
self.contentDisposition = contentDisposition
self.mimeType = mimeType
self.contentLength = contentLength
self.suggestedFilename = suggestedFilename
self.textEncodingName = textEncodingName
}
public func toMap () -> [String:Any?] {
return [
"url": url,
"userAgent": userAgent,
"contentDisposition": contentDisposition,
"mimeType": mimeType,
"contentLength": contentLength,
"suggestedFilename": suggestedFilename,
"textEncodingName": textEncodingName
]
}
}
@@ -0,0 +1,28 @@
//
// UIFindSession.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
public class FindSession: NSObject {
var resultCount: Int
var highlightedResultIndex: Int
var searchResultDisplayStyle: Int
public init(resultCount: Int, highlightedResultIndex: Int, searchResultDisplayStyle: Int) {
self.resultCount = resultCount
self.highlightedResultIndex = highlightedResultIndex
self.searchResultDisplayStyle = searchResultDisplayStyle
}
public func toMap () -> [String:Any?] {
return [
"resultCount": resultCount,
"highlightedResultIndex": highlightedResultIndex,
"searchResultDisplayStyle": searchResultDisplayStyle
]
}
}
Loaded 100 of 556 files, more files were not shown because too many files have changed in this diff. Show more