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 b6ef99a39d965..288ab4374a32e 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 @@ -2,25 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:ui/ui_web/src/ui_web.dart' as ui_web; + import '../browser_detection.dart'; import '../dom.dart'; import '../embedder.dart'; import '../util.dart'; import 'slots.dart'; -/// A function which takes a unique `id` and some `params` and creates an HTML element. -/// -/// This is made available to end-users through dart:ui in web. -typedef ParameterizedPlatformViewFactory = DomElement Function( - int viewId, { - Object? params, -}); - -/// A function which takes a unique `id` and creates an HTML element. -/// -/// This is made available to end-users through dart:ui in web. -typedef PlatformViewFactory = DomElement Function(int viewId); - /// This class handles the lifecycle of Platform Views in the DOM of a Flutter Web App. /// /// There are three important parts of Platform Views. This class manages two of @@ -79,8 +68,13 @@ class PlatformViewManager { /// `factoryFunction` needs to be a [PlatformViewFactory]. bool registerFactory(String viewType, Function factoryFunction, {bool isVisible = true}) { - assert(factoryFunction is PlatformViewFactory || - factoryFunction is ParameterizedPlatformViewFactory); + assert( + factoryFunction is ui_web.PlatformViewFactory || + factoryFunction is ui_web.ParameterizedPlatformViewFactory, + 'Factory signature is invalid. Expected either ' + '{${ui_web.PlatformViewFactory}} or {${ui_web.ParameterizedPlatformViewFactory}} ' + 'but got: {${factoryFunction.runtimeType}}', + ); if (_factories.containsKey(viewType)) { return false; @@ -132,11 +126,11 @@ class PlatformViewManager { final Function factoryFunction = _factories[viewType]!; final DomElement content; - if (factoryFunction is ParameterizedPlatformViewFactory) { - content = factoryFunction(viewId, params: params); + if (factoryFunction is ui_web.ParameterizedPlatformViewFactory) { + content = factoryFunction(viewId, params: params) as DomElement; } else { - factoryFunction as PlatformViewFactory; - content = factoryFunction(viewId); + factoryFunction as ui_web.PlatformViewFactory; + content = factoryFunction(viewId) as DomElement; } _ensureContentCorrectlySized(content, viewType); 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 30658e57f6c91..b6a5f4533c366 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 @@ -68,6 +68,7 @@ class PlatformViewMessageHandler { final Map args = methodCall.arguments as Map; final int viewId = args.readInt('id'); final String viewType = args.readString('viewType'); + final Object? params = args['params']; if (!_contentManager.knowsViewType(viewType)) { callback(_codec.encodeErrorEnvelope( @@ -89,11 +90,10 @@ class PlatformViewMessageHandler { return; } - // TODO(hterkelsen): How can users add extra `args` from the HtmlElementView widget? final DomElement content = _contentManager.renderContent( viewType, viewId, - args, + params, ); // For now, we don't need anything fancier. If needed, this can be converted diff --git a/lib/web_ui/lib/ui_web/src/ui_web/platform_view_registry.dart b/lib/web_ui/lib/ui_web/src/ui_web/platform_view_registry.dart index 45bb823b23597..ff9312f11da09 100644 --- a/lib/web_ui/lib/ui_web/src/ui_web/platform_view_registry.dart +++ b/lib/web_ui/lib/ui_web/src/ui_web/platform_view_registry.dart @@ -4,21 +4,32 @@ import 'package:ui/src/engine.dart'; +/// A function which takes a unique `id` and some `params` and creates an HTML +/// element. +typedef ParameterizedPlatformViewFactory = Object Function( + int viewId, { + Object? params, +}); + +/// A function which takes a unique `id` and creates an HTML element. +typedef PlatformViewFactory = Object Function(int viewId); + /// The platform view registry for this app. final PlatformViewRegistry platformViewRegistry = PlatformViewRegistry(); /// A registry for factories that create platform views. class PlatformViewRegistry { - /// Register [viewTypeId] as being creating by the given [viewFactory]. - /// [viewFactory] can be any function that takes an integer and returns an - /// `HTMLElement` DOM object. + /// Register [viewType] as being created by the given [viewFactory]. + /// + /// [viewFactory] can be any function that takes an integer and optional + /// `params` and returns an `HTMLElement` DOM object. bool registerViewFactory( - String viewTypeId, - Object Function(int viewId) viewFactory, { + String viewType, + Function viewFactory, { bool isVisible = true, }) { return platformViewManager.registerFactory( - viewTypeId, + viewType, viewFactory, isVisible: isVisible, ); 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 429779ac09a34..2f30e7c5e13f4 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 @@ -15,6 +15,8 @@ void main() { const MethodCodec codec = StandardMethodCodec(); +typedef PlatformViewFactoryCall = ({int viewId, Object? params}); + void testMain() { group('PlatformViewMessageHandler', () { group('handlePlatformViewCall', () { @@ -109,6 +111,80 @@ void testMain() { 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( + contentManager: contentManager, + ); + + final List> completers = >[]; + + completers.add(Completer()); + messageHandler.handlePlatformViewCall( + _getCreateMessage(viewType, 111), + completers.last.complete, + ); + + completers.add(Completer()); + messageHandler.handlePlatformViewCall( + _getCreateMessage(viewType, 222, {'foo': 'bar'}), + completers.last.complete, + ); + + completers.add(Completer()); + messageHandler.handlePlatformViewCall( + _getCreateMessage(viewType, 333, 'foobar'), + completers.last.complete, + ); + + completers.add(Completer()); + messageHandler.handlePlatformViewCall( + _getCreateMessage(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(contentManager: contentManager); + final ByteData? message = _getCreateMessage(viewType, viewId); + + expect(() { + messageHandler.handlePlatformViewCall(message, (_) {}); + }, throwsA(isA())); + }); }); group('"dispose" message', () { @@ -162,12 +238,13 @@ class _FakePlatformViewManager extends PlatformViewManager { } } -ByteData? _getCreateMessage(String viewType, int viewId) { +ByteData? _getCreateMessage(String viewType, int viewId, [Object? params]) { return codec.encodeMethodCall(MethodCall( 'create', { 'id': viewId, 'viewType': viewType, + if (params != null) 'params': params, }, )); }