@@ -30,14 +30,16 @@ class HtmlViewEmbedder {
3030 /// The root view in the stack of mutator elements for the view id.
3131 final Map <int ?, html.Element ?> _rootViews = < int ? , html.Element ? > {};
3232
33- /// The overlay for the view id.
34- final Map <int , Overlay > _overlays = < int , Overlay > {};
33+ /// Surfaces used to draw on top of platform views, keyed by platform view ID.
34+ ///
35+ /// These surfaces are cached in the [OverlayCache] and reused.
36+ final Map <int , Surface > _overlays = < int , Surface > {};
3537
3638 /// The views that need to be recomposited into the scene on the next frame.
3739 final Set <int > _viewsToRecomposite = < int > {};
3840
3941 /// The views that need to be disposed of on the next frame.
40- final Set <int ? > _viewsToDispose = < int ? > {};
42+ final Set <int > _viewsToDispose = < int > {};
4143
4244 /// The list of view ids that should be composited, in order.
4345 List <int > _compositionOrder = < int > [];
@@ -115,14 +117,15 @@ class HtmlViewEmbedder {
115117
116118 void _dispose (
117119 MethodCall methodCall, ui.PlatformMessageResponseCallback callback) {
118- int ? viewId = methodCall.arguments;
120+ final int ? viewId = methodCall.arguments;
119121 const MethodCodec codec = StandardMethodCodec ();
120- if (! _views.containsKey (viewId)) {
122+ if (viewId == null || ! _views.containsKey (viewId)) {
121123 callback (codec.encodeErrorEnvelope (
122124 code: 'unknown_view' ,
123125 message: 'trying to dispose an unknown view' ,
124126 details: 'view id: $viewId ' ,
125127 ));
128+ return ;
126129 }
127130 _viewsToDispose.add (viewId);
128131 callback (codec.encodeSuccessEnvelope (null ));
@@ -339,9 +342,9 @@ class HtmlViewEmbedder {
339342
340343 for (int i = 0 ; i < _compositionOrder.length; i++ ) {
341344 int viewId = _compositionOrder[i];
342- ensureOverlayInitialized (viewId);
345+ _ensureOverlayInitialized (viewId);
343346 final SurfaceFrame frame =
344- _overlays[viewId]! .surface. acquireFrame (_frameSize);
347+ _overlays[viewId]! .acquireFrame (_frameSize);
345348 final CkCanvas canvas = frame.skiaCanvas;
346349 canvas.drawPicture (
347350 _pictureRecorders[viewId]! .endRecording (),
@@ -353,53 +356,127 @@ class HtmlViewEmbedder {
353356 _compositionOrder.clear ();
354357 return ;
355358 }
359+
360+ final Set <int > unusedViews = Set <int >.from (_activeCompositionOrder);
356361 _activeCompositionOrder.clear ();
357362
358363 for (int i = 0 ; i < _compositionOrder.length; i++ ) {
359364 int viewId = _compositionOrder[i];
365+
366+ assert (
367+ _views.containsKey (viewId),
368+ 'Cannot render platform view $viewId . '
369+ 'It has not been created, or it has been deleted.' ,
370+ );
371+
372+ unusedViews.remove (viewId);
360373 html.Element platformViewRoot = _rootViews[viewId]! ;
361- html.Element overlay = _overlays[viewId]! .surface. htmlElement! ;
374+ html.Element overlay = _overlays[viewId]! .htmlElement;
362375 platformViewRoot.remove ();
363376 skiaSceneHost! .append (platformViewRoot);
364377 overlay.remove ();
365378 skiaSceneHost! .append (overlay);
366379 _activeCompositionOrder.add (viewId);
367380 }
368381 _compositionOrder.clear ();
382+
383+ for (final int unusedViewId in unusedViews) {
384+ _releaseOverlay (unusedViewId);
385+ }
369386 }
370387
371388 void disposeViews () {
372389 if (_viewsToDispose.isEmpty) {
373390 return ;
374391 }
375392
376- for (int ? viewId in _viewsToDispose) {
393+ for (final int viewId in _viewsToDispose) {
377394 final html.Element rootView = _rootViews[viewId]! ;
378395 rootView.remove ();
379396 _views.remove (viewId);
380397 _rootViews.remove (viewId);
381- if (_overlays[viewId] != null ) {
382- final Overlay overlay = _overlays[viewId]! ;
383- overlay.surface.htmlElement? .remove ();
384- overlay.surface.htmlElement = null ;
385- overlay.skSurface? .dispose ();
386- }
387- _overlays.remove (viewId);
398+ _releaseOverlay (viewId);
388399 _currentCompositionParams.remove (viewId);
389400 _clipCount.remove (viewId);
390401 _viewsToRecomposite.remove (viewId);
391402 }
392403 _viewsToDispose.clear ();
393404 }
394405
395- void ensureOverlayInitialized (int viewId) {
396- Overlay ? overlay = _overlays[viewId];
406+ void _releaseOverlay (int viewId) {
407+ if (_overlays[viewId] != null ) {
408+ OverlayCache .instance.releaseOverlay (_overlays[viewId]! );
409+ _overlays.remove (viewId);
410+ }
411+ }
412+
413+ void _ensureOverlayInitialized (int viewId) {
414+ // If there's an active overlay for the view ID, continue using it.
415+ Surface ? overlay = _overlays[viewId];
397416 if (overlay != null ) {
398417 return ;
399418 }
400- Surface surface = Surface (this );
401- CkSurface ? skSurface = surface.acquireRenderSurface (_frameSize);
402- _overlays[viewId] = Overlay (surface, skSurface);
419+
420+ // Try reusing a cached overlay created for another platform view.
421+ overlay = OverlayCache .instance.reserveOverlay ();
422+
423+ // If nothing to reuse, create a new overlay.
424+ if (overlay == null ) {
425+ overlay = Surface (this );
426+ }
427+
428+ _overlays[viewId] = overlay;
429+ }
430+ }
431+
432+ /// Caches surfaces used to overlay platform views.
433+ class OverlayCache {
434+ static const int kDefaultCacheSize = 5 ;
435+
436+ /// The cache singleton.
437+ static final OverlayCache instance = OverlayCache (kDefaultCacheSize);
438+
439+ OverlayCache (this .maximumSize);
440+
441+ /// The cache will not grow beyond this size.
442+ final int maximumSize;
443+
444+ /// Cached surfaces, available for reuse.
445+ final List <Surface > _cache = < Surface > [];
446+
447+ /// Returns the list of cached surfaces.
448+ ///
449+ /// Useful in tests.
450+ List <Surface > get debugCachedSurfaces => _cache;
451+
452+ /// Reserves an overlay from the cache, if available.
453+ ///
454+ /// Returns null if the cache is empty.
455+ Surface ? reserveOverlay () {
456+ if (_cache.isEmpty) {
457+ return null ;
458+ }
459+ return _cache.removeLast ();
460+ }
461+
462+ /// Returns an overlay back to the cache.
463+ ///
464+ /// If the cache is full, the overlay is deleted.
465+ void releaseOverlay (Surface overlay) {
466+ overlay.htmlElement.remove ();
467+ if (_cache.length < maximumSize) {
468+ _cache.add (overlay);
469+ } else {
470+ overlay.dispose ();
471+ }
472+ }
473+
474+ int get debugLength => _cache.length;
475+
476+ void debugClear () {
477+ for (final Surface overlay in _cache) {
478+ overlay.dispose ();
479+ }
403480 }
404481}
405482
@@ -547,11 +624,3 @@ class MutatorsStack extends Iterable<Mutator> {
547624 @override
548625 Iterator <Mutator > get iterator => _mutators.reversed.iterator;
549626}
550-
551- /// Represents a surface overlaying a platform view.
552- class Overlay {
553- final Surface surface;
554- final CkSurface ? skSurface;
555-
556- Overlay (this .surface, this .skSurface);
557- }
0 commit comments