diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 353a40eb..57f6868a 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -15,47 +15,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
-
+
+
+
+
-
@@ -78,38 +54,17 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -120,22 +75,22 @@
-
-
+
+
-
+
-
+
-
-
+
+
-
+
@@ -153,9 +108,6 @@
- onSafeBrowsingHit
- SafeBrowsingResponse
- getDomainName
a
as
IABWebViewClient
@@ -168,7 +120,6 @@
HttpAuthResponse
ClientCertResponse
onReceivedServerTrustAuthRequest
- on
ServerTrustAuthResponse
certific
CER
@@ -181,8 +132,12 @@
preferredContentMode
print
ConsoleMessageLevel
- _findLocalPath
onConsoleMessage
+ _findLocalPath
+ package
+ on
+ findAll
+ getFave
activity.getPreferences(0)
@@ -231,35 +186,34 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
@@ -270,36 +224,17 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
@@ -326,12 +261,31 @@
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -487,10 +441,10 @@
-
+
-
+
@@ -501,7 +455,7 @@
-
+
@@ -537,20 +491,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -650,16 +590,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -687,13 +617,6 @@
-
-
-
-
-
-
-
@@ -722,16 +645,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -753,16 +666,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -775,19 +678,9 @@
-
-
-
-
-
-
-
-
-
-
@@ -816,10 +709,47 @@
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -828,61 +758,86 @@
-
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93ada8b6..44e0366f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,7 +25,7 @@
- Added `clearCache()` method
- Added `HttpAuthCredentialDatabase` class
- Added `onReceivedServerTrustAuthRequest` and `onReceivedClientCertRequest` events to manage SSL requests
-
+- Added `onFindResultReceived` event, `findAllAsync`, `findNext` and `clearMatches` methods
### BREAKING CHANGES
- Deleted `WebResourceRequest` class
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java
index 16fbce8a..c291cea5 100644
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java
@@ -94,8 +94,6 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
@Override
public void onMethodCall(MethodCall call, final Result result) {
- String source;
- String urlFile;
switch (call.method) {
case "getUrl":
result.success((webView != null) ? webView.getUrl() : null);
@@ -108,22 +106,22 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
break;
case "loadUrl":
if (webView != null)
- webView.loadUrl(call.argument("url").toString(), (Map) call.argument("headers"), result);
+ webView.loadUrl((String) call.argument("url"), (Map) call.argument("headers"), result);
else
result.success(false);
break;
case "postUrl":
if (webView != null)
- webView.postUrl(call.argument("url").toString(), (byte[]) call.argument("postData"), result);
+ webView.postUrl((String) call.argument("url"), (byte[]) call.argument("postData"), result);
else
result.success(false);
break;
case "loadData":
{
- String data = call.argument("data").toString();
- String mimeType = call.argument("mimeType").toString();
- String encoding = call.argument("encoding").toString();
- String baseUrl = call.argument("baseUrl").toString();
+ String data = (String) call.argument("data");
+ String mimeType = (String) call.argument("mimeType");
+ String encoding = (String) call.argument("encoding");
+ String baseUrl = (String) call.argument("baseUrl");
if (webView != null)
webView.loadData(data, mimeType, encoding, baseUrl, result);
@@ -133,13 +131,13 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
break;
case "loadFile":
if (webView != null)
- webView.loadFile(call.argument("url").toString(), (Map) call.argument("headers"), result);
+ webView.loadFile((String) call.argument("url"), (Map) call.argument("headers"), result);
else
result.success(false);
break;
case "injectScriptCode":
if (webView != null) {
- source = call.argument("source").toString();
+ String source = (String) call.argument("source");
webView.injectScriptCode(source, result);
}
else {
@@ -148,21 +146,21 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
break;
case "injectScriptFile":
if (webView != null) {
- urlFile = call.argument("urlFile").toString();
+ String urlFile = (String) call.argument("urlFile");
webView.injectScriptFile(urlFile);
}
result.success(true);
break;
case "injectStyleCode":
if (webView != null) {
- source = call.argument("source").toString();
+ String source = (String) call.argument("source");
webView.injectStyleCode(source);
}
result.success(true);
break;
case "injectStyleFile":
if (webView != null) {
- urlFile = call.argument("urlFile").toString();
+ String urlFile = (String) call.argument("urlFile");
webView.injectStyleFile(urlFile);
}
result.success(true);
@@ -267,6 +265,30 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
result.success(false);
}
break;
+ case "findAllAsync":
+ if (webView != null) {
+ String find = (String) call.argument("find");
+ webView.findAllAsync(find);
+ }
+ result.success(true);
+ break;
+ case "findNext":
+ if (webView != null) {
+ Boolean forward = (Boolean) call.argument("forward");
+ webView.findNext(forward);
+ result.success(true);
+ } else {
+ result.success(false);
+ }
+ break;
+ case "clearMatches":
+ if (webView != null) {
+ webView.clearMatches();
+ result.success(true);
+ } else {
+ result.success(false);
+ }
+ break;
case "dispose":
dispose();
result.success(true);
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java
index 35274e80..87b112f1 100644
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java
@@ -499,4 +499,27 @@ public class InAppBrowserActivity extends AppCompatActivity {
else
result.success(false);
}
+
+ public void findAllAsync(String find) {
+ if (webView != null)
+ webView.findAllAsync(find);
+ }
+
+ public void findNext(Boolean forward, MethodChannel.Result result) {
+ if (webView != null) {
+ webView.findNext(forward);
+ result.success(true);
+ }
+ else
+ result.success(false);
+ }
+
+ public void clearMatches(MethodChannel.Result result) {
+ if (webView != null) {
+ webView.clearMatches();
+ result.success(true);
+ }
+ else
+ result.success(false);
+ }
}
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java
index 4fc2c55b..9eb4e24e 100644
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java
@@ -103,7 +103,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
case "open":
boolean isData = (boolean) call.argument("isData");
if (!isData) {
- final String url_final = call.argument("url").toString();
+ final String url_final = (String) call.argument("url");
final boolean useChromeSafariBrowser = (boolean) call.argument("useChromeSafariBrowser");
@@ -174,10 +174,10 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
@Override
public void run() {
HashMap options = (HashMap) call.argument("options");
- String data = call.argument("data").toString();
- String mimeType = call.argument("mimeType").toString();
- String encoding = call.argument("encoding").toString();
- String baseUrl = call.argument("baseUrl").toString();
+ String data = (String) call.argument("data");
+ String mimeType = (String) call.argument("mimeType");
+ String encoding = (String) call.argument("encoding");
+ String baseUrl = (String) call.argument("baseUrl");
openData(activity, uuid, options, data, mimeType, encoding, baseUrl);
result.success(true);
}
@@ -194,42 +194,42 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
result.success(getProgress(uuid));
break;
case "loadUrl":
- loadUrl(uuid, call.argument("url").toString(), (Map) call.argument("headers"), result);
+ loadUrl(uuid, (String) call.argument("url"), (Map) call.argument("headers"), result);
break;
case "postUrl":
- postUrl(uuid, call.argument("url").toString(), (byte[]) call.argument("postData"), result);
+ postUrl(uuid, (String) call.argument("url"), (byte[]) call.argument("postData"), result);
break;
case "loadData":
{
- String data = call.argument("data").toString();
- String mimeType = call.argument("mimeType").toString();
- String encoding = call.argument("encoding").toString();
- String baseUrl = call.argument("baseUrl").toString();
+ String data = (String) call.argument("data");
+ String mimeType = (String) call.argument("mimeType");
+ String encoding = (String) call.argument("encoding");
+ String baseUrl = (String) call.argument("baseUrl");
loadData(uuid, data, mimeType, encoding, baseUrl, result);
}
break;
case "loadFile":
- loadFile(uuid, call.argument("url").toString(), (Map) call.argument("headers"), result);
+ loadFile(uuid, (String) call.argument("url"), (Map) call.argument("headers"), result);
break;
case "close":
close(activity, uuid, result);
break;
case "injectScriptCode":
- source = call.argument("source").toString();
+ source = (String) call.argument("source");
injectScriptCode(uuid, source, result);
break;
case "injectScriptFile":
- urlFile = call.argument("urlFile").toString();
+ urlFile = (String) call.argument("urlFile");
injectScriptFile(uuid, urlFile);
result.success(true);
break;
case "injectStyleCode":
- source = call.argument("source").toString();
+ source = (String) call.argument("source");
injectStyleCode(uuid, source);
result.success(true);
break;
case "injectStyleFile":
- urlFile = call.argument("urlFile").toString();
+ urlFile = (String) call.argument("urlFile");
injectStyleFile(uuid, urlFile);
result.success(true);
break;
@@ -318,6 +318,18 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
case "clearClientCertPreferences":
clearClientCertPreferences(uuid, result);
break;
+ case "findAllAsync":
+ String find = (String) call.argument("find");
+ findAllAsync(uuid, find);
+ result.success(true);
+ break;
+ case "findNext":
+ Boolean forward = (Boolean) call.argument("forward");
+ findNext(uuid, forward, result);
+ break;
+ case "clearMatches":
+ clearMatches(uuid, result);
+ break;
default:
result.notImplemented();
}
@@ -713,10 +725,30 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
inAppBrowserActivity.clearSslPreferences();
}
- private void clearClientCertPreferences(String uuid, Result result) {
+ public void clearClientCertPreferences(String uuid, Result result) {
InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid);
if (inAppBrowserActivity != null)
inAppBrowserActivity.clearClientCertPreferences(result);
result.success(false);
}
+
+ public void findAllAsync(String uuid, String find) {
+ InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid);
+ if (inAppBrowserActivity != null)
+ inAppBrowserActivity.findAllAsync(find);
+ }
+
+ public void findNext(String uuid, Boolean forward, Result result) {
+ InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid);
+ if (inAppBrowserActivity != null)
+ inAppBrowserActivity.findNext(forward, result);
+ result.success(false);
+ }
+
+ public void clearMatches(String uuid, Result result) {
+ InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid);
+ if (inAppBrowserActivity != null)
+ inAppBrowserActivity.clearMatches(result);
+ result.success(false);
+ }
}
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java
index 66a0ab96..3fa73093 100644
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java
@@ -242,6 +242,19 @@ final public class InAppWebView extends InputAwareWebView {
ContentBlockerAction action = ContentBlockerAction.fromMap(contentBlocker.get("action"));
contentBlockerHandler.getRuleList().add(new ContentBlocker(trigger, action));
}
+
+ setFindListener(new FindListener() {
+ @Override
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
+ Map obj = new HashMap<>();
+ if (inAppBrowserActivity != null)
+ obj.put("uuid", inAppBrowserActivity.uuid);
+ obj.put("activeMatchOrdinal", activeMatchOrdinal);
+ obj.put("numberOfMatches", numberOfMatches);
+ obj.put("isDoneCounting", isDoneCounting);
+ getChannel().invokeMethod("onFindResultReceived", obj);
+ }
+ });
}
public void loadUrl(String url, MethodChannel.Result result) {
diff --git a/example/lib/chrome_safari_example.screen.dart b/example/lib/chrome_safari_example.screen.dart
index 12a9270a..1190547a 100644
--- a/example/lib/chrome_safari_example.screen.dart
+++ b/example/lib/chrome_safari_example.screen.dart
@@ -1,5 +1,3 @@
-import 'dart:async';
-
import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
diff --git a/example/lib/inline_example.screen.dart b/example/lib/inline_example.screen.dart
old mode 100644
new mode 100755
index b3aaf63e..f5567ac4
--- a/example/lib/inline_example.screen.dart
+++ b/example/lib/inline_example.screen.dart
@@ -1,393 +1,396 @@
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
-import 'package:flutter_downloader/flutter_downloader.dart';
-import 'package:path_provider/path_provider.dart';
-import 'package:flutter/services.dart' show rootBundle;
-
-class InlineExampleScreen extends StatefulWidget {
- @override
- _InlineExampleScreenState createState() => new _InlineExampleScreenState();
-}
-
-class Foo {
- String bar;
- String baz;
-
- Foo({this.bar, this.baz});
-
- Map toJson() {
- return {
- 'bar': this.bar,
- 'baz': this.baz
- };
- }
-}
-
-class _InlineExampleScreenState extends State {
- InAppWebViewController webView;
- String url = "";
- double progress = 0;
-
- TextEditingController _textFieldController = TextEditingController();
-
- @override
- void initState() {
- super.initState();
- }
-
- @override
- void dispose() {
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
-// HttpAuthCredentialDatabase.instance().clearAllAuthCredentials();
-//
-// HttpAuthCredentialDatabase.instance().getHttpAuthCredentials(ProtectionSpace(host: "192.168.1.20", protocol: "http", realm: "Node", port: 8081)).then((credentials) {
-// for (var credential in credentials )
-// print("\n\nCREDENTIAL: ${credential.username} ${credential.password}\n\n");
-// });
-// HttpAuthCredentialDatabase.instance().getAllAuthCredentials().then((result) {
-// for (var r in result) {
-// ProtectionSpace protectionSpace = r["protectionSpace"];
-// print("\n\nProtectionSpace: ${protectionSpace.protocol} ${protectionSpace.host}:");
-// List credentials = r["credentials"];
-// for (var credential in credentials)
-// print("\tCREDENTIAL: ${credential.username} ${credential.password}");
-// }
-// });
-// HttpAuthCredentialDatabase.instance().setHttpAuthCredential(ProtectionSpace(host: "192.168.1.20", protocol: "http", realm: "Node", port: 8081), HttpAuthCredential(username: "user 1", password: "password 1"));
-// HttpAuthCredentialDatabase.instance().setHttpAuthCredential(ProtectionSpace(host: "192.168.1.20", protocol: "http", realm: "Node", port: 8081), HttpAuthCredential(username: "user 2", password: "password 2"));
-
- return Container(
- child: Column(children: [
- Container(
- padding: EdgeInsets.all(20.0),
- child: Text(
- "CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"),
- ),
- Container(
- padding: EdgeInsets.all(10.0),
- child: progress < 1.0 ? LinearProgressIndicator(value: progress) : Container()
- ),
- Expanded(
- child: Container(
- margin: const EdgeInsets.all(10.0),
- decoration:
- BoxDecoration(border: Border.all(color: Colors.blueAccent)),
- child: InAppWebView(
- //initialUrl: "https://www.youtube.com/embed/M7lc1UVf-VE?playsinline=1",
- initialUrl: "https://flutter.dev/",
- //initialUrl: "chrome://safe-browsing/match?type=malware",
- //initialUrl: "http://192.168.1.20:8081/",
- //initialUrl: "https://192.168.1.20:4433/",
- //initialFile: "assets/index.html",
- initialHeaders: {},
- initialOptions: [
- InAppWebViewOptions(
- //clearCache: true,
- useShouldOverrideUrlLoading: true,
- useOnTargetBlank: true,
- //useOnLoadResource: true,
- useOnDownloadStart: true,
- preferredContentMode: InAppWebViewUserPreferredContentMode.DESKTOP,
- resourceCustomSchemes: ["my-special-custom-scheme"],
- /*contentBlockers: [
- ContentBlocker(
- ContentBlockerTrigger(".*",
- resourceType: [ContentBlockerTriggerResourceType.IMAGE, ContentBlockerTriggerResourceType.STYLE_SHEET],
- ifTopUrl: ["https://getbootstrap.com/"]),
- ContentBlockerAction(ContentBlockerActionType.BLOCK)
- )
- ]*/
- ),
- AndroidInAppWebViewOptions(
- databaseEnabled: true,
- appCacheEnabled: true,
- domStorageEnabled: true,
- geolocationEnabled: true,
- safeBrowsingEnabled: true,
- //blockNetworkImage: true,
- ),
- ],
- onWebViewCreated: (InAppWebViewController controller) {
- webView = controller;
-
- if (Platform.isAndroid)
- webView.startSafeBrowsing();
-
- webView.addJavaScriptHandler('handlerFoo', (args) {
- return new Foo(bar: 'bar_value', baz: 'baz_value');
- });
-
- webView.addJavaScriptHandler('handlerFooWithArgs', (args) {
- print(args);
- return [args[0] + 5, !args[1], args[2][0], args[3]['foo']];
- });
- },
- onLoadStart: (InAppWebViewController controller, String url) {
- print("started $url");
- setState(() {
- this.url = url;
- });
- },
- onLoadStop: (InAppWebViewController controller, String url) async {
- print("stopped $url");
- if (Platform.isAndroid) {
- controller.clearSslPreferences();
- controller.clearClientCertPreferences();
- }
-// controller.injectScriptCode("""
-// document.getElementById("SEARCH_WORD" + 5).scrollIntoView();
-// """);
- },
- onLoadError: (InAppWebViewController controller, String url, int code, String message) async {
- print("error $url: $code, $message");
- },
- onProgressChanged:
- (InAppWebViewController controller, int progress) {
- setState(() {
- this.progress = progress / 100;
- });
- },
- shouldOverrideUrlLoading: (InAppWebViewController controller, String url) {
- print("override $url");
- controller.loadUrl(url);
- },
- onLoadResource: (InAppWebViewController controller, WebResourceResponse response) {
- print("Resource type: '"+response.initiatorType + "' started at: " +
- response.startTime.toString() +
- "ms ---> duration: " +
- response.duration.toString() +
- "ms " +
- response.url);
- },
- onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) {
- print("""
- console output:
- sourceURL: ${consoleMessage.sourceURL}
- lineNumber: ${consoleMessage.lineNumber}
- message: ${consoleMessage.message}
- messageLevel: ${consoleMessage.messageLevel.toValue()}
- """);
- },
- onDownloadStart: (InAppWebViewController controller, String url) async {
-// final taskId = await FlutterDownloader.enqueue(
-// url: url,
-// savedDir: await _findLocalPath(),
-// showNotification: true, // show download progress in status bar (for Android)
-// openFileFromNotification: true, // click on notification to open downloaded file (for Android)
-// );
- },
- onLoadResourceCustomScheme: (InAppWebViewController controller, String scheme, String url) async {
- if (scheme == "my-special-custom-scheme") {
- var bytes = await rootBundle.load("assets/" + url.replaceFirst("my-special-custom-scheme://", "", 0));
- var asBase64 = base64.encode(bytes.buffer.asUint8List());
- var response = new CustomSchemeResponse(asBase64, "image/svg+xml", contentEnconding: "utf-8");
- return response;
- }
- return null;
- },
- onTargetBlank: (InAppWebViewController controller, String url) {
- print("target _blank: " + url);
- controller.loadUrl(url);
- },
- onGeolocationPermissionsShowPrompt: (InAppWebViewController controller, String origin) async {
- GeolocationPermissionShowPromptResponse response;
-
- await showDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- title: Text("Permission Geolocation API"),
- content: Text("Can we use Geolocation API?"),
- actions: [
- FlatButton(
- child: Text("Close"),
- onPressed: () {
- response = new GeolocationPermissionShowPromptResponse(origin, false, false);
- Navigator.of(context).pop();
- },
- ),
- FlatButton(
- child: Text("Accept"),
- onPressed: () {
- response = new GeolocationPermissionShowPromptResponse(origin, true, true);
- Navigator.of(context).pop();
- },
- ),
- ],
- );
- },
- );
-
- return response;
- },
- onJsAlert: (InAppWebViewController controller, String message) async {
- JsAlertResponseAction action = await createAlertDialog(context, message);
- return new JsAlertResponse(handledByClient: true, action: action);
- },
- onJsConfirm: (InAppWebViewController controller, String message) async {
- JsConfirmResponseAction action = await createConfirmDialog(context, message);
- return new JsConfirmResponse(handledByClient: true, action: action);
- },
- onJsPrompt: (InAppWebViewController controller, String message, String defaultValue) async {
- _textFieldController.text = defaultValue;
- JsPromptResponseAction action = await createPromptDialog(context, message);
- return new JsPromptResponse(handledByClient: true, action: action, value: _textFieldController.text);
- },
- onSafeBrowsingHit: (InAppWebViewController controller, String url, SafeBrowsingThreat threatType) async {
- SafeBrowsingResponseAction action = SafeBrowsingResponseAction.BACK_TO_SAFETY;
- return new SafeBrowsingResponse(report: true, action: action);
- },
- onReceivedHttpAuthRequest: (InAppWebViewController controller, HttpAuthChallenge challenge) async {
- print("HTTP AUTH REQUEST: ${challenge.protectionSpace.host}, realm: ${challenge.protectionSpace.realm}, previous failure count: ${challenge.previousFailureCount.toString()}");
-
- return new HttpAuthResponse(username: "USERNAME", password: "PASSWORD", action: HttpAuthResponseAction.USE_SAVED_HTTP_AUTH_CREDENTIALS, permanentPersistence: true);
- },
- onReceivedServerTrustAuthRequest: (InAppWebViewController controller, ServerTrustChallenge challenge) async {
- print("SERVER TRUST AUTH REQUEST: ${challenge.protectionSpace.host}, SSL ERROR CODE: ${challenge.error.toString()}, MESSAGE: ${challenge.message}");
-
- return new ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.PROCEED);
- },
- onReceivedClientCertRequest: (InAppWebViewController controller, ClientCertChallenge challenge) async {
- print("CLIENT CERT REQUEST: ${challenge.protectionSpace.host}");
-
- return new ClientCertResponse(certificatePath: "assets/certificate.pfx", certificatePassword: "", androidKeyStoreType: "PKCS12", action: ClientCertResponseAction.PROCEED);
- },
- ),
- ),
- ),
- ButtonBar(
- alignment: MainAxisAlignment.center,
- children: [
- RaisedButton(
- child: Icon(Icons.arrow_back),
- onPressed: () {
- if (webView != null) {
- webView.goBack();
- }
- },
- ),
- RaisedButton(
- child: Icon(Icons.arrow_forward),
- onPressed: () {
- if (webView != null) {
- webView.goForward();
- }
- },
- ),
- RaisedButton(
- child: Icon(Icons.refresh),
- onPressed: () {
- if (webView != null) {
- webView.reload();
- }
- },
- ),
- ],
- ),
- ]));
- }
-
- Future createAlertDialog(BuildContext context, String message) async {
- JsAlertResponseAction action;
-
- await showDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- content: Text(message),
- actions: [
- FlatButton(
- child: Text("Ok"),
- onPressed: () {
- action = JsAlertResponseAction.CONFIRM;
- Navigator.of(context).pop();
- },
- ),
- ],
- );
- },
- );
-
- return action;
- }
-
- Future createConfirmDialog(BuildContext context, String message) async {
- JsConfirmResponseAction action;
-
- await showDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- content: Text(message),
- actions: [
- FlatButton(
- child: Text("Cancel"),
- onPressed: () {
- action = JsConfirmResponseAction.CANCEL;
- Navigator.of(context).pop();
- },
- ),
- FlatButton(
- child: Text("Ok"),
- onPressed: () {
- action = JsConfirmResponseAction.CONFIRM;
- Navigator.of(context).pop();
- },
- ),
- ],
- );
- },
- );
-
- return action;
- }
-
- Future createPromptDialog(BuildContext context, String message) async {
- JsPromptResponseAction action;
-
- await showDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- title: Text(message),
- content: TextField(
- controller: _textFieldController,
- ),
- actions: [
- FlatButton(
- child: Text("Cancel"),
- onPressed: () {
- action = JsPromptResponseAction.CANCEL;
- Navigator.of(context).pop();
- },
- ),
- FlatButton(
- child: Text("Ok"),
- onPressed: () {
- action = JsPromptResponseAction.CONFIRM;
- Navigator.of(context).pop();
- },
- ),
- ],
- );
- },
- );
-
- return action;
- }
-
- Future _findLocalPath() async {
- final directory = Platform.isAndroid
- ? await getExternalStorageDirectory()
- : await getApplicationDocumentsDirectory();
- return directory.path;
- }
-}
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
+import 'package:flutter_downloader/flutter_downloader.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:flutter/services.dart' show rootBundle;
+
+class InlineExampleScreen extends StatefulWidget {
+ @override
+ _InlineExampleScreenState createState() => new _InlineExampleScreenState();
+}
+
+class Foo {
+ String bar;
+ String baz;
+
+ Foo({this.bar, this.baz});
+
+ Map toJson() {
+ return {
+ 'bar': this.bar,
+ 'baz': this.baz
+ };
+ }
+}
+
+class _InlineExampleScreenState extends State {
+ InAppWebViewController webView;
+ String url = "";
+ double progress = 0;
+
+ TextEditingController _textFieldController = TextEditingController();
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+// HttpAuthCredentialDatabase.instance().clearAllAuthCredentials();
+//
+// HttpAuthCredentialDatabase.instance().getHttpAuthCredentials(ProtectionSpace(host: "192.168.1.20", protocol: "http", realm: "Node", port: 8081)).then((credentials) {
+// for (var credential in credentials )
+// print("\n\nCREDENTIAL: ${credential.username} ${credential.password}\n\n");
+// });
+// HttpAuthCredentialDatabase.instance().getAllAuthCredentials().then((result) {
+// for (var r in result) {
+// ProtectionSpace protectionSpace = r["protectionSpace"];
+// print("\n\nProtectionSpace: ${protectionSpace.protocol} ${protectionSpace.host}:");
+// List credentials = r["credentials"];
+// for (var credential in credentials)
+// print("\tCREDENTIAL: ${credential.username} ${credential.password}");
+// }
+// });
+// HttpAuthCredentialDatabase.instance().setHttpAuthCredential(ProtectionSpace(host: "192.168.1.20", protocol: "http", realm: "Node", port: 8081), HttpAuthCredential(username: "user 1", password: "password 1"));
+// HttpAuthCredentialDatabase.instance().setHttpAuthCredential(ProtectionSpace(host: "192.168.1.20", protocol: "http", realm: "Node", port: 8081), HttpAuthCredential(username: "user 2", password: "password 2"));
+
+ return Container(
+ child: Column(children: [
+ Container(
+ padding: EdgeInsets.all(20.0),
+ child: Text(
+ "CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"),
+ ),
+ Container(
+ padding: EdgeInsets.all(10.0),
+ child: progress < 1.0 ? LinearProgressIndicator(value: progress) : Container()
+ ),
+ Expanded(
+ child: Container(
+ margin: const EdgeInsets.all(10.0),
+ decoration:
+ BoxDecoration(border: Border.all(color: Colors.blueAccent)),
+ child: InAppWebView(
+ //initialUrl: "https://www.youtube.com/embed/M7lc1UVf-VE?playsinline=1",
+ initialUrl: "https://github.com",
+ //initialUrl: "chrome://safe-browsing/match?type=malware",
+ //initialUrl: "http://192.168.1.20:8081/",
+ //initialUrl: "https://192.168.1.20:4433/",
+ //initialFile: "assets/index.html",
+ initialHeaders: {},
+ initialOptions: [
+ InAppWebViewOptions(
+ //clearCache: true,
+ useShouldOverrideUrlLoading: true,
+ useOnTargetBlank: true,
+ //useOnLoadResource: true,
+ useOnDownloadStart: true,
+ preferredContentMode: InAppWebViewUserPreferredContentMode.DESKTOP,
+ resourceCustomSchemes: ["my-special-custom-scheme"],
+ /*contentBlockers: [
+ ContentBlocker(
+ ContentBlockerTrigger(".*",
+ resourceType: [ContentBlockerTriggerResourceType.IMAGE, ContentBlockerTriggerResourceType.STYLE_SHEET],
+ ifTopUrl: ["https://getbootstrap.com/"]),
+ ContentBlockerAction(ContentBlockerActionType.BLOCK)
+ )
+ ]*/
+ ),
+ AndroidInAppWebViewOptions(
+ databaseEnabled: true,
+ appCacheEnabled: true,
+ domStorageEnabled: true,
+ geolocationEnabled: true,
+ safeBrowsingEnabled: true,
+ //blockNetworkImage: true,
+ ),
+ ],
+ onWebViewCreated: (InAppWebViewController controller) {
+ webView = controller;
+
+ if (Platform.isAndroid)
+ webView.startSafeBrowsing();
+
+ webView.addJavaScriptHandler('handlerFoo', (args) {
+ return new Foo(bar: 'bar_value', baz: 'baz_value');
+ });
+
+ webView.addJavaScriptHandler('handlerFooWithArgs', (args) {
+ print(args);
+ return [args[0] + 5, !args[1], args[2][0], args[3]['foo']];
+ });
+ },
+ onLoadStart: (InAppWebViewController controller, String url) {
+ print("started $url");
+ setState(() {
+ this.url = url;
+ });
+ },
+ onLoadStop: (InAppWebViewController controller, String url) async {
+ print("stopped $url");
+ if (Platform.isAndroid) {
+ controller.clearSslPreferences();
+ controller.clearClientCertPreferences();
+ }
+ //controller.findAllAsync("a");
+ controller.getFavicon();
+ },
+ onLoadError: (InAppWebViewController controller, String url, int code, String message) async {
+ print("error $url: $code, $message");
+ },
+ onProgressChanged:
+ (InAppWebViewController controller, int progress) {
+ setState(() {
+ this.progress = progress / 100;
+ });
+ },
+ shouldOverrideUrlLoading: (InAppWebViewController controller, String url) {
+ print("override $url");
+ controller.loadUrl(url);
+ },
+ onLoadResource: (InAppWebViewController controller, WebResourceResponse response) {
+ print("Resource type: '"+response.initiatorType + "' started at: " +
+ response.startTime.toString() +
+ "ms ---> duration: " +
+ response.duration.toString() +
+ "ms " +
+ response.url);
+ },
+ onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) {
+ print("""
+ console output:
+ sourceURL: ${consoleMessage.sourceURL}
+ lineNumber: ${consoleMessage.lineNumber}
+ message: ${consoleMessage.message}
+ messageLevel: ${consoleMessage.messageLevel.toValue()}
+ """);
+ },
+ onDownloadStart: (InAppWebViewController controller, String url) async {
+// final taskId = await FlutterDownloader.enqueue(
+// url: url,
+// savedDir: await _findLocalPath(),
+// showNotification: true, // show download progress in status bar (for Android)
+// openFileFromNotification: true, // click on notification to open downloaded file (for Android)
+// );
+ },
+ onLoadResourceCustomScheme: (InAppWebViewController controller, String scheme, String url) async {
+ if (scheme == "my-special-custom-scheme") {
+ var bytes = await rootBundle.load("assets/" + url.replaceFirst("my-special-custom-scheme://", "", 0));
+ var asBase64 = base64.encode(bytes.buffer.asUint8List());
+ var response = new CustomSchemeResponse(asBase64, "image/svg+xml", contentEnconding: "utf-8");
+ return response;
+ }
+ return null;
+ },
+ onTargetBlank: (InAppWebViewController controller, String url) {
+ print("target _blank: " + url);
+ controller.loadUrl(url);
+ },
+ onGeolocationPermissionsShowPrompt: (InAppWebViewController controller, String origin) async {
+ GeolocationPermissionShowPromptResponse response;
+
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: Text("Permission Geolocation API"),
+ content: Text("Can we use Geolocation API?"),
+ actions: [
+ FlatButton(
+ child: Text("Close"),
+ onPressed: () {
+ response = new GeolocationPermissionShowPromptResponse(origin, false, false);
+ Navigator.of(context).pop();
+ },
+ ),
+ FlatButton(
+ child: Text("Accept"),
+ onPressed: () {
+ response = new GeolocationPermissionShowPromptResponse(origin, true, true);
+ Navigator.of(context).pop();
+ },
+ ),
+ ],
+ );
+ },
+ );
+
+ return response;
+ },
+ onJsAlert: (InAppWebViewController controller, String message) async {
+ JsAlertResponseAction action = await createAlertDialog(context, message);
+ return new JsAlertResponse(handledByClient: true, action: action);
+ },
+ onJsConfirm: (InAppWebViewController controller, String message) async {
+ JsConfirmResponseAction action = await createConfirmDialog(context, message);
+ return new JsConfirmResponse(handledByClient: true, action: action);
+ },
+ onJsPrompt: (InAppWebViewController controller, String message, String defaultValue) async {
+ _textFieldController.text = defaultValue;
+ JsPromptResponseAction action = await createPromptDialog(context, message);
+ return new JsPromptResponse(handledByClient: true, action: action, value: _textFieldController.text);
+ },
+ onSafeBrowsingHit: (InAppWebViewController controller, String url, SafeBrowsingThreat threatType) async {
+ SafeBrowsingResponseAction action = SafeBrowsingResponseAction.BACK_TO_SAFETY;
+ return new SafeBrowsingResponse(report: true, action: action);
+ },
+ onReceivedHttpAuthRequest: (InAppWebViewController controller, HttpAuthChallenge challenge) async {
+ print("HTTP AUTH REQUEST: ${challenge.protectionSpace.host}, realm: ${challenge.protectionSpace.realm}, previous failure count: ${challenge.previousFailureCount.toString()}");
+
+ return new HttpAuthResponse(username: "USERNAME", password: "PASSWORD", action: HttpAuthResponseAction.USE_SAVED_HTTP_AUTH_CREDENTIALS, permanentPersistence: true);
+ },
+ onReceivedServerTrustAuthRequest: (InAppWebViewController controller, ServerTrustChallenge challenge) async {
+ print("SERVER TRUST AUTH REQUEST: ${challenge.protectionSpace.host}, SSL ERROR CODE: ${challenge.error.toString()}, MESSAGE: ${challenge.message}");
+
+ return new ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.PROCEED);
+ },
+ onReceivedClientCertRequest: (InAppWebViewController controller, ClientCertChallenge challenge) async {
+ print("CLIENT CERT REQUEST: ${challenge.protectionSpace.host}");
+
+ return new ClientCertResponse(certificatePath: "assets/certificate.pfx", certificatePassword: "", androidKeyStoreType: "PKCS12", action: ClientCertResponseAction.PROCEED);
+ },
+ onFindResultReceived: (InAppWebViewController controller, int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting) async {
+ print("Current highlighted: $activeMatchOrdinal, Number of matches found: $numberOfMatches, find operation completed: $isDoneCounting");
+ },
+ ),
+ ),
+ ),
+ ButtonBar(
+ alignment: MainAxisAlignment.center,
+ children: [
+ RaisedButton(
+ child: Icon(Icons.arrow_back),
+ onPressed: () {
+ if (webView != null) {
+ webView.goBack();
+ }
+ },
+ ),
+ RaisedButton(
+ child: Icon(Icons.arrow_forward),
+ onPressed: () {
+ if (webView != null) {
+ webView.goForward();
+ }
+ },
+ ),
+ RaisedButton(
+ child: Icon(Icons.refresh),
+ onPressed: () {
+ if (webView != null) {
+ webView.reload();
+ }
+ },
+ ),
+ ],
+ ),
+ ]));
+ }
+
+ Future createAlertDialog(BuildContext context, String message) async {
+ JsAlertResponseAction action;
+
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ content: Text(message),
+ actions: [
+ FlatButton(
+ child: Text("Ok"),
+ onPressed: () {
+ action = JsAlertResponseAction.CONFIRM;
+ Navigator.of(context).pop();
+ },
+ ),
+ ],
+ );
+ },
+ );
+
+ return action;
+ }
+
+ Future createConfirmDialog(BuildContext context, String message) async {
+ JsConfirmResponseAction action;
+
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ content: Text(message),
+ actions: [
+ FlatButton(
+ child: Text("Cancel"),
+ onPressed: () {
+ action = JsConfirmResponseAction.CANCEL;
+ Navigator.of(context).pop();
+ },
+ ),
+ FlatButton(
+ child: Text("Ok"),
+ onPressed: () {
+ action = JsConfirmResponseAction.CONFIRM;
+ Navigator.of(context).pop();
+ },
+ ),
+ ],
+ );
+ },
+ );
+
+ return action;
+ }
+
+ Future createPromptDialog(BuildContext context, String message) async {
+ JsPromptResponseAction action;
+
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: Text(message),
+ content: TextField(
+ controller: _textFieldController,
+ ),
+ actions: [
+ FlatButton(
+ child: Text("Cancel"),
+ onPressed: () {
+ action = JsPromptResponseAction.CANCEL;
+ Navigator.of(context).pop();
+ },
+ ),
+ FlatButton(
+ child: Text("Ok"),
+ onPressed: () {
+ action = JsPromptResponseAction.CONFIRM;
+ Navigator.of(context).pop();
+ },
+ ),
+ ],
+ );
+ },
+ );
+
+ return action;
+ }
+
+ Future _findLocalPath() async {
+ final directory = Platform.isAndroid
+ ? await getExternalStorageDirectory()
+ : await getApplicationDocumentsDirectory();
+ return directory.path;
+ }
+}
diff --git a/ios/Classes/FlutterWebViewController.swift b/ios/Classes/FlutterWebViewController.swift
index dde530eb..1588095e 100755
--- a/ios/Classes/FlutterWebViewController.swift
+++ b/ios/Classes/FlutterWebViewController.swift
@@ -1,289 +1,325 @@
-//
-// FlutterWebViewController.swift
-// flutter_inappbrowser
-//
-// Created by Lorenzo on 13/11/18.
-//
-
-import Foundation
-import WebKit
-
-public class FlutterWebViewController: NSObject, FlutterPlatformView {
-
- private weak var registrar: FlutterPluginRegistrar?
- var webView: InAppWebView?
- var viewId: Int64 = 0
- var channel: FlutterMethodChannel?
-
- init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: NSDictionary) {
- super.init()
- self.registrar = registrar
- self.viewId = viewId
-
- let initialUrl = (args["initialUrl"] as? String)!
- let initialFile = args["initialFile"] as? String
- let initialData = args["initialData"] as? [String: String]
- let initialHeaders = (args["initialHeaders"] as? [String: String])!
- let initialOptions = (args["initialOptions"] as? [String: Any])!
-
- let options = InAppWebViewOptions()
- options.parse(options: initialOptions)
- let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: options)
-
- webView = InAppWebView(frame: frame, configuration: preWebviewConfiguration, IABController: nil, IAWController: self)
- let channelName = "com.pichillilorenzo/flutter_inappwebview_" + String(viewId)
- self.channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger())
- self.channel?.setMethodCallHandler(self.handle)
-
- webView!.options = options
- webView!.prepare()
-
- if #available(iOS 11.0, *) {
- self.webView!.configuration.userContentController.removeAllContentRuleLists()
- if let contentBlockers = webView!.options?.contentBlockers, contentBlockers.count > 0 {
- do {
- let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
- let blockRules = String(data: jsonData, encoding: String.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.load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders)
- }
- return
- } catch {
- print(error.localizedDescription)
- }
- }
- }
- load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders)
- }
-
- public func view() -> UIView {
- return webView!
- }
-
- public func load(initialUrl: String, initialFile: String?, initialData: [String: String]?, initialHeaders: [String: String]) {
- if initialFile != nil {
- do {
- try webView!.loadFile(url: initialFile!, headers: initialHeaders)
- }
- catch let error as NSError {
- dump(error)
- }
- return
- }
-
- if initialData != nil {
- let data = (initialData!["data"] as? String)!
- let mimeType = (initialData!["mimeType"] as? String)!
- let encoding = (initialData!["encoding"] as? String)!
- let baseUrl = (initialData!["baseUrl"] as? String)!
- webView!.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl)
- }
- else {
- webView!.loadUrl(url: URL(string: initialUrl)!, headers: initialHeaders)
- }
- }
-
- public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
- let arguments = call.arguments as? NSDictionary
- switch call.method {
- case "getUrl":
- result( (webView != nil) ? webView!.url?.absoluteString : nil )
- break
- case "getTitle":
- result( (webView != nil) ? webView!.title : nil )
- break
- case "getProgress":
- result( (webView != nil) ? Int(webView!.estimatedProgress * 100) : nil )
- break
- case "loadUrl":
- if webView != nil {
- let url = (arguments!["url"] as? String)!
- let headers = (arguments!["headers"] as? [String: String])!
- webView!.loadUrl(url: URL(string: url)!, headers: headers)
- result(true)
- }
- else {
- result(false)
- }
- break
- case "postUrl":
- if webView != nil {
- let url = (arguments!["url"] as? String)!
- let postData = (arguments!["postData"] as? FlutterStandardTypedData)!
- webView!.postUrl(url: URL(string: url)!, postData: postData.data, completionHandler: { () -> Void in
- result(true)
- })
- }
- else {
- result(false)
- }
- break
- case "loadData":
- if webView != nil {
- let data = (arguments!["data"] as? String)!
- let mimeType = (arguments!["mimeType"] as? String)!
- let encoding = (arguments!["encoding"] as? String)!
- let baseUrl = (arguments!["baseUrl"] as? String)!
- webView!.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl)
- result(true)
- }
- else {
- result(false)
- }
- break
- case "loadFile":
- if webView != nil {
- let url = (arguments!["url"] as? String)!
- let headers = (arguments!["headers"] as? [String: String])!
-
- do {
- try webView!.loadFile(url: url, headers: headers)
- result(true)
- }
- catch let error as NSError {
- result(FlutterError(code: "InAppBrowserFlutterPlugin", message: error.domain, details: nil))
- return
- }
- }
- else {
- result(false)
- }
- break
- case "injectScriptCode":
- if webView != nil {
- let source = (arguments!["source"] as? String)!
- webView!.injectScriptCode(source: source, result: result)
- }
- else {
- result("")
- }
- break
- case "injectScriptFile":
- if webView != nil {
- let urlFile = (arguments!["urlFile"] as? String)!
- webView!.injectScriptFile(urlFile: urlFile)
- }
- result(true)
- break
- case "injectStyleCode":
- if webView != nil {
- let source = (arguments!["source"] as? String)!
- webView!.injectStyleCode(source: source)
- }
- result(true)
- break
- case "injectStyleFile":
- if webView != nil {
- let urlFile = (arguments!["urlFile"] as? String)!
- webView!.injectStyleFile(urlFile: urlFile)
- }
- result(true)
- break
- case "reload":
- if webView != nil {
- webView!.reload()
- }
- result(true)
- break
- case "goBack":
- if webView != nil {
- webView!.goBack()
- }
- result(true)
- break
- case "canGoBack":
- result((webView != nil) && webView!.canGoBack)
- break
- case "goForward":
- if webView != nil {
- webView!.goForward()
- }
- result(true)
- break
- case "canGoForward":
- result((webView != nil) && webView!.canGoForward)
- break
- case "goBackOrForward":
- if webView != nil {
- let steps = (arguments!["steps"] as? Int)!
- webView!.goBackOrForward(steps: steps)
- }
- result(true)
- break
- case "canGoBackOrForward":
- let steps = (arguments!["steps"] as? Int)!
- result((webView != nil) && webView!.canGoBackOrForward(steps: steps))
- break
- case "stopLoading":
- if webView != nil {
- webView!.stopLoading()
- }
- result(true)
- break
- case "isLoading":
- result((webView != nil) && webView!.isLoading)
- break
- case "takeScreenshot":
- if webView != nil {
- webView!.takeScreenshot(completionHandler: { (screenshot) -> Void in
- result(screenshot)
- })
- }
- else {
- result(nil)
- }
- break
- case "setOptions":
- if webView != nil {
- let inAppWebViewOptions = InAppWebViewOptions()
- let inAppWebViewOptionsMap = arguments!["options"] as! [String: Any]
- inAppWebViewOptions.parse(options: inAppWebViewOptionsMap)
- webView!.setOptions(newOptions: inAppWebViewOptions, newOptionsMap: inAppWebViewOptionsMap)
- }
- result(true)
- break
- case "getOptions":
- result((webView != nil) ? webView!.getOptions() : nil)
- break
- case "getCopyBackForwardList":
- result((webView != nil) ? webView!.getCopyBackForwardList() : nil)
- break
- case "clearCache":
- if webView != nil {
- webView!.clearCache()
- }
- result(true)
- break
- case "dispose":
- dispose()
- result(true)
- break
- default:
- result(FlutterMethodNotImplemented)
- break
- }
- }
-
- public func dispose() {
- if webView != nil {
- webView!.IABController = nil
- webView!.IAWController = nil
- webView!.uiDelegate = nil
- webView!.navigationDelegate = nil
- webView!.scrollView.delegate = nil
- webView!.stopLoading()
- webView = nil
- }
- }
-}
+//
+// FlutterWebViewController.swift
+// flutter_inappbrowser
+//
+// Created by Lorenzo on 13/11/18.
+//
+
+import Foundation
+import WebKit
+
+public class FlutterWebViewController: NSObject, FlutterPlatformView {
+
+ private weak var registrar: FlutterPluginRegistrar?
+ var webView: InAppWebView?
+ var viewId: Int64 = 0
+ var channel: FlutterMethodChannel?
+
+ init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: NSDictionary) {
+ super.init()
+ self.registrar = registrar
+ self.viewId = viewId
+
+ let initialUrl = (args["initialUrl"] as? String)!
+ let initialFile = args["initialFile"] as? String
+ let initialData = args["initialData"] as? [String: String]
+ let initialHeaders = (args["initialHeaders"] as? [String: String])!
+ let initialOptions = (args["initialOptions"] as? [String: Any])!
+
+ let options = InAppWebViewOptions()
+ options.parse(options: initialOptions)
+ let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: options)
+
+ webView = InAppWebView(frame: frame, configuration: preWebviewConfiguration, IABController: nil, IAWController: self)
+ let channelName = "com.pichillilorenzo/flutter_inappwebview_" + String(viewId)
+ self.channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger())
+ self.channel?.setMethodCallHandler(self.handle)
+
+ webView!.options = options
+ webView!.prepare()
+
+ if #available(iOS 11.0, *) {
+ self.webView!.configuration.userContentController.removeAllContentRuleLists()
+ if let contentBlockers = webView!.options?.contentBlockers, contentBlockers.count > 0 {
+ do {
+ let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
+ let blockRules = String(data: jsonData, encoding: String.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.load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders)
+ }
+ return
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+ }
+ load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders)
+ }
+
+ public func view() -> UIView {
+ return webView!
+ }
+
+ public func load(initialUrl: String, initialFile: String?, initialData: [String: String]?, initialHeaders: [String: String]) {
+ if initialFile != nil {
+ do {
+ try webView!.loadFile(url: initialFile!, headers: initialHeaders)
+ }
+ catch let error as NSError {
+ dump(error)
+ }
+ return
+ }
+
+ if initialData != nil {
+ let data = (initialData!["data"] as? String)!
+ let mimeType = (initialData!["mimeType"] as? String)!
+ let encoding = (initialData!["encoding"] as? String)!
+ let baseUrl = (initialData!["baseUrl"] as? String)!
+ webView!.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl)
+ }
+ else {
+ webView!.loadUrl(url: URL(string: initialUrl)!, headers: initialHeaders)
+ }
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ let arguments = call.arguments as? NSDictionary
+ switch call.method {
+ case "getUrl":
+ result( (webView != nil) ? webView!.url?.absoluteString : nil )
+ break
+ case "getTitle":
+ result( (webView != nil) ? webView!.title : nil )
+ break
+ case "getProgress":
+ result( (webView != nil) ? Int(webView!.estimatedProgress * 100) : nil )
+ break
+ case "loadUrl":
+ if webView != nil {
+ let url = (arguments!["url"] as? String)!
+ let headers = (arguments!["headers"] as? [String: String])!
+ webView!.loadUrl(url: URL(string: url)!, headers: headers)
+ result(true)
+ }
+ else {
+ result(false)
+ }
+ break
+ case "postUrl":
+ if webView != nil {
+ let url = (arguments!["url"] as? String)!
+ let postData = (arguments!["postData"] as? FlutterStandardTypedData)!
+ webView!.postUrl(url: URL(string: url)!, postData: postData.data, completionHandler: { () -> Void in
+ result(true)
+ })
+ }
+ else {
+ result(false)
+ }
+ break
+ case "loadData":
+ if webView != nil {
+ let data = (arguments!["data"] as? String)!
+ let mimeType = (arguments!["mimeType"] as? String)!
+ let encoding = (arguments!["encoding"] as? String)!
+ let baseUrl = (arguments!["baseUrl"] as? String)!
+ webView!.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl)
+ result(true)
+ }
+ else {
+ result(false)
+ }
+ break
+ case "loadFile":
+ if webView != nil {
+ let url = (arguments!["url"] as? String)!
+ let headers = (arguments!["headers"] as? [String: String])!
+
+ do {
+ try webView!.loadFile(url: url, headers: headers)
+ result(true)
+ }
+ catch let error as NSError {
+ result(FlutterError(code: "InAppBrowserFlutterPlugin", message: error.domain, details: nil))
+ return
+ }
+ }
+ else {
+ result(false)
+ }
+ break
+ case "injectScriptCode":
+ if webView != nil {
+ let source = (arguments!["source"] as? String)!
+ webView!.injectScriptCode(source: source, result: result)
+ }
+ else {
+ result("")
+ }
+ break
+ case "injectScriptFile":
+ if webView != nil {
+ let urlFile = (arguments!["urlFile"] as? String)!
+ webView!.injectScriptFile(urlFile: urlFile)
+ }
+ result(true)
+ break
+ case "injectStyleCode":
+ if webView != nil {
+ let source = (arguments!["source"] as? String)!
+ webView!.injectStyleCode(source: source)
+ }
+ result(true)
+ break
+ case "injectStyleFile":
+ if webView != nil {
+ let urlFile = (arguments!["urlFile"] as? String)!
+ webView!.injectStyleFile(urlFile: urlFile)
+ }
+ result(true)
+ break
+ case "reload":
+ if webView != nil {
+ webView!.reload()
+ }
+ result(true)
+ break
+ case "goBack":
+ if webView != nil {
+ webView!.goBack()
+ }
+ result(true)
+ break
+ case "canGoBack":
+ result((webView != nil) && webView!.canGoBack)
+ break
+ case "goForward":
+ if webView != nil {
+ webView!.goForward()
+ }
+ result(true)
+ break
+ case "canGoForward":
+ result((webView != nil) && webView!.canGoForward)
+ break
+ case "goBackOrForward":
+ if webView != nil {
+ let steps = (arguments!["steps"] as? Int)!
+ webView!.goBackOrForward(steps: steps)
+ }
+ result(true)
+ break
+ case "canGoBackOrForward":
+ let steps = (arguments!["steps"] as? Int)!
+ result((webView != nil) && webView!.canGoBackOrForward(steps: steps))
+ break
+ case "stopLoading":
+ if webView != nil {
+ webView!.stopLoading()
+ }
+ result(true)
+ break
+ case "isLoading":
+ result((webView != nil) && webView!.isLoading)
+ break
+ case "takeScreenshot":
+ if webView != nil {
+ webView!.takeScreenshot(completionHandler: { (screenshot) -> Void in
+ result(screenshot)
+ })
+ }
+ else {
+ result(nil)
+ }
+ break
+ case "setOptions":
+ if webView != nil {
+ let inAppWebViewOptions = InAppWebViewOptions()
+ let inAppWebViewOptionsMap = arguments!["options"] as! [String: Any]
+ inAppWebViewOptions.parse(options: inAppWebViewOptionsMap)
+ webView!.setOptions(newOptions: inAppWebViewOptions, newOptionsMap: inAppWebViewOptionsMap)
+ }
+ result(true)
+ break
+ case "getOptions":
+ result((webView != nil) ? webView!.getOptions() : nil)
+ break
+ case "getCopyBackForwardList":
+ result((webView != nil) ? webView!.getCopyBackForwardList() : nil)
+ break
+ case "findAllAsync":
+ if webView != nil {
+ let find = arguments!["find"] as! String
+ webView!.findAllAsync(find: find, completionHandler: nil)
+ result(true)
+ } else {
+ result(false)
+ }
+ break
+ case "findNext":
+ if webView != nil {
+ let forward = arguments!["forward"] as! Bool
+ webView!.findNext(forward: forward, completionHandler: {(value, error) in
+ if error != nil {
+ result(FlutterError(code: "FlutterWebViewController", message: error?.localizedDescription, details: nil))
+ return
+ }
+ result(true)
+ })
+ } else {
+ result(false)
+ }
+ break
+ case "clearMatches":
+ if webView != nil {
+ webView!.clearMatches(completionHandler: {(value, error) in
+ if error != nil {
+ result(FlutterError(code: "FlutterWebViewController", message: error?.localizedDescription, details: nil))
+ return
+ }
+ result(true)
+ })
+ } else {
+ result(false)
+ }
+ break
+ case "clearCache":
+ if webView != nil {
+ webView!.clearCache()
+ }
+ result(true)
+ break
+ case "dispose":
+ dispose()
+ result(true)
+ break
+ default:
+ result(FlutterMethodNotImplemented)
+ break
+ }
+ }
+
+ public func dispose() {
+ if webView != nil {
+ webView!.IABController = nil
+ webView!.IAWController = nil
+ webView!.uiDelegate = nil
+ webView!.navigationDelegate = nil
+ webView!.scrollView.delegate = nil
+ webView!.stopLoading()
+ webView = nil
+ }
+ }
+}
diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift
index c6db02ac..dda0b4d0 100755
--- a/ios/Classes/InAppWebView.swift
+++ b/ios/Classes/InAppWebView.swift
@@ -1,1585 +1,1631 @@
-//
-// InAppWebView.swift
-// flutter_inappbrowser
-//
-// Created by Lorenzo on 21/10/18.
-//
-
-import Flutter
-import Foundation
-import WebKit
-
-func currentTimeInMilliSeconds() -> Int64 {
- let currentDate = Date()
- let since1970 = currentDate.timeIntervalSince1970
- return Int64(since1970 * 1000)
-}
-
-func convertToDictionary(text: String) -> [String: Any]? {
- if let data = text.data(using: .utf8) {
- do {
- return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
- } catch {
- print(error.localizedDescription)
- }
- }
- return nil
-}
-
-// the message needs to be concatenated with '' in order to have the same behavior like on Android
-let consoleLogJS = """
-(function() {
- 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() {
- var message = '';
- for (var i in arguments) {
- if (message == '') {
- message += arguments[i];
- }
- else {
- message += ' ' + arguments[i];
- }
- }
- window.webkit.messageHandlers[oldLog].postMessage(message);
- }
- })(k);
- }
-})();
-"""
-
-let resourceObserverJS = """
-(function() {
- var observer = new PerformanceObserver(function(list) {
- list.getEntries().forEach(function(entry) {
- window.webkit.messageHandlers['resourceLoaded'].postMessage(JSON.stringify(entry));
- });
- });
- observer.observe({entryTypes: ['resource']});
-})();
-"""
-
-let JAVASCRIPT_BRIDGE_NAME = "flutter_inappbrowser"
-
-let javaScriptBridgeJS = """
-window.\(JAVASCRIPT_BRIDGE_NAME) = {};
-window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
- var _callHandlerID = setTimeout(function(){});
- window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1))} );
- return new Promise(function(resolve, reject) {
- window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = resolve;
- });
-}
-"""
-
-let platformReadyJS = "window.dispatchEvent(new Event('flutterInAppBrowserPlatformReady'));";
-
-let searchJavascript = """
-var uiWebview_SearchResultCount = 0;
-
-/*!
- @method uiWebview_HighlightAllOccurencesOfStringForElement
- @abstract // helper function, recursively searches in elements and their child nodes
- @discussion // helper function, recursively searches in elements and their child nodes
-
- element - HTML elements
- keyword - string to search
- */
-
-function uiWebview_HighlightAllOccurencesOfStringForElement(element,keyword) {
- if (element) {
- if (element.nodeType == 3) { // Text node
-
- var count = 0;
- 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;
-
- count++;
- elementTmp = document.createTextNode(value.substr(idx+keyword.length));
- }
-
- uiWebview_SearchResultCount += count;
-
- var index = uiWebview_SearchResultCount;
- while (true) {
- var value = element.nodeValue; // Search for keyword in text node
- var idx = value.toLowerCase().indexOf(keyword);
-
- if (idx < 0) break; // not found, abort
-
- //we create a SPAN element for every parts of matched keywords
- var span = document.createElement("span");
- var text = document.createTextNode(value.substr(idx,keyword.length));
- span.appendChild(text);
-
- span.setAttribute("class","uiWebviewHighlight");
- span.style.backgroundColor="yellow";
- span.style.color="black";
-
- index--;
- span.setAttribute("id", "SEARCH_WORD"+(index));
- //span.setAttribute("id", "SEARCH_WORD"+uiWebview_SearchResultCount);
-
- //element.parentNode.setAttribute("id", "SEARCH_WORD"+uiWebview_SearchResultCount);
-
- //uiWebview_SearchResultCount++; // update the counter
-
- text = document.createTextNode(value.substr(idx+keyword.length));
- element.deleteData(idx, value.length - idx);
-
- var next = element.nextSibling;
- //alert(element.parentNode);
- element.parentNode.insertBefore(span, next);
- element.parentNode.insertBefore(text, next);
- element = text;
- }
-
-
- } 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--) {
- uiWebview_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
- }
- }
- }
- }
-}
-
-// the main entry point to start the search
-function uiWebview_HighlightAllOccurencesOfString(keyword) {
- uiWebview_RemoveAllHighlights();
- uiWebview_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());
-}
-
-// helper function, recursively removes the highlights in elements and their childs
-function uiWebview_RemoveAllHighlightsForElement(element) {
- if (element) {
- if (element.nodeType == 1) {
- if (element.getAttribute("class") == "uiWebviewHighlight") {
- 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 (uiWebview_RemoveAllHighlightsForElement(element.childNodes[i])) {
- normalize = true;
- }
- }
- if (normalize) {
- element.normalize();
- }
- }
- }
- }
- return false;
-}
-
-// the main entry point to remove the highlights
-function uiWebview_RemoveAllHighlights() {
- uiWebview_SearchResultCount = 0;
- uiWebview_RemoveAllHighlightsForElement(document.body);
-}
-
-function uiWebview_ScrollTo(idx) {
- var scrollTo = document.getElementById("SEARCH_WORD" + idx);
- if (scrollTo) scrollTo.scrollIntoView();
-}
-
-"""
-
-public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
-
- var IABController: InAppBrowserWebViewController?
- var IAWController: FlutterWebViewController?
- var options: InAppWebViewOptions?
- var currentURL: URL?
- var WKNavigationMap: [String: [String: Any]] = [:]
- var startPageTime: Int64 = 0
- static var credentialsProposed: [URLCredential] = []
-
- init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, IAWController: FlutterWebViewController?) {
- super.init(frame: frame, configuration: configuration)
- self.IABController = IABController
- self.IAWController = IAWController
- uiDelegate = self
- navigationDelegate = self
- scrollView.delegate = self
- }
-
- required public init(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)!
- }
-
- public func prepare() {
- addObserver(self,
- forKeyPath: #keyPath(WKWebView.estimatedProgress),
- options: .new,
- context: nil)
-
- configuration.userContentController = WKUserContentController()
- configuration.preferences = WKPreferences()
-
- if (options?.transparentBackground)! {
- isOpaque = false
- backgroundColor = UIColor.clear
- scrollView.backgroundColor = UIColor.clear
- }
-
- // prevent webView from bouncing
- if (options?.disallowOverScroll)! {
- if responds(to: #selector(getter: scrollView)) {
- scrollView.bounces = false
- }
- else {
- for subview: UIView in subviews {
- if subview is UIScrollView {
- (subview as! UIScrollView).bounces = false
- }
- }
- }
- }
-
- if (options?.enableViewportScale)! {
- let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
- let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
- configuration.userContentController.addUserScript(userScript)
- }
-
- // Prevents long press on links that cause WKWebView exit
- let jscriptWebkitTouchCallout = WKUserScript(source: "document.body.style.webkitTouchCallout='none';", injectionTime: .atDocumentEnd, forMainFrameOnly: true)
- configuration.userContentController.addUserScript(jscriptWebkitTouchCallout)
-
-
- let consoleLogJSScript = WKUserScript(source: consoleLogJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
- configuration.userContentController.addUserScript(consoleLogJSScript)
- configuration.userContentController.add(self, name: "consoleLog")
- configuration.userContentController.add(self, name: "consoleDebug")
- configuration.userContentController.add(self, name: "consoleError")
- configuration.userContentController.add(self, name: "consoleInfo")
- configuration.userContentController.add(self, name: "consoleWarn")
-
- let javaScriptBridgeJSScript = WKUserScript(source: javaScriptBridgeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
- configuration.userContentController.addUserScript(javaScriptBridgeJSScript)
- configuration.userContentController.add(self, name: "callHandler")
-
- let resourceObserverJSScript = WKUserScript(source: resourceObserverJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
- configuration.userContentController.addUserScript(resourceObserverJSScript)
- configuration.userContentController.add(self, name: "resourceLoaded")
-
- //keyboardDisplayRequiresUserAction = browserOptions?.keyboardDisplayRequiresUserAction
-
- configuration.suppressesIncrementalRendering = (options?.suppressesIncrementalRendering)!
- allowsBackForwardNavigationGestures = (options?.allowsBackForwardNavigationGestures)!
- if #available(iOS 9.0, *) {
- allowsLinkPreview = (options?.allowsLinkPreview)!
- configuration.allowsPictureInPictureMediaPlayback = (options?.allowsPictureInPictureMediaPlayback)!
- if ((options?.applicationNameForUserAgent)! != "") {
- configuration.applicationNameForUserAgent = (options?.applicationNameForUserAgent)!
- }
- if ((options?.userAgent)! != "") {
- customUserAgent = (options?.userAgent)!
- }
- }
-
- configuration.preferences.javaScriptCanOpenWindowsAutomatically = (options?.javaScriptCanOpenWindowsAutomatically)!
- configuration.preferences.javaScriptEnabled = (options?.javaScriptEnabled)!
- configuration.preferences.minimumFontSize = CGFloat((options?.minimumFontSize)!)
- configuration.selectionGranularity = WKSelectionGranularity.init(rawValue: (options?.selectionGranularity)!)!
-
- if #available(iOS 10.0, *) {
- configuration.ignoresViewportScaleLimits = (options?.ignoresViewportScaleLimits)!
-
- var dataDetectorTypes = WKDataDetectorTypes.init(rawValue: 0)
- for type in options?.dataDetectorTypes ?? [] {
- let dataDetectorType = getDataDetectorType(type: type)
- dataDetectorTypes = WKDataDetectorTypes(rawValue: dataDetectorTypes.rawValue | dataDetectorType.rawValue)
- }
- configuration.dataDetectorTypes = dataDetectorTypes
- } else {
- // Fallback on earlier versions
- }
-
- if #available(iOS 13.0, *) {
- configuration.preferences.isFraudulentWebsiteWarningEnabled = (options?.isFraudulentWebsiteWarningEnabled)!
- if options?.preferredContentMode != nil {
- configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: (options?.preferredContentMode)!)!
- }
- } else {
- // Fallback on earlier versions
- }
-
- scrollView.showsVerticalScrollIndicator = (options?.verticalScrollBarEnabled)!
- scrollView.showsHorizontalScrollIndicator = (options?.horizontalScrollBarEnabled)!
-
- // options.debuggingEnabled is always enabled for iOS.
-
- if (options?.clearCache)! {
- clearCache()
- }
-
- evaluateJavaScript(searchJavascript, completionHandler: nil)
- }
-
- @available(iOS 10.0, *)
- public func getDataDetectorType(type: String) -> WKDataDetectorTypes {
- switch type {
- case "NONE":
- return WKDataDetectorTypes.init(rawValue: 0)
- case "PHONE_NUMBER":
- return .phoneNumber
- case "LINK":
- return .link
- case "ADDRESS":
- return .address
- case "CALENDAR_EVENT":
- return .calendarEvent
- case "TRACKING_NUMBER":
- return .trackingNumber
- case "FLIGHT_NUMBER":
- return .flightNumber
- case "LOOKUP_SUGGESTION":
- return .lookupSuggestion
- case "SPOTLIGHT_SUGGESTION":
- return .spotlightSuggestion
- case "ALL":
- return .all
- default:
- return WKDataDetectorTypes.init(rawValue: 0)
- }
- }
-
- public static func preWKWebViewConfiguration(options: InAppWebViewOptions?) -> WKWebViewConfiguration {
- let configuration = WKWebViewConfiguration()
-
- if #available(iOS 10.0, *) {
- configuration.mediaTypesRequiringUserActionForPlayback = ((options?.mediaPlaybackRequiresUserGesture)!) ? .all : []
- } else {
- // Fallback on earlier versions
- configuration.mediaPlaybackRequiresUserAction = (options?.mediaPlaybackRequiresUserGesture)!
- }
-
- configuration.allowsInlineMediaPlayback = (options?.allowsInlineMediaPlayback)!
-
- if #available(iOS 11.0, *) {
- if let schemes = options?.resourceCustomSchemes {
- for scheme in schemes {
- configuration.setURLSchemeHandler(CustomeSchemeHandler(), forURLScheme: scheme)
- }
- }
- } else {
- // Fallback on earlier versions
- }
-
- return configuration
- }
-
- override public func observeValue(forKeyPath keyPath: String?, of object: Any?,
- change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
- if keyPath == "estimatedProgress" {
- let progress = Int(estimatedProgress * 100)
- onProgressChanged(progress: progress)
- }
- }
-
- public func goBackOrForward(steps: Int) {
- if canGoBackOrForward(steps: steps) {
- if (steps > 0) {
- let index = steps - 1
- go(to: self.backForwardList.forwardList[index])
- }
- else if (steps < 0){
- let backListLength = self.backForwardList.backList.count
- let index = backListLength + steps
- go(to: self.backForwardList.backList[index])
- }
- }
- }
-
- public func canGoBackOrForward(steps: Int) -> Bool {
- let currentIndex = self.backForwardList.backList.count
- return (steps >= 0)
- ? steps <= self.backForwardList.forwardList.count
- : currentIndex + steps >= 0
- }
-
- public func takeScreenshot (completionHandler: @escaping (_ screenshot: Data?) -> Void) {
- if #available(iOS 11.0, *) {
- takeSnapshot(with: nil, completionHandler: {(image, error) -> Void in
- var imageData: Data? = nil
- if let screenshot = image {
- imageData = screenshot.pngData()!
- }
- completionHandler(imageData)
- })
- } else {
- completionHandler(nil)
- }
- }
-
- public func loadUrl(url: URL, headers: [String: String]?) {
- var request = URLRequest(url: url)
- currentURL = url
- if headers != nil {
- if let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest {
- for (key, value) in headers! {
- mutableRequest.setValue(value, forHTTPHeaderField: key)
- }
- request = mutableRequest as URLRequest
- }
- }
- load(request)
- }
-
- public func postUrl(url: URL, postData: Data, completionHandler: @escaping () -> Void) {
- var request = URLRequest(url: url)
- currentURL = url
- request.httpMethod = "POST"
- request.httpBody = postData
-
- let task = URLSession.shared.dataTask(with: request) { (data : Data?, response : URLResponse?, error : Error?) in
- var returnString = ""
- if data != nil {
- returnString = String(data: data!, encoding: .utf8) ?? ""
- }
- DispatchQueue.main.async(execute: {() -> Void in
- self.loadHTMLString(returnString, baseURL: url)
- completionHandler()
- })
- }
- task.resume()
- }
-
- public func loadData(data: String, mimeType: String, encoding: String, baseUrl: String) {
- let url = URL(string: baseUrl)!
- currentURL = url
- if #available(iOS 9.0, *) {
- load(data.data(using: .utf8)!, mimeType: mimeType, characterEncodingName: encoding, baseURL: url)
- } else {
- loadHTMLString(data, baseURL: url)
- }
- }
-
- public func loadFile(url: String, headers: [String: String]?) throws {
- let key = SwiftFlutterPlugin.instance!.registrar!.lookupKey(forAsset: url)
- let assetURL = Bundle.main.url(forResource: key, withExtension: nil)
- if assetURL == nil {
- throw NSError(domain: url + " asset file cannot be found!", code: 0)
- }
- loadUrl(url: assetURL!, headers: headers)
- }
-
- func setOptions(newOptions: InAppWebViewOptions, newOptionsMap: [String: Any]) {
-
- if newOptionsMap["transparentBackground"] != nil && options?.transparentBackground != newOptions.transparentBackground {
- if newOptions.transparentBackground {
- isOpaque = false
- backgroundColor = UIColor.clear
- scrollView.backgroundColor = UIColor.clear
- } else {
- isOpaque = true
- backgroundColor = nil
- scrollView.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1)
- }
- }
-
- if newOptionsMap["disallowOverScroll"] != nil && options?.disallowOverScroll != newOptions.disallowOverScroll {
- if responds(to: #selector(getter: scrollView)) {
- scrollView.bounces = !newOptions.disallowOverScroll
- }
- else {
- for subview: UIView in subviews {
- if subview is UIScrollView {
- (subview as! UIScrollView).bounces = !newOptions.disallowOverScroll
- }
- }
- }
- }
-
- if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale && newOptions.enableViewportScale {
- let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
- evaluateJavaScript(jscript, completionHandler: nil)
- }
-
- if newOptionsMap["mediaPlaybackRequiresUserGesture"] != nil && options?.mediaPlaybackRequiresUserGesture != newOptions.mediaPlaybackRequiresUserGesture {
- if #available(iOS 10.0, *) {
- configuration.mediaTypesRequiringUserActionForPlayback = (newOptions.mediaPlaybackRequiresUserGesture) ? .all : []
- } else {
- // Fallback on earlier versions
- configuration.mediaPlaybackRequiresUserAction = newOptions.mediaPlaybackRequiresUserGesture
- }
- }
-
- if newOptionsMap["allowsInlineMediaPlayback"] != nil && options?.allowsInlineMediaPlayback != newOptions.allowsInlineMediaPlayback {
- configuration.allowsInlineMediaPlayback = newOptions.allowsInlineMediaPlayback
- }
-
- // if newOptionsMap["keyboardDisplayRequiresUserAction"] != nil && browserOptions?.keyboardDisplayRequiresUserAction != newOptions.keyboardDisplayRequiresUserAction {
- // self.webView.keyboardDisplayRequiresUserAction = newOptions.keyboardDisplayRequiresUserAction
- // }
-
- if newOptionsMap["suppressesIncrementalRendering"] != nil && options?.suppressesIncrementalRendering != newOptions.suppressesIncrementalRendering {
- configuration.suppressesIncrementalRendering = newOptions.suppressesIncrementalRendering
- }
-
- if newOptionsMap["allowsBackForwardNavigationGestures"] != nil && options?.allowsBackForwardNavigationGestures != newOptions.allowsBackForwardNavigationGestures {
- allowsBackForwardNavigationGestures = newOptions.allowsBackForwardNavigationGestures
- }
-
- if newOptionsMap["allowsInlineMediaPlayback"] != nil && options?.allowsInlineMediaPlayback != newOptions.allowsInlineMediaPlayback {
- configuration.allowsInlineMediaPlayback = newOptions.allowsInlineMediaPlayback
- }
-
- if newOptionsMap["javaScriptCanOpenWindowsAutomatically"] != nil && options?.javaScriptCanOpenWindowsAutomatically != newOptions.javaScriptCanOpenWindowsAutomatically {
- configuration.preferences.javaScriptCanOpenWindowsAutomatically = newOptions.javaScriptCanOpenWindowsAutomatically
- }
-
- if newOptionsMap["javaScriptEnabled"] != nil && options?.javaScriptEnabled != newOptions.javaScriptEnabled {
- configuration.preferences.javaScriptEnabled = newOptions.javaScriptEnabled
- }
-
- if newOptionsMap["minimumFontSize"] != nil && options?.minimumFontSize != newOptions.minimumFontSize {
- configuration.preferences.minimumFontSize = CGFloat(newOptions.minimumFontSize)
- }
-
- if newOptionsMap["selectionGranularity"] != nil && options?.selectionGranularity != newOptions.selectionGranularity {
- configuration.selectionGranularity = WKSelectionGranularity.init(rawValue: newOptions.selectionGranularity)!
- }
-
- if #available(iOS 10.0, *) {
- if newOptionsMap["ignoresViewportScaleLimits"] != nil && options?.ignoresViewportScaleLimits != newOptions.ignoresViewportScaleLimits {
- configuration.ignoresViewportScaleLimits = newOptions.ignoresViewportScaleLimits
- }
-
- if newOptionsMap["dataDetectorTypes"] != nil && options?.dataDetectorTypes != newOptions.dataDetectorTypes {
- var dataDetectorTypes = WKDataDetectorTypes.init(rawValue: 0)
- for type in newOptions.dataDetectorTypes {
- let dataDetectorType = getDataDetectorType(type: type)
- dataDetectorTypes = WKDataDetectorTypes(rawValue: dataDetectorTypes.rawValue | dataDetectorType.rawValue)
- }
- configuration.dataDetectorTypes = dataDetectorTypes
- }
- } else {
- // Fallback on earlier versions
- }
-
- if #available(iOS 13.0, *) {
- configuration.preferences.isFraudulentWebsiteWarningEnabled = (options?.isFraudulentWebsiteWarningEnabled)!
- configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: (options?.preferredContentMode)!)!
- } else {
- // Fallback on earlier versions
- }
-
- if newOptionsMap["verticalScrollBarEnabled"] != nil && options?.verticalScrollBarEnabled != newOptions.verticalScrollBarEnabled {
- scrollView.showsVerticalScrollIndicator = newOptions.verticalScrollBarEnabled
- }
- if newOptionsMap["horizontalScrollBarEnabled"] != nil && options?.horizontalScrollBarEnabled != newOptions.horizontalScrollBarEnabled {
- scrollView.showsHorizontalScrollIndicator = newOptions.horizontalScrollBarEnabled
- }
-
- if #available(iOS 9.0, *) {
- if newOptionsMap["allowsLinkPreview"] != nil && options?.allowsLinkPreview != newOptions.allowsLinkPreview {
- allowsLinkPreview = newOptions.allowsLinkPreview
- }
- if newOptionsMap["allowsPictureInPictureMediaPlayback"] != nil && options?.allowsPictureInPictureMediaPlayback != newOptions.allowsPictureInPictureMediaPlayback {
- configuration.allowsPictureInPictureMediaPlayback = newOptions.allowsPictureInPictureMediaPlayback
- }
- if newOptionsMap["applicationNameForUserAgent"] != nil && options?.applicationNameForUserAgent != newOptions.applicationNameForUserAgent && newOptions.applicationNameForUserAgent != "" {
- configuration.applicationNameForUserAgent = newOptions.applicationNameForUserAgent
- }
- if newOptionsMap["userAgent"] != nil && options?.userAgent != newOptions.userAgent && newOptions.userAgent != "" {
- customUserAgent = newOptions.userAgent
- }
- }
-
-
-
- if newOptionsMap["clearCache"] != nil && newOptions.clearCache {
- clearCache()
- }
-
- if #available(iOS 11.0, *), newOptionsMap["contentBlockers"] != nil {
- configuration.userContentController.removeAllContentRuleLists()
- let contentBlockers = newOptions.contentBlockers
- if contentBlockers.count > 0 {
- do {
- let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
- let blockRules = String(data: jsonData, encoding: String.Encoding.utf8)
- WKContentRuleListStore.default().compileContentRuleList(
- forIdentifier: "ContentBlockingRules",
- encodedContentRuleList: blockRules) { (contentRuleList, error) in
- if let error = error {
- print(error.localizedDescription)
- return
- }
- self.configuration.userContentController.add(contentRuleList!)
- }
- } catch {
- print(error.localizedDescription)
- }
- }
- }
-
- self.options = newOptions
- }
-
- func getOptions() -> [String: Any]? {
- if (self.options == nil) {
- return nil
- }
- return self.options!.getHashMap()
- }
-
- public func clearCache() {
- if #available(iOS 9.0, *) {
- //let websiteDataTypes = NSSet(array: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache])
- let date = NSDate(timeIntervalSince1970: 0)
- WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: date as Date, completionHandler:{ })
- } else {
- var libraryPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, false).first!
- libraryPath += "/Cookies"
-
- do {
- try FileManager.default.removeItem(atPath: libraryPath)
- } catch {
- print("can't clear cache")
- }
- URLCache.shared.removeAllCachedResponses()
- }
- }
-
- public func injectDeferredObject(source: String, withWrapper jsWrapper: String, result: FlutterResult?) {
- let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: [])
- let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8)
- if sourceArrayString != nil {
- let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.count ?? 0) - 2))
- let jsToInject = String(format: jsWrapper, sourceString!)
-
- evaluateJavaScript(jsToInject, completionHandler: {(value, error) in
- if result == nil {
- return
- }
-
- if error != nil {
- let userInfo = (error! as NSError).userInfo
- self.onConsoleMessage(sourceURL: (userInfo["WKJavaScriptExceptionSourceURL"] as? URL)?.absoluteString ?? "", lineNumber: userInfo["WKJavaScriptExceptionLineNumber"] as! Int, message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: 3)
- }
-
- if value == nil {
- result!("")
- return
- }
-
- do {
- let data: Data = ("[" + String(describing: value!) + "]").data(using: String.Encoding.utf8, allowLossyConversion: false)!
- let json: Array = try JSONSerialization.jsonObject(with: data, options: []) as! Array
- if json[0] is String {
- result!(json[0])
- }
- else {
- result!(value)
- }
- } catch let error as NSError {
- result!(FlutterError(code: "InAppBrowserFlutterPlugin", message: "Failed to load: \(error.localizedDescription)", details: error))
- }
-
- })
- }
- }
-
- public func injectScriptCode(source: String, result: FlutterResult?) {
- let jsWrapper = "(function(){return JSON.stringify(eval(%@));})();"
- injectDeferredObject(source: source, withWrapper: jsWrapper, result: result)
- }
-
- public func injectScriptFile(urlFile: String) {
- let jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document);"
- injectDeferredObject(source: urlFile, withWrapper: jsWrapper, result: nil)
- }
-
- public func injectStyleCode(source: String) {
- let jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document);"
- injectDeferredObject(source: source, withWrapper: jsWrapper, result: nil)
- }
-
- public func injectStyleFile(urlFile: String) {
- let jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document);"
- injectDeferredObject(source: urlFile, withWrapper: jsWrapper, result: nil)
- }
-
- public func getCopyBackForwardList() -> [String: Any] {
- let currentList = backForwardList
- let currentIndex = currentList.backList.count
- var completeList = currentList.backList
- if currentList.currentItem != nil {
- completeList.append(currentList.currentItem!)
- }
- completeList.append(contentsOf: currentList.forwardList)
-
- var history: [[String: String]] = []
-
- for historyItem in completeList {
- var historyItemMap: [String: String] = [:]
- historyItemMap["originalUrl"] = historyItem.initialURL.absoluteString
- historyItemMap["title"] = historyItem.title
- historyItemMap["url"] = historyItem.url.absoluteString
- history.append(historyItemMap)
- }
-
- var result: [String: Any] = [:]
- result["history"] = history
- result["currentIndex"] = currentIndex
-
- return result;
- }
-
- public func webView(_ webView: WKWebView,
- decidePolicyFor navigationAction: WKNavigationAction,
- decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
-
- let app = UIApplication.shared
-
- if let url = navigationAction.request.url {
- if url.absoluteString != url.absoluteString && (options?.useOnLoadResource)! {
- WKNavigationMap[url.absoluteString] = [
- "startTime": currentTimeInMilliSeconds(),
- "request": navigationAction.request
- ]
- }
-
- // Handle target="_blank"
- if navigationAction.targetFrame == nil && (options?.useOnTargetBlank)! {
- onTargetBlank(url: url)
- decisionHandler(.cancel)
- return
- }
-
- if navigationAction.navigationType == .linkActivated && (options?.useShouldOverrideUrlLoading)! {
- shouldOverrideUrlLoading(url: url)
- decisionHandler(.cancel)
- return
- }
-
- // Handle phone and email links
- if url.scheme == "tel" || url.scheme == "mailto" {
- if app.canOpenURL(url) {
- if #available(iOS 10.0, *) {
- app.open(url)
- } else {
- app.openURL(url)
- }
- }
- decisionHandler(.cancel)
- return
- }
-
- if navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .backForward {
- currentURL = url
- if IABController != nil {
- IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!)
- }
- }
- }
-
- decisionHandler(.allow)
- }
-
- public func webView(_ webView: WKWebView,
- decidePolicyFor navigationResponse: WKNavigationResponse,
- decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
-
-// if (options?.useOnLoadResource)! {
-// if let url = navigationResponse.response.url {
-// if WKNavigationMap[url.absoluteString] != nil {
-// let startResourceTime: Int64 = (WKNavigationMap[url.absoluteString]!["startTime"] as! Int64)
-// let startTime: Int64 = startResourceTime - startPageTime;
-// let duration: Int64 = currentTimeInMilliSeconds() - startResourceTime;
-// onLoadResource(response: navigationResponse.response, fromRequest: WKNavigationMap[url.absoluteString]!["request"] as? URLRequest, withData: Data(), startTime: startTime, duration: duration)
-// }
-// }
-// }
-
- if (options?.useOnDownloadStart)! {
- let mimeType = navigationResponse.response.mimeType
- if let url = navigationResponse.response.url {
- if mimeType != nil && !mimeType!.starts(with: "text/") {
- onDownloadStart(url: url.absoluteString)
- decisionHandler(.cancel)
- return
- }
- }
- }
-
- decisionHandler(.allow)
- }
-
- public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
- self.startPageTime = currentTimeInMilliSeconds()
- onLoadStart(url: (currentURL?.absoluteString)!)
-
- if IABController != nil {
- // loading url, start spinner, update back/forward
- IABController!.backButton.isEnabled = canGoBack
- IABController!.forwardButton.isEnabled = canGoForward
-
- if (IABController!.browserOptions?.spinner)! {
- IABController!.spinner.startAnimating()
- }
- }
- }
-
- public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
- self.WKNavigationMap = [:]
- currentURL = url
- InAppWebView.credentialsProposed = []
- onLoadStop(url: (currentURL?.absoluteString)!)
- evaluateJavaScript(platformReadyJS, completionHandler: nil)
-
- if IABController != nil {
- IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!)
- IABController!.backButton.isEnabled = canGoBack
- IABController!.forwardButton.isEnabled = canGoForward
- IABController!.spinner.stopAnimating()
- }
-
-// findAllAsync("Flutter", completionHandler: {(value, error) in
-// if error != nil {
-// print(error)
-// } else if let foundOccurences: Int = value as! Int {
-// print(foundOccurences)
-// //self.findNext(to: foundOccurences - 4, completionHandler: nil)
-// }
-// })
- }
-
- public func webView(_ view: WKWebView,
- didFailProvisionalNavigation navigation: WKNavigation!,
- withError error: Error) {
- webView(view, didFail: navigation, withError: error)
- }
-
- public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
- InAppWebView.credentialsProposed = []
-
- onLoadError(url: (currentURL?.absoluteString)!, error: error)
-
- if IABController != nil {
- IABController!.backButton.isEnabled = canGoBack
- IABController!.forwardButton.isEnabled = canGoForward
- IABController!.spinner.stopAnimating()
- }
- }
-
- public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
-
- if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
- challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault ||
- challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest {
- let host = challenge.protectionSpace.host
- let prot = challenge.protectionSpace.protocol
- let realm = challenge.protectionSpace.realm
- let port = challenge.protectionSpace.port
- onReceivedHttpAuthRequest(challenge: challenge, result: {(result) -> Void in
- if result is FlutterError {
- print((result as! FlutterError).message)
- }
- else if (result as? NSObject) == FlutterMethodNotImplemented {
- completionHandler(.performDefaultHandling, nil)
- }
- else {
- var response: [String: Any]
- if let r = result {
- response = r as! [String: Any]
- var action = response["action"] as? Int
- action = action != nil ? action : 0;
- switch action {
- case 0:
- InAppWebView.credentialsProposed = []
- completionHandler(.cancelAuthenticationChallenge, nil)
- break
- case 1:
- let username = response["username"] as! String
- let password = response["password"] as! String
- let permanentPersistence = response["permanentPersistence"] as? Bool ?? false
- let persistence = (permanentPersistence) ? URLCredential.Persistence.permanent : URLCredential.Persistence.forSession
- let credential = URLCredential(user: username, password: password, persistence: persistence)
- completionHandler(.useCredential, credential)
- break
- case 2:
- if InAppWebView.credentialsProposed.count == 0 {
- for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials {
- if protectionSpace.host == host && protectionSpace.realm == realm &&
- protectionSpace.protocol == prot && protectionSpace.port == port {
- for credential in credentials {
- InAppWebView.credentialsProposed.append(credential.value)
- }
- break
- }
- }
- }
- if InAppWebView.credentialsProposed.count == 0, let credential = challenge.proposedCredential {
- InAppWebView.credentialsProposed.append(credential)
- }
-
- if let credential = InAppWebView.credentialsProposed.popLast() {
- completionHandler(.useCredential, credential)
- }
- else {
- completionHandler(.performDefaultHandling, nil)
- }
- break
- default:
- InAppWebView.credentialsProposed = []
- completionHandler(.performDefaultHandling, nil)
- }
- return;
- }
- completionHandler(.performDefaultHandling, nil)
- }
- })
- }
- else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
-
- guard let serverTrust = challenge.protectionSpace.serverTrust else {
- completionHandler(.performDefaultHandling, nil)
- return
- }
-
- onReceivedServerTrustAuthRequest(challenge: challenge, result: {(result) -> Void in
- if result is FlutterError {
- print((result as! FlutterError).message)
- }
- else if (result as? NSObject) == FlutterMethodNotImplemented {
- completionHandler(.performDefaultHandling, nil)
- }
- else {
- var response: [String: Any]
- if let r = result {
- response = r as! [String: Any]
- var action = response["action"] as? Int
- action = action != nil ? action : 0;
- switch action {
- case 0:
- InAppWebView.credentialsProposed = []
- completionHandler(.cancelAuthenticationChallenge, nil)
- break
- case 1:
- let exceptions = SecTrustCopyExceptions(serverTrust)
- SecTrustSetExceptions(serverTrust, exceptions)
- let credential = URLCredential(trust: serverTrust)
- completionHandler(.useCredential, credential)
- break
- default:
- InAppWebView.credentialsProposed = []
- completionHandler(.performDefaultHandling, nil)
- }
- return;
- }
- completionHandler(.performDefaultHandling, nil)
- }
- })
- }
- else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
- onReceivedClientCertRequest(challenge: challenge, result: {(result) -> Void in
- if result is FlutterError {
- print((result as! FlutterError).message)
- }
- else if (result as? NSObject) == FlutterMethodNotImplemented {
- completionHandler(.performDefaultHandling, nil)
- }
- else {
- var response: [String: Any]
- if let r = result {
- response = r as! [String: Any]
- var action = response["action"] as? Int
- action = action != nil ? action : 0;
- switch action {
- case 0:
- completionHandler(.cancelAuthenticationChallenge, nil)
- break
- case 1:
- let certificatePath = response["certificatePath"] as! String;
- let certificatePassword = response["certificatePassword"] as? String ?? "";
-
- let key = SwiftFlutterPlugin.instance!.registrar!.lookupKey(forAsset: certificatePath)
- let path = Bundle.main.path(forResource: key, ofType: nil)!
- let PKCS12Data = NSData(contentsOfFile: path)!
-
- if let identityAndTrust: IdentityAndTrust = self.extractIdentity(PKCS12Data: PKCS12Data, password: certificatePassword) {
- let urlCredential: URLCredential = URLCredential(
- identity: identityAndTrust.identityRef,
- certificates: identityAndTrust.certArray as? [AnyObject],
- persistence: URLCredential.Persistence.forSession);
- completionHandler(.useCredential, urlCredential)
- } else {
- completionHandler(.performDefaultHandling, nil)
- }
- break
- case 2:
- completionHandler(.cancelAuthenticationChallenge, nil)
- break
- default:
- completionHandler(.performDefaultHandling, nil)
- }
- return;
- }
- completionHandler(.performDefaultHandling, nil)
- }
- })
- }
- else {
- completionHandler(.performDefaultHandling, nil)
- }
- }
-
- struct IdentityAndTrust {
-
- var identityRef:SecIdentity
- var trust:SecTrust
- var certArray:AnyObject
- }
-
- func extractIdentity(PKCS12Data:NSData, password: String) -> IdentityAndTrust? {
- var identityAndTrust:IdentityAndTrust?
- var securityError:OSStatus = errSecSuccess
-
- var importResult: CFArray? = nil
- securityError = SecPKCS12Import(
- PKCS12Data as NSData,
- [kSecImportExportPassphrase as String: password] as NSDictionary,
- &importResult
- )
-
- if securityError == errSecSuccess {
- let certItems:CFArray = importResult! as CFArray;
- let certItemsArray:Array = certItems as Array
- let dict:AnyObject? = certItemsArray.first;
- if let certEntry:Dictionary = dict as? Dictionary {
- // grab the identity
- let identityPointer:AnyObject? = certEntry["identity"];
- let secIdentityRef:SecIdentity = (identityPointer as! SecIdentity?)!;
- // grab the trust
- let trustPointer:AnyObject? = certEntry["trust"];
- let trustRef:SecTrust = trustPointer as! SecTrust;
- // grab the cert
- let chainPointer:AnyObject? = certEntry["chain"];
- identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: chainPointer!);
- }
- } else {
- print("Security Error: " + securityError.description)
- if #available(iOS 11.3, *) {
- print(SecCopyErrorMessageString(securityError,nil))
- }
- }
- return identityAndTrust;
- }
-
-
- func createAlertDialog(message: String?, responseMessage: String?, confirmButtonTitle: String?, completionHandler: @escaping () -> Void) {
- let title = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
- let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
- let alertController = UIAlertController(title: title, message: nil,
- preferredStyle: UIAlertController.Style.alert);
-
- alertController.addAction(UIAlertAction(title: okButton, style: UIAlertAction.Style.default) {
- _ in completionHandler()}
- );
-
- let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!)
- presentingViewController.present(alertController, animated: true, completion: {})
- }
-
- public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String,
- initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
-
- onJsAlert(message: message, result: {(result) -> Void in
- if result is FlutterError {
- print((result as! FlutterError).message)
- }
- else if (result as? NSObject) == FlutterMethodNotImplemented {
- self.createAlertDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, completionHandler: completionHandler)
- }
- else {
- let response: [String: Any]
- var responseMessage: String?;
- var confirmButtonTitle: String?;
-
- if let r = result {
- response = r as! [String: Any]
- responseMessage = response["message"] as? String
- confirmButtonTitle = response["confirmButtonTitle"] as? String
- let handledByClient = response["handledByClient"] as? Bool
- if handledByClient != nil, handledByClient! {
- var action = response["action"] as? Int
- action = action != nil ? action : 1;
- switch action {
- case 0:
- completionHandler()
- break
- default:
- completionHandler()
- }
- return;
- }
- }
-
- self.createAlertDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler)
- }
- })
- }
-
- func createConfirmDialog(message: String?, responseMessage: String?, confirmButtonTitle: String?, cancelButtonTitle: String?, completionHandler: @escaping (Bool) -> Void) {
- let dialogMessage = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
- let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
- let cancelButton = cancelButtonTitle != nil && !cancelButtonTitle!.isEmpty ? cancelButtonTitle : NSLocalizedString("Cancel", comment: "")
-
- let alertController = UIAlertController(title: nil, message: dialogMessage, preferredStyle: .alert)
-
- alertController.addAction(UIAlertAction(title: okButton, style: .default, handler: { (action) in
- completionHandler(true)
- }))
-
- alertController.addAction(UIAlertAction(title: cancelButton, style: .cancel, handler: { (action) in
- completionHandler(false)
- }))
-
- let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!)
- presentingViewController.present(alertController, animated: true, completion: nil)
- }
-
- public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
- completionHandler: @escaping (Bool) -> Void) {
-
- onJsConfirm(message: message, result: {(result) -> Void in
- if result is FlutterError {
- print((result as! FlutterError).message)
- }
- else if (result as? NSObject) == FlutterMethodNotImplemented {
- self.createConfirmDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, completionHandler: completionHandler)
- }
- else {
- let response: [String: Any]
- var responseMessage: String?;
- var confirmButtonTitle: String?;
- var cancelButtonTitle: String?;
-
- if let r = result {
- response = r as! [String: Any]
- responseMessage = response["message"] as? String
- confirmButtonTitle = response["confirmButtonTitle"] as? String
- cancelButtonTitle = response["cancelButtonTitle"] as? String
- let handledByClient = response["handledByClient"] as? Bool
- if handledByClient != nil, handledByClient! {
- var action = response["action"] as? Int
- action = action != nil ? action : 1;
- switch action {
- case 0:
- completionHandler(true)
- break
- case 1:
- completionHandler(false)
- break
- default:
- completionHandler(false)
- }
- return;
- }
- }
- self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler)
- }
- })
- }
-
- func createPromptDialog(message: String, defaultValue: String?, responseMessage: String?, confirmButtonTitle: String?, cancelButtonTitle: String?, value: String?, completionHandler: @escaping (String?) -> Void) {
- let dialogMessage = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
- let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
- let cancelButton = cancelButtonTitle != nil && !cancelButtonTitle!.isEmpty ? cancelButtonTitle : NSLocalizedString("Cancel", comment: "")
-
- let alertController = UIAlertController(title: nil, message: dialogMessage, preferredStyle: .alert)
-
- alertController.addTextField { (textField) in
- textField.text = defaultValue
- }
-
- alertController.addAction(UIAlertAction(title: okButton, style: .default, handler: { (action) in
- if let v = value {
- completionHandler(v)
- }
- else if let text = alertController.textFields?.first?.text {
- completionHandler(text)
- } else {
- completionHandler("")
- }
- }))
-
- alertController.addAction(UIAlertAction(title: cancelButton, style: .cancel, handler: { (action) in
- completionHandler(nil)
- }))
-
- let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!)
- presentingViewController.present(alertController, animated: true, completion: nil)
- }
-
- public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo,
- completionHandler: @escaping (String?) -> Void) {
- onJsPrompt(message: message, defaultValue: defaultValue, result: {(result) -> Void in
- if result is FlutterError {
- print((result as! FlutterError).message)
- }
- else if (result as? NSObject) == FlutterMethodNotImplemented {
- self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, value: nil, completionHandler: completionHandler)
- }
- else {
- let response: [String: Any]
- var responseMessage: String?;
- var confirmButtonTitle: String?;
- var cancelButtonTitle: String?;
- var value: String?;
-
- if let r = result {
- response = r as! [String: Any]
- responseMessage = response["message"] as? String
- confirmButtonTitle = response["confirmButtonTitle"] as? String
- cancelButtonTitle = response["cancelButtonTitle"] as? String
- let handledByClient = response["handledByClient"] as? Bool
- value = response["value"] as? String;
- if handledByClient != nil, handledByClient! {
- var action = response["action"] as? Int
- action = action != nil ? action : 1;
- switch action {
- case 0:
- completionHandler(value)
- break
- case 1:
- completionHandler(nil)
- break
- default:
- completionHandler(nil)
- }
- return;
- }
- }
-
- self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler)
- }
- })
- }
-
- public func scrollViewDidScroll(_ scrollView: UIScrollView) {
- if navigationDelegate != nil {
- let x = Int(scrollView.contentOffset.x / scrollView.contentScaleFactor)
- let y = Int(scrollView.contentOffset.y / scrollView.contentScaleFactor)
- onScrollChanged(x: x, y: y)
- }
- setNeedsLayout()
- }
-
- public func onLoadStart(url: String) {
- var arguments: [String: Any] = ["url": url]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onLoadStart", arguments: arguments)
- }
- }
-
- public func onLoadStop(url: String) {
- var arguments: [String: Any] = ["url": url]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onLoadStop", arguments: arguments)
- }
- }
-
- public func onLoadError(url: String, error: Error) {
- var arguments: [String: Any] = ["url": url, "code": error._code, "message": error.localizedDescription]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onLoadError", arguments: arguments)
- }
- }
-
- public func onProgressChanged(progress: Int) {
- var arguments: [String: Any] = ["progress": progress]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onProgressChanged", arguments: arguments)
- }
- }
-
- public func onLoadResource(initiatorType: String, url: String, startTime: Double, duration: Double) {
- var arguments: [String : Any] = [
- "initiatorType": initiatorType,
- "url": url,
- "startTime": startTime,
- "duration": duration
- ]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onLoadResource", arguments: arguments)
- }
- }
-
- public func onScrollChanged(x: Int, y: Int) {
- var arguments: [String: Any] = ["x": x, "y": y]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onScrollChanged", arguments: arguments)
- }
- }
-
- public func onDownloadStart(url: String) {
- var arguments: [String: Any] = ["url": url]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onDownloadStart", arguments: arguments)
- }
- }
-
- public func onLoadResourceCustomScheme(scheme: String, url: String, result: FlutterResult?) {
- var arguments: [String: Any] = ["scheme": scheme, "url": url]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onLoadResourceCustomScheme", arguments: arguments, result: result)
- }
- }
-
- public func shouldOverrideUrlLoading(url: URL) {
- var arguments: [String: Any] = ["url": url.absoluteString]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("shouldOverrideUrlLoading", arguments: arguments)
- }
- }
-
- public func onTargetBlank(url: URL) {
- var arguments: [String: Any] = ["url": url.absoluteString]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onTargetBlank", arguments: arguments)
- }
- }
-
- public func onReceivedHttpAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
- var arguments: [String: Any?] = [
- "host": challenge.protectionSpace.host,
- "protocol": challenge.protectionSpace.protocol,
- "realm": challenge.protectionSpace.realm,
- "port": challenge.protectionSpace.port,
- "previousFailureCount": challenge.previousFailureCount
- ]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onReceivedHttpAuthRequest", arguments: arguments, result: result)
- }
- }
-
- public func onReceivedServerTrustAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
- var serverCertificateData: NSData?
- let serverTrust = challenge.protectionSpace.serverTrust!
- if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
- let serverCertificateCFData = SecCertificateCopyData(serverCertificate)
- let data = CFDataGetBytePtr(serverCertificateCFData)
- let size = CFDataGetLength(serverCertificateCFData)
- serverCertificateData = NSData(bytes: data, length: size)
- }
-
- var arguments: [String: Any?] = [
- "host": challenge.protectionSpace.host,
- "protocol": challenge.protectionSpace.protocol,
- "realm": challenge.protectionSpace.realm,
- "port": challenge.protectionSpace.port,
- "previousFailureCount": challenge.previousFailureCount,
- "serverCertificate": serverCertificateData,
- "error": -1,
- "message": "",
- ]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onReceivedServerTrustAuthRequest", arguments: arguments, result: result)
- }
- }
-
- public func onReceivedClientCertRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
- var arguments: [String: Any?] = [
- "host": challenge.protectionSpace.host,
- "protocol": challenge.protectionSpace.protocol,
- "realm": challenge.protectionSpace.realm,
- "port": challenge.protectionSpace.port
- ]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onReceivedClientCertRequest", arguments: arguments, result: result)
- }
- }
-
- public func onJsAlert(message: String, result: FlutterResult?) {
- var arguments: [String: Any] = ["message": message]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onJsAlert", arguments: arguments, result: result)
- }
- }
-
- public func onJsConfirm(message: String, result: FlutterResult?) {
- var arguments: [String: Any] = ["message": message]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onJsConfirm", arguments: arguments, result: result)
- }
- }
-
- public func onJsPrompt(message: String, defaultValue: String?, result: FlutterResult?) {
- var arguments: [String: Any] = ["message": message, "defaultValue": defaultValue as Any]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onJsPrompt", arguments: arguments, result: result)
- }
- }
-
- public func onConsoleMessage(sourceURL: String, lineNumber: Int, message: String, messageLevel: Int) {
- var arguments: [String: Any] = ["sourceURL": sourceURL, "lineNumber": lineNumber, "message": message, "messageLevel": messageLevel]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
- if let channel = getChannel() {
- channel.invokeMethod("onConsoleMessage", arguments: arguments)
- }
- }
-
- public func onCallJsHandler(handlerName: String, _callHandlerID: Int64, args: String) {
- var arguments: [String: Any] = ["handlerName": handlerName, "args": args]
- if IABController != nil {
- arguments["uuid"] = IABController!.uuid
- }
-
- if let channel = getChannel() {
- channel.invokeMethod("onCallJsHandler", arguments: arguments, result: {(result) -> Void in
- if result is FlutterError {
- print((result as! FlutterError).message)
- }
- else if (result as? NSObject) == FlutterMethodNotImplemented {}
- else {
- var json = "null"
- if let r = result {
- json = r as! String
- }
- self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)](\(json)); delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)];", completionHandler: nil)
- }
- })
- }
- }
-
- public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
- if message.name.starts(with: "console") {
- var messageLevel = 1
- switch (message.name) {
- case "consoleLog":
- messageLevel = 1
- break;
- case "consoleDebug":
- // on Android, console.debug is TIP
- messageLevel = 0
- break;
- case "consoleError":
- messageLevel = 3
- break;
- case "consoleInfo":
- // on Android, console.info is LOG
- messageLevel = 1
- break;
- case "consoleWarn":
- messageLevel = 2
- break;
- default:
- messageLevel = 1
- break;
- }
- onConsoleMessage(sourceURL: "", lineNumber: 1, message: message.body as! String, messageLevel: messageLevel)
- }
- else if message.name == "resourceLoaded" && (options?.useOnLoadResource)! {
- if let resource = convertToDictionary(text: message.body as! String) {
- // escape special chars
- let resourceName = (resource["name"] as! String).addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
-
- let url = URL(string: resourceName!)!
- if !UIApplication.shared.canOpenURL(url) {
- return
- }
- let initiatorType = resource["initiatorType"] as! String
- let startTime = resource["startTime"] as! Double
- let duration = resource["duration"] as! Double
-
- self.onLoadResource(initiatorType: initiatorType, url: url.absoluteString, startTime: startTime, duration: duration)
- }
- }
- else if message.name == "callHandler" {
- let body = message.body as! [String: Any]
- let handlerName = body["handlerName"] as! String
- let _callHandlerID = body["_callHandlerID"] as! Int64
- let args = body["args"] as! String
- onCallJsHandler(handlerName: handlerName, _callHandlerID: _callHandlerID, args: args)
- }
- }
-
- private func getChannel() -> FlutterMethodChannel? {
- return (IABController != nil) ? SwiftFlutterPlugin.instance!.channel! : ((IAWController != nil) ? IAWController!.channel! : nil);
- }
-
- func findAllAsync(_ str: String?, completionHandler: ((Any?, Error?) -> Void)?) {
- let startSearch = "uiWebview_HighlightAllOccurencesOfString('\(str ?? "")'); uiWebview_SearchResultCount"
- evaluateJavaScript(startSearch, completionHandler: completionHandler)
- }
-
- func findNext(to index: Int, completionHandler: ((Any?, Error?) -> Void)?) {
- evaluateJavaScript("uiWebview_ScrollTo('\(index)')", completionHandler: completionHandler)
- }
-
- func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) {
- evaluateJavaScript("uiWebview_RemoveAllHighlights()", completionHandler: completionHandler)
- }
-}
+//
+// InAppWebView.swift
+// flutter_inappbrowser
+//
+// Created by Lorenzo on 21/10/18.
+//
+
+import Flutter
+import Foundation
+import WebKit
+
+func currentTimeInMilliSeconds() -> Int64 {
+ let currentDate = Date()
+ let since1970 = currentDate.timeIntervalSince1970
+ return Int64(since1970 * 1000)
+}
+
+func convertToDictionary(text: String) -> [String: Any]? {
+ if let data = text.data(using: .utf8) {
+ do {
+ return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+ return nil
+}
+
+// the message needs to be concatenated with '' in order to have the same behavior like on Android
+let consoleLogJS = """
+(function() {
+ 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() {
+ var message = '';
+ for (var i in arguments) {
+ if (message == '') {
+ message += arguments[i];
+ }
+ else {
+ message += ' ' + arguments[i];
+ }
+ }
+ window.webkit.messageHandlers[oldLog].postMessage(message);
+ }
+ })(k);
+ }
+})();
+"""
+
+let resourceObserverJS = """
+(function() {
+ var observer = new PerformanceObserver(function(list) {
+ list.getEntries().forEach(function(entry) {
+ window.webkit.messageHandlers['resourceLoaded'].postMessage(JSON.stringify(entry));
+ });
+ });
+ observer.observe({entryTypes: ['resource']});
+})();
+"""
+
+let JAVASCRIPT_BRIDGE_NAME = "flutter_inappbrowser"
+
+let javaScriptBridgeJS = """
+window.\(JAVASCRIPT_BRIDGE_NAME) = {};
+window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
+ var _callHandlerID = setTimeout(function(){});
+ window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1))} );
+ return new Promise(function(resolve, reject) {
+ window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = resolve;
+ });
+}
+"""
+
+let platformReadyJS = "window.dispatchEvent(new Event('flutterInAppBrowserPlatformReady'));";
+
+let findTextHighlightJS = """
+var wkwebview_SearchResultCount = 0;
+var wkwebview_CurrentHighlight = 0;
+var wkwebview_IsDoneCounting = false;
+
+function wkwebview_FindAllAsyncForElement(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",
+ "WKWEBVIEW_SEARCH_WORD_" + wkwebview_SearchResultCount
+ );
+ span.setAttribute("class", "wkwebview_Highlight");
+ var backgroundColor = wkwebview_SearchResultCount == 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;
+
+ wkwebview_SearchResultCount++;
+ elementTmp = document.createTextNode(
+ value.substr(idx + keyword.length)
+ );
+
+ window.webkit.messageHandlers["findResultReceived"].postMessage(
+ JSON.stringify({
+ activeMatchOrdinal: wkwebview_CurrentHighlight,
+ numberOfMatches: wkwebview_SearchResultCount,
+ isDoneCounting: wkwebview_IsDoneCounting
+ })
+ );
+ }
+ } 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--) {
+ wkwebview_FindAllAsyncForElement(
+ element.childNodes[element.childNodes.length - 1 - i],
+ keyword
+ );
+ }
+ }
+ }
+ }
+}
+
+// the main entry point to start the search
+function wkwebview_FindAllAsync(keyword) {
+ wkwebview_ClearMatches();
+ wkwebview_FindAllAsyncForElement(document.body, keyword.toLowerCase());
+ wkwebview_IsDoneCounting = true;
+ window.webkit.messageHandlers["findResultReceived"].postMessage(
+ JSON.stringify({
+ activeMatchOrdinal: wkwebview_CurrentHighlight,
+ numberOfMatches: wkwebview_SearchResultCount,
+ isDoneCounting: wkwebview_IsDoneCounting
+ })
+ );
+}
+
+// helper function, recursively removes the highlights in elements and their childs
+function wkwebview_ClearMatchesForElement(element) {
+ if (element) {
+ if (element.nodeType == 1) {
+ if (element.getAttribute("class") == "wkwebview_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 (wkwebview_ClearMatchesForElement(element.childNodes[i])) {
+ normalize = true;
+ }
+ }
+ if (normalize) {
+ element.normalize();
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// the main entry point to remove the highlights
+function wkwebview_ClearMatches() {
+ wkwebview_SearchResultCount = 0;
+ wkwebview_CurrentHighlight = 0;
+ wkwebview_ClearMatchesForElement(document.body);
+}
+
+function wkwebview_FindNext(forward) {
+ if (wkwebview_SearchResultCount <= 0) return;
+
+ var idx = wkwebview_CurrentHighlight + (forward ? +1 : -1);
+ idx =
+ idx < 0
+ ? wkwebview_SearchResultCount - 1
+ : idx >= wkwebview_SearchResultCount
+ ? 0
+ : idx;
+ wkwebview_CurrentHighlight = idx;
+
+ var scrollTo = document.getElementById("WKWEBVIEW_SEARCH_WORD_" + idx);
+ if (scrollTo) {
+ var highlights = document.getElementsByClassName("wkwebview_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"
+ });
+
+ window.webkit.messageHandlers["findResultReceived"].postMessage(
+ JSON.stringify({
+ activeMatchOrdinal: wkwebview_CurrentHighlight,
+ numberOfMatches: wkwebview_SearchResultCount,
+ isDoneCounting: wkwebview_IsDoneCounting
+ })
+ );
+ }
+}
+"""
+
+public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
+
+ var IABController: InAppBrowserWebViewController?
+ var IAWController: FlutterWebViewController?
+ var options: InAppWebViewOptions?
+ var currentURL: URL?
+ var WKNavigationMap: [String: [String: Any]] = [:]
+ var startPageTime: Int64 = 0
+ static var credentialsProposed: [URLCredential] = []
+
+ init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, IAWController: FlutterWebViewController?) {
+ super.init(frame: frame, configuration: configuration)
+ self.IABController = IABController
+ self.IAWController = IAWController
+ uiDelegate = self
+ navigationDelegate = self
+ scrollView.delegate = self
+ }
+
+ required public init(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)!
+ }
+
+ public func prepare() {
+ addObserver(self,
+ forKeyPath: #keyPath(WKWebView.estimatedProgress),
+ options: .new,
+ context: nil)
+
+ configuration.userContentController = WKUserContentController()
+ configuration.preferences = WKPreferences()
+
+ if (options?.transparentBackground)! {
+ isOpaque = false
+ backgroundColor = UIColor.clear
+ scrollView.backgroundColor = UIColor.clear
+ }
+
+ // prevent webView from bouncing
+ if (options?.disallowOverScroll)! {
+ if responds(to: #selector(getter: scrollView)) {
+ scrollView.bounces = false
+ }
+ else {
+ for subview: UIView in subviews {
+ if subview is UIScrollView {
+ (subview as! UIScrollView).bounces = false
+ }
+ }
+ }
+ }
+
+ if (options?.enableViewportScale)! {
+ let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
+ let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
+ configuration.userContentController.addUserScript(userScript)
+ }
+
+ // Prevents long press on links that cause WKWebView exit
+ let jscriptWebkitTouchCallout = WKUserScript(source: "document.body.style.webkitTouchCallout='none';", injectionTime: .atDocumentEnd, forMainFrameOnly: true)
+ configuration.userContentController.addUserScript(jscriptWebkitTouchCallout)
+
+
+ let consoleLogJSScript = WKUserScript(source: consoleLogJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+ configuration.userContentController.addUserScript(consoleLogJSScript)
+ configuration.userContentController.add(self, name: "consoleLog")
+ configuration.userContentController.add(self, name: "consoleDebug")
+ configuration.userContentController.add(self, name: "consoleError")
+ configuration.userContentController.add(self, name: "consoleInfo")
+ configuration.userContentController.add(self, name: "consoleWarn")
+
+ let javaScriptBridgeJSScript = WKUserScript(source: javaScriptBridgeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+ configuration.userContentController.addUserScript(javaScriptBridgeJSScript)
+ configuration.userContentController.add(self, name: "callHandler")
+
+ let resourceObserverJSScript = WKUserScript(source: resourceObserverJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+ configuration.userContentController.addUserScript(resourceObserverJSScript)
+ configuration.userContentController.add(self, name: "resourceLoaded")
+
+ let findTextHighlightJSScript = WKUserScript(source: findTextHighlightJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+ configuration.userContentController.addUserScript(findTextHighlightJSScript)
+ configuration.userContentController.add(self, name: "findResultReceived")
+
+ //keyboardDisplayRequiresUserAction = browserOptions?.keyboardDisplayRequiresUserAction
+
+ configuration.suppressesIncrementalRendering = (options?.suppressesIncrementalRendering)!
+ allowsBackForwardNavigationGestures = (options?.allowsBackForwardNavigationGestures)!
+ if #available(iOS 9.0, *) {
+ allowsLinkPreview = (options?.allowsLinkPreview)!
+ configuration.allowsPictureInPictureMediaPlayback = (options?.allowsPictureInPictureMediaPlayback)!
+ if ((options?.applicationNameForUserAgent)! != "") {
+ configuration.applicationNameForUserAgent = (options?.applicationNameForUserAgent)!
+ }
+ if ((options?.userAgent)! != "") {
+ customUserAgent = (options?.userAgent)!
+ }
+ }
+
+ configuration.preferences.javaScriptCanOpenWindowsAutomatically = (options?.javaScriptCanOpenWindowsAutomatically)!
+ configuration.preferences.javaScriptEnabled = (options?.javaScriptEnabled)!
+ configuration.preferences.minimumFontSize = CGFloat((options?.minimumFontSize)!)
+ configuration.selectionGranularity = WKSelectionGranularity.init(rawValue: (options?.selectionGranularity)!)!
+
+ if #available(iOS 10.0, *) {
+ configuration.ignoresViewportScaleLimits = (options?.ignoresViewportScaleLimits)!
+
+ var dataDetectorTypes = WKDataDetectorTypes.init(rawValue: 0)
+ for type in options?.dataDetectorTypes ?? [] {
+ let dataDetectorType = getDataDetectorType(type: type)
+ dataDetectorTypes = WKDataDetectorTypes(rawValue: dataDetectorTypes.rawValue | dataDetectorType.rawValue)
+ }
+ configuration.dataDetectorTypes = dataDetectorTypes
+ } else {
+ // Fallback on earlier versions
+ }
+
+ if #available(iOS 13.0, *) {
+ configuration.preferences.isFraudulentWebsiteWarningEnabled = (options?.isFraudulentWebsiteWarningEnabled)!
+ if options?.preferredContentMode != nil {
+ configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: (options?.preferredContentMode)!)!
+ }
+ } else {
+ // Fallback on earlier versions
+ }
+
+ scrollView.showsVerticalScrollIndicator = (options?.verticalScrollBarEnabled)!
+ scrollView.showsHorizontalScrollIndicator = (options?.horizontalScrollBarEnabled)!
+
+ // options.debuggingEnabled is always enabled for iOS.
+
+ if (options?.clearCache)! {
+ clearCache()
+ }
+ }
+
+ @available(iOS 10.0, *)
+ public func getDataDetectorType(type: String) -> WKDataDetectorTypes {
+ switch type {
+ case "NONE":
+ return WKDataDetectorTypes.init(rawValue: 0)
+ case "PHONE_NUMBER":
+ return .phoneNumber
+ case "LINK":
+ return .link
+ case "ADDRESS":
+ return .address
+ case "CALENDAR_EVENT":
+ return .calendarEvent
+ case "TRACKING_NUMBER":
+ return .trackingNumber
+ case "FLIGHT_NUMBER":
+ return .flightNumber
+ case "LOOKUP_SUGGESTION":
+ return .lookupSuggestion
+ case "SPOTLIGHT_SUGGESTION":
+ return .spotlightSuggestion
+ case "ALL":
+ return .all
+ default:
+ return WKDataDetectorTypes.init(rawValue: 0)
+ }
+ }
+
+ public static func preWKWebViewConfiguration(options: InAppWebViewOptions?) -> WKWebViewConfiguration {
+ let configuration = WKWebViewConfiguration()
+
+ if #available(iOS 10.0, *) {
+ configuration.mediaTypesRequiringUserActionForPlayback = ((options?.mediaPlaybackRequiresUserGesture)!) ? .all : []
+ } else {
+ // Fallback on earlier versions
+ configuration.mediaPlaybackRequiresUserAction = (options?.mediaPlaybackRequiresUserGesture)!
+ }
+
+ configuration.allowsInlineMediaPlayback = (options?.allowsInlineMediaPlayback)!
+
+ if #available(iOS 11.0, *) {
+ if let schemes = options?.resourceCustomSchemes {
+ for scheme in schemes {
+ configuration.setURLSchemeHandler(CustomeSchemeHandler(), forURLScheme: scheme)
+ }
+ }
+ } else {
+ // Fallback on earlier versions
+ }
+
+ return configuration
+ }
+
+ override public func observeValue(forKeyPath keyPath: String?, of object: Any?,
+ change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
+ if keyPath == "estimatedProgress" {
+ let progress = Int(estimatedProgress * 100)
+ onProgressChanged(progress: progress)
+ }
+ }
+
+ public func goBackOrForward(steps: Int) {
+ if canGoBackOrForward(steps: steps) {
+ if (steps > 0) {
+ let index = steps - 1
+ go(to: self.backForwardList.forwardList[index])
+ }
+ else if (steps < 0){
+ let backListLength = self.backForwardList.backList.count
+ let index = backListLength + steps
+ go(to: self.backForwardList.backList[index])
+ }
+ }
+ }
+
+ public func canGoBackOrForward(steps: Int) -> Bool {
+ let currentIndex = self.backForwardList.backList.count
+ return (steps >= 0)
+ ? steps <= self.backForwardList.forwardList.count
+ : currentIndex + steps >= 0
+ }
+
+ public func takeScreenshot (completionHandler: @escaping (_ screenshot: Data?) -> Void) {
+ if #available(iOS 11.0, *) {
+ takeSnapshot(with: nil, completionHandler: {(image, error) -> Void in
+ var imageData: Data? = nil
+ if let screenshot = image {
+ imageData = screenshot.pngData()!
+ }
+ completionHandler(imageData)
+ })
+ } else {
+ completionHandler(nil)
+ }
+ }
+
+ public func loadUrl(url: URL, headers: [String: String]?) {
+ var request = URLRequest(url: url)
+ currentURL = url
+ if headers != nil {
+ if let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest {
+ for (key, value) in headers! {
+ mutableRequest.setValue(value, forHTTPHeaderField: key)
+ }
+ request = mutableRequest as URLRequest
+ }
+ }
+ load(request)
+ }
+
+ public func postUrl(url: URL, postData: Data, completionHandler: @escaping () -> Void) {
+ var request = URLRequest(url: url)
+ currentURL = url
+ request.httpMethod = "POST"
+ request.httpBody = postData
+
+ let task = URLSession.shared.dataTask(with: request) { (data : Data?, response : URLResponse?, error : Error?) in
+ var returnString = ""
+ if data != nil {
+ returnString = String(data: data!, encoding: .utf8) ?? ""
+ }
+ DispatchQueue.main.async(execute: {() -> Void in
+ self.loadHTMLString(returnString, baseURL: url)
+ completionHandler()
+ })
+ }
+ task.resume()
+ }
+
+ public func loadData(data: String, mimeType: String, encoding: String, baseUrl: String) {
+ let url = URL(string: baseUrl)!
+ currentURL = url
+ if #available(iOS 9.0, *) {
+ load(data.data(using: .utf8)!, mimeType: mimeType, characterEncodingName: encoding, baseURL: url)
+ } else {
+ loadHTMLString(data, baseURL: url)
+ }
+ }
+
+ public func loadFile(url: String, headers: [String: String]?) throws {
+ let key = SwiftFlutterPlugin.instance!.registrar!.lookupKey(forAsset: url)
+ let assetURL = Bundle.main.url(forResource: key, withExtension: nil)
+ if assetURL == nil {
+ throw NSError(domain: url + " asset file cannot be found!", code: 0)
+ }
+ loadUrl(url: assetURL!, headers: headers)
+ }
+
+ func setOptions(newOptions: InAppWebViewOptions, newOptionsMap: [String: Any]) {
+
+ if newOptionsMap["transparentBackground"] != nil && options?.transparentBackground != newOptions.transparentBackground {
+ if newOptions.transparentBackground {
+ isOpaque = false
+ backgroundColor = UIColor.clear
+ scrollView.backgroundColor = UIColor.clear
+ } else {
+ isOpaque = true
+ backgroundColor = nil
+ scrollView.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1)
+ }
+ }
+
+ if newOptionsMap["disallowOverScroll"] != nil && options?.disallowOverScroll != newOptions.disallowOverScroll {
+ if responds(to: #selector(getter: scrollView)) {
+ scrollView.bounces = !newOptions.disallowOverScroll
+ }
+ else {
+ for subview: UIView in subviews {
+ if subview is UIScrollView {
+ (subview as! UIScrollView).bounces = !newOptions.disallowOverScroll
+ }
+ }
+ }
+ }
+
+ if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale && newOptions.enableViewportScale {
+ let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
+ evaluateJavaScript(jscript, completionHandler: nil)
+ }
+
+ if newOptionsMap["mediaPlaybackRequiresUserGesture"] != nil && options?.mediaPlaybackRequiresUserGesture != newOptions.mediaPlaybackRequiresUserGesture {
+ if #available(iOS 10.0, *) {
+ configuration.mediaTypesRequiringUserActionForPlayback = (newOptions.mediaPlaybackRequiresUserGesture) ? .all : []
+ } else {
+ // Fallback on earlier versions
+ configuration.mediaPlaybackRequiresUserAction = newOptions.mediaPlaybackRequiresUserGesture
+ }
+ }
+
+ if newOptionsMap["allowsInlineMediaPlayback"] != nil && options?.allowsInlineMediaPlayback != newOptions.allowsInlineMediaPlayback {
+ configuration.allowsInlineMediaPlayback = newOptions.allowsInlineMediaPlayback
+ }
+
+ // if newOptionsMap["keyboardDisplayRequiresUserAction"] != nil && browserOptions?.keyboardDisplayRequiresUserAction != newOptions.keyboardDisplayRequiresUserAction {
+ // self.webView.keyboardDisplayRequiresUserAction = newOptions.keyboardDisplayRequiresUserAction
+ // }
+
+ if newOptionsMap["suppressesIncrementalRendering"] != nil && options?.suppressesIncrementalRendering != newOptions.suppressesIncrementalRendering {
+ configuration.suppressesIncrementalRendering = newOptions.suppressesIncrementalRendering
+ }
+
+ if newOptionsMap["allowsBackForwardNavigationGestures"] != nil && options?.allowsBackForwardNavigationGestures != newOptions.allowsBackForwardNavigationGestures {
+ allowsBackForwardNavigationGestures = newOptions.allowsBackForwardNavigationGestures
+ }
+
+ if newOptionsMap["allowsInlineMediaPlayback"] != nil && options?.allowsInlineMediaPlayback != newOptions.allowsInlineMediaPlayback {
+ configuration.allowsInlineMediaPlayback = newOptions.allowsInlineMediaPlayback
+ }
+
+ if newOptionsMap["javaScriptCanOpenWindowsAutomatically"] != nil && options?.javaScriptCanOpenWindowsAutomatically != newOptions.javaScriptCanOpenWindowsAutomatically {
+ configuration.preferences.javaScriptCanOpenWindowsAutomatically = newOptions.javaScriptCanOpenWindowsAutomatically
+ }
+
+ if newOptionsMap["javaScriptEnabled"] != nil && options?.javaScriptEnabled != newOptions.javaScriptEnabled {
+ configuration.preferences.javaScriptEnabled = newOptions.javaScriptEnabled
+ }
+
+ if newOptionsMap["minimumFontSize"] != nil && options?.minimumFontSize != newOptions.minimumFontSize {
+ configuration.preferences.minimumFontSize = CGFloat(newOptions.minimumFontSize)
+ }
+
+ if newOptionsMap["selectionGranularity"] != nil && options?.selectionGranularity != newOptions.selectionGranularity {
+ configuration.selectionGranularity = WKSelectionGranularity.init(rawValue: newOptions.selectionGranularity)!
+ }
+
+ if #available(iOS 10.0, *) {
+ if newOptionsMap["ignoresViewportScaleLimits"] != nil && options?.ignoresViewportScaleLimits != newOptions.ignoresViewportScaleLimits {
+ configuration.ignoresViewportScaleLimits = newOptions.ignoresViewportScaleLimits
+ }
+
+ if newOptionsMap["dataDetectorTypes"] != nil && options?.dataDetectorTypes != newOptions.dataDetectorTypes {
+ var dataDetectorTypes = WKDataDetectorTypes.init(rawValue: 0)
+ for type in newOptions.dataDetectorTypes {
+ let dataDetectorType = getDataDetectorType(type: type)
+ dataDetectorTypes = WKDataDetectorTypes(rawValue: dataDetectorTypes.rawValue | dataDetectorType.rawValue)
+ }
+ configuration.dataDetectorTypes = dataDetectorTypes
+ }
+ } else {
+ // Fallback on earlier versions
+ }
+
+ if #available(iOS 13.0, *) {
+ configuration.preferences.isFraudulentWebsiteWarningEnabled = (options?.isFraudulentWebsiteWarningEnabled)!
+ configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: (options?.preferredContentMode)!)!
+ } else {
+ // Fallback on earlier versions
+ }
+
+ if newOptionsMap["verticalScrollBarEnabled"] != nil && options?.verticalScrollBarEnabled != newOptions.verticalScrollBarEnabled {
+ scrollView.showsVerticalScrollIndicator = newOptions.verticalScrollBarEnabled
+ }
+ if newOptionsMap["horizontalScrollBarEnabled"] != nil && options?.horizontalScrollBarEnabled != newOptions.horizontalScrollBarEnabled {
+ scrollView.showsHorizontalScrollIndicator = newOptions.horizontalScrollBarEnabled
+ }
+
+ if #available(iOS 9.0, *) {
+ if newOptionsMap["allowsLinkPreview"] != nil && options?.allowsLinkPreview != newOptions.allowsLinkPreview {
+ allowsLinkPreview = newOptions.allowsLinkPreview
+ }
+ if newOptionsMap["allowsPictureInPictureMediaPlayback"] != nil && options?.allowsPictureInPictureMediaPlayback != newOptions.allowsPictureInPictureMediaPlayback {
+ configuration.allowsPictureInPictureMediaPlayback = newOptions.allowsPictureInPictureMediaPlayback
+ }
+ if newOptionsMap["applicationNameForUserAgent"] != nil && options?.applicationNameForUserAgent != newOptions.applicationNameForUserAgent && newOptions.applicationNameForUserAgent != "" {
+ configuration.applicationNameForUserAgent = newOptions.applicationNameForUserAgent
+ }
+ if newOptionsMap["userAgent"] != nil && options?.userAgent != newOptions.userAgent && newOptions.userAgent != "" {
+ customUserAgent = newOptions.userAgent
+ }
+ }
+
+
+
+ if newOptionsMap["clearCache"] != nil && newOptions.clearCache {
+ clearCache()
+ }
+
+ if #available(iOS 11.0, *), newOptionsMap["contentBlockers"] != nil {
+ configuration.userContentController.removeAllContentRuleLists()
+ let contentBlockers = newOptions.contentBlockers
+ if contentBlockers.count > 0 {
+ do {
+ let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
+ let blockRules = String(data: jsonData, encoding: String.Encoding.utf8)
+ WKContentRuleListStore.default().compileContentRuleList(
+ forIdentifier: "ContentBlockingRules",
+ encodedContentRuleList: blockRules) { (contentRuleList, error) in
+ if let error = error {
+ print(error.localizedDescription)
+ return
+ }
+ self.configuration.userContentController.add(contentRuleList!)
+ }
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+ }
+
+ self.options = newOptions
+ }
+
+ func getOptions() -> [String: Any]? {
+ if (self.options == nil) {
+ return nil
+ }
+ return self.options!.getHashMap()
+ }
+
+ public func clearCache() {
+ if #available(iOS 9.0, *) {
+ //let websiteDataTypes = NSSet(array: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache])
+ let date = NSDate(timeIntervalSince1970: 0)
+ WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: date as Date, completionHandler:{ })
+ } else {
+ var libraryPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, false).first!
+ libraryPath += "/Cookies"
+
+ do {
+ try FileManager.default.removeItem(atPath: libraryPath)
+ } catch {
+ print("can't clear cache")
+ }
+ URLCache.shared.removeAllCachedResponses()
+ }
+ }
+
+ public func injectDeferredObject(source: String, withWrapper jsWrapper: String, result: FlutterResult?) {
+ let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: [])
+ let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8)
+ if sourceArrayString != nil {
+ let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.count ?? 0) - 2))
+ let jsToInject = String(format: jsWrapper, sourceString!)
+
+ evaluateJavaScript(jsToInject, completionHandler: {(value, error) in
+ if result == nil {
+ return
+ }
+
+ if error != nil {
+ let userInfo = (error! as NSError).userInfo
+ self.onConsoleMessage(sourceURL: (userInfo["WKJavaScriptExceptionSourceURL"] as? URL)?.absoluteString ?? "", lineNumber: userInfo["WKJavaScriptExceptionLineNumber"] as! Int, message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: 3)
+ }
+
+ if value == nil {
+ result!("")
+ return
+ }
+
+ do {
+ let data: Data = ("[" + String(describing: value!) + "]").data(using: String.Encoding.utf8, allowLossyConversion: false)!
+ let json: Array = try JSONSerialization.jsonObject(with: data, options: []) as! Array
+ if json[0] is String {
+ result!(json[0])
+ }
+ else {
+ result!(value)
+ }
+ } catch let error as NSError {
+ result!(FlutterError(code: "InAppBrowserFlutterPlugin", message: "Failed to load: \(error.localizedDescription)", details: error))
+ }
+
+ })
+ }
+ }
+
+ public func injectScriptCode(source: String, result: FlutterResult?) {
+ let jsWrapper = "(function(){return JSON.stringify(eval(%@));})();"
+ injectDeferredObject(source: source, withWrapper: jsWrapper, result: result)
+ }
+
+ public func injectScriptFile(urlFile: String) {
+ let jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document);"
+ injectDeferredObject(source: urlFile, withWrapper: jsWrapper, result: nil)
+ }
+
+ public func injectStyleCode(source: String) {
+ let jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document);"
+ injectDeferredObject(source: source, withWrapper: jsWrapper, result: nil)
+ }
+
+ public func injectStyleFile(urlFile: String) {
+ let jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document);"
+ injectDeferredObject(source: urlFile, withWrapper: jsWrapper, result: nil)
+ }
+
+ public func getCopyBackForwardList() -> [String: Any] {
+ let currentList = backForwardList
+ let currentIndex = currentList.backList.count
+ var completeList = currentList.backList
+ if currentList.currentItem != nil {
+ completeList.append(currentList.currentItem!)
+ }
+ completeList.append(contentsOf: currentList.forwardList)
+
+ var history: [[String: String]] = []
+
+ for historyItem in completeList {
+ var historyItemMap: [String: String] = [:]
+ historyItemMap["originalUrl"] = historyItem.initialURL.absoluteString
+ historyItemMap["title"] = historyItem.title
+ historyItemMap["url"] = historyItem.url.absoluteString
+ history.append(historyItemMap)
+ }
+
+ var result: [String: Any] = [:]
+ result["history"] = history
+ result["currentIndex"] = currentIndex
+
+ return result;
+ }
+
+ public func webView(_ webView: WKWebView,
+ decidePolicyFor navigationAction: WKNavigationAction,
+ decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
+
+ let app = UIApplication.shared
+
+ if let url = navigationAction.request.url {
+ if url.absoluteString != url.absoluteString && (options?.useOnLoadResource)! {
+ WKNavigationMap[url.absoluteString] = [
+ "startTime": currentTimeInMilliSeconds(),
+ "request": navigationAction.request
+ ]
+ }
+
+ // Handle target="_blank"
+ if navigationAction.targetFrame == nil && (options?.useOnTargetBlank)! {
+ onTargetBlank(url: url)
+ decisionHandler(.cancel)
+ return
+ }
+
+ if navigationAction.navigationType == .linkActivated && (options?.useShouldOverrideUrlLoading)! {
+ shouldOverrideUrlLoading(url: url)
+ decisionHandler(.cancel)
+ return
+ }
+
+ // Handle phone and email links
+ if url.scheme == "tel" || url.scheme == "mailto" {
+ if app.canOpenURL(url) {
+ if #available(iOS 10.0, *) {
+ app.open(url)
+ } else {
+ app.openURL(url)
+ }
+ }
+ decisionHandler(.cancel)
+ return
+ }
+
+ if navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .backForward {
+ currentURL = url
+ if IABController != nil {
+ IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!)
+ }
+ }
+ }
+
+ decisionHandler(.allow)
+ }
+
+ public func webView(_ webView: WKWebView,
+ decidePolicyFor navigationResponse: WKNavigationResponse,
+ decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
+
+// if (options?.useOnLoadResource)! {
+// if let url = navigationResponse.response.url {
+// if WKNavigationMap[url.absoluteString] != nil {
+// let startResourceTime: Int64 = (WKNavigationMap[url.absoluteString]!["startTime"] as! Int64)
+// let startTime: Int64 = startResourceTime - startPageTime;
+// let duration: Int64 = currentTimeInMilliSeconds() - startResourceTime;
+// onLoadResource(response: navigationResponse.response, fromRequest: WKNavigationMap[url.absoluteString]!["request"] as? URLRequest, withData: Data(), startTime: startTime, duration: duration)
+// }
+// }
+// }
+
+ if (options?.useOnDownloadStart)! {
+ let mimeType = navigationResponse.response.mimeType
+ if let url = navigationResponse.response.url {
+ if mimeType != nil && !mimeType!.starts(with: "text/") {
+ onDownloadStart(url: url.absoluteString)
+ decisionHandler(.cancel)
+ return
+ }
+ }
+ }
+
+ decisionHandler(.allow)
+ }
+
+ public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
+ self.startPageTime = currentTimeInMilliSeconds()
+ onLoadStart(url: (currentURL?.absoluteString)!)
+
+ if IABController != nil {
+ // loading url, start spinner, update back/forward
+ IABController!.backButton.isEnabled = canGoBack
+ IABController!.forwardButton.isEnabled = canGoForward
+
+ if (IABController!.browserOptions?.spinner)! {
+ IABController!.spinner.startAnimating()
+ }
+ }
+ }
+
+ public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+ self.WKNavigationMap = [:]
+ currentURL = url
+ InAppWebView.credentialsProposed = []
+ onLoadStop(url: (currentURL?.absoluteString)!)
+ evaluateJavaScript(platformReadyJS, completionHandler: nil)
+
+ if IABController != nil {
+ IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!)
+ IABController!.backButton.isEnabled = canGoBack
+ IABController!.forwardButton.isEnabled = canGoForward
+ IABController!.spinner.stopAnimating()
+ }
+ }
+
+ public func webView(_ view: WKWebView,
+ didFailProvisionalNavigation navigation: WKNavigation!,
+ withError error: Error) {
+ webView(view, didFail: navigation, withError: error)
+ }
+
+ public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
+ InAppWebView.credentialsProposed = []
+
+ onLoadError(url: (currentURL?.absoluteString)!, error: error)
+
+ if IABController != nil {
+ IABController!.backButton.isEnabled = canGoBack
+ IABController!.forwardButton.isEnabled = canGoForward
+ IABController!.spinner.stopAnimating()
+ }
+ }
+
+ public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+
+ if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
+ challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault ||
+ challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest {
+ let host = challenge.protectionSpace.host
+ let prot = challenge.protectionSpace.protocol
+ let realm = challenge.protectionSpace.realm
+ let port = challenge.protectionSpace.port
+ onReceivedHttpAuthRequest(challenge: challenge, result: {(result) -> Void in
+ if result is FlutterError {
+ print((result as! FlutterError).message)
+ }
+ else if (result as? NSObject) == FlutterMethodNotImplemented {
+ completionHandler(.performDefaultHandling, nil)
+ }
+ else {
+ var response: [String: Any]
+ if let r = result {
+ response = r as! [String: Any]
+ var action = response["action"] as? Int
+ action = action != nil ? action : 0;
+ switch action {
+ case 0:
+ InAppWebView.credentialsProposed = []
+ completionHandler(.cancelAuthenticationChallenge, nil)
+ break
+ case 1:
+ let username = response["username"] as! String
+ let password = response["password"] as! String
+ let permanentPersistence = response["permanentPersistence"] as? Bool ?? false
+ let persistence = (permanentPersistence) ? URLCredential.Persistence.permanent : URLCredential.Persistence.forSession
+ let credential = URLCredential(user: username, password: password, persistence: persistence)
+ completionHandler(.useCredential, credential)
+ break
+ case 2:
+ if InAppWebView.credentialsProposed.count == 0 {
+ for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials {
+ if protectionSpace.host == host && protectionSpace.realm == realm &&
+ protectionSpace.protocol == prot && protectionSpace.port == port {
+ for credential in credentials {
+ InAppWebView.credentialsProposed.append(credential.value)
+ }
+ break
+ }
+ }
+ }
+ if InAppWebView.credentialsProposed.count == 0, let credential = challenge.proposedCredential {
+ InAppWebView.credentialsProposed.append(credential)
+ }
+
+ if let credential = InAppWebView.credentialsProposed.popLast() {
+ completionHandler(.useCredential, credential)
+ }
+ else {
+ completionHandler(.performDefaultHandling, nil)
+ }
+ break
+ default:
+ InAppWebView.credentialsProposed = []
+ completionHandler(.performDefaultHandling, nil)
+ }
+ return;
+ }
+ completionHandler(.performDefaultHandling, nil)
+ }
+ })
+ }
+ else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
+
+ guard let serverTrust = challenge.protectionSpace.serverTrust else {
+ completionHandler(.performDefaultHandling, nil)
+ return
+ }
+
+ onReceivedServerTrustAuthRequest(challenge: challenge, result: {(result) -> Void in
+ if result is FlutterError {
+ print((result as! FlutterError).message)
+ }
+ else if (result as? NSObject) == FlutterMethodNotImplemented {
+ completionHandler(.performDefaultHandling, nil)
+ }
+ else {
+ var response: [String: Any]
+ if let r = result {
+ response = r as! [String: Any]
+ var action = response["action"] as? Int
+ action = action != nil ? action : 0;
+ switch action {
+ case 0:
+ InAppWebView.credentialsProposed = []
+ completionHandler(.cancelAuthenticationChallenge, nil)
+ break
+ case 1:
+ let exceptions = SecTrustCopyExceptions(serverTrust)
+ SecTrustSetExceptions(serverTrust, exceptions)
+ let credential = URLCredential(trust: serverTrust)
+ completionHandler(.useCredential, credential)
+ break
+ default:
+ InAppWebView.credentialsProposed = []
+ completionHandler(.performDefaultHandling, nil)
+ }
+ return;
+ }
+ completionHandler(.performDefaultHandling, nil)
+ }
+ })
+ }
+ else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
+ onReceivedClientCertRequest(challenge: challenge, result: {(result) -> Void in
+ if result is FlutterError {
+ print((result as! FlutterError).message)
+ }
+ else if (result as? NSObject) == FlutterMethodNotImplemented {
+ completionHandler(.performDefaultHandling, nil)
+ }
+ else {
+ var response: [String: Any]
+ if let r = result {
+ response = r as! [String: Any]
+ var action = response["action"] as? Int
+ action = action != nil ? action : 0;
+ switch action {
+ case 0:
+ completionHandler(.cancelAuthenticationChallenge, nil)
+ break
+ case 1:
+ let certificatePath = response["certificatePath"] as! String;
+ let certificatePassword = response["certificatePassword"] as? String ?? "";
+
+ let key = SwiftFlutterPlugin.instance!.registrar!.lookupKey(forAsset: certificatePath)
+ let path = Bundle.main.path(forResource: key, ofType: nil)!
+ let PKCS12Data = NSData(contentsOfFile: path)!
+
+ if let identityAndTrust: IdentityAndTrust = self.extractIdentity(PKCS12Data: PKCS12Data, password: certificatePassword) {
+ let urlCredential: URLCredential = URLCredential(
+ identity: identityAndTrust.identityRef,
+ certificates: identityAndTrust.certArray as? [AnyObject],
+ persistence: URLCredential.Persistence.forSession);
+ completionHandler(.useCredential, urlCredential)
+ } else {
+ completionHandler(.performDefaultHandling, nil)
+ }
+ break
+ case 2:
+ completionHandler(.cancelAuthenticationChallenge, nil)
+ break
+ default:
+ completionHandler(.performDefaultHandling, nil)
+ }
+ return;
+ }
+ completionHandler(.performDefaultHandling, nil)
+ }
+ })
+ }
+ else {
+ completionHandler(.performDefaultHandling, nil)
+ }
+ }
+
+ struct IdentityAndTrust {
+
+ var identityRef:SecIdentity
+ var trust:SecTrust
+ var certArray:AnyObject
+ }
+
+ func extractIdentity(PKCS12Data:NSData, password: String) -> IdentityAndTrust? {
+ var identityAndTrust:IdentityAndTrust?
+ var securityError:OSStatus = errSecSuccess
+
+ var importResult: CFArray? = nil
+ securityError = SecPKCS12Import(
+ PKCS12Data as NSData,
+ [kSecImportExportPassphrase as String: password] as NSDictionary,
+ &importResult
+ )
+
+ if securityError == errSecSuccess {
+ let certItems:CFArray = importResult! as CFArray;
+ let certItemsArray:Array = certItems as Array
+ let dict:AnyObject? = certItemsArray.first;
+ if let certEntry:Dictionary = dict as? Dictionary {
+ // grab the identity
+ let identityPointer:AnyObject? = certEntry["identity"];
+ let secIdentityRef:SecIdentity = (identityPointer as! SecIdentity?)!;
+ // grab the trust
+ let trustPointer:AnyObject? = certEntry["trust"];
+ let trustRef:SecTrust = trustPointer as! SecTrust;
+ // grab the cert
+ let chainPointer:AnyObject? = certEntry["chain"];
+ identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: chainPointer!);
+ }
+ } else {
+ print("Security Error: " + securityError.description)
+ if #available(iOS 11.3, *) {
+ print(SecCopyErrorMessageString(securityError,nil))
+ }
+ }
+ return identityAndTrust;
+ }
+
+
+ func createAlertDialog(message: String?, responseMessage: String?, confirmButtonTitle: String?, completionHandler: @escaping () -> Void) {
+ let title = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
+ let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
+ let alertController = UIAlertController(title: title, message: nil,
+ preferredStyle: UIAlertController.Style.alert);
+
+ alertController.addAction(UIAlertAction(title: okButton, style: UIAlertAction.Style.default) {
+ _ in completionHandler()}
+ );
+
+ let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!)
+ presentingViewController.present(alertController, animated: true, completion: {})
+ }
+
+ public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String,
+ initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
+
+ onJsAlert(message: message, result: {(result) -> Void in
+ if result is FlutterError {
+ print((result as! FlutterError).message)
+ }
+ else if (result as? NSObject) == FlutterMethodNotImplemented {
+ self.createAlertDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, completionHandler: completionHandler)
+ }
+ else {
+ let response: [String: Any]
+ var responseMessage: String?;
+ var confirmButtonTitle: String?;
+
+ if let r = result {
+ response = r as! [String: Any]
+ responseMessage = response["message"] as? String
+ confirmButtonTitle = response["confirmButtonTitle"] as? String
+ let handledByClient = response["handledByClient"] as? Bool
+ if handledByClient != nil, handledByClient! {
+ var action = response["action"] as? Int
+ action = action != nil ? action : 1;
+ switch action {
+ case 0:
+ completionHandler()
+ break
+ default:
+ completionHandler()
+ }
+ return;
+ }
+ }
+
+ self.createAlertDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler)
+ }
+ })
+ }
+
+ func createConfirmDialog(message: String?, responseMessage: String?, confirmButtonTitle: String?, cancelButtonTitle: String?, completionHandler: @escaping (Bool) -> Void) {
+ let dialogMessage = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
+ let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
+ let cancelButton = cancelButtonTitle != nil && !cancelButtonTitle!.isEmpty ? cancelButtonTitle : NSLocalizedString("Cancel", comment: "")
+
+ let alertController = UIAlertController(title: nil, message: dialogMessage, preferredStyle: .alert)
+
+ alertController.addAction(UIAlertAction(title: okButton, style: .default, handler: { (action) in
+ completionHandler(true)
+ }))
+
+ alertController.addAction(UIAlertAction(title: cancelButton, style: .cancel, handler: { (action) in
+ completionHandler(false)
+ }))
+
+ let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!)
+ presentingViewController.present(alertController, animated: true, completion: nil)
+ }
+
+ public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
+ completionHandler: @escaping (Bool) -> Void) {
+
+ onJsConfirm(message: message, result: {(result) -> Void in
+ if result is FlutterError {
+ print((result as! FlutterError).message)
+ }
+ else if (result as? NSObject) == FlutterMethodNotImplemented {
+ self.createConfirmDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, completionHandler: completionHandler)
+ }
+ else {
+ let response: [String: Any]
+ var responseMessage: String?;
+ var confirmButtonTitle: String?;
+ var cancelButtonTitle: String?;
+
+ if let r = result {
+ response = r as! [String: Any]
+ responseMessage = response["message"] as? String
+ confirmButtonTitle = response["confirmButtonTitle"] as? String
+ cancelButtonTitle = response["cancelButtonTitle"] as? String
+ let handledByClient = response["handledByClient"] as? Bool
+ if handledByClient != nil, handledByClient! {
+ var action = response["action"] as? Int
+ action = action != nil ? action : 1;
+ switch action {
+ case 0:
+ completionHandler(true)
+ break
+ case 1:
+ completionHandler(false)
+ break
+ default:
+ completionHandler(false)
+ }
+ return;
+ }
+ }
+ self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler)
+ }
+ })
+ }
+
+ func createPromptDialog(message: String, defaultValue: String?, responseMessage: String?, confirmButtonTitle: String?, cancelButtonTitle: String?, value: String?, completionHandler: @escaping (String?) -> Void) {
+ let dialogMessage = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
+ let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
+ let cancelButton = cancelButtonTitle != nil && !cancelButtonTitle!.isEmpty ? cancelButtonTitle : NSLocalizedString("Cancel", comment: "")
+
+ let alertController = UIAlertController(title: nil, message: dialogMessage, preferredStyle: .alert)
+
+ alertController.addTextField { (textField) in
+ textField.text = defaultValue
+ }
+
+ alertController.addAction(UIAlertAction(title: okButton, style: .default, handler: { (action) in
+ if let v = value {
+ completionHandler(v)
+ }
+ else if let text = alertController.textFields?.first?.text {
+ completionHandler(text)
+ } else {
+ completionHandler("")
+ }
+ }))
+
+ alertController.addAction(UIAlertAction(title: cancelButton, style: .cancel, handler: { (action) in
+ completionHandler(nil)
+ }))
+
+ let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!)
+ presentingViewController.present(alertController, animated: true, completion: nil)
+ }
+
+ public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo,
+ completionHandler: @escaping (String?) -> Void) {
+ onJsPrompt(message: message, defaultValue: defaultValue, result: {(result) -> Void in
+ if result is FlutterError {
+ print((result as! FlutterError).message)
+ }
+ else if (result as? NSObject) == FlutterMethodNotImplemented {
+ self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, value: nil, completionHandler: completionHandler)
+ }
+ else {
+ let response: [String: Any]
+ var responseMessage: String?;
+ var confirmButtonTitle: String?;
+ var cancelButtonTitle: String?;
+ var value: String?;
+
+ if let r = result {
+ response = r as! [String: Any]
+ responseMessage = response["message"] as? String
+ confirmButtonTitle = response["confirmButtonTitle"] as? String
+ cancelButtonTitle = response["cancelButtonTitle"] as? String
+ let handledByClient = response["handledByClient"] as? Bool
+ value = response["value"] as? String;
+ if handledByClient != nil, handledByClient! {
+ var action = response["action"] as? Int
+ action = action != nil ? action : 1;
+ switch action {
+ case 0:
+ completionHandler(value)
+ break
+ case 1:
+ completionHandler(nil)
+ break
+ default:
+ completionHandler(nil)
+ }
+ return;
+ }
+ }
+
+ self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler)
+ }
+ })
+ }
+
+ public func scrollViewDidScroll(_ scrollView: UIScrollView) {
+ if navigationDelegate != nil {
+ let x = Int(scrollView.contentOffset.x / scrollView.contentScaleFactor)
+ let y = Int(scrollView.contentOffset.y / scrollView.contentScaleFactor)
+ onScrollChanged(x: x, y: y)
+ }
+ setNeedsLayout()
+ }
+
+ public func onLoadStart(url: String) {
+ var arguments: [String: Any] = ["url": url]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onLoadStart", arguments: arguments)
+ }
+ }
+
+ public func onLoadStop(url: String) {
+ var arguments: [String: Any] = ["url": url]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onLoadStop", arguments: arguments)
+ }
+ }
+
+ public func onLoadError(url: String, error: Error) {
+ var arguments: [String: Any] = ["url": url, "code": error._code, "message": error.localizedDescription]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onLoadError", arguments: arguments)
+ }
+ }
+
+ public func onProgressChanged(progress: Int) {
+ var arguments: [String: Any] = ["progress": progress]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onProgressChanged", arguments: arguments)
+ }
+ }
+
+ public func onLoadResource(initiatorType: String, url: String, startTime: Double, duration: Double) {
+ var arguments: [String : Any] = [
+ "initiatorType": initiatorType,
+ "url": url,
+ "startTime": startTime,
+ "duration": duration
+ ]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onLoadResource", arguments: arguments)
+ }
+ }
+
+ public func onFindResultReceived(activeMatchOrdinal: Int, numberOfMatches: Int, isDoneCounting: Bool) {
+ var arguments: [String : Any] = [
+ "activeMatchOrdinal": activeMatchOrdinal,
+ "numberOfMatches": numberOfMatches,
+ "isDoneCounting": isDoneCounting
+ ]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onFindResultReceived", arguments: arguments)
+ }
+ }
+
+ public func onScrollChanged(x: Int, y: Int) {
+ var arguments: [String: Any] = ["x": x, "y": y]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onScrollChanged", arguments: arguments)
+ }
+ }
+
+ public func onDownloadStart(url: String) {
+ var arguments: [String: Any] = ["url": url]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onDownloadStart", arguments: arguments)
+ }
+ }
+
+ public func onLoadResourceCustomScheme(scheme: String, url: String, result: FlutterResult?) {
+ var arguments: [String: Any] = ["scheme": scheme, "url": url]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onLoadResourceCustomScheme", arguments: arguments, result: result)
+ }
+ }
+
+ public func shouldOverrideUrlLoading(url: URL) {
+ var arguments: [String: Any] = ["url": url.absoluteString]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("shouldOverrideUrlLoading", arguments: arguments)
+ }
+ }
+
+ public func onTargetBlank(url: URL) {
+ var arguments: [String: Any] = ["url": url.absoluteString]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onTargetBlank", arguments: arguments)
+ }
+ }
+
+ public func onReceivedHttpAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
+ var arguments: [String: Any?] = [
+ "host": challenge.protectionSpace.host,
+ "protocol": challenge.protectionSpace.protocol,
+ "realm": challenge.protectionSpace.realm,
+ "port": challenge.protectionSpace.port,
+ "previousFailureCount": challenge.previousFailureCount
+ ]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onReceivedHttpAuthRequest", arguments: arguments, result: result)
+ }
+ }
+
+ public func onReceivedServerTrustAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
+ var serverCertificateData: NSData?
+ let serverTrust = challenge.protectionSpace.serverTrust!
+ if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
+ let serverCertificateCFData = SecCertificateCopyData(serverCertificate)
+ let data = CFDataGetBytePtr(serverCertificateCFData)
+ let size = CFDataGetLength(serverCertificateCFData)
+ serverCertificateData = NSData(bytes: data, length: size)
+ }
+
+ var arguments: [String: Any?] = [
+ "host": challenge.protectionSpace.host,
+ "protocol": challenge.protectionSpace.protocol,
+ "realm": challenge.protectionSpace.realm,
+ "port": challenge.protectionSpace.port,
+ "previousFailureCount": challenge.previousFailureCount,
+ "serverCertificate": serverCertificateData,
+ "error": -1,
+ "message": "",
+ ]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onReceivedServerTrustAuthRequest", arguments: arguments, result: result)
+ }
+ }
+
+ public func onReceivedClientCertRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
+ var arguments: [String: Any?] = [
+ "host": challenge.protectionSpace.host,
+ "protocol": challenge.protectionSpace.protocol,
+ "realm": challenge.protectionSpace.realm,
+ "port": challenge.protectionSpace.port
+ ]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onReceivedClientCertRequest", arguments: arguments, result: result)
+ }
+ }
+
+ public func onJsAlert(message: String, result: FlutterResult?) {
+ var arguments: [String: Any] = ["message": message]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onJsAlert", arguments: arguments, result: result)
+ }
+ }
+
+ public func onJsConfirm(message: String, result: FlutterResult?) {
+ var arguments: [String: Any] = ["message": message]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onJsConfirm", arguments: arguments, result: result)
+ }
+ }
+
+ public func onJsPrompt(message: String, defaultValue: String?, result: FlutterResult?) {
+ var arguments: [String: Any] = ["message": message, "defaultValue": defaultValue as Any]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onJsPrompt", arguments: arguments, result: result)
+ }
+ }
+
+ public func onConsoleMessage(sourceURL: String, lineNumber: Int, message: String, messageLevel: Int) {
+ var arguments: [String: Any] = ["sourceURL": sourceURL, "lineNumber": lineNumber, "message": message, "messageLevel": messageLevel]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+ if let channel = getChannel() {
+ channel.invokeMethod("onConsoleMessage", arguments: arguments)
+ }
+ }
+
+ public func onCallJsHandler(handlerName: String, _callHandlerID: Int64, args: String) {
+ var arguments: [String: Any] = ["handlerName": handlerName, "args": args]
+ if IABController != nil {
+ arguments["uuid"] = IABController!.uuid
+ }
+
+ if let channel = getChannel() {
+ channel.invokeMethod("onCallJsHandler", arguments: arguments, result: {(result) -> Void in
+ if result is FlutterError {
+ print((result as! FlutterError).message)
+ }
+ else if (result as? NSObject) == FlutterMethodNotImplemented {}
+ else {
+ var json = "null"
+ if let r = result {
+ json = r as! String
+ }
+ self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)](\(json)); delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)];", completionHandler: nil)
+ }
+ })
+ }
+ }
+
+ public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
+ if message.name.starts(with: "console") {
+ var messageLevel = 1
+ switch (message.name) {
+ case "consoleLog":
+ messageLevel = 1
+ break;
+ case "consoleDebug":
+ // on Android, console.debug is TIP
+ messageLevel = 0
+ break;
+ case "consoleError":
+ messageLevel = 3
+ break;
+ case "consoleInfo":
+ // on Android, console.info is LOG
+ messageLevel = 1
+ break;
+ case "consoleWarn":
+ messageLevel = 2
+ break;
+ default:
+ messageLevel = 1
+ break;
+ }
+ onConsoleMessage(sourceURL: "", lineNumber: 1, message: message.body as! String, messageLevel: messageLevel)
+ }
+ else if message.name == "resourceLoaded" && (options?.useOnLoadResource)! {
+ if let resource = convertToDictionary(text: message.body as! String) {
+ // escape special chars
+ let resourceName = (resource["name"] as! String).addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
+
+ let url = URL(string: resourceName!)!
+ if !UIApplication.shared.canOpenURL(url) {
+ return
+ }
+ let initiatorType = resource["initiatorType"] as! String
+ let startTime = resource["startTime"] as! Double
+ let duration = resource["duration"] as! Double
+
+ self.onLoadResource(initiatorType: initiatorType, url: url.absoluteString, startTime: startTime, duration: duration)
+ }
+ }
+ else if message.name == "callHandler" {
+ let body = message.body as! [String: Any]
+ let handlerName = body["handlerName"] as! String
+ let _callHandlerID = body["_callHandlerID"] as! Int64
+ let args = body["args"] as! String
+ onCallJsHandler(handlerName: handlerName, _callHandlerID: _callHandlerID, args: args)
+ } else if message.name == "findResultReceived" {
+ if let resource = convertToDictionary(text: message.body as! String) {
+ let activeMatchOrdinal = resource["activeMatchOrdinal"] as! Int
+ let numberOfMatches = resource["numberOfMatches"] as! Int
+ let isDoneCounting = resource["isDoneCounting"] as! Bool
+
+ self.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting)
+ }
+ }
+ }
+
+ private func getChannel() -> FlutterMethodChannel? {
+ return (IABController != nil) ? SwiftFlutterPlugin.instance!.channel! : ((IAWController != nil) ? IAWController!.channel! : nil);
+ }
+
+ func findAllAsync(find: String?, completionHandler: ((Any?, Error?) -> Void)?) {
+ let startSearch = "wkwebview_FindAllAsync('\(find ?? "")');"
+ evaluateJavaScript(startSearch, completionHandler: completionHandler)
+ }
+
+ func findNext(forward: Bool, completionHandler: ((Any?, Error?) -> Void)?) {
+ evaluateJavaScript("wkwebview_FindNext(\(forward ? "true" : "false"));", completionHandler: completionHandler)
+ }
+
+ func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) {
+ evaluateJavaScript("wkwebview_ClearMatches();", completionHandler: completionHandler)
+ }
+}
diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift
index 1d792fd2..ebfb62fa 100755
--- a/ios/Classes/SwiftFlutterPlugin.swift
+++ b/ios/Classes/SwiftFlutterPlugin.swift
@@ -239,6 +239,18 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
case "getCopyBackForwardList":
result(self.getCopyBackForwardList(uuid: uuid))
break
+ case "findAllAsync":
+ let find = arguments!["find"] as! String
+ self.findAllAsync(uuid: uuid, find: find)
+ result(true)
+ break
+ case "findNext":
+ let forward = arguments!["forward"] as! Bool
+ self.findNext(uuid: uuid, forward: forward, result: result)
+ break
+ case "clearMatches":
+ self.clearMatches(uuid: uuid, result: result)
+ break
case "clearCache":
self.clearCache(uuid: uuid)
result(true)
@@ -752,6 +764,40 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
return nil
}
+ func findAllAsync(uuid: String, find: String) {
+ if let webViewController = self.webViewControllers[uuid] {
+ webViewController!.webView.findAllAsync(find: find, completionHandler: nil)
+ }
+ }
+
+ func findNext(uuid: String, forward: Bool, result: @escaping FlutterResult) {
+ if let webViewController = self.webViewControllers[uuid] {
+ webViewController!.webView.findNext(forward: forward, completionHandler: {(value, error) in
+ if error != nil {
+ result(FlutterError(code: "FlutterWebViewController", message: error?.localizedDescription, details: nil))
+ return
+ }
+ result(true)
+ })
+ } else {
+ result(false)
+ }
+ }
+
+ func clearMatches(uuid: String, result: @escaping FlutterResult) {
+ if let webViewController = self.webViewControllers[uuid] {
+ webViewController!.webView.clearMatches(completionHandler: {(value, error) in
+ if error != nil {
+ result(FlutterError(code: "FlutterWebViewController", message: error?.localizedDescription, details: nil))
+ return
+ }
+ result(true)
+ })
+ } else {
+ result(false)
+ }
+ }
+
func clearCache(uuid: String) {
if let webViewController = self.webViewControllers[uuid] {
webViewController!.webView.clearCache()
diff --git a/lib/src/channel_manager.dart b/lib/src/channel_manager.dart
index 313de8be..1b935229 100644
--- a/lib/src/channel_manager.dart
+++ b/lib/src/channel_manager.dart
@@ -1,3 +1,4 @@
+import 'dart:async';
import 'dart:collection';
import 'package:flutter/services.dart';
diff --git a/lib/src/cookie_manager.dart b/lib/src/cookie_manager.dart
index 6b7d058c..9cb8c484 100644
--- a/lib/src/cookie_manager.dart
+++ b/lib/src/cookie_manager.dart
@@ -1,3 +1,5 @@
+import 'dart:async';
+
import 'package:flutter/services.dart';
///Manages the cookies used by WebView instances.
diff --git a/lib/src/http_auth_credentials_database.dart b/lib/src/http_auth_credentials_database.dart
index 485aa103..f0f460e5 100644
--- a/lib/src/http_auth_credentials_database.dart
+++ b/lib/src/http_auth_credentials_database.dart
@@ -1,3 +1,5 @@
+import 'dart:async';
+
import 'types.dart';
import 'package:flutter/services.dart';
diff --git a/lib/src/in_app_browser.dart b/lib/src/in_app_browser.dart
index ed8513b6..35d16e36 100644
--- a/lib/src/in_app_browser.dart
+++ b/lib/src/in_app_browser.dart
@@ -4,7 +4,6 @@ import 'dart:collection';
import 'package:flutter/services.dart';
import 'package:flutter_inappbrowser/src/webview_options.dart';
-import 'http_auth_credentials_database.dart';
import 'types.dart';
import 'channel_manager.dart';
import 'in_app_webview.dart' show InAppWebViewController;
@@ -424,6 +423,18 @@ class InAppBrowser {
}
+ ///Event fired as find-on-page operations progress.
+ ///The listener may be notified multiple times while the operation is underway, and the numberOfMatches value should not be considered final unless [isDoneCounting] is true.
+ ///
+ ///[activeMatchOrdinal] represents the zero-based ordinal of the currently selected match.
+ ///
+ ///[numberOfMatches] represents how many matches have been found.
+ ///
+ ///[isDoneCounting] whether the find operation has actually completed.
+ void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting) {
+
+ }
+
void throwIsAlreadyOpened({String message = ''}) {
if (this.isOpened()) {
throw Exception(['Error: ${ (message.isEmpty) ? '' : message + ' '}The browser is already opened.']);
diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart
old mode 100644
new mode 100755
index faf10e8d..ab9c7941
--- a/lib/src/in_app_webview.dart
+++ b/lib/src/in_app_webview.dart
@@ -1,1044 +1,1191 @@
-import 'dart:io';
-import 'dart:async';
-import 'dart:collection';
-import 'dart:typed_data';
-import 'dart:convert';
-
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter/gestures.dart';
-
-import 'types.dart';
-import 'in_app_browser.dart';
-import 'channel_manager.dart';
-import 'webview_options.dart';
-
-/*
-* TODO: injectJavaScriptBeforeLoad
-*/
-
-///Initial [data] as a content for an [InAppWebView] instance, using [baseUrl] as the base URL for it.
-///The [mimeType] property specifies the format of the data.
-///The [encoding] property specifies the encoding of the data.
-class InAppWebViewInitialData {
- String data;
- String mimeType;
- String encoding;
- String baseUrl;
-
- InAppWebViewInitialData(this.data, {this.mimeType = "text/html", this.encoding = "utf8", this.baseUrl = "about:blank"});
-
- Map toMap() {
- return {
- "data": data,
- "mimeType": mimeType,
- "encoding": encoding,
- "baseUrl": baseUrl
- };
- }
-}
-
-///InAppWebView Widget class.
-///
-///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree.
-///
-///All platforms support these options:
-/// - __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the [InAppWebView.shouldOverrideUrlLoading()] event. The default value is `false`.
-/// - __useOnLoadResource__: Set to `true` to be able to listen at the [InAppWebView.onLoadResource()] event. The default value is `false`.
-/// - __useOnDownloadStart__: Set to `true` to be able to listen at the [InAppWebView.onDownloadStart()] event. The default value is `false`.
-/// - __useOnTargetBlank__: Set to `true` to be able to listen at the [InAppWebView.onTargetBlank()] event. The default value is `false`.
-/// - __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`.
-/// - __userAgent___: Set the custom WebView's user-agent.
-/// - __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`.
-/// - __javaScriptCanOpenWindowsAutomatically__: Set to `true` to allow JavaScript open windows without user interaction. The default value is `false`.
-/// - __mediaPlaybackRequiresUserGesture__: Set to `true` to prevent HTML5 audio or video from autoplaying. The default value is `true`.
-/// - __transparentBackground__: Set to `true` to make the background of the WebView transparent. If your app has a dark theme, this can prevent a white flash on initialization. The default value is `false`.
-/// - __resourceCustomSchemes__: List of custom schemes that [InAppWebView] must handle. Use the [InAppWebView.onLoadResourceCustomScheme()] event to intercept resource requests with custom scheme.
-///
-/// **Android** supports these additional options:
-///
-/// - __clearSessionCache__: Set to `true` to have the session cookie cache cleared before the new window is opened.
-/// - __builtInZoomControls__: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`.
-/// - __displayZoomControls__: Set to `true` if the WebView should display on-screen zoom controls when using the built-in zoom mechanisms. The default value is `false`.
-/// - __supportZoom__: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
-/// - __databaseEnabled__: Set to `true` if you want the database storage API is enabled. The default value is `false`.
-/// - __domStorageEnabled__: Set to `true` if you want the DOM storage API is enabled. The default value is `false`.
-/// - __useWideViewPort__: Set to `true` if the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport. When the value of the setting is false, the layout width is always set to the width of the WebView control in device-independent (CSS) pixels. When the value is true and the page contains the viewport meta tag, the value of the width specified in the tag is used. If the page does not contain the tag or does not provide a width, then a wide viewport will be used. The default value is `true`.
-/// - __safeBrowsingEnabled__: Set to `true` if you want the Safe Browsing is enabled. Safe Browsing allows WebView to protect against malware and phishing attacks by verifying the links. The default value is `true`.
-/// - __textZoom__: Set text scaling of the WebView. The default value is `100`.
-/// - __mixedContentMode__: Configures the WebView's behavior when a secure origin attempts to load a resource from an insecure origin. By default, apps that target `Build.VERSION_CODES.KITKAT` or below default to `MIXED_CONTENT_ALWAYS_ALLOW`. Apps targeting `Build.VERSION_CODES.LOLLIPOP` default to `MIXED_CONTENT_NEVER_ALLOW`. The preferred and most secure mode of operation for the WebView is `MIXED_CONTENT_NEVER_ALLOW` and use of `MIXED_CONTENT_ALWAYS_ALLOW` is strongly discouraged.
-///
-/// **iOS** supports these additional options:
-///
-/// - __disallowOverScroll__: Set to `true` to disable the bouncing of the WebView when the scrolling has reached an edge of the content. The default value is `false`.
-/// - __enableViewportScale__: Set to `true` to allow a viewport meta tag to either disable or restrict the range of user scaling. The default value is `false`.
-/// - __suppressesIncrementalRendering__: Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory.. The default value is `false`.
-/// - __allowsAirPlayForMediaPlayback__: Set to `true` to allow AirPlay. The default value is `true`.
-/// - __allowsBackForwardNavigationGestures__: Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. The default value is `true`.
-/// - __allowsLinkPreview__: Set to `true` to allow that pressing on a link displays a preview of the destination for the link. The default value is `true`.
-/// - __ignoresViewportScaleLimits__: Set to `true` if you want that the WebView should always allow scaling of the webpage, regardless of the author's intent. The ignoresViewportScaleLimits property overrides the `user-scalable` HTML property in a webpage. The default value is `false`.
-/// - __allowsInlineMediaPlayback__: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls. For this to work, add the `webkit-playsinline` attribute to any `