[web] Support flutterViewId in platform view messages (flutter/engine#46891)

- Accept a new `flutterViewId` field in platform view messages.
- Keep transitory support for legacy platform view messages that don't contain `flutterViewId`.
- Default view factories set `width:100%` and `height:100%`.
This commit is contained in:
Mouad Debbar 2023-10-20 15:17:36 -04:00 committed by GitHub
parent 38b42c9f14
commit 92eac4f833
6 changed files with 477 additions and 99 deletions

View File

@ -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<ui.FlutterView> get views => viewData.values;
final Map<int, ui.FlutterView> viewData = <int, ui.FlutterView>{};
Iterable<EngineFlutterView> get views => viewData.values;
final Map<int, EngineFlutterView> viewData = <int, EngineFlutterView>{};
/// 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(<bool>[true]));
callback, jsonCodec.encodeSuccessEnvelope(<bool>[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<String, Object?> arguments = decoded.arguments as Map<String, Object?>;
@ -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<String, Object?> arguments = decoded.arguments as Map<String, Object?>;
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<dynamic> arguments = decoded.arguments as List<dynamic>;
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<dynamic, dynamic> arguments = decoded.arguments as Map<dynamic, dynamic>;
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<dynamic, dynamic>;
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;

View File

@ -249,5 +249,7 @@ DomElement _defaultFactory(
}) {
params!;
params as Map<Object?, Object?>;
return domDocument.createElement(params.readString('tagName'));
return domDocument.createElement(params.readString('tagName'))
..style.width = '100%'
..style.height = '100%';
}

View File

@ -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<dynamic, dynamic> args = methodCall.arguments as Map<dynamic, dynamic>;
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<dynamic, dynamic>;
_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<dynamic, dynamic> 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);

View File

@ -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<dynamic, dynamic> {
}
}
/// 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

View File

@ -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<ByteData?> completer;
setUp(() {
contentManager = PlatformViewManager();
completer = Completer<ByteData?>();
});
group('"create" message', () {
test('unregistered viewType, fails with descriptive exception',
() async {
final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler(
platformViewsContainer: createDomElement('div'),
contentManager: contentManager,
);
final Map<dynamic, dynamic> 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<dynamic, dynamic> 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<dynamic, dynamic> 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<dynamic, dynamic> 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<PlatformViewFactoryCall> factoryCalls = <PlatformViewFactoryCall>[];
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<Completer<ByteData?>> completers = <Completer<ByteData?>>[];
completers.add(Completer<ByteData?>());
messageHandler.handleLegacyPlatformViewCall(
'create',
_getCreateArguments(viewType, 111),
completers.last.complete,
);
completers.add(Completer<ByteData?>());
messageHandler.handleLegacyPlatformViewCall(
'create',
_getCreateArguments(viewType, 222, <dynamic, dynamic>{'foo': 'bar'}),
completers.last.complete,
);
completers.add(Completer<ByteData?>());
messageHandler.handleLegacyPlatformViewCall(
'create',
_getCreateArguments(viewType, 333, 'foobar'),
completers.last.complete,
);
completers.add(Completer<ByteData?>());
messageHandler.handleLegacyPlatformViewCall(
'create',
_getCreateArguments(viewType, 444, <dynamic>[1, null, 'str']),
completers.last.complete,
);
final List<ByteData?> responses = await Future.wait(
completers.map((Completer<ByteData?> 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, <dynamic, dynamic>{'foo': 'bar'});
expect(factoryCalls[2].viewId, 333);
expect(factoryCalls[2].params, 'foobar');
expect(factoryCalls[3].viewId, 444);
expect(factoryCalls[3].params, <dynamic>[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<dynamic, dynamic> arguments = _getCreateArguments(viewType, viewId);
expect(() {
messageHandler.handleLegacyPlatformViewCall('create', arguments, (_) {});
}, throwsA(isA<TypeError>()));
});
});
group('"dispose" message', () {
late Completer<int> viewIdCompleter;
setUp(() {
viewIdCompleter = Completer<int>();
});
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<dynamic, dynamic> _getCreateArguments(String viewType, int viewId, [Object? params]) {
return <String, dynamic>{
'id': viewId,
'viewType': viewType,
if (params != null) 'params': params,
};
}

View File

@ -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<ByteData?> completer;
@ -37,52 +37,64 @@ void testMain() {
platformViewsContainer: createDomElement('div'),
contentManager: contentManager,
);
final ByteData? message = _getCreateMessage(viewType, viewId);
final Map<dynamic, dynamic> 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<dynamic, dynamic> 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<dynamic, dynamic> 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<dynamic, dynamic> 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<PlatformViewFactoryCall> factoryCalls = <PlatformViewFactoryCall>[];
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<ByteData?>());
messageHandler.handlePlatformViewCall(
_getCreateMessage(viewType, 111),
'create',
_getCreateArguments(
platformViewType: platformViewType,
platformViewId: 111,
viewId: kImplicitViewId,
),
completers.last.complete,
);
completers.add(Completer<ByteData?>());
messageHandler.handlePlatformViewCall(
_getCreateMessage(viewType, 222, <dynamic, dynamic>{'foo': 'bar'}),
'create',
_getCreateArguments(
platformViewType: platformViewType,
platformViewId: 222,
viewId: kImplicitViewId,
params: <dynamic, dynamic>{'foo': 'bar'},
),
completers.last.complete,
);
completers.add(Completer<ByteData?>());
messageHandler.handlePlatformViewCall(
_getCreateMessage(viewType, 333, 'foobar'),
'create',
_getCreateArguments(
platformViewType: platformViewType,
platformViewId: 333,
viewId: kImplicitViewId,
params: 'foobar',
),
completers.last.complete,
);
completers.add(Completer<ByteData?>());
messageHandler.handlePlatformViewCall(
_getCreateMessage(viewType, 444, <dynamic>[1, null, 'str']),
'create',
_getCreateArguments(
platformViewType: platformViewType,
platformViewId: 444,
viewId: kImplicitViewId,
params: <dynamic>[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<dynamic, dynamic> arguments = _getCreateArguments(
platformViewType: platformViewType,
platformViewId: platformViewId,
viewId: kImplicitViewId,
);
expect(() {
messageHandler.handlePlatformViewCall(message, (_) {});
messageHandler.handlePlatformViewCall('create', arguments, (_) {});
}, throwsA(isA<TypeError>()));
});
});
@ -213,9 +256,12 @@ void testMain() {
platformViewsContainer: createDomElement('div'),
contentManager: contentManager,
);
final ByteData? message = _getDisposeMessage(viewId);
final Map<dynamic, dynamic> 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<dynamic, dynamic> 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',
<String, dynamic>{
'id': viewId,
'viewType': viewType,
if (params != null) 'params': params,
},
));
Map<dynamic, dynamic> _getCreateArguments({
required String platformViewType,
required int platformViewId,
required int viewId,
Object? params,
}) {
return <String, dynamic>{
'platformViewId': platformViewId,
'platformViewType': platformViewType,
if (params != null) 'params': params,
'viewId': viewId,
};
}
ByteData? _getDisposeMessage(int viewId) {
return codec.encodeMethodCall(MethodCall(
'dispose',
viewId,
));
Map<dynamic, dynamic> _getDisposeArguments({
required int platformViewId,
required int viewId,
}) {
return <dynamic, dynamic>{
'platformViewId': platformViewId,
'viewId': viewId,
};
}