From cf0c2029ff9dc3e5962b2aa57c38d010e9345062 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Mon, 1 Feb 2021 15:55:27 +0100 Subject: [PATCH] Added addUserScript, addUserScripts, removeUserScript, removeUserScripts, removeAllUserScripts WebView methods, Added initialUserScripts WebView option, Added UserScript and UserScriptInjectionTime classes, updated README.md, fix some wrong iOS swift return value on method call handler, added InAppWebViewMethodHandler native class --- CHANGELOG.md | 3 + README.md | 113 +-- .../InAppBrowser/InAppBrowserActivity.java | 644 +----------------- .../InAppBrowserManager.java | 15 +- .../InAppWebView/FlutterWebView.java | 412 +---------- .../InAppWebView/InAppWebView.java | 19 +- .../InAppWebView/InAppWebViewClient.java | 114 +++- .../InAppWebViewMethodHandler.java | 448 ++++++++++++ example/.flutter-plugins-dependencies | 2 +- .../lib/in_app_browser_example.screen.dart | 5 + example/lib/in_app_webiew_example.screen.dart | 1 + ios/Classes/ChromeSafariBrowserManager.swift | 7 +- ios/Classes/FlutterWebViewController.swift | 386 +---------- ios/Classes/InAppBrowserManager.swift | 18 +- .../InAppBrowserWebViewController.swift | 338 +-------- ios/Classes/InAppWebView.swift | 140 +++- ios/Classes/InAppWebViewMethodHandler.swift | 396 +++++++++++ lib/src/chrome_safari_browser.dart | 1 + lib/src/cookie_manager.dart | 240 +++---- lib/src/headless_in_app_webview.dart | 10 +- lib/src/in_app_browser.dart | 12 +- lib/src/in_app_webview.dart | 14 +- lib/src/in_app_webview_controller.dart | 58 +- lib/src/types.dart | 83 +++ lib/src/webview.dart | 9 +- 25 files changed, 1492 insertions(+), 1996 deletions(-) create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java create mode 100644 ios/Classes/InAppWebViewMethodHandler.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 8af6abfe..0cdc182c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - Added `allowUniversalAccessFromFileURLs` and `allowFileAccessFromFileURLs` WebView options also for iOS (also thanks to [liranhao](https://github.com/liranhao)) - Added limited cookies support on iOS below 11.0 using JavaScript - Added `IOSCookieManager` class and `CookieManager.instance().ios.getAllCookies` iOS-specific method +- Added `UserScript` and `UserScriptInjectionTime` classes +- Added `initialUserScripts` WebView option +- Added `addUserScript`, `addUserScripts`, `removeUserScript`, `removeUserScripts`, `removeAllUserScripts` WebView methods - Updated integration tests - Merge "Upgraded appcompat to 1.2.0-rc-02" [#465](https://github.com/pichillilorenzo/flutter_inappwebview/pull/465) (thanks to [andreidiaconu](https://github.com/andreidiaconu)) - Merge "Added missing field 'headers' which returned by WebResourceResponse.toMap()" [#490](https://github.com/pichillilorenzo/flutter_inappwebview/pull/490) (thanks to [Doflatango](https://github.com/Doflatango)) diff --git a/README.md b/README.md index 248a035c..643ddac6 100755 --- a/README.md +++ b/README.md @@ -14,6 +14,14 @@ A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. +## API Reference + +See the online [API Reference](https://pub.dartlang.org/documentation/flutter_inappwebview/latest/) to get the **full documentation**. + +Note that the API shown in this `README.md` file shows only a **part** of the documentation and, also, that conforms to the **GitHub master branch only**! +So, here you could have methods, options, and events that **aren't published/released yet**! +If you need a specific version, please change the **GitHub branch** of this repository to your version or use the **online [API Reference](https://pub.dartlang.org/documentation/flutter_inappwebview/latest/)** (recommended). + ## Articles/Resources - [InAppWebView: The Real Power of WebViews in Flutter](https://medium.com/flutter-community/inappwebview-the-real-power-of-webviews-in-flutter-c6d52374209d?source=friends_link&sk=cb74487219bcd85e610a670ee0b447d0) @@ -213,14 +221,6 @@ Classes: - [HttpAuthCredentialDatabase](#httpauthcredentialdatabase-class): This class implements a singleton object (shared instance) which manages the shared HTTP auth credentials cache. - [WebStorageManager](#webstoragemanager-class): This class implements a singleton object (shared instance) which manages the web storage used by WebView instances. -## API Reference - -See the online [API Reference](https://pub.dartlang.org/documentation/flutter_inappwebview/latest/) to get the full documentation. - -The API showed in this `README.md` file shows only a part of the documentation that conforms to the master branch only. -So, here you could have methods, options, and events that aren't published yet. -If you need a specific version, change the **GitHub branch** to your version or use the **online API Reference** (recommended). - ### Load a file inside `assets` folder To be able to load your local files (assets, js, css, etc.), you need to add them in the `assets` section of the `pubspec.yaml` file, otherwise they cannot be found! @@ -389,60 +389,67 @@ Screenshots: ##### `InAppWebViewController` Cross-platform methods -* `getUrl`: Gets the URL for the current page. -* `getTitle`: Gets the title for the current page. -* `getProgress`: Gets the progress for the current page. The progress value is between 0 and 100. -* `getHtml`: Gets the content html of the page. -* `getFavicons`: Gets the list of all favicons for the current page. -* `loadUrl({required String url, Map headers = const {}})`: Loads the given url with optional headers specified as a map from name to value. -* `postUrl({required String url, required Uint8List postData})`: Loads the given url with postData using `POST` method into this WebView. -* `loadData({required String data, String mimeType = "text/html", String encoding = "utf8", String baseUrl = "about:blank", String androidHistoryUrl = "about:blank"})`: Loads the given data into this WebView. -* `loadFile({required String assetFilePath, Map headers = const {}})`: Loads the given `assetFilePath` with optional headers specified as a map from name to value. -* `reload`: Reloads the WebView. -* `goBack`: Goes back in the history of the WebView. -* `canGoBack`: Returns a boolean value indicating whether the WebView can move backward. -* `goForward`: Goes forward in the history of the WebView. -* `canGoForward`: Returns a boolean value indicating whether the WebView can move forward. -* `goBackOrForward({required int steps})`: Goes to the history item that is the number of steps away from the current item. Steps is negative if backward and positive if forward. -* `canGoBackOrForward({required int steps})`: Returns a boolean value indicating whether the WebView can go back or forward the given number of steps. Steps is negative if backward and positive if forward. -* `goTo({required WebHistoryItem historyItem})`: Navigates to a `WebHistoryItem` from the back-forward `WebHistory.list` and sets it as the current item. -* `isLoading`: Check if the WebView instance is in a loading state. -* `stopLoading`: Stops the WebView from loading. -* `evaluateJavascript({required String source})`: Evaluates JavaScript code into the WebView and returns the result of the evaluation. -* `injectJavascriptFileFromUrl({required String urlFile})`: Injects an external JavaScript file into the WebView from a defined url. -* `injectJavascriptFileFromAsset({required String assetFilePath})`: Injects a JavaScript file into the WebView from the flutter assets directory. -* `injectCSSCode({required String source})`: Injects CSS into the WebView. -* `injectCSSFileFromUrl({required String urlFile})`: Injects an external CSS file into the WebView from a defined url. -* `injectCSSFileFromAsset({required String assetFilePath})`: Injects a CSS file into the WebView from the flutter assets directory. * `addJavaScriptHandler({required String handlerName, required JavaScriptHandlerCallback callback})`: Adds a JavaScript message handler callback that listen to post messages sent from JavaScript by the handler with name `handlerName`. -* `removeJavaScriptHandler({required String handlerName})`: Removes a JavaScript message handler previously added with the `addJavaScriptHandler()` associated to `handlerName` key. -* `takeScreenshot`: Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a `Uint8List`. Returns `null` if it wasn't be able to take it. -* `setOptions({required InAppWebViewGroupOptions options})`: Sets the WebView options with the new options and evaluates them. -* `getOptions`: Gets the current WebView options. Returns the options with `null` value if they are not set yet. -* `getCopyBackForwardList`: Gets the `WebHistory` for this WebView. This contains the back/forward list for use in querying each item in the history stack. +* `addUserScript(UserScript userScript)`: Injects the specified `userScript` into the webpage’s content. +* `addUserScripts(List userScripts)`: Injects the `userScripts` into the webpage’s content. +* `canGoBackOrForward({required int steps})`: Returns a boolean value indicating whether the WebView can go back or forward the given number of steps. Steps is negative if backward and positive if forward. +* `canGoBack`: Returns a boolean value indicating whether the WebView can move backward. +* `canGoForward`: Returns a boolean value indicating whether the WebView can move forward. * `clearCache`: Clears all the webview's cache. +* `clearFocus`: Clears the current focus. It will clear also, for example, the current text selection. +* `clearMatches`: Clears the highlighting surrounding text matches created by `findAllAsync()`. +* `evaluateJavascript({required String source})`: Evaluates JavaScript code into the WebView and returns the result of the evaluation. * `findAllAsync({required String find})`: Finds all instances of find on the page and highlights them. Notifies `onFindResultReceived` listener. * `findNext({required bool forward})`: Highlights and scrolls to the next match found by `findAllAsync()`. Notifies `onFindResultReceived` listener. -* `clearMatches`: Clears the highlighting surrounding text matches created by `findAllAsync()`. -* `getTRexRunnerHtml`: Gets the html (with javascript) of the Chromium's t-rex runner game. Used in combination with `getTRexRunnerCss()`. -* `getTRexRunnerCss`: Gets the css of the Chromium's t-rex runner game. Used in combination with `getTRexRunnerHtml()`. -* `scrollTo({required int x, required int y, bool animated = false})`: Scrolls the WebView to the position. -* `scrollBy({required int x, required int y, bool animated = false})`: Moves the scrolled position of the WebView. -* `pauseTimers`: On Android, it pauses all layout, parsing, and JavaScript timers for all WebViews. This is a global requests, not restricted to just this WebView. This can be useful if the application has been paused. On iOS, it is restricted to just this WebView. -* `resumeTimers`: On Android, it resumes all layout, parsing, and JavaScript timers for all WebViews. This will resume dispatching all timers. On iOS, it resumes all layout, parsing, and JavaScript timers to just this WebView. -* `printCurrentPage`: Prints the current page. -* `getScale`: Gets the current scale of this WebView. -* `getSelectedText`: Gets the selected text. +* `getCertificate`: Gets the SSL certificate for the main top-level page or null if there is no certificate (the site is not secure). +* `getContentHeight`: Gets the height of the HTML content. +* `getCopyBackForwardList`: Gets the `WebHistory` for this WebView. This contains the back/forward list for use in querying each item in the history stack. +* `getFavicons`: Gets the list of all favicons for the current page. * `getHitTestResult`: Gets the hit result for hitting an HTML elements. -* `clearFocus`: Clears the current focus. It will clear also, for example, the current text selection. -* `setContextMenu(ContextMenu contextMenu)`: Sets or updates the WebView context menu to be used next time it will appear. -* `requestFocusNodeHref`: Requests the anchor or image element URL at the last tapped point. -* `requestImageRef`: Requests the URL of the image last touched by the user. +* `getHtml`: Gets the content html of the page. * `getMetaTags`: Returns the list of `` tags of the current WebView. * `getMetaThemeColor`: Returns an instance of `Color` representing the `content` value of the `` tag of the current WebView, if available, otherwise `null`. +* `getOptions`: Gets the current WebView options. Returns the options with `null` value if they are not set yet. +* `getProgress`: Gets the progress for the current page. The progress value is between 0 and 100. +* `getScale`: Gets the current scale of this WebView. * `getScrollX`: Returns the scrolled left position of the current WebView. * `getScrollY`: Returns the scrolled top position of the current WebView. -* `getCertificate`: Gets the SSL certificate for the main top-level page or null if there is no certificate (the site is not secure). +* `getSelectedText`: Gets the selected text. +* `getTRexRunnerCss`: Gets the css of the Chromium's t-rex runner game. Used in combination with `getTRexRunnerHtml()`. +* `getTRexRunnerHtml`: Gets the html (with javascript) of the Chromium's t-rex runner game. Used in combination with `getTRexRunnerCss()`. +* `getTitle`: Gets the title for the current page. +* `getUrl`: Gets the URL for the current page. +* `goBackOrForward({required int steps})`: Goes to the history item that is the number of steps away from the current item. Steps is negative if backward and positive if forward. +* `goBack`: Goes back in the history of the WebView. +* `goForward`: Goes forward in the history of the WebView. +* `goTo({required WebHistoryItem historyItem})`: Navigates to a `WebHistoryItem` from the back-forward `WebHistory.list` and sets it as the current item. +* `injectCSSCode({required String source})`: Injects CSS into the WebView. +* `injectCSSFileFromAsset({required String assetFilePath})`: Injects a CSS file into the WebView from the flutter assets directory. +* `injectCSSFileFromUrl({required String urlFile})`: Injects an external CSS file into the WebView from a defined url. +* `injectJavascriptFileFromAsset({required String assetFilePath})`: Injects a JavaScript file into the WebView from the flutter assets directory. +* `injectJavascriptFileFromUrl({required String urlFile})`: Injects an external JavaScript file into the WebView from a defined url. +* `isLoading`: Check if the WebView instance is in a loading state. +* `loadData({required String data, String mimeType = "text/html", String encoding = "utf8", String baseUrl = "about:blank", String androidHistoryUrl = "about:blank"})`: Loads the given data into this WebView. +* `loadFile({required String assetFilePath, Map headers = const {}})`: Loads the given `assetFilePath` with optional headers specified as a map from name to value. +* `loadUrl({required String url, Map headers = const {}})`: Loads the given url with optional headers specified as a map from name to value. +* `pauseTimers`: On Android, it pauses all layout, parsing, and JavaScript timers for all WebViews. This is a global requests, not restricted to just this WebView. This can be useful if the application has been paused. On iOS, it is restricted to just this WebView. +* `postUrl({required String url, required Uint8List postData})`: Loads the given url with postData using `POST` method into this WebView. +* `printCurrentPage`: Prints the current page. +* `reload`: Reloads the WebView. +* `removeAllUserScripts()`: Removes all the user scripts from the webpage’s content. +* `removeJavaScriptHandler({required String handlerName})`: Removes a JavaScript message handler previously added with the `addJavaScriptHandler()` associated to `handlerName` key. +* `removeUserScript(UserScript userScript)`: Removes the specified `userScript` from the webpage’s content. +* `removeUserScripts(List userScripts)`: Removes the `userScripts` from the webpage’s content. +* `requestFocusNodeHref`: Requests the anchor or image element URL at the last tapped point. +* `requestImageRef`: Requests the URL of the image last touched by the user. +* `resumeTimers`: On Android, it resumes all layout, parsing, and JavaScript timers for all WebViews. This will resume dispatching all timers. On iOS, it resumes all layout, parsing, and JavaScript timers to just this WebView. +* `scrollBy({required int x, required int y, bool animated = false})`: Moves the scrolled position of the WebView. +* `scrollTo({required int x, required int y, bool animated = false})`: Scrolls the WebView to the position. +* `setContextMenu(ContextMenu contextMenu)`: Sets or updates the WebView context menu to be used next time it will appear. +* `setOptions({required InAppWebViewGroupOptions options})`: Sets the WebView options with the new options and evaluates them. +* `stopLoading`: Stops the WebView from loading. +* `takeScreenshot`: Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a `Uint8List`. Returns `null` if it wasn't be able to take it. +* `zoomBy`: Performs a zoom operation in this WebView. * `static getDefaultUserAgent`: Gets the default user agent. ##### `InAppWebViewController.webStorage` diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java index ca32113b..b3e25dc9 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java @@ -3,7 +3,6 @@ package com.pichillilorenzo.flutter_inappwebview.InAppBrowser; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; -import android.os.Build; import android.os.Bundle; import android.os.Message; import android.util.Log; @@ -13,22 +12,19 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ProgressBar; import android.widget.SearchView; -import androidx.annotation.RequiresApi; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; -import androidx.webkit.WebViewCompat; -import androidx.webkit.WebViewFeature; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewChromeClient; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewOptions; +import com.pichillilorenzo.flutter_inappwebview.InAppWebViewMethodHandler; import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.Shared; @@ -37,10 +33,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -public class InAppBrowserActivity extends AppCompatActivity implements MethodChannel.MethodCallHandler { +public class InAppBrowserActivity extends AppCompatActivity { static final String LOG_TAG = "InAppBrowserActivity"; public MethodChannel channel; @@ -56,6 +51,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha public boolean isHidden = false; public String fromActivity; public List activityResultListeners = new ArrayList<>(); + public InAppWebViewMethodHandler methodCallDelegate; @Override protected void onCreate(Bundle savedInstanceState) { @@ -70,7 +66,6 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha windowId = b.getInt("windowId"); channel = new MethodChannel(Shared.messenger, "com.pichillilorenzo/flutter_inappbrowser_" + uuid); - channel.setMethodCallHandler(this); setContentView(R.layout.activity_web_view); @@ -79,10 +74,14 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha webView.inAppBrowserActivity = this; webView.channel = channel; + methodCallDelegate = new InAppWebViewMethodHandler(webView); + channel.setMethodCallHandler(methodCallDelegate); + fromActivity = b.getString("fromActivity"); HashMap optionsMap = (HashMap) b.getSerializable("options"); HashMap contextMenu = (HashMap) b.getSerializable("contextMenu"); + List> initialUserScripts = (List>) b.getSerializable("initialUserScripts"); options = new InAppBrowserOptions(); options.parse(optionsMap); @@ -91,6 +90,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha webViewOptions.parse(optionsMap); webView.options = webViewOptions; webView.contextMenu = contextMenu; + webView.userScripts = initialUserScripts; actionBar = getSupportActionBar(); @@ -124,298 +124,6 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha channel.invokeMethod("onBrowserCreated", obj); } - @Override - public void onMethodCall(final MethodCall call, final MethodChannel.Result result) { - switch (call.method) { - case "getUrl": - result.success(getUrl()); - break; - case "getTitle": - result.success(getWebViewTitle()); - break; - case "getProgress": - result.success(getProgress()); - break; - case "loadUrl": - { - String url = (String) call.argument("url"); - Map headers = (Map) call.argument("headers"); - if (headers != null) - loadUrl(url, headers, result); - else - loadUrl(url, result); - } - break; - case "postUrl": - postUrl((String) call.argument("url"), (byte[]) call.argument("postData"), result); - break; - case "loadData": - { - String data = (String) call.argument("data"); - String mimeType = (String) call.argument("mimeType"); - String encoding = (String) call.argument("encoding"); - String baseUrl = (String) call.argument("baseUrl"); - String historyUrl = (String) call.argument("historyUrl"); - loadData(data, mimeType, encoding, baseUrl, historyUrl, result); - } - break; - case "loadFile": - { - String url = (String) call.argument("url"); - Map headers = (Map) call.argument("headers"); - if (headers != null) - loadFile(url, headers, result); - else - loadFile(url, result); - } - break; - case "close": - close(result); - break; - case "evaluateJavascript": - { - String source = (String) call.argument("source"); - evaluateJavascript(source, result); - } - break; - case "injectJavascriptFileFromUrl": - { - String urlFile = (String) call.argument("urlFile"); - injectJavascriptFileFromUrl(urlFile); - } - result.success(true); - break; - case "injectCSSCode": - { - String source = (String) call.argument("source"); - injectCSSCode(source); - } - result.success(true); - break; - case "injectCSSFileFromUrl": - { - String urlFile = (String) call.argument("urlFile"); - injectCSSFileFromUrl(urlFile); - } - result.success(true); - break; - case "show": - show(); - result.success(true); - break; - case "hide": - hide(); - result.success(true); - break; - case "reload": - reload(); - result.success(true); - break; - case "goBack": - goBack(); - result.success(true); - break; - case "canGoBack": - result.success(canGoBack()); - break; - case "goForward": - goForward(); - result.success(true); - break; - case "canGoForward": - result.success(canGoForward()); - break; - case "goBackOrForward": - goBackOrForward((Integer) call.argument("steps")); - result.success(true); - break; - case "canGoBackOrForward": - result.success(canGoBackOrForward((Integer) call.argument("steps"))); - break; - case "stopLoading": - stopLoading(); - result.success(true); - break; - case "isLoading": - result.success(isLoading()); - break; - case "isHidden": - result.success(isHidden); - break; - case "takeScreenshot": - takeScreenshot(result); - break; - case "setOptions": - { - String optionsType = (String) call.argument("optionsType"); - switch (optionsType){ - case "InAppBrowserOptions": - InAppBrowserOptions inAppBrowserOptions = new InAppBrowserOptions(); - HashMap inAppBrowserOptionsMap = (HashMap) call.argument("options"); - inAppBrowserOptions.parse(inAppBrowserOptionsMap); - setOptions(inAppBrowserOptions, inAppBrowserOptionsMap); - break; - default: - result.error(LOG_TAG, "Options " + optionsType + " not available.", null); - } - } - result.success(true); - break; - case "getOptions": - result.success(getOptions()); - break; - case "getCopyBackForwardList": - result.success(getCopyBackForwardList()); - break; - case "startSafeBrowsing": - startSafeBrowsing(result); - break; - case "clearCache": - clearCache(); - result.success(true); - break; - case "clearSslPreferences": - clearSslPreferences(); - result.success(true); - break; - case "findAllAsync": - String find = (String) call.argument("find"); - findAllAsync(find); - result.success(true); - break; - case "findNext": - Boolean forward = (Boolean) call.argument("forward"); - findNext(forward, result); - break; - case "clearMatches": - clearMatches(result); - break; - case "scrollTo": - { - Integer x = (Integer) call.argument("x"); - Integer y = (Integer) call.argument("y"); - Boolean animated = (Boolean) call.argument("animated"); - scrollTo(x, y, animated); - } - result.success(true); - break; - case "scrollBy": - { - Integer x = (Integer) call.argument("x"); - Integer y = (Integer) call.argument("y"); - Boolean animated = (Boolean) call.argument("animated"); - scrollBy(x, y, animated); - } - result.success(true); - break; - case "pause": - onPauseWebView(); - result.success(true); - break; - case "resume": - onResumeWebView(); - result.success(true); - break; - case "pauseTimers": - pauseTimers(); - result.success(true); - break; - case "resumeTimers": - resumeTimers(); - result.success(true); - break; - case "printCurrentPage": - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - printCurrentPage(); - } - result.success(true); - break; - case "getContentHeight": - result.success(getContentHeight()); - break; - case "zoomBy": - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Float zoomFactor = (Float) call.argument("zoomFactor"); - zoomBy(zoomFactor); - } - result.success(true); - break; - case "getOriginalUrl": - result.success(getOriginalUrl()); - break; - case "getScale": - result.success(getScale()); - break; - case "getSelectedText": - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - getSelectedText(result); - } else { - result.success(null); - } - break; - case "getHitTestResult": - result.success(getHitTestResult()); - break; - case "pageDown": - { - boolean bottom = (boolean) call.argument("bottom"); - result.success(pageDown(bottom)); - } - break; - case "pageUp": - { - boolean top = (boolean) call.argument("top"); - result.success(pageUp(top)); - } - break; - case "saveWebArchive": - { - String basename = (String) call.argument("basename"); - boolean autoname = (boolean) call.argument("autoname"); - saveWebArchive(basename, autoname, result); - } - break; - case "zoomIn": - result.success(zoomIn()); - break; - case "zoomOut": - result.success(zoomOut()); - break; - case "clearFocus": - clearFocus(); - result.success(true); - break; - case "setContextMenu": - { - Map contextMenu = (Map) call.argument("contextMenu"); - setContextMenu(contextMenu); - } - result.success(true); - break; - case "requestFocusNodeHref": - result.success(requestFocusNodeHref()); - break; - case "requestImageRef": - result.success(requestImageRef()); - break; - case "getScrollX": - result.success(getScrollX()); - break; - case "getScrollY": - result.success(getScrollY()); - break; - case "getCertificate": - result.success(getCertificate()); - break; - case "clearHistory": - clearHistory(); - result.success(true); - break; - default: - result.notImplemented(); - } - } - private void prepareView() { webView.prepare(); @@ -505,72 +213,6 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha return true; } - public String getUrl() { - if (webView != null) - return webView.getUrl(); - return null; - } - - public String getWebViewTitle() { - if (webView != null) - return webView.getTitle(); - return null; - } - - public Integer getProgress() { - if (webView != null) - return webView.getProgress(); - return null; - } - - public void loadUrl(String url, MethodChannel.Result result) { - if (webView != null) { - webView.loadUrl(url, result); - } else { - result.error(LOG_TAG, "webView is null", null); - } - } - - public void loadUrl(String url, Map headers, MethodChannel.Result result) { - if (webView != null) { - webView.loadUrl(url, headers, result); - } else { - result.error(LOG_TAG, "webView is null", null); - } - } - - public void postUrl(String url, byte[] postData, MethodChannel.Result result) { - if (webView != null) { - webView.postUrl(url, postData, result); - } else { - result.error(LOG_TAG, "webView is null", null); - } - } - - public void loadData(String data, String mimeType, String encoding, String baseUrl, String historyUrl, MethodChannel.Result result) { - if (webView != null) { - webView.loadData(data, mimeType, encoding, baseUrl, historyUrl, result); - } else { - result.error(LOG_TAG, "webView is null", null); - } - } - - public void loadFile(String url, MethodChannel.Result result) { - if (webView != null) { - webView.loadFile(url, result); - } else { - result.error(LOG_TAG, "webView is null", null); - } - } - - public void loadFile(String url, Map headers, MethodChannel.Result result) { - if (webView != null) { - webView.loadFile(url, headers, result); - } else { - result.error(LOG_TAG, "webView is null", null); - } - } - public boolean onKeyDown(int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK)) { if (canGoBack()) @@ -620,17 +262,6 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha return false; } - public void goBackOrForward(int steps) { - if (webView != null && canGoBackOrForward(steps)) - webView.goBackOrForward(steps); - } - - public boolean canGoBackOrForward(int steps) { - if (webView != null) - return webView.canGoBackOrForward(steps); - return false; - } - public void hide() { try { isHidden = true; @@ -650,17 +281,6 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha startActivityIfNeeded(openActivity, 0); } - public void stopLoading() { - if (webView != null) - webView.stopLoading(); - } - - public boolean isLoading() { - if (webView != null) - return webView.isLoading; - return false; - } - public void goBackButtonClicked(MenuItem item) { goBack(); } @@ -684,13 +304,6 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha close(null); } - public void takeScreenshot(MethodChannel.Result result) { - if (webView != null) - webView.takeScreenshot(result); - else - result.success(null); - } - public void setOptions(InAppBrowserOptions newOptions, HashMap newOptionsMap) { InAppWebViewOptions newInAppWebViewOptions = new InAppWebViewOptions(); @@ -747,246 +360,13 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha return optionsMap; } - public void evaluateJavascript(String source, MethodChannel.Result result) { - if (webView != null) - webView.evaluateJavascript(source, result); - else - result.success(""); - } - - public void injectJavascriptFileFromUrl(String urlFile) { - if (webView != null) - webView.injectJavascriptFileFromUrl(urlFile); - } - - public void injectCSSCode(String source) { - if (webView != null) - webView.injectCSSCode(source); - } - - public void injectCSSFileFromUrl(String urlFile) { - if (webView != null) - webView.injectCSSFileFromUrl(urlFile); - } - - public HashMap getCopyBackForwardList() { - if (webView != null) - return webView.getCopyBackForwardList(); - return null; - } - - public void startSafeBrowsing(final MethodChannel.Result result) { - if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && - WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) { - WebViewCompat.startSafeBrowsing(webView.getContext(), new ValueCallback() { - @Override - public void onReceiveValue(Boolean success) { - result.success(success); - } - }); - } - else { - result.success(false); - } - } - - public void clearCache() { - if (webView != null) - webView.clearAllCache(); - } - - public void clearSslPreferences() { - if (webView != null) - webView.clearSslPreferences(); - } - - 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); - } - - public void scrollTo(Integer x, Integer y, Boolean animated) { - if (webView != null) - webView.scrollTo(x, y, animated); - } - - public void scrollBy(Integer x, Integer y, Boolean animated) { - if (webView != null) - webView.scrollBy(x, y, animated); - } - - public void onPauseWebView() { - if (webView != null) - webView.onPause(); - } - - public void onResumeWebView() { - if (webView != null) - webView.onResume(); - } - - public void pauseTimers() { - if (webView != null) - webView.pauseTimers(); - } - - public void resumeTimers() { - if (webView != null) - webView.resumeTimers(); - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public void printCurrentPage() { - if (webView != null) - webView.printCurrentPage(); - } - - public Integer getContentHeight() { - if (webView != null) - return webView.getContentHeight(); - return null; - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public void zoomBy(Float zoomFactor) { - if (webView != null) - webView.zoomBy(zoomFactor); - } - - public String getOriginalUrl() { - if (webView != null) - return webView.getOriginalUrl(); - return null; - } - - public Float getScale() { - if (webView != null) - return webView.getUpdatedScale(); - return null; - } - - @RequiresApi(api = Build.VERSION_CODES.KITKAT) - public void getSelectedText(MethodChannel.Result result) { - if (webView != null) - webView.getSelectedText(result); - else - result.success(null); - } - - public Map getHitTestResult() { - if (webView != null) { - WebView.HitTestResult hitTestResult = webView.getHitTestResult(); - Map obj = new HashMap<>(); - obj.put("type", hitTestResult.getType()); - obj.put("extra", hitTestResult.getExtra()); - return obj; - } - return null; - } - - public boolean pageDown(boolean bottom) { - if (webView != null) - return webView.pageDown(bottom); - return false; - } - - public boolean pageUp(boolean top) { - if (webView != null) - return webView.pageUp(top); - return false; - } - - public void saveWebArchive(String basename, boolean autoname, final MethodChannel.Result result) { - if (webView != null) { - webView.saveWebArchive(basename, autoname, new ValueCallback() { - @Override - public void onReceiveValue(String value) { - result.success(value); - } - }); - } else { - result.success(null); - } - } - - public boolean zoomIn() { - if (webView != null) - return webView.zoomIn(); - return false; - } - - public boolean zoomOut() { - if (webView != null) - return webView.zoomOut(); - return false; - } - - public void clearFocus() { - if (webView != null) - webView.clearFocus(); - } - - public void setContextMenu(Map contextMenu) { - if (webView != null) - webView.contextMenu = contextMenu; - } - - public Map requestFocusNodeHref() { - if (webView != null) - return webView.requestFocusNodeHref(); - return null; - } - - public Map requestImageRef() { - if (webView != null) - return webView.requestImageRef(); - return null; - } - - public Integer getScrollX() { - if (webView != null) - return webView.getScrollX(); - return null; - } - - public Integer getScrollY() { - if (webView != null) - return webView.getScrollY(); - return null; - } - - public Map getCertificate() { - if (webView != null) - return webView.getCertificateMap(); - return null; - } - - public void clearHistory() { - if (webView != null) - webView.clearHistory(); - } - public void dispose() { channel.setMethodCallHandler(null); activityResultListeners.clear(); + if (methodCallDelegate != null) { + methodCallDelegate.dispose(); + methodCallDelegate = null; + } if (webView != null) { if (Shared.activityPluginBinding != null) { Shared.activityPluginBinding.removeActivityResultListener(webView.inAppWebViewChromeClient); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserManager.java index 26bfca7e..acefff4a 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserManager.java @@ -72,7 +72,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { Map headers = (Map) call.argument("headers"); HashMap contextMenu = (HashMap) call.argument("contextMenu"); Integer windowId = (Integer) call.argument("windowId"); - openUrl(activity, uuid, url, options, headers, contextMenu, windowId); + List> initialUserScripts = (List>) call.argument("initialUserScripts"); + openUrl(activity, uuid, url, options, headers, contextMenu, windowId, initialUserScripts); } result.success(true); break; @@ -90,7 +91,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { Map headers = (Map) call.argument("headers"); HashMap contextMenu = (HashMap) call.argument("contextMenu"); Integer windowId = (Integer) call.argument("windowId"); - openUrl(activity, uuid, url, options, headers, contextMenu, windowId); + List> initialUserScripts = (List>) call.argument("initialUserScripts"); + openUrl(activity, uuid, url, options, headers, contextMenu, windowId, initialUserScripts); } result.success(true); break; @@ -104,7 +106,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { String historyUrl = (String) call.argument("historyUrl"); HashMap contextMenu = (HashMap) call.argument("contextMenu"); Integer windowId = (Integer) call.argument("windowId"); - openData(activity, uuid, options, data, mimeType, encoding, baseUrl, historyUrl, contextMenu, windowId); + List> initialUserScripts = (List>) call.argument("initialUserScripts"); + openData(activity, uuid, options, data, mimeType, encoding, baseUrl, historyUrl, contextMenu, windowId, initialUserScripts); } result.success(true); break; @@ -196,7 +199,7 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { } public void openUrl(Activity activity, String uuid, String url, HashMap options, Map headers, - HashMap contextMenu, Integer windowId) { + HashMap contextMenu, Integer windowId, List> initialUserScripts) { Bundle extras = new Bundle(); extras.putString("fromActivity", activity.getClass().getName()); extras.putString("url", url); @@ -206,11 +209,12 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { extras.putSerializable("headers", (Serializable) headers); extras.putSerializable("contextMenu", (Serializable) contextMenu); extras.putInt("windowId", windowId != null ? windowId : -1); + extras.putSerializable("initialUserScripts", (Serializable) initialUserScripts); startInAppBrowserActivity(activity, extras); } public void openData(Activity activity, String uuid, HashMap options, String data, String mimeType, String encoding, - String baseUrl, String historyUrl, HashMap contextMenu, Integer windowId) { + String baseUrl, String historyUrl, HashMap contextMenu, Integer windowId, List> initialUserScripts) { Bundle extras = new Bundle(); extras.putBoolean("isData", true); extras.putString("uuid", uuid); @@ -222,6 +226,7 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { extras.putString("historyUrl", historyUrl); extras.putSerializable("contextMenu", (Serializable) contextMenu); extras.putInt("windowId", windowId != null ? windowId : -1); + extras.putSerializable("initialUserScripts", (Serializable) initialUserScripts); startInAppBrowserActivity(activity, extras); } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java index a59c6a95..863c60e0 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java @@ -2,48 +2,37 @@ package com.pichillilorenzo.flutter_inappwebview.InAppWebView; import android.content.Context; import android.hardware.display.DisplayManager; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.View; -import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; -import androidx.webkit.WebViewCompat; -import androidx.webkit.WebViewFeature; - +import com.pichillilorenzo.flutter_inappwebview.InAppWebViewMethodHandler; import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Util; import java.io.IOException; -import java.lang.reflect.Field; import java.util.HashMap; +import java.util.List; import java.util.Map; -import io.flutter.embedding.android.FlutterView; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.platform.PlatformView; -import static io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import static io.flutter.plugin.common.MethodChannel.Result; - -public class FlutterWebView implements PlatformView, MethodCallHandler { +public class FlutterWebView implements PlatformView { static final String LOG_TAG = "IAWFlutterWebView"; public InAppWebView webView; public final MethodChannel channel; + public InAppWebViewMethodHandler methodCallDelegate; public FlutterWebView(BinaryMessenger messenger, final Context context, Object id, HashMap params, View containerView) { channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_inappwebview_" + id); - channel.setMethodCallHandler(this); DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); @@ -56,6 +45,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { Map initialOptions = (Map) params.get("initialOptions"); Map contextMenu = (Map) params.get("contextMenu"); Integer windowId = (Integer) params.get("windowId"); + List> initialUserScripts = (List>) params.get("initialUserScripts"); InAppWebViewOptions options = new InAppWebViewOptions(); options.parse(initialOptions); @@ -67,9 +57,12 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { "- See the official wiki here: https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects\n\n\n"); } - webView = new InAppWebView(context, this, id, windowId, options, contextMenu, containerView); + webView = new InAppWebView(context, this, id, windowId, options, contextMenu, containerView, initialUserScripts); displayListenerProxy.onPostWebViewInitialization(displayManager); + methodCallDelegate = new InAppWebViewMethodHandler(webView); + channel.setMethodCallHandler(methodCallDelegate); + webView.prepare(); if (windowId != null) { @@ -114,392 +107,13 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { return webView; } - @Override - public void onMethodCall(MethodCall call, final Result result) { - switch (call.method) { - case "getUrl": - result.success((webView != null) ? webView.getUrl() : null); - break; - case "getTitle": - result.success((webView != null) ? webView.getTitle() : null); - break; - case "getProgress": - result.success((webView != null) ? webView.getProgress() : null); - break; - case "loadUrl": - if (webView != null) - webView.loadUrl((String) call.argument("url"), (Map) call.argument("headers"), result); - else - result.success(false); - break; - case "postUrl": - if (webView != null) - webView.postUrl((String) call.argument("url"), (byte[]) call.argument("postData"), result); - else - result.success(false); - break; - case "loadData": - { - String data = (String) call.argument("data"); - String mimeType = (String) call.argument("mimeType"); - String encoding = (String) call.argument("encoding"); - String baseUrl = (String) call.argument("baseUrl"); - String historyUrl = (String) call.argument("historyUrl"); - - if (webView != null) - webView.loadData(data, mimeType, encoding, baseUrl, historyUrl, result); - else - result.success(false); - } - break; - case "loadFile": - if (webView != null) - webView.loadFile((String) call.argument("url"), (Map) call.argument("headers"), result); - else - result.success(false); - break; - case "evaluateJavascript": - if (webView != null) { - String source = (String) call.argument("source"); - webView.evaluateJavascript(source, result); - } - else { - result.success(""); - } - break; - case "injectJavascriptFileFromUrl": - if (webView != null) { - String urlFile = (String) call.argument("urlFile"); - webView.injectJavascriptFileFromUrl(urlFile); - } - result.success(true); - break; - case "injectCSSCode": - if (webView != null) { - String source = (String) call.argument("source"); - webView.injectCSSCode(source); - } - result.success(true); - break; - case "injectCSSFileFromUrl": - if (webView != null) { - String urlFile = (String) call.argument("urlFile"); - webView.injectCSSFileFromUrl(urlFile); - } - result.success(true); - break; - case "reload": - if (webView != null) - webView.reload(); - result.success(true); - break; - case "goBack": - if (webView != null) - webView.goBack(); - result.success(true); - break; - case "canGoBack": - result.success((webView != null) && webView.canGoBack()); - break; - case "goForward": - if (webView != null) - webView.goForward(); - result.success(true); - break; - case "canGoForward": - result.success((webView != null) && webView.canGoForward()); - break; - case "goBackOrForward": - if (webView != null) - webView.goBackOrForward((Integer) call.argument("steps")); - result.success(true); - break; - case "canGoBackOrForward": - result.success((webView != null) && webView.canGoBackOrForward((Integer) call.argument("steps"))); - break; - case "stopLoading": - if (webView != null) - webView.stopLoading(); - result.success(true); - break; - case "isLoading": - result.success((webView != null) && webView.isLoading()); - break; - case "takeScreenshot": - if (webView != null) - webView.takeScreenshot(result); - else - result.success(null); - break; - case "setOptions": - if (webView != null) { - InAppWebViewOptions inAppWebViewOptions = new InAppWebViewOptions(); - HashMap inAppWebViewOptionsMap = (HashMap) call.argument("options"); - inAppWebViewOptions.parse(inAppWebViewOptionsMap); - webView.setOptions(inAppWebViewOptions, inAppWebViewOptionsMap); - } - result.success(true); - break; - case "getOptions": - result.success((webView != null) ? webView.getOptions() : null); - break; - case "getCopyBackForwardList": - result.success((webView != null) ? webView.getCopyBackForwardList() : null); - break; - case "startSafeBrowsing": - if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && - WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) { - WebViewCompat.startSafeBrowsing(webView.getContext(), new ValueCallback() { - @Override - public void onReceiveValue(Boolean success) { - result.success(success); - } - }); - } - else { - result.success(false); - } - break; - case "clearCache": - if (webView != null) - webView.clearAllCache(); - result.success(true); - break; - case "clearSslPreferences": - if (webView != null) - webView.clearSslPreferences(); - result.success(true); - 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 "scrollTo": - if (webView != null) { - Integer x = (Integer) call.argument("x"); - Integer y = (Integer) call.argument("y"); - Boolean animated = (Boolean) call.argument("animated"); - webView.scrollTo(x, y, animated); - } - result.success(true); - break; - case "scrollBy": - if (webView != null) { - Integer x = (Integer) call.argument("x"); - Integer y = (Integer) call.argument("y"); - Boolean animated = (Boolean) call.argument("animated"); - webView.scrollBy(x, y, animated); - } - result.success(true); - break; - case "pause": - if (webView != null) { - webView.onPause(); - result.success(true); - } else { - result.success(false); - } - break; - case "resume": - if (webView != null) { - webView.onResume(); - result.success(true); - } else { - result.success(false); - } - break; - case "pauseTimers": - if (webView != null) { - webView.pauseTimers(); - result.success(true); - } else { - result.success(false); - } - break; - case "resumeTimers": - if (webView != null) { - webView.resumeTimers(); - result.success(true); - } else { - result.success(false); - } - break; - case "printCurrentPage": - if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - webView.printCurrentPage(); - result.success(true); - } else { - result.success(false); - } - break; - case "getContentHeight": - result.success((webView != null) ? webView.getContentHeight() : null); - break; - case "zoomBy": - if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - double zoomFactor = (double) call.argument("zoomFactor"); - webView.zoomBy((float) zoomFactor); - result.success(true); - } else { - result.success(false); - } - break; - case "getOriginalUrl": - result.success((webView != null) ? webView.getOriginalUrl() : null); - break; - case "getScale": - result.success((webView != null) ? webView.getUpdatedScale() : null); - break; - case "getSelectedText": - if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.getSelectedText(result); - } else { - result.success(null); - } - break; - case "getHitTestResult": - if (webView != null) { - WebView.HitTestResult hitTestResult = webView.getHitTestResult(); - Map obj = new HashMap<>(); - obj.put("type", hitTestResult.getType()); - obj.put("extra", hitTestResult.getExtra()); - result.success(obj); - } else { - result.success(null); - } - break; - case "pageDown": - if (webView != null) { - boolean bottom = (boolean) call.argument("bottom"); - result.success(webView.pageDown(bottom)); - } else { - result.success(false); - } - break; - case "pageUp": - if (webView != null) { - boolean top = (boolean) call.argument("top"); - result.success(webView.pageUp(top)); - } else { - result.success(false); - } - break; - case "saveWebArchive": - if (webView != null) { - String basename = (String) call.argument("basename"); - boolean autoname = (boolean) call.argument("autoname"); - webView.saveWebArchive(basename, autoname, new ValueCallback() { - @Override - public void onReceiveValue(String value) { - result.success(value); - } - }); - } else { - result.success(null); - } - break; - case "zoomIn": - if (webView != null) { - result.success(webView.zoomIn()); - } else { - result.success(false); - } - break; - case "zoomOut": - if (webView != null) { - result.success(webView.zoomOut()); - } else { - result.success(false); - } - break; - case "clearFocus": - if (webView != null) { - webView.clearFocus(); - result.success(true); - } else { - result.success(false); - } - break; - case "setContextMenu": - if (webView != null) { - Map contextMenu = (Map) call.argument("contextMenu"); - webView.contextMenu = contextMenu; - result.success(true); - } else { - result.success(false); - } - break; - case "requestFocusNodeHref": - if (webView != null) { - result.success(webView.requestFocusNodeHref()); - } else { - result.success(null); - } - break; - case "requestImageRef": - if (webView != null) { - result.success(webView.requestImageRef()); - } else { - result.success(null); - } - break; - case "getScrollX": - if (webView != null) { - result.success(webView.getScrollX()); - } else { - result.success(null); - } - break; - case "getScrollY": - if (webView != null) { - result.success(webView.getScrollY()); - } else { - result.success(null); - } - break; - case "getCertificate": - if (webView != null) { - result.success(webView.getCertificateMap()); - } else { - result.success(null); - } - break; - case "clearHistory": - if (webView != null) { - webView.clearHistory(); - result.success(true); - } else { - result.success(false); - } - break; - default: - result.notImplemented(); - } - } - @Override public void dispose() { channel.setMethodCallHandler(null); + if (methodCallDelegate != null) { + methodCallDelegate.dispose(); + methodCallDelegate = null; + } if (webView != null) { webView.inAppWebViewChromeClient.dispose(); webView.inAppWebViewClient.dispose(); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java index fb3a5dbe..4ef70e50 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java @@ -104,6 +104,7 @@ final public class InAppWebView extends InputAwareWebView { public Map contextMenu = null; public Handler headlessHandler = new Handler(Looper.getMainLooper()); static Handler mHandler = new Handler(); + public List> userScripts = new ArrayList<>(); public Runnable checkScrollStoppedTask; public int initialPositionScrollStoppedTask; @@ -632,7 +633,10 @@ final public class InAppWebView extends InputAwareWebView { super(context, attrs, defaultStyle); } - public InAppWebView(Context context, Object obj, Object id, Integer windowId, InAppWebViewOptions options, Map contextMenu, View containerView) { + public InAppWebView(Context context, Object obj, Object id, + Integer windowId, InAppWebViewOptions options, + Map contextMenu, View containerView, + List> userScripts) { super(context, containerView); if (obj instanceof InAppBrowserActivity) this.inAppBrowserActivity = (InAppBrowserActivity) obj; @@ -643,6 +647,7 @@ final public class InAppWebView extends InputAwareWebView { this.windowId = windowId; this.options = options; this.contextMenu = contextMenu; + this.userScripts = userScripts; Shared.activity.registerForContextMenu(this); } @@ -1975,6 +1980,18 @@ final public class InAppWebView extends InputAwareWebView { return null; } + public boolean addUserScript(Map userScript) { + return userScripts.add(userScript); + } + + public Map removeUserScript(int index) { + return userScripts.remove(index); + } + + public void removeAllUserScripts() { + userScripts.clear(); + } + @Override public void dispose() { if (windowId != null && InAppWebViewChromeClient.windowWebViewMessages.containsKey(windowId)) { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java index dd7ca586..089e552e 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java @@ -162,44 +162,99 @@ public class InAppWebViewClient extends WebViewClient { }); } - private void loadCustomJavaScript(WebView view) { + private void loadCustomJavaScriptOnPageStarted(WebView view) { InAppWebView webView = (InAppWebView) view; - String js = InAppWebView.consoleLogJS.replaceAll("[\r\n]+", ""); - js += JavaScriptBridgeInterface.flutterInAppBroserJSClass.replaceAll("[\r\n]+", ""); - if (webView.options.useShouldInterceptAjaxRequest) { - js += InAppWebView.interceptAjaxRequestsJS.replaceAll("[\r\n]+", ""); - } - if (webView.options.useShouldInterceptFetchRequest) { - js += InAppWebView.interceptFetchRequestsJS.replaceAll("[\r\n]+", ""); - } - if (webView.options.useOnLoadResource) { - js += InAppWebView.resourceObserverJS.replaceAll("[\r\n]+", ""); - } - if (!webView.options.useHybridComposition) { - js += InAppWebView.checkGlobalKeyDownEventToHideContextMenuJS.replaceAll("[\r\n]+", ""); - } - js += InAppWebView.onWindowFocusEventJS.replaceAll("[\r\n]+", ""); - js += InAppWebView.onWindowBlurEventJS.replaceAll("[\r\n]+", ""); - js += InAppWebView.printJS.replaceAll("[\r\n]+", ""); + String js = preparePluginUserScripts(webView); + js += prepareUserScriptsAtDocumentStart(webView); js = InAppWebView.scriptsWrapperJS - .replace("$PLACEHOLDER_VALUE", js) - .replaceAll("[\r\n]+", ""); + .replace("$PLACEHOLDER_VALUE", js); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript(js, (ValueCallback) null); } else { - webView.loadUrl("javascript:" + js); + webView.loadUrl("javascript:" + js.replaceAll("[\r\n]+", "")); } } + private void loadCustomJavaScriptOnPageFinished(WebView view) { + InAppWebView webView = (InAppWebView) view; + + // try to reload also custom scripts if they were not loaded during the onPageStarted event + String js = preparePluginUserScripts(webView); + js += prepareUserScriptsAtDocumentStart(webView); + js += prepareUserScriptsAtDocumentEnd(webView); + + js = InAppWebView.scriptsWrapperJS + .replace("$PLACEHOLDER_VALUE", js); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.evaluateJavascript(js, (ValueCallback) null); + } else { + webView.loadUrl("javascript:" + js.replaceAll("[\r\n]+", "")); + } + } + + private String preparePluginUserScripts(InAppWebView webView) { + String js = InAppWebView.consoleLogJS; + js += JavaScriptBridgeInterface.flutterInAppBroserJSClass; + if (webView.options.useShouldInterceptAjaxRequest) { + js += InAppWebView.interceptAjaxRequestsJS; + } + if (webView.options.useShouldInterceptFetchRequest) { + js += InAppWebView.interceptFetchRequestsJS; + } + if (webView.options.useOnLoadResource) { + js += InAppWebView.resourceObserverJS; + } + if (!webView.options.useHybridComposition) { + js += InAppWebView.checkGlobalKeyDownEventToHideContextMenuJS; + } + js += InAppWebView.onWindowFocusEventJS; + js += InAppWebView.onWindowBlurEventJS; + js += InAppWebView.printJS; + + return js; + } + + private String prepareUserScriptsAtDocumentStart(InAppWebView webView) { + StringBuilder js = new StringBuilder(); + + for (Map userScript : webView.userScripts) { + Integer injectionTime = (Integer) userScript.get("injectionTime"); + if (injectionTime == null || injectionTime == 0) { + String source = (String) userScript.get("source"); + if (source != null) { + js.append("(function(){").append(source).append("})();"); + } + } + } + + return js.toString(); + } + + private String prepareUserScriptsAtDocumentEnd(InAppWebView webView) { + StringBuilder js = new StringBuilder(); + + for (Map userScript : webView.userScripts) { + Integer injectionTime = (Integer) userScript.get("injectionTime"); + if (injectionTime == 1) { + String source = (String) userScript.get("source"); + if (source != null) { + js.append("(function(){").append(source).append("})();"); + } + } + } + + return js.toString(); + } + @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { + final InAppWebView webView = (InAppWebView) view; - InAppWebView webView = (InAppWebView) view; - - loadCustomJavaScript(webView); + loadCustomJavaScriptOnPageStarted(webView); super.onPageStarted(view, url, favicon); @@ -219,8 +274,7 @@ public class InAppWebViewClient extends WebViewClient { public void onPageFinished(WebView view, String url) { final InAppWebView webView = (InAppWebView) view; - // try to reload custom javascript scripts if they were not loaded during the onPageStarted event - loadCustomJavaScript(webView); + loadCustomJavaScriptOnPageFinished(webView); super.onPageFinished(view, url); @@ -228,19 +282,19 @@ public class InAppWebViewClient extends WebViewClient { previousAuthRequestFailureCount = 0; credentialsProposed = null; - // CB-10395 InAppBrowserManager's WebView not storing cookies reliable to local device storage + // WebView not storing cookies reliable to local device storage if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().flush(); } else { CookieSyncManager.getInstance().sync(); } - String js = InAppWebView.platformReadyJS.replaceAll("[\r\n]+", ""); + String js = InAppWebView.platformReadyJS; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript(js, (ValueCallback) null); } else { - webView.loadUrl("javascript:" + js); + webView.loadUrl("javascript:" + js.replaceAll("[\r\n]+", "")); } Map obj = new HashMap<>(); @@ -251,7 +305,7 @@ public class InAppWebViewClient extends WebViewClient { } @Override - public void doUpdateVisitedHistory (WebView view, String url, boolean isReload) { + public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { Map obj = new HashMap<>(); if (inAppBrowserActivity != null) obj.put("uuid", inAppBrowserActivity.uuid); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java new file mode 100644 index 00000000..28758f7f --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java @@ -0,0 +1,448 @@ +package com.pichillilorenzo.flutter_inappwebview; + +import android.os.Build; +import android.webkit.ValueCallback; +import android.webkit.WebView; + +import androidx.annotation.NonNull; +import androidx.webkit.WebViewCompat; +import androidx.webkit.WebViewFeature; + +import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserOptions; +import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView; +import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewOptions; + +import java.util.HashMap; +import java.util.Map; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +public class InAppWebViewMethodHandler implements MethodChannel.MethodCallHandler { + static final String LOG_TAG = "IAWMethodHandler"; + + public InAppWebView webView; + + public InAppWebViewMethodHandler(InAppWebView webView) { + this.webView = webView; + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) { + switch (call.method) { + case "getUrl": + result.success((webView != null) ? webView.getUrl() : null); + break; + case "getTitle": + result.success((webView != null) ? webView.getTitle() : null); + break; + case "getProgress": + result.success((webView != null) ? webView.getProgress() : null); + break; + case "loadUrl": + if (webView != null) + webView.loadUrl((String) call.argument("url"), (Map) call.argument("headers"), result); + else + result.success(false); + break; + case "postUrl": + if (webView != null) + webView.postUrl((String) call.argument("url"), (byte[]) call.argument("postData"), result); + else + result.success(false); + break; + case "loadData": + { + String data = (String) call.argument("data"); + String mimeType = (String) call.argument("mimeType"); + String encoding = (String) call.argument("encoding"); + String baseUrl = (String) call.argument("baseUrl"); + String historyUrl = (String) call.argument("historyUrl"); + + if (webView != null) + webView.loadData(data, mimeType, encoding, baseUrl, historyUrl, result); + else + result.success(false); + } + break; + case "loadFile": + if (webView != null) + webView.loadFile((String) call.argument("url"), (Map) call.argument("headers"), result); + else + result.success(false); + break; + case "evaluateJavascript": + if (webView != null) { + String source = (String) call.argument("source"); + webView.evaluateJavascript(source, result); + } + else { + result.success(null); + } + break; + case "injectJavascriptFileFromUrl": + if (webView != null) { + String urlFile = (String) call.argument("urlFile"); + webView.injectJavascriptFileFromUrl(urlFile); + } + result.success(true); + break; + case "injectCSSCode": + if (webView != null) { + String source = (String) call.argument("source"); + webView.injectCSSCode(source); + } + result.success(true); + break; + case "injectCSSFileFromUrl": + if (webView != null) { + String urlFile = (String) call.argument("urlFile"); + webView.injectCSSFileFromUrl(urlFile); + } + result.success(true); + break; + case "reload": + if (webView != null) + webView.reload(); + result.success(true); + break; + case "goBack": + if (webView != null) + webView.goBack(); + result.success(true); + break; + case "canGoBack": + result.success((webView != null) && webView.canGoBack()); + break; + case "goForward": + if (webView != null) + webView.goForward(); + result.success(true); + break; + case "canGoForward": + result.success((webView != null) && webView.canGoForward()); + break; + case "goBackOrForward": + if (webView != null) + webView.goBackOrForward((Integer) call.argument("steps")); + result.success(true); + break; + case "canGoBackOrForward": + result.success((webView != null) && webView.canGoBackOrForward((Integer) call.argument("steps"))); + break; + case "stopLoading": + if (webView != null) + webView.stopLoading(); + result.success(true); + break; + case "isLoading": + result.success((webView != null) && webView.isLoading()); + break; + case "takeScreenshot": + if (webView != null) + webView.takeScreenshot(result); + else + result.success(null); + break; + case "setOptions": + if (webView != null && webView.inAppBrowserActivity != null) { + InAppBrowserOptions inAppBrowserOptions = new InAppBrowserOptions(); + HashMap inAppBrowserOptionsMap = (HashMap) call.argument("options"); + inAppBrowserOptions.parse(inAppBrowserOptionsMap); + webView.inAppBrowserActivity.setOptions(inAppBrowserOptions, inAppBrowserOptionsMap); + } else if (webView != null) { + InAppWebViewOptions inAppWebViewOptions = new InAppWebViewOptions(); + HashMap inAppWebViewOptionsMap = (HashMap) call.argument("options"); + inAppWebViewOptions.parse(inAppWebViewOptionsMap); + webView.setOptions(inAppWebViewOptions, inAppWebViewOptionsMap); + } + result.success(true); + break; + case "getOptions": + if (webView != null && webView.inAppBrowserActivity != null) { + result.success(webView.inAppBrowserActivity.getOptions()); + } else { + result.success((webView != null) ? webView.getOptions() : null); + } + break; + case "close": + if (webView != null && webView.inAppBrowserActivity != null) { + webView.inAppBrowserActivity.close(result); + } else { + result.notImplemented(); + } + break; + case "show": + if (webView != null && webView.inAppBrowserActivity != null) { + webView.inAppBrowserActivity.show(); + result.success(true); + } else { + result.notImplemented(); + } + break; + case "hide": + if (webView != null && webView.inAppBrowserActivity != null) { + webView.inAppBrowserActivity.hide(); + result.success(true); + } else { + result.notImplemented(); + } + break; + case "getCopyBackForwardList": + result.success((webView != null) ? webView.getCopyBackForwardList() : null); + break; + case "startSafeBrowsing": + if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && + WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) { + WebViewCompat.startSafeBrowsing(webView.getContext(), new ValueCallback() { + @Override + public void onReceiveValue(Boolean success) { + result.success(success); + } + }); + } + else { + result.success(false); + } + break; + case "clearCache": + if (webView != null) + webView.clearAllCache(); + result.success(true); + break; + case "clearSslPreferences": + if (webView != null) + webView.clearSslPreferences(); + result.success(true); + 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); + break; + case "clearMatches": + if (webView != null) { + webView.clearMatches(); + } + result.success(true); + break; + case "scrollTo": + if (webView != null) { + Integer x = (Integer) call.argument("x"); + Integer y = (Integer) call.argument("y"); + Boolean animated = (Boolean) call.argument("animated"); + webView.scrollTo(x, y, animated); + } + result.success(true); + break; + case "scrollBy": + if (webView != null) { + Integer x = (Integer) call.argument("x"); + Integer y = (Integer) call.argument("y"); + Boolean animated = (Boolean) call.argument("animated"); + webView.scrollBy(x, y, animated); + } + result.success(true); + break; + case "pause": + if (webView != null) { + webView.onPause(); + } + result.success(true); + break; + case "resume": + if (webView != null) { + webView.onResume(); + } + result.success(true); + break; + case "pauseTimers": + if (webView != null) { + webView.pauseTimers(); + } + result.success(true); + break; + case "resumeTimers": + if (webView != null) { + webView.resumeTimers(); + } + result.success(true); + break; + case "printCurrentPage": + if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + webView.printCurrentPage(); + } + result.success(true); + break; + case "getContentHeight": + result.success((webView != null) ? webView.getContentHeight() : null); + break; + case "zoomBy": + if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + double zoomFactor = (double) call.argument("zoomFactor"); + webView.zoomBy((float) zoomFactor); + } + result.success(true); + break; + case "getOriginalUrl": + result.success((webView != null) ? webView.getOriginalUrl() : null); + break; + case "getScale": + result.success((webView != null) ? webView.getUpdatedScale() : null); + break; + case "getSelectedText": + if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.getSelectedText(result); + } else { + result.success(null); + } + break; + case "getHitTestResult": + if (webView != null) { + WebView.HitTestResult hitTestResult = webView.getHitTestResult(); + Map obj = new HashMap<>(); + obj.put("type", hitTestResult.getType()); + obj.put("extra", hitTestResult.getExtra()); + result.success(obj); + } else { + result.success(null); + } + break; + case "pageDown": + if (webView != null) { + boolean bottom = (boolean) call.argument("bottom"); + result.success(webView.pageDown(bottom)); + } else { + result.success(false); + } + break; + case "pageUp": + if (webView != null) { + boolean top = (boolean) call.argument("top"); + result.success(webView.pageUp(top)); + } else { + result.success(false); + } + break; + case "saveWebArchive": + if (webView != null) { + String basename = (String) call.argument("basename"); + boolean autoname = (boolean) call.argument("autoname"); + webView.saveWebArchive(basename, autoname, new ValueCallback() { + @Override + public void onReceiveValue(String value) { + result.success(value); + } + }); + } else { + result.success(null); + } + break; + case "zoomIn": + if (webView != null) { + result.success(webView.zoomIn()); + } else { + result.success(false); + } + break; + case "zoomOut": + if (webView != null) { + result.success(webView.zoomOut()); + } else { + result.success(false); + } + break; + case "clearFocus": + if (webView != null) { + webView.clearFocus(); + } + result.success(true); + break; + case "setContextMenu": + if (webView != null) { + Map contextMenu = (Map) call.argument("contextMenu"); + webView.contextMenu = contextMenu; + } + result.success(true); + break; + case "requestFocusNodeHref": + if (webView != null) { + result.success(webView.requestFocusNodeHref()); + } else { + result.success(null); + } + break; + case "requestImageRef": + if (webView != null) { + result.success(webView.requestImageRef()); + } else { + result.success(null); + } + break; + case "getScrollX": + if (webView != null) { + result.success(webView.getScrollX()); + } else { + result.success(null); + } + break; + case "getScrollY": + if (webView != null) { + result.success(webView.getScrollY()); + } else { + result.success(null); + } + break; + case "getCertificate": + if (webView != null) { + result.success(webView.getCertificateMap()); + } else { + result.success(null); + } + break; + case "clearHistory": + if (webView != null) { + webView.clearHistory(); + } + result.success(true); + break; + case "addUserScript": + if (webView != null) { + Map userScript = (Map) call.argument("userScript"); + result.success(webView.addUserScript(userScript)); + } else { + result.success(false); + } + break; + case "removeUserScript": + if (webView != null) { + Integer index = (Integer) call.argument("index"); + result.success(webView.removeUserScript(index)); + } else { + result.success(false); + } + break; + case "removeAllUserScripts": + if (webView != null) { + webView.removeAllUserScripts(); + } + result.success(true); + break; + default: + result.notImplemented(); + } + } + + public void dispose() { + webView = null; + } +} diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index 58a63124..a289c59e 100644 --- a/example/.flutter-plugins-dependencies +++ b/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"android":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"device_info","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":["device_info"]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-01-31 21:44:40.583578","version":"1.26.0-18.0.pre.90"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"android":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"device_info","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":["device_info"]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-02-01 14:15:06.451560","version":"1.26.0-18.0.pre.90"} \ No newline at end of file diff --git a/example/lib/in_app_browser_example.screen.dart b/example/lib/in_app_browser_example.screen.dart index 69b3ea88..28469817 100755 --- a/example/lib/in_app_browser_example.screen.dart +++ b/example/lib/in_app_browser_example.screen.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; @@ -6,6 +7,9 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'main.dart'; class MyInAppBrowser extends InAppBrowser { + + MyInAppBrowser({int? windowId, UnmodifiableListView? initialUserScripts}) : super(windowId: windowId, initialUserScripts: initialUserScripts); + @override Future onBrowserCreated() async { print("\n\nBrowser Created!\n\n"); @@ -18,6 +22,7 @@ class MyInAppBrowser extends InAppBrowser { @override Future onLoadStop(url) async { + print(await this.webViewController.getTitle()); print("\n\nStopped $url\n\n"); } diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index 85df640c..4ce15a0b 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:io'; import 'package:flutter/material.dart'; diff --git a/ios/Classes/ChromeSafariBrowserManager.swift b/ios/Classes/ChromeSafariBrowserManager.swift index f079218d..92df09bd 100755 --- a/ios/Classes/ChromeSafariBrowserManager.swift +++ b/ios/Classes/ChromeSafariBrowserManager.swift @@ -41,9 +41,10 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { let optionsFallback = arguments!["optionsFallback"] as? [String: Any?] let contextMenuFallback = arguments!["contextMenuFallback"] as? [String: Any] let windowIdFallback = arguments!["windowIdFallback"] as? Int64 + let initialUserScriptsFallback = arguments!["initialUserScriptsFallback"] as? [[String: Any]] open(uuid: uuid, url: url, options: options, menuItemList: menuItemList, uuidFallback: uuidFallback, headersFallback: headersFallback, optionsFallback: optionsFallback, contextMenuFallback: contextMenuFallback, - windowIdFallback: windowIdFallback, result: result) + windowIdFallback: windowIdFallback, initialUserScriptsFallback: initialUserScriptsFallback, result: result) break case "isAvailable": if #available(iOS 9.0, *) { @@ -60,7 +61,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { public func open(uuid: String, url: String, options: [String: Any?], menuItemList: [[String: Any]], uuidFallback: String?, headersFallback: [String: String]?, optionsFallback: [String: Any?]?, contextMenuFallback: [String: Any]?, - windowIdFallback: Int64?, result: @escaping FlutterResult) { + windowIdFallback: Int64?, initialUserScriptsFallback: [[String: Any]]?, result: @escaping FlutterResult) { let absoluteUrl = URL(string: url)!.absoluteURL if #available(iOS 9.0, *) { @@ -104,7 +105,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { return } - SwiftFlutterPlugin.instance!.inAppBrowserManager!.openUrl(uuid: uuidFallback!, url: url, options: optionsFallback ?? [:], headers: headersFallback ?? [:], contextMenu: contextMenuFallback ?? [:], windowId: windowIdFallback) + SwiftFlutterPlugin.instance!.inAppBrowserManager!.openUrl(uuid: uuidFallback!, url: url, options: optionsFallback ?? [:], headers: headersFallback ?? [:], contextMenu: contextMenuFallback ?? [:], windowId: windowIdFallback, initialUserScripts: initialUserScriptsFallback) } } } diff --git a/ios/Classes/FlutterWebViewController.swift b/ios/Classes/FlutterWebViewController.swift index da31cc37..51031202 100755 --- a/ios/Classes/FlutterWebViewController.swift +++ b/ios/Classes/FlutterWebViewController.swift @@ -8,13 +8,14 @@ import Foundation import WebKit -public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatformView { +public class FlutterWebViewController: NSObject, FlutterPlatformView { private weak var registrar: FlutterPluginRegistrar? var webView: InAppWebView? var viewId: Any = 0 var channel: FlutterMethodChannel? var myView: UIView? + var methodCallDelegate: InAppWebViewMethodHandler? init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Any, arguments args: NSDictionary) { super.init() @@ -29,7 +30,6 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor channelName = "com.pichillilorenzo/flutter_inappwebview_" + id } channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger()) - channel!.setMethodCallHandler(LeakAvoider(delegate: self).handle) myView = UIView(frame: frame) @@ -40,6 +40,7 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor let initialOptions = args["initialOptions"] as! [String: Any?] let contextMenu = args["contextMenu"] as? [String: Any] let windowId = args["windowId"] as? Int64 + let initialUserScripts = args["initialUserScripts"] as? [[String: Any]] let options = InAppWebViewOptions() let _ = options.parse(options: initialOptions) @@ -54,6 +55,9 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor } else { webView = InAppWebView(frame: myView!.bounds, configuration: preWebviewConfiguration, IABController: nil, contextMenu: contextMenu, channel: channel!) } + + methodCallDelegate = InAppWebViewMethodHandler(webView: webView!) + channel!.setMethodCallHandler(LeakAvoider(delegate: methodCallDelegate!).handle) webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] myView!.autoresizesSubviews = true @@ -61,6 +65,9 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor myView!.addSubview(webView!) webView!.options = options + if let userScripts = initialUserScripts { + webView!.appendUserScripts(userScripts: userScripts) + } webView!.prepare() if windowId == nil { @@ -114,6 +121,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor deinit { print("FlutterWebViewController - dealloc") channel?.setMethodCallHandler(nil) + methodCallDelegate?.webView = nil + methodCallDelegate = nil webView?.dispose() webView = nil myView = nil @@ -145,377 +154,4 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor webView!.loadUrl(url: url, headers: initialHeaders) } } - - public override 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 "evaluateJavascript": - if webView != nil { - let source = (arguments!["source"] as? String)! - webView!.evaluateJavascript(source: source, result: result) - } - else { - result(nil) - } - break - case "injectJavascriptFileFromUrl": - if webView != nil { - let urlFile = (arguments!["urlFile"] as? String)! - webView!.injectJavascriptFileFromUrl(urlFile: urlFile) - } - result(true) - break - case "injectCSSCode": - if webView != nil { - let source = (arguments!["source"] as? String)! - webView!.injectCSSCode(source: source) - } - result(true) - break - case "injectCSSFileFromUrl": - if webView != nil { - let urlFile = (arguments!["urlFile"] as? String)! - webView!.injectCSSFileFromUrl(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] - let _ = 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: {(value, error) in - if error != nil { - result(FlutterError(code: "FlutterWebViewController", message: error?.localizedDescription, details: nil)) - return - } - 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 "scrollTo": - if webView != nil { - let x = arguments!["x"] as! Int - let y = arguments!["y"] as! Int - let animated = arguments!["animated"] as! Bool - webView!.scrollTo(x: x, y: y, animated: animated) - } - result(true) - break - case "scrollBy": - if webView != nil { - let x = arguments!["x"] as! Int - let y = arguments!["y"] as! Int - let animated = arguments!["animated"] as! Bool - webView!.scrollBy(x: x, y: y, animated: animated) - } - result(true) - break - case "pauseTimers": - if webView != nil { - webView!.pauseTimers() - } - result(true) - break - case "resumeTimers": - if webView != nil { - webView!.resumeTimers() - } - result(true) - break - case "printCurrentPage": - if webView != nil { - webView!.printCurrentPage(printCompletionHandler: {(completed, error) in - if !completed, let err = error { - print(err.localizedDescription) - result(false) - return - } - result(true) - }) - } else { - result(false) - } - break - case "getContentHeight": - result( (webView != nil) ? webView!.getContentHeight() : nil ) - break - case "reloadFromOrigin": - if webView != nil { - webView!.reloadFromOrigin() - } - result(true) - break - case "getScale": - result( (webView != nil) ? webView!.getScale() : nil ) - break - case "hasOnlySecureContent": - result( (webView != nil) ? webView!.hasOnlySecureContent : nil ) - break - case "getSelectedText": - if webView != nil { - webView!.getSelectedText { (value, error) in - if let err = error { - print(err.localizedDescription) - result("") - return - } - result(value) - } - } - else { - result(nil) - } - break - case "getHitTestResult": - if webView != nil { - webView!.getHitTestResult { (value, error) in - if let err = error { - print(err.localizedDescription) - result(nil) - return - } - result(value) - } - } - else { - result(nil) - } - break - case "clearFocus": - if webView != nil { - webView!.clearFocus() - result(true) - } else { - result(false) - } - break - case "setContextMenu": - if webView != nil { - let contextMenu = arguments!["contextMenu"] as? [String: Any] - webView!.contextMenu = contextMenu - result(true) - } else { - result(false) - } - break - case "requestFocusNodeHref": - if webView != nil { - webView!.requestFocusNodeHref { (value, error) in - if let err = error { - print(err.localizedDescription) - result(nil) - return - } - result(value) - } - } else { - result(false) - } - break - case "requestImageRef": - if webView != nil { - webView!.requestImageRef { (value, error) in - if let err = error { - print(err.localizedDescription) - result(nil) - return - } - result(value) - } - } else { - result(false) - } - break - case "getScrollX": - if webView != nil { - result(Int(webView!.scrollView.contentOffset.x)) - } else { - result(false) - } - break - case "getScrollY": - if webView != nil { - result(Int(webView!.scrollView.contentOffset.y)) - } else { - result(false) - } - break - case "getCertificate": - if webView != nil { - result(webView!.getCertificateMap()) - } else { - result(false) - } - break - default: - result(FlutterMethodNotImplemented) - break - } - } } diff --git a/ios/Classes/InAppBrowserManager.swift b/ios/Classes/InAppBrowserManager.swift index 27178e11..2d9a0df3 100755 --- a/ios/Classes/InAppBrowserManager.swift +++ b/ios/Classes/InAppBrowserManager.swift @@ -43,7 +43,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { let headers = arguments!["headers"] as! [String: String] let contextMenu = arguments!["contextMenu"] as! [String: Any] let windowId = arguments!["windowId"] as? Int64 - openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu, windowId: windowId) + let initialUserScripts = arguments!["initialUserScripts"] as? [[String: Any]] + openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu, windowId: windowId, initialUserScripts: initialUserScripts) result(true) break case "openFile": @@ -61,7 +62,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { let headers = arguments!["headers"] as! [String: String] let contextMenu = arguments!["contextMenu"] as! [String: Any] let windowId = arguments!["windowId"] as? Int64 - openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu, windowId: windowId) + let initialUserScripts = arguments!["initialUserScripts"] as? [[String: Any]] + openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu, windowId: windowId, initialUserScripts: initialUserScripts) result(true) break case "openData": @@ -73,7 +75,9 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { let baseUrl = arguments!["baseUrl"] as! String let contextMenu = arguments!["contextMenu"] as! [String: Any] let windowId = arguments!["windowId"] as? Int64 - openData(uuid: uuid, options: options, data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, contextMenu: contextMenu, windowId: windowId) + let initialUserScripts = arguments!["initialUserScripts"] as? [[String: Any]] + openData(uuid: uuid, options: options, data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, + contextMenu: contextMenu, windowId: windowId, initialUserScripts: initialUserScripts) result(true) break case "openWithSystemBrowser": @@ -118,16 +122,16 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { } public func openUrl(uuid: String, url: String, options: [String: Any?], headers: [String: String], - contextMenu: [String: Any], windowId: Int64?) { + contextMenu: [String: Any], windowId: Int64?, initialUserScripts: [[String: Any]]?) { let absoluteUrl = URL(string: url)!.absoluteURL let webViewController = prepareInAppBrowserWebViewController(options: options) webViewController.uuid = uuid - webViewController.prepareMethodChannel() webViewController.initURL = absoluteUrl webViewController.initHeaders = headers webViewController.contextMenu = contextMenu webViewController.windowId = windowId + webViewController.initUserScripts = initialUserScripts ?? [] if webViewController.isHidden { webViewController.view.isHidden = true @@ -147,17 +151,17 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { } public func openData(uuid: String, options: [String: Any?], data: String, mimeType: String, encoding: String, - baseUrl: String, contextMenu: [String: Any], windowId: Int64?) { + baseUrl: String, contextMenu: [String: Any], windowId: Int64?, initialUserScripts: [[String: Any]]?) { let webViewController = prepareInAppBrowserWebViewController(options: options) webViewController.uuid = uuid - webViewController.prepareMethodChannel() webViewController.initData = data webViewController.initMimeType = mimeType webViewController.initEncoding = encoding webViewController.initBaseUrl = baseUrl webViewController.contextMenu = contextMenu webViewController.windowId = windowId + webViewController.initUserScripts = initialUserScripts ?? [] if webViewController.isHidden { webViewController.view.isHidden = true diff --git a/ios/Classes/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowserWebViewController.swift index 565b2e1c..ee221d3f 100755 --- a/ios/Classes/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowserWebViewController.swift @@ -21,7 +21,7 @@ public class InAppWebView_IBWrapper: UIView { } } -public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIScrollViewDelegate, WKUIDelegate, UITextFieldDelegate { +public class InAppBrowserWebViewController: UIViewController, UIScrollViewDelegate, WKUIDelegate, UITextFieldDelegate { @IBOutlet var containerWebView: InAppWebView_IBWrapper! @IBOutlet var closeButton: UIButton! @@ -58,333 +58,17 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS var isHidden = false var viewPrepared = false var previousStatusBarStyle = -1 + var initUserScripts: [[String: Any]] = [] + var methodCallDelegate: InAppWebViewMethodHandler? required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! } - public static func register(with registrar: FlutterPluginRegistrar) { - - } - - public func prepareMethodChannel() { - channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser_" + uuid, binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) - SwiftFlutterPlugin.instance!.registrar!.addMethodCallDelegate(self, channel: channel!) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - let arguments = call.arguments as? NSDictionary - - switch call.method { - case "getUrl": - result(webView.url?.absoluteString) - break - case "getTitle": - result(webView.title) - break - case "getProgress": - let progress = Int(webView.estimatedProgress * 100) - result(progress) - break - case "loadUrl": - let url = arguments!["url"] as! String - let headers = arguments!["headers"] as? [String: String] - let absoluteUrl = URL(string: url)!.absoluteURL - webView.loadUrl(url: absoluteUrl, headers: headers) - result(true) - break - case "loadData": - 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) - break - case "postUrl": - let url = arguments!["url"] as! String - let postData = arguments!["postData"] as! FlutterStandardTypedData - let absoluteUrl = URL(string: url)!.absoluteURL - webView.postUrl(url: absoluteUrl, postData: postData.data, completionHandler: { () -> Void in - result(true) - }) - break - case "loadFile": - 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 { - dump(error) - result(FlutterError(code: "InAppBrowserWebViewController", message: error.localizedDescription, details: nil)) - } - break - case "close": - close() - result(true) - break - case "show": - show() - result(true) - break - case "hide": - hide() - result(true) - break - case "reload": - webView.reload() - result(true) - break - case "goBack": - webView.goBack() - result(true) - break - case "canGoBack": - result(webView.canGoBack) - break - case "goForward": - webView.goForward() - result(true) - break - case "canGoForward": - result(webView.canGoForward) - break - case "goBackOrForward": - let steps = arguments!["steps"] as! Int - webView.goBackOrForward(steps: steps) - result(true) - break - case "canGoBackOrForward": - let steps = arguments!["steps"] as! Int - result(webView.canGoBackOrForward(steps: steps)) - break - case "isLoading": - result(webView.isLoading == true) - break - case "stopLoading": - webView.stopLoading() - result(true) - break - case "isHidden": - result(isHidden == true) - break - case "evaluateJavascript": - let source = arguments!["source"] as! String - webView.evaluateJavascript(source: source, result: result) - break - case "injectJavascriptFileFromUrl": - let urlFile = arguments!["urlFile"] as! String - webView.injectJavascriptFileFromUrl(urlFile: urlFile) - result(true) - break - case "injectCSSCode": - let source = arguments!["source"] as! String - webView.injectCSSCode(source: source) - result(true) - break - case "injectCSSFileFromUrl": - let urlFile = arguments!["urlFile"] as! String - webView.injectCSSFileFromUrl(urlFile: urlFile) - result(true) - break - case "takeScreenshot": - webView.takeScreenshot(completionHandler: { (screenshot) -> Void in - result(screenshot) - }) - break - case "setOptions": - let inAppBrowserOptions = InAppBrowserOptions() - let inAppBrowserOptionsMap = arguments!["options"] as! [String: Any] - let _ = inAppBrowserOptions.parse(options: inAppBrowserOptionsMap) - self.setOptions(newOptions: inAppBrowserOptions, newOptionsMap: inAppBrowserOptionsMap) - result(true) - break - case "getOptions": - result(getOptions()) - break - case "getCopyBackForwardList": - result(webView.getCopyBackForwardList()) - break - case "findAllAsync": - let find = arguments!["find"] as! String - webView.findAllAsync(find: find, completionHandler: {(value, error) in - if error != nil { - result(FlutterError(code: "InAppBrowserWebViewController", message: error?.localizedDescription, details: nil)) - return - } - result(true) - }) - break - case "findNext": - let forward = arguments!["forward"] as! Bool - webView.findNext(forward: forward, completionHandler: {(value, error) in - if error != nil { - result(FlutterError(code: "InAppBrowserWebViewController", message: error?.localizedDescription, details: nil)) - return - } - result(true) - }) - break - case "clearMatches": - webView.clearMatches(completionHandler: {(value, error) in - if error != nil { - result(FlutterError(code: "InAppBrowserWebViewController", message: error?.localizedDescription, details: nil)) - return - } - result(true) - }) - break - case "clearCache": - webView.clearCache() - result(true) - break - case "scrollTo": - let x = arguments!["x"] as! Int - let y = arguments!["y"] as! Int - let animated = arguments!["animated"] as! Bool - webView.scrollTo(x: x, y: y, animated: animated) - result(true) - break - case "scrollBy": - let x = arguments!["x"] as! Int - let y = arguments!["y"] as! Int - let animated = arguments!["animated"] as! Bool - webView.scrollTo(x: x, y: y, animated: animated) - result(true) - break - case "pauseTimers": - webView.pauseTimers() - result(true) - break - case "resumeTimers": - webView.resumeTimers() - result(true) - break - case "printCurrentPage": - webView.printCurrentPage(printCompletionHandler: {(completed, error) in - if !completed, let _ = error { - result(false) - return - } - result(true) - }) - break - case "getContentHeight": - result(webView.getContentHeight()) - break - case "reloadFromOrigin": - webView.reloadFromOrigin() - result(true) - break - case "getScale": - result(webView.getScale()) - break - case "hasOnlySecureContent": - result(webView.hasOnlySecureContent) - break - case "getSelectedText": - if webView != nil { - webView!.getSelectedText { (value, error) in - if let err = error { - print(err.localizedDescription) - } - result(value) - } - } - else { - result(nil) - } - break - case "getHitTestResult": - if webView != nil { - webView!.getHitTestResult { (value, error) in - if let err = error { - print(err.localizedDescription) - } - result(value) - } - } - else { - result(nil) - } - break - case "clearFocus": - if webView != nil { - webView!.clearFocus() - result(true) - } else { - result(false) - } - - break - case "setContextMenu": - if webView != nil { - let contextMenu = arguments!["contextMenu"] as? [String: Any] - webView!.contextMenu = contextMenu - result(true) - } else { - result(false) - } - break - case "requestFocusNodeHref": - if webView != nil { - webView!.requestFocusNodeHref { (value, error) in - if let err = error { - print(err.localizedDescription) - result(nil) - return - } - result(value) - } - } else { - result(false) - } - break - case "requestImageRef": - if webView != nil { - webView!.requestImageRef { (value, error) in - if let err = error { - print(err.localizedDescription) - result(nil) - return - } - result(value) - } - } else { - result(false) - } - break - case "getScrollX": - if webView != nil { - result(Int(webView!.scrollView.contentOffset.x)) - } else { - result(false) - } - break - case "getScrollY": - if webView != nil { - result(Int(webView!.scrollView.contentOffset.y)) - } else { - result(false) - } - break - case "getCertificate": - if webView != nil { - result(webView!.getCertificateMap()) - } else { - result(false) - } - break - default: - result(FlutterMethodNotImplemented) - break - } - } - public override func viewWillAppear(_ animated: Bool) { if !viewPrepared { + channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser_" + uuid, binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) + let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: webViewOptions) if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { self.webView = webViewTransport.webView @@ -398,6 +82,11 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS contextMenu: contextMenu, channel: channel!) } + + methodCallDelegate = InAppWebViewMethodHandler(webView: webView!) + channel!.setMethodCallHandler(LeakAvoider(delegate: methodCallDelegate!).handle) + + self.webView.appendUserScripts(userScripts: initUserScripts) self.containerWebView.addSubview(self.webView) prepareConstraints() prepareWebView() @@ -456,6 +145,11 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS urlField.delegate = self urlField.text = self.initURL?.absoluteString + urlField.backgroundColor = .white + urlField.textColor = .black + urlField.layer.borderWidth = 1.0 + urlField.layer.borderColor = UIColor.lightGray.cgColor + urlField.layer.cornerRadius = 4 closeButton.addTarget(self, action: #selector(self.close), for: .touchUpInside) @@ -799,6 +493,8 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS onExit() channel?.setMethodCallHandler(nil) channel = nil + methodCallDelegate?.webView = nil + methodCallDelegate = nil } public func onBrowserCreated() { diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index 74729bd2..a75b0eb1 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -858,6 +858,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi // in order to have the same behavior as Android var activateShouldOverrideUrlLoading = false var contextMenu: [String: Any]? + var userScripts: [WKUserScript] = [] // https://github.com/mozilla-mobile/firefox-ios/blob/50531a7e9e4d459fb11d4fcb7d4322e08103501f/Client/Frontend/Browser/ContextMenuHelper.swift fileprivate var nativeHighlightLongPressRecognizer: UILongPressGestureRecognizer? @@ -1161,17 +1162,52 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } } - let userScript = WKUserScript(source: "window._flutter_inappwebview_windowId = \(windowId == nil ? "null" : String(windowId!));" , injectionTime: .atDocumentStart, forMainFrameOnly: false) - configuration.userContentController.addUserScript(userScript) + prepareAndAddUserScripts() if windowId != nil { // the new created window webview has the same WKWebViewConfiguration variable reference return } - configuration.userContentController = WKUserContentController() configuration.preferences = WKPreferences() + if let options = options { + if #available(iOS 9.0, *) { + configuration.allowsAirPlayForMediaPlayback = options.allowsAirPlayForMediaPlayback + configuration.allowsPictureInPictureMediaPlayback = options.allowsPictureInPictureMediaPlayback + if !options.applicationNameForUserAgent.isEmpty { + configuration.applicationNameForUserAgent = options.applicationNameForUserAgent + } + } + + configuration.preferences.javaScriptCanOpenWindowsAutomatically = options.javaScriptCanOpenWindowsAutomatically + configuration.preferences.javaScriptEnabled = options.javaScriptEnabled + configuration.preferences.minimumFontSize = CGFloat(options.minimumFontSize) + + if #available(iOS 13.0, *) { + configuration.preferences.isFraudulentWebsiteWarningEnabled = options.isFraudulentWebsiteWarningEnabled + configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: options.preferredContentMode)! + } + } + } + + public func prepareAndAddUserScripts() -> Void { + addWindowIdUserScript() + if windowId != nil { + // the new created window webview has the same WKWebViewConfiguration variable reference + return + } + configuration.userContentController = WKUserContentController() + addPluginUserScripts() + addAllUserScripts() + } + + func addWindowIdUserScript() -> Void { + let userScriptWindowId = WKUserScript(source: "window._flutter_inappwebview_windowId = \(windowId == nil ? "null" : String(windowId!));" , injectionTime: .atDocumentStart, forMainFrameOnly: false) + configuration.userContentController.addUserScript(userScriptWindowId) + } + + func addPluginUserScripts() -> Void { if let options = options { let originalViewPortMetaTagContentJSScript = WKUserScript(source: originalViewPortMetaTagContentJS, injectionTime: .atDocumentEnd, forMainFrameOnly: true) @@ -1192,14 +1228,20 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi let javaScriptBridgeJSScript = WKUserScript(source: javaScriptBridgeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) configuration.userContentController.addUserScript(javaScriptBridgeJSScript) + configuration.userContentController.removeScriptMessageHandler(forName: "callHandler") configuration.userContentController.add(self, name: "callHandler") let consoleLogJSScript = WKUserScript(source: consoleLogJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) configuration.userContentController.addUserScript(consoleLogJSScript) + configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog") configuration.userContentController.add(self, name: "consoleLog") + configuration.userContentController.removeScriptMessageHandler(forName: "consoleDebug") configuration.userContentController.add(self, name: "consoleDebug") + configuration.userContentController.removeScriptMessageHandler(forName: "consoleError") configuration.userContentController.add(self, name: "consoleError") + configuration.userContentController.removeScriptMessageHandler(forName: "consoleInfo") configuration.userContentController.add(self, name: "consoleInfo") + configuration.userContentController.removeScriptMessageHandler(forName: "consoleWarn") configuration.userContentController.add(self, name: "consoleWarn") let findElementsAtPointJSScript = WKUserScript(source: findElementsAtPointJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) @@ -1218,6 +1260,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi let findTextHighlightJSScript = WKUserScript(source: findTextHighlightJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) configuration.userContentController.addUserScript(findTextHighlightJSScript) + configuration.userContentController.removeScriptMessageHandler(forName: "onFindResultReceived") configuration.userContentController.add(self, name: "onFindResultReceived") let onWindowFocusEventJSScript = WKUserScript(source: onWindowFocusEventJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) @@ -1235,26 +1278,57 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi let interceptFetchRequestsJSScript = WKUserScript(source: interceptFetchRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) configuration.userContentController.addUserScript(interceptFetchRequestsJSScript) } - - if #available(iOS 9.0, *) { - configuration.allowsAirPlayForMediaPlayback = options.allowsAirPlayForMediaPlayback - configuration.allowsPictureInPictureMediaPlayback = options.allowsPictureInPictureMediaPlayback - if !options.applicationNameForUserAgent.isEmpty { - configuration.applicationNameForUserAgent = options.applicationNameForUserAgent - } - } - - configuration.preferences.javaScriptCanOpenWindowsAutomatically = options.javaScriptCanOpenWindowsAutomatically - configuration.preferences.javaScriptEnabled = options.javaScriptEnabled - configuration.preferences.minimumFontSize = CGFloat(options.minimumFontSize) - - if #available(iOS 13.0, *) { - configuration.preferences.isFraudulentWebsiteWarningEnabled = options.isFraudulentWebsiteWarningEnabled - configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: options.preferredContentMode)! - } } } + func addAllUserScripts() -> Void { + for userScript in userScripts { + configuration.userContentController.addUserScript(userScript) + } + } + + public func addUserScript(wkUserScript: WKUserScript) -> Void { + userScripts.append(wkUserScript) + configuration.userContentController.addUserScript(wkUserScript) + } + + public func appendUserScript(userScript: [String: Any]) -> Void { + let wkUserScript = WKUserScript(source: userScript["source"] as! String, + injectionTime: WKUserScriptInjectionTime.init(rawValue: userScript["injectionTime"] as! Int) ?? .atDocumentStart, + forMainFrameOnly: userScript["iosForMainFrameOnly"] as! Bool) + userScripts.append(wkUserScript) + } + + public func appendUserScripts(wkUserScripts: [WKUserScript]) -> Void { + for wkUserScript in wkUserScripts { + userScripts.append(wkUserScript) + } + } + + public func appendUserScripts(userScripts: [[String: Any]]) -> Void { + for userScript in userScripts { + appendUserScript(userScript: userScript) + } + } + + public func removeUserScript(at index: Int) -> Void { + userScripts.remove(at: index) + // there isn't a way to remove a specific user script using WKUserContentController, + // so we remove all the user scripts and, then, we add them again without the one that has been removed + configuration.userContentController.removeAllUserScripts() + addWindowIdUserScript() + addPluginUserScripts() + addAllUserScripts() + } + + public func removeAllUserScripts() -> Void { + userScripts.removeAll() + configuration.userContentController.removeAllUserScripts() + // add all the necessary base WKUserScripts of this plugin again + addWindowIdUserScript() + addPluginUserScripts() + } + @available(iOS 10.0, *) static public func getDataDetectorType(type: String) -> WKDataDetectorTypes { switch type { @@ -2000,7 +2074,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi InAppWebView.credentialsProposed = [] evaluateJavaScript(platformReadyJS, completionHandler: nil) onLoadStop(url: url?.absoluteString) - + if IABController != nil { IABController!.updateUrlTextField(url: currentURL?.absoluteString ?? "") IABController!.backButton.isEnabled = canGoBack @@ -2257,8 +2331,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi _ in completionHandler()} ); - let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!) - presentingViewController.present(alertController, animated: true, completion: {}) + if let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window?.rootViewController!) { + presentingViewController.present(alertController, animated: true, completion: {}) + } else { + completionHandler() + } } public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, @@ -2320,8 +2397,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi completionHandler(false) })) - let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!) - presentingViewController.present(alertController, animated: true, completion: nil) + if let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window?.rootViewController!) { + presentingViewController.present(alertController, animated: true, completion: nil) + } else { + completionHandler(false) + } } public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, @@ -2393,8 +2473,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi completionHandler(nil) })) - let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!) - presentingViewController.present(alertController, animated: true, completion: nil) + if let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window?.rootViewController!) { + presentingViewController.present(alertController, animated: true, completion: nil) + } else { + completionHandler(nil) + } } public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo, @@ -3158,6 +3241,9 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { configuration.userContentController.removeScriptMessageHandler(forName: "consoleWarn") configuration.userContentController.removeScriptMessageHandler(forName: "callHandler") configuration.userContentController.removeScriptMessageHandler(forName: "onFindResultReceived") + if #available(iOS 14.0, *) { + configuration.userContentController.removeAllScriptMessageHandlers() + } configuration.userContentController.removeAllUserScripts() if #available(iOS 11.0, *) { configuration.userContentController.removeAllContentRuleLists() diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift new file mode 100644 index 00000000..cf0b848f --- /dev/null +++ b/ios/Classes/InAppWebViewMethodHandler.swift @@ -0,0 +1,396 @@ +// +// WebViewMethodHandler.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 01/02/21. +// + +import Foundation +import WebKit + +class InAppWebViewMethodHandler: FlutterMethodCallDelegate { + var webView: InAppWebView? + + init(webView: InAppWebView) { + super.init() + self.webView = webView + } + + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? NSDictionary + + switch call.method { + case "getUrl": + result(webView?.url?.absoluteString) + break + case "getTitle": + result(webView?.title) + break + case "getProgress": + result( (webView != nil) ? Int(webView!.estimatedProgress * 100) : nil ) + break + case "loadUrl": + let url = (arguments!["url"] as? String)! + let headers = (arguments!["headers"] as? [String: String])! + webView?.loadUrl(url: URL(string: url)!, headers: headers) + result(true) + 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": + 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) + break + case "loadFile": + let url = (arguments!["url"] as? String)! + let headers = (arguments!["headers"] as? [String: String])! + + do { + try webView?.loadFile(url: url, headers: headers) + } + catch let error as NSError { + result(FlutterError(code: "InAppWebViewMethodHandler", message: error.domain, details: nil)) + return + } + result(true) + break + case "evaluateJavascript": + if webView != nil { + let source = (arguments!["source"] as? String)! + webView!.evaluateJavascript(source: source, result: result) + } + else { + result(nil) + } + break + case "injectJavascriptFileFromUrl": + let urlFile = (arguments!["urlFile"] as? String)! + webView?.injectJavascriptFileFromUrl(urlFile: urlFile) + result(true) + break + case "injectCSSCode": + let source = (arguments!["source"] as? String)! + webView?.injectCSSCode(source: source) + result(true) + break + case "injectCSSFileFromUrl": + let urlFile = (arguments!["urlFile"] as? String)! + webView?.injectCSSFileFromUrl(urlFile: urlFile) + result(true) + break + case "reload": + webView?.reload() + result(true) + break + case "goBack": + webView?.goBack() + result(true) + break + case "canGoBack": + result(webView?.canGoBack ?? false) + break + case "goForward": + webView?.goForward() + result(true) + break + case "canGoForward": + result(webView?.canGoForward ?? false) + break + case "goBackOrForward": + let steps = (arguments!["steps"] as? Int)! + webView?.goBackOrForward(steps: steps) + result(true) + break + case "canGoBackOrForward": + let steps = (arguments!["steps"] as? Int)! + result(webView?.canGoBackOrForward(steps: steps) ?? false) + break + case "stopLoading": + webView?.stopLoading() + result(true) + break + case "isLoading": + result(webView?.isLoading ?? false) + break + case "takeScreenshot": + if webView != nil { + webView!.takeScreenshot(completionHandler: { (screenshot) -> Void in + result(screenshot) + }) + } + else { + result(nil) + } + break + case "setOptions": + if let iabController = webView?.IABController { + let inAppBrowserOptions = InAppBrowserOptions() + let inAppBrowserOptionsMap = arguments!["options"] as! [String: Any] + let _ = inAppBrowserOptions.parse(options: inAppBrowserOptionsMap) + iabController.setOptions(newOptions: inAppBrowserOptions, newOptionsMap: inAppBrowserOptionsMap) + } else { + let inAppWebViewOptions = InAppWebViewOptions() + let inAppWebViewOptionsMap = arguments!["options"] as! [String: Any] + let _ = inAppWebViewOptions.parse(options: inAppWebViewOptionsMap) + webView?.setOptions(newOptions: inAppWebViewOptions, newOptionsMap: inAppWebViewOptionsMap) + } + result(true) + break + case "getOptions": + if let iabController = webView?.IABController { + result(iabController.getOptions()) + } else { + result(webView?.getOptions()) + } + break + case "close": + if let iabController = webView?.IABController { + iabController.close() + result(true) + } else { + result(FlutterMethodNotImplemented) + } + break + case "show": + if let iabController = webView?.IABController { + iabController.show() + result(true) + } else { + result(FlutterMethodNotImplemented) + } + break + case "hide": + if let iabController = webView?.IABController { + iabController.hide() + result(true) + } else { + result(FlutterMethodNotImplemented) + } + break + case "getCopyBackForwardList": + result(webView?.getCopyBackForwardList()) + break + case "findAllAsync": + if webView != nil { + let find = arguments!["find"] as! String + webView!.findAllAsync(find: find, completionHandler: {(value, error) in + if error != nil { + result(FlutterError(code: "InAppWebViewMethodHandler", message: error?.localizedDescription, details: nil)) + return + } + 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: "InAppWebViewMethodHandler", 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: "InAppWebViewMethodHandler", message: error?.localizedDescription, details: nil)) + return + } + result(true) + }) + } else { + result(false) + } + break + case "clearCache": + webView?.clearCache() + result(true) + break + case "scrollTo": + let x = arguments!["x"] as! Int + let y = arguments!["y"] as! Int + let animated = arguments!["animated"] as! Bool + webView?.scrollTo(x: x, y: y, animated: animated) + result(true) + break + case "scrollBy": + let x = arguments!["x"] as! Int + let y = arguments!["y"] as! Int + let animated = arguments!["animated"] as! Bool + webView?.scrollBy(x: x, y: y, animated: animated) + result(true) + break + case "pauseTimers": + webView?.pauseTimers() + result(true) + break + case "resumeTimers": + webView?.resumeTimers() + result(true) + break + case "printCurrentPage": + if webView != nil { + webView!.printCurrentPage(printCompletionHandler: {(completed, error) in + if !completed, let err = error { + print(err.localizedDescription) + result(false) + return + } + result(true) + }) + } else { + result(false) + } + break + case "getContentHeight": + result(webView?.getContentHeight()) + break + case "reloadFromOrigin": + webView?.reloadFromOrigin() + result(true) + break + case "getScale": + result(webView?.getScale()) + break + case "hasOnlySecureContent": + result(webView?.hasOnlySecureContent) + break + case "getSelectedText": + if webView != nil { + webView!.getSelectedText { (value, error) in + if let err = error { + print(err.localizedDescription) + result("") + return + } + result(value) + } + } + else { + result(nil) + } + break + case "getHitTestResult": + if webView != nil { + webView!.getHitTestResult { (value, error) in + if let err = error { + print(err.localizedDescription) + result(nil) + return + } + result(value) + } + } + else { + result(nil) + } + break + case "clearFocus": + webView?.clearFocus() + result(true) + break + case "setContextMenu": + if webView != nil { + let contextMenu = arguments!["contextMenu"] as? [String: Any] + webView!.contextMenu = contextMenu + result(true) + } else { + result(false) + } + break + case "requestFocusNodeHref": + if webView != nil { + webView!.requestFocusNodeHref { (value, error) in + if let err = error { + print(err.localizedDescription) + result(nil) + return + } + result(value) + } + } else { + result(nil) + } + break + case "requestImageRef": + if webView != nil { + webView!.requestImageRef { (value, error) in + if let err = error { + print(err.localizedDescription) + result(nil) + return + } + result(value) + } + } else { + result(nil) + } + break + case "getScrollX": + if webView != nil { + result(Int(webView!.scrollView.contentOffset.x)) + } else { + result(nil) + } + break + case "getScrollY": + if webView != nil { + result(Int(webView!.scrollView.contentOffset.y)) + } else { + result(nil) + } + break + case "getCertificate": + result(webView?.getCertificateMap()) + break + case "addUserScript": + let userScript = arguments!["userScript"] as! [String: Any] + let wkUserScript = WKUserScript(source: userScript["source"] as! String, + injectionTime: WKUserScriptInjectionTime.init(rawValue: userScript["injectionTime"] as! Int) ?? .atDocumentStart, + forMainFrameOnly: userScript["iosForMainFrameOnly"] as! Bool) + webView?.addUserScript(wkUserScript: wkUserScript) + result(true) + break + case "removeUserScript": + let index = arguments!["index"] as! Int + webView?.removeUserScript(at: index) + result(true) + break + case "removeAllUserScripts": + webView?.removeAllUserScripts() + result(true) + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + deinit { + print("InAppWebViewMethodHandler - dealloc") + webView = nil + } +} diff --git a/lib/src/chrome_safari_browser.dart b/lib/src/chrome_safari_browser.dart index fbbca474..c9c30308 100755 --- a/lib/src/chrome_safari_browser.dart +++ b/lib/src/chrome_safari_browser.dart @@ -90,6 +90,7 @@ class ChromeSafariBrowser { args.putIfAbsent('optionsFallback', () => optionsFallback?.toMap() ?? {}); args.putIfAbsent('contextMenuFallback', () => browserFallback?.contextMenu?.toMap() ?? {}); + args.putIfAbsent('initialUserScriptsFallback', () => browserFallback?.initialUserScripts?.map((e) => e.toMap()).toList() ?? []); await _sharedChannel.invokeMethod('open', args); this._isOpened = true; } diff --git a/lib/src/cookie_manager.dart b/lib/src/cookie_manager.dart index 012f90a5..f45375bd 100755 --- a/lib/src/cookie_manager.dart +++ b/lib/src/cookie_manager.dart @@ -72,49 +72,11 @@ class CookieManager { if (Platform.isIOS) { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); IosDeviceInfo iosInfo = await deviceInfo.iosInfo; - var version = double.tryParse(iosInfo.systemVersion); - if (version != null && version < 11.0) { - var cookieValue = name + "=" + value + "; Domain=" + domain + "; Path=" + path; - - if (expiresDate != null) - cookieValue += "; Expires=" + _getCookieExpirationDate(expiresDate); - - if (maxAge != null) - cookieValue += "; Max-Age=" + maxAge.toString(); - - if (isSecure != null && isSecure) - cookieValue += "; Secure"; - - if (sameSite != null) - cookieValue += "; SameSite=" + sameSite.toValue(); - - cookieValue += ";"; - - if (iosBelow11WebViewController != null) { - InAppWebViewGroupOptions? options = await iosBelow11WebViewController.getOptions(); - if (options != null && options.crossPlatform != null && - options.crossPlatform!.javaScriptEnabled == true) { - await iosBelow11WebViewController.evaluateJavascript( - source: 'document.cookie="$cookieValue"'); - return; - } - } - - var setCookieCompleter = Completer(); - var headlessWebView = new HeadlessInAppWebView( - initialUrl: url, - onLoadStop: (controller, url) async { - await controller.evaluateJavascript( - source: 'document.cookie="$cookieValue"'); - setCookieCompleter.complete(); - }, - ); - await headlessWebView.run(); - await setCookieCompleter.future; - await headlessWebView.dispose(); - + await _setCookieWithJavaScript(url: url, name: name, value: value, domain: domain, + path: path, expiresDate: expiresDate, maxAge: maxAge, isSecure: isSecure, + sameSite: sameSite, webViewController: iosBelow11WebViewController); return; } } @@ -134,6 +96,57 @@ class CookieManager { await _channel.invokeMethod('setCookie', args); } + Future _setCookieWithJavaScript( + {required String url, + required String name, + required String value, + required String domain, + String path = "/", + int? expiresDate, + int? maxAge, + bool? isSecure, + HTTPCookieSameSitePolicy? sameSite, + InAppWebViewController? webViewController}) async { + var cookieValue = name + "=" + value + "; Domain=" + domain + "; Path=" + path; + + if (expiresDate != null) + cookieValue += "; Expires=" + _getCookieExpirationDate(expiresDate); + + if (maxAge != null) + cookieValue += "; Max-Age=" + maxAge.toString(); + + if (isSecure != null && isSecure) + cookieValue += "; Secure"; + + if (sameSite != null) + cookieValue += "; SameSite=" + sameSite.toValue(); + + cookieValue += ";"; + + if (webViewController != null) { + InAppWebViewGroupOptions? options = await webViewController.getOptions(); + if (options != null && options.crossPlatform != null && + options.crossPlatform!.javaScriptEnabled) { + await webViewController.evaluateJavascript( + source: 'document.cookie="$cookieValue"'); + return; + } + } + + var setCookieCompleter = Completer(); + var headlessWebView = new HeadlessInAppWebView( + initialUrl: url, + onLoadStop: (controller, url) async { + await controller.evaluateJavascript( + source: 'document.cookie="$cookieValue"'); + setCookieCompleter.complete(); + }, + ); + await headlessWebView.run(); + await setCookieCompleter.future; + await headlessWebView.dispose(); + } + ///Gets all the cookies for the given [url]. /// ///[iosBelow11WebViewController] is used for getting the cookies (also session-only cookies) using JavaScript (cookies with `isHttpOnly` enabled cannot be found, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) @@ -146,59 +159,17 @@ class CookieManager { Future> getCookies({required String url, InAppWebViewController? iosBelow11WebViewController}) async { assert(url.isNotEmpty); - List cookies = []; - if (Platform.isIOS) { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); IosDeviceInfo iosInfo = await deviceInfo.iosInfo; - var version = double.tryParse(iosInfo.systemVersion); - if (version != null && version < 11.0) { - - if (iosBelow11WebViewController != null) { - InAppWebViewGroupOptions? options = await iosBelow11WebViewController.getOptions(); - if (options != null && options.crossPlatform != null && - options.crossPlatform!.javaScriptEnabled == true) { - List documentCookies = (await iosBelow11WebViewController.evaluateJavascript(source: 'document.cookie') as String) - .split(';').map((documentCookie) => documentCookie.trim()).toList(); - documentCookies.forEach((documentCookie) { - List cookie = documentCookie.split('='); - cookies.add(Cookie( - name: cookie[0], - value: cookie[1], - ) - ); - }); - return cookies; - } - } - - var pageLoaded = Completer(); - var headlessWebView = new HeadlessInAppWebView( - initialUrl: url, - onLoadStop: (controller, url) async { - pageLoaded.complete(); - }, - ); - await headlessWebView.run(); - await pageLoaded.future; - - List documentCookies = (await headlessWebView.webViewController.evaluateJavascript(source: 'document.cookie') as String) - .split(';').map((documentCookie) => documentCookie.trim()).toList(); - documentCookies.forEach((documentCookie) { - List cookie = documentCookie.split('='); - cookies.add(Cookie( - name: cookie[0], - value: cookie[1], - ) - ); - }); - await headlessWebView.dispose(); - return cookies; + return await _getCookiesWithJavaScript(url: url, webViewController: iosBelow11WebViewController); } } + List cookies = []; + Map args = {}; args.putIfAbsent('url', () => url); List cookieListMap = @@ -221,6 +192,53 @@ class CookieManager { return cookies; } + Future> _getCookiesWithJavaScript({required String url, InAppWebViewController? webViewController}) async { + assert(url.isNotEmpty); + + List cookies = []; + + if (webViewController != null) { + InAppWebViewGroupOptions? options = await webViewController.getOptions(); + if (options != null && options.crossPlatform != null && + options.crossPlatform!.javaScriptEnabled) { + List documentCookies = (await webViewController.evaluateJavascript(source: 'document.cookie') as String) + .split(';').map((documentCookie) => documentCookie.trim()).toList(); + documentCookies.forEach((documentCookie) { + List cookie = documentCookie.split('='); + cookies.add(Cookie( + name: cookie[0], + value: cookie[1], + ) + ); + }); + return cookies; + } + } + + var pageLoaded = Completer(); + var headlessWebView = new HeadlessInAppWebView( + initialUrl: url, + onLoadStop: (controller, url) async { + pageLoaded.complete(); + }, + ); + await headlessWebView.run(); + await pageLoaded.future; + + List documentCookies = (await headlessWebView.webViewController.evaluateJavascript(source: 'document.cookie') as String) + .split(';').map((documentCookie) => documentCookie.trim()).toList(); + documentCookies.forEach((documentCookie) { + List cookie = documentCookie.split('='); + cookies.add(Cookie( + name: cookie[0], + value: cookie[1], + ) + ); + }); + await headlessWebView.dispose(); + return cookies; + } + ///Gets a cookie by its [name] for the given [url]. /// ///[iosBelow11WebViewController] is used for getting the cookie (also session-only cookie) using JavaScript (cookie with `isHttpOnly` enabled cannot be found, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) @@ -239,50 +257,10 @@ class CookieManager { if (Platform.isIOS) { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); IosDeviceInfo iosInfo = await deviceInfo.iosInfo; - var version = double.tryParse(iosInfo.systemVersion); - if (version != null && version < 11.0) { - - if (iosBelow11WebViewController != null) { - InAppWebViewGroupOptions? options = await iosBelow11WebViewController.getOptions(); - if (options != null && options.crossPlatform != null && - options.crossPlatform!.javaScriptEnabled == true) { - List documentCookies = (await iosBelow11WebViewController.evaluateJavascript(source: 'document.cookie') as String) - .split(';').map((documentCookie) => documentCookie.trim()).toList(); - for (var i = 0; i < documentCookies.length; i++) { - List cookie = documentCookies[i].split('='); - if (cookie[0] == name) - return Cookie( - name: cookie[0], - value: cookie[1]); - } - return null; - } - } - - var pageLoaded = Completer(); - var headlessWebView = new HeadlessInAppWebView( - initialUrl: url, - onLoadStop: (controller, url) async { - pageLoaded.complete(); - }, - ); - await headlessWebView.run(); - await pageLoaded.future; - - List documentCookies = (await headlessWebView.webViewController.evaluateJavascript(source: 'document.cookie') as String) - .split(';').map((documentCookie) => documentCookie.trim()).toList(); - await headlessWebView.dispose(); - - for (var i = 0; i < documentCookies.length; i++) { - List cookie = documentCookies[i].split('='); - if (cookie[0] == name) - return Cookie( - name: cookie[0], - value: cookie[1]); - } - return null; + List cookies = await _getCookiesWithJavaScript(url: url, webViewController: iosBelow11WebViewController); + return cookies.cast().firstWhere((cookie) => cookie!.name == name, orElse: () => null); } } @@ -335,7 +313,7 @@ class CookieManager { IosDeviceInfo iosInfo = await deviceInfo.iosInfo; var version = double.tryParse(iosInfo.systemVersion); if (version != null && version < 11.0) { - await setCookie(url: url, name: name, value: "", path: path, domain: domain, maxAge: -1, iosBelow11WebViewController: iosBelow11WebViewController); + await _setCookieWithJavaScript(url: url, name: name, value: "", path: path, domain: domain, maxAge: -1, webViewController: iosBelow11WebViewController); return; } } @@ -371,9 +349,9 @@ class CookieManager { IosDeviceInfo iosInfo = await deviceInfo.iosInfo; var version = double.tryParse(iosInfo.systemVersion); if (version != null && version < 11.0) { - List cookies = await getCookies(url: url, iosBelow11WebViewController: iosBelow11WebViewController); + List cookies = await _getCookiesWithJavaScript(url: url, webViewController: iosBelow11WebViewController); for (var i = 0; i < cookies.length; i++) { - await setCookie(url: url, name: cookies[i].name, value: "", path: path, domain: domain, maxAge: -1, iosBelow11WebViewController: iosBelow11WebViewController); + await _setCookieWithJavaScript(url: url, name: cookies[i].name, value: "", path: path, domain: domain, maxAge: -1, webViewController: iosBelow11WebViewController); } return; } diff --git a/lib/src/headless_in_app_webview.dart b/lib/src/headless_in_app_webview.dart index d6aed9b1..c1b85b73 100644 --- a/lib/src/headless_in_app_webview.dart +++ b/lib/src/headless_in_app_webview.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:typed_data'; import 'package:flutter/services.dart'; @@ -81,7 +82,8 @@ class HeadlessInAppWebView implements WebView { this.initialData, this.initialHeaders, this.initialOptions, - this.contextMenu}) { + this.contextMenu, + this.initialUserScripts}) { uuid = uuidGenerator.v4(); webViewController = new InAppWebViewController(uuid, this); } @@ -115,7 +117,8 @@ class HeadlessInAppWebView implements WebView { 'initialHeaders': this.initialHeaders, 'initialOptions': this.initialOptions?.toMap() ?? {}, 'contextMenu': this.contextMenu?.toMap() ?? {}, - 'windowId': this.windowId + 'windowId': this.windowId, + 'initialUserScripts': this.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], }); await _sharedChannel.invokeMethod('createHeadlessWebView', args); } @@ -168,6 +171,9 @@ class HeadlessInAppWebView implements WebView { @override final String? initialUrl; + @override + final UnmodifiableListView? initialUserScripts; + @override final void Function(InAppWebViewController controller, String? url)? onPageCommitVisible; diff --git a/lib/src/in_app_browser.dart b/lib/src/in_app_browser.dart index 31951212..5433e8b0 100755 --- a/lib/src/in_app_browser.dart +++ b/lib/src/in_app_browser.dart @@ -19,8 +19,9 @@ class InAppBrowser { ///Context menu used by the browser. It should be set before opening the browser. ContextMenu? contextMenu; - Map javaScriptHandlersMap = - HashMap(); + ///Initial list of user scripts to be loaded at start or end of a page loading. + UnmodifiableListView? initialUserScripts; + bool _isOpened = false; late MethodChannel _channel; static const MethodChannel _sharedChannel = @@ -33,14 +34,14 @@ class InAppBrowser { final int? windowId; /// - InAppBrowser({this.windowId}) { + InAppBrowser({this.windowId, this.initialUserScripts}) { uuid = uuidGenerator.v4(); this._channel = MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$uuid'); this._channel.setMethodCallHandler(handleMethod); _isOpened = false; webViewController = - new InAppWebViewController.fromInAppBrowser(uuid, this._channel, this); + new InAppWebViewController.fromInAppBrowser(uuid, this._channel, this, this.initialUserScripts); } Future handleMethod(MethodCall call) async { @@ -79,6 +80,7 @@ class InAppBrowser { args.putIfAbsent('options', () => options?.toMap() ?? {}); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); args.putIfAbsent('windowId', () => windowId); + args.putIfAbsent('initialUserScripts', () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); await _sharedChannel.invokeMethod('openUrl', args); } @@ -129,6 +131,7 @@ class InAppBrowser { args.putIfAbsent('options', () => options?.toMap() ?? {}); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); args.putIfAbsent('windowId', () => windowId); + args.putIfAbsent('initialUserScripts', () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); await _sharedChannel.invokeMethod('openFile', args); } @@ -158,6 +161,7 @@ class InAppBrowser { args.putIfAbsent('historyUrl', () => androidHistoryUrl); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); args.putIfAbsent('windowId', () => windowId); + args.putIfAbsent('initialUserScripts', () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); await _sharedChannel.invokeMethod('openData', args); } diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart index 5013d405..93912144 100755 --- a/lib/src/in_app_webview.dart +++ b/lib/src/in_app_webview.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:collection'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; @@ -35,6 +36,7 @@ class InAppWebView extends StatefulWidget implements WebView { this.initialData, this.initialHeaders = const {}, this.initialOptions, + this.initialUserScripts, this.contextMenu, this.onWebViewCreated, this.onLoadStart, @@ -127,6 +129,9 @@ class InAppWebView extends StatefulWidget implements WebView { @override final String? initialUrl; + @override + final UnmodifiableListView? initialUserScripts; + @override final ContextMenu? contextMenu; @@ -365,7 +370,8 @@ class _InAppWebViewState extends State { 'initialHeaders': widget.initialHeaders, 'initialOptions': widget.initialOptions?.toMap() ?? {}, 'contextMenu': widget.contextMenu?.toMap() ?? {}, - 'windowId': widget.windowId + 'windowId': widget.windowId, + 'initialUserScripts': widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], }, creationParamsCodec: const StandardMessageCodec(), ) @@ -387,7 +393,8 @@ class _InAppWebViewState extends State { 'initialHeaders': widget.initialHeaders, 'initialOptions': widget.initialOptions?.toMap() ?? {}, 'contextMenu': widget.contextMenu?.toMap() ?? {}, - 'windowId': widget.windowId + 'windowId': widget.windowId, + 'initialUserScripts': widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], }, creationParamsCodec: const StandardMessageCodec(), ); @@ -404,7 +411,8 @@ class _InAppWebViewState extends State { 'initialHeaders': widget.initialHeaders, 'initialOptions': widget.initialOptions?.toMap() ?? {}, 'contextMenu': widget.contextMenu?.toMap() ?? {}, - 'windowId': widget.windowId + 'windowId': widget.windowId, + 'initialUserScripts': widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], }, creationParamsCodec: const StandardMessageCodec(), ); diff --git a/lib/src/in_app_webview_controller.dart b/lib/src/in_app_webview_controller.dart index eaa749e0..f488324d 100644 --- a/lib/src/in_app_webview_controller.dart +++ b/lib/src/in_app_webview_controller.dart @@ -45,6 +45,7 @@ class InAppWebViewController { MethodChannel('com.pichillilorenzo/flutter_inappwebview_static'); Map javaScriptHandlersMap = HashMap(); + List _userScripts = []; // ignore: unused_field bool _isOpened = false; @@ -72,14 +73,16 @@ class InAppWebViewController { MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); this._channel.setMethodCallHandler(handleMethod); this._webview = webview; + this._userScripts = List.from(webview.initialUserScripts ?? []); this._init(); } InAppWebViewController.fromInAppBrowser( - String uuid, MethodChannel channel, InAppBrowser inAppBrowser) { + String uuid, MethodChannel channel, InAppBrowser inAppBrowser, UnmodifiableListView? initialUserScripts) { this._inAppBrowserUuid = uuid; this._channel = channel; this._inAppBrowser = inAppBrowser; + this._userScripts = List.from(initialUserScripts ?? []); this._init(); } @@ -1972,6 +1975,59 @@ class InAppWebViewController { return null; } + ///Injects the specified [userScript] into the webpage’s content. + /// + ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537448-adduserscript + Future addUserScript(UserScript userScript) async { + Map args = {}; + args.putIfAbsent('userScript', () => userScript.toMap()); + if (!_userScripts.contains(userScript)) { + _userScripts.add(userScript); + await _channel.invokeMethod('addUserScript', args); + } + } + + ///Injects the [userScripts] into the webpage’s content. + Future addUserScripts(List userScripts) async { + for (var i = 0; i < userScripts.length; i++) { + await addUserScript(userScripts[i]); + } + } + + ///Removes the specified [userScript] from the webpage’s content. + ///User scripts already loaded into the webpage's content cannot be removed. This will have effect only on the next page load. + ///Returns `true` if [userScript] was in the list, `false` otherwise. + Future removeUserScript(UserScript userScript) async { + var index = _userScripts.indexOf(userScript); + if (index == -1) { + return false; + } + + _userScripts.remove(userScript); + Map args = {}; + args.putIfAbsent('index', () => index); + await _channel.invokeMethod('removeUserScript', args); + + return true; + } + + ///Removes the [userScripts] from the webpage’s content. + ///User scripts already loaded into the webpage's content cannot be removed. This will have effect only on the next page load. + Future removeUserScripts(List userScripts) async { + for (var i = 0; i < userScripts.length; i++) { + await removeUserScript(userScripts[i]); + } + } + + ///Removes all the user scripts from the webpage’s content. + /// + ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1536540-removealluserscripts + Future removeAllUserScripts() async { + _userScripts.clear(); + Map args = {}; + await _channel.invokeMethod('removeAllUserScripts', args); + } + ///Gets the default user agent. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebSettings#getDefaultUserAgent(android.content.Context) diff --git a/lib/src/types.dart b/lib/src/types.dart index fc4d3a1f..de0221af 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -4503,3 +4503,86 @@ class LoginRequest { return toMap().toString(); } } + +///Class that represents contains the constants for the times at which to inject script content into a [WebView] used by an [UserScript]. +class UserScriptInjectionTime { + final int _value; + + const UserScriptInjectionTime._internal(this._value); + + static final Set values = [ + UserScriptInjectionTime.AT_DOCUMENT_START, + UserScriptInjectionTime.AT_DOCUMENT_END, + ].toSet(); + + static UserScriptInjectionTime? fromValue(int? value) { + if (value != null) { + try { + return UserScriptInjectionTime.values.firstWhere( + (element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + int toValue() => _value; + + @override + String toString() { + switch (_value) { + case 1: + return "AT_DOCUMENT_END"; + case 0: + default: + return "AT_DOCUMENT_START"; + } + } + + ///**NOTE for iOS**: A constant to inject the script after the creation of the webpage’s document element, but before loading any other content. + /// + ///**NOTE for Android**: A constant to try to inject the script as soon as the page starts loading. + static const AT_DOCUMENT_START = + const UserScriptInjectionTime._internal(0); + + ///**NOTE for iOS**: A constant to inject the script after the document finishes loading, but before loading any other subresources. + /// + ///**NOTE for Android**: A constant to inject the script as soon as the page finishes loading. + static const AT_DOCUMENT_END = + const UserScriptInjectionTime._internal(1); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} + +///Class that represents a script that the [WebView] injects into the web page. +class UserScript { + ///The script’s source code. + String source; + + ///The time at which to inject the script into the [WebView]. + UserScriptInjectionTime injectionTime; + + ///A Boolean value that indicates whether to inject the script into the main frame. + ///Specify true to inject the script only into the main frame, or false to inject it into all frames. + ///The default value is `true`. Available only on iOS. + bool iosForMainFrameOnly; + + UserScript({required this.source, required this.injectionTime, this.iosForMainFrameOnly = true}); + + Map toMap() { + return {"source": source, "injectionTime": injectionTime.toValue(), "iosForMainFrameOnly": iosForMainFrameOnly}; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} \ No newline at end of file diff --git a/lib/src/webview.dart b/lib/src/webview.dart index a1b7c9b9..0fe96262 100644 --- a/lib/src/webview.dart +++ b/lib/src/webview.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:typed_data'; import 'context_menu.dart'; @@ -621,6 +622,11 @@ abstract class WebView { ///Context menu which contains custom menu items to be shown when [ContextMenu] is presented. final ContextMenu? contextMenu; + ///Initial list of user scripts to be loaded at start or end of a page loading. + ///To add or remove user scripts, you have to use the [InAppWebViewController]'s methods such as [InAppWebViewController.addUserScript], + ///[InAppWebViewController.removeUserScript], [InAppWebViewController.removeAllUserScripts], etc. + final UnmodifiableListView? initialUserScripts; + WebView( {this.windowId, this.onWebViewCreated, @@ -679,5 +685,6 @@ abstract class WebView { this.initialData, this.initialHeaders, this.initialOptions, - this.contextMenu}); + this.contextMenu, + this.initialUserScripts}); }