diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 51bc3a394cff5..5d667c51ccf28 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -21,6 +21,9 @@ ui.VoidCallback? scheduleFrameCallback; typedef HighContrastListener = void Function(bool enabled); typedef _KeyDataResponseCallback = void Function(bool handled); +const StandardMethodCodec standardCodec = StandardMethodCodec(); +const JSONMethodCodec jsonCodec = JSONMethodCodec(); + /// Determines if high contrast is enabled using media query 'forced-colors: active' for Windows class HighContrastSupport { static HighContrastSupport instance = HighContrastSupport(); @@ -129,13 +132,13 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// The current list of windows. @override - Iterable get views => viewData.values; - final Map viewData = {}; + Iterable get views => viewData.values; + final Map viewData = {}; /// Returns the [FlutterView] with the provided ID if one exists, or null /// otherwise. @override - ui.FlutterView? view({required int id}) => viewData[id]; + EngineFlutterView? view({required int id}) => viewData[id]; /// A map of opaque platform window identifiers to window configurations. /// @@ -470,8 +473,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// This should be in sync with shell/common/shell.cc case 'flutter/skia': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); + final MethodCall decoded = jsonCodec.decodeMethodCall(data); switch (decoded.method) { case 'Skia.setResourceCacheMaxBytes': if (renderer is CanvasKitRenderer) { @@ -486,7 +488,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // Also respond in HTML mode. Otherwise, apps would have to detect // CanvasKit vs HTML before invoking this method. replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope([true])); + callback, jsonCodec.encodeSuccessEnvelope([true])); } return; @@ -496,8 +498,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { return; case 'flutter/platform': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); + final MethodCall decoded = jsonCodec.decodeMethodCall(data); switch (decoded.method) { case 'SystemNavigator.pop': // TODO(a-wallen): As multi-window support expands, the pop call @@ -505,13 +506,13 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // supported. implicitView!.browserHistory.exit().then((_) { replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); + callback, jsonCodec.encodeSuccessEnvelope(true)); }); return; case 'HapticFeedback.vibrate': final String? type = decoded.arguments as String?; vibrate(_getHapticFeedbackDuration(type)); - replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + replyToPlatformMessage(callback, jsonCodec.encodeSuccessEnvelope(true)); return; case 'SystemChrome.setApplicationSwitcherDescription': final Map arguments = decoded.arguments as Map; @@ -520,24 +521,24 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { final int primaryColor = arguments['primaryColor'] as int? ?? 0xFF000000; domDocument.title = label; setThemeColor(ui.Color(primaryColor)); - replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + replyToPlatformMessage(callback, jsonCodec.encodeSuccessEnvelope(true)); return; case 'SystemChrome.setSystemUIOverlayStyle': final Map arguments = decoded.arguments as Map; final int? statusBarColor = arguments['statusBarColor'] as int?; setThemeColor(statusBarColor == null ? null : ui.Color(statusBarColor)); - replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + replyToPlatformMessage(callback, jsonCodec.encodeSuccessEnvelope(true)); return; case 'SystemChrome.setPreferredOrientations': final List arguments = decoded.arguments as List; ScreenOrientation.instance.setPreferredOrientation(arguments).then((bool success) { replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(success)); + callback, jsonCodec.encodeSuccessEnvelope(success)); }); return; case 'SystemSound.play': // There are no default system sounds on web. - replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + replyToPlatformMessage(callback, jsonCodec.encodeSuccessEnvelope(true)); return; case 'Clipboard.setData': ClipboardMessageHandler().setDataMethodCall(decoded, callback); @@ -560,23 +561,21 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { return; case 'flutter/contextmenu': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); + final MethodCall decoded = jsonCodec.decodeMethodCall(data); switch (decoded.method) { case 'enableContextMenu': implicitView!.contextMenu.enable(); - replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + replyToPlatformMessage(callback, jsonCodec.encodeSuccessEnvelope(true)); return; case 'disableContextMenu': implicitView!.contextMenu.disable(); - replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + replyToPlatformMessage(callback, jsonCodec.encodeSuccessEnvelope(true)); return; } return; case 'flutter/mousecursor': - const MethodCodec codec = StandardMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); + final MethodCall decoded = standardCodec.decodeMethodCall(data); final Map arguments = decoded.arguments as Map; switch (decoded.method) { case 'activateSystemCursor': @@ -585,15 +584,21 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { return; case 'flutter/web_test_e2e': - const MethodCodec codec = JSONMethodCodec(); replyToPlatformMessage( callback, - codec.encodeSuccessEnvelope( - _handleWebTestEnd2EndMessage(codec, data))); + jsonCodec.encodeSuccessEnvelope( + _handleWebTestEnd2EndMessage(jsonCodec, data))); return; case 'flutter/platform_views': - implicitView!.platformViewMessageHandler.handlePlatformViewCall(data, callback!); + 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; + viewData[flutterViewId]!.platformViewMessageHandler.handlePlatformViewCall(method, arguments, callback!); return; case 'flutter/accessibility': @@ -609,8 +614,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // supported. implicitView!.handleNavigationMessage(data).then((bool handled) { if (handled) { - const MethodCodec codec = JSONMethodCodec(); - replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + replyToPlatformMessage(callback, jsonCodec.encodeSuccessEnvelope(true)); } else { callback?.call(null); } @@ -1350,7 +1354,7 @@ class ViewConfiguration { }); ViewConfiguration copyWith({ - ui.FlutterView? view, + EngineFlutterView? view, double? devicePixelRatio, ui.Rect? geometry, bool? visible, @@ -1375,7 +1379,7 @@ class ViewConfiguration { ); } - final ui.FlutterView? view; + final EngineFlutterView? view; final double devicePixelRatio; final ui.Rect geometry; final bool visible; 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 a9ed363417caf..0b0e35270ff66 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 @@ -249,5 +249,7 @@ DomElement _defaultFactory( }) { params!; params as Map; - return domDocument.createElement(params.readString('tagName')); + return domDocument.createElement(params.readString('tagName')) + ..style.width = '100%' + ..style.height = '100%'; } 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 68c0fe340cac8..9e20b3674ada3 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 @@ -53,7 +53,7 @@ class PlatformViewMessageHandler { /// Handle a `create` Platform View message. /// - /// This will attempt to render the `contents` and of a Platform View, if its + /// This will attempt to render the `contents` of a Platform View, if its /// `viewType` has been registered previously. /// /// (See [PlatformViewManager.registerFactory] for more details.) @@ -63,37 +63,34 @@ class PlatformViewMessageHandler { /// 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. void _createPlatformView( - MethodCall methodCall, - _PlatformMessageResponseCallback callback, - ) { - 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)) { + _PlatformMessageResponseCallback callback, { + required int platformViewId, + required String platformViewType, + required Object? params, + }) { + if (!_contentManager.knowsViewType(platformViewType)) { callback(_codec.encodeErrorEnvelope( code: 'unregistered_view_type', message: 'A HtmlElementView widget is trying to create a platform view ' - 'with an unregistered type: <$viewType>.', + 'with an unregistered type: <$platformViewType>.', details: 'If you are the author of the PlatformView, make sure ' '`registerViewFactory` is invoked.', )); return; } - if (_contentManager.knowsViewId(viewId)) { + if (_contentManager.knowsViewId(platformViewId)) { callback(_codec.encodeErrorEnvelope( code: 'recreating_view', message: 'trying to create an already created view', - details: 'view id: $viewId', + details: 'view id: $platformViewId', )); return; } final DomElement content = _contentManager.renderContent( - viewType, - viewId, + platformViewType, + platformViewId, params, ); @@ -106,7 +103,7 @@ class PlatformViewMessageHandler { /// Handle a `dispose` Platform View message. /// /// This will clear the cached information that the framework has about a given - /// `viewId`, through the [_contentManager]. + /// `platformViewId`, through the [_contentManager]. /// /// Once that's done, the dispose call is delegated to the [_disposeHandler] /// function, so the active rendering backend can dispose of whatever resources @@ -114,34 +111,67 @@ class PlatformViewMessageHandler { /// /// This function should always `callback` with an empty success envelope. void _disposePlatformView( - MethodCall methodCall, - _PlatformMessageResponseCallback callback, - ) { - final int viewId = methodCall.arguments as int; - + _PlatformMessageResponseCallback callback, { + required int platformViewId, + }) { // The contentManager removes the slot and the contents from its internal // cache, and the DOM. - _contentManager.clearPlatformView(viewId); + _contentManager.clearPlatformView(platformViewId); callback(_codec.encodeSuccessEnvelope(null)); } + /// Handles legacy PlatformViewCalls that don't contain a Flutter View ID. + /// + /// 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( + String method, + dynamic arguments, + _PlatformMessageResponseCallback callback, + ) { + switch (method) { + case 'create': + arguments as Map; + _createPlatformView( + callback, + platformViewId: arguments.readInt('id'), + platformViewType: arguments.readString('viewType'), + params: arguments['params'], + ); + return; + 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( - ByteData? data, + String method, + Map arguments, _PlatformMessageResponseCallback callback, ) { - final MethodCall decoded = _codec.decodeMethodCall(data); - switch (decoded.method) { + switch (method) { case 'create': - _createPlatformView(decoded, callback); + _createPlatformView( + callback, + platformViewId: arguments.readInt('platformViewId'), + platformViewType: arguments.readString('platformViewType'), + params: arguments['params'], + ); return; case 'dispose': - _disposePlatformView(decoded, callback); + _disposePlatformView( + callback, + platformViewId: arguments.readInt('platformViewId'), + ); return; } callback(null); diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index bd4ad5b0ef333..d6581459b60c2 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -14,6 +14,7 @@ import 'package:ui/ui.dart' as ui; import 'browser_detection.dart'; import 'dom.dart'; import 'safe_browser_api.dart'; +import 'services.dart'; import 'vector_math.dart'; /// Generic callback signature, used by [_futurize]. @@ -627,6 +628,27 @@ extension JsonExtensions on Map { } } +/// Extracts view ID from the [MethodCall.arguments] map. +/// +/// Throws if the view ID is not present or if [arguments] is not a map. +int readViewId(Object? arguments) { + final int? viewId = tryViewId(arguments); + if (viewId == null) { + throw Exception('Could not find a `viewId` in the arguments: $arguments'); + } + return viewId; +} + +/// Extracts view ID from the [MethodCall.arguments] map. +/// +/// Returns null if the view ID is not present or if [arguments] is not a map. +int? tryViewId(Object? arguments) { + if (arguments is Map) { + return arguments.tryInt('viewId'); + } + return null; +} + /// Prints a list of bytes in hex format. /// /// Bytes are separated by one space and are padded on the left to always show 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 new file mode 100644 index 0000000000000..2c9f47725438f --- /dev/null +++ b/lib/web_ui/test/engine/platform_views/legacy_message_handler_test.dart @@ -0,0 +1,265 @@ +// 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 e1b28b68b954d..344de91e04ae4 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 @@ -20,8 +20,8 @@ typedef PlatformViewFactoryCall = ({int viewId, Object? params}); void testMain() { group('PlatformViewMessageHandler', () { group('handlePlatformViewCall', () { - const String viewType = 'forTest'; - const int viewId = 6; + const String platformViewType = 'forTest'; + const int platformViewId = 6; late PlatformViewManager contentManager; late Completer completer; @@ -37,52 +37,64 @@ void testMain() { platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final ByteData? message = _getCreateMessage(viewType, viewId); + final Map arguments = _getCreateArguments( + platformViewType: platformViewType, + platformViewId: platformViewId, + viewId: kImplicitViewId, + ); - messageHandler.handlePlatformViewCall(message, completer.complete); + messageHandler.handlePlatformViewCall('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.message, contains(platformViewType)); expect(e.details, contains('registerViewFactory')); } }); test('duplicate viewId, fails with descriptive exception', () async { contentManager.registerFactory( - viewType, (int id) => createDomHTMLDivElement()); - contentManager.renderContent(viewType, viewId, null); + platformViewType, (int id) => createDomHTMLDivElement()); + contentManager.renderContent(platformViewType, platformViewId, null); final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final ByteData? message = _getCreateMessage(viewType, viewId); + final Map arguments = _getCreateArguments( + platformViewType: platformViewType, + platformViewId: platformViewId, + viewId: kImplicitViewId, + ); - messageHandler.handlePlatformViewCall(message, completer.complete); + messageHandler.handlePlatformViewCall('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')); + expect(e.details, contains('$platformViewId')); } }); test('returns a successEnvelope when the view is created normally', () async { contentManager.registerFactory( - viewType, (int id) => createDomHTMLDivElement()..id = 'success'); + platformViewType, (int id) => createDomHTMLDivElement()..id = 'success'); final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final ByteData? message = _getCreateMessage(viewType, viewId); + final Map arguments = _getCreateArguments( + platformViewType: platformViewType, + platformViewId: platformViewId, + viewId: kImplicitViewId, + ); - messageHandler.handlePlatformViewCall(message, completer.complete); + messageHandler.handlePlatformViewCall('create', arguments, completer.complete); final ByteData? response = await completer.future; expect(codec.decodeEnvelope(response!), isNull, @@ -94,14 +106,18 @@ void testMain() { () async { final DomElement platformViewsContainer = createDomElement('pv-container'); contentManager.registerFactory( - viewType, (int id) => createDomHTMLDivElement()..id = 'success'); + platformViewType, (int id) => createDomHTMLDivElement()..id = 'success'); final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler( platformViewsContainer: platformViewsContainer, contentManager: contentManager, ); - final ByteData? message = _getCreateMessage(viewType, viewId); + final Map arguments = _getCreateArguments( + platformViewType: platformViewType, + platformViewId: platformViewId, + viewId: kImplicitViewId, + ); - messageHandler.handlePlatformViewCall(message, completer.complete); + messageHandler.handlePlatformViewCall('create', arguments, completer.complete); final ByteData? response = await completer.future; @@ -125,7 +141,7 @@ void testMain() { test('passes creation params to the factory', () async { final List factoryCalls = []; - contentManager.registerFactory(viewType, (int viewId, {Object? params}) { + contentManager.registerFactory(platformViewType, (int viewId, {Object? params}) { factoryCalls.add((viewId: viewId, params: params)); return createDomHTMLDivElement(); }); @@ -138,25 +154,48 @@ void testMain() { completers.add(Completer()); messageHandler.handlePlatformViewCall( - _getCreateMessage(viewType, 111), + 'create', + _getCreateArguments( + platformViewType: platformViewType, + platformViewId: 111, + viewId: kImplicitViewId, + ), completers.last.complete, ); completers.add(Completer()); messageHandler.handlePlatformViewCall( - _getCreateMessage(viewType, 222, {'foo': 'bar'}), + 'create', + _getCreateArguments( + platformViewType: platformViewType, + platformViewId: 222, + viewId: kImplicitViewId, + params: {'foo': 'bar'}, + ), completers.last.complete, ); completers.add(Completer()); messageHandler.handlePlatformViewCall( - _getCreateMessage(viewType, 333, 'foobar'), + 'create', + _getCreateArguments( + platformViewType: platformViewType, + platformViewId: 333, + viewId: kImplicitViewId, + params: 'foobar', + ), completers.last.complete, ); completers.add(Completer()); messageHandler.handlePlatformViewCall( - _getCreateMessage(viewType, 444, [1, null, 'str']), + 'create', + _getCreateArguments( + platformViewType: platformViewType, + platformViewId: 444, + viewId: kImplicitViewId, + params: [1, null, 'str'], + ), completers.last.complete, ); @@ -184,7 +223,7 @@ void testMain() { }); test('fails if the factory returns a non-DOM object', () async { - contentManager.registerFactory(viewType, (int viewId) { + contentManager.registerFactory(platformViewType, (int viewId) { // Return an object that's not a DOM element. return Object(); }); @@ -193,10 +232,14 @@ void testMain() { platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final ByteData? message = _getCreateMessage(viewType, viewId); + final Map arguments = _getCreateArguments( + platformViewType: platformViewType, + platformViewId: platformViewId, + viewId: kImplicitViewId, + ); expect(() { - messageHandler.handlePlatformViewCall(message, (_) {}); + messageHandler.handlePlatformViewCall('create', arguments, (_) {}); }, throwsA(isA())); }); }); @@ -213,9 +256,12 @@ void testMain() { platformViewsContainer: createDomElement('div'), contentManager: contentManager, ); - final ByteData? message = _getDisposeMessage(viewId); + final Map arguments = _getDisposeArguments( + platformViewId: platformViewId, + viewId: kImplicitViewId, + ); - messageHandler.handlePlatformViewCall(message, completer.complete); + messageHandler.handlePlatformViewCall('dispose', arguments, completer.complete); final ByteData? response = await completer.future; expect(codec.decodeEnvelope(response!), isNull, @@ -228,12 +274,15 @@ void testMain() { platformViewsContainer: createDomElement('div'), contentManager: _FakePlatformViewManager(viewIdCompleter.complete), ); - final ByteData? message = _getDisposeMessage(viewId); + final Map arguments = _getDisposeArguments( + platformViewId: platformViewId, + viewId: kImplicitViewId, + ); - messageHandler.handlePlatformViewCall(message, completer.complete); + messageHandler.handlePlatformViewCall('dispose', arguments, completer.complete); final int disposedViewId = await viewIdCompleter.future; - expect(disposedViewId, viewId, + expect(disposedViewId, platformViewId, reason: 'The viewId to dispose should be passed to the contentManager'); }); @@ -254,20 +303,26 @@ class _FakePlatformViewManager extends PlatformViewManager { } } -ByteData? _getCreateMessage(String viewType, int viewId, [Object? params]) { - return codec.encodeMethodCall(MethodCall( - 'create', - { - 'id': viewId, - 'viewType': viewType, - if (params != null) 'params': params, - }, - )); +Map _getCreateArguments({ + required String platformViewType, + required int platformViewId, + required int viewId, + Object? params, +}) { + return { + 'platformViewId': platformViewId, + 'platformViewType': platformViewType, + if (params != null) 'params': params, + 'viewId': viewId, + }; } -ByteData? _getDisposeMessage(int viewId) { - return codec.encodeMethodCall(MethodCall( - 'dispose', - viewId, - )); +Map _getDisposeArguments({ + required int platformViewId, + required int viewId, +}) { + return { + 'platformViewId': platformViewId, + 'viewId': viewId, + }; }