[web] Defer injection of platform views until needed. (flutter/engine#48960)
This PR defers the injection of the contents of a Platform View into the DOM of the page, until the Platform View is really needed by a renderer.
This effectively means that a `platformView` will be injected into the DOM the first time that its `slot` is injected into the Shadow DOM of the Flutter web app.
This makes passing a `(flutter)ViewId` parameter from the framework unnecessary, even in a multi-view app.
The only cases in which this change might be breaking is those where an app tries to locate the just-created Platform View by looking into the DOM from the [`onPlatformViewCreated` callback](https://api.flutter.dev/flutter/widgets/HtmlElementView/onPlatformViewCreated.html). In those cases, [a fix like this](https://github.com/flutter/packages/pull/5660) is needed (use the **only [documented way](https://api.flutter.dev/flutter/dart-ui_web/PlatformViewRegistry/getViewById.html)** to obtain the Platform View contents from its `viewId`)
## Issues
Fixes https://github.com/flutter/flutter/issues/137287
Closes https://github.com/flutter/flutter/pull/136548
[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
index e42c9a3..9289184 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
@@ -118,6 +118,9 @@
/// If this returns a [CkCanvas], then that canvas should be the new leaf
/// node. Otherwise, keep the same leaf node.
CkCanvas? compositeEmbeddedView(int viewId) {
+ // Ensure platform view with `viewId` is injected into the `rasterizer.view`.
+ rasterizer.view.dom.injectPlatformView(viewId);
+
final int overlayIndex = _context.visibleViewCount;
_compositionOrder.add(viewId);
// Keep track of the number of visible platform views.
@@ -142,10 +145,10 @@
return recorderToUseForRendering?.recordingCanvas;
}
- void _compositeWithParams(int viewId, EmbeddedViewParams params) {
+ void _compositeWithParams(int platformViewId, EmbeddedViewParams params) {
// If we haven't seen this viewId yet, cache it for clips/transforms.
- final ViewClipChain clipChain = _viewClipChains.putIfAbsent(viewId, () {
- return ViewClipChain(view: createPlatformViewSlot(viewId));
+ final ViewClipChain clipChain = _viewClipChains.putIfAbsent(platformViewId, () {
+ return ViewClipChain(view: createPlatformViewSlot(platformViewId));
});
final DomElement slot = clipChain.slot;
@@ -175,7 +178,7 @@
}
// Apply mutators to the slot
- _applyMutators(params, slot, viewId);
+ _applyMutators(params, slot, platformViewId);
}
int _countClips(MutatorsStack mutators) {
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart
index c5fda3f..367f876 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart
@@ -604,6 +604,10 @@
external DomElement? _querySelector(JSString selectors);
DomElement? querySelector(String selectors) => _querySelector(selectors.toJS);
+ @JS('matches')
+ external JSBoolean _matches(JSString selectors);
+ bool matches(String selectors) => _matches(selectors.toJS).toDart;
+
@JS('querySelectorAll')
external _DomList _querySelectorAll(JSString selectors);
Iterable<DomElement> querySelectorAll(String selectors) =>
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/html/platform_view.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/html/platform_view.dart
index d35ec97..4143c32 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/html/platform_view.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/html/platform_view.dart
@@ -3,14 +3,21 @@
// found in the LICENSE file.
import '../dom.dart';
+import '../platform_dispatcher.dart';
import '../platform_views/slots.dart';
+import '../window.dart';
import 'surface.dart';
/// A surface containing a platform view, which is an HTML element.
class PersistedPlatformView extends PersistedLeafSurface {
- PersistedPlatformView(this.viewId, this.dx, this.dy, this.width, this.height);
+ PersistedPlatformView(this.platformViewId, this.dx, this.dy, this.width, this.height) {
+ // Ensure platform view with `viewId` is injected into the `implicitView`
+ // before rendering its shadow DOM `slot`.
+ final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!;
+ implicitView.dom.injectPlatformView(platformViewId);
+ }
- final int viewId;
+ final int platformViewId;
final double dx;
final double dy;
final double width;
@@ -18,7 +25,7 @@
@override
DomElement createElement() {
- return createPlatformViewSlot(viewId);
+ return createPlatformViewSlot(platformViewId);
}
@override
@@ -36,21 +43,21 @@
bool canUpdateAsMatch(PersistedSurface oldSurface) {
if (super.canUpdateAsMatch(oldSurface)) {
// super checks the runtimeType of the surface, so we can just cast...
- return viewId == ((oldSurface as PersistedPlatformView).viewId);
+ return platformViewId == ((oldSurface as PersistedPlatformView).platformViewId);
}
return false;
}
@override
double matchForUpdate(PersistedPlatformView existingSurface) {
- return existingSurface.viewId == viewId ? 0.0 : 1.0;
+ return existingSurface.platformViewId == platformViewId ? 0.0 : 1.0;
}
@override
void update(PersistedPlatformView oldSurface) {
assert(
- viewId == oldSurface.viewId,
- 'PersistedPlatformView with different viewId should never be updated. Check the canUpdateAsMatch method.',
+ platformViewId == oldSurface.platformViewId,
+ 'PersistedPlatformView with different platformViewId should never be updated. Check the canUpdateAsMatch method.',
);
super.update(oldSurface);
// Only update if the view has been resized
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart
index f6bb447..7ca80db 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart
@@ -78,6 +78,12 @@
_addLocaleChangedListener();
registerHotRestartListener(dispose);
_setAppLifecycleState(ui.AppLifecycleState.resumed);
+ viewManager.onViewDisposed.listen((_) {
+ // Send a metrics changed event to the framework when a view is disposed.
+ // View creation/resize is handled by the `_didResize` handler in the
+ // EngineFlutterView itself.
+ invokeOnMetricsChanged();
+ });
}
/// The [EnginePlatformDispatcher] singleton.
@@ -623,18 +629,12 @@
_handleWebTestEnd2EndMessage(jsonCodec, data)));
return;
- case 'flutter/platform_views':
+ case PlatformViewMessageHandler.channelName:
+ // `arguments` can be a Map<String, Object> for `create`,
+ // but an `int` for `dispose`, hence why `dynamic` everywhere.
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>;
- viewManager[flutterViewId]!
- .platformViewMessageHandler
+ PlatformViewMessageHandler.instance
.handlePlatformViewCall(method, arguments, callback!);
return;
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart
index 222e349..bfad69b 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart
@@ -38,8 +38,7 @@
/// The shared instance of PlatformViewManager shared across the engine to handle
/// rendering of PlatformViews into the web app.
- // TODO(dit): How to make this overridable from tests?
- static final PlatformViewManager instance = PlatformViewManager();
+ static PlatformViewManager instance = PlatformViewManager();
// The factory functions, indexed by the viewType
final Map<String, Function> _factories = <String, Function>{};
@@ -65,6 +64,20 @@
return _contents.containsKey(viewId);
}
+ /// Returns the cached contents of [viewId], to be injected into the DOM.
+ ///
+ /// This is only used by the active `Renderer` object when a platform view needs
+ /// to be injected in the DOM, through `FlutterView.DomManager.injectPlatformView`.
+ ///
+ /// This may return null, if [renderContent] was not called before this. The
+ /// framework seems to allow/need this for some tests, so it is allowed here
+ /// as well.
+ ///
+ /// App programmers should not access this directly, and instead use [getViewById].
+ DomElement? getSlottedContent(int viewId) {
+ return _contents[viewId];
+ }
+
/// Returns the HTML element created by a registered factory for [viewId].
///
/// Throws an [AssertionError] if [viewId] hasn't been rendered before.
@@ -104,9 +117,8 @@
/// Creates the HTML markup for the `contents` of a Platform View.
///
- /// The result of this call is cached in the `_contents` Map. This is only
- /// cached so it can be disposed of later by [clearPlatformView]. _Note that
- /// there's no `getContents` function in this class._
+ /// The result of this call is cached in the `_contents` Map, so the active
+ /// renderer can inject it as needed.
///
/// The resulting DOM for the `contents` of a Platform View looks like this:
///
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/message_handler.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/message_handler.dart
index 9e20b36..86cd7a7 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/message_handler.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/message_handler.dart
@@ -21,7 +21,7 @@
/// This class handles incoming framework messages to create/dispose Platform Views.
///
-/// (An instance of this class is connected to the `flutter/platform_views`
+/// (The instance of this class is connected to the `flutter/platform_views`
/// Platform Channel in the [EnginePlatformDispatcher] class.)
///
/// It uses a [PlatformViewManager] to handle the CRUD of the DOM of Platform Views.
@@ -29,27 +29,33 @@
/// all operations related to platform views (registration, rendering, etc...),
/// regardless of the rendering backend.
///
-/// When the `contents` of a Platform View are created, a [PlatformViewContentHandler]
-/// function (passed from the outside) will decide where in the DOM to inject
-/// said content.
+/// Platform views are injected into the DOM when needed by the correct instance
+/// of the active renderer.
///
-/// The rendering/compositing of Platform Views can create the other "half" of a
+/// The rendering and compositing of Platform Views can create the other "half" of a
/// Platform View: the `slot`, through the [createPlatformViewSlot] method.
///
/// When a Platform View is disposed of, it is removed from the cache (and DOM)
/// directly by the `contentManager`. The canvaskit rendering backend needs to do
/// some extra cleanup of its internal state, but it can do it automatically. See
-/// [HtmlViewEmbedder.disposeViews]
+/// [HtmlViewEmbedder.disposeViews].
class PlatformViewMessageHandler {
PlatformViewMessageHandler({
- required DomElement platformViewsContainer,
- PlatformViewManager? contentManager,
- }) : _contentManager = contentManager ?? PlatformViewManager.instance,
- _platformViewsContainer = platformViewsContainer;
+ required PlatformViewManager contentManager,
+ }) : _contentManager = contentManager;
+
+ static const String channelName = 'flutter/platform_views';
+
+ /// The shared instance of PlatformViewMessageHandler.
+ ///
+ /// Unless configured differently, this connects to the shared instance of the
+ /// [PlatformViewManager].
+ static PlatformViewMessageHandler instance = PlatformViewMessageHandler(
+ contentManager: PlatformViewManager.instance,
+ );
final MethodCodec _codec = const StandardMethodCodec();
final PlatformViewManager _contentManager;
- final DomElement _platformViewsContainer;
/// Handle a `create` Platform View message.
///
@@ -58,10 +64,12 @@
///
/// (See [PlatformViewManager.registerFactory] for more details.)
///
- /// The `contents` are inserted into the [_platformViewsContainer].
- ///
/// 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.
+ ///
+ /// The `callback` signals when the contents of a given [platformViewId] have
+ /// been rendered. They're now accessible through `platformViewRegistry.getViewById`
+ /// from `dart:ui_web`. **(Not the DOM!)**
void _createPlatformView(
_PlatformMessageResponseCallback callback, {
required int platformViewId,
@@ -88,15 +96,12 @@
return;
}
- final DomElement content = _contentManager.renderContent(
+ _contentManager.renderContent(
platformViewType,
platformViewId,
params,
);
- // For now, we don't need anything fancier. If needed, this can be converted
- // to a PlatformViewStrategy class for each web-renderer backend?
- _platformViewsContainer.append(content);
callback(_codec.encodeSuccessEnvelope(null));
}
@@ -126,7 +131,7 @@
/// 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(
+ void handlePlatformViewCall(
String method,
dynamic arguments,
_PlatformMessageResponseCallback callback,
@@ -141,39 +146,11 @@
params: arguments['params'],
);
return;
+ // TODO(web): Send `arguments` as a Map for `dispose` too!
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(
- String method,
- Map<dynamic, dynamic> arguments,
- _PlatformMessageResponseCallback callback,
- ) {
- switch (method) {
- case 'create':
- _createPlatformView(
- callback,
- platformViewId: arguments.readInt('platformViewId'),
- platformViewType: arguments.readString('platformViewType'),
- params: arguments['params'],
- );
- return;
- case 'dispose':
- _disposePlatformView(
- callback,
- platformViewId: arguments.readInt('platformViewId'),
- );
- return;
- }
- callback(null);
- }
}
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/scene_view.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/scene_view.dart
index 61f4e48..23010c6 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/scene_view.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/scene_view.dart
@@ -111,6 +111,12 @@
case PlatformViewSlice():
for (final PlatformView view in slice.views) {
+ // TODO(harryterkelsen): Inject the FlutterView instance from `renderScene`,
+ // instead of using `EnginePlatformDispatcher...implicitView` directly,
+ // or make the FlutterView "register" like in canvaskit.
+ // Ensure the platform view contents are injected in the DOM.
+ EnginePlatformDispatcher.instance.implicitView?.dom.injectPlatformView(view.viewId);
+
// Attempt to reuse a container for the existing view
PlatformViewContainer? container;
for (int j = 0; j < reusableContainers.length; j++) {
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart
index 06a65a8..b1398f5 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart
@@ -6,6 +6,7 @@
import '../configuration.dart';
import '../dom.dart';
+import '../platform_views/content_manager.dart';
import '../safe_browser_api.dart';
import '../semantics/semantics.dart';
import 'style_manager.dart';
@@ -204,6 +205,33 @@
sceneHost.append(sceneElement);
}
}
+
+ /// Injects a platform view with [platformViewId] into [platformViewsHost].
+ ///
+ /// If the platform view is already injected, this method does *nothing*.
+ ///
+ /// The `platformViewsHost` can only be different if `platformViewId` is moving
+ /// from one [FlutterView] to another. In that case, the browser will move the
+ /// slot contents from the old `platformViewsHost` to the new one, but that
+ /// will cause the platformView to reset its state (an iframe will re-render,
+ /// text selections will be lost, video playback interrupted, etc...)
+ ///
+ /// Try not to move platform views across views!
+ void injectPlatformView(int platformViewId) {
+ // For now, we don't need anything fancier. If needed, this can be converted
+ // to a PlatformViewStrategy class for each web-renderer backend?
+ final DomElement? pv = PlatformViewManager.instance.getSlottedContent(platformViewId);
+ if (pv == null) {
+ domWindow.console.debug('Failed to inject Platform View Id: $platformViewId. '
+ 'Render seems to be happening before a `flutter/platform_views:create` platform message!');
+ return;
+ }
+ // If pv is already a descendant of platformViewsHost -> noop
+ if (pv.parent == platformViewsHost) {
+ return;
+ }
+ platformViewsHost.append(pv);
+ }
}
DomShadowRoot _attachShadowRoot(DomElement element) {
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/window.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/window.dart
index 6ed35bb..5093707 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/window.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/window.dart
@@ -17,7 +17,6 @@
import 'mouse/cursor.dart';
import 'navigation/history.dart';
import 'platform_dispatcher.dart';
-import 'platform_views/message_handler.dart';
import 'pointer_binding.dart';
import 'semantics.dart';
import 'services.dart';
@@ -132,9 +131,6 @@
late final DomManager dom =
DomManager(viewId: viewId, devicePixelRatio: devicePixelRatio);
- late final PlatformViewMessageHandler platformViewMessageHandler =
- PlatformViewMessageHandler(platformViewsContainer: dom.platformViewsHost);
-
late final PointerBinding pointerBinding;
// TODO(goderbauer): Provide API to configure constraints. See also TODO in "render".
diff --git a/engine/src/flutter/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart b/engine/src/flutter/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart
index 38a3096..6dd81ad 100644
--- a/engine/src/flutter/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart
+++ b/engine/src/flutter/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart
@@ -224,6 +224,34 @@
expect(view2.isDisposed, isTrue);
expect(view3.isDisposed, isTrue);
});
+
+ test('connects view disposal to metrics changed event', () {
+ final EnginePlatformDispatcher dispatcher = EnginePlatformDispatcher();
+ final EngineFlutterView view1 =
+ EngineFlutterView(dispatcher, createDomHTMLDivElement());
+ final EngineFlutterView view2 =
+ EngineFlutterView(dispatcher, createDomHTMLDivElement());
+
+ dispatcher.viewManager
+ ..registerView(view1)
+ ..registerView(view2);
+
+ expect(view1.isDisposed, isFalse);
+ expect(view2.isDisposed, isFalse);
+
+ bool onMetricsChangedCalled = false;
+ dispatcher.onMetricsChanged = () {
+ onMetricsChangedCalled = true;
+ };
+
+ expect(onMetricsChangedCalled, isFalse);
+
+ dispatcher.viewManager.disposeAndUnregisterView(view2.viewId);
+
+ expect(onMetricsChangedCalled, isTrue, reason: 'onMetricsChanged should have been called.');
+
+ dispatcher.dispose();
+ });
});
}
diff --git a/engine/src/flutter/lib/web_ui/test/engine/platform_views/legacy_message_handler_test.dart b/engine/src/flutter/lib/web_ui/test/engine/platform_views/legacy_message_handler_test.dart
deleted file mode 100644
index 2c9f477..0000000
--- a/engine/src/flutter/lib/web_ui/test/engine/platform_views/legacy_message_handler_test.dart
+++ /dev/null
@@ -1,265 +0,0 @@
-// 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,
- };
-}
diff --git a/engine/src/flutter/lib/web_ui/test/engine/platform_views/message_handler_test.dart b/engine/src/flutter/lib/web_ui/test/engine/platform_views/message_handler_test.dart
index 344de91..42b7059 100644
--- a/engine/src/flutter/lib/web_ui/test/engine/platform_views/message_handler_test.dart
+++ b/engine/src/flutter/lib/web_ui/test/engine/platform_views/message_handler_test.dart
@@ -34,14 +34,9 @@
test('unregistered viewType, fails with descriptive exception',
() async {
final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler(
- platformViewsContainer: createDomElement('div'),
contentManager: contentManager,
);
- final Map<dynamic, dynamic> arguments = _getCreateArguments(
- platformViewType: platformViewType,
- platformViewId: platformViewId,
- viewId: kImplicitViewId,
- );
+ final Map<dynamic, dynamic> arguments = _getCreateArguments(platformViewType, platformViewId);
messageHandler.handlePlatformViewCall('create', arguments, completer.complete);
@@ -60,14 +55,9 @@
platformViewType, (int id) => createDomHTMLDivElement());
contentManager.renderContent(platformViewType, platformViewId, null);
final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler(
- platformViewsContainer: createDomElement('div'),
contentManager: contentManager,
);
- final Map<dynamic, dynamic> arguments = _getCreateArguments(
- platformViewType: platformViewType,
- platformViewId: platformViewId,
- viewId: kImplicitViewId,
- );
+ final Map<dynamic, dynamic> arguments = _getCreateArguments(platformViewType, platformViewId);
messageHandler.handlePlatformViewCall('create', arguments, completer.complete);
@@ -85,14 +75,9 @@
contentManager.registerFactory(
platformViewType, (int id) => createDomHTMLDivElement()..id = 'success');
final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler(
- platformViewsContainer: createDomElement('div'),
contentManager: contentManager,
);
- final Map<dynamic, dynamic> arguments = _getCreateArguments(
- platformViewType: platformViewType,
- platformViewId: platformViewId,
- viewId: kImplicitViewId,
- );
+ final Map<dynamic, dynamic> arguments = _getCreateArguments(platformViewType, platformViewId);
messageHandler.handlePlatformViewCall('create', arguments, completer.complete);
@@ -102,41 +87,40 @@
'The response should be a success envelope, with null in it.');
});
- test('inserts the created view into the platformViewsContainer',
+ test('caches the created view so it can be retrieved (not on the DOM)',
() async {
final DomElement platformViewsContainer = createDomElement('pv-container');
contentManager.registerFactory(
platformViewType, (int id) => createDomHTMLDivElement()..id = 'success');
final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler(
- platformViewsContainer: platformViewsContainer,
contentManager: contentManager,
);
- final Map<dynamic, dynamic> arguments = _getCreateArguments(
- platformViewType: platformViewType,
- platformViewId: platformViewId,
- viewId: kImplicitViewId,
- );
+ final Map<dynamic, dynamic> arguments = _getCreateArguments(platformViewType, platformViewId);
messageHandler.handlePlatformViewCall('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.',
);
+ expect(
+ contentManager.knowsViewId(platformViewId),
+ isTrue,
+ reason: 'The contentManager should have pre-rendered the platformViewId.'
+ );
+ expect(
+ contentManager.getViewById(platformViewId).matches('div#success'),
+ isNotNull,
+ reason: 'The element created by the factory should be retrievable.',
+ );
+ expect(
+ platformViewsContainer.children,
+ hasLength(0),
+ reason: 'The view should not have been injected into the DOM',
+ );
});
test('passes creation params to the factory', () async {
@@ -146,7 +130,6 @@
return createDomHTMLDivElement();
});
final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler(
- platformViewsContainer: createDomElement('div'),
contentManager: contentManager,
);
@@ -155,47 +138,28 @@
completers.add(Completer<ByteData?>());
messageHandler.handlePlatformViewCall(
'create',
- _getCreateArguments(
- platformViewType: platformViewType,
- platformViewId: 111,
- viewId: kImplicitViewId,
- ),
+ _getCreateArguments(platformViewType, 111),
completers.last.complete,
);
completers.add(Completer<ByteData?>());
messageHandler.handlePlatformViewCall(
'create',
- _getCreateArguments(
- platformViewType: platformViewType,
- platformViewId: 222,
- viewId: kImplicitViewId,
- params: <dynamic, dynamic>{'foo': 'bar'},
- ),
+ _getCreateArguments(platformViewType, 222, <dynamic, dynamic>{'foo': 'bar'}),
completers.last.complete,
);
completers.add(Completer<ByteData?>());
messageHandler.handlePlatformViewCall(
'create',
- _getCreateArguments(
- platformViewType: platformViewType,
- platformViewId: 333,
- viewId: kImplicitViewId,
- params: 'foobar',
- ),
+ _getCreateArguments(platformViewType, 333, 'foobar'),
completers.last.complete,
);
completers.add(Completer<ByteData?>());
messageHandler.handlePlatformViewCall(
'create',
- _getCreateArguments(
- platformViewType: platformViewType,
- platformViewId: 444,
- viewId: kImplicitViewId,
- params: <dynamic>[1, null, 'str'],
- ),
+ _getCreateArguments(platformViewType, 444, <dynamic>[1, null, 'str']),
completers.last.complete,
);
@@ -229,14 +193,9 @@
});
final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler(
- platformViewsContainer: createDomElement('div'),
contentManager: contentManager,
);
- final Map<dynamic, dynamic> arguments = _getCreateArguments(
- platformViewType: platformViewType,
- platformViewId: platformViewId,
- viewId: kImplicitViewId,
- );
+ final Map<dynamic, dynamic> arguments = _getCreateArguments(platformViewType, platformViewId);
expect(() {
messageHandler.handlePlatformViewCall('create', arguments, (_) {});
@@ -253,15 +212,10 @@
test('never fails, even for unknown viewIds', () async {
final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler(
- platformViewsContainer: createDomElement('div'),
contentManager: contentManager,
);
- final Map<dynamic, dynamic> arguments = _getDisposeArguments(
- platformViewId: platformViewId,
- viewId: kImplicitViewId,
- );
- messageHandler.handlePlatformViewCall('dispose', arguments, completer.complete);
+ messageHandler.handlePlatformViewCall('dispose', platformViewId, completer.complete);
final ByteData? response = await completer.future;
expect(codec.decodeEnvelope(response!), isNull,
@@ -271,15 +225,10 @@
test('never fails, even for unknown viewIds', () async {
final PlatformViewMessageHandler messageHandler = PlatformViewMessageHandler(
- platformViewsContainer: createDomElement('div'),
contentManager: _FakePlatformViewManager(viewIdCompleter.complete),
);
- final Map<dynamic, dynamic> arguments = _getDisposeArguments(
- platformViewId: platformViewId,
- viewId: kImplicitViewId,
- );
- messageHandler.handlePlatformViewCall('dispose', arguments, completer.complete);
+ messageHandler.handlePlatformViewCall('dispose', platformViewId, completer.complete);
final int disposedViewId = await viewIdCompleter.future;
expect(disposedViewId, platformViewId,
@@ -303,26 +252,10 @@
}
}
-Map<dynamic, dynamic> _getCreateArguments({
- required String platformViewType,
- required int platformViewId,
- required int viewId,
- Object? params,
-}) {
+Map<dynamic, dynamic> _getCreateArguments(String viewType, int viewId, [Object? params]) {
return <String, dynamic>{
- 'platformViewId': platformViewId,
- 'platformViewType': platformViewType,
+ 'id': viewId,
+ 'viewType': viewType,
if (params != null) 'params': params,
- 'viewId': viewId,
- };
-}
-
-Map<dynamic, dynamic> _getDisposeArguments({
- required int platformViewId,
- required int viewId,
-}) {
- return <dynamic, dynamic>{
- 'platformViewId': platformViewId,
- 'viewId': viewId,
};
}