From fd4f93d3a2ad24b3c1732bff2ffd5646777e264c Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 30 Jun 2022 15:44:02 -0700 Subject: [PATCH 1/8] add webview expensive surface android --- .../webview_flutter_test.dart | 93 +++++++++++ .../webview_expensive_surface_android.dart | 80 +++++++++ .../lib/webview_surface_android.dart | 3 +- .../test/surface_android_test.dart | 156 ++++++++++++++++++ 4 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 packages/webview_flutter/webview_flutter_android/lib/webview_expensive_surface_android.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index c5bf76d2c6cb..e65372f483bf 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -20,6 +20,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:webview_flutter_android/webview_android.dart'; import 'package:webview_flutter_android/webview_surface_android.dart'; +import 'package:webview_flutter_android/webview_expensive_surface_android.dart'; import 'package:webview_flutter_android_example/navigation_decision.dart'; import 'package:webview_flutter_android_example/navigation_request.dart'; import 'package:webview_flutter_android_example/web_view.dart'; @@ -1422,6 +1423,98 @@ Future main() async { ); }, ); + + testWidgets( + 'clearCache should clear local storage', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer onPageFinished = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (_) => onPageFinished.complete(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await onPageFinished.future; + + await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); + + expect( + controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ), + completion('"Tom"'), + ); + + await controller.clearCache(); + + expect( + controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ), + completion('null'), + ); + }, + ); + + setUpAll(() { + WebView.platform = SurfaceAndroidWebView(); + }); + + testWidgets( + 'ExpensiveSurfaceAndroidWebView uses ExpensiveAndroidViewController', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer onPageFinished = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (_) => onPageFinished.complete(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await onPageFinished.future; + + await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); + + expect( + controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ), + completion('"Tom"'), + ); + + await controller.clearCache(); + + expect( + controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ), + completion('null'), + ); + }, + ); } // JavaScript booleans evaluate to different string values on Android and iOS. diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_expensive_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_expensive_surface_android.dart new file mode 100644 index 000000000000..3b8b78f45ee3 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_expensive_surface_android.dart @@ -0,0 +1,80 @@ +// 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/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'src/android_webview.dart'; +import 'src/instance_manager.dart'; +import 'webview_android.dart'; +import 'webview_android_widget.dart'; + +/// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the [WebView] widget. +/// +/// To use this, set [WebView.platform] to an instance of this class. +/// +/// This implementation uses hybrid composition to render the [WebView] on +/// Android. It solves multiple issues related to accessibility and interaction +/// with the [WebView] at the cost of some performance on Android versions below +/// 10. See https://github.com/flutter/flutter/wiki/Hybrid-Composition for more +/// information. +class ExpensiveSurfaceAndroidWebView extends AndroidWebView { + @override + Widget build({ + required BuildContext context, + required CreationParams creationParams, + required JavascriptChannelRegistry javascriptChannelRegistry, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + }) { + return WebViewAndroidWidget( + useHybridComposition: true, + creationParams: creationParams, + callbacksHandler: webViewPlatformCallbacksHandler, + javascriptChannelRegistry: javascriptChannelRegistry, + onBuildWidget: (WebViewAndroidPlatformController controller) { + return PlatformViewLink( + viewType: 'plugins.flutter.io/webview', + surfaceFactory: ( + BuildContext context, + PlatformViewController controller, + ) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + gestureRecognizers: gestureRecognizers ?? + const >{}, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }, + onCreatePlatformView: (PlatformViewCreationParams params) { + return PlatformViewsService.initExpensiveAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/webview', + // WebView content is not affected by the Android view's layout direction, + // we explicitly set it here so that the widget doesn't require an ambient + // directionality. + layoutDirection: + Directionality.maybeOf(context) ?? TextDirection.ltr, + creationParams: + InstanceManager.instance.getInstanceId(controller.webView), + creationParamsCodec: const StandardMessageCodec(), + ) + ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) + ..addOnPlatformViewCreatedListener((int id) { + if (onWebViewPlatformCreated != null) { + onWebViewPlatformCreated(controller); + } + }) + ..create(); + }, + ); + }, + ); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index 00d7c8c53b7f..c91f42b53884 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -59,7 +59,8 @@ class SurfaceAndroidWebView extends AndroidWebView { // WebView content is not affected by the Android view's layout direction, // we explicitly set it here so that the widget doesn't require an ambient // directionality. - layoutDirection: TextDirection.rtl, + layoutDirection: + Directionality.maybeOf(context) ?? TextDirection.ltr, creationParams: InstanceManager.instance.getInstanceId(controller.webView), creationParamsCodec: const StandardMessageCodec(), diff --git a/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart b/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart new file mode 100644 index 000000000000..cafaa38f60e0 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart @@ -0,0 +1,156 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter_android/webview_expensive_surface_android.dart'; +import 'package:webview_flutter_android/webview_surface_android.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('ExpensiveSurfaceAndroidWebView', () { + late List log; + + setUpAll(() { + SystemChannels.platform_views.setMockMethodCallHandler( + (MethodCall call) async { + log.add(call); + }, + ); + }); + + tearDownAll(() { + SystemChannels.platform_views.setMockMethodCallHandler(null); + }); + + setUp(() { + log = []; + }); + + testWidgets('uses hybrid composition', (WidgetTester tester) async { + await tester.pumpWidget(Builder(builder: (BuildContext context) { + return ExpensiveSurfaceAndroidWebView().build( + context: context, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + )), + javascriptChannelRegistry: JavascriptChannelRegistry(null), + webViewPlatformCallbacksHandler: + TestWebViewPlatformCallbacksHandler(), + ); + })); + await tester.pumpAndSettle(); + + final MethodCall createMethodCall = log[0]; + expect(createMethodCall.method, 'create'); + expect(createMethodCall.arguments, containsPair('hybrid', true)); + }); + + testWidgets('default text direction is ltr', (WidgetTester tester) async { + await tester.pumpWidget(Builder(builder: (BuildContext context) { + return ExpensiveSurfaceAndroidWebView().build( + context: context, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + )), + javascriptChannelRegistry: JavascriptChannelRegistry(null), + webViewPlatformCallbacksHandler: + TestWebViewPlatformCallbacksHandler(), + ); + })); + await tester.pumpAndSettle(); + + final MethodCall createMethodCall = log[0]; + expect(createMethodCall.method, 'create'); + expect( + createMethodCall.arguments, + containsPair( + 'direction', + AndroidViewController.kAndroidLayoutDirectionLtr, + ), + ); + }); + }); + + group('SurfaceAndroidWebView', () { + late List log; + + setUpAll(() { + SystemChannels.platform_views.setMockMethodCallHandler( + (MethodCall call) async { + log.add(call); + if (call.method == 'resize') { + return { + 'width': call.arguments['width'], + 'height': call.arguments['height'], + }; + } + }, + ); + }); + + tearDownAll(() { + SystemChannels.platform_views.setMockMethodCallHandler(null); + }); + + setUp(() { + log = []; + }); + + testWidgets('default text direction is ltr', (WidgetTester tester) async { + await tester.pumpWidget(Builder(builder: (BuildContext context) { + return SurfaceAndroidWebView().build( + context: context, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + )), + javascriptChannelRegistry: JavascriptChannelRegistry(null), + webViewPlatformCallbacksHandler: + TestWebViewPlatformCallbacksHandler(), + ); + })); + await tester.pumpAndSettle(); + + final MethodCall createMethodCall = log[0]; + expect(createMethodCall.method, 'create'); + expect( + createMethodCall.arguments, + containsPair( + 'direction', + AndroidViewController.kAndroidLayoutDirectionLtr, + ), + ); + }); + }); +} + +class TestWebViewPlatformCallbacksHandler + implements WebViewPlatformCallbacksHandler { + @override + FutureOr onNavigationRequest({ + required String url, + required bool isForMainFrame, + }) { + throw UnimplementedError(); + } + + @override + void onPageFinished(String url) {} + + @override + void onPageStarted(String url) {} + + @override + void onProgress(int progress) {} + + @override + void onWebResourceError(WebResourceError error) {} +} From 35ba65c602182414b421fe1a016dee3b82ec9cda Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 30 Jun 2022 15:48:15 -0700 Subject: [PATCH 2/8] version bump --- .../webview_flutter/webview_flutter_android/CHANGELOG.md | 5 ++++- .../webview_flutter/webview_flutter_android/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 7261cc1c2cb5..09a825167190 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,6 +1,9 @@ -## NEXT +## 2.9.0 * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). +* Fixes bug where `Directionality` from context didn't affect `SurfaceAndroidWebView`. +* Fixes bug where default text direction is different for `SurfaceAndroidWebView` and `AndroidWebView`. +* Adds support for hybrid composition though `ExpensiveSurfaceAndroidWebView`. ## 2.8.14 diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index a386cad9028c..82e0f26f0dd6 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/main/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.8.14 +version: 2.9.0 environment: sdk: ">=2.14.0 <3.0.0" From 8df3aca57dc993946470a011ffdc459c2536af8f Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 30 Jun 2022 15:52:47 -0700 Subject: [PATCH 3/8] docs and stuff --- .../webview_flutter_android/CHANGELOG.md | 3 +- .../webview_flutter_test.dart | 93 ------------------- .../lib/webview_surface_android.dart | 6 -- 3 files changed, 2 insertions(+), 100 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 09a825167190..fd271ebc4b94 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -2,7 +2,8 @@ * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). * Fixes bug where `Directionality` from context didn't affect `SurfaceAndroidWebView`. -* Fixes bug where default text direction is different for `SurfaceAndroidWebView` and `AndroidWebView`. +* Fixes bug where default text direction was different for `SurfaceAndroidWebView` and `AndroidWebView`. + Default is now `TextDirection.ltr` for both. * Adds support for hybrid composition though `ExpensiveSurfaceAndroidWebView`. ## 2.8.14 diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index e65372f483bf..c5bf76d2c6cb 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -20,7 +20,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:webview_flutter_android/webview_android.dart'; import 'package:webview_flutter_android/webview_surface_android.dart'; -import 'package:webview_flutter_android/webview_expensive_surface_android.dart'; import 'package:webview_flutter_android_example/navigation_decision.dart'; import 'package:webview_flutter_android_example/navigation_request.dart'; import 'package:webview_flutter_android_example/web_view.dart'; @@ -1423,98 +1422,6 @@ Future main() async { ); }, ); - - testWidgets( - 'clearCache should clear local storage', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer onPageFinished = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (_) => onPageFinished.complete(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ); - - final WebViewController controller = await controllerCompleter.future; - await onPageFinished.future; - - await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); - - expect( - controller.runJavascriptReturningResult( - 'localStorage.getItem("myCat");', - ), - completion('"Tom"'), - ); - - await controller.clearCache(); - - expect( - controller.runJavascriptReturningResult( - 'localStorage.getItem("myCat");', - ), - completion('null'), - ); - }, - ); - - setUpAll(() { - WebView.platform = SurfaceAndroidWebView(); - }); - - testWidgets( - 'ExpensiveSurfaceAndroidWebView uses ExpensiveAndroidViewController', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer onPageFinished = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (_) => onPageFinished.complete(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ); - - final WebViewController controller = await controllerCompleter.future; - await onPageFinished.future; - - await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); - - expect( - controller.runJavascriptReturningResult( - 'localStorage.getItem("myCat");', - ), - completion('"Tom"'), - ); - - await controller.clearCache(); - - expect( - controller.runJavascriptReturningResult( - 'localStorage.getItem("myCat");', - ), - completion('null'), - ); - }, - ); } // JavaScript booleans evaluate to different string values on Android and iOS. diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index c91f42b53884..26d6ff91aae8 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -17,12 +17,6 @@ import 'webview_android_widget.dart'; /// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the [WebView] widget. /// /// To use this, set [WebView.platform] to an instance of this class. -/// -/// This implementation uses hybrid composition to render the [WebView] on -/// Android. It solves multiple issues related to accessibility and interaction -/// with the [WebView] at the cost of some performance on Android versions below -/// 10. See https://github.com/flutter/flutter/wiki/Hybrid-Composition for more -/// information. class SurfaceAndroidWebView extends AndroidWebView { @override Widget build({ From b33b7402ce0aae72869751a5779fc56c9c9d33e0 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 30 Jun 2022 16:00:19 -0700 Subject: [PATCH 4/8] license and flutter version bump --- packages/webview_flutter/webview_flutter_android/CHANGELOG.md | 1 + packages/webview_flutter/webview_flutter_android/pubspec.yaml | 2 +- .../webview_flutter_android/test/surface_android_test.dart | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index fd271ebc4b94..794460fcf97b 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -5,6 +5,7 @@ * Fixes bug where default text direction was different for `SurfaceAndroidWebView` and `AndroidWebView`. Default is now `TextDirection.ltr` for both. * Adds support for hybrid composition though `ExpensiveSurfaceAndroidWebView`. +* Raises minimum Flutter version to 3.0.0. ## 2.8.14 diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 82e0f26f0dd6..aaa4984bc8a1 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.9.0 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.8.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart b/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart index cafaa38f60e0..43aa7dde2eba 100644 --- a/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/surface_android_test.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. + import 'dart:async'; import 'package:flutter/services.dart'; From c445c8ef15ae9e9b01eacbdd77511d40f399e709 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 30 Jun 2022 17:22:04 -0700 Subject: [PATCH 5/8] spelling --- packages/webview_flutter/webview_flutter_android/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 794460fcf97b..62247afcbfef 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -4,7 +4,7 @@ * Fixes bug where `Directionality` from context didn't affect `SurfaceAndroidWebView`. * Fixes bug where default text direction was different for `SurfaceAndroidWebView` and `AndroidWebView`. Default is now `TextDirection.ltr` for both. -* Adds support for hybrid composition though `ExpensiveSurfaceAndroidWebView`. +* Adds support for hybrid composition through `ExpensiveSurfaceAndroidWebView`. * Raises minimum Flutter version to 3.0.0. ## 2.8.14 From c39b717ba8d08b1eec175aee393bc137168977b3 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 6 Jul 2022 15:05:24 -0700 Subject: [PATCH 6/8] update to automatically switch --- .../webview_flutter_android/CHANGELOG.md | 4 +- .../webview_expensive_surface_android.dart | 80 ------------------- .../lib/webview_surface_android.dart | 61 +++++++++++--- .../test/surface_android_test.dart | 77 ++++-------------- 4 files changed, 67 insertions(+), 155 deletions(-) delete mode 100644 packages/webview_flutter/webview_flutter_android/lib/webview_expensive_surface_android.dart diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 62247afcbfef..a8e9a927d5a8 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -4,7 +4,9 @@ * Fixes bug where `Directionality` from context didn't affect `SurfaceAndroidWebView`. * Fixes bug where default text direction was different for `SurfaceAndroidWebView` and `AndroidWebView`. Default is now `TextDirection.ltr` for both. -* Adds support for hybrid composition through `ExpensiveSurfaceAndroidWebView`. +* Fixes bug where setting WebView to a transparent background could cause visual errors when using + `SurfaceAndroidWebView`. Hybrid composition is now used when the background color is not 100% + opaque. * Raises minimum Flutter version to 3.0.0. ## 2.8.14 diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_expensive_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_expensive_surface_android.dart deleted file mode 100644 index 3b8b78f45ee3..000000000000 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_expensive_surface_android.dart +++ /dev/null @@ -1,80 +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/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; - -import 'src/android_webview.dart'; -import 'src/instance_manager.dart'; -import 'webview_android.dart'; -import 'webview_android_widget.dart'; - -/// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the [WebView] widget. -/// -/// To use this, set [WebView.platform] to an instance of this class. -/// -/// This implementation uses hybrid composition to render the [WebView] on -/// Android. It solves multiple issues related to accessibility and interaction -/// with the [WebView] at the cost of some performance on Android versions below -/// 10. See https://github.com/flutter/flutter/wiki/Hybrid-Composition for more -/// information. -class ExpensiveSurfaceAndroidWebView extends AndroidWebView { - @override - Widget build({ - required BuildContext context, - required CreationParams creationParams, - required JavascriptChannelRegistry javascriptChannelRegistry, - WebViewPlatformCreatedCallback? onWebViewPlatformCreated, - Set>? gestureRecognizers, - required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, - }) { - return WebViewAndroidWidget( - useHybridComposition: true, - creationParams: creationParams, - callbacksHandler: webViewPlatformCallbacksHandler, - javascriptChannelRegistry: javascriptChannelRegistry, - onBuildWidget: (WebViewAndroidPlatformController controller) { - return PlatformViewLink( - viewType: 'plugins.flutter.io/webview', - surfaceFactory: ( - BuildContext context, - PlatformViewController controller, - ) { - return AndroidViewSurface( - controller: controller as AndroidViewController, - gestureRecognizers: gestureRecognizers ?? - const >{}, - hitTestBehavior: PlatformViewHitTestBehavior.opaque, - ); - }, - onCreatePlatformView: (PlatformViewCreationParams params) { - return PlatformViewsService.initExpensiveAndroidView( - id: params.id, - viewType: 'plugins.flutter.io/webview', - // WebView content is not affected by the Android view's layout direction, - // we explicitly set it here so that the widget doesn't require an ambient - // directionality. - layoutDirection: - Directionality.maybeOf(context) ?? TextDirection.ltr, - creationParams: - InstanceManager.instance.getInstanceId(controller.webView), - creationParamsCodec: const StandardMessageCodec(), - ) - ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) - ..addOnPlatformViewCreatedListener((int id) { - if (onWebViewPlatformCreated != null) { - onWebViewPlatformCreated(controller); - } - }) - ..create(); - }, - ); - }, - ); - } -} diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index 26d6ff91aae8..6129900760e4 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -14,9 +14,20 @@ import 'src/instance_manager.dart'; import 'webview_android.dart'; import 'webview_android_widget.dart'; -/// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the [WebView] widget. +/// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the +/// [WebView] widget. /// /// To use this, set [WebView.platform] to an instance of this class. +/// +/// This implementation uses [AndroidViewSurface] to render the [WebView] on +/// Android. It solves multiple issues related to accessibility and interaction +/// with the [WebView] at the cost of some performance on Android versions below +/// 10. +/// +/// To support transparent backgrounds, this implementation uses hybrid +/// composition when `CreationParams.backgroundColor` is less than 1.0. See +/// https://github.com/flutter/flutter/wiki/Hybrid-Composition for more +/// information. class SurfaceAndroidWebView extends AndroidWebView { @override Widget build({ @@ -47,18 +58,42 @@ class SurfaceAndroidWebView extends AndroidWebView { ); }, onCreatePlatformView: (PlatformViewCreationParams params) { - return PlatformViewsService.initSurfaceAndroidView( - id: params.id, - viewType: 'plugins.flutter.io/webview', - // WebView content is not affected by the Android view's layout direction, - // we explicitly set it here so that the widget doesn't require an ambient - // directionality. - layoutDirection: - Directionality.maybeOf(context) ?? TextDirection.ltr, - creationParams: - InstanceManager.instance.getInstanceId(controller.webView), - creationParamsCodec: const StandardMessageCodec(), - ) + late final AndroidViewController viewController; + + // On some Android devices, transparent backgrounds can cause + // rendering issues on the non hybrid composition + // AndroidViewSurface. This switches the WebView to Hybric + // Composition when then background color is not 100% opaque. + final Color? backgroundColor = creationParams.backgroundColor; + if (backgroundColor != null && backgroundColor.opacity < 1.0) { + viewController = PlatformViewsService.initExpensiveAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/webview', + // WebView content is not affected by the Android view's layout direction, + // we explicitly set it here so that the widget doesn't require an ambient + // directionality. + layoutDirection: + Directionality.maybeOf(context) ?? TextDirection.ltr, + creationParams: + InstanceManager.instance.getInstanceId(controller.webView), + creationParamsCodec: const StandardMessageCodec(), + ); + } else { + viewController = PlatformViewsService.initSurfaceAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/webview', + // WebView content is not affected by the Android view's layout direction, + // we explicitly set it here so that the widget doesn't require an ambient + // directionality. + layoutDirection: + Directionality.maybeOf(context) ?? TextDirection.ltr, + creationParams: + InstanceManager.instance.getInstanceId(controller.webView), + creationParamsCodec: const StandardMessageCodec(), + ); + } + + return viewController ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) ..addOnPlatformViewCreatedListener((int id) { if (onWebViewPlatformCreated != null) { diff --git a/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart b/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart index 43aa7dde2eba..63e752b419e6 100644 --- a/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart @@ -4,23 +4,28 @@ import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter_android/webview_expensive_surface_android.dart'; import 'package:webview_flutter_android/webview_surface_android.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('ExpensiveSurfaceAndroidWebView', () { + group('SurfaceAndroidWebView', () { late List log; setUpAll(() { SystemChannels.platform_views.setMockMethodCallHandler( (MethodCall call) async { log.add(call); + if (call.method == 'resize') { + return { + 'width': call.arguments['width'], + 'height': call.arguments['height'], + }; + } }, ); }); @@ -33,15 +38,18 @@ void main() { log = []; }); - testWidgets('uses hybrid composition', (WidgetTester tester) async { + testWidgets( + 'uses hybrid composition when background color is not 100% opaque', + (WidgetTester tester) async { await tester.pumpWidget(Builder(builder: (BuildContext context) { - return ExpensiveSurfaceAndroidWebView().build( + return SurfaceAndroidWebView().build( context: context, creationParams: CreationParams( + backgroundColor: Colors.transparent, webSettings: WebSettings( - userAgent: const WebSetting.absent(), - hasNavigationDelegate: false, - )), + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + )), javascriptChannelRegistry: JavascriptChannelRegistry(null), webViewPlatformCallbacksHandler: TestWebViewPlatformCallbacksHandler(), @@ -54,59 +62,6 @@ void main() { expect(createMethodCall.arguments, containsPair('hybrid', true)); }); - testWidgets('default text direction is ltr', (WidgetTester tester) async { - await tester.pumpWidget(Builder(builder: (BuildContext context) { - return ExpensiveSurfaceAndroidWebView().build( - context: context, - creationParams: CreationParams( - webSettings: WebSettings( - userAgent: const WebSetting.absent(), - hasNavigationDelegate: false, - )), - javascriptChannelRegistry: JavascriptChannelRegistry(null), - webViewPlatformCallbacksHandler: - TestWebViewPlatformCallbacksHandler(), - ); - })); - await tester.pumpAndSettle(); - - final MethodCall createMethodCall = log[0]; - expect(createMethodCall.method, 'create'); - expect( - createMethodCall.arguments, - containsPair( - 'direction', - AndroidViewController.kAndroidLayoutDirectionLtr, - ), - ); - }); - }); - - group('SurfaceAndroidWebView', () { - late List log; - - setUpAll(() { - SystemChannels.platform_views.setMockMethodCallHandler( - (MethodCall call) async { - log.add(call); - if (call.method == 'resize') { - return { - 'width': call.arguments['width'], - 'height': call.arguments['height'], - }; - } - }, - ); - }); - - tearDownAll(() { - SystemChannels.platform_views.setMockMethodCallHandler(null); - }); - - setUp(() { - log = []; - }); - testWidgets('default text direction is ltr', (WidgetTester tester) async { await tester.pumpWidget(Builder(builder: (BuildContext context) { return SurfaceAndroidWebView().build( From 850b3ed3a778ad572af35cea7639cef737b1e86b Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 7 Jul 2022 09:43:19 -0700 Subject: [PATCH 7/8] fix docs --- .../lib/webview_surface_android.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index 6129900760e4..c299883238b9 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -24,8 +24,9 @@ import 'webview_android_widget.dart'; /// with the [WebView] at the cost of some performance on Android versions below /// 10. /// -/// To support transparent backgrounds, this implementation uses hybrid -/// composition when `CreationParams.backgroundColor` is less than 1.0. See +/// To support transparent backgrounds on all Android devices, this +/// implementation uses hybrid composition when the opacity of +/// `CreationParams.backgroundColor` is less than 1.0. See /// https://github.com/flutter/flutter/wiki/Hybrid-Composition for more /// information. class SurfaceAndroidWebView extends AndroidWebView { @@ -62,8 +63,8 @@ class SurfaceAndroidWebView extends AndroidWebView { // On some Android devices, transparent backgrounds can cause // rendering issues on the non hybrid composition - // AndroidViewSurface. This switches the WebView to Hybric - // Composition when then background color is not 100% opaque. + // AndroidViewSurface. This switches the WebView to Hybrid + // Composition when the background color is not 100% opaque. final Color? backgroundColor = creationParams.backgroundColor; if (backgroundColor != null && backgroundColor.opacity < 1.0) { viewController = PlatformViewsService.initExpensiveAndroidView( From 854a31154b0dc510bef06f074fc6ae6937f343d6 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 7 Jul 2022 09:50:48 -0700 Subject: [PATCH 8/8] helper method --- .../lib/webview_surface_android.dart | 77 ++++++++++--------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index c299883238b9..61ec11012881 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -59,42 +59,24 @@ class SurfaceAndroidWebView extends AndroidWebView { ); }, onCreatePlatformView: (PlatformViewCreationParams params) { - late final AndroidViewController viewController; - - // On some Android devices, transparent backgrounds can cause - // rendering issues on the non hybrid composition - // AndroidViewSurface. This switches the WebView to Hybrid - // Composition when the background color is not 100% opaque. final Color? backgroundColor = creationParams.backgroundColor; - if (backgroundColor != null && backgroundColor.opacity < 1.0) { - viewController = PlatformViewsService.initExpensiveAndroidView( - id: params.id, - viewType: 'plugins.flutter.io/webview', - // WebView content is not affected by the Android view's layout direction, - // we explicitly set it here so that the widget doesn't require an ambient - // directionality. - layoutDirection: - Directionality.maybeOf(context) ?? TextDirection.ltr, - creationParams: - InstanceManager.instance.getInstanceId(controller.webView), - creationParamsCodec: const StandardMessageCodec(), - ); - } else { - viewController = PlatformViewsService.initSurfaceAndroidView( - id: params.id, - viewType: 'plugins.flutter.io/webview', - // WebView content is not affected by the Android view's layout direction, - // we explicitly set it here so that the widget doesn't require an ambient - // directionality. - layoutDirection: - Directionality.maybeOf(context) ?? TextDirection.ltr, - creationParams: - InstanceManager.instance.getInstanceId(controller.webView), - creationParamsCodec: const StandardMessageCodec(), - ); - } - - return viewController + return _createViewController( + // On some Android devices, transparent backgrounds can cause + // rendering issues on the non hybrid composition + // AndroidViewSurface. This switches the WebView to Hybrid + // Composition when the background color is not 100% opaque. + hybridComposition: + backgroundColor != null && backgroundColor.opacity < 1.0, + id: params.id, + viewType: 'plugins.flutter.io/webview', + // WebView content is not affected by the Android view's layout direction, + // we explicitly set it here so that the widget doesn't require an ambient + // directionality. + layoutDirection: + Directionality.maybeOf(context) ?? TextDirection.ltr, + webViewIdentifier: + InstanceManager.instance.getInstanceId(controller.webView)!, + ) ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) ..addOnPlatformViewCreatedListener((int id) { if (onWebViewPlatformCreated != null) { @@ -107,4 +89,29 @@ class SurfaceAndroidWebView extends AndroidWebView { }, ); } + + AndroidViewController _createViewController({ + required bool hybridComposition, + required int id, + required String viewType, + required TextDirection layoutDirection, + required int webViewIdentifier, + }) { + if (hybridComposition) { + return PlatformViewsService.initExpensiveAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: webViewIdentifier, + creationParamsCodec: const StandardMessageCodec(), + ); + } + return PlatformViewsService.initSurfaceAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: webViewIdentifier, + creationParamsCodec: const StandardMessageCodec(), + ); + } }