diff --git a/.metadata b/.metadata new file mode 100644 index 00000000..14879c61 --- /dev/null +++ b/.metadata @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index c28ba671..e65fd8c5 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 6.0.0 - 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 `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS` WebView settings - Added support for `onPermissionRequest` event on iOS 15.0+ diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/analysis_options.yaml @@ -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 diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 00000000..61b6c4de --- /dev/null +++ b/example/analysis_options.yaml @@ -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 diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 00000000..6f568019 --- /dev/null +++ b/example/android/.gitignore @@ -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 diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..7bb476c7 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..3db14bb5 --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..7bb476c7 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/example/ios/.gitignore @@ -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 diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/lib/generated_plugin_registrant.dart b/example/lib/generated_plugin_registrant.dart new file mode 100644 index 00000000..8ec58033 --- /dev/null +++ b/example/lib/generated_plugin_registrant.dart @@ -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(); +} diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index f6b6a4d6..c8102994 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'dart:io'; // import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; // import 'package:path_provider/path_provider.dart'; @@ -28,7 +29,7 @@ class _InAppWebViewExampleScreenState extends State { allowsInlineMediaPlayback: true ); - late PullToRefreshController pullToRefreshController; + PullToRefreshController? pullToRefreshController; late ContextMenu contextMenu; String url = ""; double progress = 0; @@ -69,7 +70,7 @@ class _InAppWebViewExampleScreenState extends State { contextMenuItemClicked.title); }); - pullToRefreshController = PullToRefreshController( + pullToRefreshController = !kIsWeb ? PullToRefreshController( settings: PullToRefreshSettings( color: Colors.blue, ), @@ -81,7 +82,7 @@ class _InAppWebViewExampleScreenState extends State { urlRequest: URLRequest(url: await webViewController?.getUrl())); } }, - ); + ) : null; } @override @@ -118,7 +119,7 @@ class _InAppWebViewExampleScreenState extends State { key: webViewKey, // contextMenu: contextMenu, initialUrlRequest: - URLRequest(url: Uri.parse("http://github.com/flutter/")), + URLRequest(url: Uri.parse("http://flutter.dev/")), // initialFile: "assets/index.html", initialUserScripts: UnmodifiableListView([]), initialSettings: settings, @@ -162,18 +163,18 @@ class _InAppWebViewExampleScreenState extends State { return NavigationActionPolicy.ALLOW; }, onLoadStop: (controller, url) async { - pullToRefreshController.endRefreshing(); + pullToRefreshController?.endRefreshing(); setState(() { this.url = url.toString(); urlController.text = this.url; }); }, onLoadError: (controller, url, code, message) { - pullToRefreshController.endRefreshing(); + pullToRefreshController?.endRefreshing(); }, onProgressChanged: (controller, progress) { if (progress == 100) { - pullToRefreshController.endRefreshing(); + pullToRefreshController?.endRefreshing(); } setState(() { this.progress = progress / 100; @@ -190,7 +191,7 @@ class _InAppWebViewExampleScreenState extends State { print(consoleMessage); }, ), - progress < 1.0 + !kIsWeb && progress < 1.0 ? LinearProgressIndicator(value: progress) : Container(), ], diff --git a/example/lib/main.dart b/example/lib/main.dart index 7a7654a0..43d97e5f 100755 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; @@ -19,7 +19,7 @@ Future main() async { // await Permission.microphone.request(); // await Permission.storage.request(); - if (Platform.isAndroid) { + if (defaultTargetPlatform == TargetPlatform.android) { await InAppWebViewController.setWebContentsDebuggingEnabled(true); var swAvailable = await WebViewFeature.isFeatureSupported( diff --git a/example/web/favicon.png b/example/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/example/web/favicon.png differ diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/example/web/icons/Icon-192.png differ diff --git a/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/example/web/icons/Icon-512.png differ diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/example/web/icons/Icon-maskable-192.png differ diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/example/web/icons/Icon-maskable-512.png differ diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 00000000..f99fc531 --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + flutter_inappwebview_example + + + + + + + diff --git a/example/web/manifest.json b/example/web/manifest.json new file mode 100644 index 00000000..13c8b581 --- /dev/null +++ b/example/web/manifest.json @@ -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" + } + ] +} diff --git a/lib/flutter_inappwebview_web.dart b/lib/flutter_inappwebview_web.dart new file mode 100755 index 00000000..06b6d905 --- /dev/null +++ b/lib/flutter_inappwebview_web.dart @@ -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'; \ No newline at end of file diff --git a/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart b/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart index a9ccc7ff..345177b9 100755 --- a/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart +++ b/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:ui'; import 'package:flutter/foundation.dart'; @@ -227,7 +226,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { static ChromeSafariBrowserSettings fromMap(Map map) { ChromeSafariBrowserSettings settings = new ChromeSafariBrowserSettings(); - if (Platform.isAndroid) { + if (defaultTargetPlatform == TargetPlatform.android) { settings.shareState = map["shareState"]; settings.showTitle = map["showTitle"]; settings.toolbarBackgroundColor = @@ -252,7 +251,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { } settings.screenOrientation = map["screenOrientation"]; } - if (Platform.isIOS || Platform.isMacOS) { + if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { settings.entersReaderIfAvailable = map["entersReaderIfAvailable"]; settings.barCollapsingEnabled = map["barCollapsingEnabled"]; settings.dismissButtonStyle = diff --git a/lib/src/in_app_browser/in_app_browser_settings.dart b/lib/src/in_app_browser/in_app_browser_settings.dart index 023829bd..9366f376 100755 --- a/lib/src/in_app_browser/in_app_browser_settings.dart +++ b/lib/src/in_app_browser/in_app_browser_settings.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:ui'; import 'package:flutter/foundation.dart'; @@ -281,7 +280,7 @@ class InAppBrowserSettings UtilColor.fromHex(map["toolbarTopBackgroundColor"]); settings.hideUrlBar = map["hideUrlBar"]; settings.hideProgressBar = map["hideProgressBar"]; - if (Platform.isAndroid) { + if (defaultTargetPlatform == TargetPlatform.android) { settings.hideTitleBar = map["hideTitleBar"]; settings.toolbarTopFixedTitle = map["toolbarTopFixedTitle"]; settings.closeOnCannotGoBack = map["closeOnCannotGoBack"]; @@ -289,7 +288,7 @@ class InAppBrowserSettings settings.shouldCloseOnBackButtonPressed = map["shouldCloseOnBackButtonPressed"]; } - if (Platform.isIOS || Platform.isMacOS) { + if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { settings.toolbarTopTranslucent = map["toolbarTopTranslucent"]; settings.toolbarTopTintColor = UtilColor.fromHex(map["toolbarTopTintColor"]); diff --git a/lib/src/in_app_webview/in_app_webview.dart b/lib/src/in_app_webview/in_app_webview.dart index 5a48f574..7a2c9902 100755 --- a/lib/src/in_app_webview/in_app_webview.dart +++ b/lib/src/in_app_webview/in_app_webview.dart @@ -9,6 +9,8 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/gestures.dart'; +import '../web/web_platform_manager.dart'; + import '../context_menu.dart'; import '../types.dart'; @@ -29,6 +31,7 @@ class InAppWebView extends StatefulWidget implements WebView { final Set>? gestureRecognizers; ///The window id of a [CreateWindowAction.windowId]. + @override final int? windowId; const InAppWebView({ @@ -537,7 +540,21 @@ class _InAppWebViewState extends State { widget.pullToRefreshController?.options.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 ? widget.initialSettings?.useHybridComposition : @@ -642,6 +659,10 @@ class _InAppWebViewState extends State { @override void dispose() { + int viewId = _controller.getViewId(); + if (kIsWeb && WebPlatformManager.webViews.containsKey(viewId)) { + WebPlatformManager.webViews.remove(viewId); + } super.dispose(); } diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart index 33dd0bb2..1d69ee2e 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -75,8 +75,7 @@ class InAppWebViewController InAppWebViewController(dynamic id, WebView webview) { this._id = id; - this._channel = - MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); + this._channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); this._channel.setMethodCallHandler(handleMethod); this._webview = webview; this._userScripts = @@ -2103,7 +2102,7 @@ class InAppWebViewController } ///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. /// ///**Supported Platforms/Implementations**: @@ -2781,7 +2780,7 @@ class InAppWebViewController /// // Flutter App /// child: InAppWebView( /// 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( /// jsObjectName: "myObject", /// onPostMessage: (message, sourceOrigin, isMainFrame, replyProxy) { @@ -2834,6 +2833,15 @@ class InAppWebViewController return await _channel.invokeMethod('canScrollHorizontally', args); } + ///Returns the iframe `id` attribute used on the Web platform. + /// + ///**Supported Platforms/Implementations**: + ///- Web + Future getIFrameId() async { + Map args = {}; + return await _channel.invokeMethod('getIFrameId', args); + } + ///Gets the default user agent. /// ///**Supported Platforms/Implementations**: @@ -2948,4 +2956,14 @@ class InAppWebViewController args.putIfAbsent('urlScheme', () => urlScheme); return await _staticChannel.invokeMethod('handlesURLScheme', args); } + + ///Used internally. + MethodChannel getChannel() { + return _channel; + } + + ///Used internally. + int getViewId() { + return _id; + } } diff --git a/lib/src/in_app_webview/in_app_webview_settings.dart b/lib/src/in_app_webview/in_app_webview_settings.dart index d5becaca..b4c1838a 100755 --- a/lib/src/in_app_webview/in_app_webview_settings.dart +++ b/lib/src/in_app_webview/in_app_webview_settings.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:ui'; import 'package:flutter/foundation.dart'; @@ -996,6 +995,56 @@ class InAppWebViewSettings ///- iOS 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( {this.useShouldOverrideUrlLoading = false, this.useOnLoadResource = false, @@ -1115,7 +1164,15 @@ class InAppWebViewSettings this.underPageBackgroundColor, this.isTextInteractionEnabled = 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) this.minimumFontSize = defaultTargetPlatform == TargetPlatform.android ? 8 : 0; @@ -1257,7 +1314,15 @@ class InAppWebViewSettings "underPageBackgroundColor": underPageBackgroundColor?.toHex(), "isTextInteractionEnabled": isTextInteractionEnabled, "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.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.clearSessionCache = map["clearSessionCache"]; settings.builtInZoomControls = map["builtInZoomControls"]; @@ -1382,7 +1457,7 @@ class InAppWebViewSettings settings.horizontalScrollbarTrackColor = UtilColor.fromHex(map["horizontalScrollbarTrackColor"]); } - if (Platform.isIOS || Platform.isMacOS) { + if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { settings.disallowOverScroll = map["disallowOverScroll"]; settings.enableViewportScale = map["enableViewportScale"]; settings.suppressesIncrementalRendering = diff --git a/lib/src/in_app_webview/webview.dart b/lib/src/in_app_webview/webview.dart index af7036fa..e4f30d68 100644 --- a/lib/src/in_app_webview/webview.dart +++ b/lib/src/in_app_webview/webview.dart @@ -791,12 +791,27 @@ abstract class WebView { ///Initial url request that will be loaded. /// ///**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; ///Initial asset file that will be loaded. See [InAppWebViewController.loadFile] for explanation. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- Web final String? initialFile; ///Initial [InAppWebViewInitialData] that will be loaded. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- Web final InAppWebViewInitialData? initialData; ///Use [initialSettings] instead. @@ -804,9 +819,18 @@ abstract class WebView { final InAppWebViewGroupOptions? initialOptions; ///Initial settings that will be used. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- Web final InAppWebViewSettings? initialSettings; ///Context menu which contains custom menu items to be shown when [ContextMenu] is presented. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS final ContextMenu? contextMenu; ///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. ///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. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS final UnmodifiableListView? initialUserScripts; ///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`. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS final PullToRefreshController? pullToRefreshController; ///Represents the WebView native implementation to be used. diff --git a/lib/src/types.dart b/lib/src/types.dart index e9fa0c63..ec639ee1 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -1,5 +1,4 @@ import 'dart:collection'; -import 'dart:io'; import 'dart:typed_data'; import 'dart:convert'; import 'dart:ui'; @@ -4780,9 +4779,9 @@ class PermissionResourceType { if (value != null) { try { Set valueList = [].toSet(); - if (Platform.isAndroid) { + if (defaultTargetPlatform == TargetPlatform.android) { valueList = PermissionResourceType._androidValues; - } else if (Platform.isIOS || Platform.isMacOS) { + } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { valueList = PermissionResourceType._appleValues; } return valueList.firstWhere((element) => element.toValue() == value); @@ -7360,9 +7359,9 @@ class SslErrorType { if (value != null) { try { Set valueList = [].toSet(); - if (Platform.isAndroid) { + if (defaultTargetPlatform == TargetPlatform.android) { valueList = SslErrorType._androidValues; - } else if (Platform.isIOS || Platform.isMacOS) { + } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { valueList = SslErrorType._appleValues; } return valueList.firstWhere((element) => element.toValue() == value); @@ -7377,7 +7376,7 @@ class SslErrorType { @override String toString() { - if (Platform.isAndroid) { + if (defaultTargetPlatform == TargetPlatform.android) { switch (_value) { case 1: return "SSL_EXPIRED"; @@ -7393,7 +7392,7 @@ class SslErrorType { default: return "SSL_NOTYETVALID"; } - } else if (Platform.isIOS || Platform.isMacOS) { + } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { switch (_value) { case 3: return "DENY"; diff --git a/lib/src/web/in_app_web_view_web_element.dart b/lib/src/web/in_app_web_view_web_element.dart new file mode 100644 index 00000000..44695f74 --- /dev/null +++ b/lib/src/web/in_app_web_view_web_element.dart @@ -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 handleMethodCall(MethodCall call) async { + switch (call.method) { + case "loadUrl": + URLRequest urlRequest = URLRequest.fromMap(call.arguments["urlRequest"].cast())!; + 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 _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 _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 _loadData({required String data, String mimeType = "text/html"}) async { + iframe.src = 'data:$mimeType,' + Uri.encodeFull(data); + } + + Future _loadFile({required String assetFilePath}) async { + iframe.src = assetFilePath; + } + + Future _reload() async { + var src = iframe.src; + if (src != null) { + iframe.contentWindow?.location.href = src; + } + } +} diff --git a/lib/src/web/main.dart b/lib/src/web/main.dart new file mode 100644 index 00000000..739ef7d9 --- /dev/null +++ b/lib/src/web/main.dart @@ -0,0 +1 @@ +export 'web_platform.dart'; \ No newline at end of file diff --git a/lib/src/web/shims/dart_ui.dart b/lib/src/web/shims/dart_ui.dart new file mode 100644 index 00000000..51b0d9c4 --- /dev/null +++ b/lib/src/web/shims/dart_ui.dart @@ -0,0 +1 @@ +export 'dart_ui_fake.dart' if (dart.library.html) 'dart_ui_real.dart'; \ No newline at end of file diff --git a/lib/src/web/shims/dart_ui_fake.dart b/lib/src/web/shims/dart_ui_fake.dart new file mode 100644 index 00000000..46a4ac82 --- /dev/null +++ b/lib/src/web/shims/dart_ui_fake.dart @@ -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(); \ No newline at end of file diff --git a/lib/src/web/shims/dart_ui_real.dart b/lib/src/web/shims/dart_ui_real.dart new file mode 100644 index 00000000..b0e3b648 --- /dev/null +++ b/lib/src/web/shims/dart_ui_real.dart @@ -0,0 +1 @@ +export 'dart:ui'; \ No newline at end of file diff --git a/lib/src/web/web_platform.dart b/lib/src/web/web_platform.dart new file mode 100644 index 00000000..25b90e83 --- /dev/null +++ b/lib/src/web/web_platform.dart @@ -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 handleMethodCall(MethodCall call) async { + switch (call.method) { + default: + throw PlatformException( + code: 'Unimplemented', + details: 'flutter_inappwebview for web doesn\'t implement \'${call.method}\'', + ); + } + } +} \ No newline at end of file diff --git a/lib/src/web/web_platform_manager.dart b/lib/src/web/web_platform_manager.dart new file mode 100644 index 00000000..f59aa09a --- /dev/null +++ b/lib/src/web/web_platform_manager.dart @@ -0,0 +1,3 @@ +class WebPlatformManager { + static final Map webViews = {}; +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 8678b95e..27639ddb 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + flutter_web_plugins: + sdk: flutter pedantic: ^1.11.1 # For information on the generic Dart part of this file, see the @@ -28,6 +30,10 @@ flutter: pluginClass: InAppWebViewFlutterPlugin ios: pluginClass: InAppWebViewFlutterPlugin + web: + pluginClass: FlutterInAppWebViewWebPlatform + fileName: flutter_inappwebview_web.dart + assets: - packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html