mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[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:
parent
38b42c9f14
commit
92eac4f833
@ -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;
|
||||
|
||||
@ -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%';
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user