added initial web support

This commit is contained in:
Lorenzo Pichilli 2022-04-21 23:14:51 +02:00
parent 3ef7d1949d
commit 176d41d328
39 changed files with 746 additions and 33 deletions

10
.metadata Normal file
View File

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: c860cba910319332564e1e9d470a17074c1f2dfd
channel: stable
project_type: plugin

View File

@ -1,6 +1,7 @@
## 6.0.0 ## 6.0.0
- Deprecated old classes/properties/methods to make them eventually compatible with other operating systems and WebView engines. - Deprecated old classes/properties/methods to make them eventually compatible with other operating systems and WebView engines.
- Added Web support
- Added `pauseAllMediaPlayback`, `setAllMediaPlaybackSuspended`, `closeAllMediaPresentations`, `requestMediaPlaybackState` WebView controller methods - Added `pauseAllMediaPlayback`, `setAllMediaPlaybackSuspended`, `closeAllMediaPresentations`, `requestMediaPlaybackState` WebView controller methods
- Added `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS` WebView settings - Added `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS` WebView settings
- Added support for `onPermissionRequest` event on iOS 15.0+ - Added support for `onPermissionRequest` event on iOS 15.0+

4
analysis_options.yaml Normal file
View File

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

13
example/android/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pichillilorenzo.flutterwebviewexample">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pichillilorenzo.flutterwebviewexample">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

34
example/ios/.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,18 @@
//
// Generated file. Do not edit.
//
// ignore_for_file: directives_ordering
// ignore_for_file: lines_longer_than_80_chars
import 'package:flutter_inappwebview/flutter_inappwebview_web.dart';
import 'package:url_launcher_web/url_launcher_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs
void registerPlugins(Registrar registrar) {
FlutterInAppWebViewWebPlatform.registerWith(registrar);
UrlLauncherPlugin.registerWith(registrar);
registrar.registerMessageHandler();
}

View File

@ -3,6 +3,7 @@ import 'dart:collection';
import 'dart:io'; import 'dart:io';
// import 'dart:typed_data'; // import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart';
// import 'package:path_provider/path_provider.dart'; // import 'package:path_provider/path_provider.dart';
@ -28,7 +29,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
allowsInlineMediaPlayback: true allowsInlineMediaPlayback: true
); );
late PullToRefreshController pullToRefreshController; PullToRefreshController? pullToRefreshController;
late ContextMenu contextMenu; late ContextMenu contextMenu;
String url = ""; String url = "";
double progress = 0; double progress = 0;
@ -69,7 +70,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
contextMenuItemClicked.title); contextMenuItemClicked.title);
}); });
pullToRefreshController = PullToRefreshController( pullToRefreshController = !kIsWeb ? PullToRefreshController(
settings: PullToRefreshSettings( settings: PullToRefreshSettings(
color: Colors.blue, color: Colors.blue,
), ),
@ -81,7 +82,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
urlRequest: URLRequest(url: await webViewController?.getUrl())); urlRequest: URLRequest(url: await webViewController?.getUrl()));
} }
}, },
); ) : null;
} }
@override @override
@ -118,7 +119,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
key: webViewKey, key: webViewKey,
// contextMenu: contextMenu, // contextMenu: contextMenu,
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse("http://github.com/flutter/")), URLRequest(url: Uri.parse("http://flutter.dev/")),
// initialFile: "assets/index.html", // initialFile: "assets/index.html",
initialUserScripts: UnmodifiableListView<UserScript>([]), initialUserScripts: UnmodifiableListView<UserScript>([]),
initialSettings: settings, initialSettings: settings,
@ -162,18 +163,18 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
return NavigationActionPolicy.ALLOW; return NavigationActionPolicy.ALLOW;
}, },
onLoadStop: (controller, url) async { onLoadStop: (controller, url) async {
pullToRefreshController.endRefreshing(); pullToRefreshController?.endRefreshing();
setState(() { setState(() {
this.url = url.toString(); this.url = url.toString();
urlController.text = this.url; urlController.text = this.url;
}); });
}, },
onLoadError: (controller, url, code, message) { onLoadError: (controller, url, code, message) {
pullToRefreshController.endRefreshing(); pullToRefreshController?.endRefreshing();
}, },
onProgressChanged: (controller, progress) { onProgressChanged: (controller, progress) {
if (progress == 100) { if (progress == 100) {
pullToRefreshController.endRefreshing(); pullToRefreshController?.endRefreshing();
} }
setState(() { setState(() {
this.progress = progress / 100; this.progress = progress / 100;
@ -190,7 +191,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
print(consoleMessage); print(consoleMessage);
}, },
), ),
progress < 1.0 !kIsWeb && progress < 1.0
? LinearProgressIndicator(value: progress) ? LinearProgressIndicator(value: progress)
: Container(), : Container(),
], ],

View File

@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart';
@ -19,7 +19,7 @@ Future main() async {
// await Permission.microphone.request(); // await Permission.microphone.request();
// await Permission.storage.request(); // await Permission.storage.request();
if (Platform.isAndroid) { if (defaultTargetPlatform == TargetPlatform.android) {
await InAppWebViewController.setWebContentsDebuggingEnabled(true); await InAppWebViewController.setWebContentsDebuggingEnabled(true);
var swAvailable = await WebViewFeature.isFeatureSupported( var swAvailable = await WebViewFeature.isFeatureSupported(

BIN
example/web/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

104
example/web/index.html Normal file
View File

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="Demonstrates how to use the flutter_inappwebview plugin.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="flutter_inappwebview_example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>flutter_inappwebview_example</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
</body>
</html>

35
example/web/manifest.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "flutter_inappwebview_example",
"short_name": "flutter_inappwebview_example",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "Demonstrates how to use the flutter_inappwebview plugin.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

View File

@ -0,0 +1,25 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
library flutter_inappwebview;
export 'src/main.dart';
export 'src/web/main.dart';

View File

@ -1,4 +1,3 @@
import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -227,7 +226,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
static ChromeSafariBrowserSettings fromMap(Map<String, dynamic> map) { static ChromeSafariBrowserSettings fromMap(Map<String, dynamic> map) {
ChromeSafariBrowserSettings settings = new ChromeSafariBrowserSettings(); ChromeSafariBrowserSettings settings = new ChromeSafariBrowserSettings();
if (Platform.isAndroid) { if (defaultTargetPlatform == TargetPlatform.android) {
settings.shareState = map["shareState"]; settings.shareState = map["shareState"];
settings.showTitle = map["showTitle"]; settings.showTitle = map["showTitle"];
settings.toolbarBackgroundColor = settings.toolbarBackgroundColor =
@ -252,7 +251,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
} }
settings.screenOrientation = map["screenOrientation"]; settings.screenOrientation = map["screenOrientation"];
} }
if (Platform.isIOS || Platform.isMacOS) { if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) {
settings.entersReaderIfAvailable = map["entersReaderIfAvailable"]; settings.entersReaderIfAvailable = map["entersReaderIfAvailable"];
settings.barCollapsingEnabled = map["barCollapsingEnabled"]; settings.barCollapsingEnabled = map["barCollapsingEnabled"];
settings.dismissButtonStyle = settings.dismissButtonStyle =

View File

@ -1,4 +1,3 @@
import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -281,7 +280,7 @@ class InAppBrowserSettings
UtilColor.fromHex(map["toolbarTopBackgroundColor"]); UtilColor.fromHex(map["toolbarTopBackgroundColor"]);
settings.hideUrlBar = map["hideUrlBar"]; settings.hideUrlBar = map["hideUrlBar"];
settings.hideProgressBar = map["hideProgressBar"]; settings.hideProgressBar = map["hideProgressBar"];
if (Platform.isAndroid) { if (defaultTargetPlatform == TargetPlatform.android) {
settings.hideTitleBar = map["hideTitleBar"]; settings.hideTitleBar = map["hideTitleBar"];
settings.toolbarTopFixedTitle = map["toolbarTopFixedTitle"]; settings.toolbarTopFixedTitle = map["toolbarTopFixedTitle"];
settings.closeOnCannotGoBack = map["closeOnCannotGoBack"]; settings.closeOnCannotGoBack = map["closeOnCannotGoBack"];
@ -289,7 +288,7 @@ class InAppBrowserSettings
settings.shouldCloseOnBackButtonPressed = settings.shouldCloseOnBackButtonPressed =
map["shouldCloseOnBackButtonPressed"]; map["shouldCloseOnBackButtonPressed"];
} }
if (Platform.isIOS || Platform.isMacOS) { if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) {
settings.toolbarTopTranslucent = map["toolbarTopTranslucent"]; settings.toolbarTopTranslucent = map["toolbarTopTranslucent"];
settings.toolbarTopTintColor = settings.toolbarTopTintColor =
UtilColor.fromHex(map["toolbarTopTintColor"]); UtilColor.fromHex(map["toolbarTopTintColor"]);

View File

@ -9,6 +9,8 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import '../web/web_platform_manager.dart';
import '../context_menu.dart'; import '../context_menu.dart';
import '../types.dart'; import '../types.dart';
@ -29,6 +31,7 @@ class InAppWebView extends StatefulWidget implements WebView {
final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers; final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
///The window id of a [CreateWindowAction.windowId]. ///The window id of a [CreateWindowAction.windowId].
@override
final int? windowId; final int? windowId;
const InAppWebView({ const InAppWebView({
@ -537,7 +540,21 @@ class _InAppWebViewState extends State<InAppWebView> {
widget.pullToRefreshController?.options.toMap() ?? widget.pullToRefreshController?.options.toMap() ??
PullToRefreshSettings(enabled: false).toMap(); PullToRefreshSettings(enabled: false).toMap();
if (defaultTargetPlatform == TargetPlatform.android) { if (kIsWeb) {
return HtmlElementView(
viewType: 'com.pichillilorenzo/flutter_inappwebview',
onPlatformViewCreated: (int viewId) {
var webViewHtmlElement = WebPlatformManager.webViews[viewId]!;
webViewHtmlElement.initialSettings = widget.initialSettings;
webViewHtmlElement.initialUrlRequest = widget.initialUrlRequest;
webViewHtmlElement.initialFile = widget.initialFile;
webViewHtmlElement.initialData = widget.initialData;
webViewHtmlElement.prepare();
webViewHtmlElement.makeInitialLoad();
_onPlatformViewCreated(viewId);
},
);
} else if (defaultTargetPlatform == TargetPlatform.android) {
var useHybridComposition = (widget.initialSettings != null var useHybridComposition = (widget.initialSettings != null
? widget.initialSettings?.useHybridComposition ? widget.initialSettings?.useHybridComposition
: :
@ -642,6 +659,10 @@ class _InAppWebViewState extends State<InAppWebView> {
@override @override
void dispose() { void dispose() {
int viewId = _controller.getViewId();
if (kIsWeb && WebPlatformManager.webViews.containsKey(viewId)) {
WebPlatformManager.webViews.remove(viewId);
}
super.dispose(); super.dispose();
} }

View File

@ -75,8 +75,7 @@ class InAppWebViewController
InAppWebViewController(dynamic id, WebView webview) { InAppWebViewController(dynamic id, WebView webview) {
this._id = id; this._id = id;
this._channel = this._channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id');
MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id');
this._channel.setMethodCallHandler(handleMethod); this._channel.setMethodCallHandler(handleMethod);
this._webview = webview; this._webview = webview;
this._userScripts = this._userScripts =
@ -2103,7 +2102,7 @@ class InAppWebViewController
} }
///Gets the URL that was originally requested for the current page. ///Gets the URL that was originally requested for the current page.
///This is not always the same as the URL passed to [InAppWebView.onLoadStarted] because although the load for that URL has begun, ///This is not always the same as the URL passed to [InAppWebView.onLoadStart] because although the load for that URL has begun,
///the current page may not have changed. Also, there may have been redirects resulting in a different URL to that originally requested. ///the current page may not have changed. Also, there may have been redirects resulting in a different URL to that originally requested.
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
@ -2781,7 +2780,7 @@ class InAppWebViewController
/// // Flutter App /// // Flutter App
/// child: InAppWebView( /// child: InAppWebView(
/// onWebViewCreated: (controller) async { /// onWebViewCreated: (controller) async {
/// if (!Platform.isAndroid || await WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) { /// if (defaultTargetPlatform != TargetPlatform.android || await WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
/// await controller.addWebMessageListener(WebMessageListener( /// await controller.addWebMessageListener(WebMessageListener(
/// jsObjectName: "myObject", /// jsObjectName: "myObject",
/// onPostMessage: (message, sourceOrigin, isMainFrame, replyProxy) { /// onPostMessage: (message, sourceOrigin, isMainFrame, replyProxy) {
@ -2834,6 +2833,15 @@ class InAppWebViewController
return await _channel.invokeMethod('canScrollHorizontally', args); return await _channel.invokeMethod('canScrollHorizontally', args);
} }
///Returns the iframe `id` attribute used on the Web platform.
///
///**Supported Platforms/Implementations**:
///- Web
Future<String?> getIFrameId() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getIFrameId', args);
}
///Gets the default user agent. ///Gets the default user agent.
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
@ -2948,4 +2956,14 @@ class InAppWebViewController
args.putIfAbsent('urlScheme', () => urlScheme); args.putIfAbsent('urlScheme', () => urlScheme);
return await _staticChannel.invokeMethod('handlesURLScheme', args); return await _staticChannel.invokeMethod('handlesURLScheme', args);
} }
///Used internally.
MethodChannel getChannel() {
return _channel;
}
///Used internally.
int getViewId() {
return _id;
}
} }

View File

@ -1,4 +1,3 @@
import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -996,6 +995,56 @@ class InAppWebViewSettings
///- iOS ///- iOS
bool upgradeKnownHostsToHTTPS; bool upgradeKnownHostsToHTTPS;
///Specifies a feature policy for the iframe. A list of origins the frame is allowed to display content from.
///This attribute also accepts the values `self` and `src` which represent the origin in the iframe's src attribute.
///The default value is `src`.
///
///**Supported Platforms/Implementations**:
///- Web
String? iframeAllow;
///A boolean value indicating whether the inline frame is willing to be placed into full screen mode.
///
///**Supported Platforms/Implementations**:
///- Web
bool? iframeAllowFullscreen;
///A DOMTokenList that reflects the sandbox HTML attribute, indicating extra restrictions on the behavior of the nested content.
///
///**Supported Platforms/Implementations**:
///- Web
String? iframeSandox;
///A string that reflects the width HTML attribute, indicating the width of the frame.
///
///**Supported Platforms/Implementations**:
///- Web
String? iframeWidth;
///A string that reflects the height HTML attribute, indicating the height of the frame.
///
///**Supported Platforms/Implementations**:
///- Web
String? iframeHeight;
///A string that reflects the `referrerpolicy` HTML attribute indicating which referrer to use when fetching the linked resource.
///
///**Supported Platforms/Implementations**:
///- Web
String? iframeReferrerPolicy;
///A string that reflects the `name` HTML attribute, containing a name by which to refer to the frame.
///
///**Supported Platforms/Implementations**:
///- Web
String? iframeName;
///Specifies the Content Security Policy that an embedded document must agree to enforce upon itself.
///
///**Supported Platforms/Implementations**:
///- Web
String? iframeCsp;
InAppWebViewSettings( InAppWebViewSettings(
{this.useShouldOverrideUrlLoading = false, {this.useShouldOverrideUrlLoading = false,
this.useOnLoadResource = false, this.useOnLoadResource = false,
@ -1115,7 +1164,15 @@ class InAppWebViewSettings
this.underPageBackgroundColor, this.underPageBackgroundColor,
this.isTextInteractionEnabled = true, this.isTextInteractionEnabled = true,
this.isSiteSpecificQuirksModeEnabled = true, this.isSiteSpecificQuirksModeEnabled = true,
this.upgradeKnownHostsToHTTPS = true}) { this.upgradeKnownHostsToHTTPS = true,
this.iframeAllow,
this.iframeAllowFullscreen,
this.iframeSandox,
this.iframeWidth,
this.iframeHeight,
this.iframeReferrerPolicy,
this.iframeName,
this.iframeCsp,}) {
if (this.minimumFontSize == null) if (this.minimumFontSize == null)
this.minimumFontSize = this.minimumFontSize =
defaultTargetPlatform == TargetPlatform.android ? 8 : 0; defaultTargetPlatform == TargetPlatform.android ? 8 : 0;
@ -1257,7 +1314,15 @@ class InAppWebViewSettings
"underPageBackgroundColor": underPageBackgroundColor?.toHex(), "underPageBackgroundColor": underPageBackgroundColor?.toHex(),
"isTextInteractionEnabled": isTextInteractionEnabled, "isTextInteractionEnabled": isTextInteractionEnabled,
"isSiteSpecificQuirksModeEnabled": isSiteSpecificQuirksModeEnabled, "isSiteSpecificQuirksModeEnabled": isSiteSpecificQuirksModeEnabled,
"upgradeKnownHostsToHTTPS": upgradeKnownHostsToHTTPS "upgradeKnownHostsToHTTPS": upgradeKnownHostsToHTTPS,
"iframeAllow": iframeAllow,
"iframeAllowFullscreen": iframeAllowFullscreen,
"iframeSandox": iframeSandox,
"iframeWidth": iframeWidth,
"iframeHeight": iframeHeight,
"iframeReferrerPolicy": iframeReferrerPolicy,
"iframeName": iframeName,
"iframeCsp": iframeCsp,
}; };
} }
@ -1314,7 +1379,17 @@ class InAppWebViewSettings
settings.allowFileAccessFromFileURLs = map["allowFileAccessFromFileURLs"]; settings.allowFileAccessFromFileURLs = map["allowFileAccessFromFileURLs"];
settings.allowUniversalAccessFromFileURLs = settings.allowUniversalAccessFromFileURLs =
map["allowUniversalAccessFromFileURLs"]; map["allowUniversalAccessFromFileURLs"];
if (Platform.isAndroid) { if (kIsWeb) {
settings.iframeAllow = map["iframeAllow"];
settings.iframeAllowFullscreen = map["iframeAllowFullscreen"];
settings.iframeSandox = map["iframeSandox"];
settings.iframeWidth = map["iframeWidth"];
settings.iframeHeight = map["iframeHeight"];
settings.iframeReferrerPolicy = map["iframeReferrerPolicy"];
settings.iframeName = map["iframeName"];
settings.iframeCsp = map["iframeCsp"];
}
if (defaultTargetPlatform == TargetPlatform.android) {
settings.textZoom = map["textZoom"]; settings.textZoom = map["textZoom"];
settings.clearSessionCache = map["clearSessionCache"]; settings.clearSessionCache = map["clearSessionCache"];
settings.builtInZoomControls = map["builtInZoomControls"]; settings.builtInZoomControls = map["builtInZoomControls"];
@ -1382,7 +1457,7 @@ class InAppWebViewSettings
settings.horizontalScrollbarTrackColor = settings.horizontalScrollbarTrackColor =
UtilColor.fromHex(map["horizontalScrollbarTrackColor"]); UtilColor.fromHex(map["horizontalScrollbarTrackColor"]);
} }
if (Platform.isIOS || Platform.isMacOS) { if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) {
settings.disallowOverScroll = map["disallowOverScroll"]; settings.disallowOverScroll = map["disallowOverScroll"];
settings.enableViewportScale = map["enableViewportScale"]; settings.enableViewportScale = map["enableViewportScale"];
settings.suppressesIncrementalRendering = settings.suppressesIncrementalRendering =

View File

@ -791,12 +791,27 @@ abstract class WebView {
///Initial url request that will be loaded. ///Initial url request that will be loaded.
/// ///
///**NOTE for Android**: when loading an URL Request using "POST" method, headers are ignored. ///**NOTE for Android**: when loading an URL Request using "POST" method, headers are ignored.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
final URLRequest? initialUrlRequest; final URLRequest? initialUrlRequest;
///Initial asset file that will be loaded. See [InAppWebViewController.loadFile] for explanation. ///Initial asset file that will be loaded. See [InAppWebViewController.loadFile] for explanation.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
final String? initialFile; final String? initialFile;
///Initial [InAppWebViewInitialData] that will be loaded. ///Initial [InAppWebViewInitialData] that will be loaded.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
final InAppWebViewInitialData? initialData; final InAppWebViewInitialData? initialData;
///Use [initialSettings] instead. ///Use [initialSettings] instead.
@ -804,9 +819,18 @@ abstract class WebView {
final InAppWebViewGroupOptions? initialOptions; final InAppWebViewGroupOptions? initialOptions;
///Initial settings that will be used. ///Initial settings that will be used.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
final InAppWebViewSettings? initialSettings; final InAppWebViewSettings? initialSettings;
///Context menu which contains custom menu items to be shown when [ContextMenu] is presented. ///Context menu which contains custom menu items to be shown when [ContextMenu] is presented.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
final ContextMenu? contextMenu; final ContextMenu? contextMenu;
///Initial list of user scripts to be loaded at start or end of a page loading. ///Initial list of user scripts to be loaded at start or end of a page loading.
@ -816,11 +840,19 @@ abstract class WebView {
///**NOTE for iOS**: this property will be ignored if the [WebView.windowId] has been set. ///**NOTE for iOS**: this property will be ignored if the [WebView.windowId] has been set.
///There isn't any way to add/remove user scripts specific to iOS window WebViews. ///There isn't any way to add/remove user scripts specific to iOS window WebViews.
///This is a limitation of the native iOS WebKit APIs. ///This is a limitation of the native iOS WebKit APIs.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
final UnmodifiableListView<UserScript>? initialUserScripts; final UnmodifiableListView<UserScript>? initialUserScripts;
///Represents the pull-to-refresh feature controller. ///Represents the pull-to-refresh feature controller.
/// ///
///**NOTE for Android**: to be able to use the "pull-to-refresh" feature, [InAppWebViewSettings.useHybridComposition] must be `true`. ///**NOTE for Android**: to be able to use the "pull-to-refresh" feature, [InAppWebViewSettings.useHybridComposition] must be `true`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
final PullToRefreshController? pullToRefreshController; final PullToRefreshController? pullToRefreshController;
///Represents the WebView native implementation to be used. ///Represents the WebView native implementation to be used.

View File

@ -1,5 +1,4 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:convert'; import 'dart:convert';
import 'dart:ui'; import 'dart:ui';
@ -4780,9 +4779,9 @@ class PermissionResourceType {
if (value != null) { if (value != null) {
try { try {
Set<PermissionResourceType> valueList = <PermissionResourceType>[].toSet(); Set<PermissionResourceType> valueList = <PermissionResourceType>[].toSet();
if (Platform.isAndroid) { if (defaultTargetPlatform == TargetPlatform.android) {
valueList = PermissionResourceType._androidValues; valueList = PermissionResourceType._androidValues;
} else if (Platform.isIOS || Platform.isMacOS) { } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) {
valueList = PermissionResourceType._appleValues; valueList = PermissionResourceType._appleValues;
} }
return valueList.firstWhere((element) => element.toValue() == value); return valueList.firstWhere((element) => element.toValue() == value);
@ -7360,9 +7359,9 @@ class SslErrorType {
if (value != null) { if (value != null) {
try { try {
Set<SslErrorType> valueList = <SslErrorType>[].toSet(); Set<SslErrorType> valueList = <SslErrorType>[].toSet();
if (Platform.isAndroid) { if (defaultTargetPlatform == TargetPlatform.android) {
valueList = SslErrorType._androidValues; valueList = SslErrorType._androidValues;
} else if (Platform.isIOS || Platform.isMacOS) { } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) {
valueList = SslErrorType._appleValues; valueList = SslErrorType._appleValues;
} }
return valueList.firstWhere((element) => element.toValue() == value); return valueList.firstWhere((element) => element.toValue() == value);
@ -7377,7 +7376,7 @@ class SslErrorType {
@override @override
String toString() { String toString() {
if (Platform.isAndroid) { if (defaultTargetPlatform == TargetPlatform.android) {
switch (_value) { switch (_value) {
case 1: case 1:
return "SSL_EXPIRED"; return "SSL_EXPIRED";
@ -7393,7 +7392,7 @@ class SslErrorType {
default: default:
return "SSL_NOTYETVALID"; return "SSL_NOTYETVALID";
} }
} else if (Platform.isIOS || Platform.isMacOS) { } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) {
switch (_value) { switch (_value) {
case 3: case 3:
return "DENY"; return "DENY";

View File

@ -0,0 +1,148 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:html';
import '../in_app_webview/in_app_webview_settings.dart';
import '../types.dart';
class InAppWebViewWebElement {
late int _viewId;
late BinaryMessenger _messenger;
late IFrameElement iframe;
late MethodChannel _channel;
InAppWebViewSettings? initialSettings;
URLRequest? initialUrlRequest;
InAppWebViewInitialData? initialData;
String? initialFile;
late InAppWebViewSettings settings;
InAppWebViewWebElement({required int viewId, required BinaryMessenger messenger}) {
this._viewId = viewId;
this._messenger = messenger;
iframe = IFrameElement()
..id = 'flutter_inappwebview-$_viewId'
..style.border = 'none';
_channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_$_viewId',
const StandardMethodCodec(),
_messenger,
);
this._channel.setMethodCallHandler(handleMethodCall);
iframe.addEventListener('load', (event) async {
var obj = {
"url": iframe.src
};
_channel.invokeMethod("onLoadStart", obj);
await Future.delayed(Duration(milliseconds: 100));
_channel.invokeMethod("onLoadStop", obj);
});
}
/// Handles method calls over the MethodChannel of this plugin.
Future<dynamic> handleMethodCall(MethodCall call) async {
switch (call.method) {
case "loadUrl":
URLRequest urlRequest = URLRequest.fromMap(call.arguments["urlRequest"].cast<String, dynamic>())!;
await _loadUrl(urlRequest: urlRequest);
break;
case "loadData":
String data = call.arguments["data"];
String mimeType = call.arguments["mimeType"];
await _loadData(data: data, mimeType: mimeType);
break;
case "loadFile":
String assetFilePath = call.arguments["assetFilePath"];
await _loadFile(assetFilePath: assetFilePath);
break;
case "reload":
await _reload();
break;
case "getIFrameId":
return iframe.id;
default:
throw PlatformException(
code: 'Unimplemented',
details: 'flutter_inappwebview for web doesn\'t implement \'${call.method}\'',
);
}
}
void prepare() {
settings = initialSettings ?? InAppWebViewSettings();
iframe.allow = settings.iframeAllow ?? iframe.allow;
iframe.allowFullscreen = settings.iframeAllowFullscreen ?? iframe.allowFullscreen;
if (settings.iframeSandox != null) {
iframe.setAttribute("sandbox", settings.iframeSandox ?? "");
}
var width = settings.iframeWidth ?? iframe.width;
if (width == null || width.isEmpty) {
width = '100%';
}
var height = settings.iframeHeight ?? iframe.height;
if (height == null || height.isEmpty) {
height = '100%';
}
iframe.width = iframe.style.width = width;
iframe.height = iframe.style.height = height;
iframe.referrerPolicy = settings.iframeReferrerPolicy ?? iframe.referrerPolicy;
iframe.name = settings.iframeName ?? iframe.name;
iframe.csp = settings.iframeCsp ?? iframe.csp;
}
void makeInitialLoad() async {
if (initialUrlRequest != null) {
_loadUrl(urlRequest: initialUrlRequest!);
} else if (initialData != null) {
_loadData(data: initialData!.data, mimeType: initialData!.mimeType);
} else if (initialFile != null) {
_loadFile(assetFilePath: initialFile!);
}
}
Future<HttpRequest> _makeRequest(URLRequest urlRequest, {bool? withCredentials, String? responseType, String? mimeType, void onProgress(ProgressEvent e)?}) {
return HttpRequest.request(
urlRequest.url?.toString() ?? 'about:blank',
method: urlRequest.method,
requestHeaders: urlRequest.headers,
sendData: urlRequest.body,
withCredentials: withCredentials,
responseType: responseType,
mimeType: mimeType,
onProgress: onProgress
);
}
String _convertHttpResponseToData(HttpRequest httpRequest) {
final String contentType =
httpRequest.getResponseHeader('content-type') ?? 'text/html';
return 'data:$contentType,' + Uri.encodeFull(httpRequest.responseText ?? '');
}
Future<void> _loadUrl({required URLRequest urlRequest}) async {
if ((urlRequest.method == null || urlRequest.method == "GET") &&
(urlRequest.headers == null || urlRequest.headers!.isEmpty)) {
iframe.src = urlRequest.url.toString();
} else {
iframe.src = _convertHttpResponseToData(await _makeRequest(urlRequest));
}
}
Future<void> _loadData({required String data, String mimeType = "text/html"}) async {
iframe.src = 'data:$mimeType,' + Uri.encodeFull(data);
}
Future<void> _loadFile({required String assetFilePath}) async {
iframe.src = assetFilePath;
}
Future<void> _reload() async {
var src = iframe.src;
if (src != null) {
iframe.contentWindow?.location.href = src;
}
}
}

1
lib/src/web/main.dart Normal file
View File

@ -0,0 +1 @@
export 'web_platform.dart';

View File

@ -0,0 +1 @@
export 'dart_ui_fake.dart' if (dart.library.html) 'dart_ui_real.dart';

View File

@ -0,0 +1,29 @@
import 'dart:html' as html;
// Fake interface for the logic that this package needs from (web-only) dart:ui.
// This is conditionally exported so the analyzer sees these methods as available.
// ignore_for_file: avoid_classes_with_only_static_members
// ignore_for_file: camel_case_types
/// Shim for web_ui engine.PlatformViewRegistry
/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L62
class platformViewRegistry {
/// Shim for registerViewFactory
/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L72
static bool registerViewFactory(
String viewTypeId, html.Element Function(int viewId) viewFactory) {
return false;
}
}
/// Shim for web_ui engine.AssetManager.
/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/assets.dart#L12
class webOnlyAssetManager {
/// Shim for getAssetUrl.
/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/assets.dart#L45
static String getAssetUrl(String asset) => '';
}
/// Signature of callbacks that have no arguments and return no data.
typedef VoidCallback = void Function();

View File

@ -0,0 +1 @@
export 'dart:ui';

View File

@ -0,0 +1,39 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'web_platform_manager.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'shims/dart_ui.dart' as ui;
import 'in_app_web_view_web_element.dart';
/// Builds an iframe based WebView.
///
/// This is used as the default implementation for [WebView] on web.
class FlutterInAppWebViewWebPlatform {
/// Constructs a new instance of [FlutterInAppWebViewWebPlatform].
FlutterInAppWebViewWebPlatform(Registrar registrar) {
ui.platformViewRegistry.registerViewFactory(
'com.pichillilorenzo/flutter_inappwebview',
(int viewId) {
var webView = InAppWebViewWebElement(viewId: viewId, messenger: registrar);
WebPlatformManager.webViews.putIfAbsent(viewId, () => webView);
return webView.iframe;
});
}
static void registerWith(Registrar registrar) {
final pluginInstance = FlutterInAppWebViewWebPlatform(registrar);
}
/// Handles method calls over the MethodChannel of this plugin.
Future<dynamic> handleMethodCall(MethodCall call) async {
switch (call.method) {
default:
throw PlatformException(
code: 'Unimplemented',
details: 'flutter_inappwebview for web doesn\'t implement \'${call.method}\'',
);
}
}
}

View File

@ -0,0 +1,3 @@
class WebPlatformManager {
static final Map<int, dynamic> webViews = {};
}

View File

@ -14,6 +14,8 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_web_plugins:
sdk: flutter
pedantic: ^1.11.1 pedantic: ^1.11.1
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
@ -28,6 +30,10 @@ flutter:
pluginClass: InAppWebViewFlutterPlugin pluginClass: InAppWebViewFlutterPlugin
ios: ios:
pluginClass: InAppWebViewFlutterPlugin pluginClass: InAppWebViewFlutterPlugin
web:
pluginClass: FlutterInAppWebViewWebPlatform
fileName: flutter_inappwebview_web.dart
assets: assets:
- packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html - packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html