Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use H5vcc CanvasKit implementation if it is detected. #31191

Merged
merged 8 commits into from
Feb 9, 2022
29 changes: 29 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -39,6 +47,18 @@ external set windowFlutterCanvasKit(CanvasKit? value);
@JS('window.flutterCanvasKit')
external CanvasKit? get windowFlutterCanvasKit;

@JS('window.h5vcc')
external H5vcc? get h5vcc;
b-luk marked this conversation as resolved.
Show resolved Hide resolved

@JS('window.h5vcc')
external set debugH5vccSetter(H5vcc? value);

@JS()
@anonymous
abstract class H5vcc {
external CanvasKit? get canvasKit;
}

@JS()
@anonymous
class CanvasKit {
Expand Down Expand Up @@ -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')
Expand Down
7 changes: 7 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/initialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -47,6 +48,12 @@ String canvasKitWasmModuleUrl(String canvasKitBase, String file) =>
Future<void> 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;
Expand Down
5 changes: 5 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/surface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ class Surface {

/// Creates a <canvas> 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.');
}
Expand Down
66 changes: 66 additions & 0 deletions lib/web_ui/test/canvaskit/h5vcc_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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: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(<String, dynamic>{
'dispose': () {}
});
};
});

setUpCanvasKitTest();

setUp(() {
getH5vccSkSurfaceCalledCount = 0;
});

test('sets useH5vccCanvasKit', () {
expect(useH5vccCanvasKit, true);
});

test('API includes patched getH5vccSkSurface', () {
expect(canvasKit.getH5vccSkSurface, isNotNull);
});

test('Surface scquireFrame uses getH5vccSkSurface', () {
b-luk marked this conversation as resolved.
Show resolved Hide resolved
final Surface surface = SurfaceFactory.instance.getSurface();
surface.acquireFrame(ui.Size.zero);
expect(getH5vccSkSurfaceCalledCount, 1);
b-luk marked this conversation as resolved.
Show resolved Hide resolved
});
}, testOn: 'chrome');
}

class PatchedH5vcc implements H5vcc {
@override
final CanvasKit canvasKit;

PatchedH5vcc(this.canvasKit);
}