[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,
   };
 }