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 1 commit
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
Prev Previous commit
Next Next commit
Update the PR with offline discussion:
* Make the PlatformViewsContentManager a singleton.
* Modify the DOM Manager of a view so it can inject platformViews into
  the right spot.
* Use the view.dom.injectPlatformView from renderers so naming is less
  iffy.
* Use a slightly faster way to determine if a platform view is
  injected in a platformViewHostNode or not.

* Break many more tests.
  • Loading branch information
ditman committed Dec 15, 2023
commit 5b00e5a9d0cf5ab19a2b70313e3659932a004428
12 changes: 6 additions & 6 deletions lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,14 @@ class HtmlViewEmbedder {
return recorderToUseForRendering?.recordingCanvas;
}

void _compositeWithParams(int viewId, EmbeddedViewParams params) {
// Ensure platform view with `viewId` is injected into the `rasterizer.view`
void _compositeWithParams(int platformViewId, EmbeddedViewParams params) {
// Ensure platform view with `platformViewId` is injected into the `rasterizer.view`
// before rendering its shadow DOM `slot`.
rasterizer.view.platformViewMessageHandler.injectPlatformView(viewId);
rasterizer.view.dom.injectPlatformView(platformViewId);

// 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;
Expand Down Expand Up @@ -179,7 +179,7 @@ class HtmlViewEmbedder {
}

// Apply mutators to the slot
_applyMutators(params, slot, viewId);
_applyMutators(params, slot, platformViewId);
}

int _countClips(MutatorsStack mutators) {
Expand Down
16 changes: 8 additions & 8 deletions lib/web_ui/lib/src/engine/html/platform_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ 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);

final int viewId;
final int platformViewId;
final double dx;
final double dy;
final double width;
Expand All @@ -23,9 +23,9 @@ class PersistedPlatformView extends PersistedLeafSurface {
// Ensure platform view with `viewId` is injected into the `implicitView`
// before rendering its shadow DOM `slot`.
final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!;
implicitView.platformViewMessageHandler.injectPlatformView(viewId);
implicitView.dom.injectPlatformView(platformViewId);

return createPlatformViewSlot(viewId);
return createPlatformViewSlot(platformViewId);
}

@override
Expand All @@ -43,21 +43,21 @@ class PersistedPlatformView extends PersistedLeafSurface {
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
Expand Down
14 changes: 4 additions & 10 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -614,18 +614,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
_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;

Expand Down
18 changes: 11 additions & 7 deletions lib/web_ui/lib/src/engine/platform_views/content_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ class PlatformViewManager {

/// 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>{};
Expand All @@ -65,8 +64,14 @@ class PlatformViewManager {
return _contents.containsKey(viewId);
}

/// Returns the pre-rendered contents of [viewId], to inject them into the DOM.
DomElement getContents(int viewId) {
/// Returns the cached contents of [viewId], to inject them 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`.
///
/// App programmers should not access this directly, and instead use [getViewById].
DomElement getSlottedContent(int viewId) {
assert(knowsViewId(viewId), 'No platform view has been rendered with id: $viewId');
return _contents[viewId]!;
}

Expand Down Expand Up @@ -109,9 +114,8 @@ class PlatformViewManager {

/// 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:
///
Expand Down
82 changes: 24 additions & 58 deletions lib/web_ui/lib/src/engine/platform_views/message_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,41 @@ typedef PlatformViewContentHandler = void Function(DomElement);

/// 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.
/// This `contentManager` is shared across the engine, to perform
/// 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.
///
Expand All @@ -58,10 +64,12 @@ class PlatformViewMessageHandler {
///
/// (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,
Expand All @@ -88,8 +96,6 @@ class PlatformViewMessageHandler {
return;
}

// Ensure the DomElement of the view is *created*, so programmers can
// access it through `_contentManager.getViewById` (maybe not the DOM!).
_contentManager.renderContent(
platformViewType,
platformViewId,
Expand All @@ -99,18 +105,6 @@ class PlatformViewMessageHandler {
callback(_codec.encodeSuccessEnvelope(null));
}

/// Injects a platform view with [viewId] into this handler's `platformViewsContainer`.
void injectPlatformView(int viewId) {
// 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 = _contentManager.getContents(viewId);
// If pv is a descendant of _platformViewsContainer -> noop
if (_platformViewsContainer.contains(pv)) {
return;
}
_platformViewsContainer.append(pv);
}

/// Handle a `dispose` Platform View message.
///
/// This will clear the cached information that the framework has about a given
Expand All @@ -137,54 +131,26 @@ class PlatformViewMessageHandler {
/// 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,
) {
switch (method) {
case 'create':
arguments as Map<dynamic, dynamic>;
arguments as Map<String, Object?>;
_createPlatformView(
callback,
platformViewId: arguments.readInt('id'),
platformViewType: arguments.readString('viewType'),
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);
}
}
23 changes: 23 additions & 0 deletions lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:ui/ui.dart' as ui;

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';
Expand Down Expand Up @@ -204,6 +205,28 @@ class DomManager {
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 is a descendant of _platformViewsContainer -> noop
if (pv.parent == platformViewsHost) {
return;
}
platformViewsHost.append(pv);
}
}

DomShadowRoot _attachShadowRoot(DomElement element) {
Expand Down
4 changes: 0 additions & 4 deletions lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import 'mouse/context_menu.dart';
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';
Expand Down Expand Up @@ -130,9 +129,6 @@ base class EngineFlutterView implements ui.FlutterView {

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".
Expand Down