diff --git a/apps/flutter_parent/assets/html/html_wrapper.html b/apps/flutter_parent/assets/html/html_wrapper.html index 76218ec133..990e95789b 100644 --- a/apps/flutter_parent/assets/html/html_wrapper.html +++ b/apps/flutter_parent/assets/html/html_wrapper.html @@ -26,6 +26,8 @@ height: auto; margin: 0; padding: 0; + background-color: {BACKGROUND}; + color: {COLOR}; } img { @@ -65,6 +67,7 @@ a { word-wrap: break-word; + color: {LINK_COLOR} } .lti_button { @@ -81,6 +84,10 @@ font-size: 13px; margin: auto; } + + a:visited { + color: {VISITED_LINK_COLOR} + }
diff --git a/apps/flutter_parent/lib/main.dart b/apps/flutter_parent/lib/main.dart index deae3fbbe4..1ee693d05a 100644 --- a/apps/flutter_parent/lib/main.dart +++ b/apps/flutter_parent/lib/main.dart @@ -33,7 +33,6 @@ import 'package:flutter_parent/utils/notification_util.dart'; import 'package:flutter_parent/utils/old_app_migration.dart'; import 'package:flutter_parent/utils/remote_config_utils.dart'; import 'package:flutter_parent/utils/service_locator.dart'; -import 'package:webview_flutter/webview_flutter.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; void main() async { diff --git a/apps/flutter_parent/lib/screens/help/terms_of_use_screen.dart b/apps/flutter_parent/lib/screens/help/terms_of_use_screen.dart index 531152fe15..68756af170 100644 --- a/apps/flutter_parent/lib/screens/help/terms_of_use_screen.dart +++ b/apps/flutter_parent/lib/screens/help/terms_of_use_screen.dart @@ -75,10 +75,12 @@ class _TermsOfUseScreenState extends State { // Content return WebView( - darkMode: ParentTheme.of(context)?.isWebViewDarkMode, onWebViewCreated: (controller) { - controller.loadHtml(snapshot.data!.content!, horizontalPadding: 16); - }, + controller.loadHtml(snapshot.data!.content!, + horizontalPadding: 16, + darkMode: + ParentTheme.of(context)?.isWebViewDarkMode ?? false); + }, navigationDelegate: _handleNavigation ); }, diff --git a/apps/flutter_parent/lib/screens/web_login/web_login_screen.dart b/apps/flutter_parent/lib/screens/web_login/web_login_screen.dart index 1b8d04272c..dd8f5e7c08 100644 --- a/apps/flutter_parent/lib/screens/web_login/web_login_screen.dart +++ b/apps/flutter_parent/lib/screens/web_login/web_login_screen.dart @@ -134,7 +134,6 @@ class _WebLoginScreenState extends State { navigationDelegate: (request) => _navigate(context, request, verifyResult), javascriptMode: JavascriptMode.unrestricted, - darkMode: ParentTheme.of(context)?.isWebViewDarkMode, userAgent: ApiPrefs.getUserAgent(), onPageFinished: (url) => _pageFinished(url, verifyResult), onPageStarted: (url) => _pageStarted(url), @@ -243,16 +242,15 @@ class _WebLoginScreenState extends State { /// Load the authenticated url with any necessary cookies void _loadAuthUrl() async { _showLoadingState(); - CookieManager().clearCookies(); + final cookieManager = CookieManager(); + cookieManager.clearCookies(); if (widget.loginFlow == LoginFlow.siteAdmin) { - await _controller?.setAcceptThirdPartyCookies(true); if (_domain.contains('.instructure.com')) { - String cookie = 'canvas_sa_delegated=1;domain=.instructure.com;path=/;'; - await _controller?.setCookie(_domain, cookie); - await _controller?.setCookie('.instructure.com', cookie); + cookieManager.setCookie(WebViewCookie(name: 'canvas_sa_delegated', value: '1', domain: _domain)); + cookieManager.setCookie(WebViewCookie(name: 'canvas_sa_delegated', value: '1', domain: '.instructure.com')); } else { - await _controller?.setCookie(_domain, 'canvas_sa_delegated=1'); + cookieManager.setCookie(WebViewCookie(name: 'canvas_sa_delegated', value: '1', domain: _domain)); } } diff --git a/apps/flutter_parent/lib/utils/common_widgets/web_view/canvas_web_view.dart b/apps/flutter_parent/lib/utils/common_widgets/web_view/canvas_web_view.dart index 1a1a21ee17..4052a2549f 100644 --- a/apps/flutter_parent/lib/utils/common_widgets/web_view/canvas_web_view.dart +++ b/apps/flutter_parent/lib/utils/common_widgets/web_view/canvas_web_view.dart @@ -230,14 +230,15 @@ class _ResizingWebViewState extends State<_ResizingWebView> with WidgetsBindingO if (_content != widgetContent) { _height = widget.initialHeight; _content = widgetContent!; - _controller?.loadHtml(_content, horizontalPadding: widget.horizontalPadding); + _controller?.loadHtml(_content, + horizontalPadding: widget.horizontalPadding, + darkMode: ParentTheme.of(context)?.isWebViewDarkMode ?? false); } Widget child = WebView( javascriptMode: JavascriptMode.unrestricted, onPageFinished: _handlePageLoaded, onWebViewCreated: _handleWebViewCreated, - darkMode: ParentTheme.of(context)?.isWebViewDarkMode == true, navigationDelegate: _handleNavigation, gestureRecognizers: _webViewGestures(), javascriptChannels: _webViewChannels(), @@ -260,7 +261,10 @@ class _ResizingWebViewState extends State<_ResizingWebView> with WidgetsBindingO } void _handleWebViewCreated(WebViewController webViewController) async { - webViewController.loadHtml(_content, baseUrl: ApiPrefs.getDomain(), horizontalPadding: widget.horizontalPadding); + webViewController.loadHtml(_content, + baseUrl: ApiPrefs.getDomain(), + horizontalPadding: widget.horizontalPadding, + darkMode: ParentTheme.of(context)?.isWebViewDarkMode ?? false); _controller = webViewController; } diff --git a/apps/flutter_parent/lib/utils/common_widgets/web_view/simple_web_view_screen.dart b/apps/flutter_parent/lib/utils/common_widgets/web_view/simple_web_view_screen.dart index 7bcbb880d5..cc186be9dc 100644 --- a/apps/flutter_parent/lib/utils/common_widgets/web_view/simple_web_view_screen.dart +++ b/apps/flutter_parent/lib/utils/common_widgets/web_view/simple_web_view_screen.dart @@ -48,7 +48,6 @@ class _SimpleWebViewScreenState extends State { body: WebView( javascriptMode: JavascriptMode.unrestricted, userAgent: ApiPrefs.getUserAgent(), - darkMode: ParentTheme.of(context)?.isWebViewDarkMode, gestureRecognizers: Set()..add(Factory(() => WebViewGestureRecognizer())), navigationDelegate: _handleNavigation, onWebViewCreated: (controller) { diff --git a/apps/flutter_parent/lib/utils/web_view_utils.dart b/apps/flutter_parent/lib/utils/web_view_utils.dart index cd46d97c27..ff55e34412 100644 --- a/apps/flutter_parent/lib/utils/web_view_utils.dart +++ b/apps/flutter_parent/lib/utils/web_view_utils.dart @@ -30,15 +30,20 @@ extension WebViewUtils on WebViewController { String? html, { String? baseUrl, Map? headers, - double horizontalPadding = 0}) + double horizontalPadding = 0, + bool darkMode = false}) async { String fileText = await rootBundle.loadString('assets/html/html_wrapper.html'); html = _applyWorkAroundForDoubleSlashesAsUrlSource(html); html = _addProtocolToLinks(html); html = _checkForMathTags(html); html = fileText.replaceAll('{CANVAS_CONTENT}', html); + html = html.replaceAll('{BACKGROUND}', darkMode ? '#000000' : '#ffffff'); + html = html.replaceAll('{COLOR}', darkMode ? '#ffffff' : '#000000'); + html = html.replaceAll('{LINK_COLOR}', darkMode ? '#1283C4' : '#0374B5'); + html = html.replaceAll('{VISITED_LINK_COLOR}', darkMode ? '#C74BAF' : '#BF32A4'); html = html.replaceAll('{PADDING}', horizontalPadding.toString()); - this.loadData(baseUrl, html, 'text/html', 'utf-8'); + this.loadHtmlString(html, baseUrl: baseUrl); } /** diff --git a/apps/flutter_parent/plugins/webview_flutter/LICENSE b/apps/flutter_parent/plugins/webview_flutter/LICENSE deleted file mode 100644 index ad33cf3c3e..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2018 The Chromium Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/apps/flutter_parent/plugins/webview_flutter/android/build.gradle b/apps/flutter_parent/plugins/webview_flutter/android/build.gradle deleted file mode 100644 index 9e807667a5..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -group 'io.flutter.plugins.webviewflutter' -version '1.0-SNAPSHOT' - -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath "com.android.tools.build:gradle:7.4.2" - } -} - -rootProject.allprojects { - repositories { - google() - mavenCentral() - } -} - -apply plugin: 'com.android.library' - -android { - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 21 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } - - dependencies { - implementation 'androidx.annotation:annotation:1.0.0' - implementation 'androidx.webkit:webkit:1.7.0' - } - - namespace 'io.flutter.plugins.webviewflutter' -} diff --git a/apps/flutter_parent/plugins/webview_flutter/android/settings.gradle b/apps/flutter_parent/plugins/webview_flutter/android/settings.gradle deleted file mode 100644 index 5be7a4b4c6..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'webview_flutter' diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/AndroidManifest.xml b/apps/flutter_parent/plugins/webview_flutter/android/src/main/AndroidManifest.xml deleted file mode 100644 index a087f2c75c..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java b/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java deleted file mode 100644 index 1273e73496..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java +++ /dev/null @@ -1,143 +0,0 @@ -package io.flutter.plugins.webviewflutter; - -import static android.hardware.display.DisplayManager.DisplayListener; - -import android.annotation.TargetApi; -import android.hardware.display.DisplayManager; -import android.os.Build; -import android.util.Log; -import java.lang.reflect.Field; -import java.util.ArrayList; - -/** - * Works around an Android WebView bug by filtering some DisplayListener invocations. - * - *

Older Android WebView versions had assumed that when {@link DisplayListener#onDisplayChanged} - * is invoked, the display ID it is provided is of a valid display. However it turns out that when a - * display is removed Android may call onDisplayChanged with the ID of the removed display, in this - * case the Android WebView code tries to fetch and use the display with this ID and crashes with an - * NPE. - * - *

This issue was fixed in the Android WebView code in - * https://chromium-review.googlesource.com/517913 which is available starting WebView version - * 58.0.3029.125 however older webviews in the wild still have this issue. - * - *

Since Flutter removes virtual displays whenever a platform view is resized the webview crash - * is more likely to happen than other apps. And users were reporting this issue see: - * https://github.com/flutter/flutter/issues/30420 - * - *

This class works around the webview bug by unregistering the WebView's DisplayListener, and - * instead registering its own DisplayListener which delegates the callbacks to the WebView's - * listener unless it's a onDisplayChanged for an invalid display. - * - *

I did not find a clean way to get a handle of the WebView's DisplayListener so I'm using - * reflection to fetch all registered listeners before and after initializing a webview. In the - * first initialization of a webview within the process the difference between the lists is the - * webview's display listener. - */ -@TargetApi(Build.VERSION_CODES.KITKAT) -class DisplayListenerProxy { - private static final String TAG = "DisplayListenerProxy"; - - private ArrayList listenersBeforeWebView; - - /** Should be called prior to the webview's initialization. */ - void onPreWebViewInitialization(DisplayManager displayManager) { - listenersBeforeWebView = yoinkDisplayListeners(displayManager); - } - - /** Should be called after the webview's initialization. */ - void onPostWebViewInitialization(final DisplayManager displayManager) { - final ArrayList webViewListeners = yoinkDisplayListeners(displayManager); - // We recorded the list of listeners prior to initializing webview, any new listeners we see - // after initializing the webview are listeners added by the webview. - webViewListeners.removeAll(listenersBeforeWebView); - - if (webViewListeners.isEmpty()) { - // The Android WebView registers a single display listener per process (even if there - // are multiple WebView instances) so this list is expected to be non-empty only the - // first time a webview is initialized. - // Note that in an add2app scenario if the application had instantiated a non Flutter - // WebView prior to instantiating the Flutter WebView we are not able to get a reference - // to the WebView's display listener and can't work around the bug. - // - // This means that webview resizes in add2app Flutter apps with a non Flutter WebView - // running on a system with a webview prior to 58.0.3029.125 may crash (the Android's - // behavior seems to be racy so it doesn't always happen). - return; - } - - for (DisplayListener webViewListener : webViewListeners) { - // Note that while DisplayManager.unregisterDisplayListener throws when given an - // unregistered listener, this isn't an issue as the WebView code never calls - // unregisterDisplayListener. - displayManager.unregisterDisplayListener(webViewListener); - - // We never explicitly unregister this listener as the webview's listener is never - // unregistered (it's released when the process is terminated). - displayManager.registerDisplayListener( - new DisplayListener() { - @Override - public void onDisplayAdded(int displayId) { - for (DisplayListener webViewListener : webViewListeners) { - webViewListener.onDisplayAdded(displayId); - } - } - - @Override - public void onDisplayRemoved(int displayId) { - for (DisplayListener webViewListener : webViewListeners) { - webViewListener.onDisplayRemoved(displayId); - } - } - - @Override - public void onDisplayChanged(int displayId) { - if (displayManager.getDisplay(displayId) == null) { - return; - } - for (DisplayListener webViewListener : webViewListeners) { - webViewListener.onDisplayChanged(displayId); - } - } - }, - null); - } - } - - @SuppressWarnings({"unchecked", "PrivateApi"}) - private static ArrayList yoinkDisplayListeners(DisplayManager displayManager) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - // We cannot use reflection on Android P, but it shouldn't matter as it shipped - // with WebView 66.0.3359.158 and the WebView version the bug this code is working around was - // fixed in 61.0.3116.0. - return new ArrayList<>(); - } - try { - Field displayManagerGlobalField = DisplayManager.class.getDeclaredField("mGlobal"); - displayManagerGlobalField.setAccessible(true); - Object displayManagerGlobal = displayManagerGlobalField.get(displayManager); - Field displayListenersField = - displayManagerGlobal.getClass().getDeclaredField("mDisplayListeners"); - displayListenersField.setAccessible(true); - ArrayList delegates = - (ArrayList) displayListenersField.get(displayManagerGlobal); - - Field listenerField = null; - ArrayList listeners = new ArrayList<>(); - for (Object delegate : delegates) { - if (listenerField == null) { - listenerField = delegate.getClass().getField("mListener"); - listenerField.setAccessible(true); - } - DisplayManager.DisplayListener listener = - (DisplayManager.DisplayListener) listenerField.get(delegate); - listeners.add(listener); - } - return listeners; - } catch (NoSuchFieldException | IllegalAccessException e) { - Log.w(TAG, "Could not extract WebView's display listeners. " + e); - return new ArrayList<>(); - } - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java b/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java deleted file mode 100644 index 86b4fd412a..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.os.Build; -import android.os.Build.VERSION_CODES; -import android.webkit.CookieManager; -import android.webkit.ValueCallback; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; - -class FlutterCookieManager implements MethodCallHandler { - private final MethodChannel methodChannel; - - FlutterCookieManager(BinaryMessenger messenger) { - methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager"); - methodChannel.setMethodCallHandler(this); - } - - @Override - public void onMethodCall(MethodCall methodCall, Result result) { - switch (methodCall.method) { - case "clearCookies": - clearCookies(result); - break; - default: - result.notImplemented(); - } - } - - void dispose() { - methodChannel.setMethodCallHandler(null); - } - - private static void clearCookies(final Result result) { - CookieManager cookieManager = CookieManager.getInstance(); - final boolean hasCookies = cookieManager.hasCookies(); - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - cookieManager.removeAllCookies( - new ValueCallback() { - @Override - public void onReceiveValue(Boolean value) { - result.success(hasCookies); - } - }); - } else { - cookieManager.removeAllCookie(); - result.success(hasCookies); - } - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java deleted file mode 100644 index cd0e518ea6..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java +++ /dev/null @@ -1,477 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.annotation.TargetApi; -import android.content.Context; -import android.hardware.display.DisplayManager; -import android.os.Build; -import android.os.Handler; -import android.os.Message; -import android.view.View; -import android.webkit.WebChromeClient; -import android.webkit.WebResourceRequest; -import android.webkit.WebStorage; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.webkit.CookieManager; -import androidx.webkit.WebSettingsCompat; -import androidx.webkit.WebViewFeature; -import androidx.annotation.NonNull; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.platform.PlatformView; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import android.util.Log; - - -public class FlutterWebView implements PlatformView, MethodCallHandler { - private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames"; - private final InputAwareWebView webView; - private final MethodChannel methodChannel; - private final FlutterWebViewClient flutterWebViewClient; - private final Handler platformThreadHandler; - - // Verifies that a url opened by `Window.open` has a secure url. - private class FlutterWebChromeClient extends WebChromeClient { - @Override - public boolean onCreateWindow( - final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { - final WebViewClient webViewClient = - new WebViewClient() { - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public boolean shouldOverrideUrlLoading( - @NonNull WebView view, @NonNull WebResourceRequest request) { - final String url = request.getUrl().toString(); - if (!flutterWebViewClient.shouldOverrideUrlLoading( - FlutterWebView.this.webView, request)) { - webView.loadUrl(url); - } - return true; - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (!flutterWebViewClient.shouldOverrideUrlLoading( - FlutterWebView.this.webView, url)) { - webView.loadUrl(url); - } - return true; - } - }; - - final WebView newWebView = new WebView(view.getContext()); - newWebView.setWebViewClient(webViewClient); - - final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; - transport.setWebView(newWebView); - resultMsg.sendToTarget(); - - return true; - } - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - @SuppressWarnings("unchecked") - FlutterWebView( - final Context context, - BinaryMessenger messenger, - int id, - Map params, - View containerView) { - - DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); - DisplayManager displayManager = - (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); - displayListenerProxy.onPreWebViewInitialization(displayManager); - webView = new InputAwareWebView(context, containerView); - displayListenerProxy.onPostWebViewInitialization(displayManager); - - platformThreadHandler = new Handler(context.getMainLooper()); - // Allow local storage. - webView.getSettings().setDomStorageEnabled(true); - webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); - - // Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679. - webView.getSettings().setSupportMultipleWindows(true); - webView.setWebChromeClient(new FlutterWebChromeClient()); - - methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id); - methodChannel.setMethodCallHandler(this); - - flutterWebViewClient = new FlutterWebViewClient(methodChannel); - Map settings = (Map) params.get("settings"); - if (settings != null) applySettings(settings); - - if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) { - List names = (List) params.get(JS_CHANNEL_NAMES_FIELD); - if (names != null) registerJavaScriptChannelNames(names); - } - - Integer autoMediaPlaybackPolicy = (Integer) params.get("autoMediaPlaybackPolicy"); - if (autoMediaPlaybackPolicy != null) updateAutoMediaPlaybackPolicy(autoMediaPlaybackPolicy); - if (params.containsKey("userAgent")) { - String userAgent = (String) params.get("userAgent"); - updateUserAgent(userAgent); - } - if (params.containsKey("initialUrl")) { - String url = (String) params.get("initialUrl"); - webView.loadUrl(url); - } - } - - @Override - public View getView() { - return webView; - } - - // @Override - // This is overriding a method that hasn't rolled into stable Flutter yet. Including the - // annotation would cause compile time failures in versions of Flutter too old to include the new - // method. However leaving it raw like this means that the method will be ignored in old versions - // of Flutter but used as an override anyway wherever it's actually defined. - // TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable. - public void onInputConnectionUnlocked() { - webView.unlockInputConnection(); - } - - // @Override - // This is overriding a method that hasn't rolled into stable Flutter yet. Including the - // annotation would cause compile time failures in versions of Flutter too old to include the new - // method. However leaving it raw like this means that the method will be ignored in old versions - // of Flutter but used as an override anyway wherever it's actually defined. - // TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable. - public void onInputConnectionLocked() { - webView.lockInputConnection(); - } - - // @Override - // This is overriding a method that hasn't rolled into stable Flutter yet. Including the - // annotation would cause compile time failures in versions of Flutter too old to include the new - // method. However leaving it raw like this means that the method will be ignored in old versions - // of Flutter but used as an override anyway wherever it's actually defined. - // TODO(mklim): Add the @Override annotation once stable passes v1.10.9. - public void onFlutterViewAttached(View flutterView) { - webView.setContainerView(flutterView); - } - - // @Override - // This is overriding a method that hasn't rolled into stable Flutter yet. Including the - // annotation would cause compile time failures in versions of Flutter too old to include the new - // method. However leaving it raw like this means that the method will be ignored in old versions - // of Flutter but used as an override anyway wherever it's actually defined. - // TODO(mklim): Add the @Override annotation once stable passes v1.10.9. - public void onFlutterViewDetached() { - webView.setContainerView(null); - } - - @Override - public void onMethodCall(MethodCall methodCall, Result result) { - switch (methodCall.method) { - case "loadUrl": - loadUrl(methodCall, result); - break; - case "loadData": - loadData(methodCall, result); - break; - case "updateSettings": - updateSettings(methodCall, result); - break; - case "canGoBack": - canGoBack(result); - break; - case "canGoForward": - canGoForward(result); - break; - case "goBack": - goBack(result); - break; - case "goForward": - goForward(result); - break; - case "reload": - reload(result); - break; - case "currentUrl": - currentUrl(result); - break; - case "evaluateJavascript": - evaluateJavaScript(methodCall, result); - break; - case "addJavascriptChannels": - addJavaScriptChannels(methodCall, result); - break; - case "removeJavascriptChannels": - removeJavaScriptChannels(methodCall, result); - break; - case "clearCache": - clearCache(result); - break; - case "getTitle": - getTitle(result); - break; - case "scrollTo": - scrollTo(methodCall, result); - break; - case "scrollBy": - scrollBy(methodCall, result); - break; - case "getScrollX": - getScrollX(result); - break; - case "getScrollY": - getScrollY(result); - break; - case "setAcceptThirdPartyCookies": - setAcceptThirdPartyCookies(methodCall, result); - break; - case "setCookie": - setCookie(methodCall, result); - break; - default: - result.notImplemented(); - } - } - - @SuppressWarnings("unchecked") - private void loadUrl(MethodCall methodCall, Result result) { - Map request = (Map) methodCall.arguments; - String url = (String) request.get("url"); - Map headers = (Map) request.get("headers"); - if (headers == null) { - headers = Collections.emptyMap(); - } - webView.loadUrl(url, headers); - result.success(null); - } - - @SuppressWarnings("unchecked") - private void loadData(MethodCall methodCall, Result result) { - Log.d("FlutterWebView", "Call to load data" + methodCall); - - Map request = (Map) methodCall.arguments; - String baseUrl = (String) request.get("baseUrl"); - String data = (String) request.get("data"); - String mimeType = (String) request.get("mimeType"); - String encoding = (String) request.get("encoding"); - webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, ""); - result.success(null); - } - - private void setAcceptThirdPartyCookies(MethodCall methodCall, Result result) { - boolean accept = (boolean) methodCall.arguments; - CookieManager cookieManager = CookieManager.getInstance(); - cookieManager.setAcceptThirdPartyCookies(webView, accept); - result.success(null); - } - - private void setCookie(MethodCall methodCall, Result result) { - Map args = (Map) methodCall.arguments; - String url = (String) args.get("url"); - String value = (String) args.get("value"); - CookieManager cookieManager = CookieManager.getInstance(); - cookieManager.setCookie(url, value); - result.success(null); - } - - private void canGoBack(Result result) { - result.success(webView.canGoBack()); - } - - private void canGoForward(Result result) { - result.success(webView.canGoForward()); - } - - private void goBack(Result result) { - if (webView.canGoBack()) { - webView.goBack(); - } - result.success(null); - } - - private void goForward(Result result) { - if (webView.canGoForward()) { - webView.goForward(); - } - result.success(null); - } - - private void reload(Result result) { - webView.reload(); - result.success(null); - } - - private void currentUrl(Result result) { - result.success(webView.getUrl()); - } - - @SuppressWarnings("unchecked") - private void updateSettings(MethodCall methodCall, Result result) { - applySettings((Map) methodCall.arguments); - result.success(null); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private void evaluateJavaScript(MethodCall methodCall, final Result result) { - String jsString = (String) methodCall.arguments; - if (jsString == null) { - throw new UnsupportedOperationException("JavaScript string cannot be null"); - } - webView.evaluateJavascript( - jsString, - new android.webkit.ValueCallback() { - @Override - public void onReceiveValue(String value) { - result.success(value); - } - }); - } - - @SuppressWarnings("unchecked") - private void addJavaScriptChannels(MethodCall methodCall, Result result) { - List channelNames = (List) methodCall.arguments; - registerJavaScriptChannelNames(channelNames); - result.success(null); - } - - @SuppressWarnings("unchecked") - private void removeJavaScriptChannels(MethodCall methodCall, Result result) { - List channelNames = (List) methodCall.arguments; - for (String channelName : channelNames) { - webView.removeJavascriptInterface(channelName); - } - result.success(null); - } - - private void clearCache(Result result) { - webView.clearCache(true); - WebStorage.getInstance().deleteAllData(); - result.success(null); - } - - private void getTitle(Result result) { - result.success(webView.getTitle()); - } - - private void scrollTo(MethodCall methodCall, Result result) { - Map request = methodCall.arguments(); - int x = (int) request.get("x"); - int y = (int) request.get("y"); - - webView.scrollTo(x, y); - - result.success(null); - } - - private void scrollBy(MethodCall methodCall, Result result) { - Map request = methodCall.arguments(); - int x = (int) request.get("x"); - int y = (int) request.get("y"); - - webView.scrollBy(x, y); - result.success(null); - } - - private void getScrollX(Result result) { - result.success(webView.getScrollX()); - } - - private void getScrollY(Result result) { - result.success(webView.getScrollY()); - } - - private void applySettings(Map settings) { - for (String key : settings.keySet()) { - switch (key) { - case "jsMode": - Integer mode = (Integer) settings.get(key); - if (mode != null) updateJsMode(mode); - break; - case "hasNavigationDelegate": - final boolean hasNavigationDelegate = (boolean) settings.get(key); - - final WebViewClient webViewClient = - flutterWebViewClient.createWebViewClient(hasNavigationDelegate); - - webView.setWebViewClient(webViewClient); - break; - case "debuggingEnabled": - final boolean debuggingEnabled = (boolean) settings.get(key); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.setWebContentsDebuggingEnabled(debuggingEnabled); - } - break; - case "gestureNavigationEnabled": - break; - case "userAgent": - updateUserAgent((String) settings.get(key)); - break; - case "darkMode": - setDarkMode((boolean) settings.get(key)); - break; - default: - throw new IllegalArgumentException("Unknown WebView setting: " + key); - } - } - } - - private void setDarkMode(boolean darkMode) { - if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { - WebSettingsCompat.setAlgorithmicDarkeningAllowed(webView.getSettings(), darkMode); - } else if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { - int forceDarkMode = darkMode ? WebSettingsCompat.FORCE_DARK_ON : WebSettingsCompat.FORCE_DARK_OFF; - WebSettingsCompat.setForceDark(webView.getSettings(), forceDarkMode); - } else { - Log.d("FlutterWebView", "FORCE_DARK feature is not supported by this WebView"); - } - } - - private void updateJsMode(int mode) { - switch (mode) { - case 0: // disabled - webView.getSettings().setJavaScriptEnabled(false); - break; - case 1: // unrestricted - webView.getSettings().setJavaScriptEnabled(true); - break; - default: - throw new IllegalArgumentException("Trying to set unknown JavaScript mode: " + mode); - } - } - - private void updateAutoMediaPlaybackPolicy(int mode) { - // This is the index of the AutoMediaPlaybackPolicy enum, index 1 is always_allow, for all - // other values we require a user gesture. - boolean requireUserGesture = mode != 1; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - webView.getSettings().setMediaPlaybackRequiresUserGesture(requireUserGesture); - } - } - - private void registerJavaScriptChannelNames(List channelNames) { - for (String channelName : channelNames) { - webView.addJavascriptInterface( - new JavaScriptChannel(methodChannel, channelName, platformThreadHandler), channelName); - } - } - - private void updateUserAgent(String userAgent) { - webView.getSettings().setUserAgentString(userAgent); - } - - @Override - public void dispose() { - methodChannel.setMethodCallHandler(null); - webView.dispose(); - webView.destroy(); - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java b/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java deleted file mode 100644 index 24926bfc41..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.graphics.Bitmap; -import android.os.Build; -import android.util.Log; -import android.view.KeyEvent; -import android.webkit.WebResourceError; -import android.webkit.WebResourceRequest; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import androidx.annotation.RequiresApi; -import androidx.webkit.WebResourceErrorCompat; -import androidx.webkit.WebViewClientCompat; -import io.flutter.plugin.common.MethodChannel; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -// We need to use WebViewClientCompat to get -// shouldOverrideUrlLoading(WebView view, WebResourceRequest request) -// invoked by the webview on older Android devices, without it pages that use iframes will -// be broken when a navigationDelegate is set on Android version earlier than N. -class FlutterWebViewClient { - private static final String TAG = "FlutterWebViewClient"; - private final MethodChannel methodChannel; - private boolean hasNavigationDelegate; - - FlutterWebViewClient(MethodChannel methodChannel) { - this.methodChannel = methodChannel; - } - - private static String errorCodeToString(int errorCode) { - switch (errorCode) { - case WebViewClient.ERROR_AUTHENTICATION: - return "authentication"; - case WebViewClient.ERROR_BAD_URL: - return "badUrl"; - case WebViewClient.ERROR_CONNECT: - return "connect"; - case WebViewClient.ERROR_FAILED_SSL_HANDSHAKE: - return "failedSslHandshake"; - case WebViewClient.ERROR_FILE: - return "file"; - case WebViewClient.ERROR_FILE_NOT_FOUND: - return "fileNotFound"; - case WebViewClient.ERROR_HOST_LOOKUP: - return "hostLookup"; - case WebViewClient.ERROR_IO: - return "io"; - case WebViewClient.ERROR_PROXY_AUTHENTICATION: - return "proxyAuthentication"; - case WebViewClient.ERROR_REDIRECT_LOOP: - return "redirectLoop"; - case WebViewClient.ERROR_TIMEOUT: - return "timeout"; - case WebViewClient.ERROR_TOO_MANY_REQUESTS: - return "tooManyRequests"; - case WebViewClient.ERROR_UNKNOWN: - return "unknown"; - case WebViewClient.ERROR_UNSAFE_RESOURCE: - return "unsafeResource"; - case WebViewClient.ERROR_UNSUPPORTED_AUTH_SCHEME: - return "unsupportedAuthScheme"; - case WebViewClient.ERROR_UNSUPPORTED_SCHEME: - return "unsupportedScheme"; - } - - final String message = - String.format(Locale.getDefault(), "Could not find a string for errorCode: %d", errorCode); - throw new IllegalArgumentException(message); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - if (!hasNavigationDelegate) { - return false; - } - notifyOnNavigationRequest( - request.getUrl().toString(), request.getRequestHeaders(), view, request.isForMainFrame()); - // We must make a synchronous decision here whether to allow the navigation or not, - // if the Dart code has set a navigation delegate we want that delegate to decide whether - // to navigate or not, and as we cannot get a response from the Dart delegate synchronously we - // return true here to block the navigation, if the Dart delegate decides to allow the - // navigation the plugin will later make an addition loadUrl call for this url. - // - // Since we cannot call loadUrl for a subframe, we currently only allow the delegate to stop - // navigations that target the main frame, if the request is not for the main frame - // we just return false to allow the navigation. - // - // For more details see: https://github.com/flutter/flutter/issues/25329#issuecomment-464863209 - return request.isForMainFrame(); - } - - boolean shouldOverrideUrlLoading(WebView view, String url) { - if (!hasNavigationDelegate) { - return false; - } - // This version of shouldOverrideUrlLoading is only invoked by the webview on devices with - // webview versions earlier than 67(it is also invoked when hasNavigationDelegate is false). - // On these devices we cannot tell whether the navigation is targeted to the main frame or not. - // We proceed assuming that the navigation is targeted to the main frame. If the page had any - // frames they will be loaded in the main frame instead. - Log.w( - TAG, - "Using a navigationDelegate with an old webview implementation, pages with frames or iframes will not work"); - notifyOnNavigationRequest(url, null, view, true); - return true; - } - - private void onPageStarted(WebView view, String url) { - Map args = new HashMap<>(); - args.put("url", url); - methodChannel.invokeMethod("onPageStarted", args); - } - - private void onPageFinished(WebView view, String url) { - Map args = new HashMap<>(); - args.put("url", url); - methodChannel.invokeMethod("onPageFinished", args); - } - - private void onWebResourceError( - final int errorCode, final String description, final String failingUrl) { - final Map args = new HashMap<>(); - args.put("errorCode", errorCode); - args.put("description", description); - args.put("errorType", FlutterWebViewClient.errorCodeToString(errorCode)); - args.put("failingUrl", failingUrl); - methodChannel.invokeMethod("onWebResourceError", args); - } - - private void notifyOnNavigationRequest( - String url, Map headers, WebView webview, boolean isMainFrame) { - HashMap args = new HashMap<>(); - args.put("url", url); - args.put("isForMainFrame", isMainFrame); - if (isMainFrame) { - methodChannel.invokeMethod( - "navigationRequest", args, new OnNavigationRequestResult(url, headers, webview)); - } else { - methodChannel.invokeMethod("navigationRequest", args); - } - } - - // This method attempts to avoid using WebViewClientCompat due to bug - // https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see - // https://github.com/flutter/flutter/issues/29446. - WebViewClient createWebViewClient(boolean hasNavigationDelegate) { - this.hasNavigationDelegate = hasNavigationDelegate; - - if (!hasNavigationDelegate || android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return internalCreateWebViewClient(); - } - - return internalCreateWebViewClientCompat(); - } - - private WebViewClient internalCreateWebViewClient() { - return new WebViewClient() { - @TargetApi(Build.VERSION_CODES.N) - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - FlutterWebViewClient.this.onPageStarted(view, url); - } - - @Override - public void onPageFinished(WebView view, String url) { - FlutterWebViewClient.this.onPageFinished(view, url); - } - - @TargetApi(Build.VERSION_CODES.M) - @Override - public void onReceivedError( - WebView view, WebResourceRequest request, WebResourceError error) { - FlutterWebViewClient.this.onWebResourceError( - error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString()); - } - - @Override - public void onReceivedError( - WebView view, int errorCode, String description, String failingUrl) { - FlutterWebViewClient.this.onWebResourceError(errorCode, description, failingUrl); - } - - @Override - public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - // Deliberately empty. Occasionally the webview will mark events as having failed to be - // handled even though they were handled. We don't want to propagate those as they're not - // truly lost. - } - }; - } - - private WebViewClientCompat internalCreateWebViewClientCompat() { - return new WebViewClientCompat() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, url); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - FlutterWebViewClient.this.onPageStarted(view, url); - } - - @Override - public void onPageFinished(WebView view, String url) { - FlutterWebViewClient.this.onPageFinished(view, url); - } - - // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is - // enabled. The deprecated method is called when a device doesn't support this. - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - @SuppressLint("RequiresFeature") - @Override - public void onReceivedError( - WebView view, WebResourceRequest request, WebResourceErrorCompat error) { - FlutterWebViewClient.this.onWebResourceError( - error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString()); - } - - @Override - public void onReceivedError( - WebView view, int errorCode, String description, String failingUrl) { - FlutterWebViewClient.this.onWebResourceError(errorCode, description, failingUrl); - } - - @Override - public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - // Deliberately empty. Occasionally the webview will mark events as having failed to be - // handled even though they were handled. We don't want to propagate those as they're not - // truly lost. - } - }; - } - - private static class OnNavigationRequestResult implements MethodChannel.Result { - private final String url; - private final Map headers; - private final WebView webView; - - private OnNavigationRequestResult(String url, Map headers, WebView webView) { - this.url = url; - this.headers = headers; - this.webView = webView; - } - - @Override - public void success(Object shouldLoad) { - Boolean typedShouldLoad = (Boolean) shouldLoad; - if (typedShouldLoad) { - loadUrl(); - } - } - - @Override - public void error(String errorCode, String s1, Object o) { - throw new IllegalStateException("navigationRequest calls must succeed"); - } - - @Override - public void notImplemented() { - throw new IllegalStateException( - "navigationRequest must be implemented by the webview method channel"); - } - - private void loadUrl() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - webView.loadUrl(url, headers); - } else { - webView.loadUrl(url); - } - } - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java b/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java deleted file mode 100644 index 9b81a5b7cc..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import static android.content.Context.INPUT_METHOD_SERVICE; - -import android.content.Context; -import android.graphics.Rect; -import android.os.Build; -import android.util.Log; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.webkit.WebView; -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 - * {@link #checkInputConnectionProxy}. - * - *

See also {@link ThreadedInputConnectionProxyAdapterView}. - */ -final class InputAwareWebView extends WebView { - private static final String TAG = "InputAwareWebView"; - private View threadedInputConnectionProxyView; - private ThreadedInputConnectionProxyAdapterView proxyAdapterView; - private View containerView; - - InputAwareWebView(Context context, View containerView) { - super(context); - this.containerView = containerView; - } - - void setContainerView(View containerView) { - this.containerView = containerView; - - if (proxyAdapterView == null) { - return; - } - - Log.w(TAG, "The containerView has changed while the proxyAdapterView exists."); - if (containerView != null) { - setInputConnectionTarget(proxyAdapterView); - } - } - - /** - * Set our proxy adapter view to use its cached input connection instead of creating new ones. - * - *

This is used to avoid losing our input connection when the virtual display is resized. - */ - void lockInputConnection() { - if (proxyAdapterView == null) { - return; - } - - proxyAdapterView.setLocked(true); - } - - /** Sets the proxy adapter view back to its default behavior. */ - void unlockInputConnection() { - if (proxyAdapterView == null) { - return; - } - - proxyAdapterView.setLocked(false); - } - - /** Restore the original InputConnection, if needed. */ - void dispose() { - resetInputConnection(); - } - - /** - * Creates an InputConnection from the IME thread when needed. - * - *

We only need to create a {@link ThreadedInputConnectionProxyAdapterView} and create an - * InputConnectionProxy on the IME thread when WebView is doing the same thing. So we rely on the - * system calling this method for WebView's proxy view in order to know when we need to create our - * own. - * - *

This method would normally be called for any View that used the InputMethodManager. We rely - * on flutter/engine filtering the calls we receive down to the ones in our hierarchy and the - * system WebView in order to know whether or not the system WebView expects an InputConnection on - * the IME thread. - */ - @Override - public boolean checkInputConnectionProxy(final View view) { - // Check to see if the view param is WebView's ThreadedInputConnectionProxyView. - View previousProxy = threadedInputConnectionProxyView; - threadedInputConnectionProxyView = view; - if (previousProxy == view) { - // This isn't a new ThreadedInputConnectionProxyView. Ignore it. - return super.checkInputConnectionProxy(view); - } - if (containerView == null) { - Log.e( - TAG, - "Can't create a proxy view because there's no container view. Text input may not work."); - return super.checkInputConnectionProxy(view); - } - - // We've never seen this before, so we make the assumption that this is WebView's - // ThreadedInputConnectionProxyView. We are making the assumption that the only view that could - // possibly be interacting with the IMM here is WebView's ThreadedInputConnectionProxyView. - proxyAdapterView = - new ThreadedInputConnectionProxyAdapterView( - /*containerView=*/ containerView, - /*targetView=*/ view, - /*imeHandler=*/ view.getHandler()); - setInputConnectionTarget(/*targetView=*/ proxyAdapterView); - return super.checkInputConnectionProxy(view); - } - - /** - * Ensure that input creation happens back on {@link #containerView}'s thread once this view no - * longer has focus. - * - *

The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's - * thread for all connections. We undo it here so users will be able to go back to typing in - * Flutter UIs as expected. - */ - @Override - public void clearFocus() { - super.clearFocus(); - resetInputConnection(); - } - - /** - * Ensure that input creation happens back on {@link #containerView}. - * - *

The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's - * thread for all connections. We undo it here so users will be able to go back to typing in - * Flutter UIs as expected. - */ - private void resetInputConnection() { - if (proxyAdapterView == null) { - // No need to reset the InputConnection to the default thread if we've never changed it. - return; - } - if (containerView == null) { - Log.e(TAG, "Can't reset the input connection to the container view because there is none."); - return; - } - setInputConnectionTarget(/*targetView=*/ containerView); - } - - /** - * This is the crucial trick that gets the InputConnection creation to happen on the correct - * thread pre Android N. - * https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java?l=169&rcl=f0698ee3e4483fad5b0c34159276f71cfaf81f3a - * - *

{@code targetView} should have a {@link View#getHandler} method with the thread that future - * InputConnections should be created on. - */ - private void setInputConnectionTarget(final View targetView) { - if (containerView == null) { - Log.e( - TAG, - "Can't set the input connection target because there is no containerView to use as a handler."); - return; - } - - targetView.requestFocus(); - containerView.post( - new Runnable() { - @Override - public void run() { - InputMethodManager imm = - (InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE); - // This is a hack to make InputMethodManager believe that the target view now has focus. - // As a result, InputMethodManager will think that targetView is focused, and will call - // getHandler() of the view when creating input connection. - - // Step 1: Set targetView as InputMethodManager#mNextServedView. This does not affect - // the real window focus. - targetView.onWindowFocusChanged(true); - - // Step 2: Have InputMethodManager focus in on targetView. As a result, IMM will call - // onCreateInputConnection() on targetView on the same thread as - // targetView.getHandler(). It will also call subsequent InputConnection methods on this - // thread. This is the IME thread in cases where targetView is our proxyAdapterView. - imm.isActive(containerView); - } - }); - } - - @Override - protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { - // This works around a crash when old (<67.0.3367.0) Chromium versions are used. - - // Prior to Chromium 67.0.3367 the following sequence happens when a select drop down is shown - // on tablets: - // - // - WebView is calling ListPopupWindow#show - // - buildDropDown is invoked, which sets mDropDownList to a DropDownListView. - // - showAsDropDown is invoked - resulting in mDropDownList being added to the window and is - // also synchronously performing the following sequence: - // - WebView's focus change listener is loosing focus (as mDropDownList got it) - // - WebView is hiding all popups (as it lost focus) - // - WebView's SelectPopupDropDown#hide is invoked. - // - DropDownPopupWindow#dismiss is invoked setting mDropDownList to null. - // - mDropDownList#setSelection is invoked and is throwing a NullPointerException (as we just set mDropDownList to null). - // - // To workaround this, we drop the problematic focus lost call. - // See more details on: https://github.com/flutter/flutter/issues/54164 - // - // We don't do this after Android P as it shipped with a new enough WebView version, and it's - // better to not do this on all future Android versions in case DropDownListView's code changes. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P - && isCalledFromListPopupWindowShow() - && !focused) { - return; - } - super.onFocusChanged(focused, direction, previouslyFocusedRect); - } - - private boolean isCalledFromListPopupWindowShow() { - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - for (StackTraceElement stackTraceElement : stackTraceElements) { - if (stackTraceElement.getClassName().equals(ListPopupWindow.class.getCanonicalName()) - && stackTraceElement.getMethodName().equals("show")) { - return true; - } - } - return false; - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java b/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java deleted file mode 100644 index f23aae5b2b..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.os.Handler; -import android.os.Looper; -import android.webkit.JavascriptInterface; -import io.flutter.plugin.common.MethodChannel; -import java.util.HashMap; - -/** - * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets - * up. - * - *

Exposes a single method named `postMessage` to JavaScript, which sends a message over a method - * channel to the Dart code. - */ -class JavaScriptChannel { - private final MethodChannel methodChannel; - private final String javaScriptChannelName; - private final Handler platformThreadHandler; - - /** - * @param methodChannel the Flutter WebView method channel to which JS messages are sent - * @param javaScriptChannelName the name of the JavaScript channel, this is sent over the method - * channel with each message to let the Dart code know which JavaScript channel the message - * was sent through - */ - JavaScriptChannel( - MethodChannel methodChannel, String javaScriptChannelName, Handler platformThreadHandler) { - this.methodChannel = methodChannel; - this.javaScriptChannelName = javaScriptChannelName; - this.platformThreadHandler = platformThreadHandler; - } - - // Suppressing unused warning as this is invoked from JavaScript. - @SuppressWarnings("unused") - @JavascriptInterface - public void postMessage(final String message) { - Runnable postMessageRunnable = - new Runnable() { - @Override - public void run() { - HashMap arguments = new HashMap<>(); - arguments.put("channel", javaScriptChannelName); - arguments.put("message", message); - methodChannel.invokeMethod("javascriptChannelMessage", arguments); - } - }; - if (platformThreadHandler.getLooper() == Looper.myLooper()) { - postMessageRunnable.run(); - } else { - platformThreadHandler.post(postMessageRunnable); - } - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/ThreadedInputConnectionProxyAdapterView.java b/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/ThreadedInputConnectionProxyAdapterView.java deleted file mode 100644 index 8fbdfaff1a..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/ThreadedInputConnectionProxyAdapterView.java +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.os.Handler; -import android.os.IBinder; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; - -/** - * A fake View only exposed to InputMethodManager. - * - *

This follows a similar flow to Chromium's WebView (see - * https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionProxyView.java). - * WebView itself bounces its InputConnection around several different threads. We follow its logic - * here to get the same working connection. - * - *

This exists solely to forward input creation to WebView's ThreadedInputConnectionProxyView on - * the IME thread. The way that this is created in {@link - * InputAwareWebView#checkInputConnectionProxy} guarantees that we have a handle to - * ThreadedInputConnectionProxyView and {@link #onCreateInputConnection} is always called on the IME - * thread. We delegate to ThreadedInputConnectionProxyView there to get WebView's input connection. - */ -final class ThreadedInputConnectionProxyAdapterView extends View { - final Handler imeHandler; - final IBinder windowToken; - final View containerView; - final View rootView; - final View targetView; - - private boolean triggerDelayed = true; - private boolean isLocked = false; - private InputConnection cachedConnection; - - ThreadedInputConnectionProxyAdapterView(View containerView, View targetView, Handler imeHandler) { - super(containerView.getContext()); - this.imeHandler = imeHandler; - this.containerView = containerView; - this.targetView = targetView; - windowToken = containerView.getWindowToken(); - rootView = containerView.getRootView(); - setFocusable(true); - setFocusableInTouchMode(true); - setVisibility(VISIBLE); - } - - /** Returns whether or not this is currently asynchronously acquiring an input connection. */ - boolean isTriggerDelayed() { - return triggerDelayed; - } - - /** Sets whether or not this should use its previously cached input connection. */ - void setLocked(boolean locked) { - isLocked = locked; - } - - /** - * This is expected to be called on the IME thread. See the setup required for this in {@link - * InputAwareWebView#checkInputConnectionProxy(View)}. - * - *

Delegates to ThreadedInputConnectionProxyView to get WebView's input connection. - */ - @Override - public InputConnection onCreateInputConnection(final EditorInfo outAttrs) { - triggerDelayed = false; - InputConnection inputConnection = - (isLocked) ? cachedConnection : targetView.onCreateInputConnection(outAttrs); - triggerDelayed = true; - cachedConnection = inputConnection; - return inputConnection; - } - - @Override - public boolean checkInputConnectionProxy(View view) { - return true; - } - - @Override - public boolean hasWindowFocus() { - // None of our views here correctly report they have window focus because of how we're embedding - // the platform view inside of a virtual display. - return true; - } - - @Override - public View getRootView() { - return rootView; - } - - @Override - public boolean onCheckIsTextEditor() { - return true; - } - - @Override - public boolean isFocused() { - return true; - } - - @Override - public IBinder getWindowToken() { - return windowToken; - } - - @Override - public Handler getHandler() { - return imeHandler; - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFactory.java b/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFactory.java deleted file mode 100644 index 6fdc36fbe5..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.content.Context; -import android.view.View; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.StandardMessageCodec; -import io.flutter.plugin.platform.PlatformView; -import io.flutter.plugin.platform.PlatformViewFactory; -import java.util.Map; - -public final class WebViewFactory extends PlatformViewFactory { - private final BinaryMessenger messenger; - private final View containerView; - - WebViewFactory(BinaryMessenger messenger, View containerView) { - super(StandardMessageCodec.INSTANCE); - this.messenger = messenger; - this.containerView = containerView; - } - - @SuppressWarnings("unchecked") - @Override - public PlatformView create(Context context, int id, Object args) { - Map params = (Map) args; - return new FlutterWebView(context, messenger, id, params, containerView); - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java deleted file mode 100644 index 2de8fdf94b..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.plugin.common.BinaryMessenger; - -/** - * Java platform implementation of the webview_flutter plugin. - * - *

Register this in an add to app scenario to gracefully handle activity and context changes. - * - *

Call {@link #registerWith(Registrar)} to use the stable {@code io.flutter.plugin.common} - * package instead. - */ -public class WebViewFlutterPlugin implements FlutterPlugin { - - private FlutterCookieManager flutterCookieManager; - - /** - * Add an instance of this to {@link io.flutter.embedding.engine.plugins.PluginRegistry} to - * register it. - * - *

THIS PLUGIN CODE PATH DEPENDS ON A NEWER VERSION OF FLUTTER THAN THE ONE DEFINED IN THE - * PUBSPEC.YAML. Text input will fail on some Android devices unless this is used with at least - * flutter/flutter@1d4d63ace1f801a022ea9ec737bf8c15395588b9. Use the V1 embedding with {@link - * #registerWith(Registrar)} to use this plugin with older Flutter versions. - * - *

Registration should eventually be handled automatically by v2 of the - * GeneratedPluginRegistrant. https://github.com/flutter/flutter/issues/42694 - */ - public WebViewFlutterPlugin() {} - - /** - * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} - * package. - * - *

Calling this automatically initializes the plugin. However plugins initialized this way - * won't react to changes in activity or context, unlike {@link CameraPlugin}. - */ - @SuppressWarnings("deprecation") - public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - registrar - .platformViewRegistry() - .registerViewFactory( - "plugins.flutter.io/webview", - new WebViewFactory(registrar.messenger(), registrar.view())); - new FlutterCookieManager(registrar.messenger()); - } - - @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - BinaryMessenger messenger = binding.getBinaryMessenger(); - binding - .getPlatformViewRegistry() - .registerViewFactory( - "plugins.flutter.io/webview", new WebViewFactory(messenger, /*containerView=*/ null)); - flutterCookieManager = new FlutterCookieManager(messenger); - } - - @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { - if (flutterCookieManager == null) { - return; - } - - flutterCookieManager.dispose(); - flutterCookieManager = null; - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/lib/platform_interface.dart b/apps/flutter_parent/plugins/webview_flutter/lib/platform_interface.dart deleted file mode 100644 index 2637e0d9f0..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/lib/platform_interface.dart +++ /dev/null @@ -1,549 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/widgets.dart'; - -import 'webview_flutter.dart'; - -/// Interface for callbacks made by [WebViewPlatformController]. -/// -/// The webview plugin implements this class, and passes an instance to the [WebViewPlatformController]. -/// [WebViewPlatformController] is notifying this handler on events that happened on the platform's webview. -abstract class WebViewPlatformCallbacksHandler { - /// Invoked by [WebViewPlatformController] when a JavaScript channel message is received. - void onJavaScriptChannelMessage(String channel, String message); - - /// Invoked by [WebViewPlatformController] when a navigation request is pending. - /// - /// If true is returned the navigation is allowed, otherwise it is blocked. - FutureOr onNavigationRequest({String url, bool isForMainFrame}); - - /// Invoked by [WebViewPlatformController] when a page has started loading. - void onPageStarted(String url); - - /// Invoked by [WebViewPlatformController] when a page has finished loading. - void onPageFinished(String url); - - /// Report web resource loading error to the host application. - void onWebResourceError(WebResourceError error); -} - -/// Possible error type categorizations used by [WebResourceError]. -enum WebResourceErrorType { - /// User authentication failed on server. - authentication, - - /// Malformed URL. - badUrl, - - /// Failed to connect to the server. - connect, - - /// Failed to perform SSL handshake. - failedSslHandshake, - - /// Generic file error. - file, - - /// File not found. - fileNotFound, - - /// Server or proxy hostname lookup failed. - hostLookup, - - /// Failed to read or write to the server. - io, - - /// User authentication failed on proxy. - proxyAuthentication, - - /// Too many redirects. - redirectLoop, - - /// Connection timed out. - timeout, - - /// Too many requests during this load. - tooManyRequests, - - /// Generic error. - unknown, - - /// Resource load was canceled by Safe Browsing. - unsafeResource, - - /// Unsupported authentication scheme (not basic or digest). - unsupportedAuthScheme, - - /// Unsupported URI scheme. - unsupportedScheme, - - /// The web content process was terminated. - webContentProcessTerminated, - - /// The web view was invalidated. - webViewInvalidated, - - /// A JavaScript exception occurred. - javaScriptExceptionOccurred, - - /// The result of JavaScript execution could not be returned. - javaScriptResultTypeIsUnsupported, -} - -/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. -class WebResourceError { - /// Creates a new [WebResourceError] - /// - /// A user should not need to instantiate this class, but will receive one in - /// [WebResourceErrorCallback]. - WebResourceError({ - required this.errorCode, - required this.description, - this.domain, - this.errorType, - this.failingUrl, - }) {} - - /// Raw code of the error from the respective platform. - /// - /// On Android, the error code will be a constant from a - /// [WebViewClient](https://developer.android.com/reference/android/webkit/WebViewClient#summary) and - /// will have a corresponding [errorType]. - /// - /// On iOS, the error code will be a constant from `NSError.code` in - /// Objective-C. See - /// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorObjectsDomains/ErrorObjectsDomains.html - /// for more information on error handling on iOS. Some possible error codes - /// can be found at https://developer.apple.com/documentation/webkit/wkerrorcode?language=objc. - final int errorCode; - - /// The domain of where to find the error code. - /// - /// This field is only available on iOS and represents a "domain" from where - /// the [errorCode] is from. This value is taken directly from an `NSError` - /// in Objective-C. See - /// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorObjectsDomains/ErrorObjectsDomains.html - /// for more information on error handling on iOS. - final String? domain; - - /// Description of the error that can be used to communicate the problem to the user. - final String description; - - /// The type this error can be categorized as. - /// - /// This will never be `null` on Android, but can be `null` on iOS. - final WebResourceErrorType? errorType; - - /// Gets the URL for which the resource request was made. - /// - /// This value is not provided on iOS. Alternatively, you can keep track of - /// the last values provided to [WebViewPlatformController.loadUrl]. - final String? failingUrl; -} - -/// Interface for talking to the webview's platform implementation. -/// -/// An instance implementing this interface is passed to the `onWebViewPlatformCreated` callback that is -/// passed to [WebViewPlatformBuilder#onWebViewPlatformCreated]. -/// -/// Platform implementations that live in a separate package should extend this class rather than -/// implement it as webview_flutter does not consider newly added methods to be breaking changes. -/// Extending this class (using `extends`) ensures that the subclass will get the default -/// implementation, while platform implementations that `implements` this interface will be broken -/// by newly added [WebViewPlatformController] methods. -abstract class WebViewPlatformController { - /// Creates a new WebViewPlatform. - /// - /// Callbacks made by the WebView will be delegated to `handler`. - /// - /// The `handler` parameter must not be null. - WebViewPlatformController(WebViewPlatformCallbacksHandler handler); - - /// Loads the specified URL. - /// - /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will - /// be added as key value pairs of HTTP headers for the request. - /// - /// `url` must not be null. - /// - /// Throws an ArgumentError if `url` is not a valid URL string. - Future loadUrl( - String url, - Map? headers, - ) { - throw UnimplementedError( - "WebView loadUrl is not implemented on the current platform"); - } - - Future loadData( - String? baseUrl, - String data, - String mimeType, - String encoding - ) { - throw UnimplementedError( - "WebView loadData is not implemented on the current platform"); - } - - Future setAcceptThirdPartyCookies(bool accept) { - throw UnimplementedError("WebView setAcceptThirdPartyCookies is not implemented on the current platform"); - } - - Future setCookie(String url, String value) { - throw UnimplementedError("WebView setCookie is not implemented on the current platform"); - } - - /// Updates the webview settings. - /// - /// Any non null field in `settings` will be set as the new setting value. - /// All null fields in `settings` are ignored. - Future updateSettings(WebSettings setting) { - throw UnimplementedError( - "WebView updateSettings is not implemented on the current platform"); - } - - /// Accessor to the current URL that the WebView is displaying. - /// - /// If no URL was ever loaded, returns `null`. - Future currentUrl() { - throw UnimplementedError( - "WebView currentUrl is not implemented on the current platform"); - } - - /// Checks whether there's a back history item. - Future canGoBack() { - throw UnimplementedError( - "WebView canGoBack is not implemented on the current platform"); - } - - /// Checks whether there's a forward history item. - Future canGoForward() { - throw UnimplementedError( - "WebView canGoForward is not implemented on the current platform"); - } - - /// Goes back in the history of this WebView. - /// - /// If there is no back history item this is a no-op. - Future goBack() { - throw UnimplementedError( - "WebView goBack is not implemented on the current platform"); - } - - /// Goes forward in the history of this WebView. - /// - /// If there is no forward history item this is a no-op. - Future goForward() { - throw UnimplementedError( - "WebView goForward is not implemented on the current platform"); - } - - /// Reloads the current URL. - Future reload() { - throw UnimplementedError( - "WebView reload is not implemented on the current platform"); - } - - /// Clears all caches used by the [WebView]. - /// - /// The following caches are cleared: - /// 1. Browser HTTP Cache. - /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. - /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. - /// 3. Application cache. - /// 4. Local Storage. - Future clearCache() { - throw UnimplementedError( - "WebView clearCache is not implemented on the current platform"); - } - - /// Evaluates a JavaScript expression in the context of the current page. - /// - /// The Future completes with an error if a JavaScript error occurred, or if the type of the - /// evaluated expression is not supported(e.g on iOS not all non primitive type can be evaluated). - Future evaluateJavascript(String javascriptString) { - throw UnimplementedError( - "WebView evaluateJavascript is not implemented on the current platform"); - } - - /// Adds new JavaScript channels to the set of enabled channels. - /// - /// For each value in this list the platform's webview should make sure that a corresponding - /// property with a postMessage method is set on `window`. For example for a JavaScript channel - /// named `Foo` it should be possible for JavaScript code executing in the webview to do - /// - /// ```javascript - /// Foo.postMessage('hello'); - /// ``` - /// - /// See also: [CreationParams.javascriptChannelNames]. - Future addJavascriptChannels(Set javascriptChannelNames) { - throw UnimplementedError( - "WebView addJavascriptChannels is not implemented on the current platform"); - } - - /// Removes JavaScript channel names from the set of enabled channels. - /// - /// This disables channels that were previously enabled by [addJavaScriptChannels] or through - /// [CreationParams.javascriptChannelNames]. - Future removeJavascriptChannels(Set javascriptChannelNames) { - throw UnimplementedError( - "WebView removeJavascriptChannels is not implemented on the current platform"); - } - - /// Returns the title of the currently loaded page. - Future getTitle() { - throw UnimplementedError( - "WebView getTitle is not implemented on the current platform"); - } - - /// Set the scrolled position of this view. - /// - /// The parameters `x` and `y` specify the position to scroll to in WebView pixels. - Future scrollTo(int x, int y) { - throw UnimplementedError( - "WebView scrollTo is not implemented on the current platform"); - } - - /// Move the scrolled position of this view. - /// - /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by. - Future scrollBy(int x, int y) { - throw UnimplementedError( - "WebView scrollBy is not implemented on the current platform"); - } - - /// Return the horizontal scroll position of this view. - /// - /// Scroll position is measured from left. - Future getScrollX() { - throw UnimplementedError( - "WebView getScrollX is not implemented on the current platform"); - } - - /// Return the vertical scroll position of this view. - /// - /// Scroll position is measured from top. - Future getScrollY() { - throw UnimplementedError( - "WebView getScrollY is not implemented on the current platform"); - } -} - -/// A single setting for configuring a WebViewPlatform which may be absent. -class WebSetting { - /// Constructs an absent setting instance. - /// - /// The [isPresent] field for the instance will be false. - /// - /// Accessing [value] for an absent instance will throw. - WebSetting.absent() - : _value = null, - isPresent = false; - - /// Constructs a setting of the given `value`. - /// - /// The [isPresent] field for the instance will be true. - WebSetting.of(T value) - : _value = value, - isPresent = true; - - final T? _value; - - /// The setting's value. - /// - /// Throws if [WebSetting.isPresent] is false. - T get value { - if (!isPresent) { - throw StateError('Cannot access a value of an absent WebSetting'); - } - if (_value == null) { - throw StateError('Cannot access a value of an absent WebSetting'); - } - assert(isPresent); - return _value!; - } - - /// True when this web setting instance contains a value. - /// - /// When false the [WebSetting.value] getter throws. - final bool isPresent; - - @override - bool operator ==(other) { - if (other.runtimeType != runtimeType) return false; - if (other is! WebSetting) return false; - final WebSetting typedOther = other; - return typedOther.isPresent == isPresent && typedOther._value == _value; - } - - @override - int get hashCode => hashValues(_value, isPresent); -} - -/// Settings for configuring a WebViewPlatform. -/// -/// Initial settings are passed as part of [CreationParams], settings updates are sent with -/// [WebViewPlatform#updateSettings]. -/// -/// The `userAgent` parameter must not be null. -class WebSettings { - /// Construct an instance with initial settings. Future setting changes can be - /// sent with [WebviewPlatform#updateSettings]. - /// - /// The `userAgent` parameter must not be null. - WebSettings({ - this.javascriptMode, - this.hasNavigationDelegate, - this.darkMode, - this.debuggingEnabled, - this.gestureNavigationEnabled, - required this.userAgent, - }) {} - - /// The JavaScript execution mode to be used by the webview. - final JavascriptMode? javascriptMode; - - /// Whether the [WebView] has a [NavigationDelegate] set. - final bool? hasNavigationDelegate; - - final bool? darkMode; - - /// Whether to enable the platform's webview content debugging tools. - /// - /// See also: [WebView.debuggingEnabled]. - final bool? debuggingEnabled; - - /// The value used for the HTTP `User-Agent:` request header. - /// - /// If [userAgent.value] is null the platform's default user agent should be used. - /// - /// An absent value ([userAgent.isPresent] is false) represents no change to this setting from the - /// last time it was set. - /// - /// See also [WebView.userAgent]. - final WebSetting userAgent; - - /// Whether to allow swipe based navigation in iOS. - /// - /// See also: [WebView.gestureNavigationEnabled] - final bool? gestureNavigationEnabled; - - @override - String toString() { - return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent)'; - } -} - -/// Configuration to use when creating a new [WebViewPlatformController]. -/// -/// The `autoMediaPlaybackPolicy` parameter must not be null. -class CreationParams { - /// Constructs an instance to use when creating a new - /// [WebViewPlatformController]. - /// - /// The `autoMediaPlaybackPolicy` parameter must not be null. - CreationParams({ - this.initialUrl, - this.webSettings, - this.javascriptChannelNames, - this.userAgent, - this.autoMediaPlaybackPolicy = - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - }) {} - - /// The initialUrl to load in the webview. - /// - /// When null the webview will be created without loading any page. - final String? initialUrl; - - /// The initial [WebSettings] for the new webview. - /// - /// This can later be updated with [WebViewPlatformController.updateSettings]. - final WebSettings? webSettings; - - /// The initial set of JavaScript channels that are configured for this webview. - /// - /// For each value in this set the platform's webview should make sure that a corresponding - /// property with a postMessage method is set on `window`. For example for a JavaScript channel - /// named `Foo` it should be possible for JavaScript code executing in the webview to do - /// - /// ```javascript - /// Foo.postMessage('hello'); - /// ``` - // TODO(amirh): describe what should happen when postMessage is called once that code is migrated - // to PlatformWebView. - final Set? javascriptChannelNames; - - /// The value used for the HTTP User-Agent: request header. - /// - /// When null the platform's webview default is used for the User-Agent header. - final String? userAgent; - - /// Which restrictions apply on automatic media playback. - final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy; - - @override - String toString() { - return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent)'; - } -} - -/// Signature for callbacks reporting that a [WebViewPlatformController] was created. -/// -/// See also the `onWebViewPlatformCreated` argument for [WebViewPlatform.build]. -typedef WebViewPlatformCreatedCallback = void Function( - WebViewPlatformController webViewPlatformController); - -/// Interface for a platform implementation of a WebView. -/// -/// [WebView.platform] controls the builder that is used by [WebView]. -/// [AndroidWebViewPlatform] and [CupertinoWebViewPlatform] are the default implementations -/// for Android and iOS respectively. -abstract class WebViewPlatform { - /// Builds a new WebView. - /// - /// Returns a Widget tree that embeds the created webview. - /// - /// `creationParams` are the initial parameters used to setup the webview. - /// - /// `webViewPlatformHandler` will be used for handling callbacks that are made by the created - /// [WebViewPlatformController]. - /// - /// `onWebViewPlatformCreated` will be invoked after the platform specific [WebViewPlatformController] - /// implementation is created with the [WebViewPlatformController] instance as a parameter. - /// - /// `gestureRecognizers` specifies which gestures should be consumed by the web view. - /// It is possible for other gesture recognizers to be competing with the web view on pointer - /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle - /// vertical drags. The web view will claim gestures that are recognized by any of the - /// recognizers on this list. - /// When `gestureRecognizers` is empty or null, the web view will only handle pointer events for gestures that - /// were not claimed by any other gesture recognizer. - /// - /// `webViewPlatformHandler` must not be null. - Widget build({ - BuildContext context, - // TODO(amirh): convert this to be the actual parameters. - // I'm starting without it as the PR is starting to become pretty big. - // I'll followup with the conversion PR. - CreationParams creationParams, - required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, - WebViewPlatformCreatedCallback onWebViewPlatformCreated, - Set>? gestureRecognizers, - }); - - /// Clears all cookies for all [WebView] instances. - /// - /// Returns true if cookies were present before clearing, else false. - Future clearCookies() { - throw UnimplementedError( - "WebView clearCookies is not implemented on the current platform"); - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/lib/src/webview_android.dart b/apps/flutter_parent/plugins/webview_flutter/lib/src/webview_android.dart deleted file mode 100644 index f987f5b67f..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/lib/src/webview_android.dart +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; - -import '../platform_interface.dart'; -import 'webview_method_channel.dart'; - -/// Builds an Android webview. -/// -/// This is used as the default implementation for [WebView.platform] on Android. It uses -/// an [AndroidView] to embed the webview in the widget hierarchy, and uses a method channel to -/// communicate with the platform code. -class AndroidWebView implements WebViewPlatform { - @override - Widget build({ - BuildContext? context, - CreationParams? creationParams, - @required WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler, - WebViewPlatformCreatedCallback? onWebViewPlatformCreated, - Set>? gestureRecognizers, - }) { - assert(webViewPlatformCallbacksHandler != null); - return GestureDetector( - // We prevent text selection by intercepting the long press event. - // This is a temporary stop gap due to issues with text selection on Android: - // https://github.com/flutter/flutter/issues/24585 - the text selection - // dialog is not responding to touch events. - // https://github.com/flutter/flutter/issues/24584 - the text selection - // handles are not showing. - // TODO(amirh): remove this when the issues above are fixed. - onLongPress: () {}, - excludeFromSemantics: true, - child: AndroidView( - viewType: 'plugins.flutter.io/webview', - onPlatformViewCreated: (int id) { - if (onWebViewPlatformCreated == null || webViewPlatformCallbacksHandler == null) { - return; - } - onWebViewPlatformCreated(MethodChannelWebViewPlatform( - id, webViewPlatformCallbacksHandler)); - }, - gestureRecognizers: gestureRecognizers, - // WebView content is not affected by the Android view's layout direction, - // we explicitly set it here so that the widget doesn't require an ambient - // directionality. - layoutDirection: TextDirection.rtl, - creationParams: - creationParams != null ? MethodChannelWebViewPlatform.creationParamsToMap(creationParams) : null, - creationParamsCodec: const StandardMessageCodec(), - ), - ); - } - - @override - Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); -} diff --git a/apps/flutter_parent/plugins/webview_flutter/lib/src/webview_cupertino.dart b/apps/flutter_parent/plugins/webview_flutter/lib/src/webview_cupertino.dart deleted file mode 100644 index 52e7b823fc..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/lib/src/webview_cupertino.dart +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; - -import '../platform_interface.dart'; -import 'webview_method_channel.dart'; - -/// Builds an iOS webview. -/// -/// This is used as the default implementation for [WebView.platform] on iOS. It uses -/// a [UiKitView] to embed the webview in the widget hierarchy, and uses a method channel to -/// communicate with the platform code. -class CupertinoWebView implements WebViewPlatform { - @override - Widget build({ - BuildContext? context, - CreationParams? creationParams, - @required WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler, - WebViewPlatformCreatedCallback? onWebViewPlatformCreated, - Set>? gestureRecognizers, - }) { - return UiKitView( - viewType: 'plugins.flutter.io/webview', - onPlatformViewCreated: (int id) { - if (onWebViewPlatformCreated == null || webViewPlatformCallbacksHandler == null) { - return; - } - onWebViewPlatformCreated( - MethodChannelWebViewPlatform(id, webViewPlatformCallbacksHandler)); - }, - gestureRecognizers: gestureRecognizers, - creationParams: - creationParams != null ? MethodChannelWebViewPlatform.creationParamsToMap(creationParams) : null, - creationParamsCodec: const StandardMessageCodec(), - ); - } - - @override - Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); -} diff --git a/apps/flutter_parent/plugins/webview_flutter/lib/src/webview_method_channel.dart b/apps/flutter_parent/plugins/webview_flutter/lib/src/webview_method_channel.dart deleted file mode 100644 index 7d7e232ff8..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/lib/src/webview_method_channel.dart +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/services.dart'; - -import '../platform_interface.dart'; - -/// A [WebViewPlatformController] that uses a method channel to control the webview. -class MethodChannelWebViewPlatform implements WebViewPlatformController { - /// Constructs an instance that will listen for webviews broadcasting to the - /// given [id], using the given [WebViewPlatformCallbacksHandler]. - MethodChannelWebViewPlatform(int id, this._platformCallbacksHandler) - : _channel = MethodChannel('plugins.flutter.io/webview_$id') { - _channel.setMethodCallHandler(_onMethodCall); - } - - final WebViewPlatformCallbacksHandler _platformCallbacksHandler; - - final MethodChannel _channel; - - static const MethodChannel _cookieManagerChannel = - MethodChannel('plugins.flutter.io/cookie_manager'); - - Future _onMethodCall(MethodCall call) async { - switch (call.method) { - case 'javascriptChannelMessage': - final String channel = call.arguments['channel']; - final String message = call.arguments['message']; - _platformCallbacksHandler.onJavaScriptChannelMessage(channel, message); - return true; - case 'navigationRequest': - return await _platformCallbacksHandler.onNavigationRequest( - url: call.arguments['url'], - isForMainFrame: call.arguments['isForMainFrame'], - ); - case 'onPageFinished': - _platformCallbacksHandler.onPageFinished(call.arguments['url']); - return null; - case 'onPageStarted': - _platformCallbacksHandler.onPageStarted(call.arguments['url']); - return null; - case 'onWebResourceError': - _platformCallbacksHandler.onWebResourceError( - WebResourceError( - errorCode: call.arguments['errorCode'], - description: call.arguments['description'], - domain: call.arguments['domain'], - failingUrl: call.arguments['failingUrl'], - errorType: call.arguments['errorType'] == null - ? WebResourceErrorType.unknown - : WebResourceErrorType.values.firstWhere( - (WebResourceErrorType type) { - return type.toString() == - '$WebResourceErrorType.${call.arguments['errorType']}'; - }, - ), - ), - ); - return null; - } - - throw MissingPluginException( - '${call.method} was invoked but has no handler', - ); - } - - @override - Future loadUrl( - String url, - Map? headers, - ) async { - return _channel.invokeMethod('loadUrl', { - 'url': url, - 'headers': headers, - }); - } - - @override - Future loadData(String? baseUrl, String data, String mimeType, String encoding) async { - return _channel.invokeMethod('loadData', { - 'baseUrl': baseUrl, - 'data': data, - 'mimeType': mimeType, - 'encoding': encoding, - }); - } - - @override - Future setAcceptThirdPartyCookies(bool accept) { - return _channel.invokeMethod('setAcceptThirdPartyCookies', accept); - } - - @override - Future setCookie(String url, String value) { - return _channel.invokeMethod( - 'setCookie', - { - 'url': url, - 'value': value, - }, - ); - } - - @override - Future currentUrl() => _channel.invokeMethod('currentUrl'); - - @override - Future canGoBack() => _channel.invokeMethod("canGoBack"); - - @override - Future canGoForward() => _channel.invokeMethod("canGoForward"); - - @override - Future goBack() => _channel.invokeMethod("goBack"); - - @override - Future goForward() => _channel.invokeMethod("goForward"); - - @override - Future reload() => _channel.invokeMethod("reload"); - - @override - Future clearCache() => _channel.invokeMethod("clearCache"); - - @override - Future updateSettings(WebSettings settings) { - final Map updatesMap = _webSettingsToMap(settings); - if (updatesMap.isEmpty) { - return Future.value(); - } - return _channel.invokeMethod('updateSettings', updatesMap); - } - - @override - Future evaluateJavascript(String javascriptString) { - return _channel.invokeMethod('evaluateJavascript', javascriptString); - } - - @override - Future addJavascriptChannels(Set javascriptChannelNames) { - return _channel.invokeMethod( - 'addJavascriptChannels', javascriptChannelNames.toList()); - } - - @override - Future removeJavascriptChannels(Set javascriptChannelNames) { - return _channel.invokeMethod( - 'removeJavascriptChannels', javascriptChannelNames.toList()); - } - - @override - Future getTitle() => _channel.invokeMethod("getTitle"); - - @override - Future scrollTo(int x, int y) { - return _channel.invokeMethod('scrollTo', { - 'x': x, - 'y': y, - }); - } - - @override - Future scrollBy(int x, int y) { - return _channel.invokeMethod('scrollBy', { - 'x': x, - 'y': y, - }); - } - - @override - Future getScrollX() => _channel.invokeMethod("getScrollX"); - - @override - Future getScrollY() => _channel.invokeMethod("getScrollY"); - - /// Method channel implementation for [WebViewPlatform.clearCookies]. - static Future clearCookies() { - return _cookieManagerChannel - .invokeMethod('clearCookies') - .then((dynamic result) => result); - } - - static Map _webSettingsToMap(WebSettings? settings) { - final Map map = {}; - void _addIfNonNull(String key, dynamic value) { - if (value == null) { - return; - } - map[key] = value; - } - - void _addSettingIfPresent(String key, WebSetting setting) { - if (!setting.isPresent) { - return; - } - map[key] = setting.value; - } - - _addIfNonNull('jsMode', settings?.javascriptMode?.index); - _addIfNonNull('hasNavigationDelegate', settings?.hasNavigationDelegate); - _addIfNonNull('darkMode', settings?.darkMode); - _addIfNonNull('debuggingEnabled', settings?.debuggingEnabled); - _addIfNonNull( - 'gestureNavigationEnabled', settings?.gestureNavigationEnabled); - _addSettingIfPresent('userAgent', settings?.userAgent as WebSetting); - return map; - } - - /// Converts a [CreationParams] object to a map as expected by `platform_views` channel. - /// - /// This is used for the `creationParams` argument of the platform views created by - /// [AndroidWebViewBuilder] and [CupertinoWebViewBuilder]. - static Map creationParamsToMap( - CreationParams creationParams) { - return { - 'initialUrl': creationParams.initialUrl, - 'settings': _webSettingsToMap(creationParams.webSettings), - 'javascriptChannelNames': creationParams.javascriptChannelNames?.toList(), - 'userAgent': creationParams.userAgent, - 'autoMediaPlaybackPolicy': creationParams.autoMediaPlaybackPolicy.index, - }; - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/lib/webview_flutter.dart b/apps/flutter_parent/plugins/webview_flutter/lib/webview_flutter.dart deleted file mode 100644 index 3a846f6c22..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/lib/webview_flutter.dart +++ /dev/null @@ -1,823 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; - -import 'platform_interface.dart'; -import 'src/webview_android.dart'; -import 'src/webview_cupertino.dart'; -import 'src/webview_method_channel.dart'; - -/// Optional callback invoked when a web view is first created. [controller] is -/// the [WebViewController] for the created web view. -typedef void WebViewCreatedCallback(WebViewController controller); - -/// Describes the state of JavaScript support in a given web view. -enum JavascriptMode { - /// JavaScript execution is disabled. - disabled, - - /// JavaScript execution is not restricted. - unrestricted, -} - -/// A message that was sent by JavaScript code running in a [WebView]. -class JavascriptMessage { - /// Constructs a JavaScript message object. - /// - /// The `message` parameter must not be null. - const JavascriptMessage(this.message); - - /// The contents of the message that was sent by the JavaScript code. - final String message; -} - -/// Callback type for handling messages sent from Javascript running in a web view. -typedef void JavascriptMessageHandler(JavascriptMessage message); - -/// Information about a navigation action that is about to be executed. -class NavigationRequest { - NavigationRequest._({ required this.url, required this.isForMainFrame}); - - /// The URL that will be loaded if the navigation is executed. - final String url; - - /// Whether the navigation request is to be loaded as the main frame. - final bool isForMainFrame; - - @override - String toString() { - return '$runtimeType(url: $url, isForMainFrame: $isForMainFrame)'; - } -} - -/// A decision on how to handle a navigation request. -enum NavigationDecision { - /// Prevent the navigation from taking place. - prevent, - - /// Allow the navigation to take place. - navigate, -} - -/// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the [WebView] widget. -/// -/// To use this, set [WebView.platform] to an instance of this class. -/// -/// This implementation uses hybrid composition to render the [WebView] on -/// Android. It solves multiple issues related to accessibility and interaction -/// with the [WebView] at the cost of some performance on Android versions below -/// 10. See https://github.com/flutter/flutter/wiki/Hybrid-Composition for more -/// information. -class SurfaceAndroidWebView extends AndroidWebView { - @override - Widget build({ - BuildContext? context, - CreationParams? creationParams, - WebViewPlatformCreatedCallback? onWebViewPlatformCreated, - Set>? gestureRecognizers, - WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler, - }) { - assert(webViewPlatformCallbacksHandler != null); - return PlatformViewLink( - viewType: 'plugins.flutter.io/webview', - surfaceFactory: ( - BuildContext context, - PlatformViewController controller, - ) { - return AndroidViewSurface( - controller: controller as AndroidViewController, - gestureRecognizers: gestureRecognizers ?? - const >{}, - hitTestBehavior: PlatformViewHitTestBehavior.opaque, - ); - }, - onCreatePlatformView: (PlatformViewCreationParams params) { - return PlatformViewsService.initSurfaceAndroidView( - id: params.id, - viewType: 'plugins.flutter.io/webview', - // WebView content is not affected by the Android view's layout direction, - // we explicitly set it here so that the widget doesn't require an ambient - // directionality. - layoutDirection: TextDirection.rtl, - creationParams: creationParams != null ? MethodChannelWebViewPlatform.creationParamsToMap(creationParams) : null, - creationParamsCodec: const StandardMessageCodec(), - ) - ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) - ..addOnPlatformViewCreatedListener((int id) { - if (onWebViewPlatformCreated == null || webViewPlatformCallbacksHandler == null) { - return; - } - onWebViewPlatformCreated( - MethodChannelWebViewPlatform(id, webViewPlatformCallbacksHandler), - ); - }) - ..create(); - }, - ); - } -} - -/// Decides how to handle a specific navigation request. -/// -/// The returned [NavigationDecision] determines how the navigation described by -/// `navigation` should be handled. -/// -/// See also: [WebView.navigationDelegate]. -typedef FutureOr NavigationDelegate( - NavigationRequest navigation); - -/// Signature for when a [WebView] has started loading a page. -typedef void PageStartedCallback(String url); - -/// Signature for when a [WebView] has finished loading a page. -typedef void PageFinishedCallback(String url); - -/// Signature for when a [WebView] has failed to load a resource. -typedef void WebResourceErrorCallback(WebResourceError error); - -/// Specifies possible restrictions on automatic media playback. -/// -/// This is typically used in [WebView.initialMediaPlaybackPolicy]. -// The method channel implementation is marshalling this enum to the value's index, so the order -// is important. -enum AutoMediaPlaybackPolicy { - /// Starting any kind of media playback requires a user action. - /// - /// For example: JavaScript code cannot start playing media unless the code was executed - /// as a result of a user action (like a touch event). - require_user_action_for_all_media_types, - - /// Starting any kind of media playback is always allowed. - /// - /// For example: JavaScript code that's triggered when the page is loaded can start playing - /// video or audio. - always_allow, -} - -final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9_]*\$'); - -/// A named channel for receiving messaged from JavaScript code running inside a web view. -class JavascriptChannel { - /// Constructs a Javascript channel. - /// - /// The parameters `name` and `onMessageReceived` must not be null. - JavascriptChannel({ - required this.name, - required this.onMessageReceived, - }) : assert(_validChannelNames.hasMatch(name)); - - /// The channel's name. - /// - /// Passing this channel object as part of a [WebView.javascriptChannels] adds a channel object to - /// the Javascript window object's property named `name`. - /// - /// The name must start with a letter or underscore(_), followed by any combination of those - /// characters plus digits. - /// - /// Note that any JavaScript existing `window` property with this name will be overriden. - /// - /// See also [WebView.javascriptChannels] for more details on the channel registration mechanism. - final String name; - - /// A callback that's invoked when a message is received through the channel. - final JavascriptMessageHandler onMessageReceived; -} - -/// A web view widget for showing html content. -/// -/// There is a known issue that on iOS 13.4 and 13.5, other flutter widgets covering -/// the `WebView` is not able to block the `WebView` from receiving touch events. -/// See https://github.com/flutter/flutter/issues/53490. -class WebView extends StatefulWidget { - /// Creates a new web view. - /// - /// The web view can be controlled using a `WebViewController` that is passed to the - /// `onWebViewCreated` callback once the web view is created. - /// - /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. - const WebView({ - Key? key, - this.onWebViewCreated, - this.initialUrl, - this.javascriptMode = JavascriptMode.disabled, - this.javascriptChannels, - this.navigationDelegate, - this.gestureRecognizers, - this.onPageStarted, - this.onPageFinished, - this.onWebResourceError, - this.darkMode = false, - this.debuggingEnabled = false, - this.gestureNavigationEnabled = false, - this.userAgent, - this.initialMediaPlaybackPolicy = - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - }) : super(key: key); - - static WebViewPlatform? _platform; - - /// Sets a custom [WebViewPlatform]. - /// - /// This property can be set to use a custom platform implementation for WebViews. - /// - /// Setting `platform` doesn't affect [WebView]s that were already created. - /// - /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS. - static set platform(WebViewPlatform platform) { - _platform = platform; - } - - /// The WebView platform that's used by this WebView. - /// - /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS. - static WebViewPlatform get platform { - if (_platform == null) { - switch (defaultTargetPlatform) { - case TargetPlatform.android: - _platform = AndroidWebView(); - break; - case TargetPlatform.iOS: - _platform = CupertinoWebView(); - break; - default: - throw UnsupportedError( - "Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); - } - } - return _platform!; - } - - /// If not null invoked once the web view is created. - final WebViewCreatedCallback? onWebViewCreated; - - /// Which gestures should be consumed by the web view. - /// - /// It is possible for other gesture recognizers to be competing with the web view on pointer - /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle - /// vertical drags. The web view will claim gestures that are recognized by any of the - /// recognizers on this list. - /// - /// When this set is empty or null, the web view will only handle pointer events for gestures that - /// were not claimed by any other gesture recognizer. - final Set>? gestureRecognizers; - - /// The initial URL to load. - final String? initialUrl; - - /// Whether Javascript execution is enabled. - final JavascriptMode javascriptMode; - - /// The set of [JavascriptChannel]s available to JavaScript code running in the web view. - /// - /// For each [JavascriptChannel] in the set, a channel object is made available for the - /// JavaScript code in a window property named [JavascriptChannel.name]. - /// The JavaScript code can then call `postMessage` on that object to send a message that will be - /// passed to [JavascriptChannel.onMessageReceived]. - /// - /// For example for the following JavascriptChannel: - /// - /// ```dart - /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); }); - /// ``` - /// - /// JavaScript code can call: - /// - /// ```javascript - /// Print.postMessage('Hello'); - /// ``` - /// - /// To asynchronously invoke the message handler which will print the message to standard output. - /// - /// Adding a new JavaScript channel only takes affect after the next page is loaded. - /// - /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple - /// channels in the list. - /// - /// A null value is equivalent to an empty set. - final Set? javascriptChannels; - - /// A delegate function that decides how to handle navigation actions. - /// - /// When a navigation is initiated by the WebView (e.g when a user clicks a link) - /// this delegate is called and has to decide how to proceed with the navigation. - /// - /// See [NavigationDecision] for possible decisions the delegate can take. - /// - /// When null all navigation actions are allowed. - /// - /// Caveats on Android: - /// - /// * Navigation actions targeted to the main frame can be intercepted, - /// navigation actions targeted to subframes are allowed regardless of the value - /// returned by this delegate. - /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were - /// triggered by a user gesture, this disables some of Chromium's security mechanisms. - /// A navigationDelegate should only be set when loading trusted content. - /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have - /// a later version): - /// * When a navigationDelegate is set pages with frames are not properly handled by the - /// webview, and frames will be opened in the main frame. - /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header. - final NavigationDelegate? navigationDelegate; - - /// Invoked when a page starts loading. - final PageStartedCallback? onPageStarted; - - /// Invoked when a page has finished loading. - /// - /// This is invoked only for the main frame. - /// - /// When [onPageFinished] is invoked on Android, the page being rendered may - /// not be updated yet. - /// - /// When invoked on iOS or Android, any Javascript code that is embedded - /// directly in the HTML has been loaded and code injected with - /// [WebViewController.evaluateJavascript] can assume this. - final PageFinishedCallback? onPageFinished; - - /// Invoked when a web resource has failed to load. - /// - /// This can be called for any resource (iframe, image, etc.), not just for - /// the main page. - final WebResourceErrorCallback? onWebResourceError; - - final bool? darkMode; - - /// Controls whether WebView debugging is enabled. - /// - /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/). - /// - /// WebView debugging is enabled by default in dev builds on iOS. - /// - /// To debug WebViews on iOS: - /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.) - /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> - /// - /// By default `debuggingEnabled` is false. - final bool debuggingEnabled; - - /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations. - /// - /// This only works on iOS. - /// - /// By default `gestureNavigationEnabled` is false. - final bool gestureNavigationEnabled; - - /// The value used for the HTTP User-Agent: request header. - /// - /// When null the platform's webview default is used for the User-Agent header. - /// - /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent. - /// - /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded. - /// - /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom - /// user agent. - /// - /// By default `userAgent` is null. - final String? userAgent; - - /// Which restrictions apply on automatic media playback. - /// - /// This initial value is applied to the platform's webview upon creation. Any following - /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved). - /// - /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. - final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; - - @override - State createState() => _WebViewState(); -} - -class _WebViewState extends State { - final Completer _controller = - Completer(); - - late _PlatformCallbacksHandler _platformCallbacksHandler; - - @override - Widget build(BuildContext context) { - return WebView.platform.build( - context: context, - onWebViewPlatformCreated: _onWebViewPlatformCreated, - webViewPlatformCallbacksHandler: _platformCallbacksHandler, - gestureRecognizers: widget.gestureRecognizers, - creationParams: _creationParamsfromWidget(widget), - ); - } - - @override - void initState() { - super.initState(); - _assertJavascriptChannelNamesAreUnique(); - _platformCallbacksHandler = _PlatformCallbacksHandler(widget); - } - - @override - void didUpdateWidget(WebView oldWidget) { - super.didUpdateWidget(oldWidget); - _assertJavascriptChannelNamesAreUnique(); - _controller.future.then((WebViewController controller) { - _platformCallbacksHandler._widget = widget; - controller._updateWidget(widget); - }); - } - - void _onWebViewPlatformCreated(WebViewPlatformController webViewPlatform) { - final WebViewController controller = - WebViewController._(widget, webViewPlatform, _platformCallbacksHandler); - _controller.complete(controller); - if (widget.onWebViewCreated != null) { - widget.onWebViewCreated!(controller); - } - } - - void _assertJavascriptChannelNamesAreUnique() { - if (widget.javascriptChannels == null || - widget.javascriptChannels!.isEmpty) { - return; - } - assert(_extractChannelNames(widget.javascriptChannels)?.length == - widget.javascriptChannels!.length); - } -} - -CreationParams _creationParamsfromWidget(WebView widget) { - return CreationParams( - initialUrl: widget.initialUrl, - webSettings: _webSettingsFromWidget(widget), - javascriptChannelNames: _extractChannelNames(widget.javascriptChannels), - userAgent: widget.userAgent, - autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, - ); -} - -WebSettings _webSettingsFromWidget(WebView widget) { - return WebSettings( - javascriptMode: widget.javascriptMode, - hasNavigationDelegate: widget.navigationDelegate != null, - darkMode: widget.darkMode, - debuggingEnabled: widget.debuggingEnabled, - gestureNavigationEnabled: widget.gestureNavigationEnabled, - userAgent: WebSetting.of(widget.userAgent ?? ''), - ); -} - -// This method assumes that no fields in `currentValue` are null. -WebSettings _clearUnchangedWebSettings( - WebSettings currentValue, WebSettings newValue) { - assert(currentValue.javascriptMode != null); - assert(currentValue.hasNavigationDelegate != null); - assert(currentValue.darkMode != null); - assert(currentValue.debuggingEnabled != null); - assert(currentValue.userAgent.isPresent); - assert(newValue.javascriptMode != null); - assert(newValue.hasNavigationDelegate != null); - assert(newValue.darkMode != null); - assert(newValue.debuggingEnabled != null); - assert(newValue.userAgent.isPresent); - - JavascriptMode? javascriptMode; - bool? hasNavigationDelegate; - bool? darkMode; - bool? debuggingEnabled; - WebSetting userAgent = WebSetting.absent(); - if (currentValue.javascriptMode != newValue.javascriptMode) { - javascriptMode = newValue.javascriptMode; - } - if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { - hasNavigationDelegate = newValue.hasNavigationDelegate; - } - if (currentValue.darkMode != newValue.darkMode) { - darkMode = newValue.darkMode; - } - if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { - debuggingEnabled = newValue.debuggingEnabled; - } - if (currentValue.userAgent != newValue.userAgent) { - userAgent = newValue.userAgent; - } - - return WebSettings( - javascriptMode: javascriptMode, - hasNavigationDelegate: hasNavigationDelegate, - darkMode: darkMode, - debuggingEnabled: debuggingEnabled, - userAgent: userAgent, - ); -} - -Set? _extractChannelNames(Set? channels) { - final Set channelNames = channels == null - ? {} - : channels.map((JavascriptChannel channel) => channel.name).toSet(); - return channelNames; -} - -class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { - _PlatformCallbacksHandler(this._widget) { - _updateJavascriptChannelsFromSet(_widget.javascriptChannels); - } - - WebView _widget; - - // Maps a channel name to a channel. - final Map _javascriptChannels = - {}; - - @override - void onJavaScriptChannelMessage(String channel, String message) { - _javascriptChannels[channel]?.onMessageReceived(JavascriptMessage(message)); - } - - @override - FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) async { - final NavigationRequest request = - NavigationRequest._(url: url!, isForMainFrame: isForMainFrame!); - final bool allowNavigation = _widget.navigationDelegate == null || - await _widget.navigationDelegate!(request) == - NavigationDecision.navigate; - return allowNavigation; - } - - @override - void onPageStarted(String url) { - if (_widget.onPageStarted != null) { - _widget.onPageStarted!(url); - } - } - - @override - void onPageFinished(String url) { - if (_widget.onPageFinished != null) { - _widget.onPageFinished!(url); - } - } - - @override - void onWebResourceError(WebResourceError error) { - if (_widget.onWebResourceError != null) { - _widget.onWebResourceError!(error); - } - } - - void _updateJavascriptChannelsFromSet(Set? channels) { - _javascriptChannels.clear(); - if (channels == null) { - return; - } - for (JavascriptChannel channel in channels) { - _javascriptChannels[channel.name] = channel; - } - } -} - -/// Controls a [WebView]. -/// -/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] -/// callback for a [WebView] widget. -class WebViewController { - WebViewController._( - this._widget, - this._webViewPlatformController, - this._platformCallbacksHandler, - ) { - _settings = _webSettingsFromWidget(_widget); - } - - final WebViewPlatformController _webViewPlatformController; - - final _PlatformCallbacksHandler _platformCallbacksHandler; - - late WebSettings _settings; - - WebView _widget; - - /// Loads the specified URL. - /// - /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will - /// be added as key value pairs of HTTP headers for the request. - /// - /// `url` must not be null. - /// - /// Throws an ArgumentError if `url` is not a valid URL string. - Future loadUrl( - String url, { - Map? headers, - }) async { - _validateUrlString(url); - return _webViewPlatformController.loadUrl(url, headers); - } - - Future loadData( - String? baseUrl, - String data, - String mimeType, - String encoding) async { - return _webViewPlatformController.loadData(baseUrl, data, mimeType, encoding); - } - - Future setAcceptThirdPartyCookies(bool accept) async { - return _webViewPlatformController.setAcceptThirdPartyCookies(accept); - } - - Future setCookie(String url, String value) async { - return _webViewPlatformController.setCookie(url, value); - } - - /// Accessor to the current URL that the WebView is displaying. - /// - /// If [WebView.initialUrl] was never specified, returns `null`. - /// Note that this operation is asynchronous, and it is possible that the - /// current URL changes again by the time this function returns (in other - /// words, by the time this future completes, the WebView may be displaying a - /// different URL). - Future currentUrl() { - return _webViewPlatformController.currentUrl(); - } - - /// Checks whether there's a back history item. - /// - /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has - /// changed by the time the future completed. - Future canGoBack() { - return _webViewPlatformController.canGoBack(); - } - - /// Checks whether there's a forward history item. - /// - /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has - /// changed by the time the future completed. - Future canGoForward() { - return _webViewPlatformController.canGoForward(); - } - - /// Goes back in the history of this WebView. - /// - /// If there is no back history item this is a no-op. - Future goBack() { - return _webViewPlatformController.goBack(); - } - - /// Goes forward in the history of this WebView. - /// - /// If there is no forward history item this is a no-op. - Future goForward() { - return _webViewPlatformController.goForward(); - } - - /// Reloads the current URL. - Future reload() { - return _webViewPlatformController.reload(); - } - - /// Clears all caches used by the [WebView]. - /// - /// The following caches are cleared: - /// 1. Browser HTTP Cache. - /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. - /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. - /// 3. Application cache. - /// 4. Local Storage. - /// - /// Note: Calling this method also triggers a reload. - Future clearCache() async { - await _webViewPlatformController.clearCache(); - return reload(); - } - - Future _updateWidget(WebView widget) async { - _widget = widget; - await _updateSettings(_webSettingsFromWidget(widget)); - await _updateJavascriptChannels(widget.javascriptChannels); - } - - Future _updateSettings(WebSettings newSettings) { - final WebSettings update = - _clearUnchangedWebSettings(_settings, newSettings); - _settings = newSettings; - return _webViewPlatformController.updateSettings(update); - } - - Future _updateJavascriptChannels( - Set? newChannels) async { - final Set currentChannels = - _platformCallbacksHandler._javascriptChannels.keys.toSet(); - final Set newChannelNames = _extractChannelNames(newChannels)!; - final Set channelsToAdd = - newChannelNames.difference(currentChannels); - final Set channelsToRemove = - currentChannels.difference(newChannelNames); - if (channelsToRemove.isNotEmpty) { - await _webViewPlatformController - .removeJavascriptChannels(channelsToRemove); - } - if (channelsToAdd.isNotEmpty) { - await _webViewPlatformController.addJavascriptChannels(channelsToAdd); - } - _platformCallbacksHandler._updateJavascriptChannelsFromSet(newChannels); - } - - /// Evaluates a JavaScript expression in the context of the current page. - /// - /// On Android returns the evaluation result as a JSON formatted string. - /// - /// On iOS depending on the value type the return value would be one of: - /// - /// - For primitive JavaScript types: the value string formatted (e.g JavaScript 100 returns '100'). - /// - For JavaScript arrays of supported types: a string formatted NSArray(e.g '(1,2,3), note that the string for NSArray is formatted and might contain newlines and extra spaces.'). - /// - Other non-primitive types are not supported on iOS and will complete the Future with an error. - /// - /// The Future completes with an error if a JavaScript error occurred, or on iOS, if the type of the - /// evaluated expression is not supported as described above. - /// - /// When evaluating Javascript in a [WebView], it is best practice to wait for - /// the [WebView.onPageFinished] callback. This guarantees all the Javascript - /// embedded in the main frame HTML has been loaded. - Future evaluateJavascript(String? javascriptString) { - if (_settings.javascriptMode == JavascriptMode.disabled) { - return Future.error(FlutterError( - 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); - } - if (javascriptString == null) { - return Future.error( - ArgumentError('The argument javascriptString must not be null.')); - } - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - return _webViewPlatformController.evaluateJavascript(javascriptString); - } - - /// Returns the title of the currently loaded page. - Future getTitle() { - return _webViewPlatformController.getTitle(); - } - - /// Sets the WebView's content scroll position. - /// - /// The parameters `x` and `y` specify the scroll position in WebView pixels. - Future scrollTo(int x, int y) { - return _webViewPlatformController.scrollTo(x, y); - } - - /// Move the scrolled position of this view. - /// - /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. - Future scrollBy(int x, int y) { - return _webViewPlatformController.scrollBy(x, y); - } - - /// Return the horizontal scroll position, in WebView pixels, of this view. - /// - /// Scroll position is measured from left. - Future getScrollX() { - return _webViewPlatformController.getScrollX(); - } - - /// Return the vertical scroll position, in WebView pixels, of this view. - /// - /// Scroll position is measured from top. - Future getScrollY() { - return _webViewPlatformController.getScrollY(); - } -} - -/// Manages cookies pertaining to all [WebView]s. -class CookieManager { - /// Creates a [CookieManager] -- returns the instance if it's already been called. - factory CookieManager() { - return _instance ??= CookieManager._(); - } - - CookieManager._(); - - static CookieManager? _instance; - - /// Clears all cookies for all [WebView] instances. - /// - /// This is a no op on iOS version smaller than 9. - /// - /// Returns true if cookies were present before clearing, else false. - Future clearCookies() => WebView.platform.clearCookies(); -} - -// Throws an ArgumentError if `url` is not a valid URL string. -void _validateUrlString(String url) { - try { - final Uri uri = Uri.parse(url); - if (uri.scheme.isEmpty) { - throw ArgumentError('Missing scheme in URL string: "$url"'); - } - } on FormatException catch (e) { - throw ArgumentError(e); - } -} diff --git a/apps/flutter_parent/plugins/webview_flutter/pubspec.lock b/apps/flutter_parent/plugins/webview_flutter/pubspec.lock deleted file mode 100644 index e30d186c42..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/pubspec.lock +++ /dev/null @@ -1,220 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.2" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.1" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.2" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_driver: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - fuchsia_remote_debug_protocol: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.10" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - pedantic: - dependency: "direct dev" - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" - platform: - dependency: transitive - description: - name: platform - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" - process: - dependency: transitive - description: - name: process - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.3" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - sync_http: - dependency: transitive - description: - name: sync_http - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.2" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - vm_service: - dependency: transitive - description: - name: vm_service - url: "https://pub.dartlang.org" - source: hosted - version: "7.1.1" - webdriver: - dependency: transitive - description: - name: webdriver - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" -sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=2.5.3" diff --git a/apps/flutter_parent/plugins/webview_flutter/pubspec.yaml b/apps/flutter_parent/plugins/webview_flutter/pubspec.yaml deleted file mode 100644 index 60fc18d241..0000000000 --- a/apps/flutter_parent/plugins/webview_flutter/pubspec.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: webview_flutter -description: A Flutter plugin that provides a WebView widget on Android and iOS. -version: 1.0.7 -homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter -publish_to: none - -environment: - sdk: ">=3.0.0 <3.10.6" - flutter: 3.13.2 - -dependencies: - flutter: - sdk: flutter - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_driver: - sdk: flutter - pedantic: ^1.8.0 - -flutter: - plugin: - platforms: - android: - package: io.flutter.plugins.webviewflutter - pluginClass: WebViewFlutterPlugin - ios: - pluginClass: FLTWebViewFlutterPlugin diff --git a/apps/flutter_parent/pubspec.lock b/apps/flutter_parent/pubspec.lock index bd0a5ac6b2..f05ba9414b 100644 --- a/apps/flutter_parent/pubspec.lock +++ b/apps/flutter_parent/pubspec.lock @@ -1518,10 +1518,35 @@ packages: webview_flutter: dependency: "direct main" description: - path: "plugins/webview_flutter" - relative: true - source: path - version: "1.0.7" + name: webview_flutter + sha256: "392c1d83b70fe2495de3ea2c84531268d5b8de2de3f01086a53334d8b6030a88" + url: "https://pub.dev" + source: hosted + version: "3.0.4" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "8b3b2450e98876c70bfcead876d9390573b34b9418c19e28168b74f6cb252dbd" + url: "https://pub.dev" + source: hosted + version: "2.10.4" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "812165e4e34ca677bdfbfa58c01e33b27fd03ab5fa75b70832d4b7d4ca1fa8cf" + url: "https://pub.dev" + source: hosted + version: "1.9.5" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: a5364369c758892aa487cbf59ea41d9edd10f9d9baf06a94e80f1bd1b4c7bbc0 + url: "https://pub.dev" + source: hosted + version: "2.9.5" win32: dependency: transitive description: diff --git a/apps/flutter_parent/pubspec.yaml b/apps/flutter_parent/pubspec.yaml index 9f2afddcc1..7014361166 100644 --- a/apps/flutter_parent/pubspec.yaml +++ b/apps/flutter_parent/pubspec.yaml @@ -89,9 +89,7 @@ dependencies: package_info_plus: ^4.0.2 permission_handler: ^10.4.3 shared_preferences: ^2.2.0 # Used to cache remote config properties - #webview_flutter: 0.3.19+5 - webview_flutter: # TODO: Remove once the flutter plugin supports baseUrl https://github.com/flutter/plugins/pull/2463 - path: ./plugins/webview_flutter + webview_flutter: ^3.0.4 # Routing fluro: ^2.0.5 diff --git a/apps/flutter_parent/test/utils/test_app.dart b/apps/flutter_parent/test/utils/test_app.dart index f477beeab6..62d116d4b4 100644 --- a/apps/flutter_parent/test/utils/test_app.dart +++ b/apps/flutter_parent/test/utils/test_app.dart @@ -12,8 +12,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import 'dart:ui'; - import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -30,12 +28,13 @@ import 'package:flutter_parent/utils/design/parent_theme.dart'; import 'package:flutter_parent/utils/design/theme_prefs.dart'; import 'package:flutter_parent/utils/remote_config_utils.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_test/src/deprecated.dart'; import 'package:get_it/get_it.dart'; +import 'package:mockito/mockito.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'platform_config.dart'; -import 'test_helpers/mock_helpers.dart'; import 'test_helpers/mock_helpers.mocks.dart'; class TestApp extends StatefulWidget { @@ -259,28 +258,33 @@ Future setupPlatformChannels({PlatformConfig config = const PlatformConfig } } -/// WebView helpers. These are needed as web views tie into platform views. These are special though as the channel -/// name depends on the platform view's ID. This makes mocking these generically difficult as each id has a different -/// platform channel to register. +/// WebView helpers. These are needed as web views tie into platform views. /// /// Inspired solution is a slimmed down version of the WebView test: -/// https://github.com/flutter/plugins/blob/3b71d6e9a4456505f0b079074fcbc9ba9f8e0e15/packages/webview_flutter/test/webview_flutter_test.dart +/// https://github.com/flutter/plugins/blob/webview_flutter-v3.0.4/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart void _initPlatformWebView() { - const MethodChannel('plugins.flutter.io/cookie_manager', const StandardMethodCodec()) - .setMockMethodCallHandler((_) => Future.sync(() => false)); - - // Intercept when a web view is getting created so we can set up the platform channel - SystemChannels.platform_views.setMockMethodCallHandler((call) { - switch (call.method) { - case 'create': - final id = call.arguments['id']; - MethodChannel('plugins.flutter.io/webview_$id', const StandardMethodCodec()) - .setMockMethodCallHandler((_) => Future.sync(() {})); - return Future.sync(() => 1); - default: - return Future.sync(() {}); - } + final mockWebViewPlatformController = MockWebViewPlatformController(); + final mockWebViewPlatform = MockWebViewPlatform(); + when(mockWebViewPlatform.build( + context: anyNamed('context'), + creationParams: anyNamed('creationParams'), + webViewPlatformCallbacksHandler: + anyNamed('webViewPlatformCallbacksHandler'), + javascriptChannelRegistry: anyNamed('javascriptChannelRegistry'), + onWebViewPlatformCreated: anyNamed('onWebViewPlatformCreated'), + gestureRecognizers: anyNamed('gestureRecognizers'), + )).thenAnswer((Invocation invocation) { + final WebViewPlatformCreatedCallback onWebViewPlatformCreated = + invocation.namedArguments[const Symbol('onWebViewPlatformCreated')] + as WebViewPlatformCreatedCallback; + return TestPlatformWebView( + mockWebViewPlatformController: mockWebViewPlatformController, + onWebViewPlatformCreated: onWebViewPlatformCreated, + ); }); + + WebView.platform = mockWebViewPlatform; + WebViewCookieManagerPlatform.instance = FakeWebViewCookieManager(); } /// Mocks the platform channel used by the package_info plugin @@ -391,4 +395,47 @@ void _initPathProvider() { } return null; }); +} + +class TestPlatformWebView extends StatefulWidget { + const TestPlatformWebView({ + Key? key, + required this.mockWebViewPlatformController, + this.onWebViewPlatformCreated, + }) : super(key: key); + + final MockWebViewPlatformController mockWebViewPlatformController; + final WebViewPlatformCreatedCallback? onWebViewPlatformCreated; + + @override + State createState() => TestPlatformWebViewState(); +} + +class TestPlatformWebViewState extends State { + @override + void initState() { + super.initState(); + final WebViewPlatformCreatedCallback? onWebViewPlatformCreated = + widget.onWebViewPlatformCreated; + if (onWebViewPlatformCreated != null) { + onWebViewPlatformCreated(widget.mockWebViewPlatformController); + } + } + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +class FakeWebViewCookieManager extends WebViewCookieManagerPlatform { + @override + Future clearCookies() { + return Future.value(false); + } + + @override + Future setCookie(WebViewCookie cookie) { + return Future.value(null); + } } \ No newline at end of file diff --git a/apps/flutter_parent/test/utils/test_helpers/mock_helpers.dart b/apps/flutter_parent/test/utils/test_helpers/mock_helpers.dart index 21f738eccc..a7b3985f1e 100644 --- a/apps/flutter_parent/test/utils/test_helpers/mock_helpers.dart +++ b/apps/flutter_parent/test/utils/test_helpers/mock_helpers.dart @@ -19,9 +19,7 @@ // settings will correspond the specified values. import 'dart:io'; -import 'package:barcode_scan2/platform_wrapper.dart'; import 'package:dio/dio.dart'; -import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/material.dart'; @@ -45,7 +43,6 @@ import 'package:flutter_parent/network/api/planner_api.dart'; import 'package:flutter_parent/network/api/user_api.dart'; import 'package:flutter_parent/network/utils/analytics.dart'; import 'package:flutter_parent/network/utils/authentication_interceptor.dart'; -import 'package:flutter_parent/network/utils/dio_config.dart'; import 'package:flutter_parent/screens/account_creation/account_creation_interactor.dart'; import 'package:flutter_parent/screens/alert_thresholds/alert_thresholds_interactor.dart'; import 'package:flutter_parent/screens/alerts/alerts_interactor.dart'; @@ -60,7 +57,6 @@ import 'package:flutter_parent/screens/courses/routing_shell/course_routing_shel import 'package:flutter_parent/screens/dashboard/alert_notifier.dart'; import 'package:flutter_parent/screens/dashboard/dashboard_interactor.dart'; import 'package:flutter_parent/screens/dashboard/inbox_notifier.dart'; -import 'package:flutter_parent/screens/dashboard/selected_student_notifier.dart'; import 'package:flutter_parent/screens/domain_search/domain_search_interactor.dart'; import 'package:flutter_parent/screens/events/event_details_interactor.dart'; import 'package:flutter_parent/screens/help/help_screen_interactor.dart'; @@ -102,6 +98,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:sqflite/sqflite.dart'; import 'package:video_player/video_player.dart'; +import 'package:webview_flutter/webview_flutter.dart'; @GenerateNiceMocks([ MockSpec(), @@ -189,6 +186,8 @@ import 'package:video_player/video_player.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), + MockSpec() ]) import 'mock_helpers.mocks.dart'; diff --git a/apps/flutter_parent/test/utils/test_helpers/mock_helpers.mocks.dart b/apps/flutter_parent/test/utils/test_helpers/mock_helpers.mocks.dart index aeb97f61de..6b15d639d3 100644 --- a/apps/flutter_parent/test/utils/test_helpers/mock_helpers.mocks.dart +++ b/apps/flutter_parent/test/utils/test_helpers/mock_helpers.mocks.dart @@ -20,6 +20,7 @@ import 'package:firebase_remote_config_platform_interface/firebase_remote_config as _i14; import 'package:fluro/fluro.dart' as _i84; import 'package:flutter/foundation.dart' as _i11; +import 'package:flutter/gestures.dart' as _i142; import 'package:flutter/material.dart' as _i17; import 'package:flutter/services.dart' as _i15; import 'package:flutter_downloader/flutter_downloader.dart' as _i133; @@ -9196,3 +9197,319 @@ class MockRemoteConfigInteractor extends _i1.Mock returnValueForMissingStub: null, ); } + +/// A class which mocks [WebViewPlatformController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatformController extends _i1.Mock + implements _i16.WebViewPlatformController { + @override + _i8.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( + Invocation.method( + #loadFile, + [absoluteFilePath], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future loadFlutterAsset(String? key) => (super.noSuchMethod( + Invocation.method( + #loadFlutterAsset, + [key], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future loadHtmlString( + String? html, { + String? baseUrl, + }) => + (super.noSuchMethod( + Invocation.method( + #loadHtmlString, + [html], + {#baseUrl: baseUrl}, + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future loadUrl( + String? url, + Map? headers, + ) => + (super.noSuchMethod( + Invocation.method( + #loadUrl, + [ + url, + headers, + ], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future loadRequest(_i16.WebViewRequest? request) => + (super.noSuchMethod( + Invocation.method( + #loadRequest, + [request], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future updateSettings(_i16.WebSettings? setting) => + (super.noSuchMethod( + Invocation.method( + #updateSettings, + [setting], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future currentUrl() => (super.noSuchMethod( + Invocation.method( + #currentUrl, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future canGoBack() => (super.noSuchMethod( + Invocation.method( + #canGoBack, + [], + ), + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) as _i8.Future); + @override + _i8.Future canGoForward() => (super.noSuchMethod( + Invocation.method( + #canGoForward, + [], + ), + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) as _i8.Future); + @override + _i8.Future goBack() => (super.noSuchMethod( + Invocation.method( + #goBack, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future goForward() => (super.noSuchMethod( + Invocation.method( + #goForward, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future reload() => (super.noSuchMethod( + Invocation.method( + #reload, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future clearCache() => (super.noSuchMethod( + Invocation.method( + #clearCache, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future evaluateJavascript(String? javascript) => + (super.noSuchMethod( + Invocation.method( + #evaluateJavascript, + [javascript], + ), + returnValue: _i8.Future.value(''), + returnValueForMissingStub: _i8.Future.value(''), + ) as _i8.Future); + @override + _i8.Future runJavascript(String? javascript) => (super.noSuchMethod( + Invocation.method( + #runJavascript, + [javascript], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future runJavascriptReturningResult(String? javascript) => + (super.noSuchMethod( + Invocation.method( + #runJavascriptReturningResult, + [javascript], + ), + returnValue: _i8.Future.value(''), + returnValueForMissingStub: _i8.Future.value(''), + ) as _i8.Future); + @override + _i8.Future addJavascriptChannels(Set? javascriptChannelNames) => + (super.noSuchMethod( + Invocation.method( + #addJavascriptChannels, + [javascriptChannelNames], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future removeJavascriptChannels( + Set? javascriptChannelNames) => + (super.noSuchMethod( + Invocation.method( + #removeJavascriptChannels, + [javascriptChannelNames], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future getTitle() => (super.noSuchMethod( + Invocation.method( + #getTitle, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future scrollTo( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollTo, + [ + x, + y, + ], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future scrollBy( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollBy, + [ + x, + y, + ], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future getScrollX() => (super.noSuchMethod( + Invocation.method( + #getScrollX, + [], + ), + returnValue: _i8.Future.value(0), + returnValueForMissingStub: _i8.Future.value(0), + ) as _i8.Future); + @override + _i8.Future getScrollY() => (super.noSuchMethod( + Invocation.method( + #getScrollY, + [], + ), + returnValue: _i8.Future.value(0), + returnValueForMissingStub: _i8.Future.value(0), + ) as _i8.Future); +} + +/// A class which mocks [WebViewPlatform]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatform extends _i1.Mock implements _i16.WebViewPlatform { + @override + _i17.Widget build({ + required _i17.BuildContext? context, + required _i16.CreationParams? creationParams, + required _i16.WebViewPlatformCallbacksHandler? + webViewPlatformCallbacksHandler, + required _i16.JavascriptChannelRegistry? javascriptChannelRegistry, + _i16.WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set<_i11.Factory<_i142.OneSequenceGestureRecognizer>>? gestureRecognizers, + }) => + (super.noSuchMethod( + Invocation.method( + #build, + [], + { + #context: context, + #creationParams: creationParams, + #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + #javascriptChannelRegistry: javascriptChannelRegistry, + #onWebViewPlatformCreated: onWebViewPlatformCreated, + #gestureRecognizers: gestureRecognizers, + }, + ), + returnValue: _FakeWidget_30( + this, + Invocation.method( + #build, + [], + { + #context: context, + #creationParams: creationParams, + #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + #javascriptChannelRegistry: javascriptChannelRegistry, + #onWebViewPlatformCreated: onWebViewPlatformCreated, + #gestureRecognizers: gestureRecognizers, + }, + ), + ), + returnValueForMissingStub: _FakeWidget_30( + this, + Invocation.method( + #build, + [], + { + #context: context, + #creationParams: creationParams, + #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + #javascriptChannelRegistry: javascriptChannelRegistry, + #onWebViewPlatformCreated: onWebViewPlatformCreated, + #gestureRecognizers: gestureRecognizers, + }, + ), + ), + ) as _i17.Widget); + @override + _i8.Future clearCookies() => (super.noSuchMethod( + Invocation.method( + #clearCookies, + [], + ), + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) as _i8.Future); +}