diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index d5fcf7c2b660a..05c55d2c15561 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -25,6 +25,14 @@ import '../profiler.dart'; /// Entrypoint into the CanvasKit API. late CanvasKit canvasKit; +/// Whether to use a CanvasKit implementation provided by a JavaScript +/// `window.h5vcc.canvasKit` object. +/// +/// Cobalt may use this object to expose a native implementation of the +/// CanvasKit bindings. If this exists, use it instead of using the normal +/// downloaded CanvasKit library. +final bool useH5vccCanvasKit = h5vcc != null; + /// Sets the [CanvasKit] object on `window` so we can use `@JS()` to bind to /// static APIs. /// @@ -39,6 +47,18 @@ external set windowFlutterCanvasKit(CanvasKit? value); @JS('window.flutterCanvasKit') external CanvasKit? get windowFlutterCanvasKit; +@JS('window.h5vcc') +external H5vcc? get h5vcc; + +@JS('window.h5vcc') +external set debugH5vccSetter(H5vcc? value); + +@JS() +@anonymous +abstract class H5vcc { + external CanvasKit? get canvasKit; +} + @JS() @anonymous class CanvasKit { @@ -132,6 +152,15 @@ class CanvasKit { Object src, SkPartialImageInfo info, ); + + /// Gets a Skia surface from Cobalt's h5vcc object. + /// + /// This is only applicable when running on Cobalt and when using Cobalt's + /// h5vcc CanvasKit bindings. + /// + /// On Cobalt, this is the only way to get a Skia surface. Other CanvasKit + /// Make...Surface methods are not supported. + external SkSurface getH5vccSkSurface(); } @JS('window.CanvasKitInit') diff --git a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart index 1185bf7968d81..f1157291fcde5 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart @@ -13,6 +13,7 @@ import '../embedder.dart'; import '../safe_browser_api.dart'; import 'canvaskit_api.dart'; import 'fonts.dart'; +import 'util.dart'; /// Whether to use CanvasKit as the rendering backend. final bool useCanvasKit = FlutterConfiguration.flutterWebAutoDetect @@ -47,6 +48,12 @@ String canvasKitWasmModuleUrl(String canvasKitBase, String file) => Future initializeCanvasKit({String? canvasKitBase}) async { if (windowFlutterCanvasKit != null) { canvasKit = windowFlutterCanvasKit!; + } else if (useH5vccCanvasKit) { + if (h5vcc?.canvasKit == null) { + throw CanvasKitError('H5vcc CanvasKit implementation not found.'); + } + canvasKit = h5vcc!.canvasKit!; + windowFlutterCanvasKit = canvasKit; } else { canvasKit = await downloadCanvasKit(canvasKitBase: canvasKitBase); windowFlutterCanvasKit = canvasKit; diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 20e41adbdb9de..f0e8a149574e0 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -137,6 +137,11 @@ class Surface { /// Creates a and SkSurface for the given [size]. CkSurface createOrUpdateSurface(ui.Size size) { + if (useH5vccCanvasKit) { + _surface ??= CkSurface(canvasKit.getH5vccSkSurface(), null); + return _surface!; + } + if (size.isEmpty) { throw CanvasKitError('Cannot create surfaces of empty size.'); } diff --git a/lib/web_ui/test/canvaskit/h5vcc_test.dart b/lib/web_ui/test/canvaskit/h5vcc_test.dart new file mode 100644 index 0000000000000..a5a710e435e92 --- /dev/null +++ b/lib/web_ui/test/canvaskit/h5vcc_test.dart @@ -0,0 +1,73 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:html' as html; +import 'dart:js' as js; + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +import 'common.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() { + group('H5vcc patched CanvasKit', () { + int getH5vccSkSurfaceCalledCount = 0; + + setUpAll(() async { + // Set `window.h5vcc` to PatchedH5vcc which uses a downloaded CanvasKit. + final CanvasKit downloadedCanvasKit = await downloadCanvasKit(); + debugH5vccSetter = PatchedH5vcc(downloadedCanvasKit); + + // Monkey-patch the getH5vccSkSurface function of + // `window.h5vcc.canvasKit`. + js.context['h5vcc']['canvasKit']['getH5vccSkSurface'] = () { + getH5vccSkSurfaceCalledCount++; + + // Returns a fake [SkSurface] object with a minimal implementation. + return js.JsObject.jsify({ + 'dispose': () {} + }); + }; + }); + + setUpCanvasKitTest(); + + setUp(() { + getH5vccSkSurfaceCalledCount = 0; + }); + + test('sets useH5vccCanvasKit', () { + expect(useH5vccCanvasKit, true); + }); + + test('API includes patched getH5vccSkSurface', () { + expect(canvasKit.getH5vccSkSurface, isNotNull); + }); + + test('Surface acquireFrame uses getH5vccSkSurface', () { + final Surface surface = SurfaceFactory.instance.getSurface(); + surface.acquireFrame(ui.Size.zero); + expect(getH5vccSkSurfaceCalledCount, 1); + + // No element should be created. + expect( + flutterViewEmbedder.glassPaneElement!.querySelectorAll('canvas'), + isEmpty, + ); + }); + }, testOn: 'chrome'); +} + +class PatchedH5vcc implements H5vcc { + @override + final CanvasKit canvasKit; + + PatchedH5vcc(this.canvasKit); +}