From 0cc7db54ce5cd07c7dd91a2b64e6a8ad85f45cf7 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Thu, 28 Jul 2022 11:36:04 -0700 Subject: [PATCH] Guard against usage after async callbacks in RenderAndroidView, unregister listener (#108496) --- .../lib/src/rendering/platform_view.dart | 9 +++++ .../test/rendering/platform_view_test.dart | 39 +++++++++++++++++++ .../test/services/fake_platform_views.dart | 4 +- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/rendering/platform_view.dart b/packages/flutter/lib/src/rendering/platform_view.dart index faa846c7d94..5ab23e1c867 100644 --- a/packages/flutter/lib/src/rendering/platform_view.dart +++ b/packages/flutter/lib/src/rendering/platform_view.dart @@ -109,6 +109,7 @@ class RenderAndroidView extends PlatformViewRenderBox { /// Sets a new Android view controller. @override set controller(AndroidViewController controller) { + assert(!_isDisposed); assert(_viewController != null); assert(controller != null); if (_viewController == controller) { @@ -140,6 +141,7 @@ class RenderAndroidView extends PlatformViewRenderBox { } void _onPlatformViewCreated(int id) { + assert(!_isDisposed); markNeedsSemanticsUpdate(); } @@ -179,8 +181,14 @@ class RenderAndroidView extends PlatformViewRenderBox { targetSize = size; if (_viewController.isCreated) { _currentTextureSize = await _viewController.setSize(targetSize); + if (_isDisposed) { + return; + } } else { await _viewController.create(size: targetSize); + if (_isDisposed) { + return; + } _currentTextureSize = targetSize; } // We've resized the platform view to targetSize, but it is possible that @@ -248,6 +256,7 @@ class RenderAndroidView extends PlatformViewRenderBox { void dispose() { _isDisposed = true; _clipRectLayer.layer = null; + _viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated); super.dispose(); } diff --git a/packages/flutter/test/rendering/platform_view_test.dart b/packages/flutter/test/rendering/platform_view_test.dart index 9ede358bd2d..9f3d2b26bd6 100644 --- a/packages/flutter/test/rendering/platform_view_test.dart +++ b/packages/flutter/test/rendering/platform_view_test.dart @@ -221,6 +221,45 @@ void main() { expect(renderBox.debugLayer!.firstChild, isA()); }); }); + + test('markNeedsPaint does not get called on a disposed RO', () async { + FakeAsync().run((FakeAsync async) { + final AndroidViewController viewController = + PlatformViewsService.initAndroidView(id: 0, viewType: 'webview', layoutDirection: TextDirection.rtl); + final RenderAndroidView renderBox = RenderAndroidView( + viewController: viewController, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + gestureRecognizers: >{}, + ); + + final Completer viewCreation = Completer(); + const MethodChannel channel = MethodChannel('flutter/platform_views'); + binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (MethodCall methodCall) async { + assert(methodCall.method == 'create', 'Unexpected method call'); + await viewCreation.future; + return /*textureId=*/ 0; + }); + + layout(renderBox); + pumpFrame(phase: EnginePhase.paint); + + expect(renderBox.debugLayer, isNotNull); + expect(renderBox.debugLayer!.hasChildren, isFalse); + expect(viewController.isCreated, isFalse); + expect(renderBox.debugNeedsPaint, isFalse); + + renderBox.dispose(); + viewCreation.complete(); + async.flushMicrotasks(); + + expect(viewController.isCreated, isTrue); + expect(renderBox.debugNeedsPaint, isFalse); + expect(renderBox.debugLayer, isNull); + + pumpFrame(phase: EnginePhase.paint); + expect(renderBox.debugLayer, isNull); + }); + }); } ui.PointerData _pointerData( diff --git a/packages/flutter/test/services/fake_platform_views.dart b/packages/flutter/test/services/fake_platform_views.dart index 95d9c7e2678..d5ebe534c16 100644 --- a/packages/flutter/test/services/fake_platform_views.dart +++ b/packages/flutter/test/services/fake_platform_views.dart @@ -101,9 +101,7 @@ class FakeAndroidViewController implements AndroidViewController { } @override - void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) { - throw UnimplementedError(); - } + void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {} @override Future sendMotionEvent(AndroidMotionEvent event) {