Skip to content

Commit

Permalink
[web] Render PlatformViews with SLOT tags. (#25747)
Browse files Browse the repository at this point in the history
  • Loading branch information
ditman authored May 21, 2021
1 parent acea7c4 commit b5f8141
Show file tree
Hide file tree
Showing 23 changed files with 1,117 additions and 444 deletions.
3 changes: 3 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,9 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/message_handler.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/slots.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_converter.dart
Expand Down
8 changes: 8 additions & 0 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ part 'engine/onscreen_logging.dart';
part 'engine/picture.dart';
part 'engine/platform_dispatcher.dart';
part 'engine/platform_views.dart';
part 'engine/platform_views/content_manager.dart';
part 'engine/platform_views/message_handler.dart';
part 'engine/platform_views/slots.dart';
part 'engine/profiler.dart';
part 'engine/rrect_renderer.dart';
part 'engine/semantics/accessibility.dart';
Expand Down Expand Up @@ -413,6 +416,11 @@ class NullTreeSanitizer implements html.NodeTreeSanitizer {
void sanitizeTree(html.Node node) {}
}

/// 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?
final PlatformViewManager platformViewManager = PlatformViewManager();

/// Converts a matrix represented using [Float64List] to one represented using
/// [Float32List].
///
Expand Down
186 changes: 67 additions & 119 deletions lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
// found in the LICENSE file.

import 'dart:html' as html;
import 'dart:typed_data';

import 'package:ui/src/engine.dart' show window, NullTreeSanitizer;
import 'package:ui/src/engine.dart' show window, NullTreeSanitizer, platformViewManager, createPlatformViewSlot;
import 'package:ui/ui.dart' as ui;

import '../html/path_to_svg_clip.dart';
import '../services.dart';
import '../util.dart';
import '../vector_math.dart';
import 'canvas.dart';
Expand Down Expand Up @@ -37,11 +35,13 @@ class HtmlViewEmbedder {
final Map<int, EmbeddedViewParams> _currentCompositionParams =
<int, EmbeddedViewParams>{};

/// The HTML element associated with the given view id.
final Map<int?, html.Element> _views = <int?, html.Element>{};

/// The root view in the stack of mutator elements for the view id.
final Map<int?, html.Element?> _rootViews = <int?, html.Element?>{};
/// The clip chain for a view Id.
///
/// This contains:
/// * The root view in the stack of mutator elements for the view id.
/// * The slot view in the stack (what shows the actual platform view contents).
/// * The number of clipping elements used last time the view was composited.
final Map<int, ViewClipChain> _viewClipChains = <int, ViewClipChain>{};

/// Surfaces used to draw on top of platform views, keyed by platform view ID.
///
Expand All @@ -51,18 +51,12 @@ class HtmlViewEmbedder {
/// The views that need to be recomposited into the scene on the next frame.
final Set<int> _viewsToRecomposite = <int>{};

/// The views that need to be disposed of on the next frame.
final Set<int> _viewsToDispose = <int>{};

/// The list of view ids that should be composited, in order.
List<int> _compositionOrder = <int>[];

/// The most recent composition order.
List<int> _activeCompositionOrder = <int>[];

/// The number of clipping elements used last time the view was composited.
Map<int, int> _clipCount = <int, int>{};

/// The size of the frame, in physical pixels.
ui.Size _frameSize = ui.window.physicalSize;

Expand All @@ -74,76 +68,6 @@ class HtmlViewEmbedder {
_frameSize = size;
}

void handlePlatformViewCall(
ByteData? data,
ui.PlatformMessageResponseCallback? callback,
) {
const MethodCodec codec = StandardMethodCodec();
final MethodCall decoded = codec.decodeMethodCall(data);

switch (decoded.method) {
case 'create':
_create(decoded, callback);
return;
case 'dispose':
_dispose(decoded, callback!);
return;
}
callback!(null);
}

void _create(
MethodCall methodCall, ui.PlatformMessageResponseCallback? callback) {
final Map<dynamic, dynamic> args = methodCall.arguments;
final int? viewId = args['id'];
final String? viewType = args['viewType'];
const MethodCodec codec = StandardMethodCodec();

if (_views[viewId] != null) {
callback!(codec.encodeErrorEnvelope(
code: 'recreating_view',
message: 'trying to create an already created view',
details: 'view id: $viewId',
));
return;
}

final ui.PlatformViewFactory? factory =
ui.platformViewRegistry.registeredFactories[viewType];
if (factory == null) {
callback!(codec.encodeErrorEnvelope(
code: 'unregistered_view_type',
message: 'trying to create a view with an unregistered type',
details: 'unregistered view type: $viewType',
));
return;
}

// TODO(het): Support creation parameters.
html.Element embeddedView = factory(viewId!);
_views[viewId] = embeddedView;

_rootViews[viewId] = embeddedView;

callback!(codec.encodeSuccessEnvelope(null));
}

void _dispose(
MethodCall methodCall, ui.PlatformMessageResponseCallback callback) {
final int? viewId = methodCall.arguments;
const MethodCodec codec = StandardMethodCodec();
if (viewId == null || !_views.containsKey(viewId)) {
callback(codec.encodeErrorEnvelope(
code: 'unknown_view',
message: 'trying to dispose an unknown view',
details: 'view id: $viewId',
));
return;
}
_viewsToDispose.add(viewId);
callback(codec.encodeSuccessEnvelope(null));
}

List<CkCanvas> getCurrentCanvases() {
final List<CkCanvas> canvases = <CkCanvas>[];
for (int i = 0; i < _compositionOrder.length; i++) {
Expand Down Expand Up @@ -179,28 +103,39 @@ class HtmlViewEmbedder {
}

void _compositeWithParams(int viewId, EmbeddedViewParams params) {
final html.Element platformView = _views[viewId]!;
platformView.style.width = '${params.size.width}px';
platformView.style.height = '${params.size.height}px';
platformView.style.position = 'absolute';
// If we haven't seen this viewId yet, cache it for clips/transforms.
ViewClipChain clipChain = _viewClipChains.putIfAbsent(viewId, () {
return ViewClipChain(view: createPlatformViewSlot(viewId));
});

// <flt-scene-host> disables pointer events. Reenable them here because the
// underlying platform view would want to handle the pointer events.
platformView.style.pointerEvents = 'auto';
html.Element slot = clipChain.slot;

// See `apply()` in the PersistedPlatformView class for the HTML version
// of this code.
slot.style
..width = '${params.size.width}px'
..height = '${params.size.height}px'
..position = 'absolute';

// Recompute the position in the DOM of the `slot` element...
final int currentClippingCount = _countClips(params.mutators);
final int? previousClippingCount = _clipCount[viewId];
final int previousClippingCount = clipChain.clipCount;
if (currentClippingCount != previousClippingCount) {
_clipCount[viewId] = currentClippingCount;
html.Element oldPlatformViewRoot = _rootViews[viewId]!;
html.Element? newPlatformViewRoot = _reconstructClipViewsChain(
html.Element oldPlatformViewRoot = clipChain.root;
html.Element newPlatformViewRoot = _reconstructClipViewsChain(
currentClippingCount,
platformView,
slot,
oldPlatformViewRoot,
);
_rootViews[viewId] = newPlatformViewRoot;
// Store the updated root element, and clip count
clipChain.updateClipChain(
root: newPlatformViewRoot,
clipCount: currentClippingCount,
);
}
_applyMutators(params.mutators, platformView, viewId);

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

int _countClips(MutatorsStack mutators) {
Expand All @@ -213,7 +148,7 @@ class HtmlViewEmbedder {
return clipCount;
}

html.Element? _reconstructClipViewsChain(
html.Element _reconstructClipViewsChain(
int numClips,
html.Element platformView,
html.Element headClipView,
Expand Down Expand Up @@ -386,8 +321,6 @@ class HtmlViewEmbedder {
}

void submitFrame() {
disposeViews();

for (int i = 0; i < _compositionOrder.length; i++) {
int viewId = _compositionOrder[i];
_ensureOverlayInitialized(viewId);
Expand All @@ -412,28 +345,26 @@ class HtmlViewEmbedder {
int viewId = _compositionOrder[i];

if (assertionsEnabled) {
if (!_views.containsKey(viewId)) {
if (!platformViewManager.knowsViewId(viewId)) {
debugInvalidViewIds ??= <int>[];
debugInvalidViewIds.add(viewId);
continue;
}
}

unusedViews.remove(viewId);
html.Element platformViewRoot = _rootViews[viewId]!;
html.Element platformViewRoot = _viewClipChains[viewId]!.root;
html.Element overlay = _overlays[viewId]!.htmlElement;
platformViewRoot.remove();
skiaSceneHost!.append(platformViewRoot);
overlay.remove();
skiaSceneHost!.append(overlay);
_activeCompositionOrder.add(viewId);
}

_compositionOrder.clear();

for (final int unusedViewId in unusedViews) {
_releaseOverlay(unusedViewId);
_rootViews[unusedViewId]?.remove();
}
disposeViews(unusedViews);

if (assertionsEnabled) {
if (debugInvalidViewIds != null && debugInvalidViewIds.isNotEmpty) {
Expand All @@ -445,24 +376,18 @@ class HtmlViewEmbedder {
}
}

void disposeViews() {
if (_viewsToDispose.isEmpty) {
return;
}

for (final int viewId in _viewsToDispose) {
final html.Element rootView = _rootViews[viewId]!;
rootView.remove();
_views.remove(viewId);
_rootViews.remove(viewId);
void disposeViews(Set<int> viewsToDispose) {
for (final int viewId in viewsToDispose) {
// Remove viewId from the _viewClipChains Map, and then from the DOM.
ViewClipChain clipChain = _viewClipChains.remove(viewId)!;
clipChain.root.remove();
// More cleanup
_releaseOverlay(viewId);
_currentCompositionParams.remove(viewId);
_clipCount.remove(viewId);
_viewsToRecomposite.remove(viewId);
_cleanUpClipDefs(viewId);
_svgClipDefs.remove(viewId);
}
_viewsToDispose.clear();
}

void _releaseOverlay(int viewId) {
Expand Down Expand Up @@ -499,6 +424,29 @@ class HtmlViewEmbedder {
}
}

/// Represents a Clip Chain (for a view).
///
/// Objects of this class contain:
/// * The root view in the stack of mutator elements for the view id.
/// * The slot view in the stack (the actual contents of the platform view).
/// * The number of clipping elements used last time the view was composited.
class ViewClipChain {
html.Element _root;
html.Element _slot;
int _clipCount = -1;

ViewClipChain({required html.Element view}) : this._root = view, this._slot = view;

html.Element get root => _root;
html.Element get slot => _slot;
int get clipCount => _clipCount;

void updateClipChain({required html.Element root, required int clipCount}) {
_root = root;
_clipCount = clipCount;
}
}

/// Caches surfaces used to overlay platform views.
class OverlayCache {
static const int kDefaultCacheSize = 5;
Expand Down
Loading

0 comments on commit b5f8141

Please sign in to comment.