diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index cecfa354219c1..933efdcbbd119 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -118,6 +118,9 @@ class HtmlViewEmbedder { /// If this returns a [CkCanvas], then that canvas should be the new leaf /// node. Otherwise, keep the same leaf node. CkCanvas? compositeEmbeddedView(int viewId) { + // Ensure platform view with `viewId` is injected into the `rasterizer.view`. + rasterizer.view.dom.injectPlatformView(viewId); + final int overlayIndex = _context.visibleViewCount; _compositionOrder.add(viewId); // Keep track of the number of visible platform views. @@ -142,10 +145,10 @@ class HtmlViewEmbedder { return recorderToUseForRendering?.recordingCanvas; } - void _compositeWithParams(int viewId, EmbeddedViewParams params) { + void _compositeWithParams(int platformViewId, EmbeddedViewParams params) { // If we haven't seen this viewId yet, cache it for clips/transforms. - final ViewClipChain clipChain = _viewClipChains.putIfAbsent(viewId, () { - return ViewClipChain(view: createPlatformViewSlot(viewId)); + final ViewClipChain clipChain = _viewClipChains.putIfAbsent(platformViewId, () { + return ViewClipChain(view: createPlatformViewSlot(platformViewId)); }); final DomElement slot = clipChain.slot; @@ -175,7 +178,7 @@ class HtmlViewEmbedder { } // Apply mutators to the slot - _applyMutators(params, slot, viewId); + _applyMutators(params, slot, platformViewId); } int _countClips(MutatorsStack mutators) { diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index 21b2a8fe2a467..6cc26d0a6c6b4 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -602,6 +602,10 @@ extension DomElementExtension on DomElement { external DomElement? _querySelector(JSString selectors); DomElement? querySelector(String selectors) => _querySelector(selectors.toJS); + @JS('matches') + external JSBoolean _matches(JSString selectors); + bool matches(String selectors) => _matches(selectors.toJS).toDart; + @JS('querySelectorAll') external _DomList _querySelectorAll(JSString selectors); Iterable querySelectorAll(String selectors) => diff --git a/lib/web_ui/lib/src/engine/html/platform_view.dart b/lib/web_ui/lib/src/engine/html/platform_view.dart index d35ec97995207..4143c329f964d 100644 --- a/lib/web_ui/lib/src/engine/html/platform_view.dart +++ b/lib/web_ui/lib/src/engine/html/platform_view.dart @@ -3,14 +3,21 @@ // found in the LICENSE file. import '../dom.dart'; +import '../platform_dispatcher.dart'; import '../platform_views/slots.dart'; +import '../window.dart'; import 'surface.dart'; /// A surface containing a platform view, which is an HTML element. class PersistedPlatformView extends PersistedLeafSurface { - PersistedPlatformView(this.viewId, this.dx, this.dy, this.width, this.height); + PersistedPlatformView(this.platformViewId, this.dx, this.dy, this.width, this.height) { + // Ensure platform view with `viewId` is injected into the `implicitView` + // before rendering its shadow DOM `slot`. + final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!; + implicitView.dom.injectPlatformView(platformViewId); + } - final int viewId; + final int platformViewId; final double dx; final double dy; final double width; @@ -18,7 +25,7 @@ class PersistedPlatformView extends PersistedLeafSurface { @override DomElement createElement() { - return createPlatformViewSlot(viewId); + return createPlatformViewSlot(platformViewId); } @override @@ -36,21 +43,21 @@ class PersistedPlatformView extends PersistedLeafSurface { bool canUpdateAsMatch(PersistedSurface oldSurface) { if (super.canUpdateAsMatch(oldSurface)) { // super checks the runtimeType of the surface, so we can just cast... - return viewId == ((oldSurface as PersistedPlatformView).viewId); + return platformViewId == ((oldSurface as PersistedPlatformView).platformViewId); } return false; } @override double matchForUpdate(PersistedPlatformView existingSurface) { - return existingSurface.viewId == viewId ? 0.0 : 1.0; + return existingSurface.platformViewId == platformViewId ? 0.0 : 1.0; } @override void update(PersistedPlatformView oldSurface) { assert( - viewId == oldSurface.viewId, - 'PersistedPlatformView with different viewId should never be updated. Check the canUpdateAsMatch method.', + platformViewId == oldSurface.platformViewId, + 'PersistedPlatformView with different platformViewId should never be updated. Check the canUpdateAsMatch method.', ); super.update(oldSurface); // Only update if the view has been resized diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index ec17dee09334d..b783bcb9b0c31 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -78,6 +78,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { _addLocaleChangedListener(); registerHotRestartListener(dispose); _setAppLifecycleState(ui.AppLifecycleState.resumed); + viewManager.onViewDisposed.listen((_) { + // Send a metrics changed event to the framework when a view is disposed. + // View creation/resize is handled by the `_didResize` handler in the + // EngineFlutterView itself. + invokeOnMetricsChanged(); + }); } /// The [EnginePlatformDispatcher] singleton. @@ -615,18 +621,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { _handleWebTestEnd2EndMessage(jsonCodec, data))); return; - case 'flutter/platform_views': + case PlatformViewMessageHandler.channelName: + // `arguments` can be a Map for `create`, + // but an `int` for `dispose`, hence why `dynamic` everywhere. final MethodCall(:String method, :dynamic arguments) = standardCodec.decodeMethodCall(data); - final int? flutterViewId = tryViewId(arguments); - if (flutterViewId == null) { - implicitView!.platformViewMessageHandler - .handleLegacyPlatformViewCall(method, arguments, callback!); - return; - } - arguments as Map; - viewManager[flutterViewId]! - .platformViewMessageHandler + PlatformViewMessageHandler.instance .handlePlatformViewCall(method, arguments, callback!); return; diff --git a/lib/web_ui/lib/src/engine/platform_views/content_manager.dart b/lib/web_ui/lib/src/engine/platform_views/content_manager.dart index 222e349e0910d..bfad69bf911b8 100644 --- a/lib/web_ui/lib/src/engine/platform_views/content_manager.dart +++ b/lib/web_ui/lib/src/engine/platform_views/content_manager.dart @@ -38,8 +38,7 @@ class PlatformViewManager { /// The shared instance of PlatformViewManager shared across the engine to handle /// rendering of PlatformViews into the web app. - // TODO(dit): How to make this overridable from tests? - static final PlatformViewManager instance = PlatformViewManager(); + static PlatformViewManager instance = PlatformViewManager(); // The factory functions, indexed by the viewType final Map _factories = {}; @@ -65,6 +64,20 @@ class PlatformViewManager { return _contents.containsKey(viewId); } + /// Returns the cached contents of [viewId], to be injected into the DOM. + /// + /// This is only used by the active `Renderer` object when a platform view needs + /// to be injected in the DOM, through `FlutterView.DomManager.injectPlatformView`. + /// + /// This may return null, if [renderContent] was not called before this. The + /// framework seems to allow/need this for some tests, so it is allowed here + /// as well. + /// + /// App programmers should not access this directly, and instead use [getViewById]. + DomElement? getSlottedContent(int viewId) { + return _contents[viewId]; + } + /// Returns the HTML element created by a registered factory for [viewId]. /// /// Throws an [AssertionError] if [viewId] hasn't been rendered before. @@ -104,9 +117,8 @@ class PlatformViewManager { /// Creates the HTML markup for the `contents` of a Platform View. /// - /// The result of this call is cached in the `_contents` Map. This is only - /// cached so it can be disposed of later by [clearPlatformView]. _Note that - /// there's no `getContents` function in this class._ + /// The result of this call is cached in the `_contents` Map, so the active + /// renderer can inject it as needed. /// /// The resulting DOM for the `contents` of a Platform View looks like this: /// diff --git a/lib/web_ui/lib/src/engine/platform_views/message_handler.dart b/lib/web_ui/lib/src/engine/platform_views/message_handler.dart index 9e20b3674ada3..86cd7a7e96991 100644 --- a/lib/web_ui/lib/src/engine/platform_views/message_handler.dart +++ b/lib/web_ui/lib/src/engine/platform_views/message_handler.dart @@ -21,7 +21,7 @@ typedef PlatformViewContentHandler = void Function(DomElement); /// This class handles incoming framework messages to create/dispose Platform Views. /// -/// (An instance of this class is connected to the `flutter/platform_views` +/// (The instance of this class is connected to the `flutter/platform_views` /// Platform Channel in the [EnginePlatformDispatcher] class.) /// /// It uses a [PlatformViewManager] to handle the CRUD of the DOM of Platform Views. @@ -29,27 +29,33 @@ typedef PlatformViewContentHandler = void Function(DomElement); /// all operations related to platform views (registration, rendering, etc...), /// regardless of the rendering backend. /// -/// When the `contents` of a Platform View are created, a [PlatformViewContentHandler] -/// function (passed from the outside) will decide where in the DOM to inject -/// said content. +/// Platform views are injected into the DOM when needed by the correct instance +/// of the active renderer. /// -/// The rendering/compositing of Platform Views can create the other "half" of a +/// The rendering and compositing of Platform Views can create the other "half" of a /// Platform View: the `slot`, through the [createPlatformViewSlot] method. /// /// When a Platform View is disposed of, it is removed from the cache (and DOM) /// directly by the `contentManager`. The canvaskit rendering backend needs to do /// some extra cleanup of its internal state, but it can do it automatically. See -/// [HtmlViewEmbedder.disposeViews] +/// [HtmlViewEmbedder.disposeViews]. class PlatformViewMessageHandler { PlatformViewMessageHandler({ - required DomElement platformViewsContainer, - PlatformViewManager? contentManager, - }) : _contentManager = contentManager ?? PlatformViewManager.instance, - _platformViewsContainer = platformViewsContainer; + required PlatformViewManager contentManager, + }) : _contentManager = contentManager; + + static const String channelName = 'flutter/platform_views'; + + /// The shared instance of PlatformViewMessageHandler. + /// + /// Unless configured differently, this connects to the shared instance of the + /// [PlatformViewManager]. + static PlatformViewMessageHandler instance = PlatformViewMessageHandler( + contentManager: PlatformViewManager.instance, + ); final MethodCodec _codec = const StandardMethodCodec(); final PlatformViewManager _contentManager; - final DomElement _platformViewsContainer; /// Handle a `create` Platform View message. /// @@ -58,10 +64,12 @@ class PlatformViewMessageHandler { /// /// (See [PlatformViewManager.registerFactory] for more details.) /// - /// The `contents` are inserted into the [_platformViewsContainer]. - /// /// If all goes well, this function will `callback` with an empty success envelope. /// In case of error, this will `callback` with an error envelope describing the error. + /// + /// The `callback` signals when the contents of a given [platformViewId] have + /// been rendered. They're now accessible through `platformViewRegistry.getViewById` + /// from `dart:ui_web`. **(Not the DOM!)** void _createPlatformView( _PlatformMessageResponseCallback callback, { required int platformViewId, @@ -88,15 +96,12 @@ class PlatformViewMessageHandler { return; } - final DomElement content = _contentManager.renderContent( + _contentManager.renderContent( platformViewType, platformViewId, params, ); - // For now, we don't need anything fancier. If needed, this can be converted - // to a PlatformViewStrategy class for each web-renderer backend? - _platformViewsContainer.append(content); callback(_codec.encodeSuccessEnvelope(null)); } @@ -126,7 +131,7 @@ class PlatformViewMessageHandler { /// This is transitional code to support the old platform view channel. As /// soon as the framework code is updated to send the Flutter View ID, this /// method can be removed. - void handleLegacyPlatformViewCall( + void handlePlatformViewCall( String method, dynamic arguments, _PlatformMessageResponseCallback callback, @@ -141,39 +146,11 @@ class PlatformViewMessageHandler { params: arguments['params'], ); return; + // TODO(web): Send `arguments` as a Map for `dispose` too! case 'dispose': _disposePlatformView(callback, platformViewId: arguments as int); return; } callback(null); } - - /// Handles a PlatformViewCall to the `flutter/platform_views` channel. - /// - /// This method handles two possible messages: - /// * `create`: See [_createPlatformView] - /// * `dispose`: See [_disposePlatformView] - void handlePlatformViewCall( - String method, - Map arguments, - _PlatformMessageResponseCallback callback, - ) { - switch (method) { - case 'create': - _createPlatformView( - callback, - platformViewId: arguments.readInt('platformViewId'), - platformViewType: arguments.readString('platformViewType'), - params: arguments['params'], - ); - return; - case 'dispose': - _disposePlatformView( - callback, - platformViewId: arguments.readInt('platformViewId'), - ); - return; - } - callback(null); - } } diff --git a/lib/web_ui/lib/src/engine/scene_view.dart b/lib/web_ui/lib/src/engine/scene_view.dart index 61f4e48b7bb90..23010c6db0bff 100644 --- a/lib/web_ui/lib/src/engine/scene_view.dart +++ b/lib/web_ui/lib/src/engine/scene_view.dart @@ -111,6 +111,12 @@ class EngineSceneView { case PlatformViewSlice(): for (final PlatformView view in slice.views) { + // TODO(harryterkelsen): Inject the FlutterView instance from `renderScene`, + // instead of using `EnginePlatformDispatcher...implicitView` directly, + // or make the FlutterView "register" like in canvaskit. + // Ensure the platform view contents are injected in the DOM. + EnginePlatformDispatcher.instance.implicitView?.dom.injectPlatformView(view.viewId); + // Attempt to reuse a container for the existing view PlatformViewContainer? container; for (int j = 0; j < reusableContainers.length; j++) { diff --git a/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart b/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart index 06a65a8873300..b1398f52def32 100644 --- a/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart +++ b/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart @@ -6,6 +6,7 @@ import 'package:ui/ui.dart' as ui; import '../configuration.dart'; import '../dom.dart'; +import '../platform_views/content_manager.dart'; import '../safe_browser_api.dart'; import '../semantics/semantics.dart'; import 'style_manager.dart'; @@ -204,6 +205,33 @@ class DomManager { sceneHost.append(sceneElement); } } + + /// Injects a platform view with [platformViewId] into [platformViewsHost]. + /// + /// If the platform view is already injected, this method does *nothing*. + /// + /// The `platformViewsHost` can only be different if `platformViewId` is moving + /// from one [FlutterView] to another. In that case, the browser will move the + /// slot contents from the old `platformViewsHost` to the new one, but that + /// will cause the platformView to reset its state (an iframe will re-render, + /// text selections will be lost, video playback interrupted, etc...) + /// + /// Try not to move platform views across views! + void injectPlatformView(int platformViewId) { + // For now, we don't need anything fancier. If needed, this can be converted + // to a PlatformViewStrategy class for each web-renderer backend? + final DomElement? pv = PlatformViewManager.instance.getSlottedContent(platformViewId); + if (pv == null) { + domWindow.console.debug('Failed to inject Platform View Id: $platformViewId. ' + 'Render seems to be happening before a `flutter/platform_views:create` platform message!'); + return; + } + // If pv is already a descendant of platformViewsHost -> noop + if (pv.parent == platformViewsHost) { + return; + } + platformViewsHost.append(pv); + } } DomShadowRoot _attachShadowRoot(DomElement element) { diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index a57cd1919edeb..716dc70e04760 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -17,7 +17,6 @@ import 'mouse/context_menu.dart'; import 'mouse/cursor.dart'; import 'navigation/history.dart'; import 'platform_dispatcher.dart'; -import 'platform_views/message_handler.dart'; import 'pointer_binding.dart'; import 'semantics.dart'; import 'services.dart'; @@ -130,9 +129,6 @@ base class EngineFlutterView implements ui.FlutterView { late final DomManager dom = DomManager(viewId: viewId, devicePixelRatio: devicePixelRatio); - late final PlatformViewMessageHandler platformViewMessageHandler = - PlatformViewMessageHandler(platformViewsContainer: dom.platformViewsHost); - late final PointerBinding pointerBinding; // TODO(goderbauer): Provide API to configure constraints. See also TODO in "render". diff --git a/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart b/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart index 38a3096035588..6dd81ad7fcd33 100644 --- a/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart +++ b/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart @@ -224,6 +224,34 @@ void testMain() { expect(view2.isDisposed, isTrue); expect(view3.isDisposed, isTrue); }); + + test('connects view disposal to metrics changed event', () { + final EnginePlatformDispatcher dispatcher = EnginePlatformDispatcher(); + final EngineFlutterView view1 = + EngineFlutterView(dispatcher, createDomHTMLDivElement()); + final EngineFlutterView view2 = + EngineFlutterView(dispatcher, createDomHTMLDivElement()); + + dispatcher.viewManager + ..registerView(view1) + ..registerView(view2); + + expect(view1.isDisposed, isFalse); + expect(view2.isDisposed, isFalse); + + bool onMetricsChangedCalled = false; + dispatcher.onMetricsChanged = () { + onMetricsChangedCalled = true; + }; + + expect(onMetricsChangedCalled, isFalse); + + dispatcher.viewManager.disposeAndUnregisterView(view2.viewId); + + expect(onMetricsChangedCalled, isTrue, reason: 'onMetricsChanged should have been called.'); + + dispatcher.dispose(); + }); }); } diff --git a/lib/web_ui/test/engine/platform_views/legacy_message_handler_test.dart b/lib/web_ui/test/engine/platform_views/legacy_message_handler_test.dart deleted file mode 100644 index 2c9f47725438f..0000000000000 --- a/lib/web_ui/test/engine/platform_views/legacy_message_handler_test.dart +++ /dev/null @@ -1,265 +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 'dart:async'; -import 'dart:typed_data'; - -import 'package:test/bootstrap/browser.dart'; -import 'package:test/test.dart'; -import 'package:ui/src/engine.dart'; - -void main() { - internalBootstrapBrowserTest(() => testMain); -} - -const MethodCodec codec = StandardMethodCodec(); - -typedef PlatformViewFactoryCall = ({int viewId, Object? params}); - -void testMain() { - group('PlatformViewMessageHandler', () { - group('handlePlatformViewCall', () { - const String viewType = 'forTest'; - const int viewId = 6; - late PlatformViewManager contentManager; - late Completer completer; - - setUp(() { - contentManager = PlatformViewManager(); - completer = Completer(); - }); - - group('"create" message', () { - test('unregistered viewType, fails with descriptive exception', - () async { - final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), - contentManager: contentManager, - ); - final Map arguments = _getCreateArguments(viewType, viewId); - - messageHandler.handleLegacyPlatformViewCall('create', arguments, completer.complete); - - final ByteData? response = await completer.future; - try { - codec.decodeEnvelope(response!); - } on PlatformException catch (e) { - expect(e.code, 'unregistered_view_type'); - expect(e.message, contains(viewType)); - expect(e.details, contains('registerViewFactory')); - } - }); - - test('duplicate viewId, fails with descriptive exception', () async { - contentManager.registerFactory( - viewType, (int id) => createDomHTMLDivElement()); - contentManager.renderContent(viewType, viewId, null); - final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), - contentManager: contentManager, - ); - final Map arguments = _getCreateArguments(viewType, viewId); - - messageHandler.handleLegacyPlatformViewCall('create', arguments, completer.complete); - - final ByteData? response = await completer.future; - try { - codec.decodeEnvelope(response!); - } on PlatformException catch (e) { - expect(e.code, 'recreating_view'); - expect(e.details, contains('$viewId')); - } - }); - - test('returns a successEnvelope when the view is created normally', - () async { - contentManager.registerFactory( - viewType, (int id) => createDomHTMLDivElement()..id = 'success'); - final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), - contentManager: contentManager, - ); - final Map arguments = _getCreateArguments(viewType, viewId); - - messageHandler.handleLegacyPlatformViewCall('create', arguments, completer.complete); - - final ByteData? response = await completer.future; - expect(codec.decodeEnvelope(response!), isNull, - reason: - 'The response should be a success envelope, with null in it.'); - }); - - test('inserts the created view into the platformViewsContainer', - () async { - final DomElement platformViewsContainer = createDomElement('pv-container'); - contentManager.registerFactory( - viewType, (int id) => createDomHTMLDivElement()..id = 'success'); - final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: platformViewsContainer, - contentManager: contentManager, - ); - final Map arguments = _getCreateArguments(viewType, viewId); - - messageHandler.handleLegacyPlatformViewCall('create', arguments, completer.complete); - - final ByteData? response = await completer.future; - - expect( - platformViewsContainer.children.single, - isNotNull, - reason: 'The container has a single child, the created view.', - ); - final DomElement platformView = platformViewsContainer.children.single; - expect( - platformView.querySelector('div#success'), - isNotNull, - reason: 'The element created by the factory should be present in the created view.', - ); - expect( - codec.decodeEnvelope(response!), - isNull, - reason: 'The response should be a success envelope, with null in it.', - ); - }); - - test('passes creation params to the factory', () async { - final List factoryCalls = []; - contentManager.registerFactory(viewType, (int viewId, {Object? params}) { - factoryCalls.add((viewId: viewId, params: params)); - return createDomHTMLDivElement(); - }); - final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), - contentManager: contentManager, - ); - - final List> completers = >[]; - - completers.add(Completer()); - messageHandler.handleLegacyPlatformViewCall( - 'create', - _getCreateArguments(viewType, 111), - completers.last.complete, - ); - - completers.add(Completer()); - messageHandler.handleLegacyPlatformViewCall( - 'create', - _getCreateArguments(viewType, 222, {'foo': 'bar'}), - completers.last.complete, - ); - - completers.add(Completer()); - messageHandler.handleLegacyPlatformViewCall( - 'create', - _getCreateArguments(viewType, 333, 'foobar'), - completers.last.complete, - ); - - completers.add(Completer()); - messageHandler.handleLegacyPlatformViewCall( - 'create', - _getCreateArguments(viewType, 444, [1, null, 'str']), - completers.last.complete, - ); - - final List responses = await Future.wait( - completers.map((Completer c) => c.future), - ); - - for (final ByteData? response in responses) { - expect( - codec.decodeEnvelope(response!), - isNull, - reason: 'The response should be a success envelope, with null in it.', - ); - } - - expect(factoryCalls, hasLength(4)); - expect(factoryCalls[0].viewId, 111); - expect(factoryCalls[0].params, isNull); - expect(factoryCalls[1].viewId, 222); - expect(factoryCalls[1].params, {'foo': 'bar'}); - expect(factoryCalls[2].viewId, 333); - expect(factoryCalls[2].params, 'foobar'); - expect(factoryCalls[3].viewId, 444); - expect(factoryCalls[3].params, [1, null, 'str']); - }); - - test('fails if the factory returns a non-DOM object', () async { - contentManager.registerFactory(viewType, (int viewId) { - // Return an object that's not a DOM element. - return Object(); - }); - - final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), - contentManager: contentManager, - ); - final Map arguments = _getCreateArguments(viewType, viewId); - - expect(() { - messageHandler.handleLegacyPlatformViewCall('create', arguments, (_) {}); - }, throwsA(isA())); - }); - }); - - group('"dispose" message', () { - late Completer viewIdCompleter; - - setUp(() { - viewIdCompleter = Completer(); - }); - - test('never fails, even for unknown viewIds', () async { - final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), - contentManager: contentManager, - ); - - messageHandler.handleLegacyPlatformViewCall('dispose', viewId, completer.complete); - - final ByteData? response = await completer.future; - expect(codec.decodeEnvelope(response!), isNull, - reason: - 'The response should be a success envelope, with null in it.'); - }); - - test('never fails, even for unknown viewIds', () async { - final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), - contentManager: _FakePlatformViewManager(viewIdCompleter.complete), - ); - - messageHandler.handleLegacyPlatformViewCall('dispose', viewId, completer.complete); - - final int disposedViewId = await viewIdCompleter.future; - expect(disposedViewId, viewId, - reason: - 'The viewId to dispose should be passed to the contentManager'); - }); - }); - }); - }); -} - -class _FakePlatformViewManager extends PlatformViewManager { - _FakePlatformViewManager(void Function(int) clearFunction) - : _clearPlatformView = clearFunction; - - final void Function(int) _clearPlatformView; - - @override - void clearPlatformView(int viewId) { - return _clearPlatformView(viewId); - } -} - -Map _getCreateArguments(String viewType, int viewId, [Object? params]) { - return { - 'id': viewId, - 'viewType': viewType, - if (params != null) 'params': params, - }; -} diff --git a/lib/web_ui/test/engine/platform_views/message_handler_test.dart b/lib/web_ui/test/engine/platform_views/message_handler_test.dart index 344de91e04ae4..42b7059e8299f 100644 --- a/lib/web_ui/test/engine/platform_views/message_handler_test.dart +++ b/lib/web_ui/test/engine/platform_views/message_handler_test.dart @@ -34,14 +34,9 @@ void testMain() { test('unregistered viewType, fails with descriptive exception', () async { final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final Map arguments = _getCreateArguments( - platformViewType: platformViewType, - platformViewId: platformViewId, - viewId: kImplicitViewId, - ); + final Map arguments = _getCreateArguments(platformViewType, platformViewId); messageHandler.handlePlatformViewCall('create', arguments, completer.complete); @@ -60,14 +55,9 @@ void testMain() { platformViewType, (int id) => createDomHTMLDivElement()); contentManager.renderContent(platformViewType, platformViewId, null); final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final Map arguments = _getCreateArguments( - platformViewType: platformViewType, - platformViewId: platformViewId, - viewId: kImplicitViewId, - ); + final Map arguments = _getCreateArguments(platformViewType, platformViewId); messageHandler.handlePlatformViewCall('create', arguments, completer.complete); @@ -85,14 +75,9 @@ void testMain() { contentManager.registerFactory( platformViewType, (int id) => createDomHTMLDivElement()..id = 'success'); final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final Map arguments = _getCreateArguments( - platformViewType: platformViewType, - platformViewId: platformViewId, - viewId: kImplicitViewId, - ); + final Map arguments = _getCreateArguments(platformViewType, platformViewId); messageHandler.handlePlatformViewCall('create', arguments, completer.complete); @@ -102,40 +87,39 @@ void testMain() { 'The response should be a success envelope, with null in it.'); }); - test('inserts the created view into the platformViewsContainer', + test('caches the created view so it can be retrieved (not on the DOM)', () async { final DomElement platformViewsContainer = createDomElement('pv-container'); contentManager.registerFactory( platformViewType, (int id) => createDomHTMLDivElement()..id = 'success'); final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: platformViewsContainer, contentManager: contentManager, ); - final Map arguments = _getCreateArguments( - platformViewType: platformViewType, - platformViewId: platformViewId, - viewId: kImplicitViewId, - ); + final Map arguments = _getCreateArguments(platformViewType, platformViewId); messageHandler.handlePlatformViewCall('create', arguments, completer.complete); final ByteData? response = await completer.future; expect( - platformViewsContainer.children.single, - isNotNull, - reason: 'The container has a single child, the created view.', + codec.decodeEnvelope(response!), + isNull, + reason: 'The response should be a success envelope, with null in it.', + ); + expect( + contentManager.knowsViewId(platformViewId), + isTrue, + reason: 'The contentManager should have pre-rendered the platformViewId.' ); - final DomElement platformView = platformViewsContainer.children.single; expect( - platformView.querySelector('div#success'), + contentManager.getViewById(platformViewId).matches('div#success'), isNotNull, - reason: 'The element created by the factory should be present in the created view.', + reason: 'The element created by the factory should be retrievable.', ); expect( - codec.decodeEnvelope(response!), - isNull, - reason: 'The response should be a success envelope, with null in it.', + platformViewsContainer.children, + hasLength(0), + reason: 'The view should not have been injected into the DOM', ); }); @@ -146,7 +130,6 @@ void testMain() { return createDomHTMLDivElement(); }); final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); @@ -155,47 +138,28 @@ void testMain() { completers.add(Completer()); messageHandler.handlePlatformViewCall( 'create', - _getCreateArguments( - platformViewType: platformViewType, - platformViewId: 111, - viewId: kImplicitViewId, - ), + _getCreateArguments(platformViewType, 111), completers.last.complete, ); completers.add(Completer()); messageHandler.handlePlatformViewCall( 'create', - _getCreateArguments( - platformViewType: platformViewType, - platformViewId: 222, - viewId: kImplicitViewId, - params: {'foo': 'bar'}, - ), + _getCreateArguments(platformViewType, 222, {'foo': 'bar'}), completers.last.complete, ); completers.add(Completer()); messageHandler.handlePlatformViewCall( 'create', - _getCreateArguments( - platformViewType: platformViewType, - platformViewId: 333, - viewId: kImplicitViewId, - params: 'foobar', - ), + _getCreateArguments(platformViewType, 333, 'foobar'), completers.last.complete, ); completers.add(Completer()); messageHandler.handlePlatformViewCall( 'create', - _getCreateArguments( - platformViewType: platformViewType, - platformViewId: 444, - viewId: kImplicitViewId, - params: [1, null, 'str'], - ), + _getCreateArguments(platformViewType, 444, [1, null, 'str']), completers.last.complete, ); @@ -229,14 +193,9 @@ void testMain() { }); final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final Map arguments = _getCreateArguments( - platformViewType: platformViewType, - platformViewId: platformViewId, - viewId: kImplicitViewId, - ); + final Map arguments = _getCreateArguments(platformViewType, platformViewId); expect(() { messageHandler.handlePlatformViewCall('create', arguments, (_) {}); @@ -253,15 +212,10 @@ void testMain() { test('never fails, even for unknown viewIds', () async { final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final Map arguments = _getDisposeArguments( - platformViewId: platformViewId, - viewId: kImplicitViewId, - ); - messageHandler.handlePlatformViewCall('dispose', arguments, completer.complete); + messageHandler.handlePlatformViewCall('dispose', platformViewId, completer.complete); final ByteData? response = await completer.future; expect(codec.decodeEnvelope(response!), isNull, @@ -271,15 +225,10 @@ void testMain() { test('never fails, even for unknown viewIds', () async { final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( - platformViewsContainer: createDomElement('div'), contentManager: _FakePlatformViewManager(viewIdCompleter.complete), ); - final Map arguments = _getDisposeArguments( - platformViewId: platformViewId, - viewId: kImplicitViewId, - ); - messageHandler.handlePlatformViewCall('dispose', arguments, completer.complete); + messageHandler.handlePlatformViewCall('dispose', platformViewId, completer.complete); final int disposedViewId = await viewIdCompleter.future; expect(disposedViewId, platformViewId, @@ -303,26 +252,10 @@ class _FakePlatformViewManager extends PlatformViewManager { } } -Map _getCreateArguments({ - required String platformViewType, - required int platformViewId, - required int viewId, - Object? params, -}) { +Map _getCreateArguments(String viewType, int viewId, [Object? params]) { return { - 'platformViewId': platformViewId, - 'platformViewType': platformViewType, + 'id': viewId, + 'viewType': viewType, if (params != null) 'params': params, - 'viewId': viewId, - }; -} - -Map _getDisposeArguments({ - required int platformViewId, - required int viewId, -}) { - return { - 'platformViewId': platformViewId, - 'viewId': viewId, }; }