Skip to content

Commit

Permalink
[web] Support flutterViewId in platform view messages (#46891)
Browse files Browse the repository at this point in the history
- 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%`.
  • Loading branch information
mdebbar authored Oct 20, 2023
1 parent 8bd8294 commit 471fbc5
Show file tree
Hide file tree
Showing 6 changed files with 477 additions and 99 deletions.
60 changes: 32 additions & 28 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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) {
Expand All @@ -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;

Expand All @@ -496,22 +498,21 @@ 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
// will need to include the view ID. Right now only one view is
// 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?>;
Expand All @@ -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);
Expand All @@ -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':
Expand All @@ -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':
Expand All @@ -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);
}
Expand Down Expand Up @@ -1350,7 +1354,7 @@ class ViewConfiguration {
});

ViewConfiguration copyWith({
ui.FlutterView? view,
EngineFlutterView? view,
double? devicePixelRatio,
ui.Rect? geometry,
bool? visible,
Expand All @@ -1375,7 +1379,7 @@ class ViewConfiguration {
);
}

final ui.FlutterView? view;
final EngineFlutterView? view;
final double devicePixelRatio;
final ui.Rect geometry;
final bool visible;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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%';
}
84 changes: 57 additions & 27 deletions lib/web_ui/lib/src/engine/platform_views/message_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand All @@ -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,
);

Expand All @@ -106,42 +103,75 @@ 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
/// it needed to get ahold of.
///
/// 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);
Expand Down
22 changes: 22 additions & 0 deletions lib/web_ui/lib/src/engine/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 471fbc5

Please sign in to comment.