From b9fb01f177f24581ee10a1ca20cc65d4bd5aed33 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Mon, 16 Dec 2019 23:58:10 +0100 Subject: [PATCH] fix #225, merged #228, updated ios options naming, updated default value for databaseEnabled android option, added new methods and events --- .idea/workspace.xml | 712 ++++++++++-------- CHANGELOG.md | 28 +- README.md | 88 ++- .../flutter_inappwebview/FlutterWebView.java | 18 + .../flutter_inappwebview/InAppBrowser.java | 47 +- .../InAppBrowserActivity.java | 23 + .../InAppWebView/InAppWebView.java | 65 +- .../InAppWebViewChromeClient.java | 13 +- .../InAppWebView/InAppWebViewClient.java | 19 +- .../InAppWebViewFlutterPlugin.java | 6 + .../JavaScriptBridgeInterface.java | 45 -- .../flutter_inappwebview/MyWebStorage.java | 104 +++ example/ios/Flutter/Flutter.podspec | 18 + .../chrome_safari_browser_example.screen.dart | 4 +- .../lib/in_app_browser_example.screen.dart | 2 +- example/lib/in_app_webiew_example.screen.dart | 39 +- .../test_driver/in_app_webview_ajax_test.dart | 2 +- .../in_app_webview_content_blocker_test.dart | 2 +- .../in_app_webview_cookie_manager_test.dart | 2 +- .../in_app_webview_fetch_test.dart | 2 +- ...ew_http_auth_credential_database_test.dart | 2 +- .../in_app_webview_initial_data_test.dart | 2 +- .../in_app_webview_initial_file_test.dart | 2 +- .../in_app_webview_initial_url_test.dart | 2 +- ...n_app_webview_javascript_handler_test.dart | 2 +- ...n_app_webview_on_console_message_test.dart | 2 +- .../in_app_webview_on_create_window_test.dart | 6 +- ...in_app_webview_on_download_start_test.dart | 2 +- ..._webview_on_find_result_received_test.dart | 2 +- .../in_app_webview_on_js_dialog_test.dart | 2 +- .../in_app_webview_on_load_error_test.dart | 2 +- ...n_app_webview_on_load_http_error_test.dart | 2 +- ...w_on_load_resource_custom_scheme_test.dart | 2 +- .../in_app_webview_on_load_resource_test.dart | 2 +- ...bview_on_navigation_state_change_test.dart | 2 +- ..._app_webview_on_progress_changed_test.dart | 2 +- ...ew_on_received_http_auth_request_test.dart | 2 +- ...app_webview_on_safe_browsing_hit_test.dart | 8 +- ...in_app_webview_on_scroll_changed_test.dart | 2 +- ...view_should_override_url_loading_test.dart | 2 +- .../in_app_webview_ssl_request_test.dart | 2 +- flutter_inappwebview.iml | 1 + ios/Classes/FlutterMethodCallDelegate.swift | 18 + ios/Classes/FlutterWebViewController.swift | 52 +- .../InAppBrowserWebViewController.swift | 17 +- ios/Classes/InAppWebView.swift | 162 ++-- ios/Classes/InAppWebViewOptions.swift | 1 + ios/Classes/LeakAvoider.swift | 25 + ios/Classes/MyWebStorageManager.swift | 93 +++ ios/Classes/SwiftFlutterPlugin.swift | 121 ++- ios/Classes/TestWebView.swift | 30 + lib/flutter_inappwebview.dart | 1 + lib/src/chrome_safari_browser.dart | 16 +- lib/src/in_app_browser.dart | 144 ++-- lib/src/in_app_webview.dart | 477 +++++++----- lib/src/types.dart | 334 +++++--- lib/src/web_storage_manager.dart | 159 ++++ lib/src/webview_options.dart | 99 +-- 58 files changed, 2022 insertions(+), 1019 deletions(-) create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyWebStorage.java create mode 100644 example/ios/Flutter/Flutter.podspec create mode 100644 ios/Classes/FlutterMethodCallDelegate.swift create mode 100644 ios/Classes/LeakAvoider.swift create mode 100644 ios/Classes/MyWebStorageManager.swift create mode 100644 ios/Classes/TestWebView.swift create mode 100644 lib/src/web_storage_manager.dart diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 9ce47bd8..d8bec2b6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -16,61 +16,120 @@ + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - @@ -91,43 +150,10 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -136,10 +162,34 @@ - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -148,19 +198,48 @@ - + - - + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -177,36 +256,36 @@ + Events + OnCreateWindow + onGeolocationPermissions + androidOn + ios + iosWl onPrint - ontARGET - onCreateWindow - javaScriptHandlerForbiddenNames - ajaxRequest - onTarget - AjaxR - print - shouldOver - ShouldOverrideUrlLoadingAction - headers - domStorageEnabled - fromValue - shouldOv - shouldOverrideUrlLoading - supportMultipleWindows - ThreadedInputConnection - ThreadedInputConnecti - InputAwareWebView - \n - debuggingEnabled - _inappweb - \n" + - This workaround is applied as soon as the web page fires the `DOMContentLoaded` JavaScript event. dispose - InAppWebViewOnCreateWindowTest - InAppWebViewOnTarget - InAppWebViewOnReceivedHttpAuthRequestTest - dropDownWorkaroundEnabled - mContext + androidhistor + InAppWebViewWidgetOptions + allowsInlineMediaPlayback + onnav + onUpdateVisitedhi + NavigationStateChange + getUrl + databaseA + AndroidInAppWebViewController + Ios + **NOTE + available on ios + final String _value; + "WKWebsiteDataTypeIndexedDBDatabases" + CookieMan + CookieManager + IOS + automaticallyAdjustsScrollIndicatorInsets + assert + Android-specific methods + getScale + onUpdateVisitedHistory activity.getPreferences(0) @@ -230,8 +309,6 @@ - + @@ -517,7 +590,7 @@ - + @@ -528,18 +601,18 @@ - + - - + + - + @@ -567,43 +640,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -614,16 +650,6 @@ - - - - - - - - - - @@ -634,16 +660,6 @@ - - - - - - - - - - @@ -693,15 +709,6 @@ - - - - - - - - - @@ -715,16 +722,6 @@ - - - - - - - - - - @@ -798,66 +795,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -872,34 +809,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -914,16 +823,6 @@ - - - - - - - - - - @@ -931,6 +830,16 @@ + + + + + + + + + + @@ -941,9 +850,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -951,10 +919,116 @@ + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 76477bc5..738b3d75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,24 @@ ## 3.0.0 -- Updated for Flutter 1.12 new Java Embedding API (Android) -- Updated `clearCache` for Android - Added `Promise` javascript [polyfill](https://github.com/taylorhakes/promise-polyfill/blob/master/src/index.js) for webviews that doesn't support it for `window.flutter_inappwebview.callHandler` - Added `getDefaultUserAgent` static method to `InAppWebViewController` -- Added `printCurrentPage` method to `InAppWebViewController` -- Added `onPrint` event +- Added `onUpdateVisitedHistory`, `onPrint` event +- Added `onGeolocationPermissionsHidePrompt` event for Android - Added `supportMultipleWindows` webview option for Android - Added `regexToCancelSubFramesLoading` webview option for Android to cancel subframe requests on `shouldOverrideUrlLoading` event based on a Regular Expression -- Updated default value for `domStorageEnabled` option to `true` for Android -- Fix for Android `InAppBrowser` for some controller methods not exposed. +- Added `getContentHeight`, `zoomBy`, `printCurrentPage`, `getScale` methods +- Added `getOriginalUrl` webview method for Android +- Added `reloadFromOrigin` webview method for iOS +- Added `automaticallyAdjustsScrollIndicatorInsets` webview options for iOS +- Added `WebStorageManager` class which manages the web storage used by WebView instances +- Updated for Flutter 1.12 new Java Embedding API (Android) +- Updated `clearCache` for Android +- Updated default value for `domStorageEnabled` and `databaseEnabled` options to `true` for Android - Merge "Fixes null error when calling getOptions for InAppBrowser class" [#214](https://github.com/pichillilorenzo/flutter_inappwebview/pull/214) (thanks to [panndoraBoo](https://github.com/panndoraBoo)) -- Added `dropDownWorkaroundEnabled` webview option for Android to enable a temporary workaround for html dropdowns (issue [#182](https://github.com/pichillilorenzo/flutter_inappwebview/issues/182)) +- Merge "Fixes crash onConsoleMessage iOS forced unwrapping" [#228](https://github.com/pichillilorenzo/flutter_inappwebview/pull/228) (thanks to [tokonu](https://github.com/tokonu)) +- Fix for Android and iOS `InAppBrowser` for some controller methods not exposed. - Fixed "App Crashes after clicking on dropdown (Using inappwebview)" [#182](https://github.com/pichillilorenzo/flutter_inappwebview/issues/182) +- Fixed "webview can not be released when in ios" [#225](https://github.com/pichillilorenzo/flutter_inappwebview/issues/225). Now the iOS WebView is released from memory when it is disposed from Flutter. ### BREAKING CHANGES @@ -21,6 +27,14 @@ - it has a return type `ShouldOverrideUrlLoadingAction` to allow or cancel navigation instead of cancel every time the request - Renamed `onTargetBlank` to `onCreateWindow` - Deleted `useOnTargetBlank` webview option +- Making methods available only for the specific platform more explicit: moved all the webview's controller methods for Android inside `controller.android` and all the webview's controller methods for iOS inside `controller.ios` +- Making events available only for the specific platform more explicit: + - Renamed `onSafeBrowsingHit` to `androidOnSafeBrowsingHit` + - Renamed `onGeolocationPermissionsShowPrompt` to `androidOnGeolocationPermissionsShowPrompt` + - Renamed `onPermissionRequest` to `androidOnPermissionRequest` +- Updated attribute names for `InAppWebViewWidgetOptions`, `InAppBrowserClassOptions` and `ChromeSafariBrowserClassOptions` classes +- Renamed and updated `onNavigationStateChange` to `onUpdateVisitedHistory` +- Renamed all iOS options prefix from `Ios` to `IOS` ## 2.1.0+1 diff --git a/README.md b/README.md index 41d537a5..72520f80 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Classes: - [InAppLocalhostServer](#inapplocalhostserver-class): This class allows you to create a simple server on `http://localhost:[port]/`. The default `port` value is `8080`. - [CookieManager](#cookiemanager-class): This class implements a singleton object (shared instance) which manages the cookies used by WebView instances. **NOTE for iOS**: available from iOS 11.0+. - [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 @@ -176,7 +177,7 @@ class _MyAppState extends State { initialUrl: "https://flutter.dev/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( debuggingEnabled: true, ) ), @@ -248,6 +249,8 @@ Screenshots: #### `InAppWebViewController` Methods +##### `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. @@ -255,7 +258,7 @@ Screenshots: * `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 historyUrl = "about:blank"})`: Loads the given data 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. @@ -279,12 +282,7 @@ Screenshots: * `setOptions({@required InAppWebViewWidgetOptions 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. -* `startSafeBrowsing`: Starts Safe Browsing initialization (available only on Android). -* `setSafeBrowsingWhitelist({@required List hosts})`: Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks. The list is global for all the WebViews (available only on Android). -* `getSafeBrowsingPrivacyPolicyUrl`: Returns a URL pointing to the privacy policy for Safe Browsing reporting. This value will never be `null`. * `clearCache`: Clears all the webview's cache. -* `clearSslPreferences`: Clears the SSL preferences table stored in response to proceeding with SSL certificate errors (available only on Android). -* `clearClientCertPreferences`: Clears the client certificate preferences stored in response to proceeding/cancelling client cert requests (available only on Android). * `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()`. @@ -292,9 +290,31 @@ Screenshots: * `getTRexRunnerCss`: Gets the css of the Chromium's t-rex runner game. Used in combination with `getTRexRunnerHtml()`. * `scrollTo({@required int x, @required int y})`: Scrolls the WebView to the position. * `scrollBy({@required int x, @required int y})`: 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. * `static getDefaultUserAgent`: Gets the default user agent. +##### `InAppWebViewController` Android-specific methods + +Android-specific methods can be called using the `InAppWebViewController.android` attribute. + +* `startSafeBrowsing`: Starts Safe Browsing initialization. +* `setSafeBrowsingWhitelist({@required List hosts})`: Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks. The list is global for all the WebViews. +* `getSafeBrowsingPrivacyPolicyUrl`: Returns a URL pointing to the privacy policy for Safe Browsing reporting. This value will never be `null`. +* `clearSslPreferences`: Clears the SSL preferences table stored in response to proceeding with SSL certificate errors. +* `clearClientCertPreferences`: Clears the client certificate preferences stored in response to proceeding/cancelling client cert requests. +* `pause`: Does a best-effort attempt to pause any processing that can be paused safely, such as animations and geolocation. Note that this call does not pause JavaScript. +* `resume`: Resumes a WebView after a previous call to `pause()`. +* `getOriginalUrl`: Gets the URL that was originally requested for the current page. + +##### `InAppWebViewController` iOS-specific methods + +iOS-specific methods can be called using the `InAppWebViewController.ios` attribute. + +* `reloadFromOrigin`: Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible. + ##### About the JavaScript handler The Android implementation uses [addJavascriptInterface](https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface(java.lang.Object,%20java.lang.String)). @@ -375,7 +395,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly: * `builtInZoomControls`: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`. * `displayZoomControls`: Set to `true` if the WebView should display on-screen zoom controls when using the built-in zoom mechanisms. The default value is `false`. * `supportZoom`: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`. -* `databaseEnabled`: Set to `true` if you want the database storage API is enabled. The default value is `false`. +* `databaseEnabled`: Set to `true` if you want the database storage API is enabled. The default value is `true`. * `domStorageEnabled`: Set to `true` if you want the DOM storage API is enabled. The default value is `true`. * `useWideViewPort`: Set to `true` if the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport. * `safeBrowsingEnabled`: Sets whether Safe Browsing is enabled. Safe Browsing allows WebView to protect against malware and phishing attacks by verifying the links. @@ -431,6 +451,8 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly: #### `InAppWebView` Events +Event names that starts with `android` or `ios` are events platform-specific. + * `onWebViewCreated`: Event fired when the InAppWebView is created. * `onLoadStart`: Event fired when the InAppWebView starts to load an url. * `onLoadStop`: Event fired when the InAppWebView finishes loading an url. @@ -439,17 +461,15 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly: * `onProgressChanged`: Event fired when the current progress of loading a page is changed. * `onConsoleMessage`: Event fired when the InAppWebView receives a ConsoleMessage. * `shouldOverrideUrlLoading`: Give the host application a chance to take control when a URL is about to be loaded in the current WebView (to use this event, the `useShouldOverrideUrlLoading` option must be `true`). This event is not called on the initial load of the WebView. -* `onNavigationStateChange`: Event fired when the navigation state of the InAppWebView changes, for example through the usage of the javascript **[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)** functions. +* `onUpdateVisitedHistory`: Event fired when the host application updates its visited links database. This event is also fired when the navigation state of the InAppWebView changes, for example through the usage of the javascript **[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)** functions. * `onLoadResource`: Event fired when the InAppWebView loads a resource (to use this event, the `useOnLoadResource` option must be `true`). * `onScrollChanged`: Event fired when the InAppWebView scrolls. * `onDownloadStart`: Event fired when InAppWebView recognizes and starts a downloadable file (to use this event, the `useOnDownloadStart` option must be `true`). * `onLoadResourceCustomScheme`: Event fired when the InAppWebView finds the `custom-scheme` while loading a resource. Here you can handle the url request and return a CustomSchemeResponse to load a specific resource encoded to `base64`. * `onCreateWindow`: Event fired when the InAppWebView requests the host application to create a new window, for example when trying to open a link with `target="_blank"` or when `window.open()` is called by JavaScript side. -* `onGeolocationPermissionsShowPrompt`: Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin (available only on Android). * `onJsAlert`: Event fired when javascript calls the `alert()` method to display an alert dialog. * `onJsConfirm`: Event fired when javascript calls the `confirm()` method to display a confirm dialog. * `onJsPrompt`: Event fired when javascript calls the `prompt()` method to display a prompt dialog. -* `onSafeBrowsingHit`: Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing (available only on Android). * `onReceivedHttpAuthRequest`: Event fired when the WebView received an HTTP authentication request. The default behavior is to cancel the request. * `onReceivedServerTrustAuthRequest`: Event fired when the WebView need to perform server trust authentication (certificate validation). * `onReceivedClientCertRequest`: Notify the host application to handle an SSL client certificate request. @@ -458,8 +478,11 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly: * `onAjaxReadyStateChange`: Event fired whenever the `readyState` attribute of an `XMLHttpRequest` changes (to use this event, the `useShouldInterceptAjaxRequest` option must be `true`). * `onAjaxProgress`: Event fired as an `XMLHttpRequest` progress (to use this event, the `useShouldInterceptAjaxRequest` option must be `true`). * `shouldInterceptFetchRequest`: Event fired when a request is sent to a server through [Fetch API](https://developer.mozilla.org/it/docs/Web/API/Fetch_API) (to use this event, the `useShouldInterceptFetchRequest` option must be `true`). -* `onPermissionRequest`: Event fired when the webview is requesting permission to access the specified resources and the permission currently isn't granted or denied (available only on Android). * `onPrint`: Event fired when `window.print()` is called from JavaScript side. +* `androidOnSafeBrowsingHit`: Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing (available only on Android). +* `androidOnPermissionRequest`: Event fired when the webview is requesting permission to access the specified resources and the permission currently isn't granted or denied (available only on Android). +* `androidOnGeolocationPermissionsShowPrompt`: Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin (available only on Android). +* `androidOnGeolocationPermissionsHidePrompt`: Notify the host application that a request for Geolocation permissions, made with a previous call to `androidOnGeolocationPermissionsShowPrompt` has been canceled. (available only on Android). ### `InAppBrowser` class @@ -556,7 +579,7 @@ class _MyAppState extends State { assetFilePath: "assets/index.html", options: InAppBrowserClassOptions( inAppWebViewWidgetOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( useShouldOverrideUrlLoading: true, useOnLoadResource: true, )))); @@ -714,8 +737,8 @@ class _MyAppState extends State { await widget.browser.open( url: "https://flutter.dev/", options: ChromeSafariBrowserClassOptions( - androidChromeCustomTabsOptions: AndroidChromeCustomTabsOptions(addShareButton: false), - iosSafariOptions: IosSafariOptions(barCollapsingEnabled: true))); + android: AndroidChromeCustomTabsOptions(addShareButton: false), + ios: IosSafariOptions(barCollapsingEnabled: true))); }, child: Text("Open Chrome Safari Browser")), ), @@ -860,3 +883,38 @@ On Android, this class has a custom implementation using `android.database.sqlit * `removeHttpAuthCredential({@required ProtectionSpace protectionSpace, @required HttpAuthCredential credential})`: Removes an HTTP auth `credential` for that `protectionSpace`. * `removeHttpAuthCredentials({@required ProtectionSpace protectionSpace})`: Removes all the HTTP auth credentials saved for that `protectionSpace`. * `clearAllAuthCredentials()`: Removes all the HTTP auth credentials saved in the database. + +### `WebStorageManager` class + +This class implements a singleton object (shared instance) which manages the web storage used by WebView instances. + +On Android, it is implemented using [WebStorage](https://developer.android.com/reference/android/webkit/WebStorage.html). +On iOS, it is implemented using [WKWebsiteDataStore.default()](https://developer.apple.com/documentation/webkit/wkwebsitedatastore) + +**NOTE for iOS**: available from iOS 9.0+. + +#### `WebStorageManager` methods + +* `instance`: Gets the WebStorage manager shared instance. + +#### `WebStorageManager` Android-specific methods + +Android-specific methods can be called using the `WebStorageManager.instance().android` attribute. + +`AndroidWebStorageManager` class is used to manage the JavaScript storage APIs provided by the WebView. It manages the Application Cache API, the Web SQL Database API and the HTML5 Web Storage API. + +* `getOrigins`: Gets the origins currently using either the Application Cache or Web SQL Database APIs. +* `deleteAllData`: Clears all storage currently being used by the JavaScript storage APIs. +* `deleteOrigin({@required String origin})`: Clears the storage currently being used by both the Application Cache and Web SQL Database APIs by the given `origin`. +* `getQuotaForOrigin({@required String origin})`: Gets the storage quota for the Web SQL Database API for the given `origin`. +* `getUsageForOrigin({@required String origin})`: Gets the amount of storage currently being used by both the Application Cache and Web SQL Database APIs by the given `origin`. + +#### `WebStorageManager` iOS-specific methods + +iOS-specific methods can be called using the `WebStorageManager.instance().ios` attribute. + +`IOSWebStorageManager` class represents various types of data that a website might make use of. This includes cookies, disk and memory caches, and persistent data such as WebSQL, IndexedDB databases, and local storage. + +* `fetchDataRecords({@required Set dataTypes})`: Fetches data records containing the given website data types. +* `removeDataFor({@required Set dataTypes, @required List dataRecords})`: Removes website data of the given types for the given data records. +* `removeDataModifiedSince({@required Set dataTypes, @required DateTime date})`: Removes all website data of the given types that has been modified since the given date. diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/FlutterWebView.java index 38465e9b..e827e0eb 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/FlutterWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/FlutterWebView.java @@ -354,6 +354,24 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { 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) { + Float zoomFactor = (Float) call.argument("zoomFactor"); + webView.zoomBy(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; default: result.notImplemented(); } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser.java index 9f267dac..70c8752a 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser.java @@ -22,6 +22,7 @@ package com.pichillilorenzo.flutter_inappwebview; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -133,7 +134,7 @@ public class InAppBrowser implements MethodChannel.MethodCallHandler { Intent intent = new Intent(Intent.ACTION_DIAL); intent.setData(Uri.parse(url)); activity.startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { + } catch (ActivityNotFoundException e) { Log.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); } } @@ -348,6 +349,22 @@ public class InAppBrowser implements MethodChannel.MethodCallHandler { } result.success(true); break; + case "getContentHeight": + result.success(getContentHeight(uuid)); + break; + case "zoomBy": + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Float zoomFactor = (Float) call.argument("zoomFactor"); + zoomBy(uuid, zoomFactor); + } + result.success(true); + break; + case "getOriginalUrl": + result.success(getOriginalUrl(uuid)); + break; + case "getScale": + result.success(getScale(uuid)); + break; default: result.notImplemented(); } @@ -814,6 +831,34 @@ public class InAppBrowser implements MethodChannel.MethodCallHandler { inAppBrowserActivity.printCurrentPage(); } + public Integer getContentHeight(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.getContentHeight(); + return null; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void zoomBy(String uuid, Float zoomFactor) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.zoomBy(zoomFactor); + } + + public String getOriginalUrl(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.getOriginalUrl(); + return null; + } + + public Float getScale(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.getScale(); + return null; + } + public void dispose() { channel.setMethodCallHandler(null); for ( InAppBrowserActivity activity : webViewActivities.values()) { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserActivity.java index 7fb5c761..ea660ad1 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserActivity.java @@ -575,4 +575,27 @@ public class InAppBrowserActivity extends AppCompatActivity { 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; + } } 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 90670815..2d3c58e0 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java @@ -1342,69 +1342,8 @@ final public class InAppWebView extends InputAwareWebView { new PrintAttributes.Builder().build()); } - public void showDropDownWorkaround(final List selectedValues, final List> values, final boolean isMultiSelect, final DropDownWorkaroundCallback callback) { - FrameLayout layout = new FrameLayout(Shared.activity); - - final List listViewValues = new ArrayList(); - for(List value : values) { - listViewValues.add(value.get(0)); - } - - ListView listView = new ListView(Shared.activity); - ArrayAdapter spinnerArrayAdapter = new ArrayAdapter(Shared.activity, (!isMultiSelect) ? android.R.layout.simple_list_item_1 : android.R.layout.simple_list_item_multiple_choice, listViewValues); - spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_list_item_multiple_choice); - listView.setAdapter(spinnerArrayAdapter); - - - AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(Shared.activity, R.style.Theme_AppCompat_Dialog_Alert); - final AlertDialog alertDialog = alertDialogBuilder.create(); - - final List result = new ArrayList<>(); - - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - String value = values.get(position).get(1); - if (!isMultiSelect) { - result.add(value); - alertDialog.dismiss(); - } else { - if (!result.contains(value)) { - result.add(value); - } else { - result.remove(value); - } - } - } - }); - - if (isMultiSelect) { - listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - listView.setItemsCanFocus(false); - - for(Integer selectedValueIndex : selectedValues) { - listView.setItemChecked(selectedValueIndex, true); - String value = values.get(selectedValueIndex).get(1); - result.add(value); - } - } - - alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - callback.result(result); - } - }); - - layout.addView(listView); - alertDialog.setView(layout); - alertDialog.show(); - } - - public static class DropDownWorkaroundCallback { - public void result(List value) { - - } + public Float getUpdatedScale() { + return scale; } @Override diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java index 68a4da0a..19992197 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java @@ -411,10 +411,13 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR } @Override - public boolean onCreateWindow(WebView view, boolean isDialog, boolean userGesture, final Message resultMsg) { + public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, final Message resultMsg) { final Map obj = new HashMap<>(); if (inAppBrowserActivity != null) obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("androidIsDialog", isDialog); + obj.put("androidIsUserGesture", isUserGesture); + obj.put("iosWKNavigationType", null); WebView.HitTestResult result = view.getHitTestResult(); String data = result.getExtra(); @@ -478,6 +481,14 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR }); } + @Override + public void onGeolocationPermissionsHidePrompt() { + Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + getChannel().invokeMethod("onGeolocationPermissionsHidePrompt", obj); + } + @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { Map obj = new HashMap<>(); 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 5442ee91..84ad883b 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java @@ -17,6 +17,7 @@ import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; +import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -54,7 +55,6 @@ public class InAppWebViewClient extends WebViewClient { private InAppBrowserActivity inAppBrowserActivity; private static int previousAuthRequestFailureCount = 0; private static List credentialsProposed = null; - private String onPageStartedURL = ""; public InAppWebViewClient(Object obj) { super(); @@ -188,7 +188,6 @@ public class InAppWebViewClient extends WebViewClient { webView.loadUrl("javascript:" + js); } - onPageStartedURL = url; super.onPageStarted(view, url, favicon); webView.isLoading = true; @@ -241,16 +240,14 @@ public class InAppWebViewClient extends WebViewClient { @Override public void doUpdateVisitedHistory (WebView view, String url, boolean isReload) { - super.doUpdateVisitedHistory(view, url, isReload); + Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("url", url); + obj.put("androidIsReload", isReload); + getChannel().invokeMethod("onUpdateVisitedHistory", obj); - if (!isReload && !url.equals(onPageStartedURL)) { - onPageStartedURL = ""; - Map obj = new HashMap<>(); - if (inAppBrowserActivity != null) - obj.put("uuid", inAppBrowserActivity.uuid); - obj.put("url", url); - getChannel().invokeMethod("onNavigationStateChange", obj); - } + super.doUpdateVisitedHistory(view, url, isReload); } @Override diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java index c0094336..d2e20a30 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java @@ -23,6 +23,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { public static InAppWebViewStatic inAppWebViewStatic; public static MyCookieManager myCookieManager; public static CredentialDatabaseHandler credentialDatabaseHandler; + public static MyWebStorage myWebStorage; public static ValueCallback uploadMessageArray; public InAppWebViewFlutterPlugin() {} @@ -52,6 +53,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { "com.pichillilorenzo/flutter_inappwebview", new FlutterWebViewFactory(messenger, flutterView)); inAppWebViewStatic = new InAppWebViewStatic(messenger); myCookieManager = new MyCookieManager(messenger); + myWebStorage = new MyWebStorage(messenger); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { credentialDatabaseHandler = new CredentialDatabaseHandler(messenger); } @@ -67,6 +69,10 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { myCookieManager.dispose(); myCookieManager = null; } + if (myWebStorage != null) { + myWebStorage.dispose(); + myWebStorage = null; + } if (credentialDatabaseHandler != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { credentialDatabaseHandler.dispose(); credentialDatabaseHandler = null; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java index 2727ee88..b55e5c5f 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java @@ -273,51 +273,6 @@ public class JavaScriptBridgeInterface { @Override public void run() { - // workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/182 - if (handlerName.equals("flutterInAppWebViewDropDownWorkaround")) { - try { - JSONArray jsonArray = new JSONArray(args); - - List selectedValues = new ArrayList<>(); - JSONArray jsonSelectedValues = jsonArray.getJSONArray(0); - for(int i = 0; i < jsonSelectedValues.length(); i++) { - Integer selectedValue = jsonSelectedValues.getInt(i); - selectedValues.add(selectedValue); - } - - boolean isMultiSelect = jsonArray.getBoolean(1); - - List> values = new ArrayList<>(); - JSONArray options = jsonArray.getJSONArray(2); - - for(int i = 0; i < options.length(); i++) { - JSONObject option = options.getJSONObject(i); - - List value = new ArrayList<>(); - value.add(option.getString("key")); - value.add(option.getString("value")); - - values.add(value); - } - - webView.showDropDownWorkaround(selectedValues, values, isMultiSelect, new InAppWebView.DropDownWorkaroundCallback() { - @Override - public void result(List values) { - String value = "{values: " + (new JSONArray(values)) + "}"; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript("if(window." + name + "[" + _callHandlerID + "] != null) {window." + name + "[" + _callHandlerID + "](" + value + "); delete window." + name + "[" + _callHandlerID + "];}", (ValueCallback) null); - } - else { - webView.loadUrl("javascript:if(window." + name + "[" + _callHandlerID + "] != null) {window." + name + "[" + _callHandlerID + "](" + value + "); delete window." + name + "[" + _callHandlerID + "];}"); - } - } - }); - } catch (JSONException e) { - e.printStackTrace(); - } - return; - } - if (handlerName.equals("onPrint") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { webView.printCurrentPage(); } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyWebStorage.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyWebStorage.java new file mode 100644 index 00000000..effd0802 --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyWebStorage.java @@ -0,0 +1,104 @@ +package com.pichillilorenzo.flutter_inappwebview; + +import android.util.Log; +import android.webkit.ValueCallback; +import android.webkit.WebStorage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +public class MyWebStorage implements MethodChannel.MethodCallHandler { + + static final String LOG_TAG = "MyWebStorage"; + + public static MethodChannel channel; + public static WebStorage webStorageManager; + + public MyWebStorage(BinaryMessenger messenger) { + channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_inappwebview_webstoragemanager"); + channel.setMethodCallHandler(this); + webStorageManager = WebStorage.getInstance(); + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case "getOrigins": + getOrigins(result); + break; + case "deleteAllData": + webStorageManager.deleteAllData(); + result.success(true); + break; + case "deleteOrigin": + { + String origin = (String) call.argument("origin"); + webStorageManager.deleteOrigin(origin); + } + result.success(true); + break; + case "getQuotaForOrigin": + { + String origin = (String) call.argument("origin"); + getQuotaForOrigin(origin, result); + } + break; + case "getUsageForOrigin": + { + String origin = (String) call.argument("origin"); + getUsageForOrigin(origin, result); + } + break; + default: + result.notImplemented(); + } + } + + public void getOrigins(final MethodChannel.Result result) { + webStorageManager.getOrigins(new ValueCallback() { + @Override + public void onReceiveValue(Map value) { + List> origins = new ArrayList<>(); + for(Object key : value.keySet()) { + WebStorage.Origin originObj = (WebStorage.Origin) value.get(key); + + Map originInfo = new HashMap<>(); + originInfo.put("origin", originObj.getOrigin()); + originInfo.put("quota", originObj.getQuota()); + originInfo.put("usage", originObj.getUsage()); + + origins.add(originInfo); + } + result.success(origins); + } + }); + } + + public void getQuotaForOrigin(String origin, final MethodChannel.Result result) { + webStorageManager.getQuotaForOrigin(origin, new ValueCallback() { + @Override + public void onReceiveValue(Long value) { + result.success(value); + } + }); + } + + public void getUsageForOrigin(String origin, final MethodChannel.Result result) { + webStorageManager.getUsageForOrigin(origin, new ValueCallback() { + @Override + public void onReceiveValue(Long value) { + result.success(value); + } + }); + } + + public void dispose() { + channel.setMethodCallHandler(null); + } +} diff --git a/example/ios/Flutter/Flutter.podspec b/example/ios/Flutter/Flutter.podspec new file mode 100644 index 00000000..5ca30416 --- /dev/null +++ b/example/ios/Flutter/Flutter.podspec @@ -0,0 +1,18 @@ +# +# NOTE: This podspec is NOT to be published. It is only used as a local source! +# + +Pod::Spec.new do |s| + s.name = 'Flutter' + s.version = '1.0.0' + s.summary = 'High-performance, high-fidelity mobile apps.' + s.description = <<-DESC +Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. + DESC + s.homepage = 'https://flutter.io' + s.license = { :type => 'MIT' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } + s.ios.deployment_target = '8.0' + s.vendored_frameworks = 'Flutter.framework' +end diff --git a/example/lib/chrome_safari_browser_example.screen.dart b/example/lib/chrome_safari_browser_example.screen.dart index a0607618..6b2b39f6 100644 --- a/example/lib/chrome_safari_browser_example.screen.dart +++ b/example/lib/chrome_safari_browser_example.screen.dart @@ -52,8 +52,8 @@ class _ChromeSafariBrowserExampleScreenState await widget.browser.open( url: "https://flutter.dev/", options: ChromeSafariBrowserClassOptions( - androidChromeCustomTabsOptions: AndroidChromeCustomTabsOptions(addShareButton: false), - iosSafariOptions: IosSafariOptions(barCollapsingEnabled: true))); + android: AndroidChromeCustomTabsOptions(addShareButton: false), + ios: IOSSafariOptions(barCollapsingEnabled: true))); }, child: Text("Open Chrome Safari Browser")), )); diff --git a/example/lib/in_app_browser_example.screen.dart b/example/lib/in_app_browser_example.screen.dart index 632c8829..6b13ff79 100644 --- a/example/lib/in_app_browser_example.screen.dart +++ b/example/lib/in_app_browser_example.screen.dart @@ -92,7 +92,7 @@ class _InAppBrowserExampleScreenState extends State { assetFilePath: "assets/index.html", options: InAppBrowserClassOptions( inAppWebViewWidgetOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( useShouldOverrideUrlLoading: true, useOnLoadResource: true, )))); diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index ee8f4c13..e98db1bf 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -50,9 +50,10 @@ class _InAppWebViewExampleScreenState extends State { BoxDecoration(border: Border.all(color: Colors.blueAccent)), child: InAppWebView( initialUrl: "https://flutter.dev/", + //initialFile: "assets/index.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( debuggingEnabled: true, ) ), @@ -60,25 +61,51 @@ class _InAppWebViewExampleScreenState extends State { webView = controller; }, onLoadStart: (InAppWebViewController controller, String url) { + print("onLoadStart $url"); setState(() { this.url = url; }); }, onLoadStop: (InAppWebViewController controller, String url) async { + print("onLoadStop $url"); setState(() { this.url = url; }); - }, - onNavigationStateChange: (InAppWebViewController controller, String url) async { - setState(() { - this.url = url; - }); + /*var origins = await WebStorageManager.instance().android.getOrigins(); + for (var origin in origins) { + print(origin); + print(await WebStorageManager.instance().android.getQuotaForOrigin(origin: origin.origin)); + print(await WebStorageManager.instance().android.getUsageForOrigin(origin: origin.origin)); + } + await WebStorageManager.instance().android.deleteAllData(); + print("\n\nDELETED\n\n"); + origins = await WebStorageManager.instance().android.getOrigins(); + for (var origin in origins) { + print(origin); + await WebStorageManager.instance().android.deleteOrigin(origin: origin.origin); + }*/ + /*var records = await WebStorageManager.instance().ios.fetchDataRecords(dataTypes: IOSWKWebsiteDataType.ALL); + for(var record in records) { + print(record); + } + await WebStorageManager.instance().ios.removeDataModifiedSince(dataTypes: IOSWKWebsiteDataType.ALL, date: DateTime(0)); + print("\n\nDELETED\n\n"); + records = await WebStorageManager.instance().ios.fetchDataRecords(dataTypes: IOSWKWebsiteDataType.ALL); + for(var record in records) { + print(record); + }*/ }, onProgressChanged: (InAppWebViewController controller, int progress) { setState(() { this.progress = progress / 100; }); }, + onUpdateVisitedHistory: (InAppWebViewController controller, String url, bool androidIsReload) { + print("onUpdateVisitedHistory $url"); + setState(() { + this.url = url; + }); + }, ), ), ), diff --git a/example/test_driver/in_app_webview_ajax_test.dart b/example/test_driver/in_app_webview_ajax_test.dart index 108c56bf..e1656d3b 100644 --- a/example/test_driver/in_app_webview_ajax_test.dart +++ b/example/test_driver/in_app_webview_ajax_test.dart @@ -57,7 +57,7 @@ class InAppWebViewAjaxTestState extends WidgetTestState { """), initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true, useShouldInterceptAjaxRequest: true, diff --git a/example/test_driver/in_app_webview_content_blocker_test.dart b/example/test_driver/in_app_webview_content_blocker_test.dart index 221c23d1..d45ada80 100644 --- a/example/test_driver/in_app_webview_content_blocker_test.dart +++ b/example/test_driver/in_app_webview_content_blocker_test.dart @@ -30,7 +30,7 @@ class InAppWebViewContentBlockerTestState extends WidgetTestState { initialUrl: "https://flutter.dev/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true, contentBlockers: [ diff --git a/example/test_driver/in_app_webview_cookie_manager_test.dart b/example/test_driver/in_app_webview_cookie_manager_test.dart index 8ed77e53..99d509e9 100644 --- a/example/test_driver/in_app_webview_cookie_manager_test.dart +++ b/example/test_driver/in_app_webview_cookie_manager_test.dart @@ -31,7 +31,7 @@ class InAppWebViewCookieManagerTestState extends WidgetTestState { initialUrl: "https://flutter.dev/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_fetch_test.dart b/example/test_driver/in_app_webview_fetch_test.dart index fe03e2c7..e23590c0 100644 --- a/example/test_driver/in_app_webview_fetch_test.dart +++ b/example/test_driver/in_app_webview_fetch_test.dart @@ -75,7 +75,7 @@ class InAppWebViewFetchTestState extends WidgetTestState { """), initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true, useShouldInterceptFetchRequest: true, diff --git a/example/test_driver/in_app_webview_http_auth_credential_database_test.dart b/example/test_driver/in_app_webview_http_auth_credential_database_test.dart index 60293d8c..580dfc1d 100644 --- a/example/test_driver/in_app_webview_http_auth_credential_database_test.dart +++ b/example/test_driver/in_app_webview_http_auth_credential_database_test.dart @@ -38,7 +38,7 @@ class InAppWebViewHttpAuthCredentialDatabaseTestState extends WidgetTestState { initialUrl: "http://${environment["NODE_SERVER_IP"]}:8081/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_initial_data_test.dart b/example/test_driver/in_app_webview_initial_data_test.dart index 9a2431d1..15a05a82 100644 --- a/example/test_driver/in_app_webview_initial_data_test.dart +++ b/example/test_driver/in_app_webview_initial_data_test.dart @@ -66,7 +66,7 @@ class InAppWebViewInitialDataTestState extends WidgetTestState { """), initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_initial_file_test.dart b/example/test_driver/in_app_webview_initial_file_test.dart index abe6df38..4e6810af 100644 --- a/example/test_driver/in_app_webview_initial_file_test.dart +++ b/example/test_driver/in_app_webview_initial_file_test.dart @@ -29,7 +29,7 @@ class InAppWebViewInitialFileTestState extends WidgetTestState { initialFile: "test_assets/in_app_webview_initial_file_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_initial_url_test.dart b/example/test_driver/in_app_webview_initial_url_test.dart index bf3e6258..89e8580a 100644 --- a/example/test_driver/in_app_webview_initial_url_test.dart +++ b/example/test_driver/in_app_webview_initial_url_test.dart @@ -30,7 +30,7 @@ class InAppWebViewInitialUrlTestState extends WidgetTestState { initialUrl: "https://flutter.dev/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_javascript_handler_test.dart b/example/test_driver/in_app_webview_javascript_handler_test.dart index a5833a8a..c445924d 100644 --- a/example/test_driver/in_app_webview_javascript_handler_test.dart +++ b/example/test_driver/in_app_webview_javascript_handler_test.dart @@ -44,7 +44,7 @@ class InAppWebViewJavaScriptHandlerTestState extends WidgetTestState { initialFile: "test_assets/in_app_webview_javascript_handler_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_on_console_message_test.dart b/example/test_driver/in_app_webview_on_console_message_test.dart index 0a001317..6e996dfb 100644 --- a/example/test_driver/in_app_webview_on_console_message_test.dart +++ b/example/test_driver/in_app_webview_on_console_message_test.dart @@ -29,7 +29,7 @@ class InAppWebViewOnConsoleMessageTestState extends WidgetTestState { initialFile: "test_assets/in_app_webview_on_console_message_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_on_create_window_test.dart b/example/test_driver/in_app_webview_on_create_window_test.dart index 36f8f388..73e66e35 100644 --- a/example/test_driver/in_app_webview_on_create_window_test.dart +++ b/example/test_driver/in_app_webview_on_create_window_test.dart @@ -29,7 +29,7 @@ class InAppWebViewOnCreateWindowTestState extends WidgetTestState { initialFile: "test_assets/in_app_webview_on_create_window_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true, javaScriptCanOpenWindowsAutomatically: true, @@ -48,8 +48,8 @@ class InAppWebViewOnCreateWindowTestState extends WidgetTestState { }); } }, - onCreateWindow: (InAppWebViewController controller, String url) { - controller.loadUrl(url: url); + onCreateWindow: (InAppWebViewController controller, OnCreateWindowRequest onCreateWindowRequest) { + controller.loadUrl(url: onCreateWindowRequest.url); }, ), ), diff --git a/example/test_driver/in_app_webview_on_download_start_test.dart b/example/test_driver/in_app_webview_on_download_start_test.dart index cb688262..bc633bc7 100644 --- a/example/test_driver/in_app_webview_on_download_start_test.dart +++ b/example/test_driver/in_app_webview_on_download_start_test.dart @@ -49,7 +49,7 @@ class InAppWebViewOnDownloadStartTestState extends WidgetTestState { """), initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true, useOnDownloadStart: true diff --git a/example/test_driver/in_app_webview_on_find_result_received_test.dart b/example/test_driver/in_app_webview_on_find_result_received_test.dart index a6d0262f..16c5c6bf 100644 --- a/example/test_driver/in_app_webview_on_find_result_received_test.dart +++ b/example/test_driver/in_app_webview_on_find_result_received_test.dart @@ -29,7 +29,7 @@ class InAppWebViewOnFindResultReceivedTestState extends WidgetTestState { initialFile: "test_assets/in_app_webview_initial_file_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_on_js_dialog_test.dart b/example/test_driver/in_app_webview_on_js_dialog_test.dart index bb52dd1b..5199e40f 100644 --- a/example/test_driver/in_app_webview_on_js_dialog_test.dart +++ b/example/test_driver/in_app_webview_on_js_dialog_test.dart @@ -37,7 +37,7 @@ class InAppWebViewOnJsDialogTestState extends WidgetTestState { initialFile: "test_assets/in_app_webview_on_js_dialog_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_on_load_error_test.dart b/example/test_driver/in_app_webview_on_load_error_test.dart index 8d2d5b1e..93b9d522 100644 --- a/example/test_driver/in_app_webview_on_load_error_test.dart +++ b/example/test_driver/in_app_webview_on_load_error_test.dart @@ -30,7 +30,7 @@ class InAppWebViewOnLoadErrorTestState extends WidgetTestState { initialUrl: "https://not-existing-domain.org/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_on_load_http_error_test.dart b/example/test_driver/in_app_webview_on_load_http_error_test.dart index 6a7d1de1..8b7be679 100644 --- a/example/test_driver/in_app_webview_on_load_http_error_test.dart +++ b/example/test_driver/in_app_webview_on_load_http_error_test.dart @@ -30,7 +30,7 @@ class InAppWebViewOnLoadHttpErrorTestState extends WidgetTestState { initialUrl: "https://google.com/404", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_on_load_resource_custom_scheme_test.dart b/example/test_driver/in_app_webview_on_load_resource_custom_scheme_test.dart index a1b8283f..474927ca 100644 --- a/example/test_driver/in_app_webview_on_load_resource_custom_scheme_test.dart +++ b/example/test_driver/in_app_webview_on_load_resource_custom_scheme_test.dart @@ -30,7 +30,7 @@ class InAppWebViewOnLoadResourceCustomSchemeTestState extends WidgetTestState { initialFile: "test_assets/in_app_webview_on_load_resource_custom_scheme_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true, resourceCustomSchemes: ["my-special-custom-scheme"] diff --git a/example/test_driver/in_app_webview_on_load_resource_test.dart b/example/test_driver/in_app_webview_on_load_resource_test.dart index 15a7260b..eec81501 100644 --- a/example/test_driver/in_app_webview_on_load_resource_test.dart +++ b/example/test_driver/in_app_webview_on_load_resource_test.dart @@ -35,7 +35,7 @@ class InAppWebViewOnLoadResourceTestState extends WidgetTestState { initialFile: "test_assets/in_app_webview_on_load_resource_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true, useOnLoadResource: true diff --git a/example/test_driver/in_app_webview_on_navigation_state_change_test.dart b/example/test_driver/in_app_webview_on_navigation_state_change_test.dart index aede944a..faba8ab2 100644 --- a/example/test_driver/in_app_webview_on_navigation_state_change_test.dart +++ b/example/test_driver/in_app_webview_on_navigation_state_change_test.dart @@ -29,7 +29,7 @@ class InAppWebViewOnNavigationStateChangeTestState extends WidgetTestState { initialUrl: "https://flutter.dev/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_on_progress_changed_test.dart b/example/test_driver/in_app_webview_on_progress_changed_test.dart index eb303493..c50e8f3b 100644 --- a/example/test_driver/in_app_webview_on_progress_changed_test.dart +++ b/example/test_driver/in_app_webview_on_progress_changed_test.dart @@ -30,7 +30,7 @@ class InAppWebViewOnProgressChangedTestState extends WidgetTestState { initialUrl: "https://flutter.dev/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_on_received_http_auth_request_test.dart b/example/test_driver/in_app_webview_on_received_http_auth_request_test.dart index ec58dd7d..7ae2f1f2 100644 --- a/example/test_driver/in_app_webview_on_received_http_auth_request_test.dart +++ b/example/test_driver/in_app_webview_on_received_http_auth_request_test.dart @@ -31,7 +31,7 @@ class InAppWebViewOnReceivedHttpAuthRequestTestState extends WidgetTestState { initialUrl: "http://${environment["NODE_SERVER_IP"]}:8081/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_on_safe_browsing_hit_test.dart b/example/test_driver/in_app_webview_on_safe_browsing_hit_test.dart index d4698e47..158037b0 100644 --- a/example/test_driver/in_app_webview_on_safe_browsing_hit_test.dart +++ b/example/test_driver/in_app_webview_on_safe_browsing_hit_test.dart @@ -32,20 +32,20 @@ class InAppWebViewOnSafeBrowsingHitTestState extends WidgetTestState { initialUrl: (Platform.isAndroid) ? "chrome://safe-browsing/match?type=malware" : "https://flutter.dev/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( // if I set javaScriptEnabled to true, it will crash! javaScriptEnabled: false, clearCache: true, debuggingEnabled: true ), - androidInAppWebViewOptions: AndroidInAppWebViewOptions( + android: AndroidInAppWebViewOptions( safeBrowsingEnabled: true, ), ), onWebViewCreated: (InAppWebViewController controller) { webView = controller; if(Platform.isAndroid) - controller.startSafeBrowsing(); + controller.android.startSafeBrowsing(); }, onLoadStart: (InAppWebViewController controller, String url) { @@ -55,7 +55,7 @@ class InAppWebViewOnSafeBrowsingHitTestState extends WidgetTestState { appBarTitle = url; }); }, - onSafeBrowsingHit: (InAppWebViewController controller, String url, SafeBrowsingThreat threatType) async { + androidOnSafeBrowsingHit: (InAppWebViewController controller, String url, SafeBrowsingThreat threatType) async { return SafeBrowsingResponse(report: true, action: SafeBrowsingResponseAction.PROCEED); }, ), diff --git a/example/test_driver/in_app_webview_on_scroll_changed_test.dart b/example/test_driver/in_app_webview_on_scroll_changed_test.dart index ab1af7e5..4321b788 100644 --- a/example/test_driver/in_app_webview_on_scroll_changed_test.dart +++ b/example/test_driver/in_app_webview_on_scroll_changed_test.dart @@ -31,7 +31,7 @@ class InAppWebViewOnScrollChangedTestState extends WidgetTestState { initialUrl: "https://flutter.dev/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/example/test_driver/in_app_webview_should_override_url_loading_test.dart b/example/test_driver/in_app_webview_should_override_url_loading_test.dart index a33ee5d0..4d4ded94 100644 --- a/example/test_driver/in_app_webview_should_override_url_loading_test.dart +++ b/example/test_driver/in_app_webview_should_override_url_loading_test.dart @@ -29,7 +29,7 @@ class InAppWebViewShouldOverrideUrlLoadingTestState extends WidgetTestState { initialFile: "test_assets/in_app_webview_initial_file_test.html", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true, useShouldOverrideUrlLoading: true diff --git a/example/test_driver/in_app_webview_ssl_request_test.dart b/example/test_driver/in_app_webview_ssl_request_test.dart index 5d8b7625..4ab4f036 100644 --- a/example/test_driver/in_app_webview_ssl_request_test.dart +++ b/example/test_driver/in_app_webview_ssl_request_test.dart @@ -31,7 +31,7 @@ class InAppWebViewSslRequestTestState extends WidgetTestState { initialUrl: "https://${environment["NODE_SERVER_IP"]}:4433/", initialHeaders: {}, initialOptions: InAppWebViewWidgetOptions( - inAppWebViewOptions: InAppWebViewOptions( + crossPlatform: InAppWebViewOptions( clearCache: true, debuggingEnabled: true ) diff --git a/flutter_inappwebview.iml b/flutter_inappwebview.iml index 111d60ac..c8905c93 100644 --- a/flutter_inappwebview.iml +++ b/flutter_inappwebview.iml @@ -26,6 +26,7 @@ + diff --git a/ios/Classes/FlutterMethodCallDelegate.swift b/ios/Classes/FlutterMethodCallDelegate.swift new file mode 100644 index 00000000..d4f15591 --- /dev/null +++ b/ios/Classes/FlutterMethodCallDelegate.swift @@ -0,0 +1,18 @@ +// +// FlutterMethodCallDelegate.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 15/12/2019. +// + +import Foundation + +public class FlutterMethodCallDelegate: NSObject { + public override init() { + super.init() + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + + } +} diff --git a/ios/Classes/FlutterWebViewController.swift b/ios/Classes/FlutterWebViewController.swift index 3e878ba4..da156e6e 100755 --- a/ios/Classes/FlutterWebViewController.swift +++ b/ios/Classes/FlutterWebViewController.swift @@ -8,12 +8,13 @@ import Foundation import WebKit -public class FlutterWebViewController: NSObject, FlutterPlatformView { +public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatformView { private weak var registrar: FlutterPluginRegistrar? var webView: InAppWebView? var viewId: Int64 = 0 var channel: FlutterMethodChannel? + var myView: UIView? init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: NSDictionary) { super.init() @@ -21,24 +22,29 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { self.registrar = registrar self.viewId = viewId + myView = UIView(frame: frame) + + let channelName = "com.pichillilorenzo/flutter_inappwebview_" + String(viewId) + self.channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger()) + self.channel?.setMethodCallHandler(LeakAvoider(delegate: self).handle) + let initialUrl = (args["initialUrl"] as? String)! let initialFile = args["initialFile"] as? String let initialData = args["initialData"] as? [String: String] let initialHeaders = (args["initialHeaders"] as? [String: String])! let initialOptions = (args["initialOptions"] as? [String: Any])! - + let options = InAppWebViewOptions() options.parse(options: initialOptions) let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: options) - - webView = InAppWebView(frame: frame, configuration: preWebviewConfiguration, IABController: nil, IAWController: self) - let channelName = "com.pichillilorenzo/flutter_inappwebview_" + String(viewId) - self.channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger()) - self.channel?.setMethodCallHandler(self.handle) + + webView = InAppWebView(frame: myView!.bounds, configuration: preWebviewConfiguration, IABController: nil, channel: self.channel) + webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] + myView!.addSubview(webView!) webView!.options = options webView!.prepare() - + if #available(iOS 11.0, *) { self.webView!.configuration.userContentController.removeAllContentRuleLists() if let contentBlockers = webView!.options?.contentBlockers, contentBlockers.count > 0 { @@ -48,15 +54,15 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { WKContentRuleListStore.default().compileContentRuleList( forIdentifier: "ContentBlockingRules", encodedContentRuleList: blockRules) { (contentRuleList, error) in - + if let error = error { print(error.localizedDescription) return } - + let configuration = self.webView!.configuration configuration.userContentController.add(contentRuleList!) - + self.load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders) } return @@ -68,8 +74,16 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders) } + deinit { + print("FlutterWebViewController - dealloc") + self.channel?.setMethodCallHandler(nil) + webView!.dispose() + webView = nil + myView = nil + } + public func view() -> UIView { - return webView! + return myView! } public func load(initialUrl: String, initialFile: String?, initialData: [String: String]?, initialHeaders: [String: String]) { @@ -95,7 +109,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { } } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary switch call.method { case "getUrl": @@ -344,10 +358,18 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { result(false) } break - case "removeFromSuperview": - webView!.removeFromSuperview() + 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 default: result(FlutterMethodNotImplemented) break diff --git a/ios/Classes/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowserWebViewController.swift index 152172c6..bb373195 100755 --- a/ios/Classes/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowserWebViewController.swift @@ -65,7 +65,7 @@ typealias NewerClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, B class InAppWebView_IBWrapper: InAppWebView { required init(coder: NSCoder) { let config = WKWebViewConfiguration() - super.init(frame: .zero, configuration: config, IABController: nil, IAWController: nil) + super.init(frame: .zero, configuration: config, IABController: nil, channel: nil) self.translatesAutoresizingMaskIntoConstraints = false } } @@ -114,7 +114,7 @@ class InAppBrowserWebViewController: UIViewController, UIScrollViewDelegate, WKU override func viewWillAppear(_ animated: Bool) { if !viewPrepared { let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: webViewOptions) - self.webView = InAppWebView(frame: .zero, configuration: preWebviewConfiguration, IABController: self, IAWController: nil) + self.webView = InAppWebView(frame: .zero, configuration: preWebviewConfiguration, IABController: self, channel: nil) self.containerWebView.addSubview(self.webView) prepareConstraints() prepareWebView() @@ -194,12 +194,21 @@ class InAppBrowserWebViewController: UIViewController, UIScrollViewDelegate, WKU // Prevent crashes on closing windows deinit { - webView.removeObserver(self, forKeyPath: "estimatedProgress") - webView.uiDelegate = nil + print("InAppBrowserWebViewController - dealloc") } override func viewWillDisappear (_ animated: Bool) { super.viewWillDisappear(animated) + webView.dispose() + navigationDelegate = nil + transitioningDelegate = nil + urlField.delegate = nil + closeButton.removeTarget(self, action: #selector(self.close), for: .touchUpInside) + forwardButton.target = nil + forwardButton.target = nil + backButton.target = nil + reloadButton.target = nil + shareButton.target = nil } func prepareConstraints () { diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index f160682d..4866afaa 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -873,39 +873,10 @@ let interceptFetchRequestsJS = """ })(window.fetch); """ -let interceptNavigationStateChangeJS = """ -(function(window, document, history) { - history.pushState = (function(f) { - return function pushState(){ - var ret = f.apply(this, arguments); - window.dispatchEvent(new Event('pushstate')); - window.dispatchEvent(new Event('_flutter_inappwebview_locationchange')); - return ret; - }; - })(history.pushState); - history.replaceState = ( function(f) { - return function replaceState(){ - var ret = f.apply(this, arguments); - window.dispatchEvent(new Event('replacestate')); - window.dispatchEvent(new Event('_flutter_inappwebview_locationchange')); - return ret; - }; - })(history.replaceState); - window.addEventListener('popstate',function() { - window.dispatchEvent(new Event('_flutter_inappwebview_locationchange')); - }); - window.addEventListener('_flutter_inappwebview_locationchange', function() { - window.webkit.messageHandlers["onNavigationStateChange"].postMessage(JSON.stringify({ - url: document.location.href - })); - }); -})(window, window.document, window.history); -""" - public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler { var IABController: InAppBrowserWebViewController? - var IAWController: FlutterWebViewController? + var channel: FlutterMethodChannel? var options: InAppWebViewOptions? var currentURL: URL? var startPageTime: Int64 = 0 @@ -914,37 +885,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi var lastScrollY: CGFloat = 0 var isPausedTimers = false var isPausedTimersCompletionHandler: (() -> Void)? - var webViewForUserAgent: WKWebView? - var defaultUserAgent: String? // This flag is used to block the "shouldOverrideUrlLoading" event when the WKWebView is loading the first time, // in order to have the same behavior as Android var activateShouldOverrideUrlLoading = false - init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, IAWController: FlutterWebViewController?) { + init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, channel: FlutterMethodChannel?) { super.init(frame: frame, configuration: configuration) + self.channel = channel self.IABController = IABController - self.IAWController = IAWController uiDelegate = self navigationDelegate = self scrollView.delegate = self - webViewForUserAgent = WKWebView() - - webViewForUserAgent?.evaluateJavaScript("navigator.userAgent") { (result, error) in - - if error != nil { - print("Error occured to get userAgent") - self.webViewForUserAgent = nil - return - } - - if let unwrappedUserAgent = result as? String { - self.defaultUserAgent = unwrappedUserAgent - } else { - print("Failed to get userAgent") - } - self.webViewForUserAgent = nil - } } required public init(coder aDecoder: NSCoder) { @@ -958,6 +910,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi options: .new, context: nil) + addObserver(self, + forKeyPath: #keyPath(WKWebView.url), + options: [.new, .old], + context: nil) + configuration.userContentController = WKUserContentController() configuration.preferences = WKPreferences() @@ -1018,11 +975,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi configuration.userContentController.addUserScript(findTextHighlightJSScript) configuration.userContentController.add(self, name: "onFindResultReceived") - let interceptNavigationStateChangeJSScript = WKUserScript(source: interceptNavigationStateChangeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) - configuration.userContentController.addUserScript(interceptNavigationStateChangeJSScript) - configuration.userContentController.add(self, name: "onNavigationStateChange") - - if (options?.useShouldInterceptAjaxRequest)! { let interceptAjaxRequestsJSScript = WKUserScript(source: interceptAjaxRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) configuration.userContentController.addUserScript(interceptAjaxRequestsJSScript) @@ -1093,6 +1045,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if options?.preferredContentMode != nil { configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: (options?.preferredContentMode)!)! } + scrollView.automaticallyAdjustsScrollIndicatorInsets = (options?.automaticallyAdjustsScrollIndicatorInsets)! } else { // Fallback on earlier versions } @@ -1169,6 +1122,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if keyPath == #keyPath(WKWebView.estimatedProgress) { let progress = Int(estimatedProgress * 100) onProgressChanged(progress: progress) + } else if keyPath == #keyPath(WKWebView.url) && change?[NSKeyValueChangeKey.newKey] is URL { + let newUrl = change?[NSKeyValueChangeKey.newKey] as? URL + onUpdateVisitedHistory(url: newUrl!.absoluteString) } } @@ -1383,9 +1339,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi // Fallback on earlier versions } + scrollView + if #available(iOS 13.0, *) { - configuration.preferences.isFraudulentWebsiteWarningEnabled = (options?.isFraudulentWebsiteWarningEnabled)! - configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: (options?.preferredContentMode)!)! + if newOptionsMap["isFraudulentWebsiteWarningEnabled"] != nil && options?.isFraudulentWebsiteWarningEnabled != newOptions.isFraudulentWebsiteWarningEnabled { + configuration.preferences.isFraudulentWebsiteWarningEnabled = newOptions.isFraudulentWebsiteWarningEnabled + } + if newOptionsMap["preferredContentMode"] != nil && options?.preferredContentMode != newOptions.preferredContentMode { + configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: newOptions.preferredContentMode)! + } + if newOptionsMap["automaticallyAdjustsScrollIndicatorInsets"] != nil && options?.automaticallyAdjustsScrollIndicatorInsets != newOptions.automaticallyAdjustsScrollIndicatorInsets { + scrollView.automaticallyAdjustsScrollIndicatorInsets = newOptions.automaticallyAdjustsScrollIndicatorInsets + } } else { // Fallback on earlier versions } @@ -1490,7 +1455,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if error != nil { let userInfo = (error! as NSError).userInfo - self.onConsoleMessage(message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: 3) + self.onConsoleMessage(message: userInfo["WKJavaScriptExceptionMessage"] as? String ?? "", messageLevel: 3) } if value == nil { @@ -1651,7 +1616,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi InAppWebView.credentialsProposed = [] evaluateJavaScript(platformReadyJS, completionHandler: nil) onLoadStop(url: (currentURL?.absoluteString)!) - + if IABController != nil { IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!) IABController!.backButton.isEnabled = canGoBack @@ -2111,7 +2076,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { - onCreateWindow(url: navigationAction.request.url!) + onCreateWindow(url: navigationAction.request.url!, navigationType: navigationAction.navigationType) return nil } @@ -2171,6 +2136,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi "numberOfMatches": numberOfMatches, "isDoneCounting": isDoneCounting ] + if IABController != nil { arguments["uuid"] = IABController!.uuid } @@ -2179,18 +2145,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } } - public func onNavigationStateChange(url: String) { - var arguments: [String : Any] = [ - "url": url - ] - if IABController != nil { - arguments["uuid"] = IABController!.uuid - } - if let channel = getChannel() { - channel.invokeMethod("onNavigationStateChange", arguments: arguments) - } - } - public func onScrollChanged(x: Int, y: Int) { var arguments: [String: Any] = ["x": x, "y": y] if IABController != nil { @@ -2239,8 +2193,13 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } } - public func onCreateWindow(url: URL) { - var arguments: [String: Any] = ["url": url.absoluteString] + public func onCreateWindow(url: URL, navigationType: WKNavigationType) { + var arguments: [String: Any?] = [ + "url": url.absoluteString, + "androidIsDialog": nil, + "androidIsUserGesture": nil, + "iosWKNavigationType": navigationType.rawValue + ] if IABController != nil { arguments["uuid"] = IABController!.uuid } @@ -2348,6 +2307,19 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } } + public func onUpdateVisitedHistory(url: String) { + var arguments: [String: Any?] = [ + "url": url, + "androidIsReload": nil + ] + if IABController != nil { + arguments["uuid"] = IABController!.uuid + } + if let channel = getChannel() { + channel.invokeMethod("onUpdateVisitedHistory", arguments: arguments) + } + } + public func onCallJsHandler(handlerName: String, _callHandlerID: Int64, args: String) { var arguments: [String: Any] = ["handlerName": handlerName, "args": args] if IABController != nil { @@ -2414,18 +2386,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi self.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) } - } else if message.name == "onNavigationStateChange" { - if let resource = convertToDictionary(text: message.body as! String) { - let url = resource["url"] as! String - - self.onNavigationStateChange(url: url) - } - } } private func getChannel() -> FlutterMethodChannel? { - return (IABController != nil) ? SwiftFlutterPlugin.instance!.channel! : ((IAWController != nil) ? IAWController!.channel! : nil); + return (IABController != nil) ? SwiftFlutterPlugin.instance!.channel! : ((channel != nil) ? channel! : nil); } public func findAllAsync(find: String?, completionHandler: ((Any?, Error?) -> Void)?) { @@ -2487,7 +2452,21 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi printController.present(animated: true, completionHandler: completionHandler) } - public override func removeFromSuperview() { + public func getContentHeight() -> Int64 { + return Int64(scrollView.contentSize.height) + } + + public func zoomBy(zoomFactor: Float) { + let currentZoomScale = scrollView.zoomScale + scrollView.setZoomScale(currentZoomScale * CGFloat(zoomFactor), animated: false) + } + + public func getScale() -> Float { + return Float(scrollView.zoomScale) + } + + public func dispose() { + stopLoading() configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog") configuration.userContentController.removeScriptMessageHandler(forName: "consoleDebug") configuration.userContentController.removeScriptMessageHandler(forName: "consoleError") @@ -2495,16 +2474,21 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi configuration.userContentController.removeScriptMessageHandler(forName: "consoleWarn") configuration.userContentController.removeScriptMessageHandler(forName: "callHandler") configuration.userContentController.removeScriptMessageHandler(forName: "onFindResultReceived") - configuration.userContentController.removeScriptMessageHandler(forName: "onNavigationStateChange") configuration.userContentController.removeAllUserScripts() - removeObserver(self, forKeyPath: "estimatedProgress") - super.removeFromSuperview() + removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) + removeObserver(self, forKeyPath: #keyPath(WKWebView.url)) + if #available(iOS 11.0, *) { + configuration.userContentController.removeAllContentRuleLists() + } uiDelegate = nil navigationDelegate = nil scrollView.delegate = nil - IAWController?.channel?.setMethodCallHandler(nil) IABController?.webView = nil - IAWController?.webView = nil isPausedTimersCompletionHandler = nil + super.removeFromSuperview() + } + + deinit { + print("InAppWebView - dealloc") } } diff --git a/ios/Classes/InAppWebViewOptions.swift b/ios/Classes/InAppWebViewOptions.swift index be31ef28..f93cc68c 100755 --- a/ios/Classes/InAppWebViewOptions.swift +++ b/ios/Classes/InAppWebViewOptions.swift @@ -48,6 +48,7 @@ public class InAppWebViewOptions: Options { var dataDetectorTypes: [String] = ["NONE"] // WKDataDetectorTypeNone var preferredContentMode = 0 var sharedCookiesEnabled = false + var automaticallyAdjustsScrollIndicatorInsets = false override init(){ super.init() diff --git a/ios/Classes/LeakAvoider.swift b/ios/Classes/LeakAvoider.swift new file mode 100644 index 00000000..4723ce79 --- /dev/null +++ b/ios/Classes/LeakAvoider.swift @@ -0,0 +1,25 @@ +// +// LeakAvoider.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 15/12/2019. +// + +import Foundation + +public class LeakAvoider: NSObject { + weak var delegate : FlutterMethodCallDelegate? + + init(delegate: FlutterMethodCallDelegate) { + self.delegate = delegate + super.init() + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + self.delegate?.handle(call, result: result) + } + + deinit { + print("LeakAvoider - dealloc") + } +} diff --git a/ios/Classes/MyWebStorageManager.swift b/ios/Classes/MyWebStorageManager.swift new file mode 100644 index 00000000..8daaeb23 --- /dev/null +++ b/ios/Classes/MyWebStorageManager.swift @@ -0,0 +1,93 @@ +// +// MyWebStorageManager.swift +// connectivity +// +// Created by Lorenzo Pichilli on 16/12/2019. +// + +import Foundation +import WebKit + +@available(iOS 9.0, *) +class MyWebStorageManager: NSObject, FlutterPlugin { + + static var registrar: FlutterPluginRegistrar? + static var channel: FlutterMethodChannel? + static var websiteDataStore: WKWebsiteDataStore? + + static func register(with registrar: FlutterPluginRegistrar) { + + } + + init(registrar: FlutterPluginRegistrar) { + super.init() + MyWebStorageManager.registrar = registrar + MyWebStorageManager.websiteDataStore = WKWebsiteDataStore.default() + + MyWebStorageManager.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_webstoragemanager", binaryMessenger: registrar.messenger()) + registrar.addMethodCallDelegate(self, channel: MyWebStorageManager.channel!) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? NSDictionary + switch call.method { + case "fetchDataRecords": + let dataTypes = Set(arguments!["dataTypes"] as! [String]) + MyWebStorageManager.fetchDataRecords(dataTypes: dataTypes, result: result) + break + case "removeDataFor": + let dataTypes = Set(arguments!["dataTypes"] as! [String]) + let recordList = arguments!["recordList"] as! [[String: Any?]] + MyWebStorageManager.removeDataFor(dataTypes: dataTypes, recordList: recordList, result: result) + break + case "removeDataModifiedSince": + let dataTypes = Set(arguments!["dataTypes"] as! [String]) + let timestamp = arguments!["timestamp"] as! Int64 + MyWebStorageManager.removeDataModifiedSince(dataTypes: dataTypes, timestamp: timestamp, result: result) + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + public static func fetchDataRecords(dataTypes: Set, result: @escaping FlutterResult) { + var recordList: [[String: Any?]] = [] + MyWebStorageManager.websiteDataStore!.fetchDataRecords(ofTypes: dataTypes) { (data) in + for record in data { + recordList.append([ + "displayName": record.displayName, + "dataTypes": record.dataTypes.map({ (dataType) -> String in + return dataType + }) + ]) + } + result(recordList) + } + } + + public static func removeDataFor(dataTypes: Set, recordList: [[String: Any?]], result: @escaping FlutterResult) { + var records: [WKWebsiteDataRecord] = [] + MyWebStorageManager.websiteDataStore!.fetchDataRecords(ofTypes: dataTypes) { (data) in + for record in data { + for r in recordList { + let displayName = r["displayName"] as! String + if (record.displayName == displayName) { + records.append(record) + break + } + } + } + MyWebStorageManager.websiteDataStore!.removeData(ofTypes: dataTypes, for: records) { + result(true) + } + } + } + + public static func removeDataModifiedSince(dataTypes: Set, timestamp: Int64, result: @escaping FlutterResult) { + let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp)) + MyWebStorageManager.websiteDataStore!.removeData(ofTypes: dataTypes, modifiedSince: date as Date) { + result(true) + } + } +} diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift index b7955457..3c5f557f 100755 --- a/ios/Classes/SwiftFlutterPlugin.swift +++ b/ios/Classes/SwiftFlutterPlugin.swift @@ -61,6 +61,9 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { if #available(iOS 11.0, *) { MyCookieManager(registrar: registrar) } + if #available(iOS 9.0, *) { + MyWebStorageManager(registrar: registrar) + } CredentialDatabase(registrar: registrar) } @@ -254,6 +257,39 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { self.clearCache(uuid: uuid) result(true) break + case "scrollTo": + let x = arguments!["x"] as! Int + let y = arguments!["y"] as! Int + self.scrollTo(uuid: uuid, x: x, y: y) + result(true) + break + case "scrollBy": + let x = arguments!["x"] as! Int + let y = arguments!["y"] as! Int + self.scrollTo(uuid: uuid, x: x, y: y) + result(true) + break + case "pauseTimers": + self.pauseTimers(uuid: uuid) + result(true) + break + case "resumeTimers": + self.resumeTimers(uuid: uuid) + result(true) + break + case "printCurrentPage": + self.printCurrentPage(uuid: uuid, result: result) + break + case "getContentHeight": + result(self.getContentHeight(uuid: uuid)) + break + case "reloadFromOrigin": + self.reloadFromOrigin(uuid: uuid) + result(true) + break + case "getScale": + result(self.getScale(uuid: uuid)) + break default: result(FlutterMethodNotImplemented) break @@ -690,33 +726,33 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } } - func onBrowserCreated(uuid: String, webView: WKWebView) { + public func onBrowserCreated(uuid: String, webView: WKWebView) { if let webViewController = self.webViewControllers[uuid] { self.channel!.invokeMethod("onBrowserCreated", arguments: ["uuid": uuid]) } } - func onExit(uuid: String) { + public func onExit(uuid: String) { self.channel!.invokeMethod("onExit", arguments: ["uuid": uuid]) } - func onChromeSafariBrowserOpened(uuid: String) { + public func onChromeSafariBrowserOpened(uuid: String) { if self.safariViewControllers[uuid] != nil { self.channel!.invokeMethod("onChromeSafariBrowserOpened", arguments: ["uuid": uuid]) } } - func onChromeSafariBrowserLoaded(uuid: String) { + public func onChromeSafariBrowserLoaded(uuid: String) { if self.safariViewControllers[uuid] != nil { self.channel!.invokeMethod("onChromeSafariBrowserLoaded", arguments: ["uuid": uuid]) } } - func onChromeSafariBrowserClosed(uuid: String) { + public func onChromeSafariBrowserClosed(uuid: String) { self.channel!.invokeMethod("onChromeSafariBrowserClosed", arguments: ["uuid": uuid]) } - func safariExit(uuid: String) { + public func safariExit(uuid: String) { if let safariViewController = self.safariViewControllers[uuid] { if #available(iOS 9.0, *) { (safariViewController as! SafariViewController).statusDelegate = nil @@ -727,7 +763,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } } - func browserExit(uuid: String) { + public func browserExit(uuid: String) { if let webViewController = self.webViewControllers[uuid] { // Set navigationDelegate to nil to ensure no callbacks are received from it. webViewController?.navigationDelegate = nil @@ -743,33 +779,33 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } } - func setOptions(uuid: String, options: InAppBrowserOptions, optionsMap: [String: Any]) { + public func setOptions(uuid: String, options: InAppBrowserOptions, optionsMap: [String: Any]) { if let webViewController = self.webViewControllers[uuid] { webViewController!.setOptions(newOptions: options, newOptionsMap: optionsMap) } } - func getOptions(uuid: String) -> [String: Any]? { + public func getOptions(uuid: String) -> [String: Any]? { if let webViewController = self.webViewControllers[uuid] { return webViewController!.getOptions() } return nil } - func getCopyBackForwardList(uuid: String) -> [String: Any]? { + public func getCopyBackForwardList(uuid: String) -> [String: Any]? { if let webViewController = self.webViewControllers[uuid] { return webViewController!.webView.getCopyBackForwardList() } return nil } - func findAllAsync(uuid: String, find: String) { + public func findAllAsync(uuid: String, find: String) { if let webViewController = self.webViewControllers[uuid] { webViewController!.webView.findAllAsync(find: find, completionHandler: nil) } } - func findNext(uuid: String, forward: Bool, result: @escaping FlutterResult) { + public func findNext(uuid: String, forward: Bool, result: @escaping FlutterResult) { if let webViewController = self.webViewControllers[uuid] { webViewController!.webView.findNext(forward: forward, completionHandler: {(value, error) in if error != nil { @@ -783,7 +819,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } } - func clearMatches(uuid: String, result: @escaping FlutterResult) { + public func clearMatches(uuid: String, result: @escaping FlutterResult) { if let webViewController = self.webViewControllers[uuid] { webViewController!.webView.clearMatches(completionHandler: {(value, error) in if error != nil { @@ -797,12 +833,69 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } } - func clearCache(uuid: String) { + public func clearCache(uuid: String) { if let webViewController = self.webViewControllers[uuid] { webViewController!.webView.clearCache() } } + public func scrollTo(uuid: String, x: Int, y: Int) { + if let webViewController = self.webViewControllers[uuid] { + webViewController!.webView.scrollTo(x: x, y: y) + } + } + + public func scrollBy(uuid: String, x: Int, y: Int) { + if let webViewController = self.webViewControllers[uuid] { + webViewController!.webView.scrollBy(x: x, y: y) + } + } + + public func pauseTimers(uuid: String) { + if let webViewController = self.webViewControllers[uuid] { + webViewController!.webView.pauseTimers() + } + } + + public func resumeTimers(uuid: String) { + if let webViewController = self.webViewControllers[uuid] { + webViewController!.webView.resumeTimers() + } + } + + public func printCurrentPage(uuid: String, result: @escaping FlutterResult) { + if let webViewController = self.webViewControllers[uuid] { + webViewController!.webView.printCurrentPage(printCompletionHandler: {(completed, error) in + if !completed, let e = error { + result(false) + return + } + result(true) + }) + } else { + result(false) + } + } + + public func getContentHeight(uuid: String) -> Int64? { + if let webViewController = self.webViewControllers[uuid] { + return webViewController!.webView.getContentHeight() + } + return nil + } + + public func reloadFromOrigin(uuid: String) { + if let webViewController = self.webViewControllers[uuid] { + webViewController!.webView.reloadFromOrigin() + } + } + + public func getScale(uuid: String) -> Float? { + if let webViewController = self.webViewControllers[uuid] { + return webViewController!.webView.getScale() + } + return nil + } } // Helper function inserted by Swift 4.2 migrator. diff --git a/ios/Classes/TestWebView.swift b/ios/Classes/TestWebView.swift new file mode 100644 index 00000000..8e77f07a --- /dev/null +++ b/ios/Classes/TestWebView.swift @@ -0,0 +1,30 @@ +// +// TestWebView.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 14/12/2019. +// + +import Flutter +import Foundation +import WebKit + +public class TestWebView: WKWebView { + override init(frame: CGRect, configuration: WKWebViewConfiguration) { + super.init(frame: frame, configuration: configuration) + } + + required public init(coder aDecoder: NSCoder) { + super.init(coder: aDecoder)! + } + + public override func removeFromSuperview() { + configuration.userContentController.removeAllUserScripts() + super.removeFromSuperview() + print("\n\n DISPOSE \n\n") + } + + deinit { + print("dealloc") // never called + } +} diff --git a/lib/flutter_inappwebview.dart b/lib/flutter_inappwebview.dart index 11846217..81910241 100644 --- a/lib/flutter_inappwebview.dart +++ b/lib/flutter_inappwebview.dart @@ -32,3 +32,4 @@ export 'src/in_app_localhost_server.dart'; export 'src/webview_options.dart'; export 'src/content_blocker.dart'; export 'src/http_auth_credentials_database.dart'; +export 'src/web_storage_manager.dart'; diff --git a/lib/src/chrome_safari_browser.dart b/lib/src/chrome_safari_browser.dart index 6bb33df8..cd5b869c 100644 --- a/lib/src/chrome_safari_browser.dart +++ b/lib/src/chrome_safari_browser.dart @@ -63,30 +63,30 @@ class ChromeSafariBrowser { Map optionsMap = {}; if (Platform.isAndroid) - optionsMap.addAll(options.androidChromeCustomTabsOptions?.toMap() ?? {}); + optionsMap.addAll(options.android?.toMap() ?? {}); else if (Platform.isIOS) - optionsMap.addAll(options.iosSafariOptions?.toMap() ?? {}); + optionsMap.addAll(options.ios?.toMap() ?? {}); Map optionsFallbackMap = {}; if (optionsFallback != null) { optionsFallbackMap - .addAll(optionsFallback.inAppBrowserOptions?.toMap() ?? {}); + .addAll(optionsFallback.crossPlatform?.toMap() ?? {}); optionsFallbackMap.addAll(optionsFallback - .inAppWebViewWidgetOptions?.inAppWebViewOptions + .inAppWebViewWidgetOptions?.crossPlatform ?.toMap() ?? {}); if (Platform.isAndroid) { optionsFallbackMap - .addAll(optionsFallback.androidInAppBrowserOptions?.toMap() ?? {}); + .addAll(optionsFallback.android?.toMap() ?? {}); optionsFallbackMap.addAll(optionsFallback - .inAppWebViewWidgetOptions?.androidInAppWebViewOptions + .inAppWebViewWidgetOptions?.android ?.toMap() ?? {}); } else if (Platform.isIOS) { optionsFallbackMap - .addAll(optionsFallback.iosInAppBrowserOptions?.toMap() ?? {}); + .addAll(optionsFallback.ios?.toMap() ?? {}); optionsFallbackMap.addAll(optionsFallback - .inAppWebViewWidgetOptions?.iosInAppWebViewOptions + .inAppWebViewWidgetOptions?.ios ?.toMap() ?? {}); } diff --git a/lib/src/in_app_browser.dart b/lib/src/in_app_browser.dart index e9de3a39..83801f28 100644 --- a/lib/src/in_app_browser.dart +++ b/lib/src/in_app_browser.dart @@ -62,19 +62,19 @@ class InAppBrowser { Map optionsMap = {}; - optionsMap.addAll(options.inAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.crossPlatform?.toMap() ?? {}); optionsMap.addAll( - options.inAppWebViewWidgetOptions?.inAppWebViewOptions?.toMap() ?? {}); + options.inAppWebViewWidgetOptions?.crossPlatform?.toMap() ?? {}); if (Platform.isAndroid) { - optionsMap.addAll(options.androidInAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.android?.toMap() ?? {}); optionsMap.addAll(options - .inAppWebViewWidgetOptions?.androidInAppWebViewOptions + .inAppWebViewWidgetOptions?.android ?.toMap() ?? {}); } else if (Platform.isIOS) { - optionsMap.addAll(options.iosInAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.ios?.toMap() ?? {}); optionsMap.addAll( - options.inAppWebViewWidgetOptions?.iosInAppWebViewOptions?.toMap() ?? + options.inAppWebViewWidgetOptions?.ios?.toMap() ?? {}); } @@ -132,19 +132,19 @@ class InAppBrowser { Map optionsMap = {}; - optionsMap.addAll(options.inAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.crossPlatform?.toMap() ?? {}); optionsMap.addAll( - options.inAppWebViewWidgetOptions?.inAppWebViewOptions?.toMap() ?? {}); + options.inAppWebViewWidgetOptions?.crossPlatform?.toMap() ?? {}); if (Platform.isAndroid) { - optionsMap.addAll(options.androidInAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.android?.toMap() ?? {}); optionsMap.addAll(options - .inAppWebViewWidgetOptions?.androidInAppWebViewOptions + .inAppWebViewWidgetOptions?.android ?.toMap() ?? {}); } else if (Platform.isIOS) { - optionsMap.addAll(options.iosInAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.ios?.toMap() ?? {}); optionsMap.addAll( - options.inAppWebViewWidgetOptions?.iosInAppWebViewOptions?.toMap() ?? + options.inAppWebViewWidgetOptions?.ios?.toMap() ?? {}); } @@ -166,7 +166,7 @@ class InAppBrowser { /// ///The [encoding] parameter specifies the encoding of the data. The default value is `"utf8"`. /// - ///The [historyUrl] parameter is the URL to use as the history entry. The default value is `about:blank`. If non-null, this must be a valid URL. This parameter is used only on Android. + ///The [androidHistoryUrl] parameter is the URL to use as the history entry. The default value is `about:blank`. If non-null, this must be a valid URL. This parameter is used only on Android. /// ///The [options] parameter specifies the options for the [InAppBrowser]. Future openData( @@ -174,25 +174,25 @@ class InAppBrowser { String mimeType = "text/html", String encoding = "utf8", String baseUrl = "about:blank", - String historyUrl = "about:blank", + String androidHistoryUrl = "about:blank", InAppBrowserClassOptions options}) async { assert(data != null); Map optionsMap = {}; - optionsMap.addAll(options.inAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.crossPlatform?.toMap() ?? {}); optionsMap.addAll( - options.inAppWebViewWidgetOptions?.inAppWebViewOptions?.toMap() ?? {}); + options.inAppWebViewWidgetOptions?.crossPlatform?.toMap() ?? {}); if (Platform.isAndroid) { - optionsMap.addAll(options.androidInAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.android?.toMap() ?? {}); optionsMap.addAll(options - .inAppWebViewWidgetOptions?.androidInAppWebViewOptions + .inAppWebViewWidgetOptions?.android ?.toMap() ?? {}); } else if (Platform.isIOS) { - optionsMap.addAll(options.iosInAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.ios?.toMap() ?? {}); optionsMap.addAll( - options.inAppWebViewWidgetOptions?.iosInAppWebViewOptions?.toMap() ?? + options.inAppWebViewWidgetOptions?.ios?.toMap() ?? {}); } @@ -263,19 +263,19 @@ class InAppBrowser { Map optionsMap = {}; - optionsMap.addAll(options.inAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.crossPlatform?.toMap() ?? {}); optionsMap.addAll( - options.inAppWebViewWidgetOptions?.inAppWebViewOptions?.toMap() ?? {}); + options.inAppWebViewWidgetOptions?.crossPlatform?.toMap() ?? {}); if (Platform.isAndroid) { - optionsMap.addAll(options.androidInAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.android?.toMap() ?? {}); optionsMap.addAll(options - .inAppWebViewWidgetOptions?.androidInAppWebViewOptions + .inAppWebViewWidgetOptions?.android ?.toMap() ?? {}); } else if (Platform.isIOS) { - optionsMap.addAll(options.iosInAppBrowserOptions?.toMap() ?? {}); + optionsMap.addAll(options.ios?.toMap() ?? {}); optionsMap.addAll( - options.inAppWebViewWidgetOptions?.iosInAppWebViewOptions?.toMap() ?? + options.inAppWebViewWidgetOptions?.ios?.toMap() ?? {}); } @@ -299,22 +299,22 @@ class InAppBrowser { await ChannelManager.channel.invokeMethod('getOptions', args); if (options != null) { options = options.cast(); - inAppBrowserClassOptions.inAppBrowserOptions = + inAppBrowserClassOptions.crossPlatform = InAppBrowserOptions.fromMap(options); inAppBrowserClassOptions.inAppWebViewWidgetOptions = InAppWebViewWidgetOptions(); - inAppBrowserClassOptions.inAppWebViewWidgetOptions.inAppWebViewOptions = + inAppBrowserClassOptions.inAppWebViewWidgetOptions.crossPlatform = InAppWebViewOptions.fromMap(options); if (Platform.isAndroid) { - inAppBrowserClassOptions.androidInAppBrowserOptions = + inAppBrowserClassOptions.android = AndroidInAppBrowserOptions.fromMap(options); inAppBrowserClassOptions - .inAppWebViewWidgetOptions.androidInAppWebViewOptions = + .inAppWebViewWidgetOptions.android = AndroidInAppWebViewOptions.fromMap(options); } else if (Platform.isIOS) { - inAppBrowserClassOptions.iosInAppBrowserOptions = - IosInAppBrowserOptions.fromMap(options); + inAppBrowserClassOptions.ios = + IOSInAppBrowserOptions.fromMap(options); inAppBrowserClassOptions.inAppWebViewWidgetOptions - .iosInAppWebViewOptions = IosInAppWebViewOptions.fromMap(options); + .ios = IOSInAppWebViewOptions.fromMap(options); } } @@ -403,21 +403,10 @@ class InAppBrowser { ///Event fired when the [InAppBrowser] webview requests the host application to create a new window, ///for example when trying to open a link with `target="_blank"` or when `window.open()` is called by JavaScript side. /// - ///[url] represents the url of the request. + ///[onCreateWindowRequest] represents the request. /// ///**NOTE**: on Android you need to set [AndroidInAppWebViewOptions.supportMultipleWindows] option to `true`. - void onCreateWindow(String url) {} - - ///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin. - ///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https. - ///On non-secure origins geolocation requests are automatically denied. - /// - ///[origin] represents the origin of the web content attempting to use the Geolocation API. - /// - ///**NOTE**: available only on Android. - // ignore: missing_return - Future - onGeolocationPermissionsShowPrompt(String origin) {} + void onCreateWindow(OnCreateWindowRequest onCreateWindowRequest) {} ///Event fired when javascript calls the `alert()` method to display an alert dialog. ///If [JsAlertResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog. @@ -441,18 +430,6 @@ class InAppBrowser { // ignore: missing_return Future onJsPrompt(String message, String defaultValue) {} - ///Event fired when the WebView notifies that a loading URL has been flagged by Safe Browsing. - ///The default behavior is to show an interstitial to the user, with the reporting checkbox visible. - /// - ///[url] represents the url of the request. - /// - ///[threatType] represents the reason the resource was caught by Safe Browsing, corresponding to a [SafeBrowsingThreat]. - /// - ///**NOTE**: available only on Android. - // ignore: missing_return - Future onSafeBrowsingHit( - String url, SafeBrowsingThreat threatType) {} - ///Event fired when the WebView received an HTTP authentication request. The default behavior is to cancel the request. /// ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [HttpAuthChallenge]. @@ -525,13 +502,34 @@ class InAppBrowser { // ignore: missing_return Future shouldInterceptFetchRequest(FetchRequest fetchRequest) {} - ///Event fired when the navigation state of the WebView changes throught the usage of - ///javascript **[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)** functions (`pushState()`, `replaceState()`) and `onpopstate` event. + ///Event fired when the host application updates its visited links database. + ///This event is also fired when the navigation state of the [InAppWebView] changes through the usage of + ///javascript **[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)** functions (`pushState()`, `replaceState()`) and `onpopstate` event + ///or, also, when the javascript `window.location` changes without reloading the webview (for example appending or modifying an hash to the url). /// - ///Also, the event is fired when the javascript `window.location` changes without reloading the webview (for example appending or modifying an hash to the url). + ///[url] represents the url being visited. /// - ///[url] represents the new url. - void onNavigationStateChange(String url) {} + ///[androidIsReload] indicates if this url is being reloaded. Available only on Android. + void onUpdateVisitedHistory(String url, bool androidIsReload) {} + + ///Event fired when `window.print()` is called from JavaScript side. + /// + ///[url] represents the url on which is called. + /// + ///**NOTE**: available on Android 21+. + void onPrint(String url) {} + + ///Event fired when the WebView notifies that a loading URL has been flagged by Safe Browsing. + ///The default behavior is to show an interstitial to the user, with the reporting checkbox visible. + /// + ///[url] represents the url of the request. + /// + ///[threatType] represents the reason the resource was caught by Safe Browsing, corresponding to a [SafeBrowsingThreat]. + /// + ///**NOTE**: available only on Android 27+. + // ignore: missing_return + Future androidOnSafeBrowsingHit( + String url, SafeBrowsingThreat threatType) {} ///Event fired when the WebView is requesting permission to access the specified resources and the permission currently isn't granted or denied. /// @@ -541,15 +539,25 @@ class InAppBrowser { /// ///**NOTE**: available only on Android 23+. // ignore: missing_return - Future onPermissionRequest( + Future androidOnPermissionRequest( String origin, List resources) {} - ///Event fired when `window.print()` is called from JavaScript side. + ///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin. + ///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https. + ///On non-secure origins geolocation requests are automatically denied. /// - ///[url] represents the url on which is called. + ///[origin] represents the origin of the web content attempting to use the Geolocation API. /// - ///**NOTE**: available on Android 21+. - void onPrint(String url) {} + ///**NOTE**: available only on Android. + Future + // ignore: missing_return + androidOnGeolocationPermissionsShowPrompt(String origin) {} + + ///Notify the host application that a request for Geolocation permissions, made with a previous call to [androidOnGeolocationPermissionsShowPrompt] has been canceled. + ///Any related UI should therefore be hidden. + /// + ///**NOTE**: available only on Android. + void androidOnGeolocationPermissionsHidePrompt() {} void throwIsAlreadyOpened({String message = ''}) { if (this.isOpened()) { diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart index a25caf97..56e1a774 100755 --- a/lib/src/in_app_webview.dart +++ b/lib/src/in_app_webview.dart @@ -115,23 +115,12 @@ class InAppWebView extends StatefulWidget { ///Event fired when the [InAppWebView] requests the host application to create a new window, ///for example when trying to open a link with `target="_blank"` or when `window.open()` is called by JavaScript side. /// - ///[url] represents the url of the request. + ///[onCreateWindowRequest] represents the request. /// ///**NOTE**: on Android you need to set [AndroidInAppWebViewOptions.supportMultipleWindows] option to `true`. - final void Function(InAppWebViewController controller, String url) + final void Function(InAppWebViewController controller, OnCreateWindowRequest onCreateWindowRequest) onCreateWindow; - ///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin. - ///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https. - ///On non-secure origins geolocation requests are automatically denied. - /// - ///[origin] represents the origin of the web content attempting to use the Geolocation API. - /// - ///**NOTE**: available only on Android. - final Future Function( - InAppWebViewController controller, String origin) - onGeolocationPermissionsShowPrompt; - ///Event fired when javascript calls the `alert()` method to display an alert dialog. ///If [JsAlertResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog. /// @@ -155,17 +144,6 @@ class InAppWebView extends StatefulWidget { final Future Function(InAppWebViewController controller, String message, String defaultValue) onJsPrompt; - ///Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing. - ///The default behavior is to show an interstitial to the user, with the reporting checkbox visible. - /// - ///[url] represents the url of the request. - /// - ///[threatType] represents the reason the resource was caught by Safe Browsing, corresponding to a [SafeBrowsingThreat]. - /// - ///**NOTE**: available only on Android. - final Future Function(InAppWebViewController controller, - String url, SafeBrowsingThreat threatType) onSafeBrowsingHit; - ///Event fired when the WebView received an HTTP authentication request. The default behavior is to cancel the request. /// ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [HttpAuthChallenge]. @@ -258,14 +236,33 @@ class InAppWebView extends StatefulWidget { InAppWebViewController controller, FetchRequest fetchRequest) shouldInterceptFetchRequest; - ///Event fired when the navigation state of the [InAppWebView] changes through the usage of - ///javascript **[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)** functions (`pushState()`, `replaceState()`) and `onpopstate` event. + ///Event fired when the host application updates its visited links database. + ///This event is also fired when the navigation state of the [InAppWebView] changes through the usage of + ///javascript **[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)** functions (`pushState()`, `replaceState()`) and `onpopstate` event + ///or, also, when the javascript `window.location` changes without reloading the webview (for example appending or modifying an hash to the url). /// - ///Also, the event is fired when the javascript `window.location` changes without reloading the webview (for example appending or modifying an hash to the url). + ///[url] represents the url being visited. /// - ///[url] represents the new url. - final void Function(InAppWebViewController controller, String url) - onNavigationStateChange; + ///[androidIsReload] indicates if this url is being reloaded. Available only on Android. + final void Function(InAppWebViewController controller, String url, bool androidIsReload) onUpdateVisitedHistory; + + ///Event fired when `window.print()` is called from JavaScript side. + /// + ///[url] represents the url on which is called. + /// + ///**NOTE**: available on Android 21+. + final void Function(InAppWebViewController controller, String url) onPrint; + + ///Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing. + ///The default behavior is to show an interstitial to the user, with the reporting checkbox visible. + /// + ///[url] represents the url of the request. + /// + ///[threatType] represents the reason the resource was caught by Safe Browsing, corresponding to a [SafeBrowsingThreat]. + /// + ///**NOTE**: available only on Android 27+. + final Future Function(InAppWebViewController controller, + String url, SafeBrowsingThreat threatType) androidOnSafeBrowsingHit; ///Event fired when the WebView is requesting permission to access the specified resources and the permission currently isn't granted or denied. /// @@ -277,14 +274,24 @@ class InAppWebView extends StatefulWidget { final Future Function( InAppWebViewController controller, String origin, - List resources) onPermissionRequest; + List resources) androidOnPermissionRequest; - ///Event fired when `window.print()` is called from JavaScript side. + ///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin. + ///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https. + ///On non-secure origins geolocation requests are automatically denied. /// - ///[url] represents the url on which is called. + ///[origin] represents the origin of the web content attempting to use the Geolocation API. /// - ///**NOTE**: available on Android 21+. - final void Function(InAppWebViewController controller, String url) onPrint; + ///**NOTE**: available only on Android. + final Future Function( + InAppWebViewController controller, String origin) + androidOnGeolocationPermissionsShowPrompt; + + ///Notify the host application that a request for Geolocation permissions, made with a previous call to [androidOnGeolocationPermissionsShowPrompt] has been canceled. + ///Any related UI should therefore be hidden. + /// + ///**NOTE**: available only on Android. + final Future Function(InAppWebViewController controller) androidOnGeolocationPermissionsHidePrompt; ///Initial url that will be loaded. final String initialUrl; @@ -330,11 +337,9 @@ class InAppWebView extends StatefulWidget { this.onDownloadStart, this.onLoadResourceCustomScheme, this.onCreateWindow, - this.onGeolocationPermissionsShowPrompt, this.onJsAlert, this.onJsConfirm, this.onJsPrompt, - this.onSafeBrowsingHit, this.onReceivedHttpAuthRequest, this.onReceivedServerTrustAuthRequest, this.onReceivedClientCertRequest, @@ -343,9 +348,12 @@ class InAppWebView extends StatefulWidget { this.onAjaxReadyStateChange, this.onAjaxProgress, this.shouldInterceptFetchRequest, - this.onNavigationStateChange, - this.onPermissionRequest, + this.onUpdateVisitedHistory, this.onPrint, + this.androidOnSafeBrowsingHit, + this.androidOnPermissionRequest, + this.androidOnGeolocationPermissionsShowPrompt, + this.androidOnGeolocationPermissionsHidePrompt, this.gestureRecognizers, }) : super(key: key); @@ -360,13 +368,13 @@ class _InAppWebViewState extends State { Widget build(BuildContext context) { Map initialOptions = {}; initialOptions - .addAll(widget.initialOptions.inAppWebViewOptions?.toMap() ?? {}); + .addAll(widget.initialOptions.crossPlatform?.toMap() ?? {}); if (Platform.isAndroid) initialOptions.addAll( - widget.initialOptions.androidInAppWebViewOptions?.toMap() ?? {}); + widget.initialOptions.android?.toMap() ?? {}); else if (Platform.isIOS) initialOptions - .addAll(widget.initialOptions.iosInAppWebViewOptions?.toMap() ?? {}); + .addAll(widget.initialOptions.ios?.toMap() ?? {}); if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( @@ -439,10 +447,10 @@ class _InAppWebViewState extends State { } } -/// Controls an [InAppWebView] widget instance. +/// Controls a WebView, such as an [InAppWebView] widget instance or [InAppBrowser] WebView instance. /// -/// An [InAppWebViewController] instance can be obtained by setting the [InAppWebView.onWebViewCreated] -/// callback for an [InAppWebView] widget. +/// If you are using the [InAppWebView] widget, an [InAppWebViewController] instance can be obtained by setting the [InAppWebView.onWebViewCreated] +/// callback. Instead, if you are using an [InAppBrowser] instance, you can get it through the [InAppBrowser.webViewController] attribute. class InAppWebViewController { InAppWebView _widget; MethodChannel _channel; @@ -456,12 +464,20 @@ class InAppWebViewController { String _inAppBrowserUuid; InAppBrowser _inAppBrowser; + ///Android controller that contains only android-specific methods + AndroidInAppWebViewController android; + + ///iOS controller that contains only ios-specific methods + IOSInAppWebViewController ios; + InAppWebViewController(int id, InAppWebView widget) { this._id = id; this._channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); this._channel.setMethodCallHandler(handleMethod); this._widget = widget; + this.android = AndroidInAppWebViewController(this); + this.ios = IOSInAppWebViewController(this); } InAppWebViewController.fromInAppBrowser( @@ -520,7 +536,7 @@ class InAppWebViewController { int iosWKNavigationType = call.arguments["iosWKNavigationType"]; ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest = ShouldOverrideUrlLoadingRequest(url: url, method: method, headers: headers, isForMainFrame: isForMainFrame, - androidHasGesture: androidHasGesture, androidIsRedirect: androidIsRedirect, iosWKNavigationType: IosWKNavigationType.fromValue(iosWKNavigationType)); + androidHasGesture: androidHasGesture, androidIsRedirect: androidIsRedirect, iosWKNavigationType: IOSWKNavigationType.fromValue(iosWKNavigationType)); if (_widget != null && _widget.shouldOverrideUrlLoading != null) return (await _widget.shouldOverrideUrlLoading(this, shouldOverrideUrlLoadingRequest))?.toMap(); @@ -576,22 +592,37 @@ class InAppWebViewController { break; case "onCreateWindow": String url = call.arguments["url"]; + bool androidIsDialog = call.arguments["androidIsDialog"]; + bool androidIsUserGesture = call.arguments["androidIsUserGesture"]; + int iosWKNavigationType = call.arguments["iosWKNavigationType"]; + + OnCreateWindowRequest onCreateWindowRequest = OnCreateWindowRequest(url: url, androidIsDialog: androidIsDialog, androidIsUserGesture: androidIsUserGesture, iosWKNavigationType: IOSWKNavigationType.fromValue(iosWKNavigationType)); + if (_widget != null && _widget.onCreateWindow != null) - _widget.onCreateWindow(this, url); - else if (_inAppBrowser != null) _inAppBrowser.onCreateWindow(url); + _widget.onCreateWindow(this, onCreateWindowRequest); + else if (_inAppBrowser != null) _inAppBrowser.onCreateWindow(onCreateWindowRequest); break; case "onGeolocationPermissionsShowPrompt": String origin = call.arguments["origin"]; if (_widget != null && - _widget.onGeolocationPermissionsShowPrompt != null) - return (await _widget.onGeolocationPermissionsShowPrompt( + _widget.androidOnGeolocationPermissionsShowPrompt != null) + return (await _widget.androidOnGeolocationPermissionsShowPrompt( this, origin)) ?.toMap(); else if (_inAppBrowser != null) return (await _inAppBrowser - .onGeolocationPermissionsShowPrompt(origin)) + .androidOnGeolocationPermissionsShowPrompt(origin)) ?.toMap(); break; + case "onGeolocationPermissionsHidePrompt": + if (_widget != null && + _widget.androidOnGeolocationPermissionsHidePrompt != null) + await _widget.androidOnGeolocationPermissionsHidePrompt( + this); + else if (_inAppBrowser != null) + await _inAppBrowser + .androidOnGeolocationPermissionsHidePrompt(); + break; case "onJsAlert": String message = call.arguments["message"]; if (_widget != null && _widget.onJsAlert != null) @@ -620,11 +651,11 @@ class InAppWebViewController { String url = call.arguments["url"]; SafeBrowsingThreat threatType = SafeBrowsingThreat.fromValue(call.arguments["threatType"]); - if (_widget != null && _widget.onSafeBrowsingHit != null) - return (await _widget.onSafeBrowsingHit(this, url, threatType)) + if (_widget != null && _widget.androidOnSafeBrowsingHit != null) + return (await _widget.androidOnSafeBrowsingHit(this, url, threatType)) ?.toMap(); else if (_inAppBrowser != null) - return (await _inAppBrowser.onSafeBrowsingHit(url, threatType)) + return (await _inAppBrowser.androidOnSafeBrowsingHit(url, threatType)) ?.toMap(); break; case "onReceivedHttpAuthRequest": @@ -695,23 +726,24 @@ class InAppWebViewController { _inAppBrowser.onFindResultReceived( activeMatchOrdinal, numberOfMatches, isDoneCounting); break; - case "onNavigationStateChange": - String url = call.arguments["url"]; - if (_widget != null && _widget.onNavigationStateChange != null) - _widget.onNavigationStateChange(this, url); - else if (_inAppBrowser != null) - _inAppBrowser.onNavigationStateChange(url); - break; case "onPermissionRequest": String origin = call.arguments["origin"]; List resources = call.arguments["resources"].cast(); - if (_widget != null && _widget.onPermissionRequest != null) - return (await _widget.onPermissionRequest(this, origin, resources)) + if (_widget != null && _widget.androidOnPermissionRequest != null) + return (await _widget.androidOnPermissionRequest(this, origin, resources)) ?.toMap(); else if (_inAppBrowser != null) - return (await _inAppBrowser.onPermissionRequest(origin, resources)) + return (await _inAppBrowser.androidOnPermissionRequest(origin, resources)) ?.toMap(); break; + case "onUpdateVisitedHistory": + String url = call.arguments["url"]; + bool androidIsReload = call.arguments["androidIsReload"]; + if (_widget != null && _widget.onUpdateVisitedHistory != null) + _widget.onUpdateVisitedHistory(this, url, androidIsReload); + else if (_inAppBrowser != null) + _inAppBrowser.onUpdateVisitedHistory(url, androidIsReload); + return null; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; // decode args to json @@ -969,7 +1001,7 @@ class InAppWebViewController { var html = ""; InAppWebViewWidgetOptions options = await getOptions(); if (options != null && - options.inAppWebViewOptions.javaScriptEnabled == true) { + options.crossPlatform.javaScriptEnabled == true) { html = await evaluateJavascript( source: "window.document.getElementsByTagName('html')[0].outerHTML;"); if (html != null && html.isNotEmpty) return html; @@ -1158,13 +1190,13 @@ class InAppWebViewController { /// ///The [encoding] parameter specifies the encoding of the data. The default value is `"utf8"`. /// - ///The [historyUrl] parameter is the URL to use as the history entry. The default value is `about:blank`. If non-null, this must be a valid URL. This parameter is used only on Android. + ///The [androidHistoryUrl] parameter is the URL to use as the history entry. The default value is `about:blank`. If non-null, this must be a valid URL. This parameter is used only on Android. Future loadData( {@required String data, String mimeType = "text/html", String encoding = "utf8", String baseUrl = "about:blank", - String historyUrl = "about:blank"}) async { + String androidHistoryUrl = "about:blank"}) async { assert(data != null); Map args = {}; if (_inAppBrowserUuid != null && _inAppBrowser != null) { @@ -1175,7 +1207,7 @@ class InAppWebViewController { args.putIfAbsent('mimeType', () => mimeType); args.putIfAbsent('encoding', () => encoding); args.putIfAbsent('baseUrl', () => baseUrl); - args.putIfAbsent('historyUrl', () => historyUrl); + args.putIfAbsent('historyUrl', () => androidHistoryUrl); await _channel.invokeMethod('loadData', args); } @@ -1464,11 +1496,11 @@ class InAppWebViewController { } Map optionsMap = {}; - optionsMap.addAll(options.inAppWebViewOptions?.toMap() ?? {}); + optionsMap.addAll(options.crossPlatform?.toMap() ?? {}); if (Platform.isAndroid) - optionsMap.addAll(options.androidInAppWebViewOptions?.toMap() ?? {}); + optionsMap.addAll(options.android?.toMap() ?? {}); else if (Platform.isIOS) - optionsMap.addAll(options.iosInAppWebViewOptions?.toMap() ?? {}); + optionsMap.addAll(options.ios?.toMap() ?? {}); args.putIfAbsent('options', () => optionsMap); await _channel.invokeMethod('setOptions', args); @@ -1488,14 +1520,14 @@ class InAppWebViewController { await _channel.invokeMethod('getOptions', args); if (options != null) { options = options.cast(); - inAppWebViewWidgetOptions.inAppWebViewOptions = + inAppWebViewWidgetOptions.crossPlatform = InAppWebViewOptions.fromMap(options); if (Platform.isAndroid) - inAppWebViewWidgetOptions.androidInAppWebViewOptions = + inAppWebViewWidgetOptions.android = AndroidInAppWebViewOptions.fromMap(options); else if (Platform.isIOS) - inAppWebViewWidgetOptions.iosInAppWebViewOptions = - IosInAppWebViewOptions.fromMap(options); + inAppWebViewWidgetOptions.ios = + IOSInAppWebViewOptions.fromMap(options); } return inAppWebViewWidgetOptions; @@ -1533,62 +1565,6 @@ class InAppWebViewController { return WebHistory(list: historyList, currentIndex: currentIndex); } - ///Starts Safe Browsing initialization. - /// - ///URL loads are not guaranteed to be protected by Safe Browsing until after the this method returns true. - ///Safe Browsing is not fully supported on all devices. For those devices this method will returns false. - /// - ///This should not be called if Safe Browsing has been disabled by manifest tag - ///or [AndroidInAppWebViewOptions.safeBrowsingEnabled]. This prepares resources used for Safe Browsing. - /// - ///**NOTE**: available only on Android 27+. - Future startSafeBrowsing() async { - Map args = {}; - if (_inAppBrowserUuid != null && _inAppBrowser != null) { - _inAppBrowser.throwIsNotOpened(); - args.putIfAbsent('uuid', () => _inAppBrowserUuid); - } - return await _channel.invokeMethod('startSafeBrowsing', args); - } - - ///Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks. The list is global for all the WebViews. - /// - /// Each rule should take one of these: - ///| Rule | Example | Matches Subdomain | - ///| -- | -- | -- | - ///| HOSTNAME | example.com | Yes | - ///| .HOSTNAME | .example.com | No | - ///| IPV4_LITERAL | 192.168.1.1 | No | - ///| IPV6_LITERAL_WITH_BRACKETS | [10:20:30:40:50:60:70:80] | No | - /// - ///All other rules, including wildcards, are invalid. The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2). - /// - ///[hosts] represents the list of hosts. This value must never be null. - /// - ///**NOTE**: available only on Android 27+. - Future setSafeBrowsingWhitelist({@required List hosts}) async { - assert(hosts != null); - Map args = {}; - if (_inAppBrowserUuid != null && _inAppBrowser != null) { - _inAppBrowser.throwIsNotOpened(); - args.putIfAbsent('uuid', () => _inAppBrowserUuid); - } - args.putIfAbsent('hosts', () => hosts); - return await _channel.invokeMethod('setSafeBrowsingWhitelist', args); - } - - ///Returns a URL pointing to the privacy policy for Safe Browsing reporting. This value will never be `null`. - /// - ///**NOTE**: available only on Android 27+. - Future getSafeBrowsingPrivacyPolicyUrl() async { - Map args = {}; - if (_inAppBrowserUuid != null && _inAppBrowser != null) { - _inAppBrowser.throwIsNotOpened(); - args.putIfAbsent('uuid', () => _inAppBrowserUuid); - } - return await _channel.invokeMethod('getSafeBrowsingPrivacyPolicyUrl', args); - } - ///Clears all the webview's cache. Future clearCache() async { Map args = {}; @@ -1599,34 +1575,6 @@ class InAppWebViewController { await _channel.invokeMethod('clearCache', args); } - ///Clears the SSL preferences table stored in response to proceeding with SSL certificate errors. - /// - ///**NOTE**: available only on Android. - Future clearSslPreferences() async { - Map args = {}; - if (_inAppBrowserUuid != null && _inAppBrowser != null) { - _inAppBrowser.throwIsNotOpened(); - args.putIfAbsent('uuid', () => _inAppBrowserUuid); - } - await _channel.invokeMethod('clearSslPreferences', args); - } - - ///Clears the client certificate preferences stored in response to proceeding/cancelling client cert requests. - ///Note that WebView automatically clears these preferences when the system keychain is updated. - ///The preferences are shared by all the WebViews that are created by the embedder application. - /// - ///**NOTE**: On iOS certificate-based credentials are never stored permanently. - /// - ///**NOTE**: available on Android 21+. - Future clearClientCertPreferences() async { - Map args = {}; - if (_inAppBrowserUuid != null && _inAppBrowser != null) { - _inAppBrowser.throwIsNotOpened(); - args.putIfAbsent('uuid', () => _inAppBrowserUuid); - } - await _channel.invokeMethod('clearClientCertPreferences', args); - } - ///Finds all instances of find on the page and highlights them. Notifies [onFindResultReceived] listener. /// ///[find] represents the string to find. @@ -1719,31 +1667,6 @@ class InAppWebViewController { await _channel.invokeMethod('scrollBy', args); } - ///Does a best-effort attempt to pause any processing that can be paused safely, such as animations and geolocation. Note that this call does not pause JavaScript. - ///To pause JavaScript globally, use [pauseTimers()]. To resume WebView, call [resume()]. - /// - ///**NOTE**: available only on Android. - Future pause() async { - Map args = {}; - if (_inAppBrowserUuid != null && _inAppBrowser != null) { - _inAppBrowser.throwIsNotOpened(); - args.putIfAbsent('uuid', () => _inAppBrowserUuid); - } - await _channel.invokeMethod('pause', args); - } - - ///Resumes a WebView after a previous call to [pause()]. - /// - ///**NOTE**: available only on Android. - Future resume() async { - Map args = {}; - if (_inAppBrowserUuid != null && _inAppBrowser != null) { - _inAppBrowser.throwIsNotOpened(); - args.putIfAbsent('uuid', () => _inAppBrowserUuid); - } - await _channel.invokeMethod('resume', args); - } - ///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. /// @@ -1781,15 +1704,189 @@ class InAppWebViewController { await _channel.invokeMethod('printCurrentPage', args); } + ///Gets the height of the HTML content. + Future getContentHeight() async { + Map args = {}; + if (_inAppBrowserUuid != null && _inAppBrowser != null) { + _inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _inAppBrowserUuid); + } + return await _channel.invokeMethod('getContentHeight', args); + } + + ///Gets the height of the HTML content. + /// + ///[zoomFactor] represents the zoom factor to apply. On Android, the zoom factor will be clamped to the Webview's zoom limits and, also, this value must be in the range 0.01 to 100.0 inclusive. + /// + ///**NOTE**: available on Android 21+. + Future zoomBy(double zoomFactor) async { + Map args = {}; + if (_inAppBrowserUuid != null && _inAppBrowser != null) { + _inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _inAppBrowserUuid); + } + args.putIfAbsent('zoomFactor', () => zoomFactor); + return await _channel.invokeMethod('zoomBy', args); + } + + ///Gets the current scale of this WebView. + Future getScale() async { + Map args = {}; + if (_inAppBrowserUuid != null && _inAppBrowser != null) { + _inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _inAppBrowserUuid); + } + return await _channel.invokeMethod('getScale', args); + } + ///Gets the default user agent. static Future getDefaultUserAgent() async { Map args = {}; return await _staticChannel.invokeMethod('getDefaultUserAgent', args); } - - /*Future dispose() async { - Map args = {}; - if (Platform.isIOS) - await _channel.invokeMethod('removeFromSuperview', args); - }*/ } + +///InAppWebViewControllerAndroid class represents the Android controller that contains only android-specific methods for the WebView. +class AndroidInAppWebViewController { + + InAppWebViewController _controller; + + AndroidInAppWebViewController(InAppWebViewController controller) { + this._controller = controller; + } + + ///Starts Safe Browsing initialization. + /// + ///URL loads are not guaranteed to be protected by Safe Browsing until after the this method returns true. + ///Safe Browsing is not fully supported on all devices. For those devices this method will returns false. + /// + ///This should not be called if Safe Browsing has been disabled by manifest tag + ///or [AndroidInAppWebViewOptions.safeBrowsingEnabled]. This prepares resources used for Safe Browsing. + /// + ///**NOTE**: available only on Android 27+. + Future startSafeBrowsing() async { + Map args = {}; + if (_controller._inAppBrowserUuid != null && _controller._inAppBrowser != null) { + _controller._inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _controller._inAppBrowserUuid); + } + return await _controller._channel.invokeMethod('startSafeBrowsing', args); + } + + ///Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks. The list is global for all the WebViews. + /// + /// Each rule should take one of these: + ///| Rule | Example | Matches Subdomain | + ///| -- | -- | -- | + ///| HOSTNAME | example.com | Yes | + ///| .HOSTNAME | .example.com | No | + ///| IPV4_LITERAL | 192.168.1.1 | No | + ///| IPV6_LITERAL_WITH_BRACKETS | [10:20:30:40:50:60:70:80] | No | + /// + ///All other rules, including wildcards, are invalid. The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2). + /// + ///[hosts] represents the list of hosts. This value must never be null. + /// + ///**NOTE**: available only on Android 27+. + Future setSafeBrowsingWhitelist({@required List hosts}) async { + assert(hosts != null); + Map args = {}; + if (_controller._inAppBrowserUuid != null && _controller._inAppBrowser != null) { + _controller._inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _controller._inAppBrowserUuid); + } + args.putIfAbsent('hosts', () => hosts); + return await _controller._channel.invokeMethod('setSafeBrowsingWhitelist', args); + } + + ///Returns a URL pointing to the privacy policy for Safe Browsing reporting. This value will never be `null`. + /// + ///**NOTE**: available only on Android 27+. + Future getSafeBrowsingPrivacyPolicyUrl() async { + Map args = {}; + if (_controller._inAppBrowserUuid != null && _controller._inAppBrowser != null) { + _controller._inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _controller._inAppBrowserUuid); + } + return await _controller._channel.invokeMethod('getSafeBrowsingPrivacyPolicyUrl', args); + } + + ///Clears the SSL preferences table stored in response to proceeding with SSL certificate errors. + Future clearSslPreferences() async { + Map args = {}; + if (_controller._inAppBrowserUuid != null && _controller._inAppBrowser != null) { + _controller._inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _controller._inAppBrowserUuid); + } + await _controller._channel.invokeMethod('clearSslPreferences', args); + } + + ///Clears the client certificate preferences stored in response to proceeding/cancelling client cert requests. + ///Note that WebView automatically clears these preferences when the system keychain is updated. + ///The preferences are shared by all the WebViews that are created by the embedder application. + /// + ///**NOTE**: On iOS certificate-based credentials are never stored permanently. + /// + ///**NOTE**: available on Android 21+. + Future clearClientCertPreferences() async { + Map args = {}; + if (_controller._inAppBrowserUuid != null && _controller._inAppBrowser != null) { + _controller._inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _controller._inAppBrowserUuid); + } + await _controller._channel.invokeMethod('clearClientCertPreferences', args); + } + + ///Does a best-effort attempt to pause any processing that can be paused safely, such as animations and geolocation. Note that this call does not pause JavaScript. + ///To pause JavaScript globally, use [pauseTimers()]. To resume WebView, call [resume()]. + Future pause() async { + Map args = {}; + if (_controller._inAppBrowserUuid != null && _controller._inAppBrowser != null) { + _controller._inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _controller._inAppBrowserUuid); + } + await _controller._channel.invokeMethod('pause', args); + } + + ///Resumes a WebView after a previous call to [pause()]. + Future resume() async { + Map args = {}; + if (_controller._inAppBrowserUuid != null && _controller._inAppBrowser != null) { + _controller._inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _controller._inAppBrowserUuid); + } + await _controller._channel.invokeMethod('resume', args); + } + + ///Gets the URL that was originally requested for the current page. + ///This is not always the same as the URL passed to [InAppWebView.onLoadStarted] because although the load for that URL has begun, + ///the current page may not have changed. Also, there may have been redirects resulting in a different URL to that originally requested. + Future getOriginalUrl() async { + Map args = {}; + if (_controller._inAppBrowserUuid != null && _controller._inAppBrowser != null) { + _controller._inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _controller._inAppBrowserUuid); + } + return await _controller._channel.invokeMethod('getOriginalUrl', args); + } + +} + +///InAppWebViewControllerIOS class represents the iOS controller that contains only ios-specific methods for the WebView. +class IOSInAppWebViewController { + InAppWebViewController _controller; + + IOSInAppWebViewController(InAppWebViewController controller) { + this._controller = controller; + } + + ///Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible. + Future reloadFromOrigin() async { + Map args = {}; + if (_controller._inAppBrowserUuid != null && _controller._inAppBrowser != null) { + _controller._inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _controller._inAppBrowserUuid); + } + await _controller._channel.invokeMethod('reloadFromOrigin', args); + } +} \ No newline at end of file diff --git a/lib/src/types.dart b/lib/src/types.dart index ac6ec840..527a9424 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -223,7 +223,7 @@ class WebHistoryItem { ///GeolocationPermissionPromptResponse class. /// -///Class used by the host application to set the Geolocation permission state for an origin during the [onGeolocationPermissionsShowPrompt] event. +///Class used by the host application to set the Geolocation permission state for an origin during the [androidOnGeolocationPermissionsShowPrompt] event. class GeolocationPermissionShowPromptResponse { ///The origin for which permissions are set. String origin; @@ -462,10 +462,10 @@ class SafeBrowsingResponseAction { int get hashCode => _value.hashCode; } -///SafeBrowsingResponse class represents the response used by the [onSafeBrowsingHit] event. +///SafeBrowsingResponse class represents the response used by the [androidOnSafeBrowsingHit] event. ///It is used to indicate an action to take when hitting a malicious URL. class SafeBrowsingResponse { - ///If reporting is enabled, all reports will be sent according to the privacy policy referenced by [InAppWebViewController.getSafeBrowsingPrivacyPolicyUrl]. + ///If reporting is enabled, all reports will be sent according to the privacy policy referenced by [InAppWebViewController.androidGetSafeBrowsingPrivacyPolicyUrl]. bool report; ///Indicate the [SafeBrowsingResponseAction] to take when hitting a malicious URL. @@ -944,13 +944,13 @@ class AndroidInAppWebViewMixedContentMode { int get hashCode => _value.hashCode; } -///IosInAppWebViewSelectionGranularity class represents an iOS-specific class used to set the level of granularity with which the user can interactively select content in the web view. -class IosInAppWebViewSelectionGranularity { +///IOSInAppWebViewSelectionGranularity class represents an iOS-specific class used to set the level of granularity with which the user can interactively select content in the web view. +class IOSInAppWebViewSelectionGranularity { final int _value; - const IosInAppWebViewSelectionGranularity._internal(this._value); - static IosInAppWebViewSelectionGranularity fromValue(int value) { + const IOSInAppWebViewSelectionGranularity._internal(this._value); + static IOSInAppWebViewSelectionGranularity fromValue(int value) { if (value != null && value >= 0 && value <= 1) - return IosInAppWebViewSelectionGranularity._internal(value); + return IOSInAppWebViewSelectionGranularity._internal(value); return null; } @@ -967,11 +967,11 @@ class IosInAppWebViewSelectionGranularity { } ///Selection granularity varies automatically based on the selection. - static const DYNAMIC = const IosInAppWebViewSelectionGranularity._internal(0); + static const DYNAMIC = const IOSInAppWebViewSelectionGranularity._internal(0); ///Selection endpoints can be placed at any character boundary. static const CHARACTER = - const IosInAppWebViewSelectionGranularity._internal(1); + const IOSInAppWebViewSelectionGranularity._internal(1); bool operator ==(value) => value == _value; @@ -979,13 +979,13 @@ class IosInAppWebViewSelectionGranularity { int get hashCode => _value.hashCode; } -///IosInAppWebViewDataDetectorTypes class represents an iOS-specific class used to specify a dataDetectoryTypes value that adds interactivity to web content that matches the value. +///IOSInAppWebViewDataDetectorTypes class represents an iOS-specific class used to specify a dataDetectoryTypes value that adds interactivity to web content that matches the value. /// ///**NOTE**: available on iOS 10.0+. -class IosInAppWebViewDataDetectorTypes { +class IOSInAppWebViewDataDetectorTypes { final String _value; - const IosInAppWebViewDataDetectorTypes._internal(this._value); - static IosInAppWebViewDataDetectorTypes fromValue(String value) { + const IOSInAppWebViewDataDetectorTypes._internal(this._value); + static IOSInAppWebViewDataDetectorTypes fromValue(String value) { return ([ "NONE", "PHONE_NUMBER", @@ -999,7 +999,7 @@ class IosInAppWebViewDataDetectorTypes { "SPOTLIGHT_SUGGESTION", "ALL" ].contains(value)) - ? IosInAppWebViewDataDetectorTypes._internal(value) + ? IOSInAppWebViewDataDetectorTypes._internal(value) : null; } @@ -1008,41 +1008,41 @@ class IosInAppWebViewDataDetectorTypes { String toString() => _value; ///No detection is performed. - static const NONE = const IosInAppWebViewDataDetectorTypes._internal("NONE"); + static const NONE = const IOSInAppWebViewDataDetectorTypes._internal("NONE"); ///Phone numbers are detected and turned into links. static const PHONE_NUMBER = - const IosInAppWebViewDataDetectorTypes._internal("PHONE_NUMBER"); + const IOSInAppWebViewDataDetectorTypes._internal("PHONE_NUMBER"); ///URLs in text are detected and turned into links. - static const LINK = const IosInAppWebViewDataDetectorTypes._internal("LINK"); + static const LINK = const IOSInAppWebViewDataDetectorTypes._internal("LINK"); ///Addresses are detected and turned into links. static const ADDRESS = - const IosInAppWebViewDataDetectorTypes._internal("ADDRESS"); + const IOSInAppWebViewDataDetectorTypes._internal("ADDRESS"); ///Dates and times that are in the future are detected and turned into links. static const CALENDAR_EVENT = - const IosInAppWebViewDataDetectorTypes._internal("CALENDAR_EVENT"); + const IOSInAppWebViewDataDetectorTypes._internal("CALENDAR_EVENT"); ///Tracking numbers are detected and turned into links. static const TRACKING_NUMBER = - const IosInAppWebViewDataDetectorTypes._internal("TRACKING_NUMBER"); + const IOSInAppWebViewDataDetectorTypes._internal("TRACKING_NUMBER"); ///Flight numbers are detected and turned into links. static const FLIGHT_NUMBER = - const IosInAppWebViewDataDetectorTypes._internal("FLIGHT_NUMBER"); + const IOSInAppWebViewDataDetectorTypes._internal("FLIGHT_NUMBER"); ///Lookup suggestions are detected and turned into links. static const LOOKUP_SUGGESTION = - const IosInAppWebViewDataDetectorTypes._internal("LOOKUP_SUGGESTION"); + const IOSInAppWebViewDataDetectorTypes._internal("LOOKUP_SUGGESTION"); ///Spotlight suggestions are detected and turned into links. static const SPOTLIGHT_SUGGESTION = - const IosInAppWebViewDataDetectorTypes._internal("SPOTLIGHT_SUGGESTION"); + const IOSInAppWebViewDataDetectorTypes._internal("SPOTLIGHT_SUGGESTION"); ///All of the above data types are turned into links when detected. Choosing this value will automatically include any new detection type that is added. - static const ALL = const IosInAppWebViewDataDetectorTypes._internal("ALL"); + static const ALL = const IOSInAppWebViewDataDetectorTypes._internal("ALL"); bool operator ==(value) => value == _value; @@ -1091,13 +1091,13 @@ class InAppWebViewUserPreferredContentMode { int get hashCode => _value.hashCode; } -///IosWebViewOptionsPresentationStyle class represents an iOS-specific class used to specify the modal presentation style when presenting a view controller. -class IosWebViewOptionsPresentationStyle { +///IOSWebViewOptionsPresentationStyle class represents an iOS-specific class used to specify the modal presentation style when presenting a view controller. +class IOSWebViewOptionsPresentationStyle { final int _value; - const IosWebViewOptionsPresentationStyle._internal(this._value); - static IosWebViewOptionsPresentationStyle fromValue(int value) { + const IOSWebViewOptionsPresentationStyle._internal(this._value); + static IOSWebViewOptionsPresentationStyle fromValue(int value) { if (value != null && value >= 0 && value <= 9) - return IosWebViewOptionsPresentationStyle._internal(value); + return IOSWebViewOptionsPresentationStyle._internal(value); return null; } @@ -1131,42 +1131,42 @@ class IosWebViewOptionsPresentationStyle { ///A presentation style in which the presented view covers the screen. static const FULL_SCREEN = - const IosWebViewOptionsPresentationStyle._internal(0); + const IOSWebViewOptionsPresentationStyle._internal(0); ///A presentation style that partially covers the underlying content. static const PAGE_SHEET = - const IosWebViewOptionsPresentationStyle._internal(1); + const IOSWebViewOptionsPresentationStyle._internal(1); ///A presentation style that displays the content centered in the screen. static const FORM_SHEET = - const IosWebViewOptionsPresentationStyle._internal(2); + const IOSWebViewOptionsPresentationStyle._internal(2); ///A presentation style where the content is displayed over another view controller’s content. static const CURRENT_CONTEXT = - const IosWebViewOptionsPresentationStyle._internal(3); + const IOSWebViewOptionsPresentationStyle._internal(3); ///A custom view presentation style that is managed by a custom presentation controller and one or more custom animator objects. - static const CUSTOM = const IosWebViewOptionsPresentationStyle._internal(4); + static const CUSTOM = const IOSWebViewOptionsPresentationStyle._internal(4); ///A view presentation style in which the presented view covers the screen. static const OVER_FULL_SCREEN = - const IosWebViewOptionsPresentationStyle._internal(5); + const IOSWebViewOptionsPresentationStyle._internal(5); ///A presentation style where the content is displayed over another view controller’s content. static const OVER_CURRENT_CONTEXT = - const IosWebViewOptionsPresentationStyle._internal(6); + const IOSWebViewOptionsPresentationStyle._internal(6); ///A presentation style where the content is displayed in a popover view. - static const POPOVER = const IosWebViewOptionsPresentationStyle._internal(7); + static const POPOVER = const IOSWebViewOptionsPresentationStyle._internal(7); ///A presentation style that indicates no adaptations should be made. - static const NONE = const IosWebViewOptionsPresentationStyle._internal(8); + static const NONE = const IOSWebViewOptionsPresentationStyle._internal(8); ///The default presentation style chosen by the system. /// ///**NOTE**: available on iOS 13.0+. static const AUTOMATIC = - const IosWebViewOptionsPresentationStyle._internal(9); + const IOSWebViewOptionsPresentationStyle._internal(9); bool operator ==(value) => value == _value; @@ -1174,13 +1174,13 @@ class IosWebViewOptionsPresentationStyle { int get hashCode => _value.hashCode; } -///IosWebViewOptionsTransitionStyle class represents an iOS-specific class used to specify the transition style when presenting a view controller. -class IosWebViewOptionsTransitionStyle { +///IOSWebViewOptionsTransitionStyle class represents an iOS-specific class used to specify the transition style when presenting a view controller. +class IOSWebViewOptionsTransitionStyle { final int _value; - const IosWebViewOptionsTransitionStyle._internal(this._value); - static IosWebViewOptionsTransitionStyle fromValue(int value) { + const IOSWebViewOptionsTransitionStyle._internal(this._value); + static IOSWebViewOptionsTransitionStyle fromValue(int value) { if (value != null && value >= 0 && value <= 3) - return IosWebViewOptionsTransitionStyle._internal(value); + return IOSWebViewOptionsTransitionStyle._internal(value); return null; } @@ -1203,24 +1203,24 @@ class IosWebViewOptionsTransitionStyle { ///When the view controller is presented, its view slides up from the bottom of the screen. ///On dismissal, the view slides back down. This is the default transition style. static const COVER_VERTICAL = - const IosWebViewOptionsTransitionStyle._internal(0); + const IOSWebViewOptionsTransitionStyle._internal(0); ///When the view controller is presented, the current view initiates a horizontal 3D flip from right-to-left, ///resulting in the revealing of the new view as if it were on the back of the previous view. ///On dismissal, the flip occurs from left-to-right, returning to the original view. static const FLIP_HORIZONTAL = - const IosWebViewOptionsTransitionStyle._internal(1); + const IOSWebViewOptionsTransitionStyle._internal(1); ///When the view controller is presented, the current view fades out while the new view fades in at the same time. ///On dismissal, a similar type of cross-fade is used to return to the original view. static const CROSS_DISSOLVE = - const IosWebViewOptionsTransitionStyle._internal(2); + const IOSWebViewOptionsTransitionStyle._internal(2); ///When the view controller is presented, one corner of the current view curls up to reveal the presented view underneath. ///On dismissal, the curled up page unfurls itself back on top of the presented view. ///A view controller presented using this transition is itself prevented from presenting any additional view controllers. static const PARTIAL_CURL = - const IosWebViewOptionsTransitionStyle._internal(3); + const IOSWebViewOptionsTransitionStyle._internal(3); bool operator ==(value) => value == _value; @@ -1228,15 +1228,15 @@ class IosWebViewOptionsTransitionStyle { int get hashCode => _value.hashCode; } -///IosWebViewOptionsTransitionStyle class represents an iOS-specific class used to set the custom style for the dismiss button. +///IOSSafariOptionsDismissButtonStyle class represents an iOS-specific class used to set the custom style for the dismiss button. /// ///**NOTE**: available on iOS 11.0+. -class IosSafariOptionsDismissButtonStyle { +class IOSSafariOptionsDismissButtonStyle { final int _value; - const IosSafariOptionsDismissButtonStyle._internal(this._value); - static IosSafariOptionsDismissButtonStyle fromValue(int value) { + const IOSSafariOptionsDismissButtonStyle._internal(this._value); + static IOSSafariOptionsDismissButtonStyle fromValue(int value) { if (value != null && value >= 0 && value <= 2) - return IosSafariOptionsDismissButtonStyle._internal(value); + return IOSSafariOptionsDismissButtonStyle._internal(value); return null; } @@ -1255,13 +1255,13 @@ class IosSafariOptionsDismissButtonStyle { } ///Makes the button title the localized string "Done". - static const DONE = const IosSafariOptionsDismissButtonStyle._internal(0); + static const DONE = const IOSSafariOptionsDismissButtonStyle._internal(0); ///Makes the button title the localized string "Close". - static const CLOSE = const IosSafariOptionsDismissButtonStyle._internal(1); + static const CLOSE = const IOSSafariOptionsDismissButtonStyle._internal(1); ///Makes the button title the localized string "Cancel". - static const CANCEL = const IosSafariOptionsDismissButtonStyle._internal(2); + static const CANCEL = const IOSSafariOptionsDismissButtonStyle._internal(2); bool operator ==(value) => value == _value; @@ -1272,51 +1272,51 @@ class IosSafariOptionsDismissButtonStyle { ///InAppWebViewWidgetOptions class represents the options that can be used for an [InAppWebView]. class InAppWebViewWidgetOptions { ///Cross-platform options. - InAppWebViewOptions inAppWebViewOptions; + InAppWebViewOptions crossPlatform; ///Android-specific options. - AndroidInAppWebViewOptions androidInAppWebViewOptions; + AndroidInAppWebViewOptions android; ///iOS-specific options. - IosInAppWebViewOptions iosInAppWebViewOptions; + IOSInAppWebViewOptions ios; InAppWebViewWidgetOptions( - {this.inAppWebViewOptions, - this.androidInAppWebViewOptions, - this.iosInAppWebViewOptions}); + {this.crossPlatform, + this.android, + this.ios}); } ///InAppBrowserClassOptions class represents the options that can be used for an [InAppBrowser] WebView. class InAppBrowserClassOptions { ///Cross-platform options. - InAppBrowserOptions inAppBrowserOptions; + InAppBrowserOptions crossPlatform; ///Android-specific options. - AndroidInAppBrowserOptions androidInAppBrowserOptions; + AndroidInAppBrowserOptions android; ///iOS-specific options. - IosInAppBrowserOptions iosInAppBrowserOptions; + IOSInAppBrowserOptions ios; ///WebView options. InAppWebViewWidgetOptions inAppWebViewWidgetOptions; InAppBrowserClassOptions( - {this.inAppBrowserOptions, - this.androidInAppBrowserOptions, - this.iosInAppBrowserOptions, + {this.crossPlatform, + this.android, + this.ios, this.inAppWebViewWidgetOptions}); } ///ChromeSafariBrowserClassOptions class represents the options that can be used for an [ChromeSafariBrowser] window. class ChromeSafariBrowserClassOptions { ///Android-specific options. - AndroidChromeCustomTabsOptions androidChromeCustomTabsOptions; + AndroidChromeCustomTabsOptions android; ///iOS-specific options. - IosSafariOptions iosSafariOptions; + IOSSafariOptions ios; ChromeSafariBrowserClassOptions( - {this.androidChromeCustomTabsOptions, this.iosSafariOptions}); + {this.android, this.ios}); } ///AjaxRequestAction class used by [AjaxRequest] class. @@ -1950,7 +1950,7 @@ class PermissionRequestResponseAction { int get hashCode => _value.hashCode; } -///PermissionRequestResponse class represents the response used by the [onPermissionRequest] event. +///PermissionRequestResponse class represents the response used by the [androidOnPermissionRequest] event. class PermissionRequestResponse { ///Resources granted to be accessed by origin. List resources; @@ -1992,34 +1992,34 @@ class ShouldOverrideUrlLoadingAction { } } -///IosWKNavigationType class represents the type of action triggering a navigation on iOS for the [shouldOverrideUrlLoading] event. -class IosWKNavigationType { +///IOSWKNavigationType class represents the type of action triggering a navigation on iOS for the [shouldOverrideUrlLoading] event. +class IOSWKNavigationType { final int _value; - const IosWKNavigationType._internal(this._value); + const IOSWKNavigationType._internal(this._value); int toValue() => _value; - static IosWKNavigationType fromValue(int value) { + static IOSWKNavigationType fromValue(int value) { if (value != null && ((value >= 0 && value <= 4) || value == -1)) - return IosWKNavigationType._internal(value); + return IOSWKNavigationType._internal(value); return null; } ///A link with an href attribute was activated by the user. - static const LINK_ACTIVATED = const IosWKNavigationType._internal(0); + static const LINK_ACTIVATED = const IOSWKNavigationType._internal(0); ///A form was submitted. - static const FORM_SUBMITTED = const IosWKNavigationType._internal(1); + static const FORM_SUBMITTED = const IOSWKNavigationType._internal(1); ///An item from the back-forward list was requested. - static const BACK_FORWARD = const IosWKNavigationType._internal(2); + static const BACK_FORWARD = const IOSWKNavigationType._internal(2); ///The webpage was reloaded. - static const RELOAD = const IosWKNavigationType._internal(3); + static const RELOAD = const IOSWKNavigationType._internal(3); ///A form was resubmitted (for example by going back, going forward, or reloading). - static const FORM_RESUBMITTED = const IosWKNavigationType._internal(4); + static const FORM_RESUBMITTED = const IOSWKNavigationType._internal(4); ///Navigation is taking place for some other reason. - static const OTHER = const IosWKNavigationType._internal(-1); + static const OTHER = const IOSWKNavigationType._internal(-1); bool operator ==(value) => value == _value; @@ -2053,7 +2053,171 @@ class ShouldOverrideUrlLoadingRequest { bool androidIsRedirect; ///The type of action triggering the navigation. Available only on iOS. - IosWKNavigationType iosWKNavigationType; + IOSWKNavigationType iosWKNavigationType; ShouldOverrideUrlLoadingRequest({this.url, this.method, this.headers, this.isForMainFrame, this.androidHasGesture, this.androidIsRedirect, this.iosWKNavigationType}); +} + +///OnCreateWindowRequest class represents the navigation request used by the [shouldOverrideUrlLoading] event. +class OnCreateWindowRequest { + ///Represents the url of the navigation request. + String url; + + ///Indicates if the new window should be a dialog, rather than a full-size window. Available only on Android. + bool androidIsDialog; + + ///Indicates if the request was initiated by a user gesture, such as the user clicking a link. Available only on Android. + bool androidIsUserGesture; + + ///The type of action triggering the navigation. Available only on iOS. + IOSWKNavigationType iosWKNavigationType; + + OnCreateWindowRequest({this.url, this.androidIsDialog, this.androidIsUserGesture, this.iosWKNavigationType}); +} + +///AndroidWebStorage class encapsulates information about the amount of storage currently used by an origin for the JavaScript storage APIs. +///An origin comprises the host, scheme and port of a URI. See [AndroidWebStorageManager] for details. +class AndroidWebStorageOrigin { + ///The string representation of this origin. + String origin; + + ///The quota for this origin, for the Web SQL Database API, in bytes. + int quota; + + ///The total amount of storage currently being used by this origin, for all JavaScript storage APIs, in bytes. + int usage; + + AndroidWebStorageOrigin({this.origin, this.quota, this.usage}); + + Map toMap() { + return { + "origin": origin, + "quota": quota, + "usage": usage + }; + } + + String toString() { + return toMap().toString(); + } +} + +///IOSWKWebsiteDataType class represents a website data type. +/// +///**NOTE**: available on iOS 9.0+. +class IOSWKWebsiteDataType { + final String _value; + const IOSWKWebsiteDataType._internal(this._value); + static IOSWKWebsiteDataType fromValue(String value) { + return ([ + "WKWebsiteDataTypeFetchCache", + "WKWebsiteDataTypeDiskCache", + "WKWebsiteDataTypeMemoryCache", + "WKWebsiteDataTypeOfflineWebApplicationCache", + "WKWebsiteDataTypeCookies", + "WKWebsiteDataTypeSessionStorage", + "WKWebsiteDataTypeLocalStorage", + "WKWebsiteDataTypeWebSQLDatabases", + "WKWebsiteDataTypeIndexedDBDatabases", + "WKWebsiteDataTypeServiceWorkerRegistrations" + ].contains(value)) + ? IOSWKWebsiteDataType._internal(value) + : null; + } + + String toValue() => _value; + @override + String toString() => _value; + + ///On-disk Fetch caches. + /// + ///**NOTE**: available on iOS 11.3+. + static const WKWebsiteDataTypeFetchCache = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeFetchCache"); + + ///On-disk caches. + static const WKWebsiteDataTypeDiskCache = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeDiskCache"); + + ///In-memory caches. + static const WKWebsiteDataTypeMemoryCache = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeMemoryCache"); + + ///HTML offline web application caches. + static const WKWebsiteDataTypeOfflineWebApplicationCache = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeOfflineWebApplicationCache"); + + ///Cookies. + static const WKWebsiteDataTypeCookies = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeCookies"); + + ///HTML session storage. + static const WKWebsiteDataTypeSessionStorage = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeSessionStorage"); + + ///HTML local storage. + static const WKWebsiteDataTypeLocalStorage = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeLocalStorage"); + + ///WebSQL databases. + static const WKWebsiteDataTypeWebSQLDatabases = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeWebSQLDatabases"); + + ///IndexedDB databases. + static const WKWebsiteDataTypeIndexedDBDatabases = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeIndexedDBDatabases"); + + ///Service worker registrations. + /// + ///**NOTE**: available on iOS 11.3+. + static const WKWebsiteDataTypeServiceWorkerRegistrations = + const IOSWKWebsiteDataType._internal("WKWebsiteDataTypeServiceWorkerRegistrations"); + + ///Returns a set of all available website data types. + static final Set ALL = [ + IOSWKWebsiteDataType.WKWebsiteDataTypeFetchCache, + IOSWKWebsiteDataType.WKWebsiteDataTypeDiskCache, + IOSWKWebsiteDataType.WKWebsiteDataTypeMemoryCache, + IOSWKWebsiteDataType.WKWebsiteDataTypeOfflineWebApplicationCache, + IOSWKWebsiteDataType.WKWebsiteDataTypeCookies, + IOSWKWebsiteDataType.WKWebsiteDataTypeSessionStorage, + IOSWKWebsiteDataType.WKWebsiteDataTypeLocalStorage, + IOSWKWebsiteDataType.WKWebsiteDataTypeWebSQLDatabases, + IOSWKWebsiteDataType.WKWebsiteDataTypeIndexedDBDatabases, + IOSWKWebsiteDataType.WKWebsiteDataTypeServiceWorkerRegistrations + ].toSet(); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} + +///WKWebsiteDataRecord class represents website data, grouped by domain name using the public suffix list. +/// +///**NOTE**: available on iOS 9.0+. +class IOSWKWebsiteDataRecord { + ///The display name for the data record. This is usually the domain name. + String displayName; + + ///The various types of website data that exist for this data record. + Set dataTypes; + + IOSWKWebsiteDataRecord({this.displayName, this.dataTypes}); + + Map toMap() { + List dataTypesString = []; + for (var dataType in dataTypes) { + dataTypesString.add(dataType.toValue()); + } + + return { + "displayName": displayName, + "dataTypes": dataTypesString + }; + } + + String toString() { + return toMap().toString(); + } } \ No newline at end of file diff --git a/lib/src/web_storage_manager.dart b/lib/src/web_storage_manager.dart new file mode 100644 index 00000000..ff660e46 --- /dev/null +++ b/lib/src/web_storage_manager.dart @@ -0,0 +1,159 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'types.dart'; + +///WebStorageManager class implements a singleton object (shared instance) which manages the web storage used by WebView instances. +/// +///**NOTE for iOS**: available from iOS 9.0+. +class WebStorageManager { + static WebStorageManager _instance; + static const MethodChannel _channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_webstoragemanager'); + + AndroidWebStorageManager android = AndroidWebStorageManager(); + IOSWebStorageManager ios = IOSWebStorageManager(); + + ///Gets the WebStorage manager shared instance. + static WebStorageManager instance() { + return (_instance != null) ? _instance : _init(); + } + + static WebStorageManager _init() { + _channel.setMethodCallHandler(_handleMethod); + _instance = new WebStorageManager(); + return _instance; + } + + static Future _handleMethod(MethodCall call) async {} +} + +///AndroidWebStorageManager class is used to manage the JavaScript storage APIs provided by the WebView. +///It manages the Application Cache API, the Web SQL Database API and the HTML5 Web Storage API. +class AndroidWebStorageManager { + ///Gets the origins currently using either the Application Cache or Web SQL Database APIs. + Future> getOrigins() async { + List originsList = []; + + Map args = {}; + List> origins = (await WebStorageManager._channel.invokeMethod('getOrigins', args)).cast>(); + + for(var origin in origins) { + originsList.add(AndroidWebStorageOrigin(origin: origin["origin"], quota: origin["quota"], usage: origin["usage"])); + } + + return originsList; + } + + ///Clears all storage currently being used by the JavaScript storage APIs. + ///This includes the Application Cache, Web SQL Database and the HTML5 Web Storage APIs. + Future deleteAllData() async { + Map args = {}; + await WebStorageManager._channel.invokeMethod('deleteAllData', args); + } + + ///Clears the storage currently being used by both the Application Cache and Web SQL Database APIs by the given [origin]. + ///The origin is specified using its string representation. + Future deleteOrigin({@required String origin}) async { + assert(origin != null); + Map args = {}; + args.putIfAbsent("origin", () => origin); + await WebStorageManager._channel.invokeMethod('deleteOrigin', args); + } + + ///Gets the storage quota for the Web SQL Database API for the given [origin]. + ///The quota is given in bytes and the origin is specified using its string representation. + ///Note that a quota is not enforced on a per-origin basis for the Application Cache API. + Future getQuotaForOrigin({@required String origin}) async { + assert(origin != null); + Map args = {}; + args.putIfAbsent("origin", () => origin); + return await WebStorageManager._channel.invokeMethod('getQuotaForOrigin', args); + } + + ///Gets the amount of storage currently being used by both the Application Cache and Web SQL Database APIs by the given [origin]. + ///The amount is given in bytes and the origin is specified using its string representation. + Future getUsageForOrigin({@required String origin}) async { + assert(origin != null); + Map args = {}; + args.putIfAbsent("origin", () => origin); + return await WebStorageManager._channel.invokeMethod('getUsageForOrigin', args); + } +} + +///IOSWebStorageManager class represents various types of data that a website might make use of. +///This includes cookies, disk and memory caches, and persistent data such as WebSQL, IndexedDB databases, and local storage. +/// +///**NOTE**: available on iOS 9.0+. +class IOSWebStorageManager { + ///Fetches data records containing the given website data types. + /// + ///[dataTypes] represents the website data types to fetch records for. + Future> fetchDataRecords({@required Set dataTypes}) async { + assert(dataTypes != null); + List recordList = []; + List dataTypesList = []; + for (var dataType in dataTypes) { + dataTypesList.add(dataType.toValue()); + } + Map args = {}; + args.putIfAbsent("dataTypes", () => dataTypesList); + List> records = (await WebStorageManager._channel.invokeMethod('fetchDataRecords', args)).cast>(); + for(var record in records) { + List dataTypesString = record["dataTypes"].cast(); + Set dataTypes = Set(); + for(var dataType in dataTypesString) { + dataTypes.add(IOSWKWebsiteDataType.fromValue(dataType)); + } + recordList.add(IOSWKWebsiteDataRecord(displayName: record["displayName"], dataTypes: dataTypes)); + } + return recordList; + } + + ///Removes website data of the given types for the given data records. + /// + ///[dataTypes] represents the website data types that should be removed. + /// + ///[dataRecords] represents the website data records to delete website data for. + Future removeDataFor({@required Set dataTypes, @required List dataRecords}) async { + assert(dataTypes != null && dataRecords != null); + + List dataTypesList = []; + for (var dataType in dataTypes) { + dataTypesList.add(dataType.toValue()); + } + + List> recordList = []; + for (var record in dataRecords) { + recordList.add(record.toMap()); + } + + Map args = {}; + args.putIfAbsent("dataTypes", () => dataTypesList); + args.putIfAbsent("recordList", () => recordList); + await WebStorageManager._channel.invokeMethod('removeDataFor', args); + } + + ///Removes all website data of the given types that has been modified since the given date. + /// + ///[dataTypes] represents the website data types that should be removed. + /// + ///[date] represents a date. All website data modified after this date will be removed. + Future removeDataModifiedSince({@required Set dataTypes, @required DateTime date}) async { + assert(dataTypes != null && date != null); + + List dataTypesList = []; + for (var dataType in dataTypes) { + dataTypesList.add(dataType.toValue()); + } + + var timestamp = date.millisecondsSinceEpoch; + + Map args = {}; + args.putIfAbsent("dataTypes", () => dataTypesList); + args.putIfAbsent("timestamp", () => timestamp); + await WebStorageManager._channel.invokeMethod('removeDataModifiedSince', args); + } +} diff --git a/lib/src/webview_options.dart b/lib/src/webview_options.dart index 00232b8a..b05401bf 100644 --- a/lib/src/webview_options.dart +++ b/lib/src/webview_options.dart @@ -256,7 +256,7 @@ class AndroidInAppWebViewOptions ///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`. bool supportZoom; - ///Set to `true` if you want the database storage API is enabled. The default value is `false`. + ///Set to `true` if you want the database storage API is enabled. The default value is `true`. bool databaseEnabled; ///Set to `true` if you want the DOM storage API is enabled. The default value is `true`. @@ -556,7 +556,7 @@ class AndroidInAppWebViewOptions } ///This class represents all the iOS-only WebView options available. -class IosInAppWebViewOptions +class IOSInAppWebViewOptions implements WebViewOptions, BrowserOptions, IosOptions { ///Set to `true` to disable the bouncing of the WebView when the scrolling has reached an edge of the content. The default value is `false`. bool disallowOverScroll; @@ -599,15 +599,15 @@ class IosInAppWebViewOptions bool isFraudulentWebsiteWarningEnabled; ///The level of granularity with which the user can interactively select content in the web view. - ///The default value is [IosInAppWebViewSelectionGranularity.DYNAMIC] - IosInAppWebViewSelectionGranularity selectionGranularity; + ///The default value is [IOSInAppWebViewSelectionGranularity.DYNAMIC] + IOSInAppWebViewSelectionGranularity selectionGranularity; ///Specifying a dataDetectoryTypes value adds interactivity to web content that matches the value. - ///For example, Safari adds a link to “apple.com” in the text “Visit apple.com” if the dataDetectorTypes property is set to [IosInAppWebViewDataDetectorTypes.LINK]. - ///The default value is [IosInAppWebViewDataDetectorTypes.NONE]. + ///For example, Safari adds a link to “apple.com” in the text “Visit apple.com” if the dataDetectorTypes property is set to [IOSInAppWebViewDataDetectorTypes.LINK]. + ///The default value is [IOSInAppWebViewDataDetectorTypes.NONE]. /// ///**NOTE**: available on iOS 10.0+. - List dataDetectorTypes; + List dataDetectorTypes; ///Set `true` if shared cookies from `HTTPCookieStorage.shared` should used for every load request in the WebView. ///The default value is `false`. @@ -615,7 +615,13 @@ class IosInAppWebViewOptions ///**NOTE**: available on iOS 11.0+. bool sharedCookiesEnabled; - IosInAppWebViewOptions( + ///Configures whether the scroll indicator insets are automatically adjusted by the system. + ///The default value is `false`. + /// + ///**NOTE**: available on iOS 13.0+. + bool automaticallyAdjustsScrollIndicatorInsets; + + IOSInAppWebViewOptions( {this.disallowOverScroll = false, this.enableViewportScale = false, this.suppressesIncrementalRendering = false, @@ -626,9 +632,10 @@ class IosInAppWebViewOptions this.allowsInlineMediaPlayback = false, this.allowsPictureInPictureMediaPlayback = true, this.isFraudulentWebsiteWarningEnabled = true, - this.selectionGranularity = IosInAppWebViewSelectionGranularity.DYNAMIC, - this.dataDetectorTypes = const [IosInAppWebViewDataDetectorTypes.NONE], - this.sharedCookiesEnabled = false}); + this.selectionGranularity = IOSInAppWebViewSelectionGranularity.DYNAMIC, + this.dataDetectorTypes = const [IOSInAppWebViewDataDetectorTypes.NONE], + this.sharedCookiesEnabled = false, + this.automaticallyAdjustsScrollIndicatorInsets = false}); @override Map toMap() { @@ -652,20 +659,21 @@ class IosInAppWebViewOptions "isFraudulentWebsiteWarningEnabled": isFraudulentWebsiteWarningEnabled, "selectionGranularity": selectionGranularity.toValue(), "dataDetectorTypes": dataDetectorTypesList, - "sharedCookiesEnabled": sharedCookiesEnabled + "sharedCookiesEnabled": sharedCookiesEnabled, + "automaticallyAdjustsScrollIndicatorInsets": automaticallyAdjustsScrollIndicatorInsets }; } - static IosInAppWebViewOptions fromMap(Map map) { - List dataDetectorTypes = []; + static IOSInAppWebViewOptions fromMap(Map map) { + List dataDetectorTypes = []; List dataDetectorTypesList = List.from(map["dataDetectorTypes"] ?? []); dataDetectorTypesList.forEach((dataDetectorType) { dataDetectorTypes - .add(IosInAppWebViewDataDetectorTypes.fromValue(dataDetectorType)); + .add(IOSInAppWebViewDataDetectorTypes.fromValue(dataDetectorType)); }); - IosInAppWebViewOptions options = new IosInAppWebViewOptions(); + IOSInAppWebViewOptions options = new IOSInAppWebViewOptions(); options.disallowOverScroll = map["disallowOverScroll"]; options.enableViewportScale = map["enableViewportScale"]; options.suppressesIncrementalRendering = @@ -682,10 +690,11 @@ class IosInAppWebViewOptions options.isFraudulentWebsiteWarningEnabled = map["isFraudulentWebsiteWarningEnabled"]; options.selectionGranularity = - IosInAppWebViewSelectionGranularity.fromValue( + IOSInAppWebViewSelectionGranularity.fromValue( map["selectionGranularity"]); options.dataDetectorTypes = dataDetectorTypes; options.sharedCookiesEnabled = map["sharedCookiesEnabled"]; + options.automaticallyAdjustsScrollIndicatorInsets = map["automaticallyAdjustsScrollIndicatorInsets"]; return options; } } @@ -773,7 +782,7 @@ class AndroidInAppBrowserOptions implements BrowserOptions, AndroidOptions { } ///This class represents all the iOS-only [InAppBrowser] options available. -class IosInAppBrowserOptions implements BrowserOptions, IosOptions { +class IOSInAppBrowserOptions implements BrowserOptions, IosOptions { ///Set to `false` to hide the toolbar at the bottom of the WebView. The default value is `true`. bool toolbarBottom; @@ -789,23 +798,23 @@ class IosInAppBrowserOptions implements BrowserOptions, IosOptions { ///Set the custom color for the close button. String closeButtonColor; - ///Set the custom modal presentation style when presenting the WebView. The default value is [IosWebViewOptionsPresentationStyle.FULL_SCREEN]. - IosWebViewOptionsPresentationStyle presentationStyle; + ///Set the custom modal presentation style when presenting the WebView. The default value is [IOSWebViewOptionsPresentationStyle.FULL_SCREEN]. + IOSWebViewOptionsPresentationStyle presentationStyle; - ///Set to the custom transition style when presenting the WebView. The default value is [IosWebViewOptionsTransitionStyle.COVER_VERTICAL]. - IosWebViewOptionsTransitionStyle transitionStyle; + ///Set to the custom transition style when presenting the WebView. The default value is [IOSWebViewOptionsTransitionStyle.COVER_VERTICAL]. + IOSWebViewOptionsTransitionStyle transitionStyle; ///Set to `false` to hide the spinner when the WebView is loading a page. The default value is `true`. bool spinner; - IosInAppBrowserOptions( + IOSInAppBrowserOptions( {this.toolbarBottom = true, this.toolbarBottomBackgroundColor = "", this.toolbarBottomTranslucent = true, this.closeButtonCaption = "", this.closeButtonColor = "", - this.presentationStyle = IosWebViewOptionsPresentationStyle.FULL_SCREEN, - this.transitionStyle = IosWebViewOptionsTransitionStyle.COVER_VERTICAL, + this.presentationStyle = IOSWebViewOptionsPresentationStyle.FULL_SCREEN, + this.transitionStyle = IOSWebViewOptionsTransitionStyle.COVER_VERTICAL, this.spinner = true}); @override @@ -822,17 +831,17 @@ class IosInAppBrowserOptions implements BrowserOptions, IosOptions { }; } - static IosInAppBrowserOptions fromMap(Map map) { - IosInAppBrowserOptions options = new IosInAppBrowserOptions(); + static IOSInAppBrowserOptions fromMap(Map map) { + IOSInAppBrowserOptions options = new IOSInAppBrowserOptions(); options.toolbarBottom = map["toolbarBottom"]; options.toolbarBottomBackgroundColor = map["toolbarBottomBackgroundColor"]; options.toolbarBottomTranslucent = map["toolbarBottomTranslucent"]; options.closeButtonCaption = map["closeButtonCaption"]; options.closeButtonColor = map["closeButtonColor"]; options.presentationStyle = - IosWebViewOptionsPresentationStyle.fromValue(map["presentationStyle"]); + IOSWebViewOptionsPresentationStyle.fromValue(map["presentationStyle"]); options.transitionStyle = - IosWebViewOptionsTransitionStyle.fromValue(map["transitionStyle"]); + IOSWebViewOptionsTransitionStyle.fromValue(map["transitionStyle"]); options.spinner = map["spinner"]; return options; } @@ -887,17 +896,17 @@ class AndroidChromeCustomTabsOptions } ///This class represents all the iOS-only [ChromeSafariBrowser] options available. -class IosSafariOptions implements ChromeSafariBrowserOptions, IosOptions { +class IOSSafariOptions implements ChromeSafariBrowserOptions, IosOptions { ///Set to `true` if Reader mode should be entered automatically when it is available for the webpage. The default value is `false`. bool entersReaderIfAvailable; ///Set to `true` to enable bar collapsing. The default value is `false`. bool barCollapsingEnabled; - ///Set the custom style for the dismiss button. The default value is [IosSafariOptionsDismissButtonStyle.DONE]. + ///Set the custom style for the dismiss button. The default value is [IOSSafariOptionsDismissButtonStyle.DONE]. /// ///**NOTE**: available on iOS 11.0+. - IosSafariOptionsDismissButtonStyle dismissButtonStyle; + IOSSafariOptionsDismissButtonStyle dismissButtonStyle; ///Set the custom background color of the navigation bar and the toolbar. /// @@ -909,20 +918,20 @@ class IosSafariOptions implements ChromeSafariBrowserOptions, IosOptions { ///**NOTE**: available on iOS 10.0+. String preferredControlTintColor; - ///Set the custom modal presentation style when presenting the WebView. The default value is [IosWebViewOptionsPresentationStyle.FULL_SCREEN]. - IosWebViewOptionsPresentationStyle presentationStyle; + ///Set the custom modal presentation style when presenting the WebView. The default value is [IOSWebViewOptionsPresentationStyle.FULL_SCREEN]. + IOSWebViewOptionsPresentationStyle presentationStyle; - ///Set to the custom transition style when presenting the WebView. The default value is [IosWebViewOptionsTransitionStyle.COVER_VERTICAL]. - IosWebViewOptionsTransitionStyle transitionStyle; + ///Set to the custom transition style when presenting the WebView. The default value is [IOSWebViewOptionsTransitionStyle.COVER_VERTICAL]. + IOSWebViewOptionsTransitionStyle transitionStyle; - IosSafariOptions( + IOSSafariOptions( {this.entersReaderIfAvailable = false, this.barCollapsingEnabled = false, - this.dismissButtonStyle = IosSafariOptionsDismissButtonStyle.DONE, + this.dismissButtonStyle = IOSSafariOptionsDismissButtonStyle.DONE, this.preferredBarTintColor = "", this.preferredControlTintColor = "", - this.presentationStyle = IosWebViewOptionsPresentationStyle.FULL_SCREEN, - this.transitionStyle = IosWebViewOptionsTransitionStyle.COVER_VERTICAL}); + this.presentationStyle = IOSWebViewOptionsPresentationStyle.FULL_SCREEN, + this.transitionStyle = IOSWebViewOptionsTransitionStyle.COVER_VERTICAL}); @override Map toMap() { @@ -937,18 +946,18 @@ class IosSafariOptions implements ChromeSafariBrowserOptions, IosOptions { }; } - static IosSafariOptions fromMap(Map map) { - IosSafariOptions options = new IosSafariOptions(); + static IOSSafariOptions fromMap(Map map) { + IOSSafariOptions options = new IOSSafariOptions(); options.entersReaderIfAvailable = map["entersReaderIfAvailable"]; options.barCollapsingEnabled = map["barCollapsingEnabled"]; options.dismissButtonStyle = - IosSafariOptionsDismissButtonStyle.fromValue(map["dismissButtonStyle"]); + IOSSafariOptionsDismissButtonStyle.fromValue(map["dismissButtonStyle"]); options.preferredBarTintColor = map["preferredBarTintColor"]; options.preferredControlTintColor = map["preferredControlTintColor"]; options.presentationStyle = - IosWebViewOptionsPresentationStyle.fromValue(map["presentationStyle"]); + IOSWebViewOptionsPresentationStyle.fromValue(map["presentationStyle"]); options.transitionStyle = - IosWebViewOptionsTransitionStyle.fromValue(map["transitionStyle"]); + IOSWebViewOptionsTransitionStyle.fromValue(map["transitionStyle"]); return options; } }