From db7c653e68179cd586b0c26f635cfea10ccdb683 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Fri, 5 Jan 2024 15:35:57 -0800 Subject: [PATCH 01/18] Refactor rasterization to support single GL context and multi context mode --- lib/web_ui/lib/src/engine.dart | 4 +- .../src/engine/canvaskit/embedded_views.dart | 27 +- .../canvaskit/multi_surface_rasterizer.dart | 62 +++ .../offscreen_canvas_rasterizer.dart | 61 +++ ...ctory.dart => overlay_canvas_factory.dart} | 30 +- .../lib/src/engine/canvaskit/rasterizer.dart | 103 ++-- .../src/engine/canvaskit/render_canvas.dart | 10 +- .../lib/src/engine/canvaskit/renderer.dart | 464 +++++++++--------- .../lib/src/engine/canvaskit/surface.dart | 42 +- 9 files changed, 486 insertions(+), 317 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart create mode 100644 lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart rename lib/web_ui/lib/src/engine/canvaskit/{render_canvas_factory.dart => overlay_canvas_factory.dart} (82%) diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 593bed532c250..b65ee50964a38 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -34,8 +34,11 @@ export 'engine/canvaskit/layer.dart'; export 'engine/canvaskit/layer_scene_builder.dart'; export 'engine/canvaskit/layer_tree.dart'; export 'engine/canvaskit/mask_filter.dart'; +export 'engine/canvaskit/multi_surface_rasterizer.dart'; export 'engine/canvaskit/n_way_canvas.dart'; export 'engine/canvaskit/native_memory.dart'; +export 'engine/canvaskit/offscreen_canvas_rasterizer.dart'; +export 'engine/canvaskit/overlay_canvas_factory.dart'; export 'engine/canvaskit/painting.dart'; export 'engine/canvaskit/path.dart'; export 'engine/canvaskit/path_metrics.dart'; @@ -44,7 +47,6 @@ export 'engine/canvaskit/picture_recorder.dart'; export 'engine/canvaskit/raster_cache.dart'; export 'engine/canvaskit/rasterizer.dart'; export 'engine/canvaskit/render_canvas.dart'; -export 'engine/canvaskit/render_canvas_factory.dart'; export 'engine/canvaskit/renderer.dart'; export 'engine/canvaskit/shader.dart'; export 'engine/canvaskit/surface.dart'; diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 43f847caa9474..092adbf1645c1 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -18,16 +18,13 @@ import 'path.dart'; import 'picture.dart'; import 'picture_recorder.dart'; import 'rasterizer.dart'; -import 'render_canvas.dart'; -import 'render_canvas_factory.dart'; /// This composites HTML views into the [ui.Scene]. class HtmlViewEmbedder { - HtmlViewEmbedder(this.sceneHost, this.rasterizer, this.renderCanvasFactory); + HtmlViewEmbedder(this.sceneHost, this.rasterizer); final DomElement sceneHost; - final Rasterizer rasterizer; - final RenderCanvasFactory renderCanvasFactory; + final ViewRasterizer rasterizer; /// The context for the current frame. EmbedderFrameContext _context = EmbedderFrameContext(); @@ -53,7 +50,7 @@ class HtmlViewEmbedder { static const int maximumOverlays = 7; /// Canvases used to draw on top of platform views, keyed by platform view ID. - final Map _overlays = {}; + final Map _overlays = {}; /// The views that need to be recomposited into the scene on the next frame. final Set _viewsToRecomposite = {}; @@ -381,7 +378,7 @@ class HtmlViewEmbedder { int pictureRecorderIndex = 0; for (final OverlayGroup overlayGroup in _activeOverlayGroups) { - final RenderCanvas overlay = _overlays[overlayGroup.last]!; + final OverlayCanvas overlay = _overlays[overlayGroup.last]!; final List pictures = []; for (int i = 0; i < overlayGroup.visibleCount; i++) { pictures.add( @@ -441,7 +438,7 @@ class HtmlViewEmbedder { if (diffResult.addToBeginning) { final DomElement platformViewRoot = _viewClipChains[viewId]!.root; sceneHost.insertBefore(platformViewRoot, elementToInsertBefore); - final RenderCanvas? overlay = _overlays[viewId]; + final OverlayCanvas? overlay = _overlays[viewId]; if (overlay != null) { sceneHost.insertBefore( overlay.htmlElement, elementToInsertBefore); @@ -449,7 +446,7 @@ class HtmlViewEmbedder { } else { final DomElement platformViewRoot = _viewClipChains[viewId]!.root; sceneHost.append(platformViewRoot); - final RenderCanvas? overlay = _overlays[viewId]; + final OverlayCanvas? overlay = _overlays[viewId]; if (overlay != null) { sceneHost.append(overlay.htmlElement); } @@ -474,7 +471,7 @@ class HtmlViewEmbedder { } } } else { - renderCanvasFactory.removeSurfacesFromDom(); + rasterizer.removeOverlaysFromDom(); for (int i = 0; i < _compositionOrder.length; i++) { final int viewId = _compositionOrder[i]; @@ -492,7 +489,7 @@ class HtmlViewEmbedder { } final DomElement platformViewRoot = _viewClipChains[viewId]!.root; - final RenderCanvas? overlay = _overlays[viewId]; + final OverlayCanvas? overlay = _overlays[viewId]; sceneHost.append(platformViewRoot); if (overlay != null) { sceneHost.append(overlay.htmlElement); @@ -528,8 +525,8 @@ class HtmlViewEmbedder { void _releaseOverlay(int viewId) { if (_overlays[viewId] != null) { - final RenderCanvas overlay = _overlays[viewId]!; - renderCanvasFactory.releaseCanvas(overlay); + final OverlayCanvas overlay = _overlays[viewId]!; + rasterizer.releaseOverlay(overlay); _overlays.remove(viewId); } } @@ -569,7 +566,7 @@ class HtmlViewEmbedder { if (diffResult == null) { // Everything is going to be explicitly recomposited anyway. Release all // the surfaces and assign an overlay to all the surfaces needing one. - renderCanvasFactory.releaseCanvases(); + rasterizer.releaseOverlays(); _overlays.clear(); viewsNeedingOverlays.forEach(_initializeOverlay); } else { @@ -639,7 +636,7 @@ class HtmlViewEmbedder { assert(!_overlays.containsKey(viewId)); // Try reusing a cached overlay created for another platform view. - final RenderCanvas overlay = renderCanvasFactory.getCanvas(); + final OverlayCanvas overlay = rasterizer.getOverlay(); _overlays[viewId] = overlay; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart new file mode 100644 index 0000000000000..07d21744e8b41 --- /dev/null +++ b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart @@ -0,0 +1,62 @@ +// 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 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +/// A Rasterizer which uses one or many on-screen WebGL contexts to display the +/// scene. This way of rendering is prone to bugs because there is a limit to +/// how many WebGL contexts can be live at one time as well as bugs in sharing +/// GL resources between the contexts. However, using [createImageBitmap] is +/// currently very slow on Firefox and Safari browsers, so directly rendering +/// to several +class MultiSurfaceRasterizer extends Rasterizer { + @override + MultiSurfaceViewRasterizer createViewRasterizer(EngineFlutterView view) { + return MultiSurfaceViewRasterizer(view, this); + } + + @override + void dispose() { + // TODO(harryterkelsen): implement dispose + } + + @override + void setResourceCacheMaxBytes(int bytes) { + // TODO(harryterkelsen): implement setResourceCacheMaxBytes + } +} + +class MultiSurfaceViewRasterizer extends ViewRasterizer { + MultiSurfaceViewRasterizer(super.view, this.rasterizer); + + final MultiSurfaceRasterizer rasterizer; + + @override + final OverlayCanvasFactory overlayFactory = + OverlayCanvasFactory( + createCanvas: () => Surface(useOffscreenCanvas: false)); + + @override + void dispose() { + // TODO: implement dispose + } + + @override + void prepareToDraw() { + overlayFactory.baseCanvas.ensureSurface(currentFrameSize); + } + + @override + Future rasterizeToCanvas( + OverlayCanvas canvas, List pictures) { + final Surface surface = canvas as Surface; + surface.ensureSurface(currentFrameSize); + final CkCanvas skCanvas = surface.getCanvas(); + skCanvas.clear(const ui.Color(0x00000000)); + pictures.forEach(skCanvas.drawPicture); + surface.flush(); + return Future.value(); + } +} diff --git a/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart new file mode 100644 index 0000000000000..0e61d88717202 --- /dev/null +++ b/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart @@ -0,0 +1,61 @@ +// 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 'package:ui/src/engine.dart'; + +/// A [Rasterizer] that uses a single GL context in an OffscreenCanvas to do +/// all the rendering. It transers bitmaps created in the OffscreenCanvas to +/// one or many on-screen elements to actually display the scene. +class OffscreenCanvasRasterizer extends Rasterizer { + /// This is an SkSurface backed by an OffScreenCanvas. This single Surface is + /// used to render to many RenderCanvases to produce the rendered scene. + final Surface offscreenSurface = Surface(); + + @override + OffscreenCanvasViewRasterizer createViewRasterizer(EngineFlutterView view) { + return OffscreenCanvasViewRasterizer(view, this); + } + + @override + void setResourceCacheMaxBytes(int bytes) { + // TODO(harryterkelsen): Implement. + } + + @override + void dispose() { + // TODO(harryterkelsen): Implement. + } +} + +class OffscreenCanvasViewRasterizer extends ViewRasterizer { + OffscreenCanvasViewRasterizer(super.view, this.rasterizer); + + final OffscreenCanvasRasterizer rasterizer; + + @override + final OverlayCanvasFactory overlayFactory = + OverlayCanvasFactory(createCanvas: () => RenderCanvas()); + + /// Render the given [pictures] so it is displayed by the given [canvas]. + @override + Future rasterizeToCanvas( + OverlayCanvas canvas, List pictures) async { + await rasterizer.offscreenSurface.rasterizeToCanvas( + currentFrameSize, + canvas as RenderCanvas, + pictures, + ); + } + + @override + void dispose() { + viewEmbedder.dispose(); + overlayFactory.dispose(); + } + + @override + void prepareToDraw() { + rasterizer.offscreenSurface.ensureSurface(currentFrameSize); + } +} diff --git a/lib/web_ui/lib/src/engine/canvaskit/render_canvas_factory.dart b/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart similarity index 82% rename from lib/web_ui/lib/src/engine/canvaskit/render_canvas_factory.dart rename to lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart index e814c9e6fe7e9..944138628dcb2 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/render_canvas_factory.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart @@ -6,25 +6,29 @@ import 'package:meta/meta.dart'; import '../../engine.dart'; /// Caches canvases used to overlay platform views. -class RenderCanvasFactory { - RenderCanvasFactory() { +class OverlayCanvasFactory { + OverlayCanvasFactory({required this.createCanvas}) { assert(() { registerHotRestartListener(dispose); return true; }()); } + /// A function which is passed in as a constructor parameter which is used to + /// create new overlay canvases. + final T Function() createCanvas; + /// The base canvas to paint on. This is the default canvas which will be /// painted to. If there are no platform views, then this canvas will render /// the entire scene. - final RenderCanvas baseCanvas = RenderCanvas(); + late final T baseCanvas = createCanvas(); /// Canvases created by this factory which are currently in use. - final List _liveCanvases = []; + final List _liveCanvases = []; /// Canvases created by this factory which are no longer in use. These can be /// reused. - final List _cache = []; + final List _cache = []; /// The number of canvases which have been created by this factory. int get _canvasCount => _liveCanvases.length + _cache.length + 1; @@ -40,13 +44,13 @@ class RenderCanvasFactory { /// Gets an overlay canvas from the cache or creates a new one if there are /// none in the cache. - RenderCanvas getCanvas() { + T getCanvas() { if (_cache.isNotEmpty) { - final RenderCanvas canvas = _cache.removeLast(); + final T canvas = _cache.removeLast(); _liveCanvases.add(canvas); return canvas; } else { - final RenderCanvas canvas = RenderCanvas(); + final T canvas = createCanvas(); _liveCanvases.add(canvas); return canvas; } @@ -72,12 +76,12 @@ class RenderCanvasFactory { } // Removes [canvas] from the DOM. - void _removeFromDom(RenderCanvas canvas) { + void _removeFromDom(T canvas) { canvas.htmlElement.remove(); } /// Signals that a canvas is no longer being used. It can be reused. - void releaseCanvas(RenderCanvas canvas) { + void releaseCanvas(T canvas) { assert(canvas != baseCanvas, 'Attempting to release the base canvas'); assert( _liveCanvases.contains(canvas), @@ -94,7 +98,7 @@ class RenderCanvasFactory { /// /// If a canvas is not live, then it must be in the cache and ready to be /// reused. - bool isLive(RenderCanvas canvas) { + bool isLive(T canvas) { if (canvas == baseCanvas || _liveCanvases.contains(canvas)) { return true; } @@ -104,10 +108,10 @@ class RenderCanvasFactory { /// Dispose all canvases created by this factory. void dispose() { - for (final RenderCanvas canvas in _cache) { + for (final T canvas in _cache) { canvas.dispose(); } - for (final RenderCanvas canvas in _liveCanvases) { + for (final T canvas in _liveCanvases) { canvas.dispose(); } baseCanvas.dispose(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart index 4e987d3a517f8..c7e61e90638d4 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart @@ -5,31 +5,40 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -/// A class that can rasterize [LayerTree]s into a given `sceneHost` element. -class Rasterizer { - Rasterizer(this.view); +abstract class Rasterizer { + /// Creates a [ViewRasterizer] for a given [view]. + ViewRasterizer createViewRasterizer(EngineFlutterView view); + /// Sets the maximum size of the resource cache to [bytes]. + void setResourceCacheMaxBytes(int bytes); + + /// Disposes this rasterizer and all [ViewRasterizer]s that it created. + void dispose(); +} + +abstract class ViewRasterizer { + ViewRasterizer(this.view); + + /// The view this rasterizer renders into. final EngineFlutterView view; - DomElement get sceneHost => view.dom.sceneHost; + + /// The size of the current frame being rasterized. + ui.Size currentFrameSize = ui.Size.zero; + + /// The context which is persisted between frames. final CompositorContext context = CompositorContext(); - final RenderCanvasFactory renderCanvasFactory = RenderCanvasFactory(); - late final HtmlViewEmbedder viewEmbedder = - HtmlViewEmbedder(sceneHost, this, renderCanvasFactory); - ui.Size _currentFrameSize = ui.Size.zero; + /// The platform view embedder. + late final HtmlViewEmbedder viewEmbedder = HtmlViewEmbedder(sceneHost, this); - /// Render the given [pictures] so it is displayed by the given [canvas]. - Future rasterizeToCanvas( - RenderCanvas canvas, List pictures) async { - await CanvasKitRenderer.instance.offscreenSurface.rasterizeToCanvas( - _currentFrameSize, - canvas, - pictures, - ); - } + /// A factory for creating overlays. + OverlayCanvasFactory get overlayFactory; - /// Creates a new frame from this rasterizer's surface, draws the given - /// [LayerTree] into it, and then submits the frame. + /// The scene host which this rasterizer should raster into. + DomElement get sceneHost => view.dom.sceneHost; + + /// Draws the [layerTree] to the screen for the view associated with this + /// rasterizer. Future draw(LayerTree layerTree) async { final ui.Size frameSize = view.physicalSize; if (frameSize.isEmpty) { @@ -37,27 +46,61 @@ class Rasterizer { return; } - _currentFrameSize = frameSize; - CanvasKitRenderer.instance.offscreenSurface.acquireFrame(_currentFrameSize); - viewEmbedder.frameSize = _currentFrameSize; + currentFrameSize = frameSize; + prepareToDraw(); + viewEmbedder.frameSize = currentFrameSize; final CkPictureRecorder pictureRecorder = CkPictureRecorder(); - pictureRecorder.beginRecording(ui.Offset.zero & _currentFrameSize); + pictureRecorder.beginRecording(ui.Offset.zero & currentFrameSize); pictureRecorder.recordingCanvas!.clear(const ui.Color(0x00000000)); final Frame compositorFrame = context.acquireFrame(pictureRecorder.recordingCanvas!, viewEmbedder); compositorFrame.raster(layerTree, ignoreRasterCache: true); - sceneHost.prepend(renderCanvasFactory.baseCanvas.htmlElement); - await rasterizeToCanvas(renderCanvasFactory.baseCanvas, - [pictureRecorder.endRecording()]); + sceneHost.prepend(overlayFactory.baseCanvas.htmlElement); + await rasterizeToCanvas( + overlayFactory.baseCanvas, [pictureRecorder.endRecording()]); await viewEmbedder.submitFrame(); } - /// Disposes of this rasterizer. - void dispose() { - viewEmbedder.dispose(); - renderCanvasFactory.dispose(); + /// Do some initialization to prepare to draw a frame. + /// + /// For example, in the [OffscreenCanvasRasterizer], this ensures the backing + /// [OffscreenCanvas] is the correct size to draw the frame. + void prepareToDraw(); + + /// Rasterize the [pictures] to the given [canvas]. + Future rasterizeToCanvas( + OverlayCanvas canvas, List pictures); + + /// Get a [OverlayCanvas] to use as an overlay. + OverlayCanvas getOverlay() { + return overlayFactory.getCanvas(); + } + + /// Release the given [overlay] so it may be reused. + void releaseOverlay(OverlayCanvas overlay) { + overlayFactory.releaseCanvas(overlay); + } + + /// Release all overlays. + void releaseOverlays() { + overlayFactory.releaseCanvases(); } + + /// Remove all overlays that have been created from the DOM. + void removeOverlaysFromDom() { + overlayFactory.removeSurfacesFromDom(); + } + + /// Disposes this rasterizer. + void dispose(); +} + +abstract class OverlayCanvas { + DomElement get htmlElement; + + /// Disposes this overlay. + void dispose(); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart index 9308c11ac6e2b..d777b59e2c2ce 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart @@ -8,6 +8,7 @@ import 'package:ui/ui.dart' as ui; import '../display.dart'; import '../dom.dart'; +import 'rasterizer.dart'; /// A visible (on-screen) canvas that can display bitmaps produced by CanvasKit /// in the (off-screen) SkSurface which is backed by an OffscreenCanvas. @@ -26,7 +27,7 @@ import '../dom.dart'; /// on the maximum amount of WebGL contexts which can be live at once. Using /// a single OffscreenCanvas and multiple RenderCanvases allows us to only /// create a single WebGL context. -class RenderCanvas { +class RenderCanvas extends OverlayCanvas { RenderCanvas() { canvasElement.setAttribute('aria-hidden', 'true'); canvasElement.style.position = 'absolute'; @@ -43,6 +44,7 @@ class RenderCanvas { /// Conversely, the canvas that lives inside this element can be swapped, for /// example, when the screen size changes, or when the WebGL context is lost /// due to the browser tab becoming dormant. + @override final DomElement htmlElement = createDomElement('flt-canvas-container'); /// The underlying `` element used to display the pixels. @@ -68,7 +70,8 @@ class RenderCanvas { /// match the size of the window precisely we use the most precise floating /// point value we can get. void _updateLogicalHtmlCanvasSize() { - final double devicePixelRatio = EngineFlutterDisplay.instance.devicePixelRatio; + final double devicePixelRatio = + EngineFlutterDisplay.instance.devicePixelRatio; final double logicalWidth = _pixelWidth / devicePixelRatio; final double logicalHeight = _pixelHeight / devicePixelRatio; final DomCSSStyleDeclaration style = canvasElement.style; @@ -113,7 +116,8 @@ class RenderCanvas { size.height.ceil() == _pixelHeight) { // The existing canvas doesn't need to be resized (unless the device pixel // ratio changed). - if (EngineFlutterDisplay.instance.devicePixelRatio != _currentDevicePixelRatio) { + if (EngineFlutterDisplay.instance.devicePixelRatio != + _currentDevicePixelRatio) { _updateLogicalHtmlCanvasSize(); } return; diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index b3a8b3baaec16..91a803e4c341f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -44,12 +44,17 @@ class CanvasKitRenderer implements Renderer { DomElement? _sceneHost; DomElement? get sceneHost => _sceneHost; - /// This is an SkSurface backed by an OffScreenCanvas. This single Surface is - /// used to render to many RenderCanvases to produce the rendered scene. - final Surface offscreenSurface = Surface(); + final Rasterizer _rasterizer = _createRasterizer(); + + static Rasterizer _createRasterizer() { + if (isSafari || isFirefox) { + return MultiSurfaceRasterizer(); + } + return OffscreenCanvasRasterizer(); + } set resourceCacheMaxBytes(int bytes) => - offscreenSurface.setSkiaResourceCacheMaxBytes(bytes); + _rasterizer.setResourceCacheMaxBytes(bytes); /// A surface used specifically for `Picture.toImage` when software rendering /// is supported. @@ -106,12 +111,11 @@ class CanvasKitRenderer implements Renderer { List? textureCoordinates, List? colors, List? indices, - }) => CkVertices( - mode, - positions, - textureCoordinates: textureCoordinates, - colors: colors, - indices: indices); + }) => + CkVertices(mode, positions, + textureCoordinates: textureCoordinates, + colors: colors, + indices: indices); @override ui.Vertices createVerticesRaw( @@ -120,26 +124,23 @@ class CanvasKitRenderer implements Renderer { Float32List? textureCoordinates, Int32List? colors, Uint16List? indices, - }) => CkVertices.raw( - mode, - positions, - textureCoordinates: textureCoordinates, - colors: colors, - indices: indices); + }) => + CkVertices.raw(mode, positions, + textureCoordinates: textureCoordinates, + colors: colors, + indices: indices); @override ui.Canvas createCanvas(ui.PictureRecorder recorder, [ui.Rect? cullRect]) => - CanvasKitCanvas(recorder, cullRect); + CanvasKitCanvas(recorder, cullRect); @override ui.Gradient createLinearGradient( - ui.Offset from, - ui.Offset to, - List colors, [ - List? colorStops, - ui.TileMode tileMode = ui.TileMode.clamp, - Float32List? matrix4 - ]) => CkGradientLinear(from, to, colors, colorStops, tileMode, matrix4); + ui.Offset from, ui.Offset to, List colors, + [List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix4]) => + CkGradientLinear(from, to, colors, colorStops, tileMode, matrix4); @override ui.Gradient createRadialGradient( @@ -149,38 +150,27 @@ class CanvasKitRenderer implements Renderer { List? colorStops, ui.TileMode tileMode = ui.TileMode.clamp, Float32List? matrix4, - ]) => CkGradientRadial(center, radius, colors, colorStops, tileMode, matrix4); + ]) => + CkGradientRadial(center, radius, colors, colorStops, tileMode, matrix4); @override - ui.Gradient createConicalGradient( - ui.Offset focal, - double focalRadius, - ui.Offset center, - double radius, - List colors, - [List? colorStops, - ui.TileMode tileMode = ui.TileMode.clamp, - Float32List? matrix] - ) => CkGradientConical( - focal, - focalRadius, - center, - radius, - colors, - colorStops, - tileMode, - matrix); - - @override - ui.Gradient createSweepGradient( - ui.Offset center, - List colors, [ - List? colorStops, - ui.TileMode tileMode = ui.TileMode.clamp, - double startAngle = 0.0, - double endAngle = math.pi * 2, - Float32List? matrix4 - ]) => CkGradientSweep(center, colors, colorStops, tileMode, startAngle, endAngle, matrix4); + ui.Gradient createConicalGradient(ui.Offset focal, double focalRadius, + ui.Offset center, double radius, List colors, + [List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix]) => + CkGradientConical(focal, focalRadius, center, radius, colors, colorStops, + tileMode, matrix); + + @override + ui.Gradient createSweepGradient(ui.Offset center, List colors, + [List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + double startAngle = 0.0, + double endAngle = math.pi * 2, + Float32List? matrix4]) => + CkGradientSweep( + center, colors, colorStops, tileMode, startAngle, endAngle, matrix4); @override ui.PictureRecorder createPictureRecorder() => CkPictureRecorder(); @@ -189,29 +179,32 @@ class CanvasKitRenderer implements Renderer { ui.SceneBuilder createSceneBuilder() => LayerSceneBuilder(); @override - ui.ImageFilter createBlurImageFilter({ - double sigmaX = 0.0, - double sigmaY = 0.0, - ui.TileMode tileMode = ui.TileMode.clamp - }) => CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); + ui.ImageFilter createBlurImageFilter( + {double sigmaX = 0.0, + double sigmaY = 0.0, + ui.TileMode tileMode = ui.TileMode.clamp}) => + CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); @override - ui.ImageFilter createDilateImageFilter({double radiusX = 0.0, double radiusY = 0.0}) { + ui.ImageFilter createDilateImageFilter( + {double radiusX = 0.0, double radiusY = 0.0}) { // TODO(fzyzcjy): implement dilate. https://github.com/flutter/flutter/issues/101085 - throw UnimplementedError('ImageFilter.dilate not implemented for CanvasKit.'); + throw UnimplementedError( + 'ImageFilter.dilate not implemented for CanvasKit.'); } @override - ui.ImageFilter createErodeImageFilter({double radiusX = 0.0, double radiusY = 0.0}) { + ui.ImageFilter createErodeImageFilter( + {double radiusX = 0.0, double radiusY = 0.0}) { // TODO(fzyzcjy): implement erode. https://github.com/flutter/flutter/issues/101085 - throw UnimplementedError('ImageFilter.erode not implemented for CanvasKit.'); + throw UnimplementedError( + 'ImageFilter.erode not implemented for CanvasKit.'); } @override - ui.ImageFilter createMatrixImageFilter( - Float64List matrix4, { - ui.FilterQuality filterQuality = ui.FilterQuality.low - }) => CkImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality); + ui.ImageFilter createMatrixImageFilter(Float64List matrix4, + {ui.FilterQuality filterQuality = ui.FilterQuality.low}) => + CkImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality); @override ui.ImageFilter composeImageFilters( @@ -225,33 +218,25 @@ class CanvasKitRenderer implements Renderer { inner = CkColorFilterImageFilter(colorFilter: colorFilter); } return CkImageFilter.compose( - outer: outer as CkImageFilter, inner: inner as CkImageFilter); + outer: outer as CkImageFilter, inner: inner as CkImageFilter); } @override - Future instantiateImageCodec( - Uint8List list, { - int? targetWidth, - int? targetHeight, - bool allowUpscaling = true - }) async => skiaInstantiateImageCodec( - list, - targetWidth, - targetHeight - ); + Future instantiateImageCodec(Uint8List list, + {int? targetWidth, + int? targetHeight, + bool allowUpscaling = true}) async => + skiaInstantiateImageCodec(list, targetWidth, targetHeight); @override - Future instantiateImageCodecFromUrl( - Uri uri, { - ui_web.ImageCodecChunkCallback? chunkCallback - }) => skiaInstantiateWebImageCodec(uri.toString(), chunkCallback); + Future instantiateImageCodecFromUrl(Uri uri, + {ui_web.ImageCodecChunkCallback? chunkCallback}) => + skiaInstantiateWebImageCodec(uri.toString(), chunkCallback); @override ui.Image createImageFromImageBitmap(DomImageBitmap imageBitmap) { - final SkImage? skImage = canvasKit.MakeLazyImageFromImageBitmap( - imageBitmap, - true - ); + final SkImage? skImage = + canvasKit.MakeLazyImageFromImageBitmap(imageBitmap, true); if (skImage == null) { throw Exception('Failed to convert image bitmap to an SkImage.'); } @@ -259,36 +244,26 @@ class CanvasKitRenderer implements Renderer { } @override - void decodeImageFromPixels( - Uint8List pixels, - int width, - int height, - ui.PixelFormat format, - ui.ImageDecoderCallback callback, { - int? rowBytes, - int? targetWidth, - int? targetHeight, - bool allowUpscaling = true - }) => skiaDecodeImageFromPixels( - pixels, - width, - height, - format, - callback, - rowBytes: rowBytes, - targetWidth: targetWidth, - targetHeight: targetHeight, - allowUpscaling: allowUpscaling - ); + void decodeImageFromPixels(Uint8List pixels, int width, int height, + ui.PixelFormat format, ui.ImageDecoderCallback callback, + {int? rowBytes, + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true}) => + skiaDecodeImageFromPixels(pixels, width, height, format, callback, + rowBytes: rowBytes, + targetWidth: targetWidth, + targetHeight: targetHeight, + allowUpscaling: allowUpscaling); @override ui.ImageShader createImageShader( - ui.Image image, - ui.TileMode tmx, - ui.TileMode tmy, - Float64List matrix4, - ui.FilterQuality? filterQuality - ) => CkImageShader(image, tmx, tmy, matrix4, filterQuality); + ui.Image image, + ui.TileMode tmx, + ui.TileMode tmy, + Float64List matrix4, + ui.FilterQuality? filterQuality) => + CkImageShader(image, tmx, tmy, matrix4, filterQuality); @override ui.Path createPath() => CkPath(); @@ -298,111 +273,111 @@ class CanvasKitRenderer implements Renderer { @override ui.Path combinePaths(ui.PathOperation op, ui.Path path1, ui.Path path2) => - CkPath.combine(op, path1, path2); - - @override - ui.TextStyle createTextStyle({ - ui.Color? color, - ui.TextDecoration? decoration, - ui.Color? decorationColor, - ui.TextDecorationStyle? decorationStyle, - double? decorationThickness, - ui.FontWeight? fontWeight, - ui.FontStyle? fontStyle, - ui.TextBaseline? textBaseline, - String? fontFamily, - List? fontFamilyFallback, - double? fontSize, - double? letterSpacing, - double? wordSpacing, - double? height, - ui.TextLeadingDistribution? leadingDistribution, - ui.Locale? locale, - ui.Paint? background, - ui.Paint? foreground, - List? shadows, - List? fontFeatures, - List? fontVariations - }) => CkTextStyle( - color: color, - decoration: decoration, - decorationColor: decorationColor, - decorationStyle: decorationStyle, - decorationThickness: decorationThickness, - fontWeight: fontWeight, - fontStyle: fontStyle, - textBaseline: textBaseline, - fontFamily: fontFamily, - fontFamilyFallback: fontFamilyFallback, - fontSize: fontSize, - letterSpacing: letterSpacing, - wordSpacing: wordSpacing, - height: height, - leadingDistribution: leadingDistribution, - locale: locale, - background: background as CkPaint?, - foreground: foreground as CkPaint?, - shadows: shadows, - fontFeatures: fontFeatures, - fontVariations: fontVariations, - ); - - @override - ui.ParagraphStyle createParagraphStyle({ - ui.TextAlign? textAlign, - ui.TextDirection? textDirection, - int? maxLines, - String? fontFamily, - double? fontSize, - double? height, - ui.TextHeightBehavior? textHeightBehavior, - ui.FontWeight? fontWeight, - ui.FontStyle? fontStyle, - ui.StrutStyle? strutStyle, - String? ellipsis, - ui.Locale? locale - }) => CkParagraphStyle( - textAlign: textAlign, - textDirection: textDirection, - maxLines: maxLines, - fontFamily: fontFamily, - fontSize: fontSize, - height: height, - textHeightBehavior: textHeightBehavior, - fontWeight: fontWeight, - fontStyle: fontStyle, - strutStyle: strutStyle, - ellipsis: ellipsis, - locale: locale, - applyRoundingHack: !ui.ParagraphBuilder.shouldDisableRoundingHack, - ); - - @override - ui.StrutStyle createStrutStyle({ - String? fontFamily, - List? fontFamilyFallback, - double? fontSize, - double? height, - ui.TextLeadingDistribution? leadingDistribution, - double? leading, - ui.FontWeight? fontWeight, - ui.FontStyle? fontStyle, - bool? forceStrutHeight - }) => CkStrutStyle( - fontFamily: fontFamily, - fontFamilyFallback: fontFamilyFallback, - fontSize: fontSize, - height: height, - leadingDistribution: leadingDistribution, - leading: leading, - fontWeight: fontWeight, - fontStyle: fontStyle, - forceStrutHeight: forceStrutHeight, - ); + CkPath.combine(op, path1, path2); + + @override + ui.TextStyle createTextStyle( + {ui.Color? color, + ui.TextDecoration? decoration, + ui.Color? decorationColor, + ui.TextDecorationStyle? decorationStyle, + double? decorationThickness, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.TextBaseline? textBaseline, + String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? letterSpacing, + double? wordSpacing, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + ui.Locale? locale, + ui.Paint? background, + ui.Paint? foreground, + List? shadows, + List? fontFeatures, + List? fontVariations}) => + CkTextStyle( + color: color, + decoration: decoration, + decorationColor: decorationColor, + decorationStyle: decorationStyle, + decorationThickness: decorationThickness, + fontWeight: fontWeight, + fontStyle: fontStyle, + textBaseline: textBaseline, + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + letterSpacing: letterSpacing, + wordSpacing: wordSpacing, + height: height, + leadingDistribution: leadingDistribution, + locale: locale, + background: background as CkPaint?, + foreground: foreground as CkPaint?, + shadows: shadows, + fontFeatures: fontFeatures, + fontVariations: fontVariations, + ); + + @override + ui.ParagraphStyle createParagraphStyle( + {ui.TextAlign? textAlign, + ui.TextDirection? textDirection, + int? maxLines, + String? fontFamily, + double? fontSize, + double? height, + ui.TextHeightBehavior? textHeightBehavior, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.StrutStyle? strutStyle, + String? ellipsis, + ui.Locale? locale}) => + CkParagraphStyle( + textAlign: textAlign, + textDirection: textDirection, + maxLines: maxLines, + fontFamily: fontFamily, + fontSize: fontSize, + height: height, + textHeightBehavior: textHeightBehavior, + fontWeight: fontWeight, + fontStyle: fontStyle, + strutStyle: strutStyle, + ellipsis: ellipsis, + locale: locale, + applyRoundingHack: !ui.ParagraphBuilder.shouldDisableRoundingHack, + ); + + @override + ui.StrutStyle createStrutStyle( + {String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + double? leading, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + bool? forceStrutHeight}) => + CkStrutStyle( + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + height: height, + leadingDistribution: leadingDistribution, + leading: leading, + fontWeight: fontWeight, + fontStyle: fontStyle, + forceStrutHeight: forceStrutHeight, + ); @override ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) => - CkParagraphBuilder(style); + CkParagraphBuilder(style); @override Future renderScene(ui.Scene scene, ui.FlutterView view) async { @@ -418,19 +393,19 @@ class CanvasKitRenderer implements Renderer { assert(_rasterizers.containsKey(view.viewId), "Unable to render to a view which hasn't been registered"); - final Rasterizer rasterizer = _rasterizers[view.viewId]!; + final ViewRasterizer rasterizer = _rasterizers[view.viewId]!; await rasterizer.draw((scene as LayerScene).layerTree); frameTimingsOnRasterFinish(); } // Map from view id to the associated Rasterizer for that view. - final Map _rasterizers = {}; + final Map _rasterizers = {}; void _onViewCreated(int viewId) { final EngineFlutterView view = EnginePlatformDispatcher.instance.viewManager[viewId]!; - _rasterizers[view.viewId] = Rasterizer(view); + _rasterizers[view.viewId] = _rasterizer.createViewRasterizer(view); } void _onViewDisposed(int viewId) { @@ -438,11 +413,11 @@ class CanvasKitRenderer implements Renderer { if (!_rasterizers.containsKey(viewId)) { return; } - final Rasterizer rasterizer = _rasterizers.remove(viewId)!; + final ViewRasterizer rasterizer = _rasterizers.remove(viewId)!; rasterizer.dispose(); } - Rasterizer? debugGetRasterizerForView(EngineFlutterView view) { + ViewRasterizer? debugGetRasterizerForView(EngineFlutterView view) { return _rasterizers[view.viewId]; } @@ -450,7 +425,7 @@ class CanvasKitRenderer implements Renderer { void dispose() { _onViewCreatedListener?.cancel(); _onViewDisposedListener?.cancel(); - for (final Rasterizer rasterizer in _rasterizers.values) { + for (final ViewRasterizer rasterizer in _rasterizers.values) { rasterizer.dispose(); } _rasterizers.clear(); @@ -461,38 +436,39 @@ class CanvasKitRenderer implements Renderer { _programs.clear(); } - static final Map> _programs = >{}; + static final Map> _programs = + >{}; @override Future createFragmentProgram(String assetKey) { if (_programs.containsKey(assetKey)) { return _programs[assetKey]!; } - return _programs[assetKey] = ui_web.assetManager.load(assetKey).then((ByteData data) { + return _programs[assetKey] = + ui_web.assetManager.load(assetKey).then((ByteData data) { return CkFragmentProgram.fromBytes(assetKey, data.buffer.asUint8List()); }); } @override - ui.LineMetrics createLineMetrics({ - required bool hardBreak, - required double ascent, - required double descent, - required double unscaledAscent, - required double height, - required double width, - required double left, - required double baseline, - required int lineNumber - }) => EngineLineMetrics( - hardBreak: hardBreak, - ascent: ascent, - descent: descent, - unscaledAscent: unscaledAscent, - height: height, - width: width, - left: left, - baseline: baseline, - lineNumber: lineNumber - ); + ui.LineMetrics createLineMetrics( + {required bool hardBreak, + required double ascent, + required double descent, + required double unscaledAscent, + required double height, + required double width, + required double left, + required double baseline, + required int lineNumber}) => + EngineLineMetrics( + hardBreak: hardBreak, + ascent: ascent, + descent: descent, + unscaledAscent: unscaledAscent, + height: height, + width: width, + left: left, + baseline: baseline, + lineNumber: lineNumber); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index d3e1b8f9f150a..24c7c3a8a1409 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -14,6 +14,7 @@ import '../util.dart'; import 'canvas.dart'; import 'canvaskit_api.dart'; import 'picture.dart'; +import 'rasterizer.dart'; import 'render_canvas.dart'; import 'util.dart'; @@ -47,11 +48,16 @@ class SurfaceFrame { /// The underlying representation is a [CkSurface], which can be reused by /// successive frames if they are the same size. Otherwise, a new [CkSurface] is /// created. -class Surface { - Surface(); +class Surface extends OverlayCanvas { + Surface({bool useOffscreenCanvas = true}) + : useOffscreenCanvas = + Surface.offscreenCanvasSupported && useOffscreenCanvas; CkSurface? _surface; + /// Whether or not to use an `OffscreenCanvas` to back this [Surface]. + final bool useOffscreenCanvas; + /// If true, forces a new WebGL context to be created, even if the window /// size is the same. This is used to restore the UI after the browser tab /// goes dormant and loses the GL context. @@ -90,6 +96,11 @@ class Surface { /// supported. DomCanvasElement? _canvasElement; + /// Note, if this getter is called, then this Surface is being used as an + /// overlay and must be backed by an onscreen element. + @override + DomElement get htmlElement => _canvasElement!; + int _pixelWidth = -1; int _pixelHeight = -1; int _sampleCount = -1; @@ -107,16 +118,25 @@ class Surface { } } + /// The CanvasKit canvas associated with this surface. + CkCanvas getCanvas() { + return _surface!.getCanvas(); + } + + void flush() { + _surface!.flush(); + } + Future rasterizeToCanvas( ui.Size frameSize, RenderCanvas canvas, List pictures) async { - final CkCanvas skCanvas = _surface!.getCanvas(); + final CkCanvas skCanvas = getCanvas(); skCanvas.clear(const ui.Color(0x00000000)); pictures.forEach(skCanvas.drawPicture); - _surface!.flush(); + flush(); if (browserSupportsCreateImageBitmap) { JSObject bitmapSource; - if (Surface.offscreenCanvasSupported) { + if (useOffscreenCanvas) { bitmapSource = _offscreenCanvas! as JSObject; } else { bitmapSource = _canvasElement! as JSObject; @@ -132,7 +152,7 @@ class Surface { // If the browser doesn't support `createImageBitmap` (e.g. Safari 14) // then render using `drawImage` instead. DomCanvasImageSource imageSource; - if (Surface.offscreenCanvasSupported) { + if (useOffscreenCanvas) { imageSource = _offscreenCanvas! as DomCanvasImageSource; } else { imageSource = _canvasElement! as DomCanvasImageSource; @@ -210,7 +230,7 @@ class Surface { final ui.Size newSize = size * 1.4; _surface?.dispose(); _surface = null; - if (Surface.offscreenCanvasSupported) { + if (useOffscreenCanvas) { _offscreenCanvas!.width = newSize.width; _offscreenCanvas!.height = newSize.height; } else { @@ -301,7 +321,7 @@ class Surface { _pixelWidth = physicalSize.width.ceil(); _pixelHeight = physicalSize.height.ceil(); DomEventTarget htmlCanvas; - if (Surface.offscreenCanvasSupported) { + if (useOffscreenCanvas) { final DomOffscreenCanvas offscreenCanvas = createDomOffscreenCanvas( _pixelWidth, _pixelHeight, @@ -347,7 +367,7 @@ class Surface { antialias: _kUsingMSAA ? 1 : 0, majorVersion: webGLVersion.toDouble(), ); - if (Surface.offscreenCanvasSupported) { + if (useOffscreenCanvas) { glContext = canvasKit.GetOffscreenWebGLContext( _offscreenCanvas!, options, @@ -379,7 +399,7 @@ class Surface { void _initWebglParams() { WebGLContext gl; - if (Surface.offscreenCanvasSupported) { + if (useOffscreenCanvas) { gl = _offscreenCanvas!.getGlContext(webGLVersion); } else { gl = _canvasElement!.getGlContext(webGLVersion); @@ -422,7 +442,7 @@ class Surface { } SkSurface surface; - if (Surface.offscreenCanvasSupported) { + if (useOffscreenCanvas) { surface = canvasKit.MakeOffscreenSWCanvasSurface(_offscreenCanvas!); } else { surface = canvasKit.MakeSWCanvasSurface(_canvasElement!); From 9620664d5ead92adc46e2fad324acdeac6dd5784 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Fri, 5 Jan 2024 16:39:19 -0800 Subject: [PATCH 02/18] Undo formatting --- .../lib/src/engine/canvaskit/renderer.dart | 439 ++++++++++-------- 1 file changed, 234 insertions(+), 205 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index 91a803e4c341f..f1be2ea5a2a57 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -111,11 +111,12 @@ class CanvasKitRenderer implements Renderer { List? textureCoordinates, List? colors, List? indices, - }) => - CkVertices(mode, positions, - textureCoordinates: textureCoordinates, - colors: colors, - indices: indices); + }) => CkVertices( + mode, + positions, + textureCoordinates: textureCoordinates, + colors: colors, + indices: indices); @override ui.Vertices createVerticesRaw( @@ -124,23 +125,26 @@ class CanvasKitRenderer implements Renderer { Float32List? textureCoordinates, Int32List? colors, Uint16List? indices, - }) => - CkVertices.raw(mode, positions, - textureCoordinates: textureCoordinates, - colors: colors, - indices: indices); + }) => CkVertices.raw( + mode, + positions, + textureCoordinates: textureCoordinates, + colors: colors, + indices: indices); @override ui.Canvas createCanvas(ui.PictureRecorder recorder, [ui.Rect? cullRect]) => - CanvasKitCanvas(recorder, cullRect); + CanvasKitCanvas(recorder, cullRect); @override ui.Gradient createLinearGradient( - ui.Offset from, ui.Offset to, List colors, - [List? colorStops, - ui.TileMode tileMode = ui.TileMode.clamp, - Float32List? matrix4]) => - CkGradientLinear(from, to, colors, colorStops, tileMode, matrix4); + ui.Offset from, + ui.Offset to, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix4 + ]) => CkGradientLinear(from, to, colors, colorStops, tileMode, matrix4); @override ui.Gradient createRadialGradient( @@ -150,27 +154,38 @@ class CanvasKitRenderer implements Renderer { List? colorStops, ui.TileMode tileMode = ui.TileMode.clamp, Float32List? matrix4, - ]) => - CkGradientRadial(center, radius, colors, colorStops, tileMode, matrix4); + ]) => CkGradientRadial(center, radius, colors, colorStops, tileMode, matrix4); @override - ui.Gradient createConicalGradient(ui.Offset focal, double focalRadius, - ui.Offset center, double radius, List colors, - [List? colorStops, - ui.TileMode tileMode = ui.TileMode.clamp, - Float32List? matrix]) => - CkGradientConical(focal, focalRadius, center, radius, colors, colorStops, - tileMode, matrix); - - @override - ui.Gradient createSweepGradient(ui.Offset center, List colors, - [List? colorStops, - ui.TileMode tileMode = ui.TileMode.clamp, - double startAngle = 0.0, - double endAngle = math.pi * 2, - Float32List? matrix4]) => - CkGradientSweep( - center, colors, colorStops, tileMode, startAngle, endAngle, matrix4); + ui.Gradient createConicalGradient( + ui.Offset focal, + double focalRadius, + ui.Offset center, + double radius, + List colors, + [List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix] + ) => CkGradientConical( + focal, + focalRadius, + center, + radius, + colors, + colorStops, + tileMode, + matrix); + + @override + ui.Gradient createSweepGradient( + ui.Offset center, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + double startAngle = 0.0, + double endAngle = math.pi * 2, + Float32List? matrix4 + ]) => CkGradientSweep(center, colors, colorStops, tileMode, startAngle, endAngle, matrix4); @override ui.PictureRecorder createPictureRecorder() => CkPictureRecorder(); @@ -179,32 +194,29 @@ class CanvasKitRenderer implements Renderer { ui.SceneBuilder createSceneBuilder() => LayerSceneBuilder(); @override - ui.ImageFilter createBlurImageFilter( - {double sigmaX = 0.0, - double sigmaY = 0.0, - ui.TileMode tileMode = ui.TileMode.clamp}) => - CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); + ui.ImageFilter createBlurImageFilter({ + double sigmaX = 0.0, + double sigmaY = 0.0, + ui.TileMode tileMode = ui.TileMode.clamp + }) => CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); @override - ui.ImageFilter createDilateImageFilter( - {double radiusX = 0.0, double radiusY = 0.0}) { + ui.ImageFilter createDilateImageFilter({double radiusX = 0.0, double radiusY = 0.0}) { // TODO(fzyzcjy): implement dilate. https://github.com/flutter/flutter/issues/101085 - throw UnimplementedError( - 'ImageFilter.dilate not implemented for CanvasKit.'); + throw UnimplementedError('ImageFilter.dilate not implemented for CanvasKit.'); } @override - ui.ImageFilter createErodeImageFilter( - {double radiusX = 0.0, double radiusY = 0.0}) { + ui.ImageFilter createErodeImageFilter({double radiusX = 0.0, double radiusY = 0.0}) { // TODO(fzyzcjy): implement erode. https://github.com/flutter/flutter/issues/101085 - throw UnimplementedError( - 'ImageFilter.erode not implemented for CanvasKit.'); + throw UnimplementedError('ImageFilter.erode not implemented for CanvasKit.'); } @override - ui.ImageFilter createMatrixImageFilter(Float64List matrix4, - {ui.FilterQuality filterQuality = ui.FilterQuality.low}) => - CkImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality); + ui.ImageFilter createMatrixImageFilter( + Float64List matrix4, { + ui.FilterQuality filterQuality = ui.FilterQuality.low + }) => CkImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality); @override ui.ImageFilter composeImageFilters( @@ -218,25 +230,33 @@ class CanvasKitRenderer implements Renderer { inner = CkColorFilterImageFilter(colorFilter: colorFilter); } return CkImageFilter.compose( - outer: outer as CkImageFilter, inner: inner as CkImageFilter); + outer: outer as CkImageFilter, inner: inner as CkImageFilter); } @override - Future instantiateImageCodec(Uint8List list, - {int? targetWidth, - int? targetHeight, - bool allowUpscaling = true}) async => - skiaInstantiateImageCodec(list, targetWidth, targetHeight); + Future instantiateImageCodec( + Uint8List list, { + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true + }) async => skiaInstantiateImageCodec( + list, + targetWidth, + targetHeight + ); @override - Future instantiateImageCodecFromUrl(Uri uri, - {ui_web.ImageCodecChunkCallback? chunkCallback}) => - skiaInstantiateWebImageCodec(uri.toString(), chunkCallback); + Future instantiateImageCodecFromUrl( + Uri uri, { + ui_web.ImageCodecChunkCallback? chunkCallback + }) => skiaInstantiateWebImageCodec(uri.toString(), chunkCallback); @override ui.Image createImageFromImageBitmap(DomImageBitmap imageBitmap) { - final SkImage? skImage = - canvasKit.MakeLazyImageFromImageBitmap(imageBitmap, true); + final SkImage? skImage = canvasKit.MakeLazyImageFromImageBitmap( + imageBitmap, + true + ); if (skImage == null) { throw Exception('Failed to convert image bitmap to an SkImage.'); } @@ -244,26 +264,36 @@ class CanvasKitRenderer implements Renderer { } @override - void decodeImageFromPixels(Uint8List pixels, int width, int height, - ui.PixelFormat format, ui.ImageDecoderCallback callback, - {int? rowBytes, - int? targetWidth, - int? targetHeight, - bool allowUpscaling = true}) => - skiaDecodeImageFromPixels(pixels, width, height, format, callback, - rowBytes: rowBytes, - targetWidth: targetWidth, - targetHeight: targetHeight, - allowUpscaling: allowUpscaling); + void decodeImageFromPixels( + Uint8List pixels, + int width, + int height, + ui.PixelFormat format, + ui.ImageDecoderCallback callback, { + int? rowBytes, + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true + }) => skiaDecodeImageFromPixels( + pixels, + width, + height, + format, + callback, + rowBytes: rowBytes, + targetWidth: targetWidth, + targetHeight: targetHeight, + allowUpscaling: allowUpscaling + ); @override ui.ImageShader createImageShader( - ui.Image image, - ui.TileMode tmx, - ui.TileMode tmy, - Float64List matrix4, - ui.FilterQuality? filterQuality) => - CkImageShader(image, tmx, tmy, matrix4, filterQuality); + ui.Image image, + ui.TileMode tmx, + ui.TileMode tmy, + Float64List matrix4, + ui.FilterQuality? filterQuality + ) => CkImageShader(image, tmx, tmy, matrix4, filterQuality); @override ui.Path createPath() => CkPath(); @@ -273,111 +303,111 @@ class CanvasKitRenderer implements Renderer { @override ui.Path combinePaths(ui.PathOperation op, ui.Path path1, ui.Path path2) => - CkPath.combine(op, path1, path2); - - @override - ui.TextStyle createTextStyle( - {ui.Color? color, - ui.TextDecoration? decoration, - ui.Color? decorationColor, - ui.TextDecorationStyle? decorationStyle, - double? decorationThickness, - ui.FontWeight? fontWeight, - ui.FontStyle? fontStyle, - ui.TextBaseline? textBaseline, - String? fontFamily, - List? fontFamilyFallback, - double? fontSize, - double? letterSpacing, - double? wordSpacing, - double? height, - ui.TextLeadingDistribution? leadingDistribution, - ui.Locale? locale, - ui.Paint? background, - ui.Paint? foreground, - List? shadows, - List? fontFeatures, - List? fontVariations}) => - CkTextStyle( - color: color, - decoration: decoration, - decorationColor: decorationColor, - decorationStyle: decorationStyle, - decorationThickness: decorationThickness, - fontWeight: fontWeight, - fontStyle: fontStyle, - textBaseline: textBaseline, - fontFamily: fontFamily, - fontFamilyFallback: fontFamilyFallback, - fontSize: fontSize, - letterSpacing: letterSpacing, - wordSpacing: wordSpacing, - height: height, - leadingDistribution: leadingDistribution, - locale: locale, - background: background as CkPaint?, - foreground: foreground as CkPaint?, - shadows: shadows, - fontFeatures: fontFeatures, - fontVariations: fontVariations, - ); - - @override - ui.ParagraphStyle createParagraphStyle( - {ui.TextAlign? textAlign, - ui.TextDirection? textDirection, - int? maxLines, - String? fontFamily, - double? fontSize, - double? height, - ui.TextHeightBehavior? textHeightBehavior, - ui.FontWeight? fontWeight, - ui.FontStyle? fontStyle, - ui.StrutStyle? strutStyle, - String? ellipsis, - ui.Locale? locale}) => - CkParagraphStyle( - textAlign: textAlign, - textDirection: textDirection, - maxLines: maxLines, - fontFamily: fontFamily, - fontSize: fontSize, - height: height, - textHeightBehavior: textHeightBehavior, - fontWeight: fontWeight, - fontStyle: fontStyle, - strutStyle: strutStyle, - ellipsis: ellipsis, - locale: locale, - applyRoundingHack: !ui.ParagraphBuilder.shouldDisableRoundingHack, - ); - - @override - ui.StrutStyle createStrutStyle( - {String? fontFamily, - List? fontFamilyFallback, - double? fontSize, - double? height, - ui.TextLeadingDistribution? leadingDistribution, - double? leading, - ui.FontWeight? fontWeight, - ui.FontStyle? fontStyle, - bool? forceStrutHeight}) => - CkStrutStyle( - fontFamily: fontFamily, - fontFamilyFallback: fontFamilyFallback, - fontSize: fontSize, - height: height, - leadingDistribution: leadingDistribution, - leading: leading, - fontWeight: fontWeight, - fontStyle: fontStyle, - forceStrutHeight: forceStrutHeight, - ); + CkPath.combine(op, path1, path2); + + @override + ui.TextStyle createTextStyle({ + ui.Color? color, + ui.TextDecoration? decoration, + ui.Color? decorationColor, + ui.TextDecorationStyle? decorationStyle, + double? decorationThickness, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.TextBaseline? textBaseline, + String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? letterSpacing, + double? wordSpacing, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + ui.Locale? locale, + ui.Paint? background, + ui.Paint? foreground, + List? shadows, + List? fontFeatures, + List? fontVariations + }) => CkTextStyle( + color: color, + decoration: decoration, + decorationColor: decorationColor, + decorationStyle: decorationStyle, + decorationThickness: decorationThickness, + fontWeight: fontWeight, + fontStyle: fontStyle, + textBaseline: textBaseline, + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + letterSpacing: letterSpacing, + wordSpacing: wordSpacing, + height: height, + leadingDistribution: leadingDistribution, + locale: locale, + background: background as CkPaint?, + foreground: foreground as CkPaint?, + shadows: shadows, + fontFeatures: fontFeatures, + fontVariations: fontVariations, + ); + + @override + ui.ParagraphStyle createParagraphStyle({ + ui.TextAlign? textAlign, + ui.TextDirection? textDirection, + int? maxLines, + String? fontFamily, + double? fontSize, + double? height, + ui.TextHeightBehavior? textHeightBehavior, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.StrutStyle? strutStyle, + String? ellipsis, + ui.Locale? locale + }) => CkParagraphStyle( + textAlign: textAlign, + textDirection: textDirection, + maxLines: maxLines, + fontFamily: fontFamily, + fontSize: fontSize, + height: height, + textHeightBehavior: textHeightBehavior, + fontWeight: fontWeight, + fontStyle: fontStyle, + strutStyle: strutStyle, + ellipsis: ellipsis, + locale: locale, + applyRoundingHack: !ui.ParagraphBuilder.shouldDisableRoundingHack, + ); + + @override + ui.StrutStyle createStrutStyle({ + String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + double? leading, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + bool? forceStrutHeight + }) => CkStrutStyle( + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + height: height, + leadingDistribution: leadingDistribution, + leading: leading, + fontWeight: fontWeight, + fontStyle: fontStyle, + forceStrutHeight: forceStrutHeight, + ); @override ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) => - CkParagraphBuilder(style); + CkParagraphBuilder(style); @override Future renderScene(ui.Scene scene, ui.FlutterView view) async { @@ -436,39 +466,38 @@ class CanvasKitRenderer implements Renderer { _programs.clear(); } - static final Map> _programs = - >{}; + static final Map> _programs = >{}; @override Future createFragmentProgram(String assetKey) { if (_programs.containsKey(assetKey)) { return _programs[assetKey]!; } - return _programs[assetKey] = - ui_web.assetManager.load(assetKey).then((ByteData data) { + return _programs[assetKey] = ui_web.assetManager.load(assetKey).then((ByteData data) { return CkFragmentProgram.fromBytes(assetKey, data.buffer.asUint8List()); }); } @override - ui.LineMetrics createLineMetrics( - {required bool hardBreak, - required double ascent, - required double descent, - required double unscaledAscent, - required double height, - required double width, - required double left, - required double baseline, - required int lineNumber}) => - EngineLineMetrics( - hardBreak: hardBreak, - ascent: ascent, - descent: descent, - unscaledAscent: unscaledAscent, - height: height, - width: width, - left: left, - baseline: baseline, - lineNumber: lineNumber); + ui.LineMetrics createLineMetrics({ + required bool hardBreak, + required double ascent, + required double descent, + required double unscaledAscent, + required double height, + required double width, + required double left, + required double baseline, + required int lineNumber + }) => EngineLineMetrics( + hardBreak: hardBreak, + ascent: ascent, + descent: descent, + unscaledAscent: unscaledAscent, + height: height, + width: width, + left: left, + baseline: baseline, + lineNumber: lineNumber + ); } From d3757e76a0257739c0535d25010eaa2d010dce47 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Wed, 10 Jan 2024 15:40:12 -0800 Subject: [PATCH 03/18] WIP fixing semantics tests --- .../canvaskit/multi_surface_rasterizer.dart | 6 +- .../offscreen_canvas_rasterizer.dart | 2 +- .../canvaskit/overlay_canvas_factory.dart | 3 +- .../lib/src/engine/canvaskit/rasterizer.dart | 6 + .../src/engine/canvaskit/render_canvas.dart | 9 + .../lib/src/engine/canvaskit/surface.dart | 55 +++++- .../lib/src/engine/platform_dispatcher.dart | 1 + .../lib/src/engine/semantics/focusable.dart | 17 +- .../lib/src/engine/semantics/semantics.dart | 162 +++++++++++------- ....dart => overlay_canvas_factory_test.dart} | 31 ++-- .../test/engine/semantics/semantics_test.dart | 3 + .../engine/semantics/semantics_tester.dart | 18 +- 12 files changed, 221 insertions(+), 92 deletions(-) rename lib/web_ui/test/canvaskit/{render_canvas_factory_test.dart => overlay_canvas_factory_test.dart} (72%) diff --git a/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart index 07d21744e8b41..0e592d15c295e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart @@ -36,7 +36,7 @@ class MultiSurfaceViewRasterizer extends ViewRasterizer { @override final OverlayCanvasFactory overlayFactory = OverlayCanvasFactory( - createCanvas: () => Surface(useOffscreenCanvas: false)); + createCanvas: () => Surface(isRenderCanvas: true)); @override void dispose() { @@ -45,14 +45,14 @@ class MultiSurfaceViewRasterizer extends ViewRasterizer { @override void prepareToDraw() { - overlayFactory.baseCanvas.ensureSurface(currentFrameSize); + overlayFactory.baseCanvas.createOrUpdateSurface(currentFrameSize); } @override Future rasterizeToCanvas( OverlayCanvas canvas, List pictures) { final Surface surface = canvas as Surface; - surface.ensureSurface(currentFrameSize); + surface.createOrUpdateSurface(currentFrameSize); final CkCanvas skCanvas = surface.getCanvas(); skCanvas.clear(const ui.Color(0x00000000)); pictures.forEach(skCanvas.drawPicture); diff --git a/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart index 0e61d88717202..51b9c9e4edf87 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart @@ -56,6 +56,6 @@ class OffscreenCanvasViewRasterizer extends ViewRasterizer { @override void prepareToDraw() { - rasterizer.offscreenSurface.ensureSurface(currentFrameSize); + rasterizer.offscreenSurface.createOrUpdateSurface(currentFrameSize); } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart b/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart index 944138628dcb2..2e7a9f69cdffe 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart @@ -21,7 +21,7 @@ class OverlayCanvasFactory { /// The base canvas to paint on. This is the default canvas which will be /// painted to. If there are no platform views, then this canvas will render /// the entire scene. - late final T baseCanvas = createCanvas(); + late final T baseCanvas = createCanvas()..initialize(); /// Canvases created by this factory which are currently in use. final List _liveCanvases = []; @@ -51,6 +51,7 @@ class OverlayCanvasFactory { return canvas; } else { final T canvas = createCanvas(); + canvas.initialize(); _liveCanvases.add(canvas); return canvas; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart index c7e61e90638d4..f35310638cb1e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart @@ -101,6 +101,12 @@ abstract class ViewRasterizer { abstract class OverlayCanvas { DomElement get htmlElement; + /// Whether or not this overlay canvas is attached to the DOM. + bool get isConnected; + + /// Initialize the overlay. + void initialize(); + /// Disposes this overlay. void dispose(); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart index d777b59e2c2ce..231f0b673c6db 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart @@ -134,6 +134,15 @@ class RenderCanvas extends OverlayCanvas { _updateLogicalHtmlCanvasSize(); } + @override + bool get isConnected => canvasElement.isConnected!; + + @override + void initialize() { + // No extra initialization needed. + } + + @override void dispose() { htmlElement.remove(); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 24c7c3a8a1409..db50914dbc810 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -8,6 +8,7 @@ import 'package:ui/ui.dart' as ui; import '../browser_detection.dart'; import '../configuration.dart'; +import '../display.dart'; import '../dom.dart'; import '../platform_dispatcher.dart'; import '../util.dart'; @@ -49,15 +50,18 @@ class SurfaceFrame { /// successive frames if they are the same size. Otherwise, a new [CkSurface] is /// created. class Surface extends OverlayCanvas { - Surface({bool useOffscreenCanvas = true}) + Surface({this.isRenderCanvas = false}) : useOffscreenCanvas = - Surface.offscreenCanvasSupported && useOffscreenCanvas; + Surface.offscreenCanvasSupported && !isRenderCanvas; CkSurface? _surface; /// Whether or not to use an `OffscreenCanvas` to back this [Surface]. final bool useOffscreenCanvas; + /// If `true`, this [Surface] is used for rendering. + final bool isRenderCanvas; + /// If true, forces a new WebGL context to be created, even if the window /// size is the same. This is used to restore the UI after the browser tab /// goes dormant and loses the GL context. @@ -99,10 +103,11 @@ class Surface extends OverlayCanvas { /// Note, if this getter is called, then this Surface is being used as an /// overlay and must be backed by an onscreen element. @override - DomElement get htmlElement => _canvasElement!; + final DomElement htmlElement = createDomElement('flt-canvas-container'); int _pixelWidth = -1; int _pixelHeight = -1; + double _currentDevicePixelRatio = -1; int _sampleCount = -1; int _stencilBits = -1; @@ -179,6 +184,26 @@ class Surface extends OverlayCanvas { ui.Size? _currentCanvasPhysicalSize; ui.Size? _currentSurfaceSize; + /// Sets the CSS size of the canvas so that canvas pixels are 1:1 with device + /// pixels. + /// + /// The logical size of the canvas is not based on the size of the window + /// but on the size of the canvas, which, due to `ceil()` above, may not be + /// the same as the window. We do not round/floor/ceil the logical size as + /// CSS pixels can contain more than one physical pixel and therefore to + /// match the size of the window precisely we use the most precise floating + /// point value we can get. + void _updateLogicalHtmlCanvasSize() { + final double devicePixelRatio = + EngineFlutterDisplay.instance.devicePixelRatio; + final double logicalWidth = _pixelWidth / devicePixelRatio; + final double logicalHeight = _pixelHeight / devicePixelRatio; + final DomCSSStyleDeclaration style = _canvasElement!.style; + style.width = '${logicalWidth}px'; + style.height = '${logicalHeight}px'; + _currentDevicePixelRatio = devicePixelRatio; + } + /// This is only valid after the first frame or if [ensureSurface] has been /// called bool get usingSoftwareBackend => @@ -218,6 +243,11 @@ class Surface extends OverlayCanvas { if (previousSurfaceSize != null && size.width == previousSurfaceSize.width && size.height == previousSurfaceSize.height) { + final double devicePixelRatio = + EngineFlutterDisplay.instance.devicePixelRatio; + if (isRenderCanvas && devicePixelRatio != _currentDevicePixelRatio) { + _updateLogicalHtmlCanvasSize(); + } return _surface!; } @@ -240,6 +270,9 @@ class Surface extends OverlayCanvas { _currentCanvasPhysicalSize = newSize; _pixelWidth = newSize.width.ceil(); _pixelHeight = newSize.height.ceil(); + if (isRenderCanvas) { + _updateLogicalHtmlCanvasSize(); + } } } @@ -311,6 +344,7 @@ class Surface extends OverlayCanvas { _cachedContextLostListener, false, ); + _canvasElement!.remove(); _canvasElement = null; _cachedContextRestoredListener = null; _cachedContextLostListener = null; @@ -335,6 +369,12 @@ class Surface extends OverlayCanvas { htmlCanvas = canvas; _canvasElement = canvas; _offscreenCanvas = null; + if (isRenderCanvas) { + _canvasElement!.setAttribute('aria-hidden', 'true'); + _canvasElement!.style.position = 'absolute'; + htmlElement.append(_canvasElement!); + _updateLogicalHtmlCanvasSize(); + } } // When the browser tab using WebGL goes dormant the browser and/or OS may @@ -458,6 +498,15 @@ class Surface extends OverlayCanvas { return true; } + @override + bool get isConnected => _canvasElement!.isConnected!; + + @override + void initialize() { + ensureSurface(); + } + + @override void dispose() { _offscreenCanvas?.removeEventListener( 'webglcontextlost', _cachedContextLostListener, false); diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 1453da94e0c3d..b712566782592 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -1237,6 +1237,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// Otherwise zones won't work properly. void invokeOnSemanticsAction( int nodeId, ui.SemanticsAction action, ByteData? args) { + print('invoking semantics action callback!'); invoke1( _onSemanticsActionEvent, _onSemanticsActionEventZone, diff --git a/lib/web_ui/lib/src/engine/semantics/focusable.dart b/lib/web_ui/lib/src/engine/semantics/focusable.dart index 35fff64a50158..5f0d5297e0dfa 100644 --- a/lib/web_ui/lib/src/engine/semantics/focusable.dart +++ b/lib/web_ui/lib/src/engine/semantics/focusable.dart @@ -58,7 +58,8 @@ class Focusable extends RoleManager { if (!_focusManager.isManaging) { _focusManager.manage(semanticsObject.id, owner.element); } - _focusManager.changeFocus(semanticsObject.hasFocus && (!semanticsObject.hasEnabledState || semanticsObject.isEnabled)); + _focusManager.changeFocus(semanticsObject.hasFocus && + (!semanticsObject.hasEnabledState || semanticsObject.isEnabled)); } else { _focusManager.stopManaging(); } @@ -175,6 +176,7 @@ class AccessibilityFocusManager { void _setFocusFromDom(bool acquireFocus) { final _FocusTarget? target = _target; + print('hit setFocus callback on ${target?.element}'); if (target == null) { // DOM events can be asynchronous. By the time the event reaches here, the @@ -185,8 +187,8 @@ class AccessibilityFocusManager { EnginePlatformDispatcher.instance.invokeOnSemanticsAction( target.semanticsNodeId, acquireFocus - ? ui.SemanticsAction.didGainAccessibilityFocus - : ui.SemanticsAction.didLoseAccessibilityFocus, + ? ui.SemanticsAction.didGainAccessibilityFocus + : ui.SemanticsAction.didLoseAccessibilityFocus, null, ); } @@ -194,6 +196,7 @@ class AccessibilityFocusManager { /// Requests focus or blur on the DOM element. void changeFocus(bool value) { final _FocusTarget? target = _target; + print('calling changeFocus on $target!!!'); if (target == null) { // If this branch is being executed, there's a bug somewhere already, but @@ -203,9 +206,8 @@ class AccessibilityFocusManager { // Nothing is being managed right now. assert(() { printWarning( - 'Cannot change focus to $value. No element is being managed by this ' - 'AccessibilityFocusManager.' - ); + 'Cannot change focus to $value. No element is being managed by this ' + 'AccessibilityFocusManager.'); return true; }()); return; @@ -237,6 +239,8 @@ class AccessibilityFocusManager { return; } + print('ADDING POST UPDATE CALLBACK TO FOCUS THE ELEMENT!!!'); + // Delay the focus request until the final DOM structure is established // because the element may not yet be attached to the DOM, or it may be // reparented and lose focus again. @@ -249,6 +253,7 @@ class AccessibilityFocusManager { return; } + print('CALLING FOCUS ON ${target.element}!!!!!'); target.element.focus(); }); } diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index b65f4484b7a74..9edb6d6d6b516 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -9,7 +9,7 @@ import 'package:meta/meta.dart'; import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; -import '../../engine.dart' show registerHotRestartListener; +import '../../engine.dart' show registerHotRestartListener; import '../alarm_clock.dart'; import '../browser_detection.dart'; import '../configuration.dart'; @@ -99,18 +99,19 @@ class EngineAccessibilityFeatures implements ui.AccessibilityFeatures { @override int get hashCode => _index.hashCode; - EngineAccessibilityFeatures copyWith({ - bool? accessibleNavigation, + EngineAccessibilityFeatures copyWith( + {bool? accessibleNavigation, bool? invertColors, bool? disableAnimations, bool? boldText, bool? reduceMotion, bool? highContrast, - bool? onOffSwitchLabels}) - { - final EngineAccessibilityFeaturesBuilder builder = EngineAccessibilityFeaturesBuilder(0); + bool? onOffSwitchLabels}) { + final EngineAccessibilityFeaturesBuilder builder = + EngineAccessibilityFeaturesBuilder(0); - builder.accessibleNavigation = accessibleNavigation ?? this.accessibleNavigation; + builder.accessibleNavigation = + accessibleNavigation ?? this.accessibleNavigation; builder.invertColors = invertColors ?? this.invertColors; builder.disableAnimations = disableAnimations ?? this.disableAnimations; builder.boldText = boldText ?? this.boldText; @@ -127,47 +128,58 @@ class EngineAccessibilityFeaturesBuilder { int _index = 0; - bool get accessibleNavigation => EngineAccessibilityFeatures._kAccessibleNavigation & _index != 0; - bool get invertColors => EngineAccessibilityFeatures._kInvertColorsIndex & _index != 0; - bool get disableAnimations => EngineAccessibilityFeatures._kDisableAnimationsIndex & _index != 0; - bool get boldText => EngineAccessibilityFeatures._kBoldTextIndex & _index != 0; - bool get reduceMotion => EngineAccessibilityFeatures._kReduceMotionIndex & _index != 0; - bool get highContrast => EngineAccessibilityFeatures._kHighContrastIndex & _index != 0; - bool get onOffSwitchLabels => EngineAccessibilityFeatures._kOnOffSwitchLabelsIndex & _index != 0; + bool get accessibleNavigation => + EngineAccessibilityFeatures._kAccessibleNavigation & _index != 0; + bool get invertColors => + EngineAccessibilityFeatures._kInvertColorsIndex & _index != 0; + bool get disableAnimations => + EngineAccessibilityFeatures._kDisableAnimationsIndex & _index != 0; + bool get boldText => + EngineAccessibilityFeatures._kBoldTextIndex & _index != 0; + bool get reduceMotion => + EngineAccessibilityFeatures._kReduceMotionIndex & _index != 0; + bool get highContrast => + EngineAccessibilityFeatures._kHighContrastIndex & _index != 0; + bool get onOffSwitchLabels => + EngineAccessibilityFeatures._kOnOffSwitchLabelsIndex & _index != 0; set accessibleNavigation(bool value) { - const int accessibleNavigation = EngineAccessibilityFeatures._kAccessibleNavigation; - _index = value? _index | accessibleNavigation : _index & ~accessibleNavigation; + const int accessibleNavigation = + EngineAccessibilityFeatures._kAccessibleNavigation; + _index = + value ? _index | accessibleNavigation : _index & ~accessibleNavigation; } set invertColors(bool value) { const int invertColors = EngineAccessibilityFeatures._kInvertColorsIndex; - _index = value? _index | invertColors : _index & ~invertColors; + _index = value ? _index | invertColors : _index & ~invertColors; } set disableAnimations(bool value) { - const int disableAnimations = EngineAccessibilityFeatures._kDisableAnimationsIndex; - _index = value? _index | disableAnimations : _index & ~disableAnimations; + const int disableAnimations = + EngineAccessibilityFeatures._kDisableAnimationsIndex; + _index = value ? _index | disableAnimations : _index & ~disableAnimations; } set boldText(bool value) { const int boldText = EngineAccessibilityFeatures._kBoldTextIndex; - _index = value? _index | boldText : _index & ~boldText; + _index = value ? _index | boldText : _index & ~boldText; } set reduceMotion(bool value) { const int reduceMotion = EngineAccessibilityFeatures._kReduceMotionIndex; - _index = value? _index | reduceMotion : _index & ~reduceMotion; + _index = value ? _index | reduceMotion : _index & ~reduceMotion; } set highContrast(bool value) { const int highContrast = EngineAccessibilityFeatures._kHighContrastIndex; - _index = value? _index | highContrast : _index & ~highContrast; + _index = value ? _index | highContrast : _index & ~highContrast; } set onOffSwitchLabels(bool value) { - const int onOffSwitchLabels = EngineAccessibilityFeatures._kOnOffSwitchLabelsIndex; - _index = value? _index | onOffSwitchLabels : _index & ~onOffSwitchLabels; + const int onOffSwitchLabels = + EngineAccessibilityFeatures._kOnOffSwitchLabelsIndex; + _index = value ? _index | onOffSwitchLabels : _index & ~onOffSwitchLabels; } /// Creates and returns an instance of EngineAccessibilityFeatures based on the value of _index @@ -464,12 +476,17 @@ abstract class PrimaryRoleManager { /// /// This is only meant to be used in tests. @visibleForTesting - List get debugSecondaryRoles => _secondaryRoleManagers?.map((RoleManager manager) => manager.role).toList() ?? const []; + List get debugSecondaryRoles => + _secondaryRoleManagers + ?.map((RoleManager manager) => manager.role) + .toList() ?? + const []; @protected DomElement createElement() => domDocument.createElement('flt-semantics'); - static DomElement _initElement(DomElement element, SemanticsObject semanticsObject) { + static DomElement _initElement( + DomElement element, SemanticsObject semanticsObject) { // DOM nodes created for semantics objects are positioned absolutely using // transforms. element.style.position = 'absolute'; @@ -515,9 +532,13 @@ abstract class PrimaryRoleManager { void removeAttribute(String name) => element.removeAttribute(name); - void addEventListener(String type, DomEventListener? listener, [bool? useCapture]) => element.addEventListener(type, listener, useCapture); + void addEventListener(String type, DomEventListener? listener, + [bool? useCapture]) => + element.addEventListener(type, listener, useCapture); - void removeEventListener(String type, DomEventListener? listener, [bool? useCapture]) => element.removeEventListener(type, listener, useCapture); + void removeEventListener(String type, DomEventListener? listener, + [bool? useCapture]) => + element.removeEventListener(type, listener, useCapture); /// Convenience getter for the [Focusable] role manager, if any. Focusable? get focusable => _focusable; @@ -555,7 +576,9 @@ abstract class PrimaryRoleManager { @protected void addSecondaryRole(RoleManager secondaryRoleManager) { assert( - _secondaryRoleManagers?.any((RoleManager manager) => manager.role == secondaryRoleManager.role) != true, + _secondaryRoleManagers?.any((RoleManager manager) => + manager.role == secondaryRoleManager.role) != + true, 'Cannot add secondary role ${secondaryRoleManager.role}. This object already has this secondary role.', ); _secondaryRoleManagers ??= []; @@ -618,7 +641,8 @@ abstract class PrimaryRoleManager { /// A role used when a more specific role couldn't be assigned to the node. final class GenericRole extends PrimaryRoleManager { - GenericRole(SemanticsObject semanticsObject) : super.withBasics(PrimaryRole.generic, semanticsObject); + GenericRole(SemanticsObject semanticsObject) + : super.withBasics(PrimaryRole.generic, semanticsObject); @override void update() { @@ -940,7 +964,8 @@ class SemanticsObject { String? _increasedValue; /// See [ui.SemanticsUpdateBuilder.updateNode] - List? get increasedValueAttributes => _increasedValueAttributes; + List? get increasedValueAttributes => + _increasedValueAttributes; List? _increasedValueAttributes; static const int _increasedValueIndex = 1 << 13; @@ -957,7 +982,8 @@ class SemanticsObject { String? _decreasedValue; /// See [ui.SemanticsUpdateBuilder.updateNode] - List? get decreasedValueAttributes => _decreasedValueAttributes; + List? get decreasedValueAttributes => + _decreasedValueAttributes; List? _decreasedValueAttributes; static const int _decreasedValueIndex = 1 << 14; @@ -1126,6 +1152,7 @@ class SemanticsObject { assert(owner.phase == SemanticsUpdatePhase.postUpdate); return _parent; } + SemanticsObject? _parent; /// Whether this node currently has a given [SemanticsFlag]. @@ -1163,16 +1190,18 @@ class SemanticsObject { hasAction(ui.SemanticsAction.scrollRight); /// Whether this object represents a scrollable area in any direction. - bool get isScrollContainer => isVerticalScrollContainer || isHorizontalScrollContainer; + bool get isScrollContainer => + isVerticalScrollContainer || isHorizontalScrollContainer; /// Whether this object has a non-empty list of children. bool get hasChildren => - _childrenInTraversalOrder != null && _childrenInTraversalOrder!.isNotEmpty; + _childrenInTraversalOrder != null && + _childrenInTraversalOrder!.isNotEmpty; /// Whether this object represents an editable text field. bool get isTextField => hasFlag(ui.SemanticsFlag.isTextField); - /// Whether this object represents an editable text field. + /// Whether this object represents an editable text field. bool get isLink => hasFlag(ui.SemanticsFlag.isLink); /// Whether this object needs screen readers attention right away. @@ -1182,9 +1211,7 @@ class SemanticsObject { /// Whether this object represents an image with no tappable functionality. bool get isVisualOnly => - hasFlag(ui.SemanticsFlag.isImage) && - !isTappable && - !isButton; + hasFlag(ui.SemanticsFlag.isImage) && !isTappable && !isButton; /// Whether this node defines a scope for a route. /// @@ -1391,8 +1418,7 @@ class SemanticsObject { /// z-index CSS style attribute. void updateChildren() { // Trivial case: remove all children. - if (_childrenInHitTestOrder == null || - _childrenInHitTestOrder!.isEmpty) { + if (_childrenInHitTestOrder == null || _childrenInHitTestOrder!.isEmpty) { if (_currentChildrenInRenderOrder == null || _currentChildrenInRenderOrder!.isEmpty) { // A container element must not have been created when child list is empty. @@ -1427,7 +1453,8 @@ class SemanticsObject { // is determined by the DOM order of elements. final List childrenInRenderOrder = []; for (int i = 0; i < childCount; i++) { - childrenInRenderOrder.add(owner._semanticsTree[childrenInTraversalOrder[i]]!); + childrenInRenderOrder + .add(owner._semanticsTree[childrenInTraversalOrder[i]]!); } // The z-index determines hit testing. Technically, it also affects paint @@ -1440,7 +1467,8 @@ class SemanticsObject { final bool zIndexMatters = childCount > 1; if (zIndexMatters) { for (int i = 0; i < childCount; i++) { - final SemanticsObject child = owner._semanticsTree[childrenInHitTestOrder[i]]!; + final SemanticsObject child = + owner._semanticsTree[childrenInHitTestOrder[i]]!; // Invert the z-index because hit-test order is inverted with respect to // paint order. @@ -1460,7 +1488,8 @@ class SemanticsObject { } // At this point it is guaranteed to have had a non-empty previous child list. - final List previousChildrenInRenderOrder = _currentChildrenInRenderOrder!; + final List previousChildrenInRenderOrder = + _currentChildrenInRenderOrder!; final int previousCount = previousChildrenInRenderOrder.length; // Both non-empty case. @@ -1497,7 +1526,8 @@ class SemanticsObject { } // Trivial case: child lists are identical both in length and order => do nothing. - if (previousCount == childrenInRenderOrder.length && newIndex == childCount) { + if (previousCount == childrenInRenderOrder.length && + newIndex == childCount) { return; } @@ -1516,12 +1546,13 @@ class SemanticsObject { // The longest sub-sequence in the old list maximizes the number of children // that do not need to be moved. - final List longestSequence = longestIncreasingSubsequence(intersectionIndicesOld); + final List longestSequence = + longestIncreasingSubsequence(intersectionIndicesOld); final List stationaryIds = []; for (int i = 0; i < longestSequence.length; i += 1) { - stationaryIds.add( - previousChildrenInRenderOrder[intersectionIndicesOld[longestSequence[i]!]].id - ); + stationaryIds.add(previousChildrenInRenderOrder[ + intersectionIndicesOld[longestSequence[i]!]] + .id); } // Remove children that are no longer in the list. @@ -1780,7 +1811,8 @@ class SemanticsObject { /// /// Unlike [visitDepthFirstInTraversalOrder] this method can traverse /// partially updated, incomplete, or inconsistent tree. - void _debugVisitRenderedSemanticNodesDepthFirst(void Function(SemanticsObject) callback) { + void _debugVisitRenderedSemanticNodesDepthFirst( + void Function(SemanticsObject) callback) { callback(this); _currentChildrenInRenderOrder?.forEach((SemanticsObject child) { child._debugVisitRenderedSemanticNodesDepthFirst(callback); @@ -1794,11 +1826,13 @@ class SemanticsObject { /// the callback returns true, continues visiting descendants. Otherwise, /// stops immediately after visiting the node that caused the callback to /// return false. - void visitDepthFirstInTraversalOrder(bool Function(SemanticsObject) callback) { + void visitDepthFirstInTraversalOrder( + bool Function(SemanticsObject) callback) { _visitDepthFirstInTraversalOrder(callback); } - bool _visitDepthFirstInTraversalOrder(bool Function(SemanticsObject) callback) { + bool _visitDepthFirstInTraversalOrder( + bool Function(SemanticsObject) callback) { final bool shouldContinueVisiting = callback(this); if (!shouldContinueVisiting) { @@ -1949,9 +1983,10 @@ class EngineSemantics { if (value == _semanticsEnabled) { return; } - final EngineAccessibilityFeatures original = - EnginePlatformDispatcher.instance.configuration.accessibilityFeatures - as EngineAccessibilityFeatures; + final EngineAccessibilityFeatures original = EnginePlatformDispatcher + .instance + .configuration + .accessibilityFeatures as EngineAccessibilityFeatures; final PlatformConfiguration newConfiguration = EnginePlatformDispatcher.instance.configuration.copyWith( accessibilityFeatures: @@ -1968,7 +2003,8 @@ class EngineSemantics { _gestureMode = GestureMode.pointerEvents; _notifyGestureModeListeners(); } - for (final EngineFlutterView view in EnginePlatformDispatcher.instance.views) { + for (final EngineFlutterView view + in EnginePlatformDispatcher.instance.views) { view.semantics.reset(); } _gestureModeClock?.datetime = null; @@ -2126,7 +2162,8 @@ class EngineSemantics { /// Callbacks are called synchronously. HTML DOM updates made in a callback /// take effect in the current animation frame and/or the current message loop /// event. - final List _gestureModeListeners = []; + final List _gestureModeListeners = + []; /// Calls the [callback] every time the current [GestureMode] changes. /// @@ -2223,7 +2260,8 @@ class EngineSemanticsOwner { /// Attachments take precedence over detachments (see [_detachObject]). This /// allows the same node to be detached from one parent in the tree and /// reattached to another parent. - void _attachObject({required SemanticsObject parent, required SemanticsObject child}) { + void _attachObject( + {required SemanticsObject parent, required SemanticsObject child}) { child._parent = parent; _attachments[child.id] = parent; } @@ -2294,6 +2332,7 @@ class EngineSemanticsOwner { _phase = SemanticsUpdatePhase.postUpdate; try { if (_oneTimePostUpdateCallbacks.isNotEmpty) { + print('CALLING ONETIME POSTUPDATE CALLBACKS!'); for (final ui.VoidCallback callback in _oneTimePostUpdateCallbacks) { callback(); } @@ -2337,12 +2376,14 @@ class EngineSemanticsOwner { final SemanticsObject? root = _semanticsTree[0]; if (root != null) { root._debugVisitRenderedSemanticNodesDepthFirst((SemanticsObject child) { - liveIds[child.id] = child._childrenInTraversalOrder?.toList() ?? const []; + liveIds[child.id] = + child._childrenInTraversalOrder?.toList() ?? const []; }); } final bool isConsistent = _semanticsTree.keys.every(liveIds.keys.contains); - final String heading = 'The semantics node map is ${isConsistent ? 'consistent' : 'inconsistent'}'; + final String heading = + 'The semantics node map is ${isConsistent ? 'consistent' : 'inconsistent'}'; final StringBuffer message = StringBuffer('$heading:\n'); message.writeln(' Nodes in tree:'); for (final MapEntry> entry in liveIds.entries) { @@ -2399,7 +2440,8 @@ class EngineSemanticsOwner { // Validate that the node map only contains live elements, i.e. descendants // of the root node. If a node is not reachable from the root, it should // have been removed from the map. - final (bool isConsistent, String description) = _computeNodeMapConsistencyMessage(); + final (bool isConsistent, String description) = + _computeNodeMapConsistencyMessage(); if (!isConsistent) { // Use StateError because AssertionError escapes line breaks, but this // error message is very detailed and it needs line breaks for diff --git a/lib/web_ui/test/canvaskit/render_canvas_factory_test.dart b/lib/web_ui/test/canvaskit/overlay_canvas_factory_test.dart similarity index 72% rename from lib/web_ui/test/canvaskit/render_canvas_factory_test.dart rename to lib/web_ui/test/canvaskit/overlay_canvas_factory_test.dart index fb5f04fbac7a7..945c5bbff0bf8 100644 --- a/lib/web_ui/test/canvaskit/render_canvas_factory_test.dart +++ b/lib/web_ui/test/canvaskit/overlay_canvas_factory_test.dart @@ -13,11 +13,13 @@ void main() { } void testMain() { - group('$RenderCanvasFactory', () { + group('$OverlayCanvasFactory', () { setUpCanvasKitTest(); test('getCanvas', () { - final RenderCanvasFactory factory = RenderCanvasFactory(); + final OverlayCanvasFactory factory = + OverlayCanvasFactory( + createCanvas: () => RenderCanvas()); expect(factory.baseCanvas, isNotNull); expect(factory.debugSurfaceCount, equals(1)); @@ -36,7 +38,9 @@ void testMain() { }); test('releaseCanvas', () { - final RenderCanvasFactory factory = RenderCanvasFactory(); + final OverlayCanvasFactory factory = + OverlayCanvasFactory( + createCanvas: () => RenderCanvas()); // Create a new canvas and immediately release it. final RenderCanvas canvas = factory.getCanvas(); @@ -49,7 +53,9 @@ void testMain() { }); test('isLive', () { - final RenderCanvasFactory factory = RenderCanvasFactory(); + final OverlayCanvasFactory factory = + OverlayCanvasFactory( + createCanvas: () => RenderCanvas()); expect(factory.isLive(factory.baseCanvas), isTrue); @@ -61,26 +67,27 @@ void testMain() { }); test('hot restart', () { - void expectDisposed(RenderCanvas canvas) { - expect(canvas.canvasElement.isConnected, isFalse); + void expectDisposed(OverlayCanvas canvas) { + expect(canvas.isConnected, isFalse); } final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!; - final RenderCanvasFactory originalFactory = CanvasKitRenderer.instance - .debugGetRasterizerForView(implicitView)! - .renderCanvasFactory; + final OverlayCanvasFactory originalFactory = + CanvasKitRenderer.instance + .debugGetRasterizerForView(implicitView)! + .overlayFactory; // Cause the surface and its canvas to be attached to the page implicitView.dom.sceneHost .prepend(originalFactory.baseCanvas.htmlElement); - expect(originalFactory.baseCanvas.canvasElement.isConnected, isTrue); + expect(originalFactory.baseCanvas.isConnected, isTrue); // Create a few overlay canvases - final List overlays = []; + final List overlays = []; for (int i = 0; i < 3; i++) { - final RenderCanvas canvas = originalFactory.getCanvas(); + final OverlayCanvas canvas = originalFactory.getCanvas(); implicitView.dom.sceneHost.prepend(canvas.htmlElement); overlays.add(canvas); } diff --git a/lib/web_ui/test/engine/semantics/semantics_test.dart b/lib/web_ui/test/engine/semantics/semantics_test.dart index d751eb589f075..df997aac472b8 100644 --- a/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -1545,15 +1545,18 @@ void _testIncrementables() { pumpSemantics(isFocused: false); final DomElement element = owner().debugSemanticsTree![0]!.element.querySelector('input')!; + await Future.delayed(Duration.zero); expect(capturedActions, isEmpty); pumpSemantics(isFocused: true); + await Future.delayed(Duration.zero); expect(capturedActions, [ (0, ui.SemanticsAction.didGainAccessibilityFocus, null), ]); capturedActions.clear(); pumpSemantics(isFocused: false); + await Future.delayed(Duration.zero); expect( reason: 'The engine never calls blur() explicitly.', capturedActions, diff --git a/lib/web_ui/test/engine/semantics/semantics_tester.dart b/lib/web_ui/test/engine/semantics/semantics_tester.dart index ff49a461e7ef1..0de7a2a1e6c4c 100644 --- a/lib/web_ui/test/engine/semantics/semantics_tester.dart +++ b/lib/web_ui/test/engine/semantics/semantics_tester.dart @@ -306,11 +306,15 @@ class SemanticsTester { value: value ?? '', valueAttributes: valueAttributes ?? const [], increasedValue: increasedValue ?? '', - increasedValueAttributes: increasedValueAttributes ?? const [], + increasedValueAttributes: + increasedValueAttributes ?? const [], decreasedValue: decreasedValue ?? '', - decreasedValueAttributes: decreasedValueAttributes ?? const [], + decreasedValueAttributes: + decreasedValueAttributes ?? const [], tooltip: tooltip ?? '', - transform: transform != null ? toMatrix32(transform) : Matrix4.identity().storage, + transform: transform != null + ? toMatrix32(transform) + : Matrix4.identity().storage, elevation: elevation ?? 0, thickness: thickness ?? 0, childrenInTraversalOrder: childIds, @@ -349,7 +353,9 @@ class SemanticsTester { void expectSemanticsTree(EngineSemanticsOwner owner, String semanticsHtml) { const List ignoredStyleProperties = ['pointer-events']; expect( - canonicalizeHtml(owner.semanticsHost.querySelector('flt-semantics')!.outerHTML!, ignoredStyleProperties: ignoredStyleProperties), + canonicalizeHtml( + owner.semanticsHost.querySelector('flt-semantics')!.outerHTML!, + ignoredStyleProperties: ignoredStyleProperties), canonicalizeHtml(semanticsHtml), ); } @@ -359,8 +365,8 @@ DomElement findScrollable(EngineSemanticsOwner owner) { return owner.semanticsHost.querySelectorAll('flt-semantics').singleWhere( (DomElement? element) { return element!.style.overflow == 'hidden' || - element.style.overflowY == 'scroll' || - element.style.overflowX == 'scroll'; + element.style.overflowY == 'scroll' || + element.style.overflowX == 'scroll'; }, ); } From 86457afca67162a4a771e6d72f0036bdfb85c523 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Wed, 10 Jan 2024 15:44:01 -0800 Subject: [PATCH 04/18] Undo fixes for semantics tests --- .../lib/src/engine/semantics/focusable.dart | 17 +- .../lib/src/engine/semantics/semantics.dart | 162 +++++++----------- .../test/engine/semantics/semantics_test.dart | 3 - .../engine/semantics/semantics_tester.dart | 18 +- 4 files changed, 72 insertions(+), 128 deletions(-) diff --git a/lib/web_ui/lib/src/engine/semantics/focusable.dart b/lib/web_ui/lib/src/engine/semantics/focusable.dart index 5f0d5297e0dfa..35fff64a50158 100644 --- a/lib/web_ui/lib/src/engine/semantics/focusable.dart +++ b/lib/web_ui/lib/src/engine/semantics/focusable.dart @@ -58,8 +58,7 @@ class Focusable extends RoleManager { if (!_focusManager.isManaging) { _focusManager.manage(semanticsObject.id, owner.element); } - _focusManager.changeFocus(semanticsObject.hasFocus && - (!semanticsObject.hasEnabledState || semanticsObject.isEnabled)); + _focusManager.changeFocus(semanticsObject.hasFocus && (!semanticsObject.hasEnabledState || semanticsObject.isEnabled)); } else { _focusManager.stopManaging(); } @@ -176,7 +175,6 @@ class AccessibilityFocusManager { void _setFocusFromDom(bool acquireFocus) { final _FocusTarget? target = _target; - print('hit setFocus callback on ${target?.element}'); if (target == null) { // DOM events can be asynchronous. By the time the event reaches here, the @@ -187,8 +185,8 @@ class AccessibilityFocusManager { EnginePlatformDispatcher.instance.invokeOnSemanticsAction( target.semanticsNodeId, acquireFocus - ? ui.SemanticsAction.didGainAccessibilityFocus - : ui.SemanticsAction.didLoseAccessibilityFocus, + ? ui.SemanticsAction.didGainAccessibilityFocus + : ui.SemanticsAction.didLoseAccessibilityFocus, null, ); } @@ -196,7 +194,6 @@ class AccessibilityFocusManager { /// Requests focus or blur on the DOM element. void changeFocus(bool value) { final _FocusTarget? target = _target; - print('calling changeFocus on $target!!!'); if (target == null) { // If this branch is being executed, there's a bug somewhere already, but @@ -206,8 +203,9 @@ class AccessibilityFocusManager { // Nothing is being managed right now. assert(() { printWarning( - 'Cannot change focus to $value. No element is being managed by this ' - 'AccessibilityFocusManager.'); + 'Cannot change focus to $value. No element is being managed by this ' + 'AccessibilityFocusManager.' + ); return true; }()); return; @@ -239,8 +237,6 @@ class AccessibilityFocusManager { return; } - print('ADDING POST UPDATE CALLBACK TO FOCUS THE ELEMENT!!!'); - // Delay the focus request until the final DOM structure is established // because the element may not yet be attached to the DOM, or it may be // reparented and lose focus again. @@ -253,7 +249,6 @@ class AccessibilityFocusManager { return; } - print('CALLING FOCUS ON ${target.element}!!!!!'); target.element.focus(); }); } diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index 9edb6d6d6b516..b65f4484b7a74 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -9,7 +9,7 @@ import 'package:meta/meta.dart'; import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; -import '../../engine.dart' show registerHotRestartListener; +import '../../engine.dart' show registerHotRestartListener; import '../alarm_clock.dart'; import '../browser_detection.dart'; import '../configuration.dart'; @@ -99,19 +99,18 @@ class EngineAccessibilityFeatures implements ui.AccessibilityFeatures { @override int get hashCode => _index.hashCode; - EngineAccessibilityFeatures copyWith( - {bool? accessibleNavigation, + EngineAccessibilityFeatures copyWith({ + bool? accessibleNavigation, bool? invertColors, bool? disableAnimations, bool? boldText, bool? reduceMotion, bool? highContrast, - bool? onOffSwitchLabels}) { - final EngineAccessibilityFeaturesBuilder builder = - EngineAccessibilityFeaturesBuilder(0); + bool? onOffSwitchLabels}) + { + final EngineAccessibilityFeaturesBuilder builder = EngineAccessibilityFeaturesBuilder(0); - builder.accessibleNavigation = - accessibleNavigation ?? this.accessibleNavigation; + builder.accessibleNavigation = accessibleNavigation ?? this.accessibleNavigation; builder.invertColors = invertColors ?? this.invertColors; builder.disableAnimations = disableAnimations ?? this.disableAnimations; builder.boldText = boldText ?? this.boldText; @@ -128,58 +127,47 @@ class EngineAccessibilityFeaturesBuilder { int _index = 0; - bool get accessibleNavigation => - EngineAccessibilityFeatures._kAccessibleNavigation & _index != 0; - bool get invertColors => - EngineAccessibilityFeatures._kInvertColorsIndex & _index != 0; - bool get disableAnimations => - EngineAccessibilityFeatures._kDisableAnimationsIndex & _index != 0; - bool get boldText => - EngineAccessibilityFeatures._kBoldTextIndex & _index != 0; - bool get reduceMotion => - EngineAccessibilityFeatures._kReduceMotionIndex & _index != 0; - bool get highContrast => - EngineAccessibilityFeatures._kHighContrastIndex & _index != 0; - bool get onOffSwitchLabels => - EngineAccessibilityFeatures._kOnOffSwitchLabelsIndex & _index != 0; + bool get accessibleNavigation => EngineAccessibilityFeatures._kAccessibleNavigation & _index != 0; + bool get invertColors => EngineAccessibilityFeatures._kInvertColorsIndex & _index != 0; + bool get disableAnimations => EngineAccessibilityFeatures._kDisableAnimationsIndex & _index != 0; + bool get boldText => EngineAccessibilityFeatures._kBoldTextIndex & _index != 0; + bool get reduceMotion => EngineAccessibilityFeatures._kReduceMotionIndex & _index != 0; + bool get highContrast => EngineAccessibilityFeatures._kHighContrastIndex & _index != 0; + bool get onOffSwitchLabels => EngineAccessibilityFeatures._kOnOffSwitchLabelsIndex & _index != 0; set accessibleNavigation(bool value) { - const int accessibleNavigation = - EngineAccessibilityFeatures._kAccessibleNavigation; - _index = - value ? _index | accessibleNavigation : _index & ~accessibleNavigation; + const int accessibleNavigation = EngineAccessibilityFeatures._kAccessibleNavigation; + _index = value? _index | accessibleNavigation : _index & ~accessibleNavigation; } set invertColors(bool value) { const int invertColors = EngineAccessibilityFeatures._kInvertColorsIndex; - _index = value ? _index | invertColors : _index & ~invertColors; + _index = value? _index | invertColors : _index & ~invertColors; } set disableAnimations(bool value) { - const int disableAnimations = - EngineAccessibilityFeatures._kDisableAnimationsIndex; - _index = value ? _index | disableAnimations : _index & ~disableAnimations; + const int disableAnimations = EngineAccessibilityFeatures._kDisableAnimationsIndex; + _index = value? _index | disableAnimations : _index & ~disableAnimations; } set boldText(bool value) { const int boldText = EngineAccessibilityFeatures._kBoldTextIndex; - _index = value ? _index | boldText : _index & ~boldText; + _index = value? _index | boldText : _index & ~boldText; } set reduceMotion(bool value) { const int reduceMotion = EngineAccessibilityFeatures._kReduceMotionIndex; - _index = value ? _index | reduceMotion : _index & ~reduceMotion; + _index = value? _index | reduceMotion : _index & ~reduceMotion; } set highContrast(bool value) { const int highContrast = EngineAccessibilityFeatures._kHighContrastIndex; - _index = value ? _index | highContrast : _index & ~highContrast; + _index = value? _index | highContrast : _index & ~highContrast; } set onOffSwitchLabels(bool value) { - const int onOffSwitchLabels = - EngineAccessibilityFeatures._kOnOffSwitchLabelsIndex; - _index = value ? _index | onOffSwitchLabels : _index & ~onOffSwitchLabels; + const int onOffSwitchLabels = EngineAccessibilityFeatures._kOnOffSwitchLabelsIndex; + _index = value? _index | onOffSwitchLabels : _index & ~onOffSwitchLabels; } /// Creates and returns an instance of EngineAccessibilityFeatures based on the value of _index @@ -476,17 +464,12 @@ abstract class PrimaryRoleManager { /// /// This is only meant to be used in tests. @visibleForTesting - List get debugSecondaryRoles => - _secondaryRoleManagers - ?.map((RoleManager manager) => manager.role) - .toList() ?? - const []; + List get debugSecondaryRoles => _secondaryRoleManagers?.map((RoleManager manager) => manager.role).toList() ?? const []; @protected DomElement createElement() => domDocument.createElement('flt-semantics'); - static DomElement _initElement( - DomElement element, SemanticsObject semanticsObject) { + static DomElement _initElement(DomElement element, SemanticsObject semanticsObject) { // DOM nodes created for semantics objects are positioned absolutely using // transforms. element.style.position = 'absolute'; @@ -532,13 +515,9 @@ abstract class PrimaryRoleManager { void removeAttribute(String name) => element.removeAttribute(name); - void addEventListener(String type, DomEventListener? listener, - [bool? useCapture]) => - element.addEventListener(type, listener, useCapture); + void addEventListener(String type, DomEventListener? listener, [bool? useCapture]) => element.addEventListener(type, listener, useCapture); - void removeEventListener(String type, DomEventListener? listener, - [bool? useCapture]) => - element.removeEventListener(type, listener, useCapture); + void removeEventListener(String type, DomEventListener? listener, [bool? useCapture]) => element.removeEventListener(type, listener, useCapture); /// Convenience getter for the [Focusable] role manager, if any. Focusable? get focusable => _focusable; @@ -576,9 +555,7 @@ abstract class PrimaryRoleManager { @protected void addSecondaryRole(RoleManager secondaryRoleManager) { assert( - _secondaryRoleManagers?.any((RoleManager manager) => - manager.role == secondaryRoleManager.role) != - true, + _secondaryRoleManagers?.any((RoleManager manager) => manager.role == secondaryRoleManager.role) != true, 'Cannot add secondary role ${secondaryRoleManager.role}. This object already has this secondary role.', ); _secondaryRoleManagers ??= []; @@ -641,8 +618,7 @@ abstract class PrimaryRoleManager { /// A role used when a more specific role couldn't be assigned to the node. final class GenericRole extends PrimaryRoleManager { - GenericRole(SemanticsObject semanticsObject) - : super.withBasics(PrimaryRole.generic, semanticsObject); + GenericRole(SemanticsObject semanticsObject) : super.withBasics(PrimaryRole.generic, semanticsObject); @override void update() { @@ -964,8 +940,7 @@ class SemanticsObject { String? _increasedValue; /// See [ui.SemanticsUpdateBuilder.updateNode] - List? get increasedValueAttributes => - _increasedValueAttributes; + List? get increasedValueAttributes => _increasedValueAttributes; List? _increasedValueAttributes; static const int _increasedValueIndex = 1 << 13; @@ -982,8 +957,7 @@ class SemanticsObject { String? _decreasedValue; /// See [ui.SemanticsUpdateBuilder.updateNode] - List? get decreasedValueAttributes => - _decreasedValueAttributes; + List? get decreasedValueAttributes => _decreasedValueAttributes; List? _decreasedValueAttributes; static const int _decreasedValueIndex = 1 << 14; @@ -1152,7 +1126,6 @@ class SemanticsObject { assert(owner.phase == SemanticsUpdatePhase.postUpdate); return _parent; } - SemanticsObject? _parent; /// Whether this node currently has a given [SemanticsFlag]. @@ -1190,18 +1163,16 @@ class SemanticsObject { hasAction(ui.SemanticsAction.scrollRight); /// Whether this object represents a scrollable area in any direction. - bool get isScrollContainer => - isVerticalScrollContainer || isHorizontalScrollContainer; + bool get isScrollContainer => isVerticalScrollContainer || isHorizontalScrollContainer; /// Whether this object has a non-empty list of children. bool get hasChildren => - _childrenInTraversalOrder != null && - _childrenInTraversalOrder!.isNotEmpty; + _childrenInTraversalOrder != null && _childrenInTraversalOrder!.isNotEmpty; /// Whether this object represents an editable text field. bool get isTextField => hasFlag(ui.SemanticsFlag.isTextField); - /// Whether this object represents an editable text field. + /// Whether this object represents an editable text field. bool get isLink => hasFlag(ui.SemanticsFlag.isLink); /// Whether this object needs screen readers attention right away. @@ -1211,7 +1182,9 @@ class SemanticsObject { /// Whether this object represents an image with no tappable functionality. bool get isVisualOnly => - hasFlag(ui.SemanticsFlag.isImage) && !isTappable && !isButton; + hasFlag(ui.SemanticsFlag.isImage) && + !isTappable && + !isButton; /// Whether this node defines a scope for a route. /// @@ -1418,7 +1391,8 @@ class SemanticsObject { /// z-index CSS style attribute. void updateChildren() { // Trivial case: remove all children. - if (_childrenInHitTestOrder == null || _childrenInHitTestOrder!.isEmpty) { + if (_childrenInHitTestOrder == null || + _childrenInHitTestOrder!.isEmpty) { if (_currentChildrenInRenderOrder == null || _currentChildrenInRenderOrder!.isEmpty) { // A container element must not have been created when child list is empty. @@ -1453,8 +1427,7 @@ class SemanticsObject { // is determined by the DOM order of elements. final List childrenInRenderOrder = []; for (int i = 0; i < childCount; i++) { - childrenInRenderOrder - .add(owner._semanticsTree[childrenInTraversalOrder[i]]!); + childrenInRenderOrder.add(owner._semanticsTree[childrenInTraversalOrder[i]]!); } // The z-index determines hit testing. Technically, it also affects paint @@ -1467,8 +1440,7 @@ class SemanticsObject { final bool zIndexMatters = childCount > 1; if (zIndexMatters) { for (int i = 0; i < childCount; i++) { - final SemanticsObject child = - owner._semanticsTree[childrenInHitTestOrder[i]]!; + final SemanticsObject child = owner._semanticsTree[childrenInHitTestOrder[i]]!; // Invert the z-index because hit-test order is inverted with respect to // paint order. @@ -1488,8 +1460,7 @@ class SemanticsObject { } // At this point it is guaranteed to have had a non-empty previous child list. - final List previousChildrenInRenderOrder = - _currentChildrenInRenderOrder!; + final List previousChildrenInRenderOrder = _currentChildrenInRenderOrder!; final int previousCount = previousChildrenInRenderOrder.length; // Both non-empty case. @@ -1526,8 +1497,7 @@ class SemanticsObject { } // Trivial case: child lists are identical both in length and order => do nothing. - if (previousCount == childrenInRenderOrder.length && - newIndex == childCount) { + if (previousCount == childrenInRenderOrder.length && newIndex == childCount) { return; } @@ -1546,13 +1516,12 @@ class SemanticsObject { // The longest sub-sequence in the old list maximizes the number of children // that do not need to be moved. - final List longestSequence = - longestIncreasingSubsequence(intersectionIndicesOld); + final List longestSequence = longestIncreasingSubsequence(intersectionIndicesOld); final List stationaryIds = []; for (int i = 0; i < longestSequence.length; i += 1) { - stationaryIds.add(previousChildrenInRenderOrder[ - intersectionIndicesOld[longestSequence[i]!]] - .id); + stationaryIds.add( + previousChildrenInRenderOrder[intersectionIndicesOld[longestSequence[i]!]].id + ); } // Remove children that are no longer in the list. @@ -1811,8 +1780,7 @@ class SemanticsObject { /// /// Unlike [visitDepthFirstInTraversalOrder] this method can traverse /// partially updated, incomplete, or inconsistent tree. - void _debugVisitRenderedSemanticNodesDepthFirst( - void Function(SemanticsObject) callback) { + void _debugVisitRenderedSemanticNodesDepthFirst(void Function(SemanticsObject) callback) { callback(this); _currentChildrenInRenderOrder?.forEach((SemanticsObject child) { child._debugVisitRenderedSemanticNodesDepthFirst(callback); @@ -1826,13 +1794,11 @@ class SemanticsObject { /// the callback returns true, continues visiting descendants. Otherwise, /// stops immediately after visiting the node that caused the callback to /// return false. - void visitDepthFirstInTraversalOrder( - bool Function(SemanticsObject) callback) { + void visitDepthFirstInTraversalOrder(bool Function(SemanticsObject) callback) { _visitDepthFirstInTraversalOrder(callback); } - bool _visitDepthFirstInTraversalOrder( - bool Function(SemanticsObject) callback) { + bool _visitDepthFirstInTraversalOrder(bool Function(SemanticsObject) callback) { final bool shouldContinueVisiting = callback(this); if (!shouldContinueVisiting) { @@ -1983,10 +1949,9 @@ class EngineSemantics { if (value == _semanticsEnabled) { return; } - final EngineAccessibilityFeatures original = EnginePlatformDispatcher - .instance - .configuration - .accessibilityFeatures as EngineAccessibilityFeatures; + final EngineAccessibilityFeatures original = + EnginePlatformDispatcher.instance.configuration.accessibilityFeatures + as EngineAccessibilityFeatures; final PlatformConfiguration newConfiguration = EnginePlatformDispatcher.instance.configuration.copyWith( accessibilityFeatures: @@ -2003,8 +1968,7 @@ class EngineSemantics { _gestureMode = GestureMode.pointerEvents; _notifyGestureModeListeners(); } - for (final EngineFlutterView view - in EnginePlatformDispatcher.instance.views) { + for (final EngineFlutterView view in EnginePlatformDispatcher.instance.views) { view.semantics.reset(); } _gestureModeClock?.datetime = null; @@ -2162,8 +2126,7 @@ class EngineSemantics { /// Callbacks are called synchronously. HTML DOM updates made in a callback /// take effect in the current animation frame and/or the current message loop /// event. - final List _gestureModeListeners = - []; + final List _gestureModeListeners = []; /// Calls the [callback] every time the current [GestureMode] changes. /// @@ -2260,8 +2223,7 @@ class EngineSemanticsOwner { /// Attachments take precedence over detachments (see [_detachObject]). This /// allows the same node to be detached from one parent in the tree and /// reattached to another parent. - void _attachObject( - {required SemanticsObject parent, required SemanticsObject child}) { + void _attachObject({required SemanticsObject parent, required SemanticsObject child}) { child._parent = parent; _attachments[child.id] = parent; } @@ -2332,7 +2294,6 @@ class EngineSemanticsOwner { _phase = SemanticsUpdatePhase.postUpdate; try { if (_oneTimePostUpdateCallbacks.isNotEmpty) { - print('CALLING ONETIME POSTUPDATE CALLBACKS!'); for (final ui.VoidCallback callback in _oneTimePostUpdateCallbacks) { callback(); } @@ -2376,14 +2337,12 @@ class EngineSemanticsOwner { final SemanticsObject? root = _semanticsTree[0]; if (root != null) { root._debugVisitRenderedSemanticNodesDepthFirst((SemanticsObject child) { - liveIds[child.id] = - child._childrenInTraversalOrder?.toList() ?? const []; + liveIds[child.id] = child._childrenInTraversalOrder?.toList() ?? const []; }); } final bool isConsistent = _semanticsTree.keys.every(liveIds.keys.contains); - final String heading = - 'The semantics node map is ${isConsistent ? 'consistent' : 'inconsistent'}'; + final String heading = 'The semantics node map is ${isConsistent ? 'consistent' : 'inconsistent'}'; final StringBuffer message = StringBuffer('$heading:\n'); message.writeln(' Nodes in tree:'); for (final MapEntry> entry in liveIds.entries) { @@ -2440,8 +2399,7 @@ class EngineSemanticsOwner { // Validate that the node map only contains live elements, i.e. descendants // of the root node. If a node is not reachable from the root, it should // have been removed from the map. - final (bool isConsistent, String description) = - _computeNodeMapConsistencyMessage(); + final (bool isConsistent, String description) = _computeNodeMapConsistencyMessage(); if (!isConsistent) { // Use StateError because AssertionError escapes line breaks, but this // error message is very detailed and it needs line breaks for diff --git a/lib/web_ui/test/engine/semantics/semantics_test.dart b/lib/web_ui/test/engine/semantics/semantics_test.dart index df997aac472b8..d751eb589f075 100644 --- a/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -1545,18 +1545,15 @@ void _testIncrementables() { pumpSemantics(isFocused: false); final DomElement element = owner().debugSemanticsTree![0]!.element.querySelector('input')!; - await Future.delayed(Duration.zero); expect(capturedActions, isEmpty); pumpSemantics(isFocused: true); - await Future.delayed(Duration.zero); expect(capturedActions, [ (0, ui.SemanticsAction.didGainAccessibilityFocus, null), ]); capturedActions.clear(); pumpSemantics(isFocused: false); - await Future.delayed(Duration.zero); expect( reason: 'The engine never calls blur() explicitly.', capturedActions, diff --git a/lib/web_ui/test/engine/semantics/semantics_tester.dart b/lib/web_ui/test/engine/semantics/semantics_tester.dart index 0de7a2a1e6c4c..ff49a461e7ef1 100644 --- a/lib/web_ui/test/engine/semantics/semantics_tester.dart +++ b/lib/web_ui/test/engine/semantics/semantics_tester.dart @@ -306,15 +306,11 @@ class SemanticsTester { value: value ?? '', valueAttributes: valueAttributes ?? const [], increasedValue: increasedValue ?? '', - increasedValueAttributes: - increasedValueAttributes ?? const [], + increasedValueAttributes: increasedValueAttributes ?? const [], decreasedValue: decreasedValue ?? '', - decreasedValueAttributes: - decreasedValueAttributes ?? const [], + decreasedValueAttributes: decreasedValueAttributes ?? const [], tooltip: tooltip ?? '', - transform: transform != null - ? toMatrix32(transform) - : Matrix4.identity().storage, + transform: transform != null ? toMatrix32(transform) : Matrix4.identity().storage, elevation: elevation ?? 0, thickness: thickness ?? 0, childrenInTraversalOrder: childIds, @@ -353,9 +349,7 @@ class SemanticsTester { void expectSemanticsTree(EngineSemanticsOwner owner, String semanticsHtml) { const List ignoredStyleProperties = ['pointer-events']; expect( - canonicalizeHtml( - owner.semanticsHost.querySelector('flt-semantics')!.outerHTML!, - ignoredStyleProperties: ignoredStyleProperties), + canonicalizeHtml(owner.semanticsHost.querySelector('flt-semantics')!.outerHTML!, ignoredStyleProperties: ignoredStyleProperties), canonicalizeHtml(semanticsHtml), ); } @@ -365,8 +359,8 @@ DomElement findScrollable(EngineSemanticsOwner owner) { return owner.semanticsHost.querySelectorAll('flt-semantics').singleWhere( (DomElement? element) { return element!.style.overflow == 'hidden' || - element.style.overflowY == 'scroll' || - element.style.overflowX == 'scroll'; + element.style.overflowY == 'scroll' || + element.style.overflowX == 'scroll'; }, ); } From 2bd517c49afaf20933b51156a00d53eff0bb73a6 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Wed, 10 Jan 2024 16:15:13 -0800 Subject: [PATCH 05/18] Implement dispose and max cache bytes --- .../canvaskit/multi_surface_rasterizer.dart | 24 ++++++++++++------- .../offscreen_canvas_rasterizer.dart | 20 +++++++++------- .../canvaskit/overlay_canvas_factory.dart | 8 +++++++ .../lib/src/engine/canvaskit/rasterizer.dart | 5 +++- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart index 0e592d15c295e..d70dd110a20fc 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart @@ -14,17 +14,30 @@ import 'package:ui/ui.dart' as ui; class MultiSurfaceRasterizer extends Rasterizer { @override MultiSurfaceViewRasterizer createViewRasterizer(EngineFlutterView view) { - return MultiSurfaceViewRasterizer(view, this); + return _viewRasterizers.putIfAbsent( + view, () => MultiSurfaceViewRasterizer(view, this)); } + final Map _viewRasterizers = + {}; + @override void dispose() { - // TODO(harryterkelsen): implement dispose + for (final MultiSurfaceViewRasterizer viewRasterizer + in _viewRasterizers.values) { + viewRasterizer.dispose(); + } + _viewRasterizers.clear(); } @override void setResourceCacheMaxBytes(int bytes) { - // TODO(harryterkelsen): implement setResourceCacheMaxBytes + for (final MultiSurfaceViewRasterizer viewRasterizer + in _viewRasterizers.values) { + viewRasterizer.overlayFactory.forEachCanvas((Surface surface) { + surface.setSkiaResourceCacheMaxBytes(bytes); + }); + } } } @@ -38,11 +51,6 @@ class MultiSurfaceViewRasterizer extends ViewRasterizer { OverlayCanvasFactory( createCanvas: () => Surface(isRenderCanvas: true)); - @override - void dispose() { - // TODO: implement dispose - } - @override void prepareToDraw() { overlayFactory.baseCanvas.createOrUpdateSurface(currentFrameSize); diff --git a/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart index 51b9c9e4edf87..b3fb38d127d0d 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart @@ -14,17 +14,25 @@ class OffscreenCanvasRasterizer extends Rasterizer { @override OffscreenCanvasViewRasterizer createViewRasterizer(EngineFlutterView view) { - return OffscreenCanvasViewRasterizer(view, this); + return _viewRasterizers.putIfAbsent( + view, () => OffscreenCanvasViewRasterizer(view, this)); } + final Map _viewRasterizers = + {}; + @override void setResourceCacheMaxBytes(int bytes) { - // TODO(harryterkelsen): Implement. + offscreenSurface.setSkiaResourceCacheMaxBytes(bytes); } @override void dispose() { - // TODO(harryterkelsen): Implement. + offscreenSurface.dispose(); + for (final OffscreenCanvasViewRasterizer viewRasterizer + in _viewRasterizers.values) { + viewRasterizer.dispose(); + } } } @@ -48,12 +56,6 @@ class OffscreenCanvasViewRasterizer extends ViewRasterizer { ); } - @override - void dispose() { - viewEmbedder.dispose(); - overlayFactory.dispose(); - } - @override void prepareToDraw() { rasterizer.offscreenSurface.createOrUpdateSurface(currentFrameSize); diff --git a/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart b/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart index 2e7a9f69cdffe..8adf4aa282a9e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart @@ -74,6 +74,14 @@ class OverlayCanvasFactory { /// the new surfaces. void removeSurfacesFromDom() { _cache.forEach(_removeFromDom); + _liveCanvases.forEach(_removeFromDom); + } + + /// Calls [callback] on each canvas created by this factory. + void forEachCanvas(void Function(T canvas) callback) { + callback(baseCanvas); + _cache.forEach(callback); + _liveCanvases.forEach(callback); } // Removes [canvas] from the DOM. diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart index f35310638cb1e..0de48c9237366 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart @@ -95,7 +95,10 @@ abstract class ViewRasterizer { } /// Disposes this rasterizer. - void dispose(); + void dispose() { + viewEmbedder.dispose(); + overlayFactory.dispose(); + } } abstract class OverlayCanvas { From 9a6a87a3bbd007606e6d1a8e06cc87adae39e27a Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Wed, 10 Jan 2024 16:30:48 -0800 Subject: [PATCH 06/18] fix licenses --- ci/licenses_golden/licenses_flutter | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 8ab8045245fe0..fe92079ef14f5 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -5924,8 +5924,11 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart + ../../ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/native_memory.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path_metrics.dart + ../../../flutter/LICENSE @@ -5935,7 +5938,6 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/platform_message.da ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/raster_cache.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/render_canvas_factory.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/shader.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/surface.dart + ../../../flutter/LICENSE @@ -8757,8 +8759,11 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/native_memory.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path_metrics.dart @@ -8768,7 +8773,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/platform_message.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/raster_cache.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/render_canvas_factory.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/shader.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/surface.dart From 408d8f6d3270555916bb7b21de8da91f133837e1 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 11 Jan 2024 13:48:39 -0800 Subject: [PATCH 07/18] Offset surface --- .../canvaskit/multi_surface_rasterizer.dart | 1 + .../lib/src/engine/canvaskit/surface.dart | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart index d70dd110a20fc..a886be50c56f3 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart @@ -61,6 +61,7 @@ class MultiSurfaceViewRasterizer extends ViewRasterizer { OverlayCanvas canvas, List pictures) { final Surface surface = canvas as Surface; surface.createOrUpdateSurface(currentFrameSize); + surface.positionToShowFrame(currentFrameSize); final CkCanvas skCanvas = surface.getCanvas(); skCanvas.clear(const ui.Color(0x00000000)); pictures.forEach(skCanvas.drawPicture); diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index db50914dbc810..33c788b5bbcf5 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -204,6 +204,24 @@ class Surface extends OverlayCanvas { _currentDevicePixelRatio = devicePixelRatio; } + /// The element backing this surface may be larger than the screen. + /// The Surface will draw the frame to the bottom left of the , but + /// the is, by default, positioned so that the top left corner is in + /// the top left of the window. We need to shift the canvas down so that the + /// bottom left of the is the the bottom left corner of the window. + void positionToShowFrame(ui.Size frameSize) { + assert(isRenderCanvas, + 'Should not position Surface if not used as a render canvas'); + final double devicePixelRatio = + EngineFlutterDisplay.instance.devicePixelRatio; + final double logicalHeight = _pixelHeight / devicePixelRatio; + final double logicalFrameHeight = frameSize.height / devicePixelRatio; + + // Shift the canvas up so the bottom left is in the window. + _canvasElement!.style.transform = + 'translate(0px, ${logicalFrameHeight - logicalHeight}px)'; + } + /// This is only valid after the first frame or if [ensureSurface] has been /// called bool get usingSoftwareBackend => From ef19f8f422cde3307923cf1e0afc1fbfaf77d6e9 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 11 Jan 2024 16:54:11 -0800 Subject: [PATCH 08/18] Rename OverlayCanvas to DisplayCanvas --- lib/web_ui/lib/src/engine.dart | 2 +- ...ctory.dart => display_canvas_factory.dart} | 20 +++---- .../src/engine/canvaskit/embedded_views.dart | 22 +++---- .../canvaskit/multi_surface_rasterizer.dart | 14 ++--- .../offscreen_canvas_rasterizer.dart | 8 +-- .../lib/src/engine/canvaskit/rasterizer.dart | 36 ++++++----- .../src/engine/canvaskit/render_canvas.dart | 8 +-- .../lib/src/engine/canvaskit/surface.dart | 22 +++---- ....dart => display_canvas_factory_test.dart} | 59 ++++++++++++------- 9 files changed, 107 insertions(+), 84 deletions(-) rename lib/web_ui/lib/src/engine/canvaskit/{overlay_canvas_factory.dart => display_canvas_factory.dart} (88%) rename lib/web_ui/test/canvaskit/{overlay_canvas_factory_test.dart => display_canvas_factory_test.dart} (60%) diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index b65ee50964a38..500392ac9ac22 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -23,6 +23,7 @@ export 'engine/canvaskit/canvas.dart'; export 'engine/canvaskit/canvaskit_api.dart'; export 'engine/canvaskit/canvaskit_canvas.dart'; export 'engine/canvaskit/color_filter.dart'; +export 'engine/canvaskit/display_canvas_factory.dart'; export 'engine/canvaskit/embedded_views.dart'; export 'engine/canvaskit/embedded_views_diff.dart'; export 'engine/canvaskit/fonts.dart'; @@ -38,7 +39,6 @@ export 'engine/canvaskit/multi_surface_rasterizer.dart'; export 'engine/canvaskit/n_way_canvas.dart'; export 'engine/canvaskit/native_memory.dart'; export 'engine/canvaskit/offscreen_canvas_rasterizer.dart'; -export 'engine/canvaskit/overlay_canvas_factory.dart'; export 'engine/canvaskit/painting.dart'; export 'engine/canvaskit/path.dart'; export 'engine/canvaskit/path_metrics.dart'; diff --git a/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart b/lib/web_ui/lib/src/engine/canvaskit/display_canvas_factory.dart similarity index 88% rename from lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart rename to lib/web_ui/lib/src/engine/canvaskit/display_canvas_factory.dart index 8adf4aa282a9e..93807b60bb596 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/display_canvas_factory.dart @@ -5,9 +5,9 @@ import 'package:meta/meta.dart'; import '../../engine.dart'; -/// Caches canvases used to overlay platform views. -class OverlayCanvasFactory { - OverlayCanvasFactory({required this.createCanvas}) { +/// Caches canvases used to display Skia-drawn content. +class DisplayCanvasFactory { + DisplayCanvasFactory({required this.createCanvas}) { assert(() { registerHotRestartListener(dispose); return true; @@ -15,7 +15,7 @@ class OverlayCanvasFactory { } /// A function which is passed in as a constructor parameter which is used to - /// create new overlay canvases. + /// create new display canvases. final T Function() createCanvas; /// The base canvas to paint on. This is the default canvas which will be @@ -42,7 +42,7 @@ class OverlayCanvasFactory { /// Useful in tests. int get debugCacheSize => _cache.length; - /// Gets an overlay canvas from the cache or creates a new one if there are + /// Gets a display canvas from the cache or creates a new one if there are /// none in the cache. T getCanvas() { if (_cache.isNotEmpty) { @@ -68,11 +68,11 @@ class OverlayCanvasFactory { _liveCanvases.clear(); } - /// Removes all surfaces except the base surface from the DOM. + /// Removes all canvases except the base canvas from the DOM. /// /// This is called at the beginning of the frame to prepare for painting into - /// the new surfaces. - void removeSurfacesFromDom() { + /// the new canvases. + void removeCanvasesFromDom() { _cache.forEach(_removeFromDom); _liveCanvases.forEach(_removeFromDom); } @@ -86,7 +86,7 @@ class OverlayCanvasFactory { // Removes [canvas] from the DOM. void _removeFromDom(T canvas) { - canvas.htmlElement.remove(); + canvas.hostElement.remove(); } /// Signals that a canvas is no longer being used. It can be reused. @@ -96,7 +96,7 @@ class OverlayCanvasFactory { _liveCanvases.contains(canvas), 'Attempting to release a Canvas which ' 'was not created by this factory'); - canvas.htmlElement.remove(); + canvas.hostElement.remove(); _liveCanvases.remove(canvas); _cache.add(canvas); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 092adbf1645c1..bb2b89b3ae0b9 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -50,7 +50,7 @@ class HtmlViewEmbedder { static const int maximumOverlays = 7; /// Canvases used to draw on top of platform views, keyed by platform view ID. - final Map _overlays = {}; + final Map _overlays = {}; /// The views that need to be recomposited into the scene on the next frame. final Set _viewsToRecomposite = {}; @@ -378,7 +378,7 @@ class HtmlViewEmbedder { int pictureRecorderIndex = 0; for (final OverlayGroup overlayGroup in _activeOverlayGroups) { - final OverlayCanvas overlay = _overlays[overlayGroup.last]!; + final DisplayCanvas overlay = _overlays[overlayGroup.last]!; final List pictures = []; for (int i = 0; i < overlayGroup.visibleCount; i++) { pictures.add( @@ -438,17 +438,17 @@ class HtmlViewEmbedder { if (diffResult.addToBeginning) { final DomElement platformViewRoot = _viewClipChains[viewId]!.root; sceneHost.insertBefore(platformViewRoot, elementToInsertBefore); - final OverlayCanvas? overlay = _overlays[viewId]; + final DisplayCanvas? overlay = _overlays[viewId]; if (overlay != null) { sceneHost.insertBefore( - overlay.htmlElement, elementToInsertBefore); + overlay.hostElement, elementToInsertBefore); } } else { final DomElement platformViewRoot = _viewClipChains[viewId]!.root; sceneHost.append(platformViewRoot); - final OverlayCanvas? overlay = _overlays[viewId]; + final DisplayCanvas? overlay = _overlays[viewId]; if (overlay != null) { - sceneHost.append(overlay.htmlElement); + sceneHost.append(overlay.hostElement); } } } @@ -457,7 +457,7 @@ class HtmlViewEmbedder { for (int i = 0; i < _compositionOrder.length; i++) { final int view = _compositionOrder[i]; if (_overlays[view] != null) { - final DomElement overlayElement = _overlays[view]!.htmlElement; + final DomElement overlayElement = _overlays[view]!.hostElement; if (!overlayElement.isConnected!) { // This overlay wasn't added to the DOM. if (i == _compositionOrder.length - 1) { @@ -489,10 +489,10 @@ class HtmlViewEmbedder { } final DomElement platformViewRoot = _viewClipChains[viewId]!.root; - final OverlayCanvas? overlay = _overlays[viewId]; + final DisplayCanvas? overlay = _overlays[viewId]; sceneHost.append(platformViewRoot); if (overlay != null) { - sceneHost.append(overlay.htmlElement); + sceneHost.append(overlay.hostElement); } _activeCompositionOrder.add(viewId); unusedViews.remove(viewId); @@ -525,7 +525,7 @@ class HtmlViewEmbedder { void _releaseOverlay(int viewId) { if (_overlays[viewId] != null) { - final OverlayCanvas overlay = _overlays[viewId]!; + final DisplayCanvas overlay = _overlays[viewId]!; rasterizer.releaseOverlay(overlay); _overlays.remove(viewId); } @@ -636,7 +636,7 @@ class HtmlViewEmbedder { assert(!_overlays.containsKey(viewId)); // Try reusing a cached overlay created for another platform view. - final OverlayCanvas overlay = rasterizer.getOverlay(); + final DisplayCanvas overlay = rasterizer.getOverlay(); _overlays[viewId] = overlay; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart index a886be50c56f3..f3b2d78405bf1 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart @@ -10,7 +10,7 @@ import 'package:ui/ui.dart' as ui; /// how many WebGL contexts can be live at one time as well as bugs in sharing /// GL resources between the contexts. However, using [createImageBitmap] is /// currently very slow on Firefox and Safari browsers, so directly rendering -/// to several +/// to several [Surface]s is how we can achieve 60 fps on these browsers. class MultiSurfaceRasterizer extends Rasterizer { @override MultiSurfaceViewRasterizer createViewRasterizer(EngineFlutterView view) { @@ -34,7 +34,7 @@ class MultiSurfaceRasterizer extends Rasterizer { void setResourceCacheMaxBytes(int bytes) { for (final MultiSurfaceViewRasterizer viewRasterizer in _viewRasterizers.values) { - viewRasterizer.overlayFactory.forEachCanvas((Surface surface) { + viewRasterizer.displayFactory.forEachCanvas((Surface surface) { surface.setSkiaResourceCacheMaxBytes(bytes); }); } @@ -47,18 +47,18 @@ class MultiSurfaceViewRasterizer extends ViewRasterizer { final MultiSurfaceRasterizer rasterizer; @override - final OverlayCanvasFactory overlayFactory = - OverlayCanvasFactory( - createCanvas: () => Surface(isRenderCanvas: true)); + final DisplayCanvasFactory displayFactory = + DisplayCanvasFactory( + createCanvas: () => Surface(isDisplayCanvas: true)); @override void prepareToDraw() { - overlayFactory.baseCanvas.createOrUpdateSurface(currentFrameSize); + displayFactory.baseCanvas.createOrUpdateSurface(currentFrameSize); } @override Future rasterizeToCanvas( - OverlayCanvas canvas, List pictures) { + DisplayCanvas canvas, List pictures) { final Surface surface = canvas as Surface; surface.createOrUpdateSurface(currentFrameSize); surface.positionToShowFrame(currentFrameSize); diff --git a/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart index b3fb38d127d0d..585575391a1cb 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart @@ -5,7 +5,7 @@ import 'package:ui/src/engine.dart'; /// A [Rasterizer] that uses a single GL context in an OffscreenCanvas to do -/// all the rendering. It transers bitmaps created in the OffscreenCanvas to +/// all the rendering. It transfers bitmaps created in the OffscreenCanvas to /// one or many on-screen elements to actually display the scene. class OffscreenCanvasRasterizer extends Rasterizer { /// This is an SkSurface backed by an OffScreenCanvas. This single Surface is @@ -42,13 +42,13 @@ class OffscreenCanvasViewRasterizer extends ViewRasterizer { final OffscreenCanvasRasterizer rasterizer; @override - final OverlayCanvasFactory overlayFactory = - OverlayCanvasFactory(createCanvas: () => RenderCanvas()); + final DisplayCanvasFactory displayFactory = + DisplayCanvasFactory(createCanvas: () => RenderCanvas()); /// Render the given [pictures] so it is displayed by the given [canvas]. @override Future rasterizeToCanvas( - OverlayCanvas canvas, List pictures) async { + DisplayCanvas canvas, List pictures) async { await rasterizer.offscreenSurface.rasterizeToCanvas( currentFrameSize, canvas as RenderCanvas, diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart index 0de48c9237366..650b5bea80ea3 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart @@ -32,7 +32,7 @@ abstract class ViewRasterizer { late final HtmlViewEmbedder viewEmbedder = HtmlViewEmbedder(sceneHost, this); /// A factory for creating overlays. - OverlayCanvasFactory get overlayFactory; + DisplayCanvasFactory get displayFactory; /// The scene host which this rasterizer should raster into. DomElement get sceneHost => view.dom.sceneHost; @@ -57,9 +57,9 @@ abstract class ViewRasterizer { compositorFrame.raster(layerTree, ignoreRasterCache: true); - sceneHost.prepend(overlayFactory.baseCanvas.htmlElement); + sceneHost.prepend(displayFactory.baseCanvas.hostElement); await rasterizeToCanvas( - overlayFactory.baseCanvas, [pictureRecorder.endRecording()]); + displayFactory.baseCanvas, [pictureRecorder.endRecording()]); await viewEmbedder.submitFrame(); } @@ -72,37 +72,45 @@ abstract class ViewRasterizer { /// Rasterize the [pictures] to the given [canvas]. Future rasterizeToCanvas( - OverlayCanvas canvas, List pictures); + DisplayCanvas canvas, List pictures); - /// Get a [OverlayCanvas] to use as an overlay. - OverlayCanvas getOverlay() { - return overlayFactory.getCanvas(); + /// Get a [DisplayCanvas] to use as an overlay. + DisplayCanvas getOverlay() { + return displayFactory.getCanvas(); } /// Release the given [overlay] so it may be reused. - void releaseOverlay(OverlayCanvas overlay) { - overlayFactory.releaseCanvas(overlay); + void releaseOverlay(DisplayCanvas overlay) { + displayFactory.releaseCanvas(overlay); } /// Release all overlays. void releaseOverlays() { - overlayFactory.releaseCanvases(); + displayFactory.releaseCanvases(); } /// Remove all overlays that have been created from the DOM. void removeOverlaysFromDom() { - overlayFactory.removeSurfacesFromDom(); + displayFactory.removeCanvasesFromDom(); } /// Disposes this rasterizer. void dispose() { viewEmbedder.dispose(); - overlayFactory.dispose(); + displayFactory.dispose(); } } -abstract class OverlayCanvas { - DomElement get htmlElement; +/// A [DisplayCanvas] is an abstraction for a canvas element which displays +/// Skia-drawn pictures to the screen. They are also sometimes called "overlays" +/// because they can be overlaid on top of platform views, which are HTML +/// content that isn't rendered by Skia. +/// +/// [DisplayCanvas]es are drawn into with [ViewRasterizer.rasterizeToCanvas]. +abstract class DisplayCanvas { + /// The DOM element which, when appended to the scene host, will display the + /// Skia-rendered content to the screen. + DomElement get hostElement; /// Whether or not this overlay canvas is attached to the DOM. bool get isConnected; diff --git a/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart index 231f0b673c6db..8f5f1fc40086a 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart @@ -27,12 +27,12 @@ import 'rasterizer.dart'; /// on the maximum amount of WebGL contexts which can be live at once. Using /// a single OffscreenCanvas and multiple RenderCanvases allows us to only /// create a single WebGL context. -class RenderCanvas extends OverlayCanvas { +class RenderCanvas extends DisplayCanvas { RenderCanvas() { canvasElement.setAttribute('aria-hidden', 'true'); canvasElement.style.position = 'absolute'; _updateLogicalHtmlCanvasSize(); - htmlElement.append(canvasElement); + hostElement.append(canvasElement); } /// The root HTML element for this canvas. @@ -45,7 +45,7 @@ class RenderCanvas extends OverlayCanvas { /// example, when the screen size changes, or when the WebGL context is lost /// due to the browser tab becoming dormant. @override - final DomElement htmlElement = createDomElement('flt-canvas-container'); + final DomElement hostElement = createDomElement('flt-canvas-container'); /// The underlying `` element used to display the pixels. final DomCanvasElement canvasElement = createDomCanvasElement(); @@ -144,6 +144,6 @@ class RenderCanvas extends OverlayCanvas { @override void dispose() { - htmlElement.remove(); + hostElement.remove(); } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 33c788b5bbcf5..8b1c7d7644a61 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -49,18 +49,18 @@ class SurfaceFrame { /// The underlying representation is a [CkSurface], which can be reused by /// successive frames if they are the same size. Otherwise, a new [CkSurface] is /// created. -class Surface extends OverlayCanvas { - Surface({this.isRenderCanvas = false}) +class Surface extends DisplayCanvas { + Surface({this.isDisplayCanvas = false}) : useOffscreenCanvas = - Surface.offscreenCanvasSupported && !isRenderCanvas; + Surface.offscreenCanvasSupported && !isDisplayCanvas; CkSurface? _surface; /// Whether or not to use an `OffscreenCanvas` to back this [Surface]. final bool useOffscreenCanvas; - /// If `true`, this [Surface] is used for rendering. - final bool isRenderCanvas; + /// If `true`, this [Surface] is used as a [DisplayCanvas]. + final bool isDisplayCanvas; /// If true, forces a new WebGL context to be created, even if the window /// size is the same. This is used to restore the UI after the browser tab @@ -103,7 +103,7 @@ class Surface extends OverlayCanvas { /// Note, if this getter is called, then this Surface is being used as an /// overlay and must be backed by an onscreen element. @override - final DomElement htmlElement = createDomElement('flt-canvas-container'); + final DomElement hostElement = createDomElement('flt-canvas-container'); int _pixelWidth = -1; int _pixelHeight = -1; @@ -210,7 +210,7 @@ class Surface extends OverlayCanvas { /// the top left of the window. We need to shift the canvas down so that the /// bottom left of the is the the bottom left corner of the window. void positionToShowFrame(ui.Size frameSize) { - assert(isRenderCanvas, + assert(isDisplayCanvas, 'Should not position Surface if not used as a render canvas'); final double devicePixelRatio = EngineFlutterDisplay.instance.devicePixelRatio; @@ -263,7 +263,7 @@ class Surface extends OverlayCanvas { size.height == previousSurfaceSize.height) { final double devicePixelRatio = EngineFlutterDisplay.instance.devicePixelRatio; - if (isRenderCanvas && devicePixelRatio != _currentDevicePixelRatio) { + if (isDisplayCanvas && devicePixelRatio != _currentDevicePixelRatio) { _updateLogicalHtmlCanvasSize(); } return _surface!; @@ -288,7 +288,7 @@ class Surface extends OverlayCanvas { _currentCanvasPhysicalSize = newSize; _pixelWidth = newSize.width.ceil(); _pixelHeight = newSize.height.ceil(); - if (isRenderCanvas) { + if (isDisplayCanvas) { _updateLogicalHtmlCanvasSize(); } } @@ -387,10 +387,10 @@ class Surface extends OverlayCanvas { htmlCanvas = canvas; _canvasElement = canvas; _offscreenCanvas = null; - if (isRenderCanvas) { + if (isDisplayCanvas) { _canvasElement!.setAttribute('aria-hidden', 'true'); _canvasElement!.style.position = 'absolute'; - htmlElement.append(_canvasElement!); + hostElement.append(_canvasElement!); _updateLogicalHtmlCanvasSize(); } } diff --git a/lib/web_ui/test/canvaskit/overlay_canvas_factory_test.dart b/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart similarity index 60% rename from lib/web_ui/test/canvaskit/overlay_canvas_factory_test.dart rename to lib/web_ui/test/canvaskit/display_canvas_factory_test.dart index 945c5bbff0bf8..9bfbbb444333e 100644 --- a/lib/web_ui/test/canvaskit/overlay_canvas_factory_test.dart +++ b/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart @@ -5,6 +5,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart'; import 'common.dart'; @@ -12,54 +13,68 @@ void main() { internalBootstrapBrowserTest(() => testMain); } +class DummyDisplayCanvas extends DisplayCanvas { + @override + void dispose() {} + + @override + DomElement get hostElement => throw UnimplementedError(); + + @override + void initialize() {} + + @override + bool get isConnected => throw UnimplementedError(); +} + void testMain() { - group('$OverlayCanvasFactory', () { + group('$DisplayCanvasFactory', () { setUpCanvasKitTest(); test('getCanvas', () { - final OverlayCanvasFactory factory = - OverlayCanvasFactory( - createCanvas: () => RenderCanvas()); + final DisplayCanvasFactory factory = + DisplayCanvasFactory( + createCanvas: () => DummyDisplayCanvas()); expect(factory.baseCanvas, isNotNull); expect(factory.debugSurfaceCount, equals(1)); // Get a canvas from the factory, it should be unique. - final RenderCanvas newCanvas = factory.getCanvas(); + final DisplayCanvas newCanvas = factory.getCanvas(); expect(newCanvas, isNot(equals(factory.baseCanvas))); expect(factory.debugSurfaceCount, equals(2)); // Get another canvas from the factory. Now we are at maximum capacity. - final RenderCanvas anotherCanvas = factory.getCanvas(); + final DisplayCanvas anotherCanvas = factory.getCanvas(); expect(anotherCanvas, isNot(equals(factory.baseCanvas))); expect(factory.debugSurfaceCount, equals(3)); }); test('releaseCanvas', () { - final OverlayCanvasFactory factory = - OverlayCanvasFactory( - createCanvas: () => RenderCanvas()); + final DisplayCanvasFactory factory = + DisplayCanvasFactory( + createCanvas: () => DummyDisplayCanvas()); // Create a new canvas and immediately release it. - final RenderCanvas canvas = factory.getCanvas(); + final DisplayCanvas canvas = factory.getCanvas(); factory.releaseCanvas(canvas); // If we create a new canvas, it should be the same as the one we // just created. - final RenderCanvas newCanvas = factory.getCanvas(); + final DisplayCanvas newCanvas = factory.getCanvas(); expect(newCanvas, equals(canvas)); }); test('isLive', () { - final OverlayCanvasFactory factory = - OverlayCanvasFactory( - createCanvas: () => RenderCanvas()); + final DisplayCanvasFactory factory = + DisplayCanvasFactory( + createCanvas: () => DummyDisplayCanvas()); expect(factory.isLive(factory.baseCanvas), isTrue); - final RenderCanvas canvas = factory.getCanvas(); + final DisplayCanvas canvas = factory.getCanvas(); expect(factory.isLive(canvas), isTrue); factory.releaseCanvas(canvas); @@ -67,28 +82,28 @@ void testMain() { }); test('hot restart', () { - void expectDisposed(OverlayCanvas canvas) { + void expectDisposed(DisplayCanvas canvas) { expect(canvas.isConnected, isFalse); } final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!; - final OverlayCanvasFactory originalFactory = + final DisplayCanvasFactory originalFactory = CanvasKitRenderer.instance .debugGetRasterizerForView(implicitView)! - .overlayFactory; + .displayFactory; // Cause the surface and its canvas to be attached to the page implicitView.dom.sceneHost - .prepend(originalFactory.baseCanvas.htmlElement); + .prepend(originalFactory.baseCanvas.hostElement); expect(originalFactory.baseCanvas.isConnected, isTrue); // Create a few overlay canvases - final List overlays = []; + final List overlays = []; for (int i = 0; i < 3; i++) { - final OverlayCanvas canvas = originalFactory.getCanvas(); - implicitView.dom.sceneHost.prepend(canvas.htmlElement); + final DisplayCanvas canvas = originalFactory.getCanvas(); + implicitView.dom.sceneHost.prepend(canvas.hostElement); overlays.add(canvas); } expect(originalFactory.debugSurfaceCount, 4); From c48f020c260a74a2ec3e95ec3e8115ccd2f52420 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 11 Jan 2024 16:57:04 -0800 Subject: [PATCH 09/18] Revert debug print --- lib/web_ui/lib/src/engine/platform_dispatcher.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index b712566782592..1453da94e0c3d 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -1237,7 +1237,6 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// Otherwise zones won't work properly. void invokeOnSemanticsAction( int nodeId, ui.SemanticsAction action, ByteData? args) { - print('invoking semantics action callback!'); invoke1( _onSemanticsActionEvent, _onSemanticsActionEventZone, From 45103810e29734d1df000bb82655f714126e1c14 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 16 Jan 2024 12:41:29 -0800 Subject: [PATCH 10/18] fix licenses --- ci/licenses_golden/licenses_flutter | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index fe92079ef14f5..a8f80d55686e1 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -5913,6 +5913,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart + ../.. ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/display_canvas_factory.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views_diff.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/fonts.dart + ../../../flutter/LICENSE @@ -5928,7 +5929,6 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/multi_surface_raste ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/native_memory.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path_metrics.dart + ../../../flutter/LICENSE @@ -8748,6 +8748,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/display_canvas_factory.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views_diff.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/fonts.dart @@ -8763,7 +8764,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasteri FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/native_memory.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/offscreen_canvas_rasterizer.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/overlay_canvas_factory.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path_metrics.dart From f046d69f69d903847e5800f60412602fefde88c9 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 16 Jan 2024 13:07:53 -0800 Subject: [PATCH 11/18] delete unused import --- lib/web_ui/test/canvaskit/display_canvas_factory_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart b/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart index 9bfbbb444333e..71ecfda208cd4 100644 --- a/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart +++ b/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart @@ -5,7 +5,6 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -import 'package:ui/ui.dart'; import 'common.dart'; From d4404031cf105abcf8ba01a0d6466913f24c7a88 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 16 Jan 2024 13:41:14 -0800 Subject: [PATCH 12/18] Fix test --- lib/web_ui/test/canvaskit/display_canvas_factory_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart b/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart index 71ecfda208cd4..ea2d4b1ea7fbe 100644 --- a/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart +++ b/lib/web_ui/test/canvaskit/display_canvas_factory_test.dart @@ -16,8 +16,10 @@ class DummyDisplayCanvas extends DisplayCanvas { @override void dispose() {} + final DomElement _element = createDomElement('div'); + @override - DomElement get hostElement => throw UnimplementedError(); + DomElement get hostElement => _element; @override void initialize() {} From 43d2734ffcf768a46bee161cb576d81181477084 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 16 Jan 2024 17:01:58 -0800 Subject: [PATCH 13/18] Fix off-by-one blurriness bug --- .../lib/src/engine/canvaskit/surface.dart | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 8b1c7d7644a61..a6cff70ea730b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -278,16 +278,17 @@ class Surface extends DisplayCanvas { final ui.Size newSize = size * 1.4; _surface?.dispose(); _surface = null; + _pixelWidth = newSize.width.ceil(); + _pixelHeight = newSize.height.ceil(); if (useOffscreenCanvas) { - _offscreenCanvas!.width = newSize.width; - _offscreenCanvas!.height = newSize.height; + _offscreenCanvas!.width = _pixelWidth.toDouble(); + _offscreenCanvas!.height = _pixelHeight.toDouble(); } else { - _canvasElement!.width = newSize.width; - _canvasElement!.height = newSize.height; + _canvasElement!.width = _pixelWidth.toDouble(); + _canvasElement!.height = _pixelHeight.toDouble(); } - _currentCanvasPhysicalSize = newSize; - _pixelWidth = newSize.width.ceil(); - _pixelHeight = newSize.height.ceil(); + _currentCanvasPhysicalSize = + ui.Size(_pixelWidth.toDouble(), _pixelHeight.toDouble()); if (isDisplayCanvas) { _updateLogicalHtmlCanvasSize(); } @@ -477,8 +478,8 @@ class Surface extends DisplayCanvas { } else { final SkSurface? skSurface = canvasKit.MakeOnScreenGLSurface( _grContext!, - size.width.roundToDouble(), - size.height.roundToDouble(), + size.width.ceilToDouble(), + size.height.ceilToDouble(), SkColorSpaceSRGB, _sampleCount, _stencilBits); @@ -565,8 +566,8 @@ class CkSurface { int? get context => _glContext; - int width() => surface.width().round(); - int height() => surface.height().round(); + int width() => surface.width().ceil(); + int height() => surface.height().ceil(); void dispose() { if (_isDisposed) { From 6e92c4367df1cea2c74738c5d7e0da7458265bd7 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Wed, 17 Jan 2024 15:36:54 -0800 Subject: [PATCH 14/18] Add test for proper sizing of Surface --- lib/web_ui/test/canvaskit/surface_test.dart | 195 ++++++++++++++++---- 1 file changed, 155 insertions(+), 40 deletions(-) diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart index 920b9fa40dc52..1dc1e861d07b3 100644 --- a/lib/web_ui/test/canvaskit/surface_test.dart +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:html'; import 'dart:js_util' as js_util; import 'package:test/bootstrap/browser.dart'; @@ -68,7 +69,8 @@ void testMain() { expect(secondIncreaseSurface.height(), 22); // Increases beyond the 40% limit will cause a new allocation. - final CkSurface hugeSurface = surface.acquireFrame(const ui.Size(20, 40)).skiaSurface; + final CkSurface hugeSurface = + surface.acquireFrame(const ui.Size(20, 40)).skiaSurface; final DomOffscreenCanvas huge = surface.debugOffscreenCanvas!; expect(huge, same(secondIncrease)); expect(hugeSurface, isNot(same(secondIncreaseSurface))); @@ -106,6 +108,94 @@ void testMain() { // surfaces. }, skip: isFirefox || !Surface.offscreenCanvasSupported); + test('Surface used as DisplayCanvas resizes correctly', () { + final Surface surface = Surface(isDisplayCanvas: true); + + surface.createOrUpdateSurface(const ui.Size(9, 19)); + final DomCanvasElement original = getDisplayCanvas(surface); + ui.Size canvasSize = getCssSize(surface); + + // Expect exact requested dimensions. + expect(original.width, 9); + expect(original.height, 19); + expect(canvasSize.width, 9); + expect(canvasSize.height, 19); + + // Shrinking reuses the existing canvas but translates it so + // Skia renders into the visible area. + surface.createOrUpdateSurface(const ui.Size(5, 15)); + final DomCanvasElement shrunk = getDisplayCanvas(surface); + canvasSize = getCssSize(surface); + expect(original.width, 9); + expect(original.height, 19); + expect(canvasSize.width, 9); + expect(canvasSize.height, 19); + + // The first increase will allocate a new surface, but will overallocate + // by 40% to accommodate future increases. + surface.createOrUpdateSurface(const ui.Size(10, 20)); + final DomCanvasElement firstIncrease = getDisplayCanvas(surface); + canvasSize = getCssSize(surface); + + expect(firstIncrease, same(original)); + + // Expect overallocated dimensions + expect(firstIncrease.width, 14); + expect(firstIncrease.height, 28); + expect(canvasSize.width, 14); + expect(canvasSize.height, 28); + + // Subsequent increases within 40% reuse the old canvas. + surface.createOrUpdateSurface(const ui.Size(11, 22)); + final DomCanvasElement secondIncrease = getDisplayCanvas(surface); + canvasSize = getCssSize(surface); + + expect(secondIncrease, same(firstIncrease)); + expect(secondIncrease.width, 14); + expect(secondIncrease.height, 28); + expect(canvasSize.width, 14); + expect(canvasSize.height, 28); + + // Increases beyond the 40% limit will cause a new allocation. + surface.createOrUpdateSurface(const ui.Size(20, 40)); + final DomCanvasElement huge = getDisplayCanvas(surface); + canvasSize = getCssSize(surface); + + expect(huge, same(secondIncrease)); + + // Also over-allocated + expect(huge.width, 28); + expect(huge.height, 56); + expect(canvasSize.width, 28); + expect(canvasSize.height, 56); + + // Shrink again. Reuse the last allocated surface. + surface.createOrUpdateSurface(const ui.Size(5, 15)); + final DomCanvasElement shrunk2 = getDisplayCanvas(surface); + canvasSize = getCssSize(surface); + + expect(shrunk2, same(huge)); + expect(shrunk2.width, 28); + expect(shrunk2.height, 56); + expect(canvasSize.width, 28); + expect(canvasSize.height, 56); + + // Doubling the DPR should halve the CSS width, height, and translation of the canvas. + // This tests https://github.com/flutter/flutter/issues/77084 + EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(2.0); + surface.createOrUpdateSurface(const ui.Size(5, 15)); + final DomCanvasElement dpr2Canvas = getDisplayCanvas(surface); + canvasSize = getCssSize(surface); + + expect(dpr2Canvas, same(huge)); + expect(dpr2Canvas.width, 28); + expect(dpr2Canvas.height, 56); + // Canvas is half the size in logical pixels because device pixel ratio is + // 2.0. + expect(canvasSize.width, 14); + expect(canvasSize.height, 28); + }); + test( 'Surface creates new context when WebGL context is restored', () async { @@ -130,17 +220,20 @@ void testMain() { 'getExtension', ['WEBGL_lose_context'], ); - js_util.callMethod(loseContextExtension, 'loseContext', const []); + js_util.callMethod( + loseContextExtension, 'loseContext', const []); // Pump a timer to allow the "lose context" event to propagate. await Future.delayed(Duration.zero); // We don't create a new GL context until the context is restored. expect(surface.debugContextLost, isTrue); - final bool isContextLost = js_util.callMethod(ctx, 'isContextLost', const []); + final bool isContextLost = + js_util.callMethod(ctx, 'isContextLost', const []); expect(isContextLost, isTrue); // Emulate WebGL context restoration. - js_util.callMethod(loseContextExtension, 'restoreContext', const []); + js_util.callMethod( + loseContextExtension, 'restoreContext', const []); // Pump a timer to allow the "restore context" event to propagate. await Future.delayed(Duration.zero); @@ -156,46 +249,68 @@ void testMain() { ); // Regression test for https://github.com/flutter/flutter/issues/75286 - test('updates canvas logical size when device-pixel ratio changes', () { - final Surface surface = Surface(); - final CkSurface original = - surface.acquireFrame(const ui.Size(10, 16)).skiaSurface; + test( + 'updates canvas logical size when device-pixel ratio changes', + () { + final Surface surface = Surface(); + final CkSurface original = + surface.acquireFrame(const ui.Size(10, 16)).skiaSurface; - expect(original.width(), 10); - expect(original.height(), 16); - expect(surface.debugOffscreenCanvas!.width, 10); - expect(surface.debugOffscreenCanvas!.height, 16); + expect(original.width(), 10); + expect(original.height(), 16); + expect(surface.debugOffscreenCanvas!.width, 10); + expect(surface.debugOffscreenCanvas!.height, 16); - // Increase device-pixel ratio: this makes CSS pixels bigger, so we need - // fewer of them to cover the browser window. - EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(2.0); - final CkSurface highDpr = - surface.acquireFrame(const ui.Size(10, 16)).skiaSurface; - expect(highDpr.width(), 10); - expect(highDpr.height(), 16); - expect(surface.debugOffscreenCanvas!.width, 10); - expect(surface.debugOffscreenCanvas!.height, 16); - - // Decrease device-pixel ratio: this makes CSS pixels smaller, so we need - // more of them to cover the browser window. - EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(0.5); - final CkSurface lowDpr = - surface.acquireFrame(const ui.Size(10, 16)).skiaSurface; - expect(lowDpr.width(), 10); - expect(lowDpr.height(), 16); - expect(surface.debugOffscreenCanvas!.width, 10); - expect(surface.debugOffscreenCanvas!.height, 16); - - // See https://github.com/flutter/flutter/issues/77084#issuecomment-1120151172 - EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(2.0); - final CkSurface changeRatioAndSize = - surface.acquireFrame(const ui.Size(9.9, 15.9)).skiaSurface; - expect(changeRatioAndSize.width(), 10); - expect(changeRatioAndSize.height(), 16); - expect(surface.debugOffscreenCanvas!.width, 10); - expect(surface.debugOffscreenCanvas!.height, 16); + // Increase device-pixel ratio: this makes CSS pixels bigger, so we need + // fewer of them to cover the browser window. + EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(2.0); + final CkSurface highDpr = + surface.acquireFrame(const ui.Size(10, 16)).skiaSurface; + expect(highDpr.width(), 10); + expect(highDpr.height(), 16); + expect(surface.debugOffscreenCanvas!.width, 10); + expect(surface.debugOffscreenCanvas!.height, 16); + + // Decrease device-pixel ratio: this makes CSS pixels smaller, so we need + // more of them to cover the browser window. + EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(0.5); + final CkSurface lowDpr = + surface.acquireFrame(const ui.Size(10, 16)).skiaSurface; + expect(lowDpr.width(), 10); + expect(lowDpr.height(), 16); + expect(surface.debugOffscreenCanvas!.width, 10); + expect(surface.debugOffscreenCanvas!.height, 16); + + // See https://github.com/flutter/flutter/issues/77084#issuecomment-1120151172 + EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(2.0); + final CkSurface changeRatioAndSize = + surface.acquireFrame(const ui.Size(9.9, 15.9)).skiaSurface; + expect(changeRatioAndSize.width(), 10); + expect(changeRatioAndSize.height(), 16); + expect(surface.debugOffscreenCanvas!.width, 10); + expect(surface.debugOffscreenCanvas!.height, 16); }, skip: !Surface.offscreenCanvasSupported, ); }); } + +DomCanvasElement getDisplayCanvas(Surface surface) { + assert(surface.isDisplayCanvas); + return surface.hostElement.children.first as DomCanvasElement; +} + +/// Extracts the CSS style values of 'width' and 'height' and returns them +/// as a [ui.Size]. +ui.Size getCssSize(Surface surface) { + final DomCanvasElement canvas = getDisplayCanvas(surface); + final String cssWidth = canvas.style.width; + final String cssHeight = canvas.style.height; + // CSS width and height should be in the form 'NNNpx'. So cut off the 'px' and + // convert to a number. + final double width = + double.parse(cssWidth.substring(0, cssWidth.length - 2).trim()); + final double height = + double.parse(cssHeight.substring(0, cssHeight.length - 2).trim()); + return ui.Size(width, height); +} From 6d0ede5768a631adc285c807198fc13196f5e200 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Wed, 17 Jan 2024 15:55:18 -0800 Subject: [PATCH 15/18] Fix analysis errors in test --- lib/web_ui/test/canvaskit/surface_test.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart index 1dc1e861d07b3..91ce73fbeacde 100644 --- a/lib/web_ui/test/canvaskit/surface_test.dart +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:html'; import 'dart:js_util' as js_util; import 'package:test/bootstrap/browser.dart'; @@ -126,8 +125,8 @@ void testMain() { surface.createOrUpdateSurface(const ui.Size(5, 15)); final DomCanvasElement shrunk = getDisplayCanvas(surface); canvasSize = getCssSize(surface); - expect(original.width, 9); - expect(original.height, 19); + expect(shrunk.width, 9); + expect(shrunk.height, 19); expect(canvasSize.width, 9); expect(canvasSize.height, 19); From 526610ff83aa2e43025288f55e4f44ef923b767d Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Wed, 17 Jan 2024 16:35:08 -0800 Subject: [PATCH 16/18] Skip test on skwasm since same() doesn't work for JSValues --- lib/web_ui/test/canvaskit/surface_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart index 91ce73fbeacde..3e8755d6af45d 100644 --- a/lib/web_ui/test/canvaskit/surface_test.dart +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -9,6 +9,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; +import '../ui/utils.dart'; import 'common.dart'; void main() { @@ -193,7 +194,8 @@ void testMain() { // 2.0. expect(canvasSize.width, 14); expect(canvasSize.height, 28); - }); + // Skip on Skwasm since same() doesn't work for JSValues. + }, skip: isSkwasm); test( 'Surface creates new context when WebGL context is restored', From b27302bda9bbf9444162410c4e25406ab304d980 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 18 Jan 2024 10:12:39 -0800 Subject: [PATCH 17/18] skip test on wasm --- lib/web_ui/test/canvaskit/surface_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart index 3e8755d6af45d..c2a2a03e7ebef 100644 --- a/lib/web_ui/test/canvaskit/surface_test.dart +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -194,8 +194,8 @@ void testMain() { // 2.0. expect(canvasSize.width, 14); expect(canvasSize.height, 28); - // Skip on Skwasm since same() doesn't work for JSValues. - }, skip: isSkwasm); + // Skip on wasm since same() doesn't work for JSValues. + }, skip: isWasm); test( 'Surface creates new context when WebGL context is restored', From abc1876ef425d587099bd215b65b0de8ad6e3d7d Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 18 Jan 2024 10:36:36 -0800 Subject: [PATCH 18/18] remove unused import --- lib/web_ui/test/canvaskit/surface_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart index c2a2a03e7ebef..0df8e0fb052cc 100644 --- a/lib/web_ui/test/canvaskit/surface_test.dart +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -9,7 +9,6 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import '../ui/utils.dart'; import 'common.dart'; void main() {