Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally forgot about this method. Good repurposing though!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You must've been looking at an individual commit from the PR 🙂 This method is new.

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