diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f3ce978..61595997 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,32 @@
-## 5.0.0
+## 5.0.0-nullsafety.0
+- Added support for Dart null-safety feature
- Added Android Hybrid Composition support "Use PlatformViewLink widget for Android WebView" [#462](https://github.com/pichillilorenzo/flutter_inappwebview/pull/462) (thanks to [plateaukao](https://github.com/plateaukao) and [tneotia](https://github.com/tneotia))
+- Added `allowUniversalAccessFromFileURLs` and `allowFileAccessFromFileURLs` WebView options also for iOS (also thanks to [liranhao](https://github.com/liranhao))
+- Updated integration tests
- Merge "Upgraded appcompat to 1.2.0-rc-02" [#465](https://github.com/pichillilorenzo/flutter_inappwebview/pull/465) (thanks to [andreidiaconu](https://github.com/andreidiaconu))
- Merge "Added missing field 'headers' which returned by WebResourceResponse.toMap()" [#490](https://github.com/pichillilorenzo/flutter_inappwebview/pull/490) (thanks to [Doflatango](https://github.com/Doflatango))
- Merge "Fix: added iOS fallback module import" [#466](https://github.com/pichillilorenzo/flutter_inappwebview/pull/466) (thanks to [Eddayy](https://github.com/Eddayy))
- Merge "Fix NullPointerException after taking a photo by a camera app on Android" [#492](https://github.com/pichillilorenzo/flutter_inappwebview/pull/492) (thanks to [AAkira](https://github.com/AAkira))
+- Merge "iOS CookieManager.getCookies - Check that URL has suffix of cookie do…" [#658](https://github.com/pichillilorenzo/flutter_inappwebview/pull/658) (thanks to [arneke](https://github.com/arneke))
+- Merge "Add NTLM Auth" [#634](https://github.com/pichillilorenzo/flutter_inappwebview/pull/634) (thanks to [albatrosify](https://github.com/albatrosify))
+- Merge "iOS ChromeSafariBrowserManager - Fixing unnecessary casting of rootViewController to FlutterViewController" [#567](https://github.com/pichillilorenzo/flutter_inappwebview/pull/567) (thanks to [gunantosteven](https://github.com/gunantosteven))
+- Merge "Fix _channel.invokeMethod name for injectCSSFileFromUrl method" [#645](https://github.com/pichillilorenzo/flutter_inappwebview/pull/645) (thanks to [omralcrt](https://github.com/omralcrt))
+- Merge "Add android media intents on wildcard input accept" [#620](https://github.com/pichillilorenzo/flutter_inappwebview/pull/620) (thanks to [cbodin](https://github.com/cbodin))
+- Fixed missing properties initialization when using InAppWebViewController.fromInAppBrowser
- Fixed "Issue in Flutter web: 'Unsupported operation: Platform._operatingSystem'" [#507](https://github.com/pichillilorenzo/flutter_inappwebview/issues/507)
- Fixed "window.flutter_inappwebview.callHandler is not a function" [#218](https://github.com/pichillilorenzo/flutter_inappwebview/issues/218)
- Fixed "Android ContentBlocker - java.lang.NullPointerException ContentBlockerTrigger resource type" [#506](https://github.com/pichillilorenzo/flutter_inappwebview/issues/506)
- Fixed "Android CookieManager throws error caused by websites that are sending back illegal/invalid cookies." [#476](https://github.com/pichillilorenzo/flutter_inappwebview/issues/476)
- Fixed missing `clearHistory` webview method implementation on Android
- Fixed iOS crash when using CookieManager getCookies for an URL and the host URL is `null`
+- Fixed "IOS does not support allowUniversalAccessFromFileURLs" [#654](https://github.com/pichillilorenzo/flutter_inappwebview/issues/654)
+
+### BREAKING CHANGES
+
+- Minimum Flutter version required is `1.22.0` and Dart SDK `>=2.12.0-0 <3.0.0`
+- Removed `debuggingEnabled` WebView option; on Android you should use now the `AndroidInAppWebViewController.setWebContentsDebuggingEnabled(bool debuggingEnabled)` static method; on iOS, debugging is always enabled
+- `allowUniversalAccessFromFileURLs` and `allowFileAccessFromFileURLs` WebView options moved from Android-specific options to cross-platform options.
## 4.0.0+4
diff --git a/README.md b/README.md
index d82c572f..f7d7cfe0 100755
--- a/README.md
+++ b/README.md
@@ -22,8 +22,8 @@ A Flutter plugin that allows you to add an inline webview, to use an headless we
## Requirements
-- Dart sdk: ">=2.7.0 <3.0.0"
-- Flutter: ">=1.12.13+hotfix.5"
+- Dart sdk: ">=2.12.0-0 <3.0.0"
+- Flutter: ">=1.22.0"
- Android: `minSdkVersion 17` and add support for `androidx` (see [AndroidX Migration](https://flutter.dev/docs/development/androidx-migration) to migrate an existing app)
- iOS: `--ios-language swift`, Xcode version `>= 11`
@@ -75,7 +75,21 @@ or **Android API 19+** if you enable the `useHybridComposition` Android-specific
- Check the official [Network security configuration - "Opt out of cleartext traffic"](https://developer.android.com/training/articles/security-config#CleartextTrafficPermitted) section.
- Also, check this StackOverflow issue answer: [Cleartext HTTP traffic not permitted](https://stackoverflow.com/a/50834600/4637638).
-If you want to use the `ChromeSafariBrowser` on Android 11+ you need to specify your app querying for `android.support.customtabs.action.CustomTabsService` in your `AndroidManifest.xml` you can read more about it here: https://developers.google.com/web/android/custom-tabs/best-practices#applications_targeting_android_11_api_level_30_or_above
+#### Debugging Android WebViews
+On Android, in order to enable/disable debugging WebViews using `chrome://inspect` on Chrome, you should use the `AndroidInAppWebViewController.setWebContentsDebuggingEnabled(bool debuggingEnabled)` static method.
+
+For example, you could call it inside the main function:
+```dart
+Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ if (Platform.isAndroid) {
+ await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
+ }
+
+ runApp(new MyApp());
+}
+```
### IMPORTANT Note for iOS
@@ -139,6 +153,9 @@ Other useful `Info.plist` properties are:
* `NSAllowsLocalNetworking`: A Boolean value indicating whether to allow loading of local resources ([Official wiki](https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity/nsallowslocalnetworking));
* `NSAllowsArbitraryLoadsInWebContent`: A Boolean value indicating whether all App Transport Security restrictions are disabled for requests made from web views ([Official wiki](https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity/nsallowsarbitraryloadsinwebcontent)).
+#### Debugging iOS WebViews
+On iOS, debugging WebViews on Safari through developer tools is always enabled. There isn't a way to enable or disable it.
+
### How to enable the usage of camera for HTML inputs such as ``
In order to be able to use camera, for example, for taking images through `` HTML tag, you need to ask camera permission.
@@ -184,11 +201,11 @@ First, add `flutter_inappwebview` as a [dependency in your pubspec.yaml file](ht
## Usage
Classes:
-- [InAppWebView](#inappwebview-class): Flutter Widget for adding an **inline native WebView** integrated into the flutter widget tree. To use `InAppWebView` class on iOS you need to opt-in for the embedded views preview by adding a boolean property to the app's `Info.plist` file, with the key `io.flutter.embedded_views_preview` and the value `YES`. Also, note that on Android it requires **Android API 20+** (see [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html)) or **Android API 19+** if you enable the `useHybridComposition` Android-specific option.
+- [InAppWebView](#inappwebview-class): Flutter Widget for adding an **inline native WebView** integrated into the flutter widget tree. Note that on Android it requires **Android API 20+** (see [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html)) or **Android API 19+** if you enable the `useHybridComposition` Android-specific option.
- [ContextMenu](#contextmenu-class): This class represents the WebView context menu.
- [HeadlessInAppWebView](#headlessinappwebview-class): Class that represents a WebView in headless mode. It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree.
- [InAppBrowser](#inappbrowser-class): In-App Browser using native WebView.
-- [ChromeSafariBrowser](#chromesafaribrowser-class): In-App Browser using [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android / [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS.
+- [ChromeSafariBrowser](#chromesafaribrowser-class): In-App Browser using [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android / [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS. If you want to use the `ChromeSafariBrowser` on Android 11+ you need to specify your app querying for `android.support.customtabs.action.CustomTabsService` in your `AndroidManifest.xml` (you can read more about it here: https://developers.google.com/web/android/custom-tabs/best-practices#applications_targeting_android_11_api_level_30_or_above).
- [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.
@@ -234,20 +251,22 @@ The plugin relies on Flutter's mechanism (in developers preview) for embedding A
Known issues are tagged with the [platform-views](https://github.com/flutter/flutter/labels/a%3A%20platform-views) label in the [Flutter official repo](https://github.com/flutter/flutter).
Keyboard support within webviews is also experimental.
-To use `InAppWebView` class on iOS you need to opt-in for the embedded views preview by adding a boolean property to the app's `Info.plist` file, with the key `io.flutter.embedded_views_preview` and the value `YES`.
-
-Also, note that on Android it requires **Android API 20+** (see [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html))
+Note that on Android it requires **Android API 20+** (see [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html))
or **Android API 19+** if you enable the `useHybridComposition` Android-specific option.
Use `InAppWebViewController` to control the WebView instance.
Example:
```dart
import 'dart:async';
+import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
+ if (Platform.isAndroid) {
+ await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
+ }
runApp(new MyApp());
}
@@ -258,7 +277,7 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State {
- InAppWebViewController webView;
+ InAppWebViewController? webView;
String url = "";
double progress = 0;
@@ -280,81 +299,75 @@ class _MyAppState extends State {
title: const Text('InAppWebView Example'),
),
body: Container(
- child: Column(children: [
- Container(
- padding: EdgeInsets.all(20.0),
- child: Text(
- "CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"),
- ),
- Container(
- padding: EdgeInsets.all(10.0),
- child: progress < 1.0
- ? LinearProgressIndicator(value: progress)
- : Container()),
- Expanded(
- child: Container(
- margin: const EdgeInsets.all(10.0),
- decoration:
- BoxDecoration(border: Border.all(color: Colors.blueAccent)),
- child: InAppWebView(
- initialUrl: "https://flutter.dev/",
- initialHeaders: {},
- initialOptions: InAppWebViewGroupOptions(
- crossPlatform: InAppWebViewOptions(
- debuggingEnabled: true,
- )
+ child: Column(children: [
+ Container(
+ padding: EdgeInsets.all(20.0),
+ child: Text(
+ "CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"),
+ ),
+ Container(
+ padding: EdgeInsets.all(10.0),
+ child: progress < 1.0
+ ? LinearProgressIndicator(value: progress)
+ : Container()),
+ Expanded(
+ child: Container(
+ margin: const EdgeInsets.all(10.0),
+ decoration:
+ BoxDecoration(border: Border.all(color: Colors.blueAccent)),
+ child: InAppWebView(
+ initialUrl: "https://flutter.dev/",
+ initialHeaders: {},
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+
+ )
+ ),
+ onWebViewCreated: (InAppWebViewController controller) {
+ webView = controller;
+ },
+ onLoadStart: (controller, url) {
+ setState(() {
+ this.url = url ?? '';
+ });
+ },
+ onLoadStop: (controller, url) async {
+ setState(() {
+ this.url = url ?? '';
+ });
+ },
+ onProgressChanged: (controller, progress) {
+ setState(() {
+ this.progress = progress / 100;
+ });
+ },
),
- onWebViewCreated: (InAppWebViewController controller) {
- webView = controller;
- },
- onLoadStart: (InAppWebViewController controller, String url) {
- setState(() {
- this.url = url;
- });
- },
- onLoadStop: (InAppWebViewController controller, String url) async {
- setState(() {
- this.url = url;
- });
- },
- onProgressChanged: (InAppWebViewController controller, int progress) {
- setState(() {
- this.progress = progress / 100;
- });
- },
),
),
- ),
- ButtonBar(
- alignment: MainAxisAlignment.center,
- children: [
- RaisedButton(
- child: Icon(Icons.arrow_back),
- onPressed: () {
- if (webView != null) {
- webView.goBack();
- }
- },
- ),
- RaisedButton(
- child: Icon(Icons.arrow_forward),
- onPressed: () {
- if (webView != null) {
- webView.goForward();
- }
- },
- ),
- RaisedButton(
- child: Icon(Icons.refresh),
- onPressed: () {
- if (webView != null) {
- webView.reload();
- }
- },
- ),
- ],
- ),
- ])),
+ ButtonBar(
+ alignment: MainAxisAlignment.center,
+ children: [
+ RaisedButton(
+ child: Icon(Icons.arrow_back),
+ onPressed: () {
+ webView?.goBack();
+ },
+ ),
+ RaisedButton(
+ child: Icon(Icons.arrow_forward),
+ onPressed: () {
+ webView?.goForward();
+ },
+ ),
+ RaisedButton(
+ child: Icon(Icons.refresh),
+ onPressed: () {
+ webView?.reload();
+ },
+ ),
+ ],
+ ),
+ ])),
),
);
}
@@ -447,7 +460,7 @@ Methods available:
##### `InAppWebViewController` Android-specific methods
-Android-specific methods can be called using the `InAppWebViewController.android` attribute.
+Android-specific methods can be called using the `InAppWebViewController.android` attribute. Static methods can be called using the `AndroidInAppWebViewController` class directly.
* `startSafeBrowsing`: Starts Safe Browsing initialization.
* `clearSslPreferences`: Clears the SSL preferences table stored in response to proceeding with SSL certificate errors.
@@ -464,10 +477,11 @@ Android-specific methods can be called using the `InAppWebViewController.android
* `static getSafeBrowsingPrivacyPolicyUrl`: Returns a URL pointing to the privacy policy for Safe Browsing reporting. This value will never be `null`.
* `static 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.
* `static getCurrentWebViewPackage`: Gets the current Android WebView package info.
+* `static setWebContentsDebuggingEnabled(bool debuggingEnabled)`: Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application. Debugging is disabled by default.
##### `InAppWebViewController` iOS-specific methods
-iOS-specific methods can be called using the `InAppWebViewController.ios` attribute.
+iOS-specific methods can be called using the `InAppWebViewController.ios` attribute. Static methods can be called using the `IOSInAppWebViewController` class directly.
* `hasOnlySecureContent`: A Boolean value indicating whether all resources on the page have been loaded over securely encrypted connections.
* `reloadFromOrigin`: Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible.
@@ -521,112 +535,111 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
##### `InAppWebView` Cross-platform options
-* `useShouldOverrideUrlLoading`: Set to `true` to be able to listen at the `shouldOverrideUrlLoading` event. The default value is `false`.
-* `useOnLoadResource`: Set to `true` to be able to listen at the `onLoadResource` event. The default value is `false`.
-* `useOnDownloadStart`: Set to `true` to be able to listen at the `onDownloadStart` event. The default value is `false`.
-* `useShouldInterceptAjaxRequest`: Set to `true` to be able to listen at the `shouldInterceptAjaxRequest` event. The default value is `false`.
-* `useShouldInterceptFetchRequest`: Set to `true` to be able to listen at the `shouldInterceptFetchRequest` event. The default value is `false`.
-* `clearCache`: Set to `true` to have all the browser's cache cleared before the new WebView is opened. The default value is `false`.
-* `userAgent`: Sets the user-agent for the WebView.
+* `allowFileAccessFromFileURLs`: Sets whether JavaScript running in the context of a file scheme URL should be allowed to access content from other file scheme URLs. The default value is `false`.
+* `allowUniversalAccessFromFileURLs`: Sets whether JavaScript running in the context of a file scheme URL should be allowed to access content from any origin. The default value is `false`.
* `applicationNameForUserAgent`: Append to the existing user-agent. Setting userAgent will override this.
-* `javaScriptEnabled`: Set to `true` to enable JavaScript. The default value is `true`.
-* `debuggingEnabled`: Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application.
+* `cacheEnabled`: Sets whether WebView should use browser caching. The default value is `true`.
+* `clearCache`: Set to `true` to have all the browser's cache cleared before the new WebView is opened. The default value is `false`.
+* `contentBlockers`: List of `ContentBlocker` that are a set of rules used to block content in the browser window.
+* `disableContextMenu`: Set to `true` to disable context menu. The default value is `false`.
+* `disableHorizontalScroll`: Set to `true` to disable horizontal scroll. The default value is `false`.
+* `disableVerticalScroll`: Set to `true` to disable vertical scroll. The default value is `false`.
+* `horizontalScrollBarEnabled`: Define whether the horizontal scrollbar should be drawn or not. The default value is `true`.
+* `incognito`: Set to `true` to open a browser window with incognito mode. The default value is `false`.
* `javaScriptCanOpenWindowsAutomatically`: Set to `true` to allow JavaScript open windows without user interaction. The default value is `false`.
+* `javaScriptEnabled`: Set to `true` to enable JavaScript. The default value is `true`.
* `mediaPlaybackRequiresUserGesture`: Set to `true` to prevent HTML5 audio or video from autoplaying. The default value is `true`.
* `minimumFontSize`: Sets the minimum font size. The default value is `8` for Android, `0` for iOS.
-* `verticalScrollBarEnabled`: Define whether the vertical scrollbar should be drawn or not. The default value is `true`.
-* `horizontalScrollBarEnabled`: Define whether the horizontal scrollbar should be drawn or not. The default value is `true`.
-* `resourceCustomSchemes`: List of custom schemes that the WebView must handle. Use the `onLoadResourceCustomScheme` event to intercept resource requests with custom scheme.
-* `contentBlockers`: List of `ContentBlocker` that are a set of rules used to block content in the browser window.
* `preferredContentMode`: Sets the content mode that the WebView needs to use when loading and rendering a webpage. The default value is `InAppWebViewUserPreferredContentMode.RECOMMENDED`.
-* `incognito`: Set to `true` to open a browser window with incognito mode. The default value is `false`.
-* `cacheEnabled`: Sets whether WebView should use browser caching. The default value is `true`.
-* `transparentBackground`: Set to `true` to make the background of the WebView transparent. If your app has a dark theme, this can prevent a white flash on initialization. The default value is `false`.
-* `disableVerticalScroll`: Set to `true` to disable vertical scroll. The default value is `false`.
-* `disableHorizontalScroll`: Set to `true` to disable horizontal scroll. The default value is `false`.
-* `disableContextMenu`: Set to `true` to disable context menu. The default value is `false`.
+* `resourceCustomSchemes`: List of custom schemes that the WebView must handle. Use the `onLoadResourceCustomScheme` event to intercept resource requests with custom scheme.
* `supportZoom`: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
+* `transparentBackground`: Set to `true` to make the background of the WebView transparent. If your app has a dark theme, this can prevent a white flash on initialization. The default value is `false`.
+* `useOnDownloadStart`: Set to `true` to be able to listen at the `onDownloadStart` event. The default value is `false`.
+* `useOnLoadResource`: Set to `true` to be able to listen at the `onLoadResource` event. The default value is `false`.
+* `useShouldInterceptAjaxRequest`: Set to `true` to be able to listen at the `shouldInterceptAjaxRequest` event. The default value is `false`.
+* `useShouldInterceptFetchRequest`: Set to `true` to be able to listen at the `shouldInterceptFetchRequest` event. The default value is `false`.
+* `useShouldOverrideUrlLoading`: Set to `true` to be able to listen at the `shouldOverrideUrlLoading` event. The default value is `false`.
+* `userAgent`: Sets the user-agent for the WebView.
+* `verticalScrollBarEnabled`: Define whether the vertical scrollbar should be drawn or not. The default value is `true`.
##### `InAppWebView` Android-specific options
-* `useHybridComposition`: Set to `true` to use Flutter's new Hybrid Composition rendering method, which fixes all issues [here](https://github.com/flutter/flutter/issues/61133). The default value is `false`. Note that this option requires Flutter v1.20+ and should only be used on Android 10+ for release apps, as animations will drop frames on < Android 10 (see [Hybrid-Composition#performance](https://github.com/flutter/flutter/wiki/Hybrid-Composition#performance)).
-* `useShouldInterceptRequest`: Set to `true` to be able to listen at the `androidShouldInterceptRequest` event. The default value is `false`.
-* `useOnRenderProcessGone`: Set to `true` to be able to listen at the `androidOnRenderProcessGone` event. The default value is `false`.
-* `textZoom`: Sets the text zoom of the page in percent. The default value is `100`.
-* `clearSessionCache`: Set to `true` to have the session cookie cache cleared before the new window is opened.
-* `builtInZoomControls`: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `true`.
-* `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`.
-* `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.
-* `mixedContentMode`: Configures the WebView's behavior when a secure origin attempts to load a resource from an insecure origin.
* `allowContentAccess`: Enables or disables content URL access within WebView. Content URL access allows WebView to load content from a content provider installed in the system. The default value is `true`.
* `allowFileAccess`: Enables or disables file access within WebView. Note that this enables or disables file system access only.
-* `allowFileAccessFromFileURLs`: Sets whether JavaScript running in the context of a file scheme URL should be allowed to access content from other file scheme URLs.
-* `allowUniversalAccessFromFileURLs`: Sets whether JavaScript running in the context of a file scheme URL should be allowed to access content from any origin.
* `appCachePath`: Sets the path to the Application Caches files. In order for the Application Caches API to be enabled, this option must be set a path to which the application can write.
* `blockNetworkImage`: Sets whether the WebView should not load image resources from the network (resources accessed via http and https URI schemes). The default value is `false`.
* `blockNetworkLoads`: Sets whether the WebView should not load resources from the network. The default value is `false`.
+* `builtInZoomControls`: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `true`.
* `cacheMode`: Overrides the way the cache is used. The way the cache is used is based on the navigation type. For a normal page load, the cache is checked and content is re-validated as needed.
+* `clearSessionCache`: Set to `true` to have the session cookie cache cleared before the new window is opened.
* `cursiveFontFamily`: Sets the cursive font family name. The default value is `"cursive"`.
+* `databaseEnabled`: Set to `true` if you want the database storage API is enabled. The default value is `true`.
* `defaultFixedFontSize`: Sets the default fixed font size. The default value is `16`.
* `defaultFontSize`: Sets the default font size. The default value is `16`.
* `defaultTextEncodingName`: Sets the default text encoding name to use when decoding html pages. The default value is `"UTF-8"`.
+* `disableDefaultErrorPage`: Sets whether the default Android error page should be disabled. The default value is `false`.
* `disabledActionModeMenuItems`: Disables the action mode menu items according to menuItems flag.
+* `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`.
+* `domStorageEnabled`: Set to `true` if you want the DOM storage API is enabled. The default value is `true`.
* `fantasyFontFamily`: Sets the fantasy font family name. The default value is `"fantasy"`.
* `fixedFontFamily`: Sets the fixed font family name. The default value is `"monospace"`.
* `forceDark`: Set the force dark mode for this WebView. The default value is `AndroidInAppWebViewForceDark.FORCE_DARK_OFF`.
* `geolocationEnabled`: Sets whether Geolocation API is enabled. The default value is `true`.
+* `hardwareAcceleration`: Boolean value to enable Hardware Acceleration in the WebView.
+* `initialScale`: Sets the initial scale for this WebView. 0 means default. The behavior for the default scale depends on the state of `useWideViewPort` and `loadWithOverviewMode`.
* `layoutAlgorithm`: Sets the underlying layout algorithm. This will cause a re-layout of the WebView.
* `loadWithOverviewMode`: Sets whether the WebView loads pages in overview mode, that is, zooms out the content to fit on screen by width.
* `loadsImagesAutomatically`: Sets whether the WebView should load image resources. Note that this method controls loading of all images, including those embedded using the data URI scheme.
* `minimumLogicalFontSize`: Sets the minimum logical font size. The default is `8`.
-* `initialScale`: Sets the initial scale for this WebView. 0 means default. The behavior for the default scale depends on the state of `useWideViewPort` and `loadWithOverviewMode`.
+* `mixedContentMode`: Configures the WebView's behavior when a secure origin attempts to load a resource from an insecure origin.
* `needInitialFocus`: Tells the WebView whether it needs to set a node. The default value is `true`.
* `offscreenPreRaster`: Sets whether this WebView should raster tiles when it is offscreen but attached to a window.
+* `overScrollMode`: Sets the WebView's over-scroll mode. The default value is `AndroidOverScrollMode.OVER_SCROLL_IF_CONTENT_SCROLLS`.
+* `regexToCancelSubFramesLoading`: Regular expression used by `shouldOverrideUrlLoading` event to cancel navigation for frames that are not the main frame. If the url request of a subframe matches the regular expression, then the request of that subframe is canceled.
+* `rendererPriorityPolicy`: Set the renderer priority policy for this WebView.
+* `safeBrowsingEnabled`: Sets whether Safe Browsing is enabled. Safe Browsing allows WebView to protect against malware and phishing attacks by verifying the links.
* `sansSerifFontFamily`: Sets the sans-serif font family name. The default value is `"sans-serif"`.
+* `saveFormData`: Sets whether the WebView should save form data. In Android O, the platform has implemented a fully functional Autofill feature to store form data.
+* `scrollBarDefaultDelayBeforeFade`: Defines the delay in milliseconds that a scrollbar waits before fade out.
+* `scrollBarFadeDuration`: Define the scrollbar fade duration in milliseconds.
+* `scrollBarStyle`: Specify the style of the scrollbars. The scrollbars can be overlaid or inset. The default value is `AndroidScrollBarStyle.SCROLLBARS_INSIDE_OVERLAY`.
+* `scrollbarFadingEnabled`: Define whether scrollbars will fade when the view is not scrolling. The default value is `true`.
* `serifFontFamily`: Sets the serif font family name. The default value is `"sans-serif"`.
* `standardFontFamily`: Sets the standard font family name. The default value is `"sans-serif"`.
-* `saveFormData`: Sets whether the WebView should save form data. In Android O, the platform has implemented a fully functional Autofill feature to store form data.
-* `thirdPartyCookiesEnabled`: Boolean value to enable third party cookies in the WebView.
-* `hardwareAcceleration`: Boolean value to enable Hardware Acceleration in the WebView.
* `supportMultipleWindows`: Sets whether the WebView whether supports multiple windows.
-* `regexToCancelSubFramesLoading`: Regular expression used by `shouldOverrideUrlLoading` event to cancel navigation for frames that are not the main frame. If the url request of a subframe matches the regular expression, then the request of that subframe is canceled.
-* `overScrollMode`: Sets the WebView's over-scroll mode. The default value is `AndroidOverScrollMode.OVER_SCROLL_IF_CONTENT_SCROLLS`.
-* `scrollBarStyle`: Specify the style of the scrollbars. The scrollbars can be overlaid or inset. The default value is `AndroidScrollBarStyle.SCROLLBARS_INSIDE_OVERLAY`.
+* `textZoom`: Sets the text zoom of the page in percent. The default value is `100`.
+* `thirdPartyCookiesEnabled`: Boolean value to enable third party cookies in the WebView.
+* `useHybridComposition`: Set to `true` to use Flutter's new Hybrid Composition rendering method, which fixes all issues [here](https://github.com/flutter/flutter/issues/61133). The default value is `false`. Note that this option requires Flutter v1.20+ and should only be used on Android 10+ for release apps, as animations will drop frames on < Android 10 (see [Hybrid-Composition#performance](https://github.com/flutter/flutter/wiki/Hybrid-Composition#performance)).
+* `useOnRenderProcessGone`: Set to `true` to be able to listen at the `androidOnRenderProcessGone` event. The default value is `false`.
+* `useShouldInterceptRequest`: Set to `true` to be able to listen at the `androidShouldInterceptRequest` event. The default value is `false`.
+* `useWideViewPort`: Set to `true` if the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport.
* `verticalScrollbarPosition`: Set the position of the vertical scroll bar. The default value is `AndroidVerticalScrollbarPosition.SCROLLBAR_POSITION_DEFAULT`.
-* `scrollBarDefaultDelayBeforeFade`: Defines the delay in milliseconds that a scrollbar waits before fade out.
-* `scrollbarFadingEnabled`: Define whether scrollbars will fade when the view is not scrolling. The default value is `true`.
-* `scrollBarFadeDuration`: Define the scrollbar fade duration in milliseconds.
-* `rendererPriorityPolicy`: Set the renderer priority policy for this WebView.
-* `disableDefaultErrorPage`: Sets whether the default Android error page should be disabled. The default value is `false`.
##### `InAppWebView` iOS-specific options
-* `disallowOverScroll`: Set to `true` to disable the bouncing of the WebView when the scrolling has reached an edge of the content. The default value is `false`.
-* `enableViewportScale`: Set to `true` to allow a viewport meta tag to either disable or restrict the range of user scaling. The default value is `false`.
-* `suppressesIncrementalRendering`: Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory. The default value is `false`.
+* `accessibilityIgnoresInvertColors`: A Boolean value indicating whether the view ignores an accessibility request to invert its colors. The default value is `false`.
* `allowsAirPlayForMediaPlayback`: Set to `true` to allow AirPlay. The default value is `true`.
* `allowsBackForwardNavigationGestures`: Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. The default value is `true`.
-* `allowsLinkPreview`: Set to `true` to allow that pressing on a link displays a preview of the destination for the link. The default value is `true`.
-* `ignoresViewportScaleLimits`: Set to `true` if you want that the WebView should always allow scaling of the webpage, regardless of the author's intent.
* `allowsInlineMediaPlayback`: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls.
+* `allowsLinkPreview`: Set to `true` to allow that pressing on a link displays a preview of the destination for the link. The default value is `true`.
* `allowsPictureInPictureMediaPlayback`: Set to `true` to allow HTML5 videos play picture-in-picture. The default value is `true`.
-* `isFraudulentWebsiteWarningEnabled`: A Boolean value indicating whether warnings should be shown for suspected fraudulent content such as phishing or malware.
-* `selectionGranularity`: The level of granularity with which the user can interactively select content in the web view.
-* `dataDetectorTypes`: Specifying a dataDetectoryTypes value adds interactivity to web content that matches the value.
-* `sharedCookiesEnabled`: Set `true` if shared cookies from `HTTPCookieStorage.shared` should used for every load request in the WebView.
-* `automaticallyAdjustsScrollIndicatorInsets`: Configures whether the scroll indicator insets are automatically adjusted by the system. The default value is `false`.
-* `accessibilityIgnoresInvertColors`: A Boolean value indicating whether the view ignores an accessibility request to invert its colors. The default value is `false`.
-* `decelerationRate`: A `IOSUIScrollViewDecelerationRate` value that determines the rate of deceleration after the user lifts their finger. The default value is `IOSUIScrollViewDecelerationRate.NORMAL`.
-* `alwaysBounceVertical`: A Boolean value that determines whether bouncing always occurs when vertical scrolling reaches the end of the content. The default value is `false`.
* `alwaysBounceHorizontal`: A Boolean value that determines whether bouncing always occurs when horizontal scrolling reaches the end of the content view. The default value is `false`.
-* `scrollsToTop`: A Boolean value that controls whether the scroll-to-top gesture is enabled. The default value is `true`.
+* `alwaysBounceVertical`: A Boolean value that determines whether bouncing always occurs when vertical scrolling reaches the end of the content. The default value is `false`.
+* `automaticallyAdjustsScrollIndicatorInsets`: Configures whether the scroll indicator insets are automatically adjusted by the system. The default value is `false`.
+* `contentInsetAdjustmentBehavior`: Configures how safe area insets are added to the adjusted content inset. The default value is `IOSUIScrollViewContentInsetAdjustmentBehavior.NEVER`.
+* `dataDetectorTypes`: Specifying a dataDetectoryTypes value adds interactivity to web content that matches the value.
+* `decelerationRate`: A `IOSUIScrollViewDecelerationRate` value that determines the rate of deceleration after the user lifts their finger. The default value is `IOSUIScrollViewDecelerationRate.NORMAL`.
+* `disallowOverScroll`: Set to `true` to disable the bouncing of the WebView when the scrolling has reached an edge of the content. The default value is `false`.
+* `enableViewportScale`: Set to `true` to allow a viewport meta tag to either disable or restrict the range of user scaling. The default value is `false`.
+* `ignoresViewportScaleLimits`: Set to `true` if you want that the WebView should always allow scaling of the webpage, regardless of the author's intent.
+* `isFraudulentWebsiteWarningEnabled`: A Boolean value indicating whether warnings should be shown for suspected fraudulent content such as phishing or malware.
* `isPagingEnabled`: A Boolean value that determines whether paging is enabled for the scroll view. The default value is `false`.
* `maximumZoomScale`: A floating-point value that specifies the maximum scale factor that can be applied to the scroll view's content. The default value is `1.0`.
* `minimumZoomScale`: A floating-point value that specifies the minimum scale factor that can be applied to the scroll view's content. The default value is `1.0`.
-* `contentInsetAdjustmentBehavior`: Configures how safe area insets are added to the adjusted content inset. The default value is `IOSUIScrollViewContentInsetAdjustmentBehavior.NEVER`.
+* `scrollsToTop`: A Boolean value that controls whether the scroll-to-top gesture is enabled. The default value is `true`.
+* `selectionGranularity`: The level of granularity with which the user can interactively select content in the web view.
+* `sharedCookiesEnabled`: Set `true` if shared cookies from `HTTPCookieStorage.shared` should used for every load request in the WebView.
+* `suppressesIncrementalRendering`: Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory. The default value is `false`.
#### `InAppWebView` Events
@@ -701,6 +714,9 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
+ if (Platform.isAndroid) {
+ await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
+ }
runApp(new MyApp());
}
@@ -711,8 +727,8 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State {
- InAppWebViewController webView;
- ContextMenu contextMenu;
+ InAppWebViewController? webView;
+ ContextMenu? contextMenu;
String url = "";
double progress = 0;
@@ -729,7 +745,7 @@ class _MyAppState extends State {
onCreateContextMenu: (hitTestResult) async {
print("onCreateContextMenu");
print(hitTestResult.extra);
- print(await webView.getSelectedText());
+ print(await webView?.getSelectedText());
},
onHideContextMenu: () {
print("onHideContextMenu");
@@ -777,23 +793,23 @@ class _MyAppState extends State {
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
- debuggingEnabled: true,
+
)
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
- onLoadStart: (InAppWebViewController controller, String url) {
+ onLoadStart: (controller, url) {
setState(() {
- this.url = url;
+ this.url = url ?? '';
});
},
- onLoadStop: (InAppWebViewController controller, String url) async {
+ onLoadStop: (controller, url) async {
setState(() {
- this.url = url;
+ this.url = url ?? '';
});
},
- onProgressChanged: (InAppWebViewController controller, int progress) {
+ onProgressChanged: (controller, progress) {
setState(() {
this.progress = progress / 100;
});
@@ -807,25 +823,19 @@ class _MyAppState extends State {
RaisedButton(
child: Icon(Icons.arrow_back),
onPressed: () {
- if (webView != null) {
- webView.goBack();
- }
+ webView?.goBack();
},
),
RaisedButton(
child: Icon(Icons.arrow_forward),
onPressed: () {
- if (webView != null) {
- webView.goForward();
- }
+ webView?.goForward();
},
),
RaisedButton(
child: Icon(Icons.refresh),
onPressed: () {
- if (webView != null) {
- webView.reload();
- }
+ webView?.reload();
},
),
],
@@ -858,12 +868,16 @@ As `InAppWebView`, it has the same options and events. Use `InAppWebViewControll
Example:
```dart
import 'dart:async';
+import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
+ if (Platform.isAndroid) {
+ await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
+ }
runApp(new MyApp());
}
@@ -874,7 +888,7 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State {
- HeadlessInAppWebView headlessWebView;
+ HeadlessInAppWebView? headlessWebView;
String url = "";
@override
@@ -885,7 +899,7 @@ class _MyAppState extends State {
initialUrl: "https://flutter.dev/",
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
- debuggingEnabled: true,
+
),
),
onWebViewCreated: (controller) {
@@ -897,19 +911,19 @@ class _MyAppState extends State {
onLoadStart: (controller, url) async {
print("onLoadStart $url");
setState(() {
- this.url = url;
+ this.url = url ?? '';
});
},
onLoadStop: (controller, url) async {
print("onLoadStop $url");
setState(() {
- this.url = url;
+ this.url = url ?? '';
});
},
- onUpdateVisitedHistory: (InAppWebViewController controller, String url, bool androidIsReload) {
+ onUpdateVisitedHistory: (controller, url, androidIsReload) {
print("onUpdateVisitedHistory $url");
setState(() {
- this.url = url;
+ this.url = url ?? '';
});
},
);
@@ -918,7 +932,7 @@ class _MyAppState extends State {
@override
void dispose() {
super.dispose();
- headlessWebView.dispose();
+ headlessWebView?.dispose();
}
@override
@@ -938,8 +952,8 @@ class _MyAppState extends State {
Center(
child: RaisedButton(
onPressed: () async {
- await headlessWebView.dispose();
- await headlessWebView.run();
+ await headlessWebView?.dispose();
+ await headlessWebView?.run();
},
child: Text("Run HeadlessInAppWebView")),
),
@@ -947,7 +961,7 @@ class _MyAppState extends State {
child: RaisedButton(
onPressed: () async {
try {
- await headlessWebView.webViewController.evaluateJavascript(source: """console.log('Here is the message!');""");
+ await headlessWebView?.webViewController.evaluateJavascript(source: """console.log('Here is the message!');""");
} on MissingPluginException catch(e) {
print("HeadlessInAppWebView is not running. Click on \"Run HeadlessInAppWebView\"!");
}
@@ -957,7 +971,7 @@ class _MyAppState extends State {
Center(
child: RaisedButton(
onPressed: () {
- headlessWebView.dispose();
+ headlessWebView?.dispose();
},
child: Text("Dispose HeadlessInAppWebView")),
)
@@ -977,6 +991,8 @@ In-App Browser using native WebView.
Create a Class that extends the `InAppBrowser` Class in order to override the callbacks to manage the browser events.
Example:
```dart
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
@@ -987,22 +1003,22 @@ class MyInAppBrowser extends InAppBrowser {
}
@override
- Future onLoadStart(String url) async {
+ Future onLoadStart(url) async {
print("\n\nStarted $url\n\n");
}
@override
- Future onLoadStop(String url) async {
+ Future onLoadStop(url) async {
print("\n\nStopped $url\n\n");
}
@override
- void onLoadError(String url, int code, String message) {
+ void onLoadError(url, code, message) {
print("Can't load $url.. Error: $message");
}
@override
- void onProgressChanged(int progress) {
+ void onProgressChanged(progress) {
print("Progress: $progress");
}
@@ -1024,7 +1040,7 @@ class MyInAppBrowser extends InAppBrowser {
"ms ---> duration: " +
response.duration.toString() +
"ms " +
- response.url);
+ (response.url ?? ''));
}
@override
@@ -1032,13 +1048,16 @@ class MyInAppBrowser extends InAppBrowser {
print("""
console output:
message: ${consoleMessage.message}
- messageLevel: ${consoleMessage.messageLevel.toValue()}
+ messageLevel: ${consoleMessage.messageLevel?.toValue()}
""");
}
}
-void main() {
+Future main() async {
WidgetsFlutterBinding.ensureInitialized();
+ if (Platform.isAndroid) {
+ await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
+ }
runApp(
new MyApp(),
);
@@ -1046,7 +1065,7 @@ void main() {
class MyApp extends StatefulWidget {
final MyInAppBrowser browser = new MyInAppBrowser();
-
+
@override
_MyAppState createState() => new _MyAppState();
}
@@ -1111,27 +1130,27 @@ Specific options of the `InAppBrowser` class are:
##### `InAppBrowser` Cross-platform options
* `hidden`: Set to `true` to create the browser and load the page, but not show it. Omit or set to `false` to have the browser open and load normally. The default value is `false`.
-* `toolbarTop`: Set to `false` to hide the toolbar at the top of the WebView. The default value is `true`.
-* `toolbarTopBackgroundColor`: Set the custom background color of the toolbar at the top.
* `hideUrlBar`: Set to `true` to hide the url bar on the toolbar at the top. The default value is `false`.
+* `toolbarTopBackgroundColor`: Set the custom background color of the toolbar at the top.
+* `toolbarTop`: Set to `false` to hide the toolbar at the top of the WebView. The default value is `true`.
##### `InAppBrowser` Android-specific options
-* `hideTitleBar`: Set to `true` if you want the title should be displayed. The default value is `false`.
-* `toolbarTopFixedTitle`: Set the action bar's title.
* `closeOnCannotGoBack`: Set to `false` to not close the InAppBrowser when the user click on the back button and the WebView cannot go back to the history. The default value is `true`.
+* `hideTitleBar`: Set to `true` if you want the title should be displayed. The default value is `false`.
* `progressBar`: Set to `false` to hide the progress bar at the bottom of the toolbar at the top. The default value is `true`.
+* `toolbarTopFixedTitle`: Set the action bar's title.
##### `InAppBrowser` iOS-specific options
-* `toolbarBottom`: Set to `false` to hide the toolbar at the bottom of the WebView. The default value is `true`.
-* `toolbarBottomBackgroundColor`: Set the custom background color of the toolbar at the bottom.
-* `toolbarBottomTranslucent`: Set to `true` to set the toolbar at the bottom translucent. The default value is `true`.
* `closeButtonCaption`: Set the custom text for the close button.
* `closeButtonColor`: Set the custom color for the close button.
* `presentationStyle`: Set the custom modal presentation style when presenting the WebView. The default value is `IOSUIModalPresentationStyle.FULL_SCREEN`.
-* `transitionStyle`: Set to the custom transition style when presenting the WebView. The default value is `IOSUIModalTransitionStyle.COVER_VERTICAL`.
* `spinner`: Set to `false` to hide the spinner when the WebView is loading a page. The default value is `true`.
+* `toolbarBottomBackgroundColor`: Set the custom background color of the toolbar at the bottom.
+* `toolbarBottomTranslucent`: Set to `true` to set the toolbar at the bottom translucent. The default value is `true`.
+* `toolbarBottom`: Set to `false` to hide the toolbar at the bottom of the WebView. The default value is `true`.
+* `transitionStyle`: Set to the custom transition style when presenting the WebView. The default value is `IOSUIModalTransitionStyle.COVER_VERTICAL`.
#### `InAppBrowser` Events
@@ -1145,27 +1164,31 @@ Specific events of the `InAppBrowser` class are:
[Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android / [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS.
+If you want to use the `ChromeSafariBrowser` on Android 11+ you need to specify your app querying for `android.support.customtabs.action.CustomTabsService` in your `AndroidManifest.xml` (you can read more about it here: https://developers.google.com/web/android/custom-tabs/best-practices#applications_targeting_android_11_api_level_30_or_above).
+
You can initialize the `ChromeSafariBrowser` instance with an `InAppBrowser` fallback instance.
Create a Class that extends the `ChromeSafariBrowser` Class in order to override the callbacks to manage the browser events. Example:
```dart
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
class MyInAppBrowser extends InAppBrowser {
@override
- Future onLoadStart(String url) async {
+ Future onLoadStart(url) async {
print("\n\nStarted $url\n\n");
}
@override
- Future onLoadStop(String url) async {
+ Future onLoadStop(url) async {
print("\n\nStopped $url\n\n");
}
@override
- void onLoadError(String url, int code, String message) {
+ void onLoadError(url, code, message) {
print("\n\nCan't load $url.. Error: $message\n\n");
}
@@ -1196,11 +1219,12 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser {
}
}
-void main() {
+Future main() async {
WidgetsFlutterBinding.ensureInitialized();
- runApp(
- new MyApp(),
- );
+ if (Platform.isAndroid) {
+ await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
+ }
+ runApp(new MyApp());
}
class MyApp extends StatefulWidget {
@@ -1262,11 +1286,11 @@ Screenshots:
#### `ChromeSafariBrowser` Methods
-* `open({@required String url, ChromeSafariBrowserClassOptions options, Map headersFallback = const {}, InAppBrowserClassOptions optionsFallback})`: Opens an `url` in a new `ChromeSafariBrowser` instance.
-* `isOpened`: Returns `true` if the `ChromeSafariBrowser` instance is opened, otherwise `false`.
-* `close`: Closes the `ChromeSafariBrowser` instance.
* `addMenuItem`: Adds a `ChromeSafariBrowserMenuItem` to the menu.
* `addMenuItems`: Adds a list of `ChromeSafariBrowserMenuItem` to the menu.
+* `close`: Closes the `ChromeSafariBrowser` instance.
+* `isOpened`: Returns `true` if the `ChromeSafariBrowser` instance is opened, otherwise `false`.
+* `open({@required String url, ChromeSafariBrowserClassOptions options, Map headersFallback = const {}, InAppBrowserClassOptions optionsFallback})`: Opens an `url` in a new `ChromeSafariBrowser` instance.
* `static isAvailable`: On Android, returns `true` if Chrome Custom Tabs is available. On iOS, returns `true` if SFSafariViewController is available. Otherwise returns `false`.
#### `ChromeSafariBrowser` options
@@ -1274,18 +1298,18 @@ Screenshots:
##### `ChromeSafariBrowser` Android-specific options
* `addDefaultShareMenuItem`: Set to `false` if you don't want the default share item to the menu. The default value is `true`.
-* `showTitle`: Set to `false` if the title shouldn't be shown in the custom tab. The default value is `true`.
-* `toolbarBackgroundColor`: Set the custom background color of the toolbar.
* `enableUrlBarHiding`: Set to `true` to enable the url bar to hide as the user scrolls down on the page. The default value is `false`.
* `instantAppsEnabled`: Set to `true` to enable Instant Apps. The default value is `false`.
-* `packageName`: Set the name of the application package to handle the intent (for example `com.android.chrome`), or null to allow any application package.
* `keepAliveEnabled`: Set to `true` to enable Keep Alive. The default value is `false`.
+* `packageName`: Set the name of the application package to handle the intent (for example `com.android.chrome`), or null to allow any application package.
+* `showTitle`: Set to `false` if the title shouldn't be shown in the custom tab. The default value is `true`.
+* `toolbarBackgroundColor`: Set the custom background color of the toolbar.
##### `ChromeSafariBrowser` iOS-specific options
-* `entersReaderIfAvailable`: Set to `true` if Reader mode should be entered automatically when it is available for the webpage. The default value is `false`.
* `barCollapsingEnabled`: Set to `true` to enable bar collapsing. The default value is `false`.
* `dismissButtonStyle`: Set the custom style for the dismiss button. The default value is `IOSSafariDismissButtonStyle.DONE`.
+* `entersReaderIfAvailable`: Set to `true` if Reader mode should be entered automatically when it is available for the webpage. The default value is `false`.
* `preferredBarTintColor`: Set the custom background color of the navigation bar and the toolbar.
* `preferredControlTintColor`: Set the custom color of the control buttons on the navigation bar and the toolbar.
* `presentationStyle`: Set the custom modal presentation style when presenting the WebView. The default value is `IOSUIModalPresentationStyle.FULL_SCREEN`.
@@ -1310,6 +1334,9 @@ InAppLocalhostServer localhostServer = new InAppLocalhostServer();
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await localhostServer.start();
+ if (Platform.isAndroid) {
+ await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
+ }
runApp(new MyApp());
}
@@ -1330,17 +1357,17 @@ Future main() async {
initialUrl: "http://localhost:8080/assets/index.html",
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
- inAppWebViewOptions: InAppWebViewOptions(
- debuggingEnabled: true,
+ crossPlatform: InAppWebViewOptions(
+
)
),
- onWebViewCreated: (InAppWebViewController controller) {
+ onWebViewCreated: (controller) {
},
- onLoadStart: (InAppWebViewController controller, String url) {
+ onLoadStart: (controller, url) {
},
- onLoadStop: (InAppWebViewController controller, String url) {
+ onLoadStop: (controller, url) {
},
),
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ContentBlocker/ContentBlockerHandler.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ContentBlocker/ContentBlockerHandler.java
index fa245709..eee7b752 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ContentBlocker/ContentBlockerHandler.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ContentBlocker/ContentBlockerHandler.java
@@ -109,31 +109,33 @@ public class ContentBlockerHandler {
latch.await();
}
- if (!trigger.loadType.isEmpty()) {
- URI cUrl = new URI(webViewUrl[0]);
- String cHost = cUrl.getHost();
- int cPort = cUrl.getPort();
- String cScheme = cUrl.getScheme();
+ if (webViewUrl[0] != null) {
+ if (!trigger.loadType.isEmpty()) {
+ URI cUrl = new URI(webViewUrl[0]);
+ String cHost = cUrl.getHost();
+ int cPort = cUrl.getPort();
+ String cScheme = cUrl.getScheme();
- if ( (trigger.loadType.contains("first-party") && cHost != null && !(cScheme.equals(scheme) && cHost.equals(host) && cPort == port)) ||
- (trigger.loadType.contains("third-party") && cHost != null && cHost.equals(host)) )
- return null;
- }
- if (!trigger.ifTopUrl.isEmpty()) {
- boolean matchFound = false;
- for (String topUrl : trigger.ifTopUrl) {
- if (webViewUrl[0].startsWith(topUrl)) {
- matchFound = true;
- break;
- }
- }
- if (!matchFound)
- return null;
- }
- if (!trigger.unlessTopUrl.isEmpty()) {
- for (String topUrl : trigger.unlessTopUrl)
- if (webViewUrl[0].startsWith(topUrl))
+ if ( (trigger.loadType.contains("first-party") && cHost != null && !(cScheme.equals(scheme) && cHost.equals(host) && cPort == port)) ||
+ (trigger.loadType.contains("third-party") && cHost != null && cHost.equals(host)) )
return null;
+ }
+ if (!trigger.ifTopUrl.isEmpty()) {
+ boolean matchFound = false;
+ for (String topUrl : trigger.ifTopUrl) {
+ if (webViewUrl[0].startsWith(topUrl)) {
+ matchFound = true;
+ break;
+ }
+ }
+ if (!matchFound)
+ return null;
+ }
+ if (!trigger.unlessTopUrl.isEmpty()) {
+ for (String topUrl : trigger.unlessTopUrl)
+ if (webViewUrl[0].startsWith(topUrl))
+ return null;
+ }
}
switch (action.type) {
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java
index 912afd67..a59c6a95 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java
@@ -67,28 +67,9 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
"- See the official wiki here: https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects\n\n\n");
}
- // MutableContextWrapper mMutableContext = new MutableContextWrapper(Shared.activity);
- // webView = new InAppWebView(mMutableContext, this, id, options, contextMenu, containerView);
- // displayListenerProxy.onPostWebViewInitialization(displayManager);
- // mMutableContext.setBaseContext(context);
-
- webView = new InAppWebView(Shared.activity, this, id, windowId, options, contextMenu, containerView);
+ webView = new InAppWebView(context, this, id, windowId, options, contextMenu, containerView);
displayListenerProxy.onPostWebViewInitialization(displayManager);
- // fix https://github.com/pichillilorenzo/flutter_inappwebview/issues/182
- try {
- Class superClass = webView.getClass().getSuperclass();
- while(!superClass.getName().equals("android.view.View")) {
- superClass = superClass.getSuperclass();
- }
- Field mContext = superClass.getDeclaredField("mContext");
- mContext.setAccessible(true);
- mContext.set(webView, context);
- } catch (Exception e) {
- e.printStackTrace();
- Log.e(LOG_TAG, "Cannot find mContext for this WebView");
- }
-
webView.prepare();
if (windowId != 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 d985344a..fb3a5dbe 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java
@@ -677,9 +677,6 @@ final public class InAppWebView extends InputAwareWebView {
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(options.javaScriptEnabled);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- setWebContentsDebuggingEnabled(options.debuggingEnabled);
- }
settings.setJavaScriptCanOpenWindowsAutomatically(options.javaScriptCanOpenWindowsAutomatically);
settings.setBuiltInZoomControls(options.builtInZoomControls);
settings.setDisplayZoomControls(options.displayZoomControls);
@@ -853,7 +850,7 @@ final public class InAppWebView extends InputAwareWebView {
}
};
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !options.useHybridComposition) {
checkContextMenuShouldBeClosedTask = new Runnable() {
@Override
public void run() {
@@ -1133,9 +1130,6 @@ final public class InAppWebView extends InputAwareWebView {
if (newOptionsMap.get("javaScriptEnabled") != null && options.javaScriptEnabled != newOptions.javaScriptEnabled)
settings.setJavaScriptEnabled(newOptions.javaScriptEnabled);
- if (newOptionsMap.get("debuggingEnabled") != null && options.debuggingEnabled != newOptions.debuggingEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
- setWebContentsDebuggingEnabled(newOptions.debuggingEnabled);
-
if (newOptionsMap.get("useShouldInterceptAjaxRequest") != null && options.useShouldInterceptAjaxRequest != newOptions.useShouldInterceptAjaxRequest) {
String placeholderValue = newOptions.useShouldInterceptAjaxRequest ? "true" : "false";
String sourceJs = InAppWebView.enableVariableForShouldInterceptAjaxRequestJS.replace("$PLACEHOLDER_VALUE", placeholderValue);
@@ -1505,14 +1499,11 @@ final public class InAppWebView extends InputAwareWebView {
}
@Override
- protected void onScrollChanged (int l,
- int t,
- int oldl,
- int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
-
- int x = (int) (l/scale);
- int y = (int) (t/scale);
+ protected void onScrollChanged (int x,
+ int y,
+ int oldX,
+ int oldY) {
+ super.onScrollChanged(x, y, oldX, oldY);
if (floatingContextMenu != null) {
floatingContextMenu.setAlpha(0f);
@@ -1662,12 +1653,18 @@ final public class InAppWebView extends InputAwareWebView {
@Override
public ActionMode startActionMode(ActionMode.Callback callback) {
+ if (options.useHybridComposition && !options.disableContextMenu && (contextMenu == null || contextMenu.keySet().size() == 0)) {
+ return super.startActionMode(callback);
+ }
return rebuildActionMode(super.startActionMode(callback), callback);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+ if (options.useHybridComposition && !options.disableContextMenu && (contextMenu == null || contextMenu.keySet().size() == 0)) {
+ return super.startActionMode(callback, type);
+ }
return rebuildActionMode(super.startActionMode(callback, type), callback);
}
@@ -1711,6 +1708,7 @@ final public class InAppWebView extends InputAwareWebView {
final MenuItem menuItem = actionMenu.getItem(i);
final int itemId = menuItem.getItemId();
final String itemTitle = menuItem.getTitle().toString();
+
TextView text = (TextView) LayoutInflater.from(this.getContext())
.inflate(R.layout.floating_action_mode_item, this, false);
text.setText(itemTitle);
@@ -1855,7 +1853,7 @@ final public class InAppWebView extends InputAwareWebView {
" var clientRect = range.getClientRects();" +
" if (clientRect.length > 0) {" +
" rangeY = clientRect[0].y;" +
- " } else if (document.activeElement) {" +
+ " } else if (document.activeElement != null && document.activeElement.tagName.toLowerCase() !== 'iframe') {" +
" var boundingClientRect = document.activeElement.getBoundingClientRect();" +
" rangeY = boundingClientRect.y;" +
" }" +
@@ -1865,7 +1863,7 @@ final public class InAppWebView extends InputAwareWebView {
@Override
public void onReceiveValue(String value) {
if (floatingContextMenu != null) {
- if (value != null) {
+ if (value != null && !value.equals("null")) {
int x = contextMenuPoint.x;
int y = (int) ((Float.parseFloat(value) * scale) + (floatingContextMenu.getHeight() / 3.5));
contextMenuPoint.y = y;
@@ -1873,6 +1871,7 @@ final public class InAppWebView extends InputAwareWebView {
} else {
floatingContextMenu.setVisibility(View.VISIBLE);
floatingContextMenu.animate().alpha(1f).setDuration(100).setListener(null);
+ onFloatingActionGlobalLayout(contextMenuPoint.x, contextMenuPoint.y);
}
}
}
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 7cf1fec2..b0e09bb4 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java
@@ -3,9 +3,11 @@ package com.pichillilorenzo.flutter_inappwebview.InAppWebView;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
+import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@@ -46,6 +48,7 @@ import com.pichillilorenzo.flutter_inappwebview.Shared;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -72,7 +75,8 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
private static final int PICKER = 1;
private static final int PICKER_LEGACY = 3;
final String DEFAULT_MIME_TYPES = "*/*";
- private static Uri outputFileUri;
+ private static Uri videoOutputFileUri;
+ private static Uri imageOutputFileUri;
protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
@@ -810,39 +814,37 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
// this filename instead
switch (requestCode) {
case PICKER:
- if (resultCode != RESULT_OK) {
- if (InAppWebViewFlutterPlugin.filePathCallback != null) {
- InAppWebViewFlutterPlugin.filePathCallback.onReceiveValue(null);
- }
- } else {
- Uri result[] = this.getSelectedFiles(data, resultCode);
- if (result != null) {
- InAppWebViewFlutterPlugin.filePathCallback.onReceiveValue(result);
- } else {
- InAppWebViewFlutterPlugin.filePathCallback.onReceiveValue(new Uri[]{outputFileUri});
- }
+ Uri[] results = null;
+ if (resultCode == RESULT_OK) {
+ results = getSelectedFiles(data, resultCode);
+ }
+
+ if (InAppWebViewFlutterPlugin.filePathCallback != null) {
+ InAppWebViewFlutterPlugin.filePathCallback.onReceiveValue(results);
}
break;
+
case PICKER_LEGACY:
- Uri result = resultCode != Activity.RESULT_OK ? null : data == null ? outputFileUri : data.getData();
+ Uri result = null;
+ if (resultCode == RESULT_OK) {
+ result = data != null ? data.getData() : getCapturedMediaFile();
+ }
+
InAppWebViewFlutterPlugin.filePathCallbackLegacy.onReceiveValue(result);
break;
-
}
+
InAppWebViewFlutterPlugin.filePathCallback = null;
InAppWebViewFlutterPlugin.filePathCallbackLegacy = null;
- outputFileUri = null;
+ imageOutputFileUri = null;
+ videoOutputFileUri = null;
return true;
}
private Uri[] getSelectedFiles(Intent data, int resultCode) {
- if (data == null) {
- return null;
- }
-
// we have one file selected
- if (data.getData() != null) {
+ if (data != null && data.getData() != null) {
if (resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
} else {
@@ -851,7 +853,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
}
// we have multiple files selected
- if (data.getClipData() != null) {
+ if (data != null && data.getClipData() != null) {
final int numSelectedFiles = data.getClipData().getItemCount();
Uri[] result = new Uri[numSelectedFiles];
for (int i = 0; i < numSelectedFiles; i++) {
@@ -859,6 +861,40 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
}
return result;
}
+
+ // we have a captured image or video file
+ Uri mediaUri = getCapturedMediaFile();
+ if (mediaUri != null) {
+ return new Uri[]{mediaUri};
+ }
+
+ return null;
+ }
+
+ private boolean isFileNotEmpty(Uri uri) {
+ Activity activity = inAppBrowserActivity != null ? inAppBrowserActivity : Shared.activity;
+
+ long length;
+ try {
+ AssetFileDescriptor descriptor = activity.getContentResolver().openAssetFileDescriptor(uri, "r");
+ length = descriptor.getLength();
+ descriptor.close();
+ } catch (IOException e) {
+ return false;
+ }
+
+ return length > 0;
+ }
+
+ private Uri getCapturedMediaFile() {
+ if (imageOutputFileUri != null && isFileNotEmpty(imageOutputFileUri)) {
+ return imageOutputFileUri;
+ }
+
+ if (videoOutputFileUri != null && isFileNotEmpty(videoOutputFileUri)) {
+ return videoOutputFileUri;
+ }
+
return null;
}
@@ -935,15 +971,15 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
private Intent getPhotoIntent() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- outputFileUri = getOutputUri(MediaStore.ACTION_IMAGE_CAPTURE);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
+ imageOutputFileUri = getOutputUri(MediaStore.ACTION_IMAGE_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, imageOutputFileUri);
return intent;
}
private Intent getVideoIntent() {
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
- outputFileUri = getOutputUri(MediaStore.ACTION_VIDEO_CAPTURE);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
+ videoOutputFileUri = getOutputUri(MediaStore.ACTION_VIDEO_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, videoOutputFileUri);
return intent;
}
@@ -971,6 +1007,20 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
return intent;
}
+ private Boolean acceptsAny(String[] types) {
+ if (isArrayEmpty(types)) {
+ return true;
+ }
+
+ for (String type : types) {
+ if (type.equals("*/*")) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private Boolean acceptsImages(String types) {
String mimeType = types;
if (types.matches("\\.\\w+")) {
@@ -981,7 +1031,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
private Boolean acceptsImages(String[] types) {
String[] mimeTypes = getAcceptedMimeType(types);
- return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "image");
+ return acceptsAny(types) || arrayContainsString(mimeTypes, "image");
}
private Boolean acceptsVideo(String types) {
@@ -994,7 +1044,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
private Boolean acceptsVideo(String[] types) {
String[] mimeTypes = getAcceptedMimeType(types);
- return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "video");
+ return acceptsAny(types) || arrayContainsString(mimeTypes, "video");
}
private Boolean arrayContainsString(String[] array, String pattern) {
@@ -1056,31 +1106,29 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
String prefix = "";
String suffix = "";
String dir = "";
- String filename = "";
if (intentType.equals(MediaStore.ACTION_IMAGE_CAPTURE)) {
- prefix = "image-";
+ prefix = "image";
suffix = ".jpg";
dir = Environment.DIRECTORY_PICTURES;
} else if (intentType.equals(MediaStore.ACTION_VIDEO_CAPTURE)) {
- prefix = "video-";
+ prefix = "video";
suffix = ".mp4";
dir = Environment.DIRECTORY_MOVIES;
}
- filename = prefix + String.valueOf(System.currentTimeMillis()) + suffix;
-
// for versions below 6.0 (23) we use the old File creation & permissions model
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// only this Directory works on all tested Android versions
// ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
File storageDir = Environment.getExternalStoragePublicDirectory(dir);
+ String filename = String.format("%s-%d%s", prefix, System.currentTimeMillis(), suffix);
return new File(storageDir, filename);
}
Activity activity = inAppBrowserActivity != null ? inAppBrowserActivity : Shared.activity;
File storageDir = activity.getApplicationContext().getExternalFilesDir(null);
- return File.createTempFile(filename, suffix, storageDir);
+ return File.createTempFile(prefix, suffix, storageDir);
}
private Boolean isArrayEmpty(String[] arr) {
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 ea62d7f0..dd7ca586 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java
@@ -176,7 +176,9 @@ public class InAppWebViewClient extends WebViewClient {
if (webView.options.useOnLoadResource) {
js += InAppWebView.resourceObserverJS.replaceAll("[\r\n]+", "");
}
- js += InAppWebView.checkGlobalKeyDownEventToHideContextMenuJS.replaceAll("[\r\n]+", "");
+ if (!webView.options.useHybridComposition) {
+ js += InAppWebView.checkGlobalKeyDownEventToHideContextMenuJS.replaceAll("[\r\n]+", "");
+ }
js += InAppWebView.onWindowFocusEventJS.replaceAll("[\r\n]+", "");
js += InAppWebView.onWindowBlurEventJS.replaceAll("[\r\n]+", "");
js += InAppWebView.printJS.replaceAll("[\r\n]+", "");
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java
index a40098fa..96ad6ffc 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java
@@ -26,7 +26,6 @@ public class InAppWebViewOptions implements Options {
public String userAgent = "";
public String applicationNameForUserAgent = "";
public Boolean javaScriptEnabled = true;
- public Boolean debuggingEnabled = false;
public Boolean javaScriptCanOpenWindowsAutomatically = false;
public Boolean mediaPlaybackRequiresUserGesture = true;
public Integer minimumFontSize = 8;
@@ -44,6 +43,8 @@ public class InAppWebViewOptions implements Options {
public Boolean disableHorizontalScroll = false;
public Boolean disableContextMenu = false;
public Boolean supportZoom = true;
+ public Boolean allowFileAccessFromFileURLs = false;
+ public Boolean allowUniversalAccessFromFileURLs = false;
public Integer textZoom = 100;
public Boolean clearSessionCache = false;
@@ -56,8 +57,6 @@ public class InAppWebViewOptions implements Options {
public Integer mixedContentMode;
public Boolean allowContentAccess = true;
public Boolean allowFileAccess = true;
- public Boolean allowFileAccessFromFileURLs = true;
- public Boolean allowUniversalAccessFromFileURLs = true;
public String appCachePath;
public Boolean blockNetworkImage = false;
public Boolean blockNetworkLoads = false;
@@ -97,6 +96,7 @@ public class InAppWebViewOptions implements Options {
public Boolean useShouldInterceptRequest = false;
public Boolean useOnRenderProcessGone = false;
public Boolean disableDefaultErrorPage = false;
+ public Boolean useHybridComposition = false;
@Override
public InAppWebViewOptions parse(Map options) {
@@ -129,9 +129,6 @@ public class InAppWebViewOptions implements Options {
case "javaScriptEnabled":
javaScriptEnabled = (Boolean) value;
break;
- case "debuggingEnabled":
- debuggingEnabled = (Boolean) value;
- break;
case "javaScriptCanOpenWindowsAutomatically":
javaScriptCanOpenWindowsAutomatically = (Boolean) value;
break;
@@ -339,6 +336,9 @@ public class InAppWebViewOptions implements Options {
case "disableDefaultErrorPage":
disableDefaultErrorPage = (Boolean) value;
break;
+ case "useHybridComposition":
+ useHybridComposition = (Boolean) value;
+ break;
}
}
@@ -355,7 +355,6 @@ public class InAppWebViewOptions implements Options {
options.put("userAgent", userAgent);
options.put("applicationNameForUserAgent", applicationNameForUserAgent);
options.put("javaScriptEnabled", javaScriptEnabled);
- options.put("debuggingEnabled", debuggingEnabled);
options.put("javaScriptCanOpenWindowsAutomatically", javaScriptCanOpenWindowsAutomatically);
options.put("mediaPlaybackRequiresUserGesture", mediaPlaybackRequiresUserGesture);
options.put("minimumFontSize", minimumFontSize);
@@ -425,6 +424,7 @@ public class InAppWebViewOptions implements Options {
options.put("useShouldInterceptRequest", useShouldInterceptRequest);
options.put("useOnRenderProcessGone", useOnRenderProcessGone);
options.put("disableDefaultErrorPage", disableDefaultErrorPage);
+ options.put("useHybridComposition", useHybridComposition);
return options;
}
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InputAwareWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InputAwareWebView.java
index 24f77f4d..6a69a084 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InputAwareWebView.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InputAwareWebView.java
@@ -16,6 +16,8 @@ import android.widget.ListPopupWindow;
* A WebView subclass that mirrors the same implementation hacks that the system WebView does in
* order to correctly create an InputConnection.
*
+ * These hacks are only needed in Android versions below N and exist to create an InputConnection
+ * on the WebView's dedicated input, or IME, thread. The majority of this proxying logic is in
* https://github.com/flutter/plugins/blob/master/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java
*/
public class InputAwareWebView extends WebView {
@@ -234,9 +236,9 @@ public class InputAwareWebView extends WebView {
private boolean isCalledFromListPopupWindowShow() {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
- for (int i = 0; i < stackTraceElements.length; i++) {
- if (stackTraceElements[i].getClassName().equals(ListPopupWindow.class.getCanonicalName())
- && stackTraceElements[i].getMethodName().equals("show")) {
+ for (StackTraceElement stackTraceElement : stackTraceElements) {
+ if (stackTraceElement.getClassName().equals(ListPopupWindow.class.getCanonicalName())
+ && stackTraceElement.getMethodName().equals("show")) {
return true;
}
}
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewStatic.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewStatic.java
index a9744e28..db74d146 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewStatic.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewStatic.java
@@ -72,6 +72,13 @@ public class InAppWebViewStatic implements MethodChannel.MethodCallHandler {
result.success(null);
}
break;
+ case "setWebContentsDebuggingEnabled":
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ boolean debuggingEnabled = (boolean) call.argument("debuggingEnabled");
+ WebView.setWebContentsDebuggingEnabled(debuggingEnabled);
+ }
+ result.success(true);
+ break;
default:
result.notImplemented();
}
diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies
old mode 100755
new mode 100644
index 6f9ab3d4..9b4c8abe
--- a/example/.flutter-plugins-dependencies
+++ b/example/.flutter-plugins-dependencies
@@ -1 +1 @@
-{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.10/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-5.4.11/","dependencies":[]}],"android":[{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.10/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-5.4.11/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.0.1+7/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+1/","dependencies":[]}],"windows":[],"web":[{"name":"url_launcher_web","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_web-0.1.2/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_inappwebview","dependencies":[]},{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_web","url_launcher_macos"]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]}],"date_created":"2020-09-07 18:06:16.830498","version":"1.20.3"}
\ No newline at end of file
+{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"android":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-01-29 00:53:56.206541","version":"1.26.0-13.0.pre.194"}
\ No newline at end of file
diff --git a/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/EmbedderV1Activity.java b/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/EmbedderV1Activity.java
index f1d74591..6ef55096 100755
--- a/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/EmbedderV1Activity.java
+++ b/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/EmbedderV1Activity.java
@@ -1,15 +1,16 @@
package com.pichillilorenzo.flutterwebviewexample;
import android.os.Bundle;
-import dev.flutter.plugins.e2e.E2EPlugin;
-import io.flutter.app.FlutterActivity;
+import dev.flutter.plugins.integration_test.IntegrationTestPlugin;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
-public class EmbedderV1Activity extends FlutterActivity {
+@SuppressWarnings("deprecation")
+public class EmbedderV1Activity extends io.flutter.app.FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- E2EPlugin.registerWith(registrarFor("dev.flutter.plugins.e2e.E2EPlugin"));
+ IntegrationTestPlugin.registerWith(
+ registrarFor("dev.flutter.plugins.integration_test.IntegrationTestPlugin"));
InAppWebViewFlutterPlugin.registerWith(
registrarFor("com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin"));
}
diff --git a/example/assets/sample_audio.ogg b/example/assets/sample_audio.ogg
new file mode 100644
index 00000000..27e17104
Binary files /dev/null and b/example/assets/sample_audio.ogg differ
diff --git a/example/assets/sample_video.mp4 b/example/assets/sample_video.mp4
new file mode 100644
index 00000000..a203d0cd
Binary files /dev/null and b/example/assets/sample_video.mp4 differ
diff --git a/example/integration_test/.env.dart b/example/integration_test/.env.dart
new file mode 100755
index 00000000..43e66331
--- /dev/null
+++ b/example/integration_test/.env.dart
@@ -0,0 +1 @@
+final environment = {"NODE_SERVER_IP":"192.168.1.129"};
\ No newline at end of file
diff --git a/example/integration_test/webview_flutter_test.dart b/example/integration_test/webview_flutter_test.dart
new file mode 100644
index 00000000..4c4f9746
--- /dev/null
+++ b/example/integration_test/webview_flutter_test.dart
@@ -0,0 +1,2050 @@
+// @dart = 2.9
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_inappwebview/flutter_inappwebview.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import '.env.dart';
+
+class Foo {
+ String bar;
+ String baz;
+
+ Foo({this.bar, this.baz});
+
+ Map toJson() {
+ return {
+ 'bar': this.bar,
+ 'baz': this.baz
+ };
+ }
+}
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ if (Platform.isAndroid) {
+ AndroidInAppWebViewController.setWebContentsDebuggingEnabled(false);
+ }
+
+ testWidgets('initialUrl', (WidgetTester tester) async {
+ final Completer controllerCompleter = Completer();
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialUrl: 'https://flutter.dev/',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ ),
+ ),
+ );
+ final InAppWebViewController controller = await controllerCompleter.future;
+ final String currentUrl = await controller.getUrl();
+ expect(currentUrl, 'https://flutter.dev/');
+ });
+
+ testWidgets('loadUrl', (WidgetTester tester) async {
+ final Completer controllerCompleter =
+ Completer();
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialUrl: 'https://flutter.dev/',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ ),
+ ),
+ );
+ final InAppWebViewController controller = await controllerCompleter.future;
+ await controller.loadUrl(url: 'https://www.google.com/');
+ final String currentUrl = await controller.getUrl();
+ expect(currentUrl, 'https://www.google.com/');
+ });
+
+ testWidgets('loadUrl with headers', (WidgetTester tester) async {
+ final Completer controllerCompleter =
+ Completer();
+ final StreamController pageStarts = StreamController();
+ final StreamController pageLoads = StreamController();
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialUrl: 'https://flutter.dev/',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true
+ )
+ ),
+ onLoadStart: (controller, url) {
+ pageStarts.add(url);
+ },
+ onLoadStop: (controller, url) {
+ pageLoads.add(url);
+ },
+ ),
+ ),
+ );
+ final InAppWebViewController controller = await controllerCompleter.future;
+ final Map headers = {
+ 'test_header': 'flutter_test_header'
+ };
+ await controller.loadUrl(url: 'https://flutter-header-echo.herokuapp.com/',
+ headers: headers);
+ final String currentUrl = await controller.getUrl();
+ expect(currentUrl, 'https://flutter-header-echo.herokuapp.com/');
+
+ await pageStarts.stream.firstWhere((String url) => url == currentUrl);
+ await pageLoads.stream.firstWhere((String url) => url == currentUrl);
+
+ final String content = await controller
+ .evaluateJavascript(source: 'document.documentElement.innerText');
+ expect(content.contains('flutter_test_header'), isTrue);
+ });
+
+ testWidgets('JavaScript Handler', (WidgetTester tester) async {
+
+ final Completer controllerCompleter =
+ Completer();
+ final Completer pageStarted = Completer();
+ final Completer pageLoaded = Completer();
+ final Completer handlerFoo = Completer();
+ final Completer handlerFooWithArgs = Completer();
+ final List messagesReceived = [];
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialFile: "test_assets/in_app_webview_javascript_handler_test.html",
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+
+ controller.addJavaScriptHandler(handlerName:'handlerFoo', callback: (args) {
+ handlerFoo.complete();
+ return Foo(bar: 'bar_value', baz: 'baz_value');
+ });
+
+ controller.addJavaScriptHandler(handlerName: 'handlerFooWithArgs', callback: (args) {
+ messagesReceived.add(args[0] as int);
+ messagesReceived.add(args[1] as bool);
+ messagesReceived.add(args[2].cast());
+ messagesReceived.add(args[3].cast());
+ messagesReceived.add(args[4].cast());
+ handlerFooWithArgs.complete();
+ });
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true
+ )
+ ),
+ onLoadStart: (controller, url) {
+ pageStarted.complete();
+ },
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+ final InAppWebViewController controller = await controllerCompleter.future;
+ await pageStarted.future;
+ await pageLoaded.future;
+ await handlerFoo.future;
+ await handlerFooWithArgs.future;
+
+ expect(messagesReceived[0], 1);
+ expect(messagesReceived[1], true);
+ expect(listEquals(messagesReceived[2], ["bar", 5]), true);
+ expect(mapEquals(messagesReceived[3], {"foo": "baz"}), true);
+ expect(mapEquals(messagesReceived[4], {"bar":"bar_value","baz":"baz_value"}), true);
+ });
+
+ testWidgets('resize webview', (WidgetTester tester) async {
+ final String resizeTest = '''
+
+ Resize test
+
+
+
+
+
+ ''';
+ final String resizeTestBase64 =
+ base64Encode(const Utf8Encoder().convert(resizeTest));
+ final Completer resizeCompleter = Completer();
+ final Completer pageStarted = Completer();
+ final Completer pageLoaded = Completer();
+ final Completer controllerCompleter =
+ Completer();
+ final GlobalKey key = GlobalKey();
+
+ final InAppWebView webView = InAppWebView(
+ key: key,
+ initialUrl: 'data:text/html;charset=utf-8;base64,$resizeTestBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+
+ controller.addJavaScriptHandler(handlerName:'resize', callback: (args) {
+ resizeCompleter.complete(true);
+ });
+ },
+ onLoadStart: (controller, url) {
+ pageStarted.complete();
+ },
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true
+ )
+ ),
+ );
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: Column(
+ children: [
+ SizedBox(
+ width: 200,
+ height: 200,
+ child: webView,
+ ),
+ ],
+ ),
+ ),
+ );
+
+ await controllerCompleter.future;
+ await pageStarted.future;
+ await pageLoaded.future;
+
+ expect(resizeCompleter.isCompleted, false);
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: Column(
+ children: [
+ SizedBox(
+ width: 400,
+ height: 400,
+ child: webView,
+ ),
+ ],
+ ),
+ ),
+ );
+
+ await resizeCompleter.future;
+ });
+
+ testWidgets('set custom userAgent', (WidgetTester tester) async {
+ final Completer controllerCompleter1 =
+ Completer();
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ initialUrl: 'about:blank',
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true,
+ userAgent: 'Custom_User_Agent1',
+ )
+ ),
+ onWebViewCreated: (controller) {
+ controllerCompleter1.complete(controller);
+ },
+ ),
+ ),
+ );
+ InAppWebViewController controller1 = await controllerCompleter1.future;
+ final String customUserAgent1 = await controller1.evaluateJavascript(source: 'navigator.userAgent;');
+ expect(customUserAgent1, 'Custom_User_Agent1');
+
+ await controller1.setOptions(options: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ userAgent: 'Custom_User_Agent2',
+ )
+ ));
+
+ final String customUserAgent2 = await controller1.evaluateJavascript(source: 'navigator.userAgent;');
+ expect(customUserAgent2, 'Custom_User_Agent2');
+ });
+
+ group('Video playback policy', () {
+ String videoTestBase64;
+ setUpAll(() async {
+ final ByteData videoData =
+ await rootBundle.load('test_assets/sample_video.mp4');
+ final String base64VideoData =
+ base64Encode(Uint8List.view(videoData.buffer));
+ final String videoTest = '''
+
+ Video auto play
+
+
+
+
+
+
+ ''';
+ videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest));
+ });
+
+ testWidgets('Auto media playback', (WidgetTester tester) async {
+ Completer controllerCompleter =
+ Completer();
+ Completer pageLoaded = Completer();
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true,
+ mediaPlaybackRequiresUserGesture: false
+ )
+ ),
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+ InAppWebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ bool isPaused = await controller.evaluateJavascript(source: 'isPaused();');
+ expect(isPaused, false);
+
+ controllerCompleter = Completer();
+ pageLoaded = Completer();
+
+ // We change the key to re-create a new webview as we change the mediaPlaybackRequiresUserGesture
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true,
+ mediaPlaybackRequiresUserGesture: true
+ )
+ ),
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+
+ controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ isPaused = await controller.evaluateJavascript(source: 'isPaused();');
+ expect(isPaused, true);
+ });
+
+ testWidgets('Video plays inline when allowsInlineMediaPlayback is true',
+ (WidgetTester tester) async {
+ Completer controllerCompleter =
+ Completer();
+ Completer pageLoaded = Completer();
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true,
+ mediaPlaybackRequiresUserGesture: false,
+ ),
+ ios: IOSInAppWebViewOptions(
+ allowsInlineMediaPlayback: true
+ )
+ ),
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+ InAppWebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ bool isFullScreen =
+ await controller.evaluateJavascript(source: 'isFullScreen();');
+ expect(isFullScreen, false);
+ });
+
+ testWidgets('Video plays fullscreen when allowsInlineMediaPlayback is false',
+ (WidgetTester tester) async {
+ Completer controllerCompleter =
+ Completer();
+ Completer pageLoaded = Completer();
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true,
+ mediaPlaybackRequiresUserGesture: false,
+ ),
+ ios: IOSInAppWebViewOptions(
+ allowsInlineMediaPlayback: false
+ )
+ ),
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+
+ InAppWebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ bool isFullScreen = await controller.evaluateJavascript(source: 'isFullScreen();');
+ expect(isFullScreen, true);
+ }, skip: true /*https://github.com/flutter/flutter/issues/72572 */);
+ });
+
+ group('Audio playback policy', () {
+ String audioTestBase64;
+ setUpAll(() async {
+ final ByteData audioData =
+ await rootBundle.load('test_assets/sample_audio.ogg');
+ final String base64AudioData =
+ base64Encode(Uint8List.view(audioData.buffer));
+ final String audioTest = '''
+
+ Audio auto play
+
+
+
+
+
+
+ ''';
+ audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest));
+ });
+
+ testWidgets('Auto media playback', (WidgetTester tester) async {
+ Completer controllerCompleter =
+ Completer();
+ Completer pageStarted = Completer();
+ Completer pageLoaded = Completer();
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true,
+ mediaPlaybackRequiresUserGesture: false
+ )
+ ),
+ onLoadStart: (controller, url) {
+ pageStarted.complete();
+ },
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+ InAppWebViewController controller = await controllerCompleter.future;
+ await pageStarted.future;
+ await pageLoaded.future;
+
+ bool isPaused = await controller.evaluateJavascript(source: 'isPaused();');
+ expect(isPaused, false);
+
+ controllerCompleter = Completer();
+ pageStarted = Completer();
+ pageLoaded = Completer();
+
+ // We change the key to re-create a new webview as we change the mediaPlaybackRequiresUserGesture
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ javaScriptEnabled: true,
+ mediaPlaybackRequiresUserGesture: true
+ ),
+ ),
+ onLoadStart: (controller, url) {
+ pageStarted.complete();
+ },
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+
+ controller = await controllerCompleter.future;
+ await pageStarted.future;
+ await pageLoaded.future;
+
+ isPaused = await controller.evaluateJavascript(source: 'isPaused();');
+ expect(isPaused, true);
+ });
+ });
+
+ testWidgets('getTitle', (WidgetTester tester) async {
+ final String getTitleTest = '''
+
+ Some title
+
+
+
+
+ ''';
+ final String getTitleTestBase64 =
+ base64Encode(const Utf8Encoder().convert(getTitleTest));
+ final Completer pageStarted = Completer();
+ final Completer pageLoaded = Completer();
+ final Completer controllerCompleter =
+ Completer();
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ onLoadStart: (controller, url) {
+ pageStarted.complete();
+ },
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+
+ final InAppWebViewController controller = await controllerCompleter.future;
+ await pageStarted.future;
+ await pageLoaded.future;
+
+ final String title = await controller.getTitle();
+ expect(title, 'Some title');
+ });
+
+ group('Programmatic Scroll', () {
+ testWidgets('setAndGetScrollPosition', (WidgetTester tester) async {
+ final String scrollTestPage = '''
+
+
+
+
+
+
+
+
+
+ ''';
+
+ final String scrollTestPageBase64 =
+ base64Encode(const Utf8Encoder().convert(scrollTestPage));
+
+ final Completer pageLoaded = Completer();
+ final Completer controllerCompleter =
+ Completer();
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ initialUrl:
+ 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+
+ final InAppWebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ await tester.pumpAndSettle(Duration(seconds: 3));
+
+ // Check scrollTo()
+ const int X_SCROLL = 123;
+ const int Y_SCROLL = 321;
+
+ await controller.scrollTo(x: X_SCROLL, y: Y_SCROLL);
+ await tester.pumpAndSettle(Duration(seconds: 1));
+ int scrollPosX = await controller.getScrollX();
+ int scrollPosY = await controller.getScrollY();
+ expect(scrollPosX, X_SCROLL);
+ expect(scrollPosY, Y_SCROLL);
+
+ // Check scrollBy() (on top of scrollTo())
+ await controller.scrollBy(x: X_SCROLL, y: Y_SCROLL);
+ await tester.pumpAndSettle(Duration(seconds: 1));
+ scrollPosX = await controller.getScrollX();
+ scrollPosY = await controller.getScrollY();
+ expect(scrollPosX, X_SCROLL * 2);
+ expect(scrollPosY, Y_SCROLL * 2);
+ });
+ });
+
+ group('Android Hybrid Composition', () {
+ testWidgets('setAndGetScrollPosition', (WidgetTester tester) async {
+ final String scrollTestPage = '''
+
+
+
+
+
+
+
+
+
+ ''';
+
+ final String scrollTestPageBase64 =
+ base64Encode(const Utf8Encoder().convert(scrollTestPage));
+
+ final Completer pageLoaded = Completer();
+ final Completer controllerCompleter =
+ Completer();
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: InAppWebView(
+ initialUrl:
+ 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
+ onWebViewCreated: (controller) {
+ controllerCompleter.complete(controller);
+ },
+ initialOptions: InAppWebViewGroupOptions(
+ android: AndroidInAppWebViewOptions(
+ useHybridComposition: true
+ )
+ ),
+ onLoadStop: (controller, url) {
+ pageLoaded.complete();
+ },
+ ),
+ ),
+ );
+
+ final InAppWebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ await tester.pumpAndSettle(Duration(seconds: 3));
+
+ // Check scrollTo()
+ const int X_SCROLL = 123;
+ const int Y_SCROLL = 321;
+
+ await controller.scrollTo(x: X_SCROLL, y: Y_SCROLL);
+ await tester.pumpAndSettle(Duration(seconds: 1));
+ int scrollPosX = await controller.getScrollX();
+ int scrollPosY = await controller.getScrollY();
+ expect(scrollPosX, X_SCROLL);
+ expect(scrollPosY, Y_SCROLL);
+
+ // Check scrollBy() (on top of scrollTo())
+ await controller.scrollBy(x: X_SCROLL, y: Y_SCROLL);
+ await tester.pumpAndSettle(Duration(seconds: 1));
+ scrollPosX = await controller.getScrollX();
+ scrollPosY = await controller.getScrollY();
+ expect(scrollPosX, X_SCROLL * 2);
+ expect(scrollPosY, Y_SCROLL * 2);
+ });
+ }, skip: !Platform.isAndroid);
+
+ group('shouldOverrideUrlLoading', () {
+ final String page = '''flutter_inappwebview