From 9950ee57b0fe9a1c2cf1baa9c514b8e1158f9b78 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 15:06:59 +0100 Subject: [PATCH 01/24] Added cookie manager to interface. --- .../CHANGELOG.md | 6 +++ .../webview_method_channel.dart | 8 ++++ .../platform_interface.dart | 1 + .../webview_cookie_manager.dart | 24 ++++++++++ .../platform_interface/webview_platform.dart | 2 + .../lib/src/types/creation_params.dart | 8 +++- .../lib/src/types/types.dart | 1 + .../lib/src/types/webview_cookie.dart | 44 +++++++++++++++++++ .../pubspec.yaml | 2 +- .../webview_method_channel_test.dart | 20 +++++++++ .../webview_cookie_manager_test.dart | 27 ++++++++++++ .../test/src/types/webview_cookie_test.dart | 21 +++++++++ 12 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_cookie_manager.dart create mode 100644 packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart create mode 100644 packages/webview_flutter/webview_flutter_platform_interface/test/src/platform_interface/webview_cookie_manager_test.dart create mode 100644 packages/webview_flutter/webview_flutter_platform_interface/test/src/types/webview_cookie_test.dart diff --git a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md index efc43cf28ed1..e52e728965bd 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.6.0 + +* Adds platform interface for cookie manager. +* Deprecates `clearCookies` in WebViewPlatform in favour of `CookieManager#clearCookies`. +* Expanded `CreationParams` to include cookies to be set at webview creation. + ## 1.5.1 * Reverts the addition of `onUrlChanged`, which was unintentionally a breaking diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart index 8df9f4c62b33..1df1dad89abc 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart @@ -213,6 +213,12 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { .then((dynamic result) => result!); } + /// Method channel implementation for [WebViewPlatform.setCookie]. + static Future setCookie(WebViewCookie cookie) { + return _cookieManagerChannel.invokeMethod( + 'setCookie', cookie.toJson()); + } + static Map _webSettingsToMap(WebSettings? settings) { final Map map = {}; void _addIfNonNull(String key, dynamic value) { @@ -257,6 +263,8 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { 'userAgent': creationParams.userAgent, 'autoMediaPlaybackPolicy': creationParams.autoMediaPlaybackPolicy.index, 'usesHybridComposition': usesHybridComposition, + 'cookies': + creationParams.cookies.map((cookie) => cookie.toJson()).toList() }; } } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/platform_interface.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/platform_interface.dart index 43f967fb13b0..be42dcbf7c54 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/platform_interface.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/platform_interface.dart @@ -6,3 +6,4 @@ export 'javascript_channel_registry.dart'; export 'webview_platform.dart'; export 'webview_platform_callbacks_handler.dart'; export 'webview_platform_controller.dart'; +export 'webview_cookie_manager.dart'; diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_cookie_manager.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_cookie_manager.dart new file mode 100644 index 000000000000..8fb087d2ac9e --- /dev/null +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_cookie_manager.dart @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter 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 'package:webview_flutter_platform_interface/src/types/webview_cookie.dart'; + +/// Interface for a platform implementation of a cookie manager. +/// +/// This interface should only be extended, not implemented, as more methods may be added later. +abstract class WebViewCookieManagerPlatform { + /// Clears all cookies for all [WebView] instances. + /// + /// Returns true if cookies were present before clearing, else false. + Future clearCookies() { + throw UnimplementedError( + 'clearCookies is not implemented on the current platform'); + } + + /// Sets a cookie for all [WebView] instances. + Future setCookie(WebViewCookie cookie) { + throw UnimplementedError( + 'setCookie is not implemented on the current platform'); + } +} diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart index 4732f54d6456..a105adc5fc45 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:webview_flutter_platform_interface/src/platform_interface/javascript_channel_registry.dart'; +import 'package:webview_flutter_platform_interface/src/types/webview_cookie.dart'; import '../types/types.dart'; import 'webview_platform_callbacks_handler.dart'; @@ -59,6 +60,7 @@ abstract class WebViewPlatform { /// Clears all cookies for all [WebView] instances. /// /// Returns true if cookies were present before clearing, else false. + @Deprecated('Use `WebViewCookieManager.clearCookies` instead.') Future clearCookies() { throw UnimplementedError( "WebView clearCookies is not implemented on the current platform"); diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart index f213e976ad84..477878b16b7b 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:webview_flutter_platform_interface/src/types/types.dart'; + import 'auto_media_playback_policy.dart'; import 'web_settings.dart'; @@ -20,6 +22,7 @@ class CreationParams { this.userAgent, this.autoMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + this.cookies = const [], }) : assert(autoMediaPlaybackPolicy != null); /// The initialUrl to load in the webview. @@ -53,8 +56,11 @@ class CreationParams { /// Which restrictions apply on automatic media playback. final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy; + /// The initial set of cookies to set before the webview does its first load. + final List cookies; + @override String toString() { - return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent)'; + return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent, cookies: $cookies)'; } } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/types.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/types.dart index 8ce834196cd8..9956c9250602 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/types.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/types.dart @@ -11,3 +11,4 @@ export 'web_resource_error.dart'; export 'web_resource_error_type.dart'; export 'web_settings.dart'; export 'webview_request.dart'; +export 'webview_cookie.dart'; diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart new file mode 100644 index 000000000000..5725462f5956 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Configuration to use when creating a new [WebViewPlatformController]. +/// +/// The `autoMediaPlaybackPolicy` parameter must not be null. +class WebViewCookie { + /// The name of the cookie. + /// + /// Its value should match "cookie-name" in RFC6265bis: + /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + final String name; + + /// The value of the cookie. + /// + /// Its value should match "cookie-value" in RFC6265bis: + /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + final String value; + + /// The value of the cookie. + /// + /// Its value should match "domain-value" in RFC6265bis: + /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + final String domain; + + /// The value of the cookie. + /// + /// Its value should match "path-value" in RFC6265bis: + /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + final String path; + + /// Construct a new [WebViewCookie]. + const WebViewCookie( + {required this.name, + required this.value, + required this.domain, + this.path = '/'}); + + /// Serialize a [WebViewCookie] to a Map. + Map toJson() { + return {'name': name, 'value': value, 'domain': domain, 'path': path}; + } +} diff --git a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml index 33eb77cbefd5..3c0b5fa34b63 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/plugins/tree/master/packages/webview_flut issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview_flutter%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.5.1 +version: 1.6.0 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart b/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart index 396013535aa9..63bb3006b3e4 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart @@ -590,6 +590,26 @@ void main() { ], ); }); + + test('setCookie', () async { + await MethodChannelWebViewPlatform.setCookie( + WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev')); + + expect( + log, + [ + isMethodCall( + 'setCookie', + arguments: { + 'name': 'foo', + 'value': 'bar', + 'domain': 'flutter.dev', + 'path': '/', + }, + ), + ], + ); + }); }); } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/test/src/platform_interface/webview_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_platform_interface/test/src/platform_interface/webview_cookie_manager_test.dart new file mode 100644 index 000000000000..c03b7238415b --- /dev/null +++ b/packages/webview_flutter/webview_flutter_platform_interface/test/src/platform_interface/webview_cookie_manager_test.dart @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter 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 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter_platform_interface/src/platform_interface/platform_interface.dart'; +import 'package:webview_flutter_platform_interface/src/types/webview_cookie.dart'; + +void main() { + WebViewCookieManagerPlatform? cookieManager; + + setUp(() { + cookieManager = TestWebViewCookieManagerPlatform(); + }); + + test('clearCookies should throw UnimplementedError', () { + expect(() => cookieManager!.clearCookies(), throwsUnimplementedError); + }); + + test('setCookie should throw UnimplementedError', () { + WebViewCookie cookie = + WebViewCookie(domain: 'flutter.dev', name: 'foo', value: 'bar'); + expect(() => cookieManager!.setCookie(cookie), throwsUnimplementedError); + }); +} + +class TestWebViewCookieManagerPlatform extends WebViewCookieManagerPlatform {} diff --git a/packages/webview_flutter/webview_flutter_platform_interface/test/src/types/webview_cookie_test.dart b/packages/webview_flutter/webview_flutter_platform_interface/test/src/types/webview_cookie_test.dart new file mode 100644 index 000000000000..79db8a431dc0 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_platform_interface/test/src/types/webview_cookie_test.dart @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter 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 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter_platform_interface/src/types/types.dart'; + +void main() { + test('WebViewCookie should serialize correctly', () { + WebViewCookie cookie; + Map serializedCookie; + // Test serialization + cookie = WebViewCookie( + name: 'foo', value: 'bar', domain: 'example.com', path: '/test'); + serializedCookie = cookie.toJson(); + expect(serializedCookie['name'], 'foo'); + expect(serializedCookie['value'], 'bar'); + expect(serializedCookie['domain'], 'example.com'); + expect(serializedCookie['path'], '/test'); + }); +} From 4782a689fa7b5e0530d1c26f0aa206441a7de1de Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 15:11:50 +0100 Subject: [PATCH 02/24] Added Android implementation for new CookieManager. --- .../webview_flutter_android/CHANGELOG.md | 3 +- .../android/build.gradle | 8 +- .../CookieManagerHostApiImpl.java | 24 +++ .../webviewflutter/FlutterCookieManager.java | 56 ------- .../GeneratedAndroidWebView.java | 92 +++++++++++- .../webviewflutter/WebViewFlutterPlugin.java | 15 +- .../CookieManagerHostApiImplTest.java | 79 ++++++++++ .../webviewflutter/utils/TestUtils.java | 47 ++++++ .../example/android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../example/lib/web_view.dart | 34 +++++ .../lib/src/android_webview.dart | 41 +++++ .../lib/src/android_webview.pigeon.dart | 70 ++++++++- .../lib/webview_android.dart | 4 +- .../lib/webview_android_cookie_manager.dart | 44 ++++++ .../lib/webview_android_widget.dart | 5 +- .../pigeons/android_webview.dart | 8 + .../webview_flutter_android/pubspec.yaml | 6 +- .../test/android_webview.pigeon.dart | 140 +++++++++--------- .../test/android_webview_test.dart | 17 +++ .../test/android_webview_test.mocks.dart | 41 +++-- .../webview_android_cookie_manager_test.dart | 53 +++++++ ...iew_android_cookie_manager_test.mocks.dart | 38 +++++ .../webview_android_widget_test.mocks.dart | 4 - 24 files changed, 658 insertions(+), 175 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java create mode 100644 packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index ae76ea8bc342..95c1f79dcea7 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 2.4.0 * Updates compileSdkVersion to 31. +* Implemented new cookie manager for setting cookies and providing initial cookies. ## 2.3.0 diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle index e70d4e68edc8..c087839d2777 100644 --- a/packages/webview_flutter/webview_flutter_android/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle @@ -43,8 +43,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } testOptions { @@ -58,8 +58,4 @@ android { } } } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java new file mode 100644 index 000000000000..337301e3453d --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java @@ -0,0 +1,24 @@ +package io.flutter.plugins.webviewflutter; + +import android.os.Build; +import android.webkit.CookieManager; + + +class CookieManagerHostApiImpl implements GeneratedAndroidWebView.CookieManagerHostApi { + @Override + public void clearCookies(GeneratedAndroidWebView.Result result) { + CookieManager cookieManager = CookieManager.getInstance(); + final boolean hasCookies = cookieManager.hasCookies(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cookieManager.removeAllCookies(value -> result.success(hasCookies)); + } else { + cookieManager.removeAllCookie(); + result.success(hasCookies); + } + } + + @Override + public void setCookie(String url, String value) { + CookieManager.getInstance().setCookie(url, value); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java deleted file mode 100644 index df3f21daadeb..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2013 The Flutter 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/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index a34274893117..eaff3fa0bd8f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -1,7 +1,3 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - // Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon @@ -161,6 +157,94 @@ public interface Result { void error(Throwable error); } + private static class CookieManagerHostApiCodec extends StandardMessageCodec { + public static final CookieManagerHostApiCodec INSTANCE = new CookieManagerHostApiCodec(); + + private CookieManagerHostApiCodec() {} + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface CookieManagerHostApi { + void clearCookies(Result result); + + void setCookie(String url, String value); + + /** The codec used by CookieManagerHostApi. */ + static MessageCodec getCodec() { + return CookieManagerHostApiCodec.INSTANCE; + } + + /** + * Sets up an instance of `CookieManagerHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup(BinaryMessenger binaryMessenger, CookieManagerHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.CookieManagerHostApi.clearCookies", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + Result resultCallback = + new Result() { + public void success(Boolean result) { + wrapped.put("result", result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + wrapped.put("error", wrapError(error)); + reply.reply(wrapped); + } + }; + + api.clearCookies(resultCallback); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + reply.reply(wrapped); + } + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.CookieManagerHostApi.setCookie", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + String urlArg = (String) args.get(0); + if (urlArg == null) { + throw new NullPointerException("urlArg unexpectedly null."); + } + String valueArg = (String) args.get(1); + if (valueArg == null) { + throw new NullPointerException("valueArg unexpectedly null."); + } + api.setCookie(urlArg, valueArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + private static class WebViewHostApiCodec extends StandardMessageCodec { public static final WebViewHostApiCodec INSTANCE = new WebViewHostApiCodec(); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index 2b174ff35e6f..288c64a69963 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java @@ -29,8 +29,8 @@ */ public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { private FlutterPluginBinding pluginBinding; - private FlutterCookieManager flutterCookieManager; private WebViewHostApiImpl webViewHostApi; + private CookieManagerHostApiImpl cookieManagerHostApi; private JavaScriptChannelHostApiImpl javaScriptChannelHostApi; /** @@ -62,7 +62,6 @@ public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registra registrar.platformViewRegistry(), registrar.activity(), registrar.view()); - new FlutterCookieManager(registrar.messenger()); } private void setUp( @@ -70,7 +69,6 @@ private void setUp( PlatformViewRegistry viewRegistry, Context context, View containerView) { - new FlutterCookieManager(binaryMessenger); InstanceManager instanceManager = new InstanceManager(); @@ -86,6 +84,7 @@ private void setUp( new JavaScriptChannelHostApiImpl.JavaScriptChannelCreator(), new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), new Handler(context.getMainLooper())); + cookieManagerHostApi = new CookieManagerHostApiImpl(); WebViewHostApi.setup(binaryMessenger, webViewHostApi); JavaScriptChannelHostApi.setup(binaryMessenger, javaScriptChannelHostApi); @@ -111,6 +110,7 @@ private void setUp( binaryMessenger, new WebSettingsHostApiImpl( instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator())); + GeneratedAndroidWebView.CookieManagerHostApi.setup(binaryMessenger, cookieManagerHostApi); } @Override @@ -124,14 +124,7 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { } @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - if (flutterCookieManager == null) { - return; - } - - flutterCookieManager.dispose(); - flutterCookieManager = null; - } + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {} @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java new file mode 100644 index 000000000000..1e2fea4681e3 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java @@ -0,0 +1,79 @@ +package io.flutter.plugins.webviewflutter; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Build; +import android.webkit.CookieManager; +import android.webkit.ValueCallback; +import io.flutter.plugins.webviewflutter.utils.TestUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; + +public class CookieManagerHostApiImplTest { + + private CookieManager cookieManager; + private MockedStatic staticMockCookieManager; + + @Before + public void setup() { + staticMockCookieManager = mockStatic(CookieManager.class); + cookieManager = mock(CookieManager.class); + when(CookieManager.getInstance()).thenReturn(cookieManager); + when(cookieManager.hasCookies()).thenReturn(true); + doAnswer( + answer -> { + ((ValueCallback) answer.getArgument(0)).onReceiveValue(true); + return null; + }) + .when(cookieManager) + .removeAllCookies(any()); + } + + @After + public void tearDown() { + staticMockCookieManager.close(); + } + + @Test + public void setCookieShouldCallSetCookie() { + // Setup + CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl(); + // Run + impl.setCookie("flutter.dev", "foo=bar; path=/"); + // Verify + verify(cookieManager).setCookie("flutter.dev", "foo=bar; path=/"); + } + + @Test + public void clearCookiesShouldCallRemoveAllCookiesOnAndroidLAbove() { + // Setup + TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP); + GeneratedAndroidWebView.Result result = mock(GeneratedAndroidWebView.Result.class); + CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl(); + // Run + impl.clearCookies(result); + // Verify + verify(cookieManager).removeAllCookies(any()); + verify(result).success(true); + } + + @Test + public void clearCookiesShouldCallRemoveAllCookieBelowAndroidL() { + // Setup + TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.KITKAT_WATCH); + GeneratedAndroidWebView.Result result = mock(GeneratedAndroidWebView.Result.class); + CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl(); + // Run + impl.clearCookies(result); + // Verify + verify(cookieManager).removeAllCookie(); + verify(result).success(true); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java new file mode 100644 index 000000000000..31e7d58ee13f --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java @@ -0,0 +1,47 @@ +// Copyright 2013 The Flutter 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.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import org.junit.Assert; + +public class TestUtils { + public static void setFinalStatic(Class classToModify, String fieldName, Object newValue) { + try { + Field field = classToModify.getField(fieldName); + field.setAccessible(true); + + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + + field.set(null, newValue); + } catch (Exception e) { + Assert.fail("Unable to mock static field: " + fieldName); + } + } + + public static void setPrivateField(T instance, String fieldName, Object newValue) { + try { + Field field = instance.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(instance, newValue); + } catch (Exception e) { + Assert.fail("Unable to mock private field: " + fieldName); + } + } + + public static Object getPrivateField(T instance, String fieldName) { + try { + Field field = instance.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(instance); + } catch (Exception e) { + Assert.fail("Unable to mock private field: " + fieldName); + return null; + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/example/android/build.gradle b/packages/webview_flutter/webview_flutter_android/example/android/build.gradle index e101ac08df55..d21451f63b46 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.android.tools.build:gradle:7.0.3' } } diff --git a/packages/webview_flutter/webview_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/webview_flutter/webview_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties index 2819f022f1fd..b8793d3c0d69 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/webview_flutter/webview_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart index 9a535cc38d2b..55a6fbb4df06 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart @@ -3,11 +3,13 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter_android/webview_android.dart'; +import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'navigation_decision.dart'; @@ -61,6 +63,7 @@ class WebView extends StatefulWidget { Key? key, this.onWebViewCreated, this.initialUrl, + this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, @@ -103,6 +106,9 @@ class WebView extends StatefulWidget { /// The initial URL to load. final String? initialUrl; + /// The initial cookies to set. + final List initialCookies; + /// Whether JavaScript execution is enabled. final JavascriptMode javascriptMode; @@ -287,6 +293,7 @@ class _WebViewState extends State { _javascriptChannelRegistry.channels.keys.toSet(), autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, + cookies: widget.initialCookies, ), javascriptChannelRegistry: _javascriptChannelRegistry, ); @@ -631,3 +638,30 @@ WebSettings _webSettingsFromWidget(WebView widget) { zoomEnabled: widget.zoomEnabled, ); } + +class WebViewCookieManager extends WebViewCookieManagerPlatform { + static WebViewCookieManager? _instance; + + static WebViewCookieManager get instance => + _instance ??= WebViewCookieManager._(); + + WebViewCookieManager._(); + + @override + Future clearCookies() async { + if (Platform.isAndroid) { + return WebViewAndroidCookieManager.instance.clearCookies(); + } else { + return super.clearCookies(); + } + } + + @override + Future setCookie(WebViewCookie cookie) { + if (Platform.isAndroid) { + return WebViewAndroidCookieManager.instance.setCookie(cookie); + } else { + return super.setCookie(cookie); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index 1d0dc259ca3d..2b21803b6e68 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart' show AndroidViewSurface; +import 'android_webview.pigeon.dart'; import 'android_webview_api_impls.dart'; // TODO(bparrishMines): This can be removed once pigeon supports null values: https://github.com/flutter/flutter/issues/59118 @@ -270,6 +271,46 @@ class WebView { } } +/// Manages cookies globally for all webviews. +class CookieManager { + CookieManager._(); + + static CookieManager? _instance; + + static CookieManager get instance => _instance ??= CookieManager._(); + + /// Setter for the singleton value, for testing purposes only. + @visibleForTesting + static set instance(CookieManager value) => _instance = value; + + /// Pigeon Host Api implementation for [CookieManager]. + @visibleForTesting + static CookieManagerHostApi api = CookieManagerHostApi(); + + /// Sets a single cookie (key-value pair) for the given URL. Any existing + /// cookie with the same host, path and name will be replaced with the new + /// cookie. The cookie being set will be ignored if it is expired. To set + /// multiple cookies, your application should invoke this method multiple + /// times. + /// + /// The value parameter must follow the format of the Set-Cookie HTTP + /// response header defined by RFC6265bis. This is a key-value pair of the + /// form "key=value", optionally followed by a list of cookie attributes + /// delimited with semicolons (ex. "key=value; Max-Age=123"). Please consult + /// the RFC specification for a list of valid attributes. + /// + /// Note: if specifying a value containing the "Secure" attribute, url must + /// use the "https://" scheme. + /// + /// Params: + /// url – the URL for which the cookie is to be set + /// value – the cookie as a string, using the format of the 'Set-Cookie' HTTP response header + Future setCookie(String url, String value) => api.setCookie(url, value); + + /// Removes all cookies. + Future clearCookies() => api.clearCookies(); +} + /// Manages settings state for a [WebView]. /// /// When a WebView is first created, it obtains a set of default settings. These diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart index 7fb95f1dd942..40b13407da77 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart @@ -1,7 +1,3 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - // Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name @@ -63,6 +59,72 @@ class WebResourceErrorData { } } +class _CookieManagerHostApiCodec extends StandardMessageCodec { + const _CookieManagerHostApiCodec(); +} + +class CookieManagerHostApi { + /// Constructor for [CookieManagerHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + CookieManagerHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _CookieManagerHostApiCodec(); + + Future clearCookies() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CookieManagerHostApi.clearCookies', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send(null) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return (replyMap['result'] as bool?)!; + } + } + + Future setCookie(String arg_url, String arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CookieManagerHostApi.setCookie', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel + .send([arg_url, arg_value]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } +} + class _WebViewHostApiCodec extends StandardMessageCodec { const _WebViewHostApiCodec(); } diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart index ee474b9d43f8..2d1fe1433dca 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart @@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'src/android_webview.dart'; @@ -65,5 +66,6 @@ class AndroidWebView implements WebViewPlatform { } @override - Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); + Future clearCookies() => + WebViewAndroidCookieManager.instance.clearCookies(); } diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart new file mode 100644 index 000000000000..62be234dc4db --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter 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 'package:webview_flutter_android/src/android_webview.dart' + as android_webview; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +/// Handles all cookie operations for the current platform. +class WebViewAndroidCookieManager extends WebViewCookieManagerPlatform { + WebViewAndroidCookieManager._(); + + static WebViewAndroidCookieManager? _instance; + + /// Obtain a singleton instance for [WebViewAndroidCookieManager]. + static WebViewAndroidCookieManager get instance => + _instance ??= WebViewAndroidCookieManager._(); + + @override + Future clearCookies() => + android_webview.CookieManager.instance.clearCookies(); + + @override + Future setCookie(WebViewCookie cookie) { + if (!_isValidPath(cookie.path)) { + throw ArgumentError( + 'The path property for the provided cookie was not given a legal value.'); + } + return android_webview.CookieManager.instance.setCookie( + cookie.domain, + '${Uri.encodeComponent(cookie.name)}=${Uri.encodeComponent(cookie.value)}; path=${cookie.path}', + ); + } + + bool _isValidPath(String path) { + // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + for (final int char in path.codeUnits) { + if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { + return false; + } + } + return true; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index d63d641ab7cb..a841984d47c3 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; - +import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'src/android_webview.dart' as android_webview; @@ -281,6 +281,9 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { ); addJavascriptChannels(creationParams.javascriptChannelNames); + + creationParams.cookies + .forEach(WebViewAndroidCookieManager.instance.setCookie); } Future _setHasProgressTracking(bool hasProgressTracking) async { diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index 47bc42058ba9..860aa611ee14 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -18,6 +18,14 @@ class WebResourceErrorData { String? description; } +@HostApi() +abstract class CookieManagerHostApi { + @async + bool clearCookies(); + + void setCookie(String url, String value); +} + @HostApi(dartHostTestHandler: 'TestWebViewHostApi') abstract class WebViewHostApi { void create(int instanceId, bool useHybridComposition); diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 705951b1fda0..125d2d32130b 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.3.0 +version: 2.4.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -19,7 +19,9 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_platform_interface: ^1.2.0 +# TODO (BeMacized): Update dependency once platform interface has been published. + webview_flutter_platform_interface: + path: ../webview_flutter_platform_interface dev_dependencies: build_runner: ^2.1.4 diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart index 07bbebb6d9d7..daf3e9a46890 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart @@ -1,10 +1,6 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - // Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, avoid_relative_lib_imports +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import // @dart = 2.12 import 'dart:async'; import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; @@ -56,10 +52,10 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.create was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.create was null, expected non-null int.'); - final bool? arg_useHybridComposition = args[1] as bool?; + final bool? arg_useHybridComposition = (args[1] as bool?); assert(arg_useHybridComposition != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.create was null, expected non-null bool.'); api.create(arg_instanceId!, arg_useHybridComposition!); @@ -78,7 +74,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.dispose was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.dispose was null, expected non-null int.'); api.dispose(arg_instanceId!); @@ -97,10 +93,10 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadUrl was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadUrl was null, expected non-null int.'); - final String? arg_url = args[1] as String?; + final String? arg_url = (args[1] as String?); assert(arg_url != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadUrl was null, expected non-null String.'); final Map? arg_headers = @@ -123,7 +119,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getUrl was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getUrl was null, expected non-null int.'); final String output = api.getUrl(arg_instanceId!); @@ -142,7 +138,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoBack was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoBack was null, expected non-null int.'); final bool output = api.canGoBack(arg_instanceId!); @@ -161,7 +157,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoForward was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoForward was null, expected non-null int.'); final bool output = api.canGoForward(arg_instanceId!); @@ -180,7 +176,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goBack was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goBack was null, expected non-null int.'); api.goBack(arg_instanceId!); @@ -199,7 +195,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goForward was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goForward was null, expected non-null int.'); api.goForward(arg_instanceId!); @@ -218,7 +214,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.reload was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.reload was null, expected non-null int.'); api.reload(arg_instanceId!); @@ -237,10 +233,10 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.clearCache was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.clearCache was null, expected non-null int.'); - final bool? arg_includeDiskFiles = args[1] as bool?; + final bool? arg_includeDiskFiles = (args[1] as bool?); assert(arg_includeDiskFiles != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.clearCache was null, expected non-null bool.'); api.clearCache(arg_instanceId!, arg_includeDiskFiles!); @@ -259,10 +255,10 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.evaluateJavascript was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.evaluateJavascript was null, expected non-null int.'); - final String? arg_javascriptString = args[1] as String?; + final String? arg_javascriptString = (args[1] as String?); assert(arg_javascriptString != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.evaluateJavascript was null, expected non-null String.'); final String output = await api.evaluateJavascript( @@ -282,7 +278,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getTitle was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getTitle was null, expected non-null int.'); final String output = api.getTitle(arg_instanceId!); @@ -301,13 +297,13 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollTo was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollTo was null, expected non-null int.'); - final int? arg_x = args[1] as int?; + final int? arg_x = (args[1] as int?); assert(arg_x != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollTo was null, expected non-null int.'); - final int? arg_y = args[2] as int?; + final int? arg_y = (args[2] as int?); assert(arg_y != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollTo was null, expected non-null int.'); api.scrollTo(arg_instanceId!, arg_x!, arg_y!); @@ -326,13 +322,13 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollBy was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollBy was null, expected non-null int.'); - final int? arg_x = args[1] as int?; + final int? arg_x = (args[1] as int?); assert(arg_x != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollBy was null, expected non-null int.'); - final int? arg_y = args[2] as int?; + final int? arg_y = (args[2] as int?); assert(arg_y != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollBy was null, expected non-null int.'); api.scrollBy(arg_instanceId!, arg_x!, arg_y!); @@ -351,7 +347,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollX was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollX was null, expected non-null int.'); final int output = api.getScrollX(arg_instanceId!); @@ -370,7 +366,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollY was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollY was null, expected non-null int.'); final int output = api.getScrollY(arg_instanceId!); @@ -390,7 +386,7 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled was null.'); final List args = (message as List?)!; - final bool? arg_enabled = args[0] as bool?; + final bool? arg_enabled = (args[0] as bool?); assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled was null, expected non-null bool.'); api.setWebContentsDebuggingEnabled(arg_enabled!); @@ -409,10 +405,10 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebViewClient was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebViewClient was null, expected non-null int.'); - final int? arg_webViewClientInstanceId = args[1] as int?; + final int? arg_webViewClientInstanceId = (args[1] as int?); assert(arg_webViewClientInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebViewClient was null, expected non-null int.'); api.setWebViewClient(arg_instanceId!, arg_webViewClientInstanceId!); @@ -431,10 +427,10 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel was null, expected non-null int.'); - final int? arg_javaScriptChannelInstanceId = args[1] as int?; + final int? arg_javaScriptChannelInstanceId = (args[1] as int?); assert(arg_javaScriptChannelInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel was null, expected non-null int.'); api.addJavaScriptChannel( @@ -454,10 +450,10 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel was null, expected non-null int.'); - final int? arg_javaScriptChannelInstanceId = args[1] as int?; + final int? arg_javaScriptChannelInstanceId = (args[1] as int?); assert(arg_javaScriptChannelInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel was null, expected non-null int.'); api.removeJavaScriptChannel( @@ -477,10 +473,10 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setDownloadListener was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setDownloadListener was null, expected non-null int.'); - final int? arg_listenerInstanceId = args[1] as int?; + final int? arg_listenerInstanceId = (args[1] as int?); assert(arg_listenerInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setDownloadListener was null, expected non-null int.'); api.setDownloadListener(arg_instanceId!, arg_listenerInstanceId!); @@ -499,10 +495,10 @@ abstract class TestWebViewHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebChromeClient was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebChromeClient was null, expected non-null int.'); - final int? arg_clientInstanceId = args[1] as int?; + final int? arg_clientInstanceId = (args[1] as int?); assert(arg_clientInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebChromeClient was null, expected non-null int.'); api.setWebChromeClient(arg_instanceId!, arg_clientInstanceId!); @@ -546,10 +542,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.create was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.create was null, expected non-null int.'); - final int? arg_webViewInstanceId = args[1] as int?; + final int? arg_webViewInstanceId = (args[1] as int?); assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!, arg_webViewInstanceId!); @@ -568,7 +564,7 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.dispose was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.dispose was null, expected non-null int.'); api.dispose(arg_instanceId!); @@ -587,10 +583,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled was null, expected non-null int.'); - final bool? arg_flag = args[1] as bool?; + final bool? arg_flag = (args[1] as bool?); assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled was null, expected non-null bool.'); api.setDomStorageEnabled(arg_instanceId!, arg_flag!); @@ -610,10 +606,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically was null, expected non-null int.'); - final bool? arg_flag = args[1] as bool?; + final bool? arg_flag = (args[1] as bool?); assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically was null, expected non-null bool.'); api.setJavaScriptCanOpenWindowsAutomatically( @@ -634,10 +630,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows was null, expected non-null int.'); - final bool? arg_support = args[1] as bool?; + final bool? arg_support = (args[1] as bool?); assert(arg_support != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows was null, expected non-null bool.'); api.setSupportMultipleWindows(arg_instanceId!, arg_support!); @@ -656,10 +652,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled was null, expected non-null int.'); - final bool? arg_flag = args[1] as bool?; + final bool? arg_flag = (args[1] as bool?); assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled was null, expected non-null bool.'); api.setJavaScriptEnabled(arg_instanceId!, arg_flag!); @@ -678,10 +674,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString was null, expected non-null int.'); - final String? arg_userAgentString = args[1] as String?; + final String? arg_userAgentString = (args[1] as String?); assert(arg_userAgentString != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString was null, expected non-null String.'); api.setUserAgentString(arg_instanceId!, arg_userAgentString!); @@ -701,10 +697,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture was null, expected non-null int.'); - final bool? arg_require = args[1] as bool?; + final bool? arg_require = (args[1] as bool?); assert(arg_require != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture was null, expected non-null bool.'); api.setMediaPlaybackRequiresUserGesture( @@ -724,10 +720,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom was null, expected non-null int.'); - final bool? arg_support = args[1] as bool?; + final bool? arg_support = (args[1] as bool?); assert(arg_support != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom was null, expected non-null bool.'); api.setSupportZoom(arg_instanceId!, arg_support!); @@ -747,10 +743,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode was null, expected non-null int.'); - final bool? arg_overview = args[1] as bool?; + final bool? arg_overview = (args[1] as bool?); assert(arg_overview != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode was null, expected non-null bool.'); api.setLoadWithOverviewMode(arg_instanceId!, arg_overview!); @@ -769,10 +765,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort was null, expected non-null int.'); - final bool? arg_use = args[1] as bool?; + final bool? arg_use = (args[1] as bool?); assert(arg_use != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort was null, expected non-null bool.'); api.setUseWideViewPort(arg_instanceId!, arg_use!); @@ -791,10 +787,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls was null, expected non-null int.'); - final bool? arg_enabled = args[1] as bool?; + final bool? arg_enabled = (args[1] as bool?); assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls was null, expected non-null bool.'); api.setDisplayZoomControls(arg_instanceId!, arg_enabled!); @@ -813,10 +809,10 @@ abstract class TestWebSettingsHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls was null, expected non-null int.'); - final bool? arg_enabled = args[1] as bool?; + final bool? arg_enabled = (args[1] as bool?); assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls was null, expected non-null bool.'); api.setBuiltInZoomControls(arg_instanceId!, arg_enabled!); @@ -849,10 +845,10 @@ abstract class TestJavaScriptChannelHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.JavaScriptChannelHostApi.create was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.JavaScriptChannelHostApi.create was null, expected non-null int.'); - final String? arg_channelName = args[1] as String?; + final String? arg_channelName = (args[1] as String?); assert(arg_channelName != null, 'Argument for dev.flutter.pigeon.JavaScriptChannelHostApi.create was null, expected non-null String.'); api.create(arg_instanceId!, arg_channelName!); @@ -884,10 +880,10 @@ abstract class TestWebViewClientHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewClientHostApi.create was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientHostApi.create was null, expected non-null int.'); - final bool? arg_shouldOverrideUrlLoading = args[1] as bool?; + final bool? arg_shouldOverrideUrlLoading = (args[1] as bool?); assert(arg_shouldOverrideUrlLoading != null, 'Argument for dev.flutter.pigeon.WebViewClientHostApi.create was null, expected non-null bool.'); api.create(arg_instanceId!, arg_shouldOverrideUrlLoading!); @@ -920,7 +916,7 @@ abstract class TestDownloadListenerHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.DownloadListenerHostApi.create was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.DownloadListenerHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); @@ -952,10 +948,10 @@ abstract class TestWebChromeClientHostApi { assert(message != null, 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.create was null.'); final List args = (message as List?)!; - final int? arg_instanceId = args[0] as int?; + final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.create was null, expected non-null int.'); - final int? arg_webViewClientInstanceId = args[1] as int?; + final int? arg_webViewClientInstanceId = (args[1] as int?); assert(arg_webViewClientInstanceId != null, 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!, arg_webViewClientInstanceId!); diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index 29c0c3a0bb34..369ecd67e4a6 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -14,6 +14,7 @@ import 'android_webview.pigeon.dart'; import 'android_webview_test.mocks.dart'; @GenerateMocks([ + CookieManagerHostApi, DownloadListener, JavaScriptChannel, TestDownloadListenerHostApi, @@ -591,4 +592,20 @@ void main() { }); }); }); + + group('CookieManager', () { + test('setCookie calls setCookie on CookieManagerHostApi', () { + CookieManager.api = MockCookieManagerHostApi(); + CookieManager.instance.setCookie('foo', 'bar'); + verify(CookieManager.api.setCookie('foo', 'bar')); + }); + + test('clearCookies calls clearCookies on CookieManagerHostApi', () { + CookieManager.api = MockCookieManagerHostApi(); + when(CookieManager.api.clearCookies()) + .thenAnswer((_) => Future.value(true)); + CookieManager.instance.clearCookies(); + verify(CookieManager.api.clearCookies()); + }); + }); } diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index 4378ed7abf77..b13099ea1539 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -1,7 +1,3 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - // Mocks generated by Mockito 5.0.16 from annotations // in webview_flutter_android/test/android_webview_test.dart. // Do not manually edit this file. @@ -10,8 +6,9 @@ import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; +import 'package:webview_flutter_android/src/android_webview.pigeon.dart' as _i3; -import 'android_webview.pigeon.dart' as _i3; +import 'android_webview.pigeon.dart' as _i5; // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters @@ -24,6 +21,28 @@ import 'android_webview.pigeon.dart' as _i3; class _FakeWebSettings_0 extends _i1.Fake implements _i2.WebSettings {} +/// A class which mocks [CookieManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCookieManagerHostApi extends _i1.Mock + implements _i3.CookieManagerHostApi { + MockCookieManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Future clearCookies() => + (super.noSuchMethod(Invocation.method(#clearCookies, []), + returnValue: Future.value(false)) as _i4.Future); + @override + _i4.Future setCookie(String? arg_url, String? arg_value) => + (super.noSuchMethod(Invocation.method(#setCookie, [arg_url, arg_value]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + String toString() => super.toString(); +} + /// A class which mocks [DownloadListener]. /// /// See the documentation for Mockito's code generation for more information. @@ -67,7 +86,7 @@ class MockJavaScriptChannel extends _i1.Mock implements _i2.JavaScriptChannel { /// /// See the documentation for Mockito's code generation for more information. class MockTestDownloadListenerHostApi extends _i1.Mock - implements _i3.TestDownloadListenerHostApi { + implements _i5.TestDownloadListenerHostApi { MockTestDownloadListenerHostApi() { _i1.throwOnMissingStub(this); } @@ -84,7 +103,7 @@ class MockTestDownloadListenerHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestJavaScriptChannelHostApi extends _i1.Mock - implements _i3.TestJavaScriptChannelHostApi { + implements _i5.TestJavaScriptChannelHostApi { MockTestJavaScriptChannelHostApi() { _i1.throwOnMissingStub(this); } @@ -101,7 +120,7 @@ class MockTestJavaScriptChannelHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestWebChromeClientHostApi extends _i1.Mock - implements _i3.TestWebChromeClientHostApi { + implements _i5.TestWebChromeClientHostApi { MockTestWebChromeClientHostApi() { _i1.throwOnMissingStub(this); } @@ -119,7 +138,7 @@ class MockTestWebChromeClientHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestWebSettingsHostApi extends _i1.Mock - implements _i3.TestWebSettingsHostApi { + implements _i5.TestWebSettingsHostApi { MockTestWebSettingsHostApi() { _i1.throwOnMissingStub(this); } @@ -193,7 +212,7 @@ class MockTestWebSettingsHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestWebViewClientHostApi extends _i1.Mock - implements _i3.TestWebViewClientHostApi { + implements _i5.TestWebViewClientHostApi { MockTestWebViewClientHostApi() { _i1.throwOnMissingStub(this); } @@ -211,7 +230,7 @@ class MockTestWebViewClientHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestWebViewHostApi extends _i1.Mock - implements _i3.TestWebViewHostApi { + implements _i5.TestWebViewHostApi { MockTestWebViewHostApi() { _i1.throwOnMissingStub(this); } diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart new file mode 100644 index 000000000000..5818433ef33f --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter 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 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter_android/src/android_webview.dart' + as android_webview; +import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'webview_android_cookie_manager_test.mocks.dart'; + +@GenerateMocks([android_webview.CookieManager]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + android_webview.CookieManager.instance = MockCookieManager(); + }); + + test('clearCookies should call android_webview.clearCookies', () { + when(android_webview.CookieManager.instance.clearCookies()) + .thenAnswer((_) => Future.value(true)); + WebViewAndroidCookieManager.instance.clearCookies(); + verify(android_webview.CookieManager.instance.clearCookies()); + }); + + test('setCookie should throw ArgumentError for cookie with invalid path', () { + expect( + () => WebViewAndroidCookieManager.instance.setCookie(const WebViewCookie( + name: 'foo', + value: 'bar', + domain: 'flutter.dev', + path: 'invalid;path', + )), + throwsA(const TypeMatcher()), + ); + }); + + test( + 'setCookie should call android_webview.csetCookie with properly formatted cookie value', + () { + WebViewAndroidCookieManager.instance.setCookie(const WebViewCookie( + name: 'foo&', + value: 'bar@', + domain: 'flutter.dev', + )); + verify(android_webview.CookieManager.instance + .setCookie('flutter.dev', 'foo%26=bar%40; path=/')); + }); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart new file mode 100644 index 000000000000..131977bd3dfd --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart @@ -0,0 +1,38 @@ +// Mocks generated by Mockito 5.0.16 from annotations +// in webview_flutter_android/test/webview_android_cookie_manager_test.dart. +// Do not manually edit this file. + +import 'dart:async' as _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_android/src/android_webview.dart' as _i2; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +/// A class which mocks [CookieManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCookieManager extends _i1.Mock implements _i2.CookieManager { + MockCookieManager() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future setCookie(String? url, String? value) => + (super.noSuchMethod(Invocation.method(#setCookie, [url, value]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i3.Future); + @override + _i3.Future clearCookies() => + (super.noSuchMethod(Invocation.method(#clearCookies, []), + returnValue: Future.value(false)) as _i3.Future); + @override + String toString() => super.toString(); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart index c9d3324ecfc3..4f20b2d1e328 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart @@ -1,7 +1,3 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - // Mocks generated by Mockito 5.0.16 from annotations // in webview_flutter_android/test/webview_android_widget_test.dart. // Do not manually edit this file. From 05778158d44494b21e2779872cf09577f191f05f Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 15:15:24 +0100 Subject: [PATCH 03/24] Added iOS implementation for new CookieManager. --- .../webview_flutter_wkwebview/CHANGELOG.md | 4 +- .../ios/Runner.xcodeproj/project.pbxproj | 70 +++++----- .../ios/RunnerTests/FLTCookieManagerTests.m | 127 ++++++++++++++++++ .../FLTWKNavigationDelegateTests.m | 1 + .../example/ios/RunnerTests/FLTWebViewTests.m | 26 +++- .../example/lib/web_view.dart | 33 +++++ .../ios/Classes/FLTCookieManager.h | 6 + .../ios/Classes/FLTCookieManager.m | 62 ++++++++- .../ios/Classes/FLTCookieManager_Test.h | 20 +++ .../ios/Classes/FLTWebViewFlutterPlugin.m | 5 +- .../ios/Classes/FlutterWebView.h | 4 +- .../ios/Classes/FlutterWebView.m | 9 +- .../ios/Classes/FlutterWebView.modulemap | 1 + .../lib/src/webview_ios_cookie_manager.dart | 38 ++++++ .../lib/webview_flutter_wkwebview.dart | 1 + .../webview_flutter_wkwebview/pubspec.yaml | 5 +- .../src/webview_ios_cookie_manager_test.dart | 64 +++++++++ 17 files changed, 430 insertions(+), 46 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index 3b7816188dec..25e9c7613dae 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,6 +1,8 @@ -## NEXT +## 2.5.0 * Integration test fixes. +* Updates compileSdkVersion to 31. +* Implemented new cookie manager for setting cookies and providing initial cookies. ## 2.4.0 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj index e292b1bd52fa..50faddbe8885 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ @@ -16,8 +16,9 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */; }; + D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */; }; DAF0E91266956134538CC667 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 572FFC2B2BA326B420B22679 /* libPods-Runner.a */; }; + E43693B527512C0F00382F85 /* FLTCookieManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */; }; F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F76266057800028CB91 /* FLTWebViewUITests.m */; }; /* End PBXBuildFile section */ @@ -54,10 +55,11 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 572FFC2B2BA326B420B22679 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTWKNavigationDelegateTests.m; sourceTree = ""; }; 68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 68BDCAED23C3F7CB00D9C032 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -74,7 +76,7 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCookieManagerTests.m; sourceTree = ""; }; F7151F74266057800028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F76266057800028CB91 /* FLTWebViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTWebViewUITests.m; sourceTree = ""; }; F7151F78266057800028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -86,7 +88,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */, + D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -112,7 +114,7 @@ isa = PBXGroup; children = ( 572FFC2B2BA326B420B22679 /* libPods-Runner.a */, - 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */, + 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; @@ -123,6 +125,7 @@ 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */, 68BDCAF523C3F97800D9C032 /* FLTWebViewTests.m */, 68BDCAED23C3F7CB00D9C032 /* Info.plist */, + E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */, ); path = RunnerTests; sourceTree = ""; @@ -190,8 +193,8 @@ children = ( F7A1921261392D1CBDAEC2E8 /* Pods-Runner.debug.xcconfig */, B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */, - C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */, - 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */, + 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */, + 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -212,7 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */, + AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */, 68BDCAE523C3F7CB00D9C032 /* Sources */, 68BDCAE623C3F7CB00D9C032 /* Frameworks */, 68BDCAE723C3F7CB00D9C032 /* Resources */, @@ -338,7 +341,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; + }; + 6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -353,28 +370,28 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Thin Binary"; + name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; - 6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */ = { + AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -389,27 +406,13 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -419,6 +422,7 @@ files = ( 334734012669319100DCC49E /* FLTWebViewTests.m in Sources */, 334734022669319400DCC49E /* FLTWKNavigationDelegateTests.m in Sources */, + E43693B527512C0F00382F85 /* FLTCookieManagerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -477,7 +481,7 @@ /* Begin XCBuildConfiguration section */ 68BDCAF023C3F7CB00D9C032 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -491,7 +495,7 @@ }; 68BDCAF123C3F7CB00D9C032 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m new file mode 100644 index 000000000000..837c0d892b64 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m @@ -0,0 +1,127 @@ +// Copyright 2013 The Flutter 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 Flutter; +@import XCTest; +@import webview_flutter_wkwebview; +@import webview_flutter_wkwebview.Test; + +// OCMock library doesn't generate a valid modulemap. +#import + +@interface FLTCookieManagerTests : XCTestCase + +@end + +@implementation FLTCookieManagerTests + +- (void)setUp { + [super setUp]; +} + +- (void)testSetCookieForResultSetsCookieAndReturnsResultOnIOS11 { + if (@available(iOS 11.0, *)) { + // Setup + XCTestExpectation *resultExpectation = [self + expectationWithDescription:@"Should return success result when setting cookie completes."]; + [FLTCookieManager.instance setHttpCookieStore:OCMClassMock(WKHTTPCookieStore.class)]; + NSDictionary *arguments = @{ + @"name" : @"foo", + @"value" : @"bar", + @"domain" : @"flutter.dev", + @"path" : @"/", + }; + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : arguments[@"name"], + NSHTTPCookieValue : arguments[@"value"], + NSHTTPCookieDomain : arguments[@"domain"], + NSHTTPCookiePath : arguments[@"path"], + }]; + [OCMStub([FLTCookieManager.instance.httpCookieStore setCookie:[OCMArg isEqual:cookie] + completionHandler:[OCMArg any]]) + andDo:^(NSInvocation *invocation) { + void (^setCookieCompletionHandler)(void); + [invocation getArgument:&setCookieCompletionHandler atIndex:3]; + setCookieCompletionHandler(); + }]; + // Run + [[FLTCookieManager instance] + setCookieForResult:^(id _Nullable result) { + XCTAssertNil(result); + [resultExpectation fulfill]; + } + arguments:arguments]; + // Verify + [self waitForExpectationsWithTimeout:30.0 handler:nil]; + } +} + +- (void)testSetCookieForDataSetsCookieOnIOS11 { + if (@available(iOS 11.0, *)) { + // Setup + WKHTTPCookieStore *mockHttpCookieStore = OCMClassMock(WKHTTPCookieStore.class); + [FLTCookieManager.instance setHttpCookieStore:mockHttpCookieStore]; + NSDictionary *cookieData = @{ + @"name" : @"foo", + @"value" : @"bar", + @"domain" : @"flutter.dev", + @"path" : @"/", + }; + // Run + [[FLTCookieManager instance] setCookieForData:cookieData]; + // Verify + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : cookieData[@"name"], + NSHTTPCookieValue : cookieData[@"value"], + NSHTTPCookieDomain : cookieData[@"domain"], + NSHTTPCookiePath : cookieData[@"path"], + }]; + OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie] + completionHandler:[OCMArg any]]); + } +} + +- (void)testSetCookiesForDataSetsCookiesOnIOS11 { + if (@available(iOS 11.0, *)) { + // Setup + WKHTTPCookieStore *mockHttpCookieStore = OCMClassMock(WKHTTPCookieStore.class); + [FLTCookieManager.instance setHttpCookieStore:mockHttpCookieStore]; + NSArray *cookieDatas = @[ + @{ + @"name" : @"foo1", + @"value" : @"bar1", + @"domain" : @"flutter.dev", + @"path" : @"/", + }, + @{ + @"name" : @"foo2", + @"value" : @"bar2", + @"domain" : @"flutter2.dev", + @"path" : @"/2", + } + ]; + // Run + [[FLTCookieManager instance] setCookiesForData:cookieDatas]; + // Verify + NSHTTPCookie *cookie1 = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : cookieDatas[0][@"name"], + NSHTTPCookieValue : cookieDatas[0][@"value"], + NSHTTPCookieDomain : cookieDatas[0][@"domain"], + NSHTTPCookiePath : cookieDatas[0][@"path"], + }]; + + OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie1] + completionHandler:[OCMArg any]]); + NSHTTPCookie *cookie2 = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : cookieDatas[1][@"name"], + NSHTTPCookieValue : cookieDatas[1][@"value"], + NSHTTPCookieDomain : cookieDatas[1][@"domain"], + NSHTTPCookiePath : cookieDatas[1][@"path"], + }]; + OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie2] + completionHandler:[OCMArg any]]); + } +} + +@end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m index a819a9b53d60..d39a9f203d70 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m @@ -5,6 +5,7 @@ @import Flutter; @import XCTest; @import webview_flutter_wkwebview; +@import webview_flutter_wkwebview.Test; // OCMock library doesn't generate a valid modulemap. #import diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m index a3c314a79d2a..6c7c6bffbc3f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m @@ -16,6 +16,8 @@ @interface FLTWebViewTests : XCTestCase @property(strong, nonatomic) NSObject *mockBinaryMessenger; +@property(strong, nonatomic) FLTCookieManager *mockCookieManager; + @end @implementation FLTWebViewTests @@ -23,6 +25,7 @@ @implementation FLTWebViewTests - (void)setUp { [super setUp]; self.mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + self.mockCookieManager = OCMClassMock(FLTCookieManager.class); } - (void)testCanInitFLTWebViewController { @@ -35,8 +38,8 @@ - (void)testCanInitFLTWebViewController { } - (void)testCanInitFLTWebViewFactory { - FLTWebViewFactory *factory = - [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger]; + FLTWebViewFactory *factory = [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger + cookieManager:self.mockCookieManager]; XCTAssertNotNil(factory); } @@ -648,7 +651,7 @@ - (void)testOnLoadUrlLoadsRequestWithSuccessResult { [self waitForExpectationsWithTimeout:30.0 handler:nil]; } -- (void)testOnLoadRequestReturnsErroResultForInvalidRequest { +- (void)testOnLoadRequestReturnsErrorResultForInvalidRequest { // Setup FLTWebViewController *controller = [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) @@ -697,4 +700,21 @@ - (void)testOnLoadRequestLoadsRequestWithSuccessResult { [self waitForExpectationsWithTimeout:30.0 handler:nil]; } +- (void)testCreateWithFrameShouldSetCookiesOnIOS11 { + if (@available(iOS 11, *)) { + // Setup + FLTWebViewFactory *factory = + [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger + cookieManager:self.mockCookieManager]; + NSArray *cookies = + @[ @{@"name" : @"foo", @"value" : @"bar", @"domain" : @"flutter.dev", @"path" : @"/"} ]; + // Run + [factory createWithFrame:CGRectMake(0, 0, 300, 400) + viewIdentifier:1 + arguments:@{@"cookies" : cookies}]; + // Verify + OCMVerify([_mockCookieManager setCookiesForData:cookies]); + } +} + @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart index ab4b77336765..f7547aafa0ae 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -54,6 +55,7 @@ class WebView extends StatefulWidget { Key? key, this.onWebViewCreated, this.initialUrl, + this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, @@ -94,6 +96,9 @@ class WebView extends StatefulWidget { /// The initial URL to load. final String? initialUrl; + /// The initial cookies to set. + final List initialCookies; + /// Whether JavaScript execution is enabled. final JavascriptMode javascriptMode; @@ -278,6 +283,7 @@ class _WebViewState extends State { _javascriptChannelRegistry.channels.keys.toSet(), autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, + cookies: widget.initialCookies, ), javascriptChannelRegistry: _javascriptChannelRegistry, ); @@ -651,3 +657,30 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { } } } + +class WebViewCookieManager extends WebViewCookieManagerPlatform { + static WebViewCookieManager? _instance; + + static WebViewCookieManager get instance => + _instance ??= WebViewCookieManager._(); + + WebViewCookieManager._(); + + @override + Future clearCookies() async { + if (Platform.isIOS) { + return WebViewIOSCookieManager.instance.clearCookies(); + } else { + return super.clearCookies(); + } + } + + @override + Future setCookie(WebViewCookie cookie) { + if (Platform.isIOS) { + return WebViewIOSCookieManager.instance.setCookie(cookie); + } else { + return super.setCookie(cookie); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h index 8fe331875250..dc5b8f589ade 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h @@ -9,6 +9,12 @@ NS_ASSUME_NONNULL_BEGIN @interface FLTCookieManager : NSObject ++ (FLTCookieManager*)instance; + +- (void)setCookiesForData:(NSArray*)cookies; + +- (void)setCookieForData:(NSDictionary*)cookie; + @end NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m index f4783ffb4123..39976d11153e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m @@ -3,22 +3,32 @@ // found in the LICENSE file. #import "FLTCookieManager.h" +#import "FLTCookieManager_Test.h" @implementation FLTCookieManager { + WKHTTPCookieStore *_httpCookieStore API_AVAILABLE(macos(10.13), ios(11.0)); } -+ (void)registerWithRegistrar:(NSObject *)registrar { - FLTCookieManager *instance = [[FLTCookieManager alloc] init]; ++ (FLTCookieManager *)instance { + static FLTCookieManager *instance = nil; + if (instance == nil) { + instance = [[FLTCookieManager alloc] init]; + } + return instance; +} ++ (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/cookie_manager" binaryMessenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:channel]; + [registrar addMethodCallDelegate:[self instance] channel:channel]; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([[call method] isEqualToString:@"clearCookies"]) { [self clearCookies:result]; + } else if ([[call method] isEqualToString:@"setCookie"]) { + [self setCookieForResult:result arguments:[call arguments]]; } else { result(FlutterMethodNotImplemented); } @@ -46,4 +56,50 @@ - (void)clearCookies:(FlutterResult)result { } } +- (void)setCookiesForData:(NSArray *)cookies { + for (id cookie in cookies) { + [self setCookieForData:cookie]; + } +} + +- (void)setCookieForData:(NSDictionary *)cookieData { + if (@available(iOS 11.0, *)) { + if (!_httpCookieStore) { + _httpCookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; + } + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : cookieData[@"name"], + NSHTTPCookieValue : cookieData[@"value"], + NSHTTPCookieDomain : cookieData[@"domain"], + NSHTTPCookiePath : cookieData[@"path"], + }]; + [_httpCookieStore setCookie:cookie + completionHandler:^{ + }]; + } else { + NSLog(@"Setting cookies is not supported for Flutter WebViews prior to iOS 11."); + } +} + +- (void)setCookieForResult:(FlutterResult)result arguments:(NSDictionary *)arguments { + if (@available(iOS 11.0, *)) { + if (!_httpCookieStore) { + _httpCookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; + } + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : arguments[@"name"], + NSHTTPCookieValue : arguments[@"value"], + NSHTTPCookieDomain : arguments[@"domain"], + NSHTTPCookiePath : arguments[@"path"], + }]; + [_httpCookieStore setCookie:cookie + completionHandler:^{ + result(nil); + }]; + } else { + NSLog(@"Setting cookies is not supported for Flutter WebViews prior to iOS 11."); + result(nil); + } +} + @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h new file mode 100644 index 000000000000..fecec4932570 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h @@ -0,0 +1,20 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header is available in the Test module. Import via "@import webview_flutter_wkwebview.Test;" + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FLTCookieManager () + +@property(nonatomic, strong) + WKHTTPCookieStore *httpCookieStore API_AVAILABLE(macos(10.13), ios(11.0)); + +- (void)setCookieForResult:(FlutterResult)result arguments:(NSDictionary *)arguments; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m index 9f01416acc6a..0d04d6c3a9ee 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m @@ -9,10 +9,11 @@ @implementation FLTWebViewFlutterPlugin + (void)registerWithRegistrar:(NSObject*)registrar { + [FLTCookieManager registerWithRegistrar:registrar]; FLTWebViewFactory* webviewFactory = - [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger]; + [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger + cookieManager:[FLTCookieManager instance]]; [registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"]; - [FLTCookieManager registerWithRegistrar:registrar]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h index 6d8e463c7b13..145a772c5015 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import #import #import @@ -31,7 +32,8 @@ NS_ASSUME_NONNULL_BEGIN @end @interface FLTWebViewFactory : NSObject -- (instancetype)initWithMessenger:(NSObject*)messenger; +- (instancetype)initWithMessenger:(NSObject*)messenger + cookieManager:(FLTCookieManager*)cookieManager; @end NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m index 351d1ae58760..696fde918be9 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m @@ -10,12 +10,15 @@ @implementation FLTWebViewFactory { NSObject* _messenger; + FLTCookieManager* _cookieManager; } -- (instancetype)initWithMessenger:(NSObject*)messenger { +- (instancetype)initWithMessenger:(NSObject*)messenger + cookieManager:(FLTCookieManager*)cookieManager { self = [super init]; if (self) { _messenger = messenger; + _cookieManager = cookieManager; } return self; } @@ -27,6 +30,10 @@ - (instancetype)initWithMessenger:(NSObject*)messenger { - (NSObject*)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args { + if (@available(iOS 11.0, *)) { + [_cookieManager setCookiesForData:args[@"cookies"]]; + } + FLTWebViewController* webviewController = [[FLTWebViewController alloc] initWithFrame:frame viewIdentifier:viewId arguments:args diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap index fa5143060381..096507557688 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap @@ -6,5 +6,6 @@ framework module webview_flutter_wkwebview { explicit module Test { header "FlutterWebView_Test.h" + header "FLTCookieManager_Test.h" } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart new file mode 100644 index 000000000000..f6dde177ecf6 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter 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 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +/// Handles all cookie operations for the current platform. +class WebViewIOSCookieManager extends WebViewCookieManagerPlatform { + WebViewIOSCookieManager._(); + + static WebViewIOSCookieManager? _instance; + + /// Obtain a singleton instance for [WebViewiOSCookieManager]. + static WebViewIOSCookieManager get instance => + _instance ??= WebViewIOSCookieManager._(); + + @override + Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); + + @override + Future setCookie(WebViewCookie cookie) { + if (!_isValidPath(cookie.path)) { + throw ArgumentError( + 'The path property for the provided cookie was not given a legal value.'); + } + return MethodChannelWebViewPlatform.setCookie(cookie); + } + + bool _isValidPath(String path) { + // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + for (final int char in path.codeUnits) { + if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { + return false; + } + } + return true; + } +} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart index bbec415dccd0..a976bace2c75 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart @@ -3,3 +3,4 @@ // found in the LICENSE file. export 'src/webview_cupertino.dart'; +export 'src/webview_ios_cookie_manager.dart'; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index 466c1a2a4fcd..cdf2598f77eb 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.4.0 +version: 2.5.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -18,7 +18,8 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_platform_interface: ^1.3.0 + webview_flutter_platform_interface: + path: ../webview_flutter_platform_interface dev_dependencies: flutter_driver: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart new file mode 100644 index 000000000000..10d13e44873e --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart @@ -0,0 +1,64 @@ +// Copyright 2013 The Flutter 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 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_wkwebview/src/webview_ios_cookie_manager.dart'; + +main() { + TestWidgetsFlutterBinding.ensureInitialized(); + const MethodChannel cookieChannel = + MethodChannel('plugins.flutter.io/cookie_manager'); + final List log = []; + + cookieChannel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + + if (methodCall.method == 'clearCookies') { + return true; + } + + // Return null explicitly instead of relying on the implicit null + // returned by the method channel if no return statement is specified. + return null; + }); + + tearDown(() { + log.clear(); + }); + + test('clearCookies should call `clearCookies` on the method channel', + () async { + await WebViewIOSCookieManager.instance.clearCookies(); + expect( + log, + [ + isMethodCall( + 'clearCookies', + arguments: null, + ), + ], + ); + }); + + test('setCookie should call `setCookie` on the method channel', () async { + await WebViewIOSCookieManager.instance.setCookie( + WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev')); + expect( + log, + [ + isMethodCall( + 'setCookie', + arguments: { + 'name': 'foo', + 'value': 'bar', + 'domain': 'flutter.dev', + 'path': '/', + }, + ), + ], + ); + }); +} From 2e3dba3932fd36605b3fbfcb6f20a24f65e4fed9 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 15:16:01 +0100 Subject: [PATCH 04/24] Update pubspec --- packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index cdf2598f77eb..aa52363dadf8 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -18,6 +18,7 @@ flutter: dependencies: flutter: sdk: flutter +# TODO (BeMacized): Update dependency when platform interface has been updated and published. webview_flutter_platform_interface: path: ../webview_flutter_platform_interface From 4816d7132d6400816b6a3f1650a783017a4cd6a4 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 15:22:50 +0100 Subject: [PATCH 05/24] Revert accidental push of ios changes --- .../webview_flutter_wkwebview/CHANGELOG.md | 4 +- .../ios/Runner.xcodeproj/project.pbxproj | 70 +++++----- .../ios/RunnerTests/FLTCookieManagerTests.m | 127 ------------------ .../FLTWKNavigationDelegateTests.m | 1 - .../example/ios/RunnerTests/FLTWebViewTests.m | 26 +--- .../example/lib/web_view.dart | 33 ----- .../ios/Classes/FLTCookieManager.h | 6 - .../ios/Classes/FLTCookieManager.m | 62 +-------- .../ios/Classes/FLTCookieManager_Test.h | 20 --- .../ios/Classes/FLTWebViewFlutterPlugin.m | 5 +- .../ios/Classes/FlutterWebView.h | 4 +- .../ios/Classes/FlutterWebView.m | 9 +- .../ios/Classes/FlutterWebView.modulemap | 1 - .../lib/src/webview_ios_cookie_manager.dart | 38 ------ .../lib/webview_flutter_wkwebview.dart | 1 - .../webview_flutter_wkwebview/pubspec.yaml | 6 +- .../src/webview_ios_cookie_manager_test.dart | 64 --------- 17 files changed, 46 insertions(+), 431 deletions(-) delete mode 100644 packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m delete mode 100644 packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h delete mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart delete mode 100644 packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index 25e9c7613dae..3b7816188dec 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,8 +1,6 @@ -## 2.5.0 +## NEXT * Integration test fixes. -* Updates compileSdkVersion to 31. -* Implemented new cookie manager for setting cookies and providing initial cookies. ## 2.4.0 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj index 50faddbe8885..e292b1bd52fa 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -16,9 +16,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */; }; + AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */; }; DAF0E91266956134538CC667 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 572FFC2B2BA326B420B22679 /* libPods-Runner.a */; }; - E43693B527512C0F00382F85 /* FLTCookieManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */; }; F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F76266057800028CB91 /* FLTWebViewUITests.m */; }; /* End PBXBuildFile section */ @@ -55,11 +54,10 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 572FFC2B2BA326B420B22679 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTWKNavigationDelegateTests.m; sourceTree = ""; }; 68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 68BDCAED23C3F7CB00D9C032 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -76,7 +74,7 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCookieManagerTests.m; sourceTree = ""; }; + C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; F7151F74266057800028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F76266057800028CB91 /* FLTWebViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTWebViewUITests.m; sourceTree = ""; }; F7151F78266057800028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -88,7 +86,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */, + AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -114,7 +112,7 @@ isa = PBXGroup; children = ( 572FFC2B2BA326B420B22679 /* libPods-Runner.a */, - 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */, + 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; @@ -125,7 +123,6 @@ 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */, 68BDCAF523C3F97800D9C032 /* FLTWebViewTests.m */, 68BDCAED23C3F7CB00D9C032 /* Info.plist */, - E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */, ); path = RunnerTests; sourceTree = ""; @@ -193,8 +190,8 @@ children = ( F7A1921261392D1CBDAEC2E8 /* Pods-Runner.debug.xcconfig */, B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */, - 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */, - 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */, + C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */, + 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -215,7 +212,7 @@ isa = PBXNativeTarget; buildConfigurationList = 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */, + 0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */, 68BDCAE523C3F7CB00D9C032 /* Sources */, 68BDCAE623C3F7CB00D9C032 /* Frameworks */, 68BDCAE723C3F7CB00D9C032 /* Resources */, @@ -341,21 +338,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; - }; - 6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */ = { + 0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -370,28 +353,28 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Run Script"; + name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; }; - AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */ = { + 6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -406,13 +389,27 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -422,7 +419,6 @@ files = ( 334734012669319100DCC49E /* FLTWebViewTests.m in Sources */, 334734022669319400DCC49E /* FLTWKNavigationDelegateTests.m in Sources */, - E43693B527512C0F00382F85 /* FLTCookieManagerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -481,7 +477,7 @@ /* Begin XCBuildConfiguration section */ 68BDCAF023C3F7CB00D9C032 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -495,7 +491,7 @@ }; 68BDCAF123C3F7CB00D9C032 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m deleted file mode 100644 index 837c0d892b64..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2013 The Flutter 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 Flutter; -@import XCTest; -@import webview_flutter_wkwebview; -@import webview_flutter_wkwebview.Test; - -// OCMock library doesn't generate a valid modulemap. -#import - -@interface FLTCookieManagerTests : XCTestCase - -@end - -@implementation FLTCookieManagerTests - -- (void)setUp { - [super setUp]; -} - -- (void)testSetCookieForResultSetsCookieAndReturnsResultOnIOS11 { - if (@available(iOS 11.0, *)) { - // Setup - XCTestExpectation *resultExpectation = [self - expectationWithDescription:@"Should return success result when setting cookie completes."]; - [FLTCookieManager.instance setHttpCookieStore:OCMClassMock(WKHTTPCookieStore.class)]; - NSDictionary *arguments = @{ - @"name" : @"foo", - @"value" : @"bar", - @"domain" : @"flutter.dev", - @"path" : @"/", - }; - NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ - NSHTTPCookieName : arguments[@"name"], - NSHTTPCookieValue : arguments[@"value"], - NSHTTPCookieDomain : arguments[@"domain"], - NSHTTPCookiePath : arguments[@"path"], - }]; - [OCMStub([FLTCookieManager.instance.httpCookieStore setCookie:[OCMArg isEqual:cookie] - completionHandler:[OCMArg any]]) - andDo:^(NSInvocation *invocation) { - void (^setCookieCompletionHandler)(void); - [invocation getArgument:&setCookieCompletionHandler atIndex:3]; - setCookieCompletionHandler(); - }]; - // Run - [[FLTCookieManager instance] - setCookieForResult:^(id _Nullable result) { - XCTAssertNil(result); - [resultExpectation fulfill]; - } - arguments:arguments]; - // Verify - [self waitForExpectationsWithTimeout:30.0 handler:nil]; - } -} - -- (void)testSetCookieForDataSetsCookieOnIOS11 { - if (@available(iOS 11.0, *)) { - // Setup - WKHTTPCookieStore *mockHttpCookieStore = OCMClassMock(WKHTTPCookieStore.class); - [FLTCookieManager.instance setHttpCookieStore:mockHttpCookieStore]; - NSDictionary *cookieData = @{ - @"name" : @"foo", - @"value" : @"bar", - @"domain" : @"flutter.dev", - @"path" : @"/", - }; - // Run - [[FLTCookieManager instance] setCookieForData:cookieData]; - // Verify - NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ - NSHTTPCookieName : cookieData[@"name"], - NSHTTPCookieValue : cookieData[@"value"], - NSHTTPCookieDomain : cookieData[@"domain"], - NSHTTPCookiePath : cookieData[@"path"], - }]; - OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie] - completionHandler:[OCMArg any]]); - } -} - -- (void)testSetCookiesForDataSetsCookiesOnIOS11 { - if (@available(iOS 11.0, *)) { - // Setup - WKHTTPCookieStore *mockHttpCookieStore = OCMClassMock(WKHTTPCookieStore.class); - [FLTCookieManager.instance setHttpCookieStore:mockHttpCookieStore]; - NSArray *cookieDatas = @[ - @{ - @"name" : @"foo1", - @"value" : @"bar1", - @"domain" : @"flutter.dev", - @"path" : @"/", - }, - @{ - @"name" : @"foo2", - @"value" : @"bar2", - @"domain" : @"flutter2.dev", - @"path" : @"/2", - } - ]; - // Run - [[FLTCookieManager instance] setCookiesForData:cookieDatas]; - // Verify - NSHTTPCookie *cookie1 = [NSHTTPCookie cookieWithProperties:@{ - NSHTTPCookieName : cookieDatas[0][@"name"], - NSHTTPCookieValue : cookieDatas[0][@"value"], - NSHTTPCookieDomain : cookieDatas[0][@"domain"], - NSHTTPCookiePath : cookieDatas[0][@"path"], - }]; - - OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie1] - completionHandler:[OCMArg any]]); - NSHTTPCookie *cookie2 = [NSHTTPCookie cookieWithProperties:@{ - NSHTTPCookieName : cookieDatas[1][@"name"], - NSHTTPCookieValue : cookieDatas[1][@"value"], - NSHTTPCookieDomain : cookieDatas[1][@"domain"], - NSHTTPCookiePath : cookieDatas[1][@"path"], - }]; - OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie2] - completionHandler:[OCMArg any]]); - } -} - -@end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m index d39a9f203d70..a819a9b53d60 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m @@ -5,7 +5,6 @@ @import Flutter; @import XCTest; @import webview_flutter_wkwebview; -@import webview_flutter_wkwebview.Test; // OCMock library doesn't generate a valid modulemap. #import diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m index 6c7c6bffbc3f..a3c314a79d2a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m @@ -16,8 +16,6 @@ @interface FLTWebViewTests : XCTestCase @property(strong, nonatomic) NSObject *mockBinaryMessenger; -@property(strong, nonatomic) FLTCookieManager *mockCookieManager; - @end @implementation FLTWebViewTests @@ -25,7 +23,6 @@ @implementation FLTWebViewTests - (void)setUp { [super setUp]; self.mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); - self.mockCookieManager = OCMClassMock(FLTCookieManager.class); } - (void)testCanInitFLTWebViewController { @@ -38,8 +35,8 @@ - (void)testCanInitFLTWebViewController { } - (void)testCanInitFLTWebViewFactory { - FLTWebViewFactory *factory = [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger - cookieManager:self.mockCookieManager]; + FLTWebViewFactory *factory = + [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger]; XCTAssertNotNil(factory); } @@ -651,7 +648,7 @@ - (void)testOnLoadUrlLoadsRequestWithSuccessResult { [self waitForExpectationsWithTimeout:30.0 handler:nil]; } -- (void)testOnLoadRequestReturnsErrorResultForInvalidRequest { +- (void)testOnLoadRequestReturnsErroResultForInvalidRequest { // Setup FLTWebViewController *controller = [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) @@ -700,21 +697,4 @@ - (void)testOnLoadRequestLoadsRequestWithSuccessResult { [self waitForExpectationsWithTimeout:30.0 handler:nil]; } -- (void)testCreateWithFrameShouldSetCookiesOnIOS11 { - if (@available(iOS 11, *)) { - // Setup - FLTWebViewFactory *factory = - [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger - cookieManager:self.mockCookieManager]; - NSArray *cookies = - @[ @{@"name" : @"foo", @"value" : @"bar", @"domain" : @"flutter.dev", @"path" : @"/"} ]; - // Run - [factory createWithFrame:CGRectMake(0, 0, 300, 400) - viewIdentifier:1 - arguments:@{@"cookies" : cookies}]; - // Verify - OCMVerify([_mockCookieManager setCookiesForData:cookies]); - } -} - @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart index f7547aafa0ae..ab4b77336765 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -55,7 +54,6 @@ class WebView extends StatefulWidget { Key? key, this.onWebViewCreated, this.initialUrl, - this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, @@ -96,9 +94,6 @@ class WebView extends StatefulWidget { /// The initial URL to load. final String? initialUrl; - /// The initial cookies to set. - final List initialCookies; - /// Whether JavaScript execution is enabled. final JavascriptMode javascriptMode; @@ -283,7 +278,6 @@ class _WebViewState extends State { _javascriptChannelRegistry.channels.keys.toSet(), autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, - cookies: widget.initialCookies, ), javascriptChannelRegistry: _javascriptChannelRegistry, ); @@ -657,30 +651,3 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { } } } - -class WebViewCookieManager extends WebViewCookieManagerPlatform { - static WebViewCookieManager? _instance; - - static WebViewCookieManager get instance => - _instance ??= WebViewCookieManager._(); - - WebViewCookieManager._(); - - @override - Future clearCookies() async { - if (Platform.isIOS) { - return WebViewIOSCookieManager.instance.clearCookies(); - } else { - return super.clearCookies(); - } - } - - @override - Future setCookie(WebViewCookie cookie) { - if (Platform.isIOS) { - return WebViewIOSCookieManager.instance.setCookie(cookie); - } else { - return super.setCookie(cookie); - } - } -} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h index dc5b8f589ade..8fe331875250 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h @@ -9,12 +9,6 @@ NS_ASSUME_NONNULL_BEGIN @interface FLTCookieManager : NSObject -+ (FLTCookieManager*)instance; - -- (void)setCookiesForData:(NSArray*)cookies; - -- (void)setCookieForData:(NSDictionary*)cookie; - @end NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m index 39976d11153e..f4783ffb4123 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m @@ -3,32 +3,22 @@ // found in the LICENSE file. #import "FLTCookieManager.h" -#import "FLTCookieManager_Test.h" @implementation FLTCookieManager { - WKHTTPCookieStore *_httpCookieStore API_AVAILABLE(macos(10.13), ios(11.0)); -} - -+ (FLTCookieManager *)instance { - static FLTCookieManager *instance = nil; - if (instance == nil) { - instance = [[FLTCookieManager alloc] init]; - } - return instance; } + (void)registerWithRegistrar:(NSObject *)registrar { + FLTCookieManager *instance = [[FLTCookieManager alloc] init]; + FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/cookie_manager" binaryMessenger:[registrar messenger]]; - [registrar addMethodCallDelegate:[self instance] channel:channel]; + [registrar addMethodCallDelegate:instance channel:channel]; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([[call method] isEqualToString:@"clearCookies"]) { [self clearCookies:result]; - } else if ([[call method] isEqualToString:@"setCookie"]) { - [self setCookieForResult:result arguments:[call arguments]]; } else { result(FlutterMethodNotImplemented); } @@ -56,50 +46,4 @@ - (void)clearCookies:(FlutterResult)result { } } -- (void)setCookiesForData:(NSArray *)cookies { - for (id cookie in cookies) { - [self setCookieForData:cookie]; - } -} - -- (void)setCookieForData:(NSDictionary *)cookieData { - if (@available(iOS 11.0, *)) { - if (!_httpCookieStore) { - _httpCookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; - } - NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ - NSHTTPCookieName : cookieData[@"name"], - NSHTTPCookieValue : cookieData[@"value"], - NSHTTPCookieDomain : cookieData[@"domain"], - NSHTTPCookiePath : cookieData[@"path"], - }]; - [_httpCookieStore setCookie:cookie - completionHandler:^{ - }]; - } else { - NSLog(@"Setting cookies is not supported for Flutter WebViews prior to iOS 11."); - } -} - -- (void)setCookieForResult:(FlutterResult)result arguments:(NSDictionary *)arguments { - if (@available(iOS 11.0, *)) { - if (!_httpCookieStore) { - _httpCookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; - } - NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ - NSHTTPCookieName : arguments[@"name"], - NSHTTPCookieValue : arguments[@"value"], - NSHTTPCookieDomain : arguments[@"domain"], - NSHTTPCookiePath : arguments[@"path"], - }]; - [_httpCookieStore setCookie:cookie - completionHandler:^{ - result(nil); - }]; - } else { - NSLog(@"Setting cookies is not supported for Flutter WebViews prior to iOS 11."); - result(nil); - } -} - @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h deleted file mode 100644 index fecec4932570..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This header is available in the Test module. Import via "@import webview_flutter_wkwebview.Test;" - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface FLTCookieManager () - -@property(nonatomic, strong) - WKHTTPCookieStore *httpCookieStore API_AVAILABLE(macos(10.13), ios(11.0)); - -- (void)setCookieForResult:(FlutterResult)result arguments:(NSDictionary *)arguments; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m index 0d04d6c3a9ee..9f01416acc6a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m @@ -9,11 +9,10 @@ @implementation FLTWebViewFlutterPlugin + (void)registerWithRegistrar:(NSObject*)registrar { - [FLTCookieManager registerWithRegistrar:registrar]; FLTWebViewFactory* webviewFactory = - [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger - cookieManager:[FLTCookieManager instance]]; + [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger]; [registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"]; + [FLTCookieManager registerWithRegistrar:registrar]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h index 145a772c5015..6d8e463c7b13 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import #import #import @@ -32,8 +31,7 @@ NS_ASSUME_NONNULL_BEGIN @end @interface FLTWebViewFactory : NSObject -- (instancetype)initWithMessenger:(NSObject*)messenger - cookieManager:(FLTCookieManager*)cookieManager; +- (instancetype)initWithMessenger:(NSObject*)messenger; @end NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m index 696fde918be9..351d1ae58760 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m @@ -10,15 +10,12 @@ @implementation FLTWebViewFactory { NSObject* _messenger; - FLTCookieManager* _cookieManager; } -- (instancetype)initWithMessenger:(NSObject*)messenger - cookieManager:(FLTCookieManager*)cookieManager { +- (instancetype)initWithMessenger:(NSObject*)messenger { self = [super init]; if (self) { _messenger = messenger; - _cookieManager = cookieManager; } return self; } @@ -30,10 +27,6 @@ - (instancetype)initWithMessenger:(NSObject*)messenger - (NSObject*)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args { - if (@available(iOS 11.0, *)) { - [_cookieManager setCookiesForData:args[@"cookies"]]; - } - FLTWebViewController* webviewController = [[FLTWebViewController alloc] initWithFrame:frame viewIdentifier:viewId arguments:args diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap index 096507557688..fa5143060381 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap @@ -6,6 +6,5 @@ framework module webview_flutter_wkwebview { explicit module Test { header "FlutterWebView_Test.h" - header "FLTCookieManager_Test.h" } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart deleted file mode 100644 index f6dde177ecf6..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2013 The Flutter 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 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; - -/// Handles all cookie operations for the current platform. -class WebViewIOSCookieManager extends WebViewCookieManagerPlatform { - WebViewIOSCookieManager._(); - - static WebViewIOSCookieManager? _instance; - - /// Obtain a singleton instance for [WebViewiOSCookieManager]. - static WebViewIOSCookieManager get instance => - _instance ??= WebViewIOSCookieManager._(); - - @override - Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); - - @override - Future setCookie(WebViewCookie cookie) { - if (!_isValidPath(cookie.path)) { - throw ArgumentError( - 'The path property for the provided cookie was not given a legal value.'); - } - return MethodChannelWebViewPlatform.setCookie(cookie); - } - - bool _isValidPath(String path) { - // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 - for (final int char in path.codeUnits) { - if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { - return false; - } - } - return true; - } -} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart index a976bace2c75..bbec415dccd0 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart @@ -3,4 +3,3 @@ // found in the LICENSE file. export 'src/webview_cupertino.dart'; -export 'src/webview_ios_cookie_manager.dart'; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index aa52363dadf8..466c1a2a4fcd 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.5.0 +version: 2.4.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -18,9 +18,7 @@ flutter: dependencies: flutter: sdk: flutter -# TODO (BeMacized): Update dependency when platform interface has been updated and published. - webview_flutter_platform_interface: - path: ../webview_flutter_platform_interface + webview_flutter_platform_interface: ^1.3.0 dev_dependencies: flutter_driver: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart deleted file mode 100644 index 10d13e44873e..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2013 The Flutter 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 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'package:webview_flutter_wkwebview/src/webview_ios_cookie_manager.dart'; - -main() { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel cookieChannel = - MethodChannel('plugins.flutter.io/cookie_manager'); - final List log = []; - - cookieChannel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - - if (methodCall.method == 'clearCookies') { - return true; - } - - // Return null explicitly instead of relying on the implicit null - // returned by the method channel if no return statement is specified. - return null; - }); - - tearDown(() { - log.clear(); - }); - - test('clearCookies should call `clearCookies` on the method channel', - () async { - await WebViewIOSCookieManager.instance.clearCookies(); - expect( - log, - [ - isMethodCall( - 'clearCookies', - arguments: null, - ), - ], - ); - }); - - test('setCookie should call `setCookie` on the method channel', () async { - await WebViewIOSCookieManager.instance.setCookie( - WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev')); - expect( - log, - [ - isMethodCall( - 'setCookie', - arguments: { - 'name': 'foo', - 'value': 'bar', - 'domain': 'flutter.dev', - 'path': '/', - }, - ), - ], - ); - }); -} From 34c680eb1e7d0a5b025248a148c58008c6b97686 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 15:30:45 +0100 Subject: [PATCH 06/24] Update deprecation notice --- .../lib/src/platform_interface/webview_platform.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart index a105adc5fc45..65a02f59c8ad 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart @@ -60,7 +60,7 @@ abstract class WebViewPlatform { /// Clears all cookies for all [WebView] instances. /// /// Returns true if cookies were present before clearing, else false. - @Deprecated('Use `WebViewCookieManager.clearCookies` instead.') + @Deprecated('Use `WebViewCookieManagerPlatform.clearCookies` instead.') Future clearCookies() { throw UnimplementedError( "WebView clearCookies is not implemented on the current platform"); From 555ed5cec972ab4965d904de1f4dd6eeb52906a6 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 15:40:18 +0100 Subject: [PATCH 07/24] Added iOS implementations for new CookieManager --- .../webview_flutter_wkwebview/CHANGELOG.md | 5 +- .../analysis_options.yaml | 1 + .../webview_flutter_test.dart | 42 +++--- .../ios/Runner.xcodeproj/project.pbxproj | 73 +++++----- .../ios/RunnerTests/FLTCookieManagerTests.m | 127 ++++++++++++++++++ .../FLTWKNavigationDelegateTests.m | 1 + .../example/ios/RunnerTests/FLTWebViewTests.m | 26 +++- .../example/lib/main.dart | 54 ++++---- .../example/lib/navigation_request.dart | 2 +- .../example/lib/web_view.dart | 50 +++++-- .../example/pubspec.yaml | 4 +- .../ios/Classes/FLTCookieManager.h | 6 + .../ios/Classes/FLTCookieManager.m | 62 ++++++++- .../ios/Classes/FLTCookieManager_Test.h | 20 +++ .../ios/Classes/FLTWebViewFlutterPlugin.m | 5 +- .../ios/Classes/FlutterWebView.h | 4 +- .../ios/Classes/FlutterWebView.m | 9 +- .../ios/Classes/FlutterWebView.modulemap | 1 + .../lib/src/webview_ios_cookie_manager.dart | 38 ++++++ .../lib/webview_flutter_wkwebview.dart | 1 + .../webview_flutter_wkwebview/pubspec.yaml | 6 +- .../src/webview_ios_cookie_manager_test.dart | 64 +++++++++ 22 files changed, 493 insertions(+), 108 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/analysis_options.yaml create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index e9c59c26c3ce..c01cfc8e51e3 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,8 +1,7 @@ -## NEXT +## 2.5.0 -* Migrates from `analysis_options_legacy.yaml` to `analysis_options.yaml`. * Integration test fixes. -* Updates to webview_flutter_platform_interface version 1.5.2. +* Implemented new cookie manager for setting cookies and providing initial cookies. ## 2.4.0 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/analysis_options.yaml b/packages/webview_flutter/webview_flutter_wkwebview/analysis_options.yaml new file mode 100644 index 000000000000..5aeb4e7c5e21 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../../analysis_options_legacy.yaml diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart index 8806495e629c..00c7e998edc8 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart @@ -191,7 +191,7 @@ void main() { await onPageFinished.future; resizeButtonTapped = true; - await tester.tap(find.byKey(const ValueKey('resizeButton'))); + await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); expect(buttonTapResizeCompleter.future, completes); }); @@ -446,10 +446,10 @@ void main() { testWidgets('Video plays inline when allowsInlineMediaPlayback is true', (WidgetTester tester) async { - final Completer controllerCompleter = + Completer controllerCompleter = Completer(); - final Completer pageLoaded = Completer(); - final Completer videoPlaying = Completer(); + Completer pageLoaded = Completer(); + Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( @@ -480,7 +480,7 @@ void main() { ), ), ); - final WebViewController controller = await controllerCompleter.future; + WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. @@ -489,7 +489,7 @@ void main() { // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; - final String fullScreen = + String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(false)); }); @@ -497,10 +497,10 @@ void main() { testWidgets( 'Video plays full screen when allowsInlineMediaPlayback is false', (WidgetTester tester) async { - final Completer controllerCompleter = + Completer controllerCompleter = Completer(); - final Completer pageLoaded = Completer(); - final Completer videoPlaying = Completer(); + Completer pageLoaded = Completer(); + Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( @@ -531,7 +531,7 @@ void main() { ), ), ); - final WebViewController controller = await controllerCompleter.future; + WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. @@ -540,7 +540,7 @@ void main() { // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; - final String fullScreen = + String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(true)); }); @@ -718,7 +718,7 @@ void main() { }); testWidgets('getTitle', (WidgetTester tester) async { - const String getTitleTest = ''' + final String getTitleTest = ''' Some title @@ -768,7 +768,7 @@ void main() { group('Programmatic Scroll', () { testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { - const String scrollTestPage = ''' + final String scrollTestPage = ''' @@ -815,7 +815,7 @@ void main() { final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; - await tester.pumpAndSettle(const Duration(seconds: 3)); + await tester.pumpAndSettle(Duration(seconds: 3)); int scrollPosX = await controller.getScrollX(); int scrollPosY = await controller.getScrollY(); @@ -845,7 +845,7 @@ void main() { }); group('NavigationDelegate', () { - const String blankPage = ''; + final String blankPage = ""; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + base64Encode(const Utf8Encoder().convert(blankPage)); @@ -941,7 +941,7 @@ void main() { testWidgets( 'onWebResourceError only called for main frame', (WidgetTester tester) async { - const String iframeTest = ''' + final String iframeTest = ''' @@ -1169,13 +1169,11 @@ Future _runJavascriptReturningResult( if (defaultTargetPlatform == TargetPlatform.iOS) { return await controller.runJavascriptReturningResult(js); } - return jsonDecode(await controller.runJavascriptReturningResult(js)) - as String; + return jsonDecode(await controller.runJavascriptReturningResult(js)); } class ResizableWebView extends StatefulWidget { - const ResizableWebView( - {required this.onResize, required this.onPageFinished}); + ResizableWebView({required this.onResize, required this.onPageFinished}); final JavascriptMessageHandler onResize; final VoidCallback onPageFinished; @@ -1230,14 +1228,14 @@ class ResizableWebViewState extends State { ), ), TextButton( - key: const Key('resizeButton'), + key: Key('resizeButton'), onPressed: () { setState(() { webViewWidth += 100.0; webViewHeight += 100.0; }); }, - child: const Text('ResizeButton'), + child: Text('ResizeButton'), ), ], ), diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj index e292b1bd52fa..b681c4704ddd 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ @@ -16,8 +16,9 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */; }; + D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */; }; DAF0E91266956134538CC667 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 572FFC2B2BA326B420B22679 /* libPods-Runner.a */; }; + E43693B527512C0F00382F85 /* FLTCookieManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */; }; F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F76266057800028CB91 /* FLTWebViewUITests.m */; }; /* End PBXBuildFile section */ @@ -54,10 +55,11 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 572FFC2B2BA326B420B22679 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTWKNavigationDelegateTests.m; sourceTree = ""; }; 68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 68BDCAED23C3F7CB00D9C032 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -74,7 +76,7 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCookieManagerTests.m; sourceTree = ""; }; F7151F74266057800028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F76266057800028CB91 /* FLTWebViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTWebViewUITests.m; sourceTree = ""; }; F7151F78266057800028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -86,7 +88,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */, + D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -112,7 +114,7 @@ isa = PBXGroup; children = ( 572FFC2B2BA326B420B22679 /* libPods-Runner.a */, - 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */, + 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; @@ -123,6 +125,7 @@ 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */, 68BDCAF523C3F97800D9C032 /* FLTWebViewTests.m */, 68BDCAED23C3F7CB00D9C032 /* Info.plist */, + E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */, ); path = RunnerTests; sourceTree = ""; @@ -190,8 +193,8 @@ children = ( F7A1921261392D1CBDAEC2E8 /* Pods-Runner.debug.xcconfig */, B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */, - C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */, - 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */, + 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */, + 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -212,7 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */, + AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */, 68BDCAE523C3F7CB00D9C032 /* Sources */, 68BDCAE623C3F7CB00D9C032 /* Frameworks */, 68BDCAE723C3F7CB00D9C032 /* Resources */, @@ -277,13 +280,16 @@ ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 68BDCAE823C3F7CB00D9C032 = { + DevelopmentTeam = 7624MWN53C; ProvisioningStyle = Automatic; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = 7624MWN53C; }; F7151F73266057800028CB91 = { CreatedOnToolsVersion = 12.5; + DevelopmentTeam = 7624MWN53C; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; @@ -338,7 +344,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; + }; + 6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -353,28 +373,28 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Thin Binary"; + name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; - 6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */ = { + AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -389,27 +409,13 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -419,6 +425,7 @@ files = ( 334734012669319100DCC49E /* FLTWebViewTests.m in Sources */, 334734022669319400DCC49E /* FLTWKNavigationDelegateTests.m in Sources */, + E43693B527512C0F00382F85 /* FLTCookieManagerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -477,7 +484,7 @@ /* Begin XCBuildConfiguration section */ 68BDCAF023C3F7CB00D9C032 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -491,7 +498,7 @@ }; 68BDCAF123C3F7CB00D9C032 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m new file mode 100644 index 000000000000..837c0d892b64 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m @@ -0,0 +1,127 @@ +// Copyright 2013 The Flutter 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 Flutter; +@import XCTest; +@import webview_flutter_wkwebview; +@import webview_flutter_wkwebview.Test; + +// OCMock library doesn't generate a valid modulemap. +#import + +@interface FLTCookieManagerTests : XCTestCase + +@end + +@implementation FLTCookieManagerTests + +- (void)setUp { + [super setUp]; +} + +- (void)testSetCookieForResultSetsCookieAndReturnsResultOnIOS11 { + if (@available(iOS 11.0, *)) { + // Setup + XCTestExpectation *resultExpectation = [self + expectationWithDescription:@"Should return success result when setting cookie completes."]; + [FLTCookieManager.instance setHttpCookieStore:OCMClassMock(WKHTTPCookieStore.class)]; + NSDictionary *arguments = @{ + @"name" : @"foo", + @"value" : @"bar", + @"domain" : @"flutter.dev", + @"path" : @"/", + }; + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : arguments[@"name"], + NSHTTPCookieValue : arguments[@"value"], + NSHTTPCookieDomain : arguments[@"domain"], + NSHTTPCookiePath : arguments[@"path"], + }]; + [OCMStub([FLTCookieManager.instance.httpCookieStore setCookie:[OCMArg isEqual:cookie] + completionHandler:[OCMArg any]]) + andDo:^(NSInvocation *invocation) { + void (^setCookieCompletionHandler)(void); + [invocation getArgument:&setCookieCompletionHandler atIndex:3]; + setCookieCompletionHandler(); + }]; + // Run + [[FLTCookieManager instance] + setCookieForResult:^(id _Nullable result) { + XCTAssertNil(result); + [resultExpectation fulfill]; + } + arguments:arguments]; + // Verify + [self waitForExpectationsWithTimeout:30.0 handler:nil]; + } +} + +- (void)testSetCookieForDataSetsCookieOnIOS11 { + if (@available(iOS 11.0, *)) { + // Setup + WKHTTPCookieStore *mockHttpCookieStore = OCMClassMock(WKHTTPCookieStore.class); + [FLTCookieManager.instance setHttpCookieStore:mockHttpCookieStore]; + NSDictionary *cookieData = @{ + @"name" : @"foo", + @"value" : @"bar", + @"domain" : @"flutter.dev", + @"path" : @"/", + }; + // Run + [[FLTCookieManager instance] setCookieForData:cookieData]; + // Verify + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : cookieData[@"name"], + NSHTTPCookieValue : cookieData[@"value"], + NSHTTPCookieDomain : cookieData[@"domain"], + NSHTTPCookiePath : cookieData[@"path"], + }]; + OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie] + completionHandler:[OCMArg any]]); + } +} + +- (void)testSetCookiesForDataSetsCookiesOnIOS11 { + if (@available(iOS 11.0, *)) { + // Setup + WKHTTPCookieStore *mockHttpCookieStore = OCMClassMock(WKHTTPCookieStore.class); + [FLTCookieManager.instance setHttpCookieStore:mockHttpCookieStore]; + NSArray *cookieDatas = @[ + @{ + @"name" : @"foo1", + @"value" : @"bar1", + @"domain" : @"flutter.dev", + @"path" : @"/", + }, + @{ + @"name" : @"foo2", + @"value" : @"bar2", + @"domain" : @"flutter2.dev", + @"path" : @"/2", + } + ]; + // Run + [[FLTCookieManager instance] setCookiesForData:cookieDatas]; + // Verify + NSHTTPCookie *cookie1 = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : cookieDatas[0][@"name"], + NSHTTPCookieValue : cookieDatas[0][@"value"], + NSHTTPCookieDomain : cookieDatas[0][@"domain"], + NSHTTPCookiePath : cookieDatas[0][@"path"], + }]; + + OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie1] + completionHandler:[OCMArg any]]); + NSHTTPCookie *cookie2 = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : cookieDatas[1][@"name"], + NSHTTPCookieValue : cookieDatas[1][@"value"], + NSHTTPCookieDomain : cookieDatas[1][@"domain"], + NSHTTPCookiePath : cookieDatas[1][@"path"], + }]; + OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie2] + completionHandler:[OCMArg any]]); + } +} + +@end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m index a819a9b53d60..d39a9f203d70 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m @@ -5,6 +5,7 @@ @import Flutter; @import XCTest; @import webview_flutter_wkwebview; +@import webview_flutter_wkwebview.Test; // OCMock library doesn't generate a valid modulemap. #import diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m index a3c314a79d2a..6c7c6bffbc3f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m @@ -16,6 +16,8 @@ @interface FLTWebViewTests : XCTestCase @property(strong, nonatomic) NSObject *mockBinaryMessenger; +@property(strong, nonatomic) FLTCookieManager *mockCookieManager; + @end @implementation FLTWebViewTests @@ -23,6 +25,7 @@ @implementation FLTWebViewTests - (void)setUp { [super setUp]; self.mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + self.mockCookieManager = OCMClassMock(FLTCookieManager.class); } - (void)testCanInitFLTWebViewController { @@ -35,8 +38,8 @@ - (void)testCanInitFLTWebViewController { } - (void)testCanInitFLTWebViewFactory { - FLTWebViewFactory *factory = - [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger]; + FLTWebViewFactory *factory = [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger + cookieManager:self.mockCookieManager]; XCTAssertNotNil(factory); } @@ -648,7 +651,7 @@ - (void)testOnLoadUrlLoadsRequestWithSuccessResult { [self waitForExpectationsWithTimeout:30.0 handler:nil]; } -- (void)testOnLoadRequestReturnsErroResultForInvalidRequest { +- (void)testOnLoadRequestReturnsErrorResultForInvalidRequest { // Setup FLTWebViewController *controller = [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) @@ -697,4 +700,21 @@ - (void)testOnLoadRequestLoadsRequestWithSuccessResult { [self waitForExpectationsWithTimeout:30.0 handler:nil]; } +- (void)testCreateWithFrameShouldSetCookiesOnIOS11 { + if (@available(iOS 11, *)) { + // Setup + FLTWebViewFactory *factory = + [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger + cookieManager:self.mockCookieManager]; + NSArray *cookies = + @[ @{@"name" : @"foo", @"value" : @"bar", @"domain" : @"flutter.dev", @"path" : @"/"} ]; + // Run + [factory createWithFrame:CGRectMake(0, 0, 300, 400) + viewIdentifier:1 + arguments:@{@"cookies" : cookies}]; + // Verify + OCMVerify([_mockCookieManager setCookiesForData:cookies]); + } +} + @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart index df85d42bea8f..068087c5222e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -19,7 +19,7 @@ import 'navigation_request.dart'; import 'web_view.dart'; void main() { - runApp(const MaterialApp(home: _WebViewExample())); + runApp(MaterialApp(home: _WebViewExample())); } const String kNavigationExamplePage = ''' @@ -67,6 +67,11 @@ class _WebViewExampleState extends State<_WebViewExample> { final Completer _controller = Completer(); + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -80,9 +85,9 @@ class _WebViewExampleState extends State<_WebViewExample> { ), // We're using a Builder here so we have a context that is below the Scaffold // to allow calling Scaffold.of(context) so we can show a snackbar. - body: Builder(builder: (BuildContext context) { + body: Builder(builder: (context) { return WebView( - initialUrl: 'https://flutter.dev', + initialUrl: 'https://httpbin.org/anything', onWebViewCreated: (WebViewController controller) { _controller.complete(controller); }, @@ -125,7 +130,7 @@ class _WebViewExampleState extends State<_WebViewExample> { } Set _createJavascriptChannels(BuildContext context) { - return { + return { JavascriptChannel( name: 'Snackbar', onMessageReceived: (JavascriptMessage message) { @@ -149,7 +154,7 @@ enum _MenuOptions { } class _SampleMenu extends StatelessWidget { - const _SampleMenu(this.controller); + _SampleMenu(this.controller); final Future controller; @@ -242,7 +247,7 @@ class _SampleMenu extends StatelessWidget { ); } - Future _onShowUserAgent( + void _onShowUserAgent( WebViewController controller, BuildContext context) async { // Send a message with the user agent string to the Snackbar JavaScript channel we registered // with the WebView. @@ -250,7 +255,7 @@ class _SampleMenu extends StatelessWidget { 'Snackbar.postMessage("User Agent: " + navigator.userAgent);'); } - Future _onListCookies( + void _onListCookies( WebViewController controller, BuildContext context) async { final String cookies = await controller.runJavascriptReturningResult('document.cookie'); @@ -266,8 +271,7 @@ class _SampleMenu extends StatelessWidget { )); } - Future _onAddToCache( - WebViewController controller, BuildContext context) async { + void _onAddToCache(WebViewController controller, BuildContext context) async { await controller.runJavascript( 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";'); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( @@ -275,22 +279,20 @@ class _SampleMenu extends StatelessWidget { )); } - Future _onListCache( - WebViewController controller, BuildContext context) async { + void _onListCache(WebViewController controller, BuildContext context) async { await controller.runJavascript('caches.keys()' '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' '.then((caches) => Snackbar.postMessage(caches))'); } - Future _onClearCache( - WebViewController controller, BuildContext context) async { + void _onClearCache(WebViewController controller, BuildContext context) async { await controller.clearCache(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Cache cleared.'), + content: Text("Cache cleared."), )); } - Future _onClearCookies( + void _onClearCookies( WebViewController controller, BuildContext context) async { final bool hadCookies = await WebView.platform.clearCookies(); String message = 'There were cookies. Now, they are gone!'; @@ -302,31 +304,31 @@ class _SampleMenu extends StatelessWidget { )); } - Future _onNavigationDelegateExample( + void _onNavigationDelegateExample( WebViewController controller, BuildContext context) async { final String contentBase64 = base64Encode(const Utf8Encoder().convert(kNavigationExamplePage)); await controller.loadUrl('data:text/html;base64,$contentBase64'); } - Future _onLoadLocalFileExample( + void _onLoadLocalFileExample( WebViewController controller, BuildContext context) async { - final String pathToIndex = await _prepareLocalFile(); + String pathToIndex = await _prepareLocalFile(); await controller.loadFile(pathToIndex); } - Future _onLoadHtmlStringExample( + void _onLoadHtmlStringExample( WebViewController controller, BuildContext context) async { await controller.loadHtmlString(kLocalFileExamplePage); } - Future _onDoPostRequest( + void _onDoPostRequest( WebViewController controller, BuildContext context) async { - final WebViewRequest request = WebViewRequest( + WebViewRequest request = WebViewRequest( uri: Uri.parse('https://httpbin.org/post'), method: WebViewRequestMethod.post, - headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, + headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, body: Uint8List.fromList('Test Body'.codeUnits), ); await controller.loadRequest(request); @@ -348,7 +350,7 @@ class _SampleMenu extends StatelessWidget { static Future _prepareLocalFile() async { final String tmpDir = (await getTemporaryDirectory()).path; - final File indexFile = File('$tmpDir/www/index.html'); + File indexFile = File('$tmpDir/www/index.html'); await Directory('$tmpDir/www').create(recursive: true); await indexFile.writeAsString(kLocalFileExamplePage); @@ -385,7 +387,7 @@ class _NavigationControls extends StatelessWidget { } else { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( - const SnackBar(content: Text('No back history item')), + const SnackBar(content: Text("No back history item")), ); return; } @@ -402,7 +404,7 @@ class _NavigationControls extends StatelessWidget { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( const SnackBar( - content: Text('No forward history item')), + content: Text("No forward history item")), ); return; } @@ -424,4 +426,4 @@ class _NavigationControls extends StatelessWidget { } /// Callback type for handling messages sent from JavaScript running in a web view. -typedef JavascriptMessageHandler = void Function(JavascriptMessage message); +typedef void JavascriptMessageHandler(JavascriptMessage message); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart index 2f6d7c9f8cdd..c1ff8dc5a690 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart @@ -14,6 +14,6 @@ class NavigationRequest { @override String toString() { - return 'NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; + return '$runtimeType(url: $url, isForMainFrame: $isForMainFrame)'; } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart index ae540ae23c2d..f7547aafa0ae 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -15,7 +16,7 @@ import 'navigation_request.dart'; /// Optional callback invoked when a web view is first created. [controller] is /// the [WebViewController] for the created web view. -typedef WebViewCreatedCallback = void Function(WebViewController controller); +typedef void WebViewCreatedCallback(WebViewController controller); /// Decides how to handle a specific navigation request. /// @@ -23,20 +24,20 @@ typedef WebViewCreatedCallback = void Function(WebViewController controller); /// `navigation` should be handled. /// /// See also: [WebView.navigationDelegate]. -typedef NavigationDelegate = FutureOr Function( +typedef FutureOr NavigationDelegate( NavigationRequest navigation); /// Signature for when a [WebView] has started loading a page. -typedef PageStartedCallback = void Function(String url); +typedef void PageStartedCallback(String url); /// Signature for when a [WebView] has finished loading a page. -typedef PageFinishedCallback = void Function(String url); +typedef void PageFinishedCallback(String url); /// Signature for when a [WebView] is loading a page. -typedef PageLoadingCallback = void Function(int progress); +typedef void PageLoadingCallback(int progress); /// Signature for when a [WebView] has failed to load a resource. -typedef WebResourceErrorCallback = void Function(WebResourceError error); +typedef void WebResourceErrorCallback(WebResourceError error); /// A web view widget for showing html content. /// @@ -54,6 +55,7 @@ class WebView extends StatefulWidget { Key? key, this.onWebViewCreated, this.initialUrl, + this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, @@ -94,6 +96,9 @@ class WebView extends StatefulWidget { /// The initial URL to load. final String? initialUrl; + /// The initial cookies to set. + final List initialCookies; + /// Whether JavaScript execution is enabled. final JavascriptMode javascriptMode; @@ -259,7 +264,7 @@ class _WebViewState extends State { context: context, onWebViewPlatformCreated: (WebViewPlatformController? webViewPlatformController) { - final WebViewController controller = WebViewController._( + WebViewController controller = WebViewController._( widget, webViewPlatformController!, _javascriptChannelRegistry, @@ -278,6 +283,7 @@ class _WebViewState extends State { _javascriptChannelRegistry.channels.keys.toSet(), autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, + cookies: widget.initialCookies, ), javascriptChannelRegistry: _javascriptChannelRegistry, ); @@ -547,7 +553,7 @@ class WebViewController { bool? hasNavigationDelegate; bool? hasProgressTracking; bool? debuggingEnabled; - WebSetting userAgent = const WebSetting.absent(); + WebSetting userAgent = WebSetting.absent(); if (currentValue.javascriptMode != newValue.javascriptMode) { javascriptMode = newValue.javascriptMode; } @@ -645,10 +651,36 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { } } - @override void onWebResourceError(WebResourceError error) { if (_webView.onWebResourceError != null) { _webView.onWebResourceError!(error); } } } + +class WebViewCookieManager extends WebViewCookieManagerPlatform { + static WebViewCookieManager? _instance; + + static WebViewCookieManager get instance => + _instance ??= WebViewCookieManager._(); + + WebViewCookieManager._(); + + @override + Future clearCookies() async { + if (Platform.isIOS) { + return WebViewIOSCookieManager.instance.clearCookies(); + } else { + return super.clearCookies(); + } + } + + @override + Future setCookie(WebViewCookie cookie) { + if (Platform.isIOS) { + return WebViewIOSCookieManager.instance.setCookie(cookie); + } else { + return super.setCookie(cookie); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml index 2888cddac552..c8001c8ffde4 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml @@ -21,10 +21,10 @@ dependencies: dev_dependencies: espresso: ^0.1.0+2 - flutter_driver: - sdk: flutter flutter_test: sdk: flutter + flutter_driver: + sdk: flutter integration_test: sdk: flutter pedantic: ^1.10.0 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h index 8fe331875250..dc5b8f589ade 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h @@ -9,6 +9,12 @@ NS_ASSUME_NONNULL_BEGIN @interface FLTCookieManager : NSObject ++ (FLTCookieManager*)instance; + +- (void)setCookiesForData:(NSArray*)cookies; + +- (void)setCookieForData:(NSDictionary*)cookie; + @end NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m index f4783ffb4123..39976d11153e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m @@ -3,22 +3,32 @@ // found in the LICENSE file. #import "FLTCookieManager.h" +#import "FLTCookieManager_Test.h" @implementation FLTCookieManager { + WKHTTPCookieStore *_httpCookieStore API_AVAILABLE(macos(10.13), ios(11.0)); } -+ (void)registerWithRegistrar:(NSObject *)registrar { - FLTCookieManager *instance = [[FLTCookieManager alloc] init]; ++ (FLTCookieManager *)instance { + static FLTCookieManager *instance = nil; + if (instance == nil) { + instance = [[FLTCookieManager alloc] init]; + } + return instance; +} ++ (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/cookie_manager" binaryMessenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:channel]; + [registrar addMethodCallDelegate:[self instance] channel:channel]; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([[call method] isEqualToString:@"clearCookies"]) { [self clearCookies:result]; + } else if ([[call method] isEqualToString:@"setCookie"]) { + [self setCookieForResult:result arguments:[call arguments]]; } else { result(FlutterMethodNotImplemented); } @@ -46,4 +56,50 @@ - (void)clearCookies:(FlutterResult)result { } } +- (void)setCookiesForData:(NSArray *)cookies { + for (id cookie in cookies) { + [self setCookieForData:cookie]; + } +} + +- (void)setCookieForData:(NSDictionary *)cookieData { + if (@available(iOS 11.0, *)) { + if (!_httpCookieStore) { + _httpCookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; + } + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : cookieData[@"name"], + NSHTTPCookieValue : cookieData[@"value"], + NSHTTPCookieDomain : cookieData[@"domain"], + NSHTTPCookiePath : cookieData[@"path"], + }]; + [_httpCookieStore setCookie:cookie + completionHandler:^{ + }]; + } else { + NSLog(@"Setting cookies is not supported for Flutter WebViews prior to iOS 11."); + } +} + +- (void)setCookieForResult:(FlutterResult)result arguments:(NSDictionary *)arguments { + if (@available(iOS 11.0, *)) { + if (!_httpCookieStore) { + _httpCookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore]; + } + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ + NSHTTPCookieName : arguments[@"name"], + NSHTTPCookieValue : arguments[@"value"], + NSHTTPCookieDomain : arguments[@"domain"], + NSHTTPCookiePath : arguments[@"path"], + }]; + [_httpCookieStore setCookie:cookie + completionHandler:^{ + result(nil); + }]; + } else { + NSLog(@"Setting cookies is not supported for Flutter WebViews prior to iOS 11."); + result(nil); + } +} + @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h new file mode 100644 index 000000000000..fecec4932570 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h @@ -0,0 +1,20 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header is available in the Test module. Import via "@import webview_flutter_wkwebview.Test;" + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FLTCookieManager () + +@property(nonatomic, strong) + WKHTTPCookieStore *httpCookieStore API_AVAILABLE(macos(10.13), ios(11.0)); + +- (void)setCookieForResult:(FlutterResult)result arguments:(NSDictionary *)arguments; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m index 9f01416acc6a..0d04d6c3a9ee 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m @@ -9,10 +9,11 @@ @implementation FLTWebViewFlutterPlugin + (void)registerWithRegistrar:(NSObject*)registrar { + [FLTCookieManager registerWithRegistrar:registrar]; FLTWebViewFactory* webviewFactory = - [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger]; + [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger + cookieManager:[FLTCookieManager instance]]; [registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"]; - [FLTCookieManager registerWithRegistrar:registrar]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h index 6d8e463c7b13..145a772c5015 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import #import #import @@ -31,7 +32,8 @@ NS_ASSUME_NONNULL_BEGIN @end @interface FLTWebViewFactory : NSObject -- (instancetype)initWithMessenger:(NSObject*)messenger; +- (instancetype)initWithMessenger:(NSObject*)messenger + cookieManager:(FLTCookieManager*)cookieManager; @end NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m index 351d1ae58760..696fde918be9 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m @@ -10,12 +10,15 @@ @implementation FLTWebViewFactory { NSObject* _messenger; + FLTCookieManager* _cookieManager; } -- (instancetype)initWithMessenger:(NSObject*)messenger { +- (instancetype)initWithMessenger:(NSObject*)messenger + cookieManager:(FLTCookieManager*)cookieManager { self = [super init]; if (self) { _messenger = messenger; + _cookieManager = cookieManager; } return self; } @@ -27,6 +30,10 @@ - (instancetype)initWithMessenger:(NSObject*)messenger { - (NSObject*)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args { + if (@available(iOS 11.0, *)) { + [_cookieManager setCookiesForData:args[@"cookies"]]; + } + FLTWebViewController* webviewController = [[FLTWebViewController alloc] initWithFrame:frame viewIdentifier:viewId arguments:args diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap index fa5143060381..096507557688 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap @@ -6,5 +6,6 @@ framework module webview_flutter_wkwebview { explicit module Test { header "FlutterWebView_Test.h" + header "FLTCookieManager_Test.h" } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart new file mode 100644 index 000000000000..f6dde177ecf6 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter 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 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +/// Handles all cookie operations for the current platform. +class WebViewIOSCookieManager extends WebViewCookieManagerPlatform { + WebViewIOSCookieManager._(); + + static WebViewIOSCookieManager? _instance; + + /// Obtain a singleton instance for [WebViewiOSCookieManager]. + static WebViewIOSCookieManager get instance => + _instance ??= WebViewIOSCookieManager._(); + + @override + Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); + + @override + Future setCookie(WebViewCookie cookie) { + if (!_isValidPath(cookie.path)) { + throw ArgumentError( + 'The path property for the provided cookie was not given a legal value.'); + } + return MethodChannelWebViewPlatform.setCookie(cookie); + } + + bool _isValidPath(String path) { + // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + for (final int char in path.codeUnits) { + if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { + return false; + } + } + return true; + } +} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart index bbec415dccd0..a976bace2c75 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart @@ -3,3 +3,4 @@ // found in the LICENSE file. export 'src/webview_cupertino.dart'; +export 'src/webview_ios_cookie_manager.dart'; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index a152e2ba2882..f6da86ddb636 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.4.0 +version: 2.5.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -18,7 +18,9 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_platform_interface: ^1.5.2 +# TODO (BeMacized): Update dependency when changes have been merged and published. + webview_flutter_platform_interface: + path: ../webview_flutter_platform_interface dev_dependencies: flutter_driver: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart new file mode 100644 index 000000000000..10d13e44873e --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart @@ -0,0 +1,64 @@ +// Copyright 2013 The Flutter 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 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_wkwebview/src/webview_ios_cookie_manager.dart'; + +main() { + TestWidgetsFlutterBinding.ensureInitialized(); + const MethodChannel cookieChannel = + MethodChannel('plugins.flutter.io/cookie_manager'); + final List log = []; + + cookieChannel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + + if (methodCall.method == 'clearCookies') { + return true; + } + + // Return null explicitly instead of relying on the implicit null + // returned by the method channel if no return statement is specified. + return null; + }); + + tearDown(() { + log.clear(); + }); + + test('clearCookies should call `clearCookies` on the method channel', + () async { + await WebViewIOSCookieManager.instance.clearCookies(); + expect( + log, + [ + isMethodCall( + 'clearCookies', + arguments: null, + ), + ], + ); + }); + + test('setCookie should call `setCookie` on the method channel', () async { + await WebViewIOSCookieManager.instance.setCookie( + WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev')); + expect( + log, + [ + isMethodCall( + 'setCookie', + arguments: { + 'name': 'foo', + 'value': 'bar', + 'domain': 'flutter.dev', + 'path': '/', + }, + ), + ], + ); + }); +} From 0d0f2816ecb78d316628432f34c65f30a8b3de8a Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 16:05:11 +0100 Subject: [PATCH 08/24] Fix analysis issues --- .../webview_method_channel.dart | 5 +++-- .../platform_interface.dart | 2 +- .../platform_interface/webview_platform.dart | 1 - .../lib/src/types/creation_params.dart | 2 +- .../lib/src/types/types.dart | 2 +- .../lib/src/types/webview_cookie.dart | 21 ++++++++++++------- .../webview_method_channel_test.dart | 6 +++--- .../webview_cookie_manager_test.dart | 2 +- .../test/src/types/webview_cookie_test.dart | 2 +- 9 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart index 45c73cf6f9d1..61e0dd7e247a 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart @@ -266,8 +266,9 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { 'userAgent': creationParams.userAgent, 'autoMediaPlaybackPolicy': creationParams.autoMediaPlaybackPolicy.index, 'usesHybridComposition': usesHybridComposition, - 'cookies': - creationParams.cookies.map((cookie) => cookie.toJson()).toList() + 'cookies': creationParams.cookies + .map((WebViewCookie cookie) => cookie.toJson()) + .toList() }; } } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/platform_interface.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/platform_interface.dart index be42dcbf7c54..a6967a5410f4 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/platform_interface.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/platform_interface.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. export 'javascript_channel_registry.dart'; +export 'webview_cookie_manager.dart'; export 'webview_platform.dart'; export 'webview_platform_callbacks_handler.dart'; export 'webview_platform_controller.dart'; -export 'webview_cookie_manager.dart'; diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart index 9af4493d3174..a472f00d4bd2 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart @@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:webview_flutter_platform_interface/src/platform_interface/javascript_channel_registry.dart'; -import 'package:webview_flutter_platform_interface/src/types/webview_cookie.dart'; import '../types/types.dart'; import 'webview_platform_callbacks_handler.dart'; diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart index 477878b16b7b..e9f5ef598b1d 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart @@ -61,6 +61,6 @@ class CreationParams { @override String toString() { - return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent, cookies: $cookies)'; + return 'CreationParams(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent, cookies: $cookies)'; } } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/types.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/types.dart index 9956c9250602..f2bcf19f42fd 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/types.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/types.dart @@ -10,5 +10,5 @@ export 'javascript_mode.dart'; export 'web_resource_error.dart'; export 'web_resource_error_type.dart'; export 'web_settings.dart'; -export 'webview_request.dart'; export 'webview_cookie.dart'; +export 'webview_request.dart'; diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart index 5725462f5956..c50b3da5b68f 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart @@ -6,6 +6,13 @@ /// /// The `autoMediaPlaybackPolicy` parameter must not be null. class WebViewCookie { + /// Construct a new [WebViewCookie]. + const WebViewCookie( + {required this.name, + required this.value, + required this.domain, + this.path = '/'}); + /// The name of the cookie. /// /// Its value should match "cookie-name" in RFC6265bis: @@ -30,15 +37,13 @@ class WebViewCookie { /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String path; - /// Construct a new [WebViewCookie]. - const WebViewCookie( - {required this.name, - required this.value, - required this.domain, - this.path = '/'}); - /// Serialize a [WebViewCookie] to a Map. Map toJson() { - return {'name': name, 'value': value, 'domain': domain, 'path': path}; + return { + 'name': name, + 'value': value, + 'domain': domain, + 'path': path + }; } } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart b/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart index d57366e0455a..0432373fc8ac 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart @@ -592,15 +592,15 @@ void main() { }); test('setCookie', () async { - await MethodChannelWebViewPlatform.setCookie( - WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev')); + await MethodChannelWebViewPlatform.setCookie(const WebViewCookie( + name: 'foo', value: 'bar', domain: 'flutter.dev')); expect( log, [ isMethodCall( 'setCookie', - arguments: { + arguments: { 'name': 'foo', 'value': 'bar', 'domain': 'flutter.dev', diff --git a/packages/webview_flutter/webview_flutter_platform_interface/test/src/platform_interface/webview_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_platform_interface/test/src/platform_interface/webview_cookie_manager_test.dart index c03b7238415b..e0aae2146abc 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/test/src/platform_interface/webview_cookie_manager_test.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/test/src/platform_interface/webview_cookie_manager_test.dart @@ -18,7 +18,7 @@ void main() { }); test('setCookie should throw UnimplementedError', () { - WebViewCookie cookie = + const WebViewCookie cookie = WebViewCookie(domain: 'flutter.dev', name: 'foo', value: 'bar'); expect(() => cookieManager!.setCookie(cookie), throwsUnimplementedError); }); diff --git a/packages/webview_flutter/webview_flutter_platform_interface/test/src/types/webview_cookie_test.dart b/packages/webview_flutter/webview_flutter_platform_interface/test/src/types/webview_cookie_test.dart index 79db8a431dc0..f058b8649b96 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/test/src/types/webview_cookie_test.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/test/src/types/webview_cookie_test.dart @@ -10,7 +10,7 @@ void main() { WebViewCookie cookie; Map serializedCookie; // Test serialization - cookie = WebViewCookie( + cookie = const WebViewCookie( name: 'foo', value: 'bar', domain: 'example.com', path: '/test'); serializedCookie = cookie.toJson(); expect(serializedCookie['name'], 'foo'); From cbdba1ca40b2ff1ace32ca9795faf6d446d39bbf Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 16:09:02 +0100 Subject: [PATCH 09/24] Partially fix merge error --- .../webview_flutter_wkwebview/analysis_options.yaml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 packages/webview_flutter/webview_flutter_wkwebview/analysis_options.yaml diff --git a/packages/webview_flutter/webview_flutter_wkwebview/analysis_options.yaml b/packages/webview_flutter/webview_flutter_wkwebview/analysis_options.yaml deleted file mode 100644 index 5aeb4e7c5e21..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: ../../../analysis_options_legacy.yaml From 490da19421cbe9cbef9df584be205db78a0c9e2b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 19:51:46 +0100 Subject: [PATCH 10/24] Enforce extending class for cookie manager platform interface --- .../webview_cookie_manager.dart | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_cookie_manager.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_cookie_manager.dart index 8fb087d2ac9e..d960926e419c 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_cookie_manager.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_cookie_manager.dart @@ -2,12 +2,38 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:webview_flutter_platform_interface/src/types/webview_cookie.dart'; /// Interface for a platform implementation of a cookie manager. /// -/// This interface should only be extended, not implemented, as more methods may be added later. -abstract class WebViewCookieManagerPlatform { +/// Platform implementations 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 +/// [WebViewCookieManagerPlatform] methods. +abstract class WebViewCookieManagerPlatform extends PlatformInterface { + /// Constructs a WebViewCookieManagerPlatform. + WebViewCookieManagerPlatform() : super(token: _token); + + static final Object _token = Object(); + + static WebViewCookieManagerPlatform? _instance; + + /// The instance of [WebViewCookieManagerPlatform] to use. + static WebViewCookieManagerPlatform? get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [WebViewCookieManagerPlatform] when they register themselves. + static set instance(WebViewCookieManagerPlatform? instance) { + if (instance == null) { + throw AssertionError( + 'Platform interfaces can only be set to a non-null instance'); + } + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + /// Clears all cookies for all [WebView] instances. /// /// Returns true if cookies were present before clearing, else false. From 8eb7bf5707787dfb342814411f7574126a836897 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 19:52:28 +0100 Subject: [PATCH 11/24] Update to match platform interface changes --- .../CookieManagerHostApiImpl.java | 1 - .../example/lib/web_view.dart | 27 ++++++------------- .../lib/webview_android.dart | 2 +- .../lib/webview_android_cookie_manager.dart | 8 ------ .../lib/webview_android_widget.dart | 2 +- .../webview_android_cookie_manager_test.dart | 6 ++--- 6 files changed, 13 insertions(+), 33 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java index 337301e3453d..09c35d0329fb 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java @@ -3,7 +3,6 @@ import android.os.Build; import android.webkit.CookieManager; - class CookieManagerHostApiImpl implements GeneratedAndroidWebView.CookieManagerHostApi { @Override public void clearCookies(GeneratedAndroidWebView.Result result) { diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart index 1e54ae63c7cf..6b97affed0fe 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart @@ -639,29 +639,18 @@ WebSettings _webSettingsFromWidget(WebView widget) { ); } +/// App-facing cookie manager that exposes the correct platform implementation. class WebViewCookieManager extends WebViewCookieManagerPlatform { - static WebViewCookieManager? _instance; - - static WebViewCookieManager get instance => - _instance ??= WebViewCookieManager._(); - WebViewCookieManager._(); - @override - Future clearCookies() async { - if (Platform.isAndroid) { - return WebViewAndroidCookieManager.instance.clearCookies(); - } else { - return super.clearCookies(); - } - } - - @override - Future setCookie(WebViewCookie cookie) { - if (Platform.isAndroid) { - return WebViewAndroidCookieManager.instance.setCookie(cookie); + /// Returns an instance of the cookie manager for the current platform. + static WebViewCookieManagerPlatform get instance { + if (WebViewCookieManagerPlatform.instance == null && Platform.isAndroid) { + WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager(); } else { - return super.setCookie(cookie); + throw AssertionError( + 'This platform is currently unsupported for webview_flutter_android.'); } + return WebViewCookieManagerPlatform.instance!; } } diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart index 2d1fe1433dca..f1e6551a4109 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart @@ -67,5 +67,5 @@ class AndroidWebView implements WebViewPlatform { @override Future clearCookies() => - WebViewAndroidCookieManager.instance.clearCookies(); + WebViewCookieManagerPlatform.instance!.clearCookies(); } diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart index 62be234dc4db..bba75ff4f613 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart @@ -8,14 +8,6 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_inte /// Handles all cookie operations for the current platform. class WebViewAndroidCookieManager extends WebViewCookieManagerPlatform { - WebViewAndroidCookieManager._(); - - static WebViewAndroidCookieManager? _instance; - - /// Obtain a singleton instance for [WebViewAndroidCookieManager]. - static WebViewAndroidCookieManager get instance => - _instance ??= WebViewAndroidCookieManager._(); - @override Future clearCookies() => android_webview.CookieManager.instance.clearCookies(); diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index a841984d47c3..4b83f54e1566 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -283,7 +283,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { addJavascriptChannels(creationParams.javascriptChannelNames); creationParams.cookies - .forEach(WebViewAndroidCookieManager.instance.setCookie); + .forEach(WebViewCookieManagerPlatform.instance!.setCookie); } Future _setHasProgressTracking(bool hasProgressTracking) async { diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart index 5818433ef33f..4f274ff4499f 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart @@ -23,13 +23,13 @@ void main() { test('clearCookies should call android_webview.clearCookies', () { when(android_webview.CookieManager.instance.clearCookies()) .thenAnswer((_) => Future.value(true)); - WebViewAndroidCookieManager.instance.clearCookies(); + WebViewAndroidCookieManager().clearCookies(); verify(android_webview.CookieManager.instance.clearCookies()); }); test('setCookie should throw ArgumentError for cookie with invalid path', () { expect( - () => WebViewAndroidCookieManager.instance.setCookie(const WebViewCookie( + () => WebViewAndroidCookieManager().setCookie(const WebViewCookie( name: 'foo', value: 'bar', domain: 'flutter.dev', @@ -42,7 +42,7 @@ void main() { test( 'setCookie should call android_webview.csetCookie with properly formatted cookie value', () { - WebViewAndroidCookieManager.instance.setCookie(const WebViewCookie( + WebViewAndroidCookieManager().setCookie(const WebViewCookie( name: 'foo&', value: 'bar@', domain: 'flutter.dev', From eb66b7a4268e3637137edf7c293b7a84ee52d26d Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 29 Nov 2021 19:53:08 +0100 Subject: [PATCH 12/24] Update to match platform interface changes --- .../example/lib/main.dart | 18 ++++++++++++- .../example/lib/web_view.dart | 27 ++++++------------- .../lib/src/webview_cupertino.dart | 3 ++- ...ger.dart => wkwebview_cookie_manager.dart} | 10 +------ .../lib/webview_flutter_wkwebview.dart | 2 +- ...art => wkwebview_cookie_manager_test.dart} | 9 ++++--- 6 files changed, 34 insertions(+), 35 deletions(-) rename packages/webview_flutter/webview_flutter_wkwebview/lib/src/{webview_ios_cookie_manager.dart => wkwebview_cookie_manager.dart} (76%) rename packages/webview_flutter/webview_flutter_wkwebview/test/src/{webview_ios_cookie_manager_test.dart => wkwebview_cookie_manager_test.dart} (85%) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart index 068087c5222e..46d1954fff8a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -87,7 +87,7 @@ class _WebViewExampleState extends State<_WebViewExample> { // to allow calling Scaffold.of(context) so we can show a snackbar. body: Builder(builder: (context) { return WebView( - initialUrl: 'https://httpbin.org/anything', + initialUrl: 'https://flutter.dev/', onWebViewCreated: (WebViewController controller) { _controller.complete(controller); }, @@ -151,6 +151,7 @@ enum _MenuOptions { loadLocalFile, loadHtmlString, doPostRequest, + setCookie, } class _SampleMenu extends StatelessWidget { @@ -197,6 +198,9 @@ class _SampleMenu extends StatelessWidget { case _MenuOptions.doPostRequest: _onDoPostRequest(controller.data!, context); break; + case _MenuOptions.setCookie: + _onSetCookie(controller.data!, context); + break; } }, itemBuilder: (BuildContext context) => >[ @@ -241,6 +245,10 @@ class _SampleMenu extends StatelessWidget { value: _MenuOptions.doPostRequest, child: Text('Post Request'), ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.setCookie, + child: Text('Set Cookie'), + ), ], ); }, @@ -334,6 +342,14 @@ class _SampleMenu extends StatelessWidget { await controller.loadRequest(request); } + void _onSetCookie(WebViewController controller, BuildContext context) async { + await WebViewCookieManager.instance.setCookie( + const WebViewCookie( + name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'), + ); + await controller.loadUrl('https://httpbin.org/anything'); + } + Widget _getCookieList(String cookies) { if (cookies == null || cookies == '""') { return Container(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart index f7547aafa0ae..6299f6b34821 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart @@ -658,29 +658,18 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { } } +/// App-facing cookie manager that exposes the correct platform implementation. class WebViewCookieManager extends WebViewCookieManagerPlatform { - static WebViewCookieManager? _instance; - - static WebViewCookieManager get instance => - _instance ??= WebViewCookieManager._(); - WebViewCookieManager._(); - @override - Future clearCookies() async { - if (Platform.isIOS) { - return WebViewIOSCookieManager.instance.clearCookies(); - } else { - return super.clearCookies(); - } - } - - @override - Future setCookie(WebViewCookie cookie) { - if (Platform.isIOS) { - return WebViewIOSCookieManager.instance.setCookie(cookie); + /// Returns an instance of the cookie manager for the current platform. + static WebViewCookieManagerPlatform get instance { + if (WebViewCookieManagerPlatform.instance == null && Platform.isIOS) { + WebViewCookieManagerPlatform.instance = WKWebViewCookieManager(); } else { - return super.setCookie(cookie); + throw AssertionError( + 'This platform is currently unsupported for webview_flutter_wkwebview.'); } + return WebViewCookieManagerPlatform.instance!; } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart index 05b79d0a72e4..e4292e28e50e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart @@ -45,5 +45,6 @@ class CupertinoWebView implements WebViewPlatform { } @override - Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); + Future clearCookies() => + WebViewCookieManagerPlatform.instance!.clearCookies(); } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/wkwebview_cookie_manager.dart similarity index 76% rename from packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart rename to packages/webview_flutter/webview_flutter_wkwebview/lib/src/wkwebview_cookie_manager.dart index f6dde177ecf6..d460ce02ab68 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_ios_cookie_manager.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/wkwebview_cookie_manager.dart @@ -5,15 +5,7 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; /// Handles all cookie operations for the current platform. -class WebViewIOSCookieManager extends WebViewCookieManagerPlatform { - WebViewIOSCookieManager._(); - - static WebViewIOSCookieManager? _instance; - - /// Obtain a singleton instance for [WebViewiOSCookieManager]. - static WebViewIOSCookieManager get instance => - _instance ??= WebViewIOSCookieManager._(); - +class WKWebViewCookieManager extends WebViewCookieManagerPlatform { @override Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart index a976bace2c75..f647ab38a41b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart @@ -3,4 +3,4 @@ // found in the LICENSE file. export 'src/webview_cupertino.dart'; -export 'src/webview_ios_cookie_manager.dart'; +export 'src/wkwebview_cookie_manager.dart'; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart similarity index 85% rename from packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart rename to packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart index 10d13e44873e..a6e5d5243005 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/webview_ios_cookie_manager_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart @@ -5,7 +5,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'package:webview_flutter_wkwebview/src/webview_ios_cookie_manager.dart'; +import 'package:webview_flutter_wkwebview/src/wkwebview_cookie_manager.dart'; main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -31,7 +31,7 @@ main() { test('clearCookies should call `clearCookies` on the method channel', () async { - await WebViewIOSCookieManager.instance.clearCookies(); + await WKWebViewCookieManager().clearCookies(); expect( log, [ @@ -44,8 +44,9 @@ main() { }); test('setCookie should call `setCookie` on the method channel', () async { - await WebViewIOSCookieManager.instance.setCookie( - WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev')); + await WKWebViewCookieManager().setCookie( + const WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'), + ); expect( log, [ From 6f9742ad6f127299458552478ac9a280bb992b41 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 30 Nov 2021 14:29:28 +0100 Subject: [PATCH 13/24] Process PR feedback --- .../lib/src/types/webview_cookie.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart index c50b3da5b68f..64142929d04d 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart @@ -2,42 +2,42 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -/// Configuration to use when creating a new [WebViewPlatformController]. -/// -/// The `autoMediaPlaybackPolicy` parameter must not be null. +/// A cookie that can be set globally for all web views +/// using [WebViewCookieManagerPlatform]. class WebViewCookie { - /// Construct a new [WebViewCookie]. + /// Constructs a new [WebViewCookie]. const WebViewCookie( {required this.name, required this.value, required this.domain, this.path = '/'}); - /// The name of the cookie. + /// The cookie-name of the cookie. /// /// Its value should match "cookie-name" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String name; - /// The value of the cookie. + /// The cookie-value of the cookie. /// /// Its value should match "cookie-value" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String value; - /// The value of the cookie. + /// The domain-value of the cookie. /// /// Its value should match "domain-value" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String domain; - /// The value of the cookie. + /// The path-value of the cookie. + /// Is set to `/` in the constructor by default. /// /// Its value should match "path-value" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String path; - /// Serialize a [WebViewCookie] to a Map. + /// Serializes a [WebViewCookie] to a Map. Map toJson() { return { 'name': name, From c7067c0565af65cbcda45d86ffbb63e8b07048a6 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 30 Nov 2021 14:33:15 +0100 Subject: [PATCH 14/24] Process PR feedback --- .../lib/src/types/webview_cookie.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart index 64142929d04d..406c510afd4b 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart @@ -37,7 +37,7 @@ class WebViewCookie { /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String path; - /// Serializes a [WebViewCookie] to a Map. + /// Serializes the [WebViewCookie] to a Map. Map toJson() { return { 'name': name, From 4b2d0b9bdb2cf50f8295027e2583797781deb5d4 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 30 Nov 2021 14:43:14 +0100 Subject: [PATCH 15/24] Fix issue with WebViewCookieManager --- .../example/lib/web_view.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart index 6299f6b34821..97f869858304 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart @@ -664,11 +664,13 @@ class WebViewCookieManager extends WebViewCookieManagerPlatform { /// Returns an instance of the cookie manager for the current platform. static WebViewCookieManagerPlatform get instance { - if (WebViewCookieManagerPlatform.instance == null && Platform.isIOS) { - WebViewCookieManagerPlatform.instance = WKWebViewCookieManager(); - } else { - throw AssertionError( - 'This platform is currently unsupported for webview_flutter_wkwebview.'); + if (WebViewCookieManagerPlatform.instance == null) { + if (Platform.isIOS) { + WebViewCookieManagerPlatform.instance = WKWebViewCookieManager(); + } else { + throw AssertionError( + 'This platform is currently unsupported for webview_flutter_wkwebview.'); + } } return WebViewCookieManagerPlatform.instance!; } From 8f5bb778c8af95152a81544c6d1a768ae7e50b8b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 30 Nov 2021 14:43:53 +0100 Subject: [PATCH 16/24] Fix issue with WebViewCookieManager --- .../example/lib/web_view.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart index 6b97affed0fe..c2d6107b3c7c 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart @@ -645,11 +645,13 @@ class WebViewCookieManager extends WebViewCookieManagerPlatform { /// Returns an instance of the cookie manager for the current platform. static WebViewCookieManagerPlatform get instance { - if (WebViewCookieManagerPlatform.instance == null && Platform.isAndroid) { - WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager(); - } else { - throw AssertionError( - 'This platform is currently unsupported for webview_flutter_android.'); + if (WebViewCookieManagerPlatform.instance == null) { + if (Platform.isAndroid) { + WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager(); + } else { + throw AssertionError( + 'This platform is currently unsupported for webview_flutter_android.'); + } } return WebViewCookieManagerPlatform.instance!; } From 68782234d22a4c813a1f1544c8643739a05dcc43 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 1 Dec 2021 09:41:11 +0100 Subject: [PATCH 17/24] Add setCookie to CookieManager and support initial cookies in creation params. --- .../webview_flutter/CHANGELOG.md | 4 +- .../webview_flutter/example/lib/main.dart | 20 ++++++++- .../lib/platform_interface.dart | 3 +- .../webview_flutter/lib/src/webview.dart | 32 +++++++++++++-- .../webview_flutter/pubspec.yaml | 11 +++-- .../test/webview_flutter_test.dart | 41 +++++++++++++++++-- .../test/webview_flutter_test.mocks.dart | 20 +++++++-- 7 files changed, 114 insertions(+), 17 deletions(-) diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 5dd2ea0cf554..c432611111e3 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,7 +1,9 @@ -## NEXT +## 2.4.0 * Updates example app Android compileSdkVersion to 31. * Integration test fixes. +* Added `setCookie` to CookieManager. +* CreationParams now supports setting `initialCookies`. ## 2.3.1 diff --git a/packages/webview_flutter/webview_flutter/example/lib/main.dart b/packages/webview_flutter/webview_flutter/example/lib/main.dart index b660ce37a0b1..24fdf04556b1 100644 --- a/packages/webview_flutter/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter/example/lib/main.dart @@ -58,7 +58,9 @@ class _WebViewExampleState extends State { // to allow calling Scaffold.of(context) so we can show a snackbar. body: Builder(builder: (BuildContext context) { return WebView( - initialUrl: 'https://flutter.dev', + // initialUrl: 'https://flutter.dev', + initialUrl: 'https://httpbin.org/anything', + initialCookies: [const WebViewCookie(name: 'fooo', value: 'baar', domain: 'httpbin.org', path: '/anything')], javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { _controller.complete(webViewController); @@ -131,6 +133,7 @@ enum MenuOptions { listCache, clearCache, navigationDelegate, + setCookie, } class SampleMenu extends StatelessWidget { @@ -169,6 +172,9 @@ class SampleMenu extends StatelessWidget { case MenuOptions.navigationDelegate: _onNavigationDelegateExample(controller.data!, context); break; + case MenuOptions.setCookie: + _onSetCookie(controller.data!, context); + break; } }, itemBuilder: (BuildContext context) => >[ @@ -201,6 +207,10 @@ class SampleMenu extends StatelessWidget { value: MenuOptions.navigationDelegate, child: Text('Navigation Delegate example'), ), + const PopupMenuItem( + value: MenuOptions.setCookie, + child: Text('Set cookie'), + ), ], ); }, @@ -274,6 +284,14 @@ class SampleMenu extends StatelessWidget { await controller.loadUrl('data:text/html;base64,$contentBase64'); } + void _onSetCookie(WebViewController controller, BuildContext context) async { + await CookieManager().setCookie( + const WebViewCookie( + name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'), + ); + await controller.loadUrl('https://httpbin.org/anything'); + } + Widget _getCookieList(String cookies) { if (cookies == null || cookies == '""') { return Container(); diff --git a/packages/webview_flutter/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/webview_flutter/lib/platform_interface.dart index aa7b3a0931e8..01a36791a422 100644 --- a/packages/webview_flutter/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/webview_flutter/lib/platform_interface.dart @@ -21,4 +21,5 @@ export 'package:webview_flutter_platform_interface/webview_flutter_platform_inte WebSetting, WebSettings, WebResourceError, - WebResourceErrorType; + WebResourceErrorType, + WebViewCookie; diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/webview.dart index 61702e514a4b..726d8e19204c 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/webview.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/webview.dart @@ -3,12 +3,15 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:webview_flutter_android/webview_android.dart'; +import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import '../platform_interface.dart'; @@ -79,6 +82,7 @@ class WebView extends StatefulWidget { Key? key, this.onWebViewCreated, this.initialUrl, + this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, @@ -149,6 +153,9 @@ class WebView extends StatefulWidget { /// The initial URL to load. final String? initialUrl; + /// The initial cookies to set. + final List initialCookies; + /// Whether JavaScript execution is enabled. final JavascriptMode javascriptMode; @@ -357,6 +364,7 @@ CreationParams _creationParamsfromWidget(WebView widget) { javascriptChannelNames: _extractChannelNames(widget.javascriptChannels), userAgent: widget.userAgent, autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, + cookies: widget.initialCookies, ); } @@ -721,16 +729,34 @@ class CookieManager { return _instance ??= CookieManager._(); } - CookieManager._(); + CookieManager._() { + if (WebViewCookieManagerPlatform.instance == null) { + if (Platform.isAndroid) { + WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager(); + } else if (Platform.isIOS) { + WebViewCookieManagerPlatform.instance = WKWebViewCookieManager(); + } else { + throw AssertionError( + 'This platform is currently unsupported by webview_flutter.'); + } + } + } static CookieManager? _instance; /// Clears all cookies for all [WebView] instances. /// - /// This is a no op on iOS version smaller than 9. + /// This is a no op on iOS versions below 9. /// /// Returns true if cookies were present before clearing, else false. - Future clearCookies() => WebView.platform.clearCookies(); + Future clearCookies() => + WebViewCookieManagerPlatform.instance!.clearCookies(); + + /// Sets a cookie for all [WebView] instances. + /// + /// This is a no op on iOS versions below 11. + Future setCookie(WebViewCookie cookie) => + WebViewCookieManagerPlatform.instance!.setCookie(cookie); } // Throws an ArgumentError if `url` is not a valid URL string. diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index 4450837b1ede..706999e0cafa 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.3.1 +version: 2.4.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -19,9 +19,12 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_android: ^2.2.0 - webview_flutter_platform_interface: ^1.2.0 - webview_flutter_wkwebview: ^2.2.0 + webview_flutter_android: + path: ../webview_flutter_android + webview_flutter_platform_interface: + path: ../webview_flutter_platform_interface + webview_flutter_wkwebview: + path: ../webview_flutter_wkwebview dev_dependencies: build_runner: ^2.1.5 diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart index 69d4142eb150..0a3d94935f28 100644 --- a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart @@ -21,10 +21,12 @@ void main() { late MockWebViewPlatform mockWebViewPlatform; late MockWebViewPlatformController mockWebViewPlatformController; + late MockWebViewCookieManagerPlatform mockWebViewCookieManagerPlatform; setUp(() { mockWebViewPlatformController = MockWebViewPlatformController(); mockWebViewPlatform = MockWebViewPlatform(); + mockWebViewCookieManagerPlatform = MockWebViewCookieManagerPlatform(); when(mockWebViewPlatform.build( context: anyNamed('context'), creationParams: anyNamed('creationParams'), @@ -43,6 +45,11 @@ void main() { }); WebView.platform = mockWebViewPlatform; + WebViewCookieManagerPlatform.instance = mockWebViewCookieManagerPlatform; + }); + + tearDown(() { + mockWebViewCookieManagerPlatform.reset(); }); testWidgets('Create WebView', (WidgetTester tester) async { @@ -381,9 +388,6 @@ void main() { }); testWidgets('Cookies can be cleared once', (WidgetTester tester) async { - when(mockWebViewPlatform.clearCookies()) - .thenAnswer((_) => Future.value(true)); - await tester.pumpWidget( const WebView( initialUrl: 'https://flutter.io', @@ -394,6 +398,21 @@ void main() { expect(hasCookies, true); }); + testWidgets('Cookies can be set', (WidgetTester tester) async { + const cookie = + WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'); + + await tester.pumpWidget( + const WebView( + initialUrl: 'https://flutter.io', + ), + ); + final CookieManager cookieManager = CookieManager(); + await cookieManager.setCookie(cookie); + expect(mockWebViewCookieManagerPlatform.setCookieCalls, + [cookie]); + }); + testWidgets('Initial JavaScript channels', (WidgetTester tester) async { await tester.pumpWidget( WebView( @@ -1161,3 +1180,19 @@ class MatchesCreationParams extends Matcher { .matches(creationParams.javascriptChannelNames, matchState); } } + +class MockWebViewCookieManagerPlatform extends WebViewCookieManagerPlatform { + List setCookieCalls = []; + + @override + Future clearCookies() async => true; + + @override + Future setCookie(WebViewCookie cookie) async { + setCookieCalls.add(cookie); + } + + void reset() { + setCookieCalls = []; + } +} diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart index b9d43cc6f182..8857f606f06d 100644 --- a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart @@ -1,7 +1,3 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - // Mocks generated by Mockito 5.0.16 from annotations // in webview_flutter/test/webview_flutter_test.dart. // Do not manually edit this file. @@ -81,12 +77,28 @@ class MockWebViewPlatformController extends _i1.Mock _i1.throwOnMissingStub(this); } + @override + _i9.Future loadFile(String? absoluteFilePath) => + (super.noSuchMethod(Invocation.method(#loadFile, [absoluteFilePath]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i9.Future); + @override + _i9.Future loadHtmlString(String? html, {String? baseUrl}) => + (super.noSuchMethod( + Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i9.Future); @override _i9.Future loadUrl(String? url, Map? headers) => (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i9.Future); @override + _i9.Future loadRequest(_i4.WebViewRequest? request) => + (super.noSuchMethod(Invocation.method(#loadRequest, [request]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i9.Future); + @override _i9.Future updateSettings(_i4.WebSettings? setting) => (super.noSuchMethod(Invocation.method(#updateSettings, [setting]), returnValue: Future.value(), From cdc302e16cc7e85beb41f12c672ffc9449eda8f4 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 1 Dec 2021 10:10:31 +0100 Subject: [PATCH 18/24] Add fix for platform implementation registration. --- .../lib/webview_android_widget.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index 4b83f54e1566..7ea4a635661e 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -276,14 +276,17 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } webView.settings.setMediaPlaybackRequiresUserGesture( - creationParams.autoMediaPlaybackPolicy != - AutoMediaPlaybackPolicy.always_allow, + creationParams.autoMediaPlaybackPolicy != AutoMediaPlaybackPolicy.always_allow, ); addJavascriptChannels(creationParams.javascriptChannelNames); - creationParams.cookies - .forEach(WebViewCookieManagerPlatform.instance!.setCookie); + /// TODO (BeMacized): Remove once platform implementations + /// are able to register themselves (Flutter >=2.8). + /// https://github.com/flutter/flutter/issues/94224 + WebViewCookieManagerPlatform.instance ??= WebViewAndroidCookieManager(); + + creationParams.cookies.forEach(WebViewCookieManagerPlatform.instance!.setCookie); } Future _setHasProgressTracking(bool hasProgressTracking) async { From 9f98faf40c42d84bd2c492de27532fc03ab34c59 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 2 Dec 2021 11:59:01 +0100 Subject: [PATCH 19/24] Fix build issue --- .../webview_flutter_android/android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle index c087839d2777..37954b36a834 100644 --- a/packages/webview_flutter/webview_flutter_android/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle @@ -43,8 +43,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } testOptions { From 6b021e9adebad6e1119a27c91a80aa4db460527e Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 7 Dec 2021 18:49:37 +0100 Subject: [PATCH 20/24] Process PR feedback --- packages/webview_flutter/webview_flutter/lib/src/webview.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/webview.dart index 726d8e19204c..e3be2217fade 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/webview.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/webview.dart @@ -746,8 +746,6 @@ class CookieManager { /// Clears all cookies for all [WebView] instances. /// - /// This is a no op on iOS versions below 9. - /// /// Returns true if cookies were present before clearing, else false. Future clearCookies() => WebViewCookieManagerPlatform.instance!.clearCookies(); From f09e80797585acde16a6f0538c87f43fc9c97517 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 7 Dec 2021 20:42:06 +0100 Subject: [PATCH 21/24] Merge android, ios and platform interface from master --- .../webview_flutter_android/CHANGELOG.md | 25 +- .../CookieManagerHostApiImpl.java | 12 +- .../webviewflutter/FlutterAssetManager.java | 108 ++++++++ .../FlutterAssetManagerHostApiImpl.java | 46 ++++ .../GeneratedAndroidWebView.java | 251 ++++++++++++++++++ .../webviewflutter/WebViewFlutterPlugin.java | 19 +- .../webviewflutter/WebViewHostApiImpl.java | 45 ++++ .../CookieManagerHostApiImplTest.java | 4 + .../FlutterAssetManagerHostApiImplTest.java | 76 ++++++ .../PluginBindingFlutterAssetManagerTest.java | 54 ++++ .../RegistrarFlutterAssetManagerTest.java | 55 ++++ .../plugins/webviewflutter/WebViewTest.java | 52 ++++ .../BackgroundColorTest.java | 61 +++++ .../android/app/src/debug/AndroidManifest.xml | 8 + .../DriverExtensionActivity.java | 16 ++ .../example/android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../example/assets/www/index.html | 20 ++ .../example/assets/www/styles/style.css | 3 + .../example/lib/main.dart | 154 ++++++++++- .../example/lib/web_view.dart | 45 +++- .../example/pubspec.yaml | 5 + .../lib/src/android_webview.dart | 127 +++++++++ .../lib/src/android_webview.pigeon.dart | 187 +++++++++++++ .../lib/src/android_webview_api_impls.dart | 50 ++++ .../lib/webview_android.dart | 10 +- .../lib/webview_android_widget.dart | 95 ++++++- .../pigeons/android_webview.dart | 31 +++ .../webview_flutter_android/pubspec.yaml | 7 +- .../test/android_webview.pigeon.dart | 173 ++++++++++++ .../test/android_webview_test.dart | 56 ++++ .../test/android_webview_test.mocks.dart | 83 ++++++ .../test/webview_android_widget_test.dart | 195 ++++++++++++++ .../webview_android_widget_test.mocks.dart | 75 +++++- .../CHANGELOG.md | 12 + .../webview_method_channel.dart | 27 +- .../platform_interface/webview_platform.dart | 2 +- .../webview_platform_controller.dart | 11 + .../lib/src/types/creation_params.dart | 9 +- .../pubspec.yaml | 2 +- .../webview_method_channel_test.dart | 128 +++++++++ .../webview_flutter_wkwebview/CHANGELOG.md | 10 +- .../webview_flutter_test.dart | 49 ++-- .../ios/RunnerUITests/FLTWebViewUITests.m | 67 +++++ .../example/lib/main.dart | 92 +++++-- .../example/lib/navigation_request.dart | 2 +- .../example/lib/web_view.dart | 27 +- .../example/pubspec.yaml | 7 +- .../ios/Classes/FlutterWebView.m | 14 + .../lib/src/webview_cupertino.dart | 9 +- .../webview_flutter_wkwebview/pubspec.yaml | 8 +- .../src/wkwebview_cookie_manager_test.dart | 4 +- 52 files changed, 2513 insertions(+), 119 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImpl.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImplTest.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java create mode 100644 packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/BackgroundColorTest.java create mode 100644 packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java create mode 100644 packages/webview_flutter/webview_flutter_android/example/assets/www/index.html create mode 100644 packages/webview_flutter/webview_flutter_android/example/assets/www/styles/style.css diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index f987f941d585..7bd33879cd7d 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,12 +1,28 @@ +## 2.8.0 + +* Implements new cookie manager for setting cookies and providing initial cookies. + +## 2.7.0 + +* Adds support for the `loadRequest` method from the platform interface. + +## 2.6.0 + +* Adds implementation of the `loadFlutterAsset` method from the platform interface. + +## 2.5.0 + +* Adds an option to set the background color of the webview. + ## 2.4.0 +* Adds support for Android's `WebView.loadData` and `WebView.loadDataWithBaseUrl` methods and implements the `loadFile` and `loadHtmlString` methods from the platform interface. * Updates to webview_flutter_platform_interface version 1.5.2. ## 2.3.1 * Adds explanation on how to generate the pigeon communication layer and mockito mock objects. * Updates compileSdkVersion to 31. -* Implemented new cookie manager for setting cookies and providing initial cookies. ## 2.3.0 @@ -27,13 +43,12 @@ when it is created without Hybrid Composition. ## 2.0.15 -* Added Overrides in FlutterWebView.java - +* Added Overrides in FlutterWebView.java + ## 2.0.14 -* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). +* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). ## 2.0.13 * Extract Android implementation from `webview_flutter`. - diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java index 09c35d0329fb..3e38ce94b3a5 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter 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; @@ -7,11 +11,13 @@ class CookieManagerHostApiImpl implements GeneratedAndroidWebView.CookieManagerH @Override public void clearCookies(GeneratedAndroidWebView.Result result) { CookieManager cookieManager = CookieManager.getInstance(); - final boolean hasCookies = cookieManager.hasCookies(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - cookieManager.removeAllCookies(value -> result.success(hasCookies)); + cookieManager.removeAllCookies(result::success); } else { - cookieManager.removeAllCookie(); + final boolean hasCookies = cookieManager.hasCookies(); + if (hasCookies) { + cookieManager.removeAllCookie(); + } result.success(hasCookies); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java new file mode 100644 index 000000000000..1d484d8639a0 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java @@ -0,0 +1,108 @@ +// Copyright 2013 The Flutter 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.res.AssetManager; +import androidx.annotation.NonNull; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.PluginRegistry; +import java.io.IOException; + +/** Provides access to the assets registered as part of the App bundle. */ +abstract class FlutterAssetManager { + final AssetManager assetManager; + + /** + * Constructs a new instance of the {@link FlutterAssetManager}. + * + * @param assetManager Instance of Android's {@link AssetManager} used to access assets within the + * App bundle. + */ + public FlutterAssetManager(AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * Gets the relative file path to the Flutter asset with the given name, including the file's + * extension, e.g., "myImage.jpg". + * + *

The returned file path is relative to the Android app's standard asset's directory. + * Therefore, the returned path is appropriate to pass to Android's AssetManager, but the path is + * not appropriate to load as an absolute path. + */ + abstract String getAssetFilePathByName(String name); + + /** + * Returns a String array of all the assets at the given path. + * + * @param path A relative path within the assets, i.e., "docs/home.html". This value cannot be + * null. + * @return String[] Array of strings, one for each asset. These file names are relative to 'path'. + * This value may be null. + * @throws IOException Throws an IOException in case I/O operations were interrupted. + */ + public String[] list(@NonNull String path) throws IOException { + return assetManager.list(path); + } + + /** + * Provides access to assets using the {@link PluginRegistry.Registrar} for looking up file paths + * to Flutter assets. + * + * @deprecated The {@link RegistrarFlutterAssetManager} is for Flutter's v1 embedding. For + * instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit + * http://flutter.dev/go/android-plugin-migration + */ + @Deprecated + static class RegistrarFlutterAssetManager extends FlutterAssetManager { + final PluginRegistry.Registrar registrar; + + /** + * Constructs a new instance of the {@link RegistrarFlutterAssetManager}. + * + * @param assetManager Instance of Android's {@link AssetManager} used to access assets within + * the App bundle. + * @param registrar Instance of {@link io.flutter.plugin.common.PluginRegistry.Registrar} used + * to look up file paths to assets registered by Flutter. + */ + RegistrarFlutterAssetManager(AssetManager assetManager, PluginRegistry.Registrar registrar) { + super(assetManager); + this.registrar = registrar; + } + + @Override + public String getAssetFilePathByName(String name) { + return registrar.lookupKeyForAsset(name); + } + } + + /** + * Provides access to assets using the {@link FlutterPlugin.FlutterAssets} for looking up file + * paths to Flutter assets. + */ + static class PluginBindingFlutterAssetManager extends FlutterAssetManager { + final FlutterPlugin.FlutterAssets flutterAssets; + + /** + * Constructs a new instance of the {@link PluginBindingFlutterAssetManager}. + * + * @param assetManager Instance of Android's {@link AssetManager} used to access assets within + * the App bundle. + * @param flutterAssets Instance of {@link + * io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterAssets} used to look up file + * paths to assets registered by Flutter. + */ + PluginBindingFlutterAssetManager( + AssetManager assetManager, FlutterPlugin.FlutterAssets flutterAssets) { + super(assetManager); + this.flutterAssets = flutterAssets; + } + + @Override + public String getAssetFilePathByName(String name) { + return flutterAssets.getAssetFilePathByName(name); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImpl.java new file mode 100644 index 000000000000..791912adb815 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImpl.java @@ -0,0 +1,46 @@ +// Copyright 2013 The Flutter 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.webkit.WebView; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Host api implementation for {@link WebView}. + * + *

Handles creating {@link WebView}s that intercommunicate with a paired Dart object. + */ +public class FlutterAssetManagerHostApiImpl implements FlutterAssetManagerHostApi { + final FlutterAssetManager flutterAssetManager; + + /** Constructs a new instance of {@link FlutterAssetManagerHostApiImpl}. */ + public FlutterAssetManagerHostApiImpl(FlutterAssetManager flutterAssetManager) { + this.flutterAssetManager = flutterAssetManager; + } + + @Override + public List list(String path) { + try { + String[] paths = flutterAssetManager.list(path); + + if (paths == null) { + return new ArrayList<>(); + } + + return Arrays.asList(paths); + } catch (IOException ex) { + throw new RuntimeException(ex.getMessage()); + } + } + + @Override + public String getAssetFilePathByName(String name) { + return flutterAssetManager.getAssetFilePathByName(name); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index eaff3fa0bd8f..8ef0b8d11f96 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + // Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon @@ -12,6 +16,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; /** Generated class from Pigeon. */ @@ -257,8 +262,20 @@ public interface WebViewHostApi { void dispose(Long instanceId); + void loadData(Long instanceId, String data, String mimeType, String encoding); + + void loadDataWithBaseUrl( + Long instanceId, + String baseUrl, + String data, + String mimeType, + String encoding, + String historyUrl); + void loadUrl(Long instanceId, String url, Map headers); + void postUrl(Long instanceId, String url, byte[] data); + String getUrl(Long instanceId); Boolean canGoBack(Long instanceId); @@ -297,6 +314,8 @@ public interface WebViewHostApi { void setWebChromeClient(Long instanceId, Long clientInstanceId); + void setBackgroundColor(Long instanceId, Long color); + /** The codec used by WebViewHostApi. */ static MessageCodec getCodec() { return WebViewHostApiCodec.INSTANCE; @@ -358,6 +377,96 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.loadData", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number instanceIdArg = (Number) args.get(0); + if (instanceIdArg == null) { + throw new NullPointerException("instanceIdArg unexpectedly null."); + } + String dataArg = (String) args.get(1); + if (dataArg == null) { + throw new NullPointerException("dataArg unexpectedly null."); + } + String mimeTypeArg = (String) args.get(2); + if (mimeTypeArg == null) { + throw new NullPointerException("mimeTypeArg unexpectedly null."); + } + String encodingArg = (String) args.get(3); + if (encodingArg == null) { + throw new NullPointerException("encodingArg unexpectedly null."); + } + api.loadData(instanceIdArg.longValue(), dataArg, mimeTypeArg, encodingArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number instanceIdArg = (Number) args.get(0); + if (instanceIdArg == null) { + throw new NullPointerException("instanceIdArg unexpectedly null."); + } + String baseUrlArg = (String) args.get(1); + if (baseUrlArg == null) { + throw new NullPointerException("baseUrlArg unexpectedly null."); + } + String dataArg = (String) args.get(2); + if (dataArg == null) { + throw new NullPointerException("dataArg unexpectedly null."); + } + String mimeTypeArg = (String) args.get(3); + if (mimeTypeArg == null) { + throw new NullPointerException("mimeTypeArg unexpectedly null."); + } + String encodingArg = (String) args.get(4); + if (encodingArg == null) { + throw new NullPointerException("encodingArg unexpectedly null."); + } + String historyUrlArg = (String) args.get(5); + if (historyUrlArg == null) { + throw new NullPointerException("historyUrlArg unexpectedly null."); + } + api.loadDataWithBaseUrl( + instanceIdArg.longValue(), + baseUrlArg, + dataArg, + mimeTypeArg, + encodingArg, + historyUrlArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -391,6 +500,39 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.postUrl", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number instanceIdArg = (Number) args.get(0); + if (instanceIdArg == null) { + throw new NullPointerException("instanceIdArg unexpectedly null."); + } + String urlArg = (String) args.get(1); + if (urlArg == null) { + throw new NullPointerException("urlArg unexpectedly null."); + } + byte[] dataArg = (byte[]) args.get(2); + if (dataArg == null) { + throw new NullPointerException("dataArg unexpectedly null."); + } + api.postUrl(instanceIdArg.longValue(), urlArg, dataArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -942,6 +1084,37 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.WebViewHostApi.setBackgroundColor", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number instanceIdArg = (Number) args.get(0); + if (instanceIdArg == null) { + throw new NullPointerException("instanceIdArg unexpectedly null."); + } + Number colorArg = (Number) args.get(1); + if (colorArg == null) { + throw new NullPointerException("colorArg unexpectedly null."); + } + api.setBackgroundColor(instanceIdArg.longValue(), colorArg.longValue()); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } @@ -1866,6 +2039,84 @@ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) { } } + private static class FlutterAssetManagerHostApiCodec extends StandardMessageCodec { + public static final FlutterAssetManagerHostApiCodec INSTANCE = + new FlutterAssetManagerHostApiCodec(); + + private FlutterAssetManagerHostApiCodec() {} + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface FlutterAssetManagerHostApi { + List list(String path); + + String getAssetFilePathByName(String name); + + /** The codec used by FlutterAssetManagerHostApi. */ + static MessageCodec getCodec() { + return FlutterAssetManagerHostApiCodec.INSTANCE; + } + + /** + * Sets up an instance of `FlutterAssetManagerHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.FlutterAssetManagerHostApi.list", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + String pathArg = (String) args.get(0); + if (pathArg == null) { + throw new NullPointerException("pathArg unexpectedly null."); + } + List output = api.list(pathArg); + wrapped.put("result", output); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + String nameArg = (String) args.get(0); + if (nameArg == null) { + throw new NullPointerException("nameArg unexpectedly null."); + } + String output = api.getAssetFilePathByName(nameArg); + wrapped.put("result", output); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + private static class WebChromeClientFlutterApiCodec extends StandardMessageCodec { public static final WebChromeClientFlutterApiCodec INSTANCE = new WebChromeClientFlutterApiCodec(); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index 288c64a69963..4ef622fe47cb 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java @@ -13,7 +13,9 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.platform.PlatformViewRegistry; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CookieManagerHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebSettingsHostApi; @@ -30,7 +32,6 @@ public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { private FlutterPluginBinding pluginBinding; private WebViewHostApiImpl webViewHostApi; - private CookieManagerHostApiImpl cookieManagerHostApi; private JavaScriptChannelHostApiImpl javaScriptChannelHostApi; /** @@ -61,14 +62,17 @@ public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registra registrar.messenger(), registrar.platformViewRegistry(), registrar.activity(), - registrar.view()); + registrar.view(), + new FlutterAssetManager.RegistrarFlutterAssetManager( + registrar.context().getAssets(), registrar)); } private void setUp( BinaryMessenger binaryMessenger, PlatformViewRegistry viewRegistry, Context context, - View containerView) { + View containerView, + FlutterAssetManager flutterAssetManager) { InstanceManager instanceManager = new InstanceManager(); @@ -84,7 +88,6 @@ private void setUp( new JavaScriptChannelHostApiImpl.JavaScriptChannelCreator(), new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), new Handler(context.getMainLooper())); - cookieManagerHostApi = new CookieManagerHostApiImpl(); WebViewHostApi.setup(binaryMessenger, webViewHostApi); JavaScriptChannelHostApi.setup(binaryMessenger, javaScriptChannelHostApi); @@ -110,7 +113,9 @@ private void setUp( binaryMessenger, new WebSettingsHostApiImpl( instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator())); - GeneratedAndroidWebView.CookieManagerHostApi.setup(binaryMessenger, cookieManagerHostApi); + FlutterAssetManagerHostApi.setup( + binaryMessenger, new FlutterAssetManagerHostApiImpl(flutterAssetManager)); + CookieManagerHostApi.setup(binaryMessenger, new CookieManagerHostApiImpl()); } @Override @@ -120,7 +125,9 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { binding.getBinaryMessenger(), binding.getPlatformViewRegistry(), binding.getApplicationContext(), - null); + null, + new FlutterAssetManager.PluginBindingFlutterAssetManager( + binding.getApplicationContext().getAssets(), binding.getFlutterAssets())); } @Override diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java index 86bef0fab563..0f3161355dcb 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java @@ -352,12 +352,42 @@ public void dispose(Long instanceId) { } } + @Override + public void loadData(Long instanceId, String data, String mimeType, String encoding) { + final WebView webView = (WebView) instanceManager.getInstance(instanceId); + webView.loadData( + data, parseNullStringIdentifier(mimeType), parseNullStringIdentifier(encoding)); + } + + @Override + public void loadDataWithBaseUrl( + Long instanceId, + String baseUrl, + String data, + String mimeType, + String encoding, + String historyUrl) { + final WebView webView = (WebView) instanceManager.getInstance(instanceId); + webView.loadDataWithBaseURL( + parseNullStringIdentifier(baseUrl), + data, + parseNullStringIdentifier(mimeType), + parseNullStringIdentifier(encoding), + parseNullStringIdentifier(historyUrl)); + } + @Override public void loadUrl(Long instanceId, String url, Map headers) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.loadUrl(url, headers); } + @Override + public void postUrl(Long instanceId, String url, byte[] data) { + final WebView webView = (WebView) instanceManager.getInstance(instanceId); + webView.postUrl(url, data); + } + @Override public String getUrl(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); @@ -477,4 +507,19 @@ public void setWebChromeClient(Long instanceId, Long clientInstanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.setWebChromeClient((WebChromeClient) instanceManager.getInstance(clientInstanceId)); } + + @Override + public void setBackgroundColor(Long instanceId, Long color) { + final WebView webView = (WebView) instanceManager.getInstance(instanceId); + webView.setBackgroundColor(color.intValue()); + } + + @Nullable + private static String parseNullStringIdentifier(String value) { + if (value.equals(nullStringIdentifier)) { + return null; + } + + return value; + } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java index 1e2fea4681e3..6daeb1be7f63 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter 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 org.mockito.ArgumentMatchers.any; diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImplTest.java new file mode 100644 index 000000000000..f530365a9334 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImplTest.java @@ -0,0 +1,76 @@ +// Copyright 2013 The Flutter 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 org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +public class FlutterAssetManagerHostApiImplTest { + @Mock FlutterAssetManager mockFlutterAssetManager; + + FlutterAssetManagerHostApiImpl testFlutterAssetManagerHostApiImpl; + + @Before + public void setUp() { + mockFlutterAssetManager = mock(FlutterAssetManager.class); + + testFlutterAssetManagerHostApiImpl = + new FlutterAssetManagerHostApiImpl(mockFlutterAssetManager); + } + + @Test + public void list() { + try { + when(mockFlutterAssetManager.list("test/path")) + .thenReturn(new String[] {"index.html", "styles.css"}); + List actualFilePaths = testFlutterAssetManagerHostApiImpl.list("test/path"); + verify(mockFlutterAssetManager).list("test/path"); + assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths.toArray()); + } catch (IOException ex) { + fail(); + } + } + + @Test + public void list_returns_empty_list_when_no_results() { + try { + when(mockFlutterAssetManager.list("test/path")).thenReturn(null); + List actualFilePaths = testFlutterAssetManagerHostApiImpl.list("test/path"); + verify(mockFlutterAssetManager).list("test/path"); + assertArrayEquals(new String[] {}, actualFilePaths.toArray()); + } catch (IOException ex) { + fail(); + } + } + + @Test(expected = RuntimeException.class) + public void list_should_convert_io_exception_to_runtime_exception() { + try { + when(mockFlutterAssetManager.list("test/path")).thenThrow(new IOException()); + testFlutterAssetManagerHostApiImpl.list("test/path"); + } catch (IOException ex) { + fail(); + } + } + + @Test + public void getAssetFilePathByName() { + when(mockFlutterAssetManager.getAssetFilePathByName("index.html")) + .thenReturn("flutter_assets/index.html"); + String filePath = testFlutterAssetManagerHostApiImpl.getAssetFilePathByName("index.html"); + verify(mockFlutterAssetManager).getAssetFilePathByName("index.html"); + assertEquals("flutter_assets/index.html", filePath); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java new file mode 100644 index 000000000000..1f556b7bd486 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java @@ -0,0 +1,54 @@ +// Copyright 2013 The Flutter 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 org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.AssetManager; +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterAssets; +import io.flutter.plugins.webviewflutter.FlutterAssetManager.PluginBindingFlutterAssetManager; +import java.io.IOException; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +public class PluginBindingFlutterAssetManagerTest { + @Mock AssetManager mockAssetManager; + @Mock FlutterAssets mockFlutterAssets; + + PluginBindingFlutterAssetManager tesPluginBindingFlutterAssetManager; + + @Before + public void setUp() { + mockAssetManager = mock(AssetManager.class); + mockFlutterAssets = mock(FlutterAssets.class); + + tesPluginBindingFlutterAssetManager = + new PluginBindingFlutterAssetManager(mockAssetManager, mockFlutterAssets); + } + + @Test + public void list() { + try { + when(mockAssetManager.list("test/path")) + .thenReturn(new String[] {"index.html", "styles.css"}); + String[] actualFilePaths = tesPluginBindingFlutterAssetManager.list("test/path"); + verify(mockAssetManager).list("test/path"); + assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths); + } catch (IOException ex) { + fail(); + } + } + + @Test + public void registrar_getAssetFilePathByName() { + tesPluginBindingFlutterAssetManager.getAssetFilePathByName("sample_movie.mp4"); + verify(mockFlutterAssets).getAssetFilePathByName("sample_movie.mp4"); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java new file mode 100644 index 000000000000..86b0fb5432b9 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java @@ -0,0 +1,55 @@ +// Copyright 2013 The Flutter 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 org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.AssetManager; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugins.webviewflutter.FlutterAssetManager.RegistrarFlutterAssetManager; +import java.io.IOException; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +@SuppressWarnings("deprecation") +public class RegistrarFlutterAssetManagerTest { + @Mock AssetManager mockAssetManager; + @Mock Registrar mockRegistrar; + + RegistrarFlutterAssetManager testRegistrarFlutterAssetManager; + + @Before + public void setUp() { + mockAssetManager = mock(AssetManager.class); + mockRegistrar = mock(Registrar.class); + + testRegistrarFlutterAssetManager = + new RegistrarFlutterAssetManager(mockAssetManager, mockRegistrar); + } + + @Test + public void list() { + try { + when(mockAssetManager.list("test/path")) + .thenReturn(new String[] {"index.html", "styles.css"}); + String[] actualFilePaths = testRegistrarFlutterAssetManager.list("test/path"); + verify(mockAssetManager).list("test/path"); + assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths); + } catch (IOException ex) { + fail(); + } + } + + @Test + public void registrar_getAssetFilePathByName() { + testRegistrarFlutterAssetManager.getAssetFilePathByName("sample_movie.mp4"); + verify(mockRegistrar).lookupKeyForAsset("sample_movie.mp4"); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java index e506188d0c9d..2312b764342f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java @@ -156,12 +156,64 @@ public void releaseInputAwareWebViewDependents() { verify(mockJavaScriptChannel2).release(); } + @Test + public void loadData() { + testHostApiImpl.loadData( + 0L, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "text/plain", "base64"); + verify(mockWebView) + .loadData("VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "text/plain", "base64"); + } + + @Test + public void loadDataWithNullValues() { + testHostApiImpl.loadData( + 0L, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "", ""); + verify(mockWebView).loadData("VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", null, null); + } + + @Test + public void loadDataWithBaseUrl() { + testHostApiImpl.loadDataWithBaseUrl( + 0L, + "https://flutter.dev", + "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", + "text/plain", + "base64", + "about:blank"); + verify(mockWebView) + .loadDataWithBaseURL( + "https://flutter.dev", + "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", + "text/plain", + "base64", + "about:blank"); + } + + @Test + public void loadDataWithBaseUrlAndNullValues() { + testHostApiImpl.loadDataWithBaseUrl( + 0L, + "", + "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", + "", + "", + ""); + verify(mockWebView) + .loadDataWithBaseURL(null, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", null, null, null); + } + @Test public void loadUrl() { testHostApiImpl.loadUrl(0L, "https://www.google.com", new HashMap<>()); verify(mockWebView).loadUrl("https://www.google.com", new HashMap<>()); } + @Test + public void postUrl() { + testHostApiImpl.postUrl(0L, "https://www.google.com", new byte[] {0x01, 0x02}); + verify(mockWebView).postUrl("https://www.google.com", new byte[] {0x01, 0x02}); + } + @Test public void getUrl() { when(mockWebView.getUrl()).thenReturn("https://www.google.com"); diff --git a/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/BackgroundColorTest.java b/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/BackgroundColorTest.java new file mode 100644 index 000000000000..a63629e0b9e2 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/BackgroundColorTest.java @@ -0,0 +1,61 @@ +// Copyright 2013 The Flutter 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.webviewflutterexample; + +import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget; +import static androidx.test.espresso.flutter.action.FlutterActions.click; +import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withText; +import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey; +import static org.junit.Assert.assertEquals; + +import android.graphics.Bitmap; +import android.graphics.Color; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.screenshot.ScreenCapture; +import androidx.test.runner.screenshot.Screenshot; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class BackgroundColorTest { + @Rule + public ActivityTestRule myActivityTestRule = + new ActivityTestRule<>(DriverExtensionActivity.class, true, false); + + @Before + public void setUp() { + ActivityScenario.launch(DriverExtensionActivity.class); + } + + @Ignore("Doesn't run in Firebase Test Lab: https://github.com/flutter/flutter/issues/94748") + @Test + public void backgroundColor() { + onFlutterWidget(withValueKey("ShowPopupMenu")).perform(click()); + onFlutterWidget(withValueKey("ShowTransparentBackgroundExample")).perform(click()); + onFlutterWidget(withText("Transparent background test")); + + final ScreenCapture screenCapture = Screenshot.capture(); + final Bitmap screenBitmap = screenCapture.getBitmap(); + + final int centerLeftColor = + screenBitmap.getPixel(10, (int) Math.floor(screenBitmap.getHeight() / 2.0)); + final int centerColor = + screenBitmap.getPixel( + (int) Math.floor(screenBitmap.getWidth() / 2.0), + (int) Math.floor(screenBitmap.getHeight() / 2.0)); + + // Flutter Colors.green color : 0xFF4CAF50 + // https://github.com/flutter/flutter/blob/f4abaa0735eba4dfd8f33f73363911d63931fe03/packages/flutter/lib/src/material/colors.dart#L1208 + // The background color of the webview is : rgba(0, 0, 0, 0.5) + // The expected color is : rgba(38, 87, 40, 1) -> 0xFF265728 + assertEquals(0xFF265728, centerLeftColor); + assertEquals(Color.RED, centerColor); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/example/android/app/src/debug/AndroidManifest.xml b/packages/webview_flutter/webview_flutter_android/example/android/app/src/debug/AndroidManifest.xml index 28792201bc36..110b9abe1cd8 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/webview_flutter/webview_flutter_android/example/android/app/src/debug/AndroidManifest.xml @@ -13,5 +13,13 @@ android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> + + diff --git a/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java b/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java new file mode 100644 index 000000000000..59e1b04c37f2 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter 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.webviewflutterexample; + +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; + +public class DriverExtensionActivity extends FlutterActivity { + @Override + @NonNull + public String getDartEntrypointFunctionName() { + return "appMain"; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/example/android/build.gradle b/packages/webview_flutter/webview_flutter_android/example/android/build.gradle index d21451f63b46..e101ac08df55 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:3.3.0' } } diff --git a/packages/webview_flutter/webview_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/webview_flutter/webview_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties index b8793d3c0d69..2819f022f1fd 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/webview_flutter/webview_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html b/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html new file mode 100644 index 000000000000..9895dd3ce6cb --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html @@ -0,0 +1,20 @@ + + + + +Load file or HTML string example + + + + +

Local demo page

+

+ This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter + webview plugin. +

+ + + \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_android/example/assets/www/styles/style.css b/packages/webview_flutter/webview_flutter_android/example/assets/www/styles/style.css new file mode 100644 index 000000000000..c2140b8b0fd8 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/example/assets/www/styles/style.css @@ -0,0 +1,3 @@ +h1 { + color: blue; +} \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index 9041f9c56f06..0d0cd59af796 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -6,8 +6,12 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_driver/driver_extension.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:webview_flutter_android/webview_surface_android.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; @@ -15,6 +19,11 @@ import 'navigation_decision.dart'; import 'navigation_request.dart'; import 'web_view.dart'; +void appMain() { + enableFlutterDriverExtension(); + main(); +} + void main() { // Configure the [WebView] to use the [SurfaceAndroidWebView] // implementation instead of the default [AndroidWebView]. @@ -38,6 +47,46 @@ The navigation delegate is set to block navigation to the youtube website. '''; +const String kExamplePage = ''' + + + +Load file or HTML string example + + + +

Local demo page

+

+ This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter + webview plugin. +

+ + + +'''; + +const String kTransparentBackgroundPage = ''' + + + + Transparent background test + + + +
+

Transparent background test

+
+
+ + +'''; + class _WebViewExample extends StatefulWidget { const _WebViewExample({Key? key}) : super(key: key); @@ -52,6 +101,7 @@ class _WebViewExampleState extends State<_WebViewExample> { @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: const Color(0xFF4CAF50), appBar: AppBar( title: const Text('Flutter WebView example'), // This drop down menu demonstrates that Flutter widgets can be shown over the web view. @@ -88,6 +138,7 @@ class _WebViewExampleState extends State<_WebViewExample> { javascriptChannels: _createJavascriptChannels(context), javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent', + backgroundColor: const Color(0x80000000), ); }), floatingActionButton: favoriteButton(), @@ -135,6 +186,12 @@ enum _MenuOptions { listCache, clearCache, navigationDelegate, + loadFlutterAsset, + loadLocalFile, + loadHtmlString, + transparentBackground, + doPostRequest, + setCookie, } class _SampleMenu extends StatelessWidget { @@ -149,6 +206,7 @@ class _SampleMenu extends StatelessWidget { builder: (BuildContext context, AsyncSnapshot controller) { return PopupMenuButton<_MenuOptions>( + key: const ValueKey('ShowPopupMenu'), onSelected: (_MenuOptions value) { switch (value) { case _MenuOptions.showUserAgent: @@ -172,6 +230,24 @@ class _SampleMenu extends StatelessWidget { case _MenuOptions.navigationDelegate: _onNavigationDelegateExample(controller.data!, context); break; + case _MenuOptions.loadFlutterAsset: + _onLoadFlutterAssetExample(controller.data!, context); + break; + case _MenuOptions.loadLocalFile: + _onLoadLocalFileExample(controller.data!, context); + break; + case _MenuOptions.loadHtmlString: + _onLoadHtmlStringExample(controller.data!, context); + break; + case _MenuOptions.transparentBackground: + _onTransparentBackground(controller.data!, context); + break; + case _MenuOptions.doPostRequest: + _onDoPostRequest(controller.data!, context); + break; + case _MenuOptions.setCookie: + _onSetCookie(controller.data!, context); + break; } }, itemBuilder: (BuildContext context) => >[ @@ -204,6 +280,31 @@ class _SampleMenu extends StatelessWidget { value: _MenuOptions.navigationDelegate, child: Text('Navigation Delegate example'), ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.loadFlutterAsset, + child: Text('Load Flutter Asset'), + ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.loadHtmlString, + child: Text('Load HTML string'), + ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.loadLocalFile, + child: Text('Load local file'), + ), + const PopupMenuItem<_MenuOptions>( + key: ValueKey('ShowTransparentBackgroundExample'), + value: _MenuOptions.transparentBackground, + child: Text('Transparent background example'), + ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.doPostRequest, + child: Text('Post Request'), + ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.setCookie, + child: Text('Set Cookie'), + ), ], ); }, @@ -263,7 +364,7 @@ class _SampleMenu extends StatelessWidget { Future _onClearCookies( WebViewController controller, BuildContext context) async { - final bool hadCookies = await WebView.platform.clearCookies(); + final bool hadCookies = await WebViewCookieManager.instance.clearCookies(); String message = 'There were cookies. Now, they are gone!'; if (!hadCookies) { message = 'There are no cookies.'; @@ -274,6 +375,15 @@ class _SampleMenu extends StatelessWidget { )); } + Future _onSetCookie( + WebViewController controller, BuildContext context) async { + await WebViewCookieManager.instance.setCookie( + const WebViewCookie( + name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'), + ); + await controller.loadUrl('https://httpbin.org/anything'); + } + Future _onNavigationDelegateExample( WebViewController controller, BuildContext context) async { final String contentBase64 = @@ -281,6 +391,33 @@ class _SampleMenu extends StatelessWidget { await controller.loadUrl('data:text/html;base64,$contentBase64'); } + Future _onLoadFlutterAssetExample( + WebViewController controller, BuildContext context) async { + await controller.loadFlutterAsset('assets/www/index.html'); + } + + Future _onLoadLocalFileExample( + WebViewController controller, BuildContext context) async { + final String pathToIndex = await _prepareLocalFile(); + + await controller.loadFile(pathToIndex); + } + + Future _onLoadHtmlStringExample( + WebViewController controller, BuildContext context) async { + await controller.loadHtmlString(kExamplePage); + } + + Future _onDoPostRequest( + WebViewController controller, BuildContext context) async { + final WebViewRequest request = WebViewRequest( + uri: Uri.parse('https://httpbin.org/post'), + method: WebViewRequestMethod.post, + body: Uint8List.fromList('Test Body'.codeUnits), + ); + await controller.loadRequest(request); + } + Widget _getCookieList(String cookies) { if (cookies == null || cookies == '""') { return Container(); @@ -294,6 +431,21 @@ class _SampleMenu extends StatelessWidget { children: cookieWidgets.toList(), ); } + + static Future _prepareLocalFile() async { + final String tmpDir = (await getTemporaryDirectory()).path; + final File indexFile = File('$tmpDir/www/index.html'); + + await Directory('$tmpDir/www').create(recursive: true); + await indexFile.writeAsString(kExamplePage); + + return indexFile.path; + } + + Future _onTransparentBackground( + WebViewController controller, BuildContext context) async { + await controller.loadHtmlString(kTransparentBackgroundPage); + } } class _NavigationControls extends StatelessWidget { diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart index c2d6107b3c7c..91ea66376904 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart @@ -63,7 +63,7 @@ class WebView extends StatefulWidget { Key? key, this.onWebViewCreated, this.initialUrl, - this.initialCookies = const [], + this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, @@ -79,6 +79,7 @@ class WebView extends StatefulWidget { this.initialMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, this.allowsInlineMediaPlayback = false, + this.backgroundColor, }) : assert(javascriptMode != null), assert(initialMediaPlaybackPolicy != null), assert(allowsInlineMediaPlayback != null), @@ -242,6 +243,12 @@ class WebView extends StatefulWidget { /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; + /// The background color of the [WebView]. + /// + /// When `null` the platform's webview default background color is used. By + /// default [backgroundColor] is `null`. + final Color? backgroundColor; + @override _WebViewState createState() => _WebViewState(); } @@ -293,6 +300,7 @@ class _WebViewState extends State { _javascriptChannelRegistry.channels.keys.toSet(), autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, + backgroundColor: widget.backgroundColor, cookies: widget.initialCookies, ), javascriptChannelRegistry: _javascriptChannelRegistry, @@ -370,6 +378,36 @@ class WebViewController { WebView _widget; + /// Loads the file located on the specified [absoluteFilePath]. + /// + /// The [absoluteFilePath] parameter should contain the absolute path to the + /// file as it is stored on the device. For example: + /// `/Users/username/Documents/www/index.html`. + /// + /// Throws an ArgumentError if the [absoluteFilePath] does not exist. + Future loadFile(String absoluteFilePath) { + return _webViewPlatformController.loadFile(absoluteFilePath); + } + + /// Loads the Flutter asset specified in the pubspec.yaml file. + /// + /// Throws an ArgumentError if [key] is not part of the specified assets + /// in the pubspec.yaml file. + Future loadFlutterAsset(String key) { + return _webViewPlatformController.loadFlutterAsset(key); + } + + /// Loads the supplied HTML string. + /// + /// The [baseUrl] parameter is used when resolving relative URLs within the + /// HTML string. + Future loadHtmlString(String html, {String? baseUrl}) { + return _webViewPlatformController.loadHtmlString( + html, + baseUrl: baseUrl, + ); + } + /// Loads the specified URL. /// /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will @@ -387,6 +425,11 @@ class WebViewController { return _webViewPlatformController.loadUrl(url, headers); } + /// Loads a page by making the specified request. + Future loadRequest(WebViewRequest request) async { + return _webViewPlatformController.loadRequest(request); + } + /// Accessor to the current URL that the WebView is displaying. /// /// If [WebView.initialUrl] was never specified, returns `null`. diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 2faa74394054..85990bd02139 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -8,6 +8,9 @@ environment: dependencies: flutter: sdk: flutter + + path_provider: ^2.0.6 + webview_flutter_android: # When depending on this package from a real application you should use: # webview_flutter: ^x.y.z @@ -31,3 +34,5 @@ flutter: assets: - assets/sample_audio.ogg - assets/sample_video.mp4 + - assets/www/index.html + - assets/www/styles/style.css diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index 2b21803b6e68..dfa05cd92ee6 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:typed_data'; +import 'dart:ui'; + import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart' show AndroidViewSurface; @@ -67,6 +70,95 @@ class WebView { return api.setWebContentsDebuggingEnabled(enabled); } + /// Loads the given data into this WebView using a 'data' scheme URL. + /// + /// Note that JavaScript's same origin policy means that script running in a + /// page loaded using this method will be unable to access content loaded + /// using any scheme other than 'data', including 'http(s)'. To avoid this + /// restriction, use [loadDataWithBaseURL()] with an appropriate base URL. + /// + /// The [encoding] parameter specifies whether the data is base64 or URL + /// encoded. If the data is base64 encoded, the value of the encoding + /// parameter must be `'base64'`. HTML can be encoded with + /// `base64.encode(bytes)` like so: + /// ```dart + /// import 'dart:convert'; + /// + /// final unencodedHtml = ''' + /// '%28' is the code for '(' + /// '''; + /// final encodedHtml = base64.encode(utf8.encode(unencodedHtml)); + /// print(encodedHtml); + /// ``` + /// + /// The [mimeType] parameter specifies the format of the data. If WebView + /// can't handle the specified MIME type, it will download the data. If + /// `null`, defaults to 'text/html'. + Future loadData({ + required String data, + String? mimeType, + String? encoding, + }) { + return api.loadDataFromInstance( + this, + data, + mimeType ?? _nullStringIdentifier, + encoding ?? _nullStringIdentifier, + ); + } + + /// Loads the given data into this WebView. + /// + /// The [baseUrl] is used as base URL for the content. It is used both to + /// resolve relative URLs and when applying JavaScript's same origin policy. + /// + /// The [historyUrl] is used for the history entry. + /// + /// The [mimeType] parameter specifies the format of the data. If WebView + /// can't handle the specified MIME type, it will download the data. If + /// `null`, defaults to 'text/html'. + /// + /// Note that content specified in this way can access local device files (via + /// 'file' scheme URLs) only if baseUrl specifies a scheme other than 'http', + /// 'https', 'ftp', 'ftps', 'about' or 'javascript'. + /// + /// If the base URL uses the data scheme, this method is equivalent to calling + /// [loadData] and the [historyUrl] is ignored, and the data will be treated + /// as part of a data: URL, including the requirement that the content be + /// URL-encoded or base64 encoded. If the base URL uses any other scheme, then + /// the data will be loaded into the WebView as a plain string (i.e. not part + /// of a data URL) and any URL-encoded entities in the string will not be + /// decoded. + /// + /// Note that the [baseUrl] is sent in the 'Referer' HTTP header when + /// requesting subresources (images, etc.) of the page loaded using this + /// method. + /// + /// If a valid HTTP or HTTPS base URL is not specified in [baseUrl], then + /// content loaded using this method will have a `window.origin` value of + /// `"null"`. This must not be considered to be a trusted origin by the + /// application or by any JavaScript code running inside the WebView (for + /// example, event sources in DOM event handlers or web messages), because + /// malicious content can also create frames with a null origin. If you need + /// to identify the main frame's origin in a trustworthy way, you should use a + /// valid HTTP or HTTPS base URL to set the origin. + Future loadDataWithBaseUrl({ + String? baseUrl, + required String data, + String? mimeType, + String? encoding, + String? historyUrl, + }) { + return api.loadDataWithBaseUrlFromInstance( + this, + baseUrl ?? _nullStringIdentifier, + data, + mimeType ?? _nullStringIdentifier, + encoding ?? _nullStringIdentifier, + historyUrl ?? _nullStringIdentifier, + ); + } + /// Loads the given URL with additional HTTP headers, specified as a map from name to value. /// /// Note that if this map contains any of the headers that are set by default @@ -78,6 +170,13 @@ class WebView { return api.loadUrlFromInstance(this, url, headers); } + /// Loads the URL with postData using "POST" method into this WebView. + /// + /// If url is not a network URL, it will be loaded with [loadUrl] instead, ignoring the postData param. + Future postUrl(String url, Uint8List data) { + return api.postUrlFromInstance(this, url, data); + } + /// Gets the URL for the current page. /// /// This is not always the same as the URL passed to @@ -261,6 +360,11 @@ class WebView { return api.setWebChromeClientFromInstance(this, client); } + /// Sets the background color of this WebView. + Future setBackgroundColor(Color color) { + return api.setBackgroundColorFromInstance(this, color.value); + } + /// Releases all resources used by the [WebView]. /// /// Any methods called after [release] will throw an exception. @@ -277,6 +381,7 @@ class CookieManager { static CookieManager? _instance; + /// Gets the globally set CookieManager instance. static CookieManager get instance => _instance ??= CookieManager._(); /// Setter for the singleton value, for testing purposes only. @@ -308,6 +413,8 @@ class CookieManager { Future setCookie(String url, String value) => api.setCookie(url, value); /// Removes all cookies. + /// + /// The returned future resolves to true if any cookies were removed. Future clearCookies() => api.clearCookies(); } @@ -715,3 +822,23 @@ class WebResourceError { /// Describes the error. final String description; } + +/// Manages Flutter assets that are part of Android's app bundle. +class FlutterAssetManager { + /// Constructs the [FlutterAssetManager]. + const FlutterAssetManager(); + + /// Pigeon Host Api implementation for [FlutterAssetManager]. + @visibleForTesting + static FlutterAssetManagerHostApi api = FlutterAssetManagerHostApi(); + + /// Lists all assets at the given path. + /// + /// The assets are returned as a `List`. The `List` only + /// contains files which are direct childs + Future> list(String path) => api.list(path); + + /// Gets the relative file path to the Flutter asset with the given name. + Future getAssetFilePathByName(String name) => + api.getAssetFilePathByName(name); +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart index 40b13407da77..810a71732e7b 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + // Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name @@ -191,6 +195,70 @@ class WebViewHostApi { } } + Future loadData(int arg_instanceId, String arg_data, + String arg_mimeType, String arg_encoding) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.loadData', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel.send( + [arg_instanceId, arg_data, arg_mimeType, arg_encoding]) + as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + + Future loadDataWithBaseUrl( + int arg_instanceId, + String arg_baseUrl, + String arg_data, + String arg_mimeType, + String arg_encoding, + String arg_historyUrl) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel.send([ + arg_instanceId, + arg_baseUrl, + arg_data, + arg_mimeType, + arg_encoding, + arg_historyUrl + ]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + Future loadUrl(int arg_instanceId, String arg_url, Map arg_headers) async { final BasicMessageChannel channel = BasicMessageChannel( @@ -218,6 +286,33 @@ class WebViewHostApi { } } + Future postUrl( + int arg_instanceId, String arg_url, Uint8List arg_data) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.postUrl', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_instanceId, arg_url, arg_data]) + as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + Future getUrl(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getUrl', codec, @@ -706,6 +801,31 @@ class WebViewHostApi { return; } } + + Future setBackgroundColor(int arg_instanceId, int arg_color) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.setBackgroundColor', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel + .send([arg_instanceId, arg_color]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } } class _WebSettingsHostApiCodec extends StandardMessageCodec { @@ -1589,6 +1709,73 @@ class WebChromeClientHostApi { } } +class _FlutterAssetManagerHostApiCodec extends StandardMessageCodec { + const _FlutterAssetManagerHostApiCodec(); +} + +class FlutterAssetManagerHostApi { + /// Constructor for [FlutterAssetManagerHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + FlutterAssetManagerHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _FlutterAssetManagerHostApiCodec(); + + Future> list(String arg_path) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FlutterAssetManagerHostApi.list', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_path]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return (replyMap['result'] as List?)!.cast(); + } + } + + Future getAssetFilePathByName(String arg_name) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName', + codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_name]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return (replyMap['result'] as String?)!; + } + } +} + class _WebChromeClientFlutterApiCodec extends StandardMessageCodec { const _WebChromeClientFlutterApiCodec(); } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index 93391a51ad21..1db5ed449c56 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:typed_data'; + import 'package:flutter/services.dart'; import 'android_webview.dart'; @@ -109,6 +111,40 @@ class WebViewHostApiImpl extends WebViewHostApi { instanceManager.removeInstance(instance); } + /// Helper method to convert the instances ids to objects. + Future loadDataFromInstance( + WebView instance, + String data, + String mimeType, + String encoding, + ) { + return loadData( + instanceManager.getInstanceId(instance)!, + data, + mimeType, + encoding, + ); + } + + /// Helper method to convert instances ids to objects. + Future loadDataWithBaseUrlFromInstance( + WebView instance, + String baseUrl, + String data, + String mimeType, + String encoding, + String historyUrl, + ) { + return loadDataWithBaseUrl( + instanceManager.getInstanceId(instance)!, + baseUrl, + data, + mimeType, + encoding, + historyUrl, + ); + } + /// Helper method to convert instances ids to objects. Future loadUrlFromInstance( WebView instance, @@ -118,6 +154,15 @@ class WebViewHostApiImpl extends WebViewHostApi { return loadUrl(instanceManager.getInstanceId(instance)!, url, headers); } + /// Helper method to convert instances ids to objects. + Future postUrlFromInstance( + WebView instance, + String url, + Uint8List data, + ) { + return postUrl(instanceManager.getInstanceId(instance)!, url, data); + } + /// Helper method to convert instances ids to objects. Future getUrlFromInstance(WebView instance) { return getUrl(instanceManager.getInstanceId(instance)!); @@ -244,6 +289,11 @@ class WebViewHostApiImpl extends WebViewHostApi { instanceManager.getInstanceId(client)!, ); } + + /// Helper method to convert instances ids to objects. + Future setBackgroundColorFromInstance(WebView instance, int color) { + return setBackgroundColor(instanceManager.getInstanceId(instance)!, color); + } } /// Host api implementation for [WebSettings]. diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart index f1e6551a4109..1f0eb7bd7ded 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart @@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'src/android_webview.dart'; @@ -66,6 +65,11 @@ class AndroidWebView implements WebViewPlatform { } @override - Future clearCookies() => - WebViewCookieManagerPlatform.instance!.clearCookies(); + Future clearCookies() { + if (WebViewCookieManagerPlatform.instance == null) { + throw Exception( + 'Could not clear cookies as no implementation for WebViewCookieManagerPlatform has been registered.'); + } + return WebViewCookieManagerPlatform.instance!.clearCookies(); + } } diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index 7ea4a635661e..1dec9c105741 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/widgets.dart'; import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; @@ -20,6 +21,8 @@ class WebViewAndroidWidget extends StatefulWidget { required this.javascriptChannelRegistry, required this.onBuildWidget, @visibleForTesting this.webViewProxy = const WebViewProxy(), + @visibleForTesting + this.flutterAssetManager = const android_webview.FlutterAssetManager(), }); /// Initial parameters used to setup the WebView. @@ -47,6 +50,11 @@ class WebViewAndroidWidget extends StatefulWidget { /// This should only be changed for testing purposes. final WebViewProxy webViewProxy; + /// Manages access to Flutter assets that are part of the Android App bundle. + /// + /// This should only be changed for testing purposes. + final android_webview.FlutterAssetManager flutterAssetManager; + /// Callback to build a widget once [android_webview.WebView] has been initialized. final Widget Function(WebViewAndroidPlatformController controller) onBuildWidget; @@ -67,6 +75,7 @@ class _WebViewAndroidWidgetState extends State { callbacksHandler: widget.callbacksHandler, javascriptChannelRegistry: widget.javascriptChannelRegistry, webViewProxy: widget.webViewProxy, + flutterAssetManager: widget.flutterAssetManager, ); } @@ -91,6 +100,8 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { required this.callbacksHandler, required this.javascriptChannelRegistry, @visibleForTesting this.webViewProxy = const WebViewProxy(), + @visibleForTesting + this.flutterAssetManager = const android_webview.FlutterAssetManager(), }) : assert(creationParams.webSettings?.hasNavigationDelegate != null), super(callbacksHandler) { webView = webViewProxy.createWebView( @@ -134,6 +145,11 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { /// This should only be changed for testing purposes. final WebViewProxy webViewProxy; + /// Manages access to Flutter assets that are part of the Android App bundle. + /// + /// This should only be changed for testing purposes. + final android_webview.FlutterAssetManager flutterAssetManager; + /// Receives callbacks when content should be downloaded instead. @visibleForTesting late final WebViewAndroidDownloadListener downloadListener = @@ -148,6 +164,46 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @visibleForTesting WebViewAndroidWebViewClient get webViewClient => _webViewClient; + @override + Future loadHtmlString(String html, {String? baseUrl}) { + return webView.loadDataWithBaseUrl( + baseUrl: baseUrl, + data: html, + mimeType: 'text/html', + ); + } + + @override + Future loadFile(String absoluteFilePath) { + final String url = absoluteFilePath.startsWith('file://') + ? absoluteFilePath + : 'file://$absoluteFilePath'; + + return webView.loadUrl(url, {}); + } + + @override + Future loadFlutterAsset(String key) async { + final String assetFilePath = + await flutterAssetManager.getAssetFilePathByName(key); + final List pathElements = assetFilePath.split('/'); + final String fileName = pathElements.removeLast(); + final List paths = + await flutterAssetManager.list(pathElements.join('/')); + + if (!paths.contains(fileName)) { + throw ArgumentError( + 'Asset for key "$key" not found.', + 'key', + ); + } + + return webView.loadUrl( + 'file:///android_asset/$assetFilePath', + {}, + ); + } + @override Future loadUrl( String url, @@ -156,6 +212,28 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { return webView.loadUrl(url, headers ?? {}); } + /// When making a POST request, headers are ignored. As a workaround, make + /// the request manually and load the response data using [loadHTMLString]. + @override + Future loadRequest( + WebViewRequest request, + ) async { + if (!request.uri.hasScheme) { + throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); + } + switch (request.method) { + case WebViewRequestMethod.get: + return webView.loadUrl(request.uri.toString(), request.headers); + case WebViewRequestMethod.post: + return webView.postUrl( + request.uri.toString(), request.body ?? Uint8List(0)); + default: + throw UnimplementedError( + 'This version of webview_android_widget currently has no implementation for HTTP method ${request.method.serialize()} in loadRequest.', + ); + } + } + @override Future currentUrl() => webView.getUrl(); @@ -276,17 +354,24 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } webView.settings.setMediaPlaybackRequiresUserGesture( - creationParams.autoMediaPlaybackPolicy != AutoMediaPlaybackPolicy.always_allow, + creationParams.autoMediaPlaybackPolicy != + AutoMediaPlaybackPolicy.always_allow, ); + final Color? backgroundColor = creationParams.backgroundColor; + if (backgroundColor != null) { + webView.setBackgroundColor(backgroundColor); + } + addJavascriptChannels(creationParams.javascriptChannelNames); - /// TODO (BeMacized): Remove once platform implementations - /// are able to register themselves (Flutter >=2.8). - /// https://github.com/flutter/flutter/issues/94224 + // TODO(BeMacized): Remove once platform implementations + // are able to register themselves (Flutter >=2.8), + // https://github.com/flutter/flutter/issues/94224 WebViewCookieManagerPlatform.instance ??= WebViewAndroidCookieManager(); - creationParams.cookies.forEach(WebViewCookieManagerPlatform.instance!.setCookie); + creationParams.cookies + .forEach(WebViewCookieManagerPlatform.instance!.setCookie); } Future _setHasProgressTracking(bool hasProgressTracking) async { diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index 860aa611ee14..36862f7cbacc 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -32,12 +32,34 @@ abstract class WebViewHostApi { void dispose(int instanceId); + void loadData( + int instanceId, + String data, + String mimeType, + String encoding, + ); + + void loadDataWithBaseUrl( + int instanceId, + String baseUrl, + String data, + String mimeType, + String encoding, + String historyUrl, + ); + void loadUrl( int instanceId, String url, Map headers, ); + void postUrl( + int instanceId, + String url, + Uint8List data, + ); + String getUrl(int instanceId); bool canGoBack(int instanceId); @@ -79,6 +101,8 @@ abstract class WebViewHostApi { void setDownloadListener(int instanceId, int listenerInstanceId); void setWebChromeClient(int instanceId, int clientInstanceId); + + void setBackgroundColor(int instanceId, int color); } @HostApi(dartHostTestHandler: 'TestWebSettingsHostApi') @@ -183,6 +207,13 @@ abstract class WebChromeClientHostApi { void create(int instanceId, int webViewClientInstanceId); } +@HostApi(dartHostTestHandler: 'TestAssetManagerHostApi') +abstract class FlutterAssetManagerHostApi { + List list(String path); + + String getAssetFilePathByName(String name); +} + @FlutterApi() abstract class WebChromeClientFlutterApi { void dispose(int instanceId); diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 125d2d32130b..34bea570ae43 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.4.0 +version: 2.8.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -19,9 +19,7 @@ flutter: dependencies: flutter: sdk: flutter -# TODO (BeMacized): Update dependency once platform interface has been published. - webview_flutter_platform_interface: - path: ../webview_flutter_platform_interface + webview_flutter_platform_interface: ^1.8.0 dev_dependencies: build_runner: ^2.1.4 @@ -32,4 +30,3 @@ dev_dependencies: mockito: ^5.0.16 pedantic: ^1.10.0 pigeon: 1.0.9 - diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart index daf3e9a46890..1e47c79d32b7 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + // Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import @@ -19,7 +23,11 @@ abstract class TestWebViewHostApi { void create(int instanceId, bool useHybridComposition); void dispose(int instanceId); + void loadData(int instanceId, String data, String mimeType, String encoding); + void loadDataWithBaseUrl(int instanceId, String baseUrl, String data, + String mimeType, String encoding, String historyUrl); void loadUrl(int instanceId, String url, Map headers); + void postUrl(int instanceId, String url, Uint8List data); String getUrl(int instanceId); bool canGoBack(int instanceId); bool canGoForward(int instanceId); @@ -39,6 +47,7 @@ abstract class TestWebViewHostApi { void removeJavaScriptChannel(int instanceId, int javaScriptChannelInstanceId); void setDownloadListener(int instanceId, int listenerInstanceId); void setWebChromeClient(int instanceId, int clientInstanceId); + void setBackgroundColor(int instanceId, int color); static void setup(TestWebViewHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -82,6 +91,70 @@ abstract class TestWebViewHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.loadData', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadData was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadData was null, expected non-null int.'); + final String? arg_data = (args[1] as String?); + assert(arg_data != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadData was null, expected non-null String.'); + final String? arg_mimeType = (args[2] as String?); + assert(arg_mimeType != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadData was null, expected non-null String.'); + final String? arg_encoding = (args[3] as String?); + assert(arg_encoding != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadData was null, expected non-null String.'); + api.loadData( + arg_instanceId!, arg_data!, arg_mimeType!, arg_encoding!); + return {}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl was null, expected non-null int.'); + final String? arg_baseUrl = (args[1] as String?); + assert(arg_baseUrl != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl was null, expected non-null String.'); + final String? arg_data = (args[2] as String?); + assert(arg_data != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl was null, expected non-null String.'); + final String? arg_mimeType = (args[3] as String?); + assert(arg_mimeType != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl was null, expected non-null String.'); + final String? arg_encoding = (args[4] as String?); + assert(arg_encoding != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl was null, expected non-null String.'); + final String? arg_historyUrl = (args[5] as String?); + assert(arg_historyUrl != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl was null, expected non-null String.'); + api.loadDataWithBaseUrl(arg_instanceId!, arg_baseUrl!, arg_data!, + arg_mimeType!, arg_encoding!, arg_historyUrl!); + return {}; + }); + } + } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadUrl', codec, @@ -108,6 +181,31 @@ abstract class TestWebViewHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.postUrl', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null int.'); + final String? arg_url = (args[1] as String?); + assert(arg_url != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null String.'); + final Uint8List? arg_data = (args[2] as Uint8List?); + assert(arg_data != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null Uint8List.'); + api.postUrl(arg_instanceId!, arg_url!, arg_data!); + return {}; + }); + } + } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getUrl', codec, @@ -506,6 +604,28 @@ abstract class TestWebViewHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.setBackgroundColor', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.setBackgroundColor was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.setBackgroundColor was null, expected non-null int.'); + final int? arg_color = (args[1] as int?); + assert(arg_color != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.setBackgroundColor was null, expected non-null int.'); + api.setBackgroundColor(arg_instanceId!, arg_color!); + return {}; + }); + } + } } } @@ -961,3 +1081,56 @@ abstract class TestWebChromeClientHostApi { } } } + +class _TestAssetManagerHostApiCodec extends StandardMessageCodec { + const _TestAssetManagerHostApiCodec(); +} + +abstract class TestAssetManagerHostApi { + static const MessageCodec codec = _TestAssetManagerHostApiCodec(); + + List list(String path); + String getAssetFilePathByName(String name); + static void setup(TestAssetManagerHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FlutterAssetManagerHostApi.list', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.list was null.'); + final List args = (message as List?)!; + final String? arg_path = (args[0] as String?); + assert(arg_path != null, + 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.list was null, expected non-null String.'); + final List output = api.list(arg_path!); + return {'result': output}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName was null.'); + final List args = (message as List?)!; + final String? arg_name = (args[0] as String?); + assert(arg_name != null, + 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName was null, expected non-null String.'); + final String output = api.getAssetFilePathByName(arg_name!); + return {'result': output}; + }); + } + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index 369ecd67e4a6..cc29fc755067 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -23,6 +23,7 @@ import 'android_webview_test.mocks.dart'; TestWebSettingsHostApi, TestWebViewClientHostApi, TestWebViewHostApi, + TestAssetManagerHostApi, WebChromeClient, WebView, WebViewClient, @@ -59,6 +60,61 @@ void main() { verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(true)); }); + test('loadData', () { + webView.loadData( + data: 'hello', + mimeType: 'text/plain', + encoding: 'base64', + ); + verify(mockPlatformHostApi.loadData( + webViewInstanceId, + 'hello', + 'text/plain', + 'base64', + )); + }); + + test('loadData with null values', () { + webView.loadData(data: 'hello', mimeType: null, encoding: null); + verify(mockPlatformHostApi.loadData( + webViewInstanceId, + 'hello', + '', + '', + )); + }); + + test('loadDataWithBaseUrl', () { + webView.loadDataWithBaseUrl( + baseUrl: 'https://base.url', + data: 'hello', + mimeType: 'text/plain', + encoding: 'base64', + historyUrl: 'https://history.url', + ); + + verify(mockPlatformHostApi.loadDataWithBaseUrl( + webViewInstanceId, + 'https://base.url', + 'hello', + 'text/plain', + 'base64', + 'https://history.url', + )); + }); + + test('loadDataWithBaseUrl with null values', () { + webView.loadDataWithBaseUrl(data: 'hello'); + verify(mockPlatformHostApi.loadDataWithBaseUrl( + webViewInstanceId, + '', + 'hello', + '', + '', + '', + )); + }); + test('loadUrl', () { webView.loadUrl('hello', {'a': 'header'}); verify(mockPlatformHostApi.loadUrl( diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index b13099ea1539..d25d2338886b 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -1,8 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + // Mocks generated by Mockito 5.0.16 from annotations // in webview_flutter_android/test/android_webview_test.dart. // Do not manually edit this file. import 'dart:async' as _i4; +import 'dart:typed_data' as _i6; +import 'dart:ui' as _i7; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; @@ -245,11 +251,28 @@ class MockTestWebViewHostApi extends _i1.Mock super.noSuchMethod(Invocation.method(#dispose, [instanceId]), returnValueForMissingStub: null); @override + void loadData( + int? instanceId, String? data, String? mimeType, String? encoding) => + super.noSuchMethod( + Invocation.method(#loadData, [instanceId, data, mimeType, encoding]), + returnValueForMissingStub: null); + @override + void loadDataWithBaseUrl(int? instanceId, String? baseUrl, String? data, + String? mimeType, String? encoding, String? historyUrl) => + super.noSuchMethod( + Invocation.method(#loadDataWithBaseUrl, + [instanceId, baseUrl, data, mimeType, encoding, historyUrl]), + returnValueForMissingStub: null); + @override void loadUrl(int? instanceId, String? url, Map? headers) => super.noSuchMethod( Invocation.method(#loadUrl, [instanceId, url, headers]), returnValueForMissingStub: null); @override + void postUrl(int? instanceId, String? url, _i6.Uint8List? data) => + super.noSuchMethod(Invocation.method(#postUrl, [instanceId, url, data]), + returnValueForMissingStub: null); + @override String getUrl(int? instanceId) => (super.noSuchMethod(Invocation.method(#getUrl, [instanceId]), returnValue: '') as String); @@ -342,6 +365,31 @@ class MockTestWebViewHostApi extends _i1.Mock #setWebChromeClient, [instanceId, clientInstanceId]), returnValueForMissingStub: null); @override + void setBackgroundColor(int? instanceId, int? color) => super.noSuchMethod( + Invocation.method(#setBackgroundColor, [instanceId, color]), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); +} + +/// A class which mocks [TestAssetManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestAssetManagerHostApi extends _i1.Mock + implements _i5.TestAssetManagerHostApi { + MockTestAssetManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + List list(String? path) => + (super.noSuchMethod(Invocation.method(#list, [path]), + returnValue: []) as List); + @override + String getAssetFilePathByName(String? name) => + (super.noSuchMethod(Invocation.method(#getAssetFilePathByName, [name]), + returnValue: '') as String); + @override String toString() => super.toString(); } @@ -378,11 +426,41 @@ class MockWebView extends _i1.Mock implements _i2.WebView { (super.noSuchMethod(Invocation.getter(#settings), returnValue: _FakeWebSettings_0()) as _i2.WebSettings); @override + _i4.Future loadData( + {String? data, String? mimeType, String? encoding}) => + (super.noSuchMethod( + Invocation.method(#loadData, [], + {#data: data, #mimeType: mimeType, #encoding: encoding}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future loadDataWithBaseUrl( + {String? baseUrl, + String? data, + String? mimeType, + String? encoding, + String? historyUrl}) => + (super.noSuchMethod( + Invocation.method(#loadDataWithBaseUrl, [], { + #baseUrl: baseUrl, + #data: data, + #mimeType: mimeType, + #encoding: encoding, + #historyUrl: historyUrl + }), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override _i4.Future loadUrl(String? url, Map? headers) => (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override + _i4.Future postUrl(String? url, _i6.Uint8List? data) => + (super.noSuchMethod(Invocation.method(#postUrl, [url, data]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override _i4.Future getUrl() => (super.noSuchMethod(Invocation.method(#getUrl, []), returnValue: Future.value()) as _i4.Future); @@ -470,6 +548,11 @@ class MockWebView extends _i1.Mock implements _i2.WebView { returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override + _i4.Future setBackgroundColor(_i7.Color? color) => + (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override _i4.Future release() => (super.noSuchMethod(Invocation.method(#release, []), returnValue: Future.value(), diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart index d969664a42ed..c203ef04a2ce 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -16,6 +17,7 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_inte import 'webview_android_widget_test.mocks.dart'; @GenerateMocks([ + android_webview.FlutterAssetManager, android_webview.WebSettings, android_webview.WebView, WebViewAndroidDownloadListener, @@ -30,6 +32,7 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$WebViewAndroidWidget', () { + late MockFlutterAssetManager mockFlutterAssetManager; late MockWebView mockWebView; late MockWebSettings mockWebSettings; late MockWebViewProxy mockWebViewProxy; @@ -44,6 +47,7 @@ void main() { late WebViewAndroidPlatformController testController; setUp(() { + mockFlutterAssetManager = MockFlutterAssetManager(); mockWebView = MockWebView(); mockWebSettings = MockWebSettings(); when(mockWebView.settings).thenReturn(mockWebSettings); @@ -77,6 +81,7 @@ void main() { callbacksHandler: mockCallbacksHandler, javascriptChannelRegistry: mockJavascriptChannelRegistry, webViewProxy: mockWebViewProxy, + flutterAssetManager: mockFlutterAssetManager, onBuildWidget: (WebViewAndroidPlatformController controller) { testController = controller; return Container(); @@ -274,6 +279,121 @@ void main() { }); group('$WebViewPlatformController', () { + testWidgets('loadFile without "file://" prefix', + (WidgetTester tester) async { + await buildWidget(tester); + + const String filePath = '/path/to/file.html'; + await testController.loadFile(filePath); + + verify(mockWebView.loadUrl( + 'file://$filePath', + {}, + )); + }); + + testWidgets('loadFile with "file://" prefix', + (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadFile('file:///path/to/file.html'); + + verify(mockWebView.loadUrl( + 'file:///path/to/file.html', + {}, + )); + }); + + testWidgets('loadFlutterAsset', (WidgetTester tester) async { + await buildWidget(tester); + const String assetKey = 'test_assets/index.html'; + + when(mockFlutterAssetManager.getAssetFilePathByName(assetKey)) + .thenAnswer( + (_) => Future.value('flutter_assets/$assetKey')); + when(mockFlutterAssetManager.list('flutter_assets/test_assets')) + .thenAnswer( + (_) => Future>.value(['index.html'])); + + await testController.loadFlutterAsset(assetKey); + + verify(mockWebView.loadUrl( + 'file:///android_asset/flutter_assets/$assetKey', + {}, + )); + }); + + testWidgets('loadFlutterAsset with file in root', + (WidgetTester tester) async { + await buildWidget(tester); + const String assetKey = 'index.html'; + + when(mockFlutterAssetManager.getAssetFilePathByName(assetKey)) + .thenAnswer( + (_) => Future.value('flutter_assets/$assetKey')); + when(mockFlutterAssetManager.list('flutter_assets')).thenAnswer( + (_) => Future>.value(['index.html'])); + + await testController.loadFlutterAsset(assetKey); + + verify(mockWebView.loadUrl( + 'file:///android_asset/flutter_assets/$assetKey', + {}, + )); + }); + + testWidgets( + 'loadFlutterAsset throws ArgumentError when asset does not exists', + (WidgetTester tester) async { + await buildWidget(tester); + const String assetKey = 'test_assets/index.html'; + + when(mockFlutterAssetManager.getAssetFilePathByName(assetKey)) + .thenAnswer( + (_) => Future.value('flutter_assets/$assetKey')); + when(mockFlutterAssetManager.list('flutter_assets/test_assets')) + .thenAnswer((_) => Future>.value([''])); + + expect( + () => testController.loadFlutterAsset(assetKey), + throwsA( + isA() + .having((ArgumentError error) => error.name, 'name', 'key') + .having((ArgumentError error) => error.message, 'message', + 'Asset for key "$assetKey" not found.'), + ), + ); + }); + + testWidgets('loadHtmlString without base URL', + (WidgetTester tester) async { + await buildWidget(tester); + + const String htmlString = 'Test data.'; + await testController.loadHtmlString(htmlString); + + verify(mockWebView.loadDataWithBaseUrl( + data: htmlString, + mimeType: 'text/html', + )); + }); + + testWidgets('loadHtmlString with base URL', (WidgetTester tester) async { + await buildWidget(tester); + + const String htmlString = 'Test data.'; + await testController.loadHtmlString( + htmlString, + baseUrl: 'https://flutter.dev', + ); + + verify(mockWebView.loadDataWithBaseUrl( + baseUrl: 'https://flutter.dev', + data: htmlString, + mimeType: 'text/html', + )); + }); + testWidgets('loadUrl', (WidgetTester tester) async { await buildWidget(tester); @@ -288,6 +408,81 @@ void main() { )); }); + group('loadRequest', () { + testWidgets('Throws ArgumentError for empty scheme', + (WidgetTester tester) async { + await buildWidget(tester); + + expect( + () async => await testController.loadRequest( + WebViewRequest( + uri: Uri.parse('www.google.com'), + method: WebViewRequestMethod.get, + ), + ), + throwsA(const TypeMatcher())); + }); + + testWidgets('GET without headers', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadRequest(WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.get, + )); + + verify(mockWebView.loadUrl( + 'https://www.google.com', + {}, + )); + }); + + testWidgets('GET with headers', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadRequest(WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.get, + headers: {'a': 'header'}, + )); + + verify(mockWebView.loadUrl( + 'https://www.google.com', + {'a': 'header'}, + )); + }); + + testWidgets('POST without body', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadRequest(WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.post, + )); + + verify(mockWebView.postUrl( + 'https://www.google.com', + Uint8List(0), + )); + }); + + testWidgets('POST with body', (WidgetTester tester) async { + await buildWidget(tester); + + final Uint8List body = Uint8List.fromList('Test Body'.codeUnits); + + await testController.loadRequest(WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.post, + body: body)); + + verify(mockWebView.postUrl( + 'https://www.google.com', + body, + )); + }); + }); + testWidgets('currentUrl', (WidgetTester tester) async { await buildWidget(tester); diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart index 4f20b2d1e328..ece17ad61cb8 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart @@ -1,12 +1,18 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + // Mocks generated by Mockito 5.0.16 from annotations // in webview_flutter_android/test/webview_android_widget_test.dart. // Do not manually edit this file. import 'dart:async' as _i4; +import 'dart:typed_data' as _i5; +import 'dart:ui' as _i6; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; -import 'package:webview_flutter_android/webview_android_widget.dart' as _i5; +import 'package:webview_flutter_android/webview_android_widget.dart' as _i7; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' as _i3; @@ -26,6 +32,28 @@ class _FakeJavascriptChannelRegistry_1 extends _i1.Fake class _FakeWebView_2 extends _i1.Fake implements _i2.WebView {} +/// A class which mocks [FlutterAssetManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFlutterAssetManager extends _i1.Mock + implements _i2.FlutterAssetManager { + MockFlutterAssetManager() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Future> list(String? path) => + (super.noSuchMethod(Invocation.method(#list, [path]), + returnValue: Future>.value([])) + as _i4.Future>); + @override + _i4.Future getAssetFilePathByName(String? name) => + (super.noSuchMethod(Invocation.method(#getAssetFilePathByName, [name]), + returnValue: Future.value('')) as _i4.Future); + @override + String toString() => super.toString(); +} + /// A class which mocks [WebSettings]. /// /// See the documentation for Mockito's code generation for more information. @@ -112,11 +140,41 @@ class MockWebView extends _i1.Mock implements _i2.WebView { (super.noSuchMethod(Invocation.getter(#settings), returnValue: _FakeWebSettings_0()) as _i2.WebSettings); @override + _i4.Future loadData( + {String? data, String? mimeType, String? encoding}) => + (super.noSuchMethod( + Invocation.method(#loadData, [], + {#data: data, #mimeType: mimeType, #encoding: encoding}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future loadDataWithBaseUrl( + {String? baseUrl, + String? data, + String? mimeType, + String? encoding, + String? historyUrl}) => + (super.noSuchMethod( + Invocation.method(#loadDataWithBaseUrl, [], { + #baseUrl: baseUrl, + #data: data, + #mimeType: mimeType, + #encoding: encoding, + #historyUrl: historyUrl + }), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override _i4.Future loadUrl(String? url, Map? headers) => (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override + _i4.Future postUrl(String? url, _i5.Uint8List? data) => + (super.noSuchMethod(Invocation.method(#postUrl, [url, data]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override _i4.Future getUrl() => (super.noSuchMethod(Invocation.method(#getUrl, []), returnValue: Future.value()) as _i4.Future); @@ -204,6 +262,11 @@ class MockWebView extends _i1.Mock implements _i2.WebView { returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override + _i4.Future setBackgroundColor(_i6.Color? color) => + (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override _i4.Future release() => (super.noSuchMethod(Invocation.method(#release, []), returnValue: Future.value(), @@ -216,7 +279,7 @@ class MockWebView extends _i1.Mock implements _i2.WebView { /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidDownloadListener extends _i1.Mock - implements _i5.WebViewAndroidDownloadListener { + implements _i7.WebViewAndroidDownloadListener { MockWebViewAndroidDownloadListener() { _i1.throwOnMissingStub(this); } @@ -242,7 +305,7 @@ class MockWebViewAndroidDownloadListener extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidJavaScriptChannel extends _i1.Mock - implements _i5.WebViewAndroidJavaScriptChannel { + implements _i7.WebViewAndroidJavaScriptChannel { MockWebViewAndroidJavaScriptChannel() { _i1.throwOnMissingStub(this); } @@ -268,7 +331,7 @@ class MockWebViewAndroidJavaScriptChannel extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidWebChromeClient extends _i1.Mock - implements _i5.WebViewAndroidWebChromeClient { + implements _i7.WebViewAndroidWebChromeClient { MockWebViewAndroidWebChromeClient() { _i1.throwOnMissingStub(this); } @@ -285,7 +348,7 @@ class MockWebViewAndroidWebChromeClient extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidWebViewClient extends _i1.Mock - implements _i5.WebViewAndroidWebViewClient { + implements _i7.WebViewAndroidWebViewClient { MockWebViewAndroidWebViewClient() { _i1.throwOnMissingStub(this); } @@ -417,7 +480,7 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock /// A class which mocks [WebViewProxy]. /// /// See the documentation for Mockito's code generation for more information. -class MockWebViewProxy extends _i1.Mock implements _i5.WebViewProxy { +class MockWebViewProxy extends _i1.Mock implements _i7.WebViewProxy { MockWebViewProxy() { _i1.throwOnMissingStub(this); } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md index 81d4e46b6ce9..ac61d482ff16 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,15 @@ +## 1.8.0 + +* Adds the `loadFlutterAsset` method to the platform interface. + +## 1.7.0 + +* Add an option to set the background color of the webview. + +## 1.6.1 + +* Revert deprecation of `clearCookies` in WebViewPlatform for later deprecation. + ## 1.6.0 * Adds platform interface for cookie manager. diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart index 61e0dd7e247a..bb2a4ac9fb50 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart @@ -84,7 +84,31 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { @override Future loadFile(String absoluteFilePath) async { assert(absoluteFilePath != null); - return _channel.invokeMethod('loadFile', absoluteFilePath); + + try { + return await _channel.invokeMethod('loadFile', absoluteFilePath); + } on PlatformException catch (ex) { + if (ex.code == 'loadFile_failed') { + throw ArgumentError(ex.message); + } + + rethrow; + } + } + + @override + Future loadFlutterAsset(String key) async { + assert(key.isNotEmpty); + + try { + return await _channel.invokeMethod('loadFlutterAsset', key); + } on PlatformException catch (ex) { + if (ex.code == 'loadFlutterAsset_invalidKey') { + throw ArgumentError(ex.message); + } + + rethrow; + } } @override @@ -266,6 +290,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { 'userAgent': creationParams.userAgent, 'autoMediaPlaybackPolicy': creationParams.autoMediaPlaybackPolicy.index, 'usesHybridComposition': usesHybridComposition, + 'backgroundColor': creationParams.backgroundColor?.value, 'cookies': creationParams.cookies .map((WebViewCookie cookie) => cookie.toJson()) .toList() diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart index a472f00d4bd2..e35635d73a9e 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform.dart @@ -59,7 +59,7 @@ abstract class WebViewPlatform { /// Clears all cookies for all [WebView] instances. /// /// Returns true if cookies were present before clearing, else false. - @Deprecated('Use `WebViewCookieManagerPlatform.clearCookies` instead.') + /// Soon to be deprecated. 'Use `WebViewCookieManagerPlatform.clearCookies` instead. Future clearCookies() { throw UnimplementedError( 'WebView clearCookies is not implemented on the current platform'); diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform_controller.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform_controller.dart index da73204e0099..3437fe1f2c09 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform_controller.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_interface/webview_platform_controller.dart @@ -35,6 +35,17 @@ abstract class WebViewPlatformController { /// Throws an ArgumentError if the [absoluteFilePath] does not exist. Future loadFile( String absoluteFilePath, + ) { + throw UnimplementedError( + 'WebView loadFile is not implemented on the current platform'); + } + + /// Loads the Flutter asset specified in the pubspec.yaml file. + /// + /// Throws an ArgumentError if [key] is not part of the specified assets + /// in the pubspec.yaml file. + Future loadFlutterAsset( + String key, ) { throw UnimplementedError( 'WebView loadFlutterAsset is not implemented on the current platform'); diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart index e9f5ef598b1d..6add51ce2d73 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/widgets.dart'; import 'package:webview_flutter_platform_interface/src/types/types.dart'; import 'auto_media_playback_policy.dart'; @@ -22,6 +23,7 @@ class CreationParams { this.userAgent, this.autoMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + this.backgroundColor, this.cookies = const [], }) : assert(autoMediaPlaybackPolicy != null); @@ -56,11 +58,16 @@ class CreationParams { /// Which restrictions apply on automatic media playback. final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy; + /// The background color of the webview. + /// + /// When null the platform's webview default background color is used. + final Color? backgroundColor; + /// The initial set of cookies to set before the webview does its first load. final List cookies; @override String toString() { - return 'CreationParams(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent, cookies: $cookies)'; + return 'CreationParams(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent, backgroundColor: $backgroundColor, cookies: $cookies)'; } } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml index 3c0b5fa34b63..5950cab1e46f 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/plugins/tree/master/packages/webview_flut issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview_flutter%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.6.0 +version: 1.8.0 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart b/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart index 0432373fc8ac..5d2c717be0e5 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; @@ -32,6 +33,35 @@ void main() { case 'canGoBack': case 'canGoForward': return true; + case 'loadFile': + if (methodCall.arguments == 'invalid file') { + throw PlatformException( + code: 'loadFile_failed', + message: 'Failed loading file.', + details: null); + } else if (methodCall.arguments == 'some error') { + throw PlatformException( + code: 'some_error', + message: 'Some error occurred.', + details: null, + ); + } + return null; + case 'loadFlutterAsset': + if (methodCall.arguments == 'invalid key') { + throw PlatformException( + code: 'loadFlutterAsset_invalidKey', + message: 'Failed loading asset.', + details: null, + ); + } else if (methodCall.arguments == 'some error') { + throw PlatformException( + code: 'some_error', + message: 'Some error occurred.', + details: null, + ); + } + return null; case 'runJavascriptReturningResult': case 'evaluateJavascript': return methodCall.arguments as String; @@ -73,6 +103,78 @@ void main() { ); }); + test('loadFile with invalid file', () async { + expect( + () => webViewPlatform.loadFile('invalid file'), + throwsA( + isA().having( + (ArgumentError error) => error.message, + 'message', + 'Failed loading file.', + ), + ), + ); + }); + + test('loadFile with some error.', () async { + expect( + () => webViewPlatform.loadFile('some error'), + throwsA( + isA().having( + (PlatformException error) => error.message, + 'message', + 'Some error occurred.', + ), + ), + ); + }); + + test('loadFlutterAsset', () async { + await webViewPlatform.loadFlutterAsset( + 'folder/asset.html', + ); + + expect( + log, + [ + isMethodCall( + 'loadFlutterAsset', + arguments: 'folder/asset.html', + ), + ], + ); + }); + + test('loadFlutterAsset with empty key', () async { + expect(() => webViewPlatform.loadFlutterAsset(''), throwsAssertionError); + }); + + test('loadFlutterAsset with invalid key', () async { + expect( + () => webViewPlatform.loadFlutterAsset('invalid key'), + throwsA( + isA().having( + (ArgumentError error) => error.message, + 'message', + 'Failed loading asset.', + ), + ), + ); + }); + + test('loadFlutterAsset with some error.', () async { + expect( + () => webViewPlatform.loadFlutterAsset('some error'), + throwsA( + isA().having( + (PlatformException error) => error.message, + 'message', + 'Some error occurred.', + ), + ), + ); + }); + test('loadHtmlString without base URL', () async { await webViewPlatform.loadHtmlString( 'Test HTML string', @@ -552,6 +654,32 @@ void main() { ], ); }); + + test('backgroundColor is null by default', () { + final CreationParams creationParams = CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.of('Dart Test'), + ), + ); + final Map creationParamsMap = + MethodChannelWebViewPlatform.creationParamsToMap(creationParams); + + expect(creationParamsMap['backgroundColor'], null); + }); + + test('backgroundColor is converted to an int', () { + const Color whiteColor = Color(0xFFFFFFFF); + final CreationParams creationParams = CreationParams( + backgroundColor: whiteColor, + webSettings: WebSettings( + userAgent: const WebSetting.of('Dart Test'), + ), + ); + final Map creationParamsMap = + MethodChannelWebViewPlatform.creationParamsToMap(creationParams); + + expect(creationParamsMap['backgroundColor'], whiteColor.value); + }); }); group('Tests on `plugins.flutter.io/cookie_manager` channel', () { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index c01cfc8e51e3..c1d27121f9ee 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,7 +1,13 @@ +## 2.6.0 + +* Implements new cookie manager for setting cookies and providing initial cookies. + ## 2.5.0 +* Adds an option to set the background color of the webview. +* Migrates from `analysis_options_legacy.yaml` to `analysis_options.yaml`. * Integration test fixes. -* Implemented new cookie manager for setting cookies and providing initial cookies. +* Updates to webview_flutter_platform_interface version 1.5.2. ## 2.4.0 @@ -21,7 +27,7 @@ ## 2.0.14 -* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). +* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). ## 2.0.13 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart index 00c7e998edc8..1e4adb9f7de0 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart @@ -191,7 +191,7 @@ void main() { await onPageFinished.future; resizeButtonTapped = true; - await tester.tap(find.byKey(const ValueKey('resizeButton'))); + await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); expect(buttonTapResizeCompleter.future, completes); }); @@ -446,10 +446,10 @@ void main() { testWidgets('Video plays inline when allowsInlineMediaPlayback is true', (WidgetTester tester) async { - Completer controllerCompleter = + final Completer controllerCompleter = Completer(); - Completer pageLoaded = Completer(); - Completer videoPlaying = Completer(); + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( @@ -480,7 +480,7 @@ void main() { ), ), ); - WebViewController controller = await controllerCompleter.future; + final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. @@ -489,7 +489,7 @@ void main() { // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; - String fullScreen = + final String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(false)); }); @@ -497,10 +497,10 @@ void main() { testWidgets( 'Video plays full screen when allowsInlineMediaPlayback is false', (WidgetTester tester) async { - Completer controllerCompleter = + final Completer controllerCompleter = Completer(); - Completer pageLoaded = Completer(); - Completer videoPlaying = Completer(); + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( @@ -531,7 +531,7 @@ void main() { ), ), ); - WebViewController controller = await controllerCompleter.future; + final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. @@ -540,7 +540,7 @@ void main() { // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; - String fullScreen = + final String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(true)); }); @@ -718,7 +718,7 @@ void main() { }); testWidgets('getTitle', (WidgetTester tester) async { - final String getTitleTest = ''' + const String getTitleTest = ''' Some title @@ -768,7 +768,7 @@ void main() { group('Programmatic Scroll', () { testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { - final String scrollTestPage = ''' + const String scrollTestPage = ''' @@ -815,7 +815,7 @@ void main() { final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; - await tester.pumpAndSettle(Duration(seconds: 3)); + await tester.pumpAndSettle(const Duration(seconds: 3)); int scrollPosX = await controller.getScrollX(); int scrollPosY = await controller.getScrollY(); @@ -845,7 +845,7 @@ void main() { }); group('NavigationDelegate', () { - final String blankPage = ""; + const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + base64Encode(const Utf8Encoder().convert(blankPage)); @@ -941,7 +941,7 @@ void main() { testWidgets( 'onWebResourceError only called for main frame', (WidgetTester tester) async { - final String iframeTest = ''' + const String iframeTest = ''' @@ -1161,19 +1161,12 @@ String _webviewBool(bool value) { /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. Future _getUserAgent(WebViewController controller) async { - return _runJavascriptReturningResult(controller, 'navigator.userAgent;'); -} - -Future _runJavascriptReturningResult( - WebViewController controller, String js) async { - if (defaultTargetPlatform == TargetPlatform.iOS) { - return await controller.runJavascriptReturningResult(js); - } - return jsonDecode(await controller.runJavascriptReturningResult(js)); + return await controller.runJavascriptReturningResult('navigator.userAgent;'); } class ResizableWebView extends StatefulWidget { - ResizableWebView({required this.onResize, required this.onPageFinished}); + const ResizableWebView( + {required this.onResize, required this.onPageFinished}); final JavascriptMessageHandler onResize; final VoidCallback onPageFinished; @@ -1228,14 +1221,14 @@ class ResizableWebViewState extends State { ), ), TextButton( - key: Key('resizeButton'), + key: const Key('resizeButton'), onPressed: () { setState(() { webViewWidth += 100.0; webViewHeight += 100.0; }); }, - child: Text('ResizeButton'), + child: const Text('ResizeButton'), ), ], ), diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/FLTWebViewUITests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/FLTWebViewUITests.m index d193be745972..e43fb97d41ef 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/FLTWebViewUITests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/FLTWebViewUITests.m @@ -5,6 +5,25 @@ @import XCTest; @import os.log; +static UIColor* getPixelColorInImage(CGImageRef image, size_t x, size_t y) { + CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image)); + const UInt8* data = CFDataGetBytePtr(pixelData); + + size_t bytesPerRow = CGImageGetBytesPerRow(image); + size_t pixelInfo = (bytesPerRow * y) + (x * 4); // 4 bytes per pixel + + UInt8 red = data[pixelInfo + 0]; + UInt8 green = data[pixelInfo + 1]; + UInt8 blue = data[pixelInfo + 2]; + UInt8 alpha = data[pixelInfo + 3]; + CFRelease(pixelData); + + return [UIColor colorWithRed:red / 255.0f + green:green / 255.0f + blue:blue / 255.0f + alpha:alpha / 255.0f]; +} + @interface FLTWebViewUITests : XCTestCase @property(nonatomic, strong) XCUIApplication* app; @end @@ -18,6 +37,54 @@ - (void)setUp { [self.app launch]; } +- (void)testTransparentBackground { + XCUIApplication* app = self.app; + XCUIElement* menu = app.buttons[@"Show menu"]; + if (![menu waitForExistenceWithTimeout:30.0]) { + os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); + XCTFail(@"Failed due to not able to find menu"); + } + [menu tap]; + + XCUIElement* transparentBackground = app.buttons[@"Transparent background example"]; + if (![transparentBackground waitForExistenceWithTimeout:30.0]) { + os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); + XCTFail(@"Failed due to not able to find Transparent background example"); + } + [transparentBackground tap]; + + XCUIElement* transparentBackgroundLoaded = + app.webViews.staticTexts[@"Transparent background test"]; + if (![transparentBackgroundLoaded waitForExistenceWithTimeout:30.0]) { + os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); + XCTFail(@"Failed due to not able to find Transparent background test"); + } + + XCUIScreenshot* screenshot = [[XCUIScreen mainScreen] screenshot]; + + UIImage* screenshotImage = screenshot.image; + CGImageRef screenshotCGImage = screenshotImage.CGImage; + UIColor* centerLeftColor = + getPixelColorInImage(screenshotCGImage, 0, CGImageGetHeight(screenshotCGImage) / 2); + UIColor* centerColor = + getPixelColorInImage(screenshotCGImage, CGImageGetWidth(screenshotCGImage) / 2, + CGImageGetHeight(screenshotCGImage) / 2); + + CGColorSpaceRef centerLeftColorSpace = CGColorGetColorSpace(centerLeftColor.CGColor); + // Flutter Colors.green color : 0xFF4CAF50 -> rgba(76, 175, 80, 1) + // https://github.com/flutter/flutter/blob/f4abaa0735eba4dfd8f33f73363911d63931fe03/packages/flutter/lib/src/material/colors.dart#L1208 + // The background color of the webview is : rgba(0, 0, 0, 0.5) + // The expected color is : rgba(38, 87, 40, 1) + CGFloat flutterGreenColorComponents[] = {38.0f / 255.0f, 87.0f / 255.0f, 40.0f / 255.0f, 1.0f}; + CGColorRef flutterGreenColor = CGColorCreate(centerLeftColorSpace, flutterGreenColorComponents); + CGFloat redColorComponents[] = {1.0f, 0.0f, 0.0f, 1.0f}; + CGColorRef redColor = CGColorCreate(centerLeftColorSpace, redColorComponents); + CGColorSpaceRelease(centerLeftColorSpace); + + XCTAssertTrue(CGColorEqualToColor(flutterGreenColor, centerLeftColor.CGColor)); + XCTAssertTrue(CGColorEqualToColor(redColor, centerColor.CGColor)); +} + - (void)testUserAgent { XCUIApplication* app = self.app; XCUIElement* menu = app.buttons[@"Show menu"]; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart index 46d1954fff8a..ce7548a87cab 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -19,7 +19,7 @@ import 'navigation_request.dart'; import 'web_view.dart'; void main() { - runApp(MaterialApp(home: _WebViewExample())); + runApp(const MaterialApp(home: _WebViewExample())); } const String kNavigationExamplePage = ''' @@ -37,6 +37,27 @@ The navigation delegate is set to block navigation to the youtube website. '''; +const String kTransparentBackgroundPage = ''' + + + + Transparent background test + + + +
+

Transparent background test

+
+
+ + +'''; + const String kLocalFileExamplePage = ''' @@ -47,8 +68,8 @@ const String kLocalFileExamplePage = '''

Local demo page

- This is an example page used to demonstrate how to load a local file or HTML - string using the Flutter + This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter webview plugin.

@@ -75,6 +96,7 @@ class _WebViewExampleState extends State<_WebViewExample> { @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: const Color(0xFF4CAF50), appBar: AppBar( title: const Text('Flutter WebView example'), // This drop down menu demonstrates that Flutter widgets can be shown over the web view. @@ -85,7 +107,7 @@ class _WebViewExampleState extends State<_WebViewExample> { ), // We're using a Builder here so we have a context that is below the Scaffold // to allow calling Scaffold.of(context) so we can show a snackbar. - body: Builder(builder: (context) { + body: Builder(builder: (BuildContext context) { return WebView( initialUrl: 'https://flutter.dev/', onWebViewCreated: (WebViewController controller) { @@ -101,6 +123,7 @@ class _WebViewExampleState extends State<_WebViewExample> { print('allowing navigation to $request'); return NavigationDecision.navigate; }, + backgroundColor: const Color(0x80000000), ); }), floatingActionButton: favoriteButton(), @@ -130,7 +153,7 @@ class _WebViewExampleState extends State<_WebViewExample> { } Set _createJavascriptChannels(BuildContext context) { - return { + return { JavascriptChannel( name: 'Snackbar', onMessageReceived: (JavascriptMessage message) { @@ -152,10 +175,11 @@ enum _MenuOptions { loadHtmlString, doPostRequest, setCookie, + transparentBackground, } class _SampleMenu extends StatelessWidget { - _SampleMenu(this.controller); + const _SampleMenu(this.controller); final Future controller; @@ -166,6 +190,7 @@ class _SampleMenu extends StatelessWidget { builder: (BuildContext context, AsyncSnapshot controller) { return PopupMenuButton<_MenuOptions>( + key: const ValueKey('ShowPopupMenu'), onSelected: (_MenuOptions value) { switch (value) { case _MenuOptions.showUserAgent: @@ -201,6 +226,9 @@ class _SampleMenu extends StatelessWidget { case _MenuOptions.setCookie: _onSetCookie(controller.data!, context); break; + case _MenuOptions.transparentBackground: + _onTransparentBackground(controller.data!, context); + break; } }, itemBuilder: (BuildContext context) => >[ @@ -249,13 +277,18 @@ class _SampleMenu extends StatelessWidget { value: _MenuOptions.setCookie, child: Text('Set Cookie'), ), + const PopupMenuItem<_MenuOptions>( + key: ValueKey('ShowTransparentBackgroundExample'), + value: _MenuOptions.transparentBackground, + child: Text('Transparent background example'), + ), ], ); }, ); } - void _onShowUserAgent( + Future _onShowUserAgent( WebViewController controller, BuildContext context) async { // Send a message with the user agent string to the Snackbar JavaScript channel we registered // with the WebView. @@ -263,7 +296,7 @@ class _SampleMenu extends StatelessWidget { 'Snackbar.postMessage("User Agent: " + navigator.userAgent);'); } - void _onListCookies( + Future _onListCookies( WebViewController controller, BuildContext context) async { final String cookies = await controller.runJavascriptReturningResult('document.cookie'); @@ -279,7 +312,8 @@ class _SampleMenu extends StatelessWidget { )); } - void _onAddToCache(WebViewController controller, BuildContext context) async { + Future _onAddToCache( + WebViewController controller, BuildContext context) async { await controller.runJavascript( 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";'); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( @@ -287,20 +321,22 @@ class _SampleMenu extends StatelessWidget { )); } - void _onListCache(WebViewController controller, BuildContext context) async { + Future _onListCache( + WebViewController controller, BuildContext context) async { await controller.runJavascript('caches.keys()' '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' '.then((caches) => Snackbar.postMessage(caches))'); } - void _onClearCache(WebViewController controller, BuildContext context) async { + Future _onClearCache( + WebViewController controller, BuildContext context) async { await controller.clearCache(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Cache cleared."), + content: Text('Cache cleared.'), )); } - void _onClearCookies( + Future _onClearCookies( WebViewController controller, BuildContext context) async { final bool hadCookies = await WebView.platform.clearCookies(); String message = 'There were cookies. Now, they are gone!'; @@ -312,37 +348,38 @@ class _SampleMenu extends StatelessWidget { )); } - void _onNavigationDelegateExample( + Future _onNavigationDelegateExample( WebViewController controller, BuildContext context) async { final String contentBase64 = base64Encode(const Utf8Encoder().convert(kNavigationExamplePage)); await controller.loadUrl('data:text/html;base64,$contentBase64'); } - void _onLoadLocalFileExample( + Future _onLoadLocalFileExample( WebViewController controller, BuildContext context) async { - String pathToIndex = await _prepareLocalFile(); + final String pathToIndex = await _prepareLocalFile(); await controller.loadFile(pathToIndex); } - void _onLoadHtmlStringExample( + Future _onLoadHtmlStringExample( WebViewController controller, BuildContext context) async { await controller.loadHtmlString(kLocalFileExamplePage); } - void _onDoPostRequest( + Future _onDoPostRequest( WebViewController controller, BuildContext context) async { - WebViewRequest request = WebViewRequest( + final WebViewRequest request = WebViewRequest( uri: Uri.parse('https://httpbin.org/post'), method: WebViewRequestMethod.post, - headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, + headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, body: Uint8List.fromList('Test Body'.codeUnits), ); await controller.loadRequest(request); } - void _onSetCookie(WebViewController controller, BuildContext context) async { + Future _onSetCookie( + WebViewController controller, BuildContext context) async { await WebViewCookieManager.instance.setCookie( const WebViewCookie( name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'), @@ -364,9 +401,14 @@ class _SampleMenu extends StatelessWidget { ); } + Future _onTransparentBackground( + WebViewController controller, BuildContext context) async { + await controller.loadHtmlString(kTransparentBackgroundPage); + } + static Future _prepareLocalFile() async { final String tmpDir = (await getTemporaryDirectory()).path; - File indexFile = File('$tmpDir/www/index.html'); + final File indexFile = File('$tmpDir/www/index.html'); await Directory('$tmpDir/www').create(recursive: true); await indexFile.writeAsString(kLocalFileExamplePage); @@ -403,7 +445,7 @@ class _NavigationControls extends StatelessWidget { } else { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( - const SnackBar(content: Text("No back history item")), + const SnackBar(content: Text('No back history item')), ); return; } @@ -420,7 +462,7 @@ class _NavigationControls extends StatelessWidget { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( const SnackBar( - content: Text("No forward history item")), + content: Text('No forward history item')), ); return; } @@ -442,4 +484,4 @@ class _NavigationControls extends StatelessWidget { } /// Callback type for handling messages sent from JavaScript running in a web view. -typedef void JavascriptMessageHandler(JavascriptMessage message); +typedef JavascriptMessageHandler = void Function(JavascriptMessage message); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart index c1ff8dc5a690..2f6d7c9f8cdd 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart @@ -14,6 +14,6 @@ class NavigationRequest { @override String toString() { - return '$runtimeType(url: $url, isForMainFrame: $isForMainFrame)'; + return 'NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart index 97f869858304..1efd0315072b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart @@ -16,7 +16,7 @@ import 'navigation_request.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); +typedef WebViewCreatedCallback = void Function(WebViewController controller); /// Decides how to handle a specific navigation request. /// @@ -24,20 +24,20 @@ typedef void WebViewCreatedCallback(WebViewController controller); /// `navigation` should be handled. /// /// See also: [WebView.navigationDelegate]. -typedef FutureOr NavigationDelegate( +typedef NavigationDelegate = FutureOr Function( NavigationRequest navigation); /// Signature for when a [WebView] has started loading a page. -typedef void PageStartedCallback(String url); +typedef PageStartedCallback = void Function(String url); /// Signature for when a [WebView] has finished loading a page. -typedef void PageFinishedCallback(String url); +typedef PageFinishedCallback = void Function(String url); /// Signature for when a [WebView] is loading a page. -typedef void PageLoadingCallback(int progress); +typedef PageLoadingCallback = void Function(int progress); /// Signature for when a [WebView] has failed to load a resource. -typedef void WebResourceErrorCallback(WebResourceError error); +typedef WebResourceErrorCallback = void Function(WebResourceError error); /// A web view widget for showing html content. /// @@ -55,7 +55,7 @@ class WebView extends StatefulWidget { Key? key, this.onWebViewCreated, this.initialUrl, - this.initialCookies = const [], + this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, @@ -71,6 +71,7 @@ class WebView extends StatefulWidget { this.initialMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, this.allowsInlineMediaPlayback = false, + this.backgroundColor, }) : assert(javascriptMode != null), assert(initialMediaPlaybackPolicy != null), assert(allowsInlineMediaPlayback != null), @@ -232,6 +233,12 @@ class WebView extends StatefulWidget { /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; + /// The background color of the [WebView]. + /// + /// When `null` the platform's webview default background color is used. By + /// default [backgroundColor] is `null`. + final Color? backgroundColor; + @override _WebViewState createState() => _WebViewState(); } @@ -264,7 +271,7 @@ class _WebViewState extends State { context: context, onWebViewPlatformCreated: (WebViewPlatformController? webViewPlatformController) { - WebViewController controller = WebViewController._( + final WebViewController controller = WebViewController._( widget, webViewPlatformController!, _javascriptChannelRegistry, @@ -284,6 +291,7 @@ class _WebViewState extends State { autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, cookies: widget.initialCookies, + backgroundColor: widget.backgroundColor, ), javascriptChannelRegistry: _javascriptChannelRegistry, ); @@ -553,7 +561,7 @@ class WebViewController { bool? hasNavigationDelegate; bool? hasProgressTracking; bool? debuggingEnabled; - WebSetting userAgent = WebSetting.absent(); + WebSetting userAgent = const WebSetting.absent(); if (currentValue.javascriptMode != newValue.javascriptMode) { javascriptMode = newValue.javascriptMode; } @@ -651,6 +659,7 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { } } + @override void onWebResourceError(WebResourceError error) { if (_webView.onWebResourceError != null) { _webView.onWebResourceError!(error); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml index c8001c8ffde4..2070b1161ba0 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: sdk: flutter path_provider: ^2.0.6 - + webview_flutter_wkwebview: # When depending on this package from a real application you should use: # webview_flutter: ^x.y.z @@ -21,10 +21,10 @@ dependencies: dev_dependencies: espresso: ^0.1.0+2 - flutter_test: - sdk: flutter flutter_driver: sdk: flutter + flutter_test: + sdk: flutter integration_test: sdk: flutter pedantic: ^1.10.0 @@ -34,4 +34,3 @@ flutter: assets: - assets/sample_audio.ogg - assets/sample_video.mp4 - \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m index 696fde918be9..ea45a8b9b534 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m @@ -103,6 +103,20 @@ - (instancetype)initWithFrame:(CGRect)frame inConfiguration:configuration]; _webView = [[FLTWKWebView alloc] initWithFrame:frame configuration:configuration]; + + // Background color + NSNumber* backgroundColorNSNumber = args[@"backgroundColor"]; + if ([backgroundColorNSNumber isKindOfClass:[NSNumber class]]) { + int backgroundColorInt = [backgroundColorNSNumber intValue]; + UIColor* backgroundColor = [UIColor colorWithRed:(backgroundColorInt >> 16 & 0xff) / 255.0 + green:(backgroundColorInt >> 8 & 0xff) / 255.0 + blue:(backgroundColorInt & 0xff) / 255.0 + alpha:(backgroundColorInt >> 24 & 0xff) / 255.0]; + _webView.opaque = NO; + _webView.backgroundColor = UIColor.clearColor; + _webView.scrollView.backgroundColor = backgroundColor; + } + _navigationDelegate = [[FLTWKNavigationDelegate alloc] initWithChannel:_channel]; _webView.UIDelegate = self; _webView.navigationDelegate = _navigationDelegate; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart index e4292e28e50e..cd00d9a7d14b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart @@ -45,6 +45,11 @@ class CupertinoWebView implements WebViewPlatform { } @override - Future clearCookies() => - WebViewCookieManagerPlatform.instance!.clearCookies(); + Future clearCookies() { + if (WebViewCookieManagerPlatform.instance == null) { + throw Exception( + 'Could not clear cookies as no implementation for WebViewCookieManagerPlatform has been registered.'); + } + return WebViewCookieManagerPlatform.instance!.clearCookies(); + } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index f6da86ddb636..fc274b453995 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.5.0 +version: 2.6.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -18,13 +18,11 @@ flutter: dependencies: flutter: sdk: flutter -# TODO (BeMacized): Update dependency when changes have been merged and published. - webview_flutter_platform_interface: - path: ../webview_flutter_platform_interface + webview_flutter_platform_interface: ^1.8.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter - pedantic: ^1.10.0 \ No newline at end of file + pedantic: ^1.10.0 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart index a6e5d5243005..54b1921583b9 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart @@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:webview_flutter_wkwebview/src/wkwebview_cookie_manager.dart'; -main() { +void main() { TestWidgetsFlutterBinding.ensureInitialized(); const MethodChannel cookieChannel = MethodChannel('plugins.flutter.io/cookie_manager'); @@ -52,7 +52,7 @@ main() { [ isMethodCall( 'setCookie', - arguments: { + arguments: { 'name': 'foo', 'value': 'bar', 'domain': 'flutter.dev', From 362e0ddaf68e3b36bf3180ae0b964fc0d1d83e74 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 7 Dec 2021 21:10:16 +0100 Subject: [PATCH 22/24] Fix merge errors --- .../DriverExtensionActivity.java~HEAD | 16 --------------- .../DriverExtensionActivity.java~master | 16 --------------- .../example/assets/www/index.html~HEAD | 20 ------------------- .../example/assets/www/index.html~master | 20 ------------------- 4 files changed, 72 deletions(-) delete mode 100644 packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java~HEAD delete mode 100644 packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java~master delete mode 100644 packages/webview_flutter/webview_flutter_android/example/assets/www/index.html~HEAD delete mode 100644 packages/webview_flutter/webview_flutter_android/example/assets/www/index.html~master diff --git a/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java~HEAD b/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java~HEAD deleted file mode 100644 index 59e1b04c37f2..000000000000 --- a/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java~HEAD +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Flutter 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.webviewflutterexample; - -import androidx.annotation.NonNull; -import io.flutter.embedding.android.FlutterActivity; - -public class DriverExtensionActivity extends FlutterActivity { - @Override - @NonNull - public String getDartEntrypointFunctionName() { - return "appMain"; - } -} diff --git a/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java~master b/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java~master deleted file mode 100644 index 59e1b04c37f2..000000000000 --- a/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java~master +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Flutter 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.webviewflutterexample; - -import androidx.annotation.NonNull; -import io.flutter.embedding.android.FlutterActivity; - -public class DriverExtensionActivity extends FlutterActivity { - @Override - @NonNull - public String getDartEntrypointFunctionName() { - return "appMain"; - } -} diff --git a/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html~HEAD b/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html~HEAD deleted file mode 100644 index 9895dd3ce6cb..000000000000 --- a/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html~HEAD +++ /dev/null @@ -1,20 +0,0 @@ - - - - -Load file or HTML string example - - - - -

Local demo page

-

- This is an example page used to demonstrate how to load a local file or HTML - string using the Flutter - webview plugin. -

- - - \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html~master b/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html~master deleted file mode 100644 index 9895dd3ce6cb..000000000000 --- a/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html~master +++ /dev/null @@ -1,20 +0,0 @@ - - - - -Load file or HTML string example - - - - -

Local demo page

-

- This is an example page used to demonstrate how to load a local file or HTML - string using the Flutter - webview plugin. -

- - - \ No newline at end of file From 79b3d546a22271e1006e5e67954f37700f17c74b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 7 Dec 2021 21:24:16 +0100 Subject: [PATCH 23/24] Fix changelog --- packages/webview_flutter/webview_flutter/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index cad52e0b840f..1df7e53d8e92 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,6 +1,6 @@ ## 2.7.0 -* Added `setCookie` to CookieManager. +* Adds `setCookie` to CookieManager. * CreationParams now supports setting `initialCookies`. ## 2.6.0 From bc01990d44b04409ac8cfb6718d4f8719d77cece Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 7 Dec 2021 21:25:13 +0100 Subject: [PATCH 24/24] Updated dependency --- packages/webview_flutter/webview_flutter/pubspec.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index 68e80200d205..cd6c618025fe 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -21,9 +21,7 @@ dependencies: sdk: flutter webview_flutter_android: ^2.8.0 webview_flutter_platform_interface: ^1.8.0 -# TODO(BeMacized): Update dependency once published - webview_flutter_wkwebview: - path: ../webview_flutter_wkwebview + webview_flutter_wkwebview: ^2.6.0 dev_dependencies: build_runner: ^2.1.5