From 3b572ab66b64cceb2942afc99d382eb51b4d5dce Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 10 May 2021 16:03:01 -0700 Subject: [PATCH 1/4] If CanvasKit has already been initialized, don't load it again. This could be useful for hot restart or for DartPad. --- .../src/engine/canvaskit/canvaskit_api.dart | 11 ++++- .../src/engine/canvaskit/initialization.dart | 31 +++++++------- .../lib/src/engine/canvaskit/surface.dart | 2 +- lib/web_ui/lib/src/engine/dom_renderer.dart | 18 ++++++++- .../test/canvaskit/hot_restart_test.dart | 40 +++++++++++++++++++ .../test/canvaskit/initialization_test.dart | 3 +- 6 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 lib/web_ui/test/canvaskit/hot_restart_test.dart 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 c51b8b40b37ca..e73aab17378ab 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -16,8 +16,15 @@ late CanvasKit canvasKit; /// static APIs. /// /// See, e.g. [SkPaint]. +/// +/// This also acts as a cache of an initialized CanvasKit instance. We can use +/// this, for example, to perform a hot restart without needing to redownload +/// and reinitialized CanvasKit. @JS('window.flutterCanvasKit') -external set windowFlutterCanvasKit(CanvasKit value); +external set windowFlutterCanvasKit(CanvasKit? value); + +@JS('window.flutterCanvasKit') +external CanvasKit? get windowFlutterCanvasKit; @JS() @anonymous @@ -141,7 +148,7 @@ class ColorSpace {} @anonymous class SkWebGLContextOptions { external factory SkWebGLContextOptions({ - required int anitalias, + required int antialias, // WebGL version: 1 or 2. required int majorVersion, }); diff --git a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart index 74582ab6c80c4..ac6039f2bb665 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart @@ -87,20 +87,23 @@ String canvasKitWasmModuleUrl(String file) => canvasKitBuildUrl + file; /// This calls `CanvasKitInit` and assigns the global [canvasKit] object. Future initializeCanvasKit() { final Completer canvasKitCompleter = Completer(); - late StreamSubscription loadSubscription; - loadSubscription = domRenderer.canvasKitScript!.onLoad.listen((_) { - loadSubscription.cancel(); - final CanvasKitInitPromise canvasKitInitPromise = - CanvasKitInit(CanvasKitInitOptions( - locateFile: js.allowInterop( - (String file, String unusedBase) => canvasKitWasmModuleUrl(file)), - )); - canvasKitInitPromise.then(js.allowInterop((CanvasKit ck) { - canvasKit = ck; - windowFlutterCanvasKit = canvasKit; - canvasKitCompleter.complete(); - })); - }); + if (windowFlutterCanvasKit != null) { + canvasKit = windowFlutterCanvasKit!; + canvasKitCompleter.complete(); + } else { + domRenderer.canvasKitLoaded!.then((_) { + final CanvasKitInitPromise canvasKitInitPromise = + CanvasKitInit(CanvasKitInitOptions( + locateFile: js.allowInterop( + (String file, String unusedBase) => canvasKitWasmModuleUrl(file)), + )); + canvasKitInitPromise.then(js.allowInterop((CanvasKit ck) { + canvasKit = ck; + windowFlutterCanvasKit = canvasKit; + canvasKitCompleter.complete(); + })); + }); + } /// Add a Skia scene host. skiaSceneHost = html.Element.tag('flt-scene'); diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index fbcc20e962156..399fe49fc86f3 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -210,7 +210,7 @@ class Surface { SkWebGLContextOptions( // Default to no anti-aliasing. Paint commands can be explicitly // anti-aliased by setting their `Paint` object's `antialias` property. - anitalias: 0, + antialias: 0, majorVersion: webGLVersion, ), ); diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 302368a366301..200a5ffce3719 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -48,6 +48,9 @@ class DomRenderer { html.ScriptElement? get canvasKitScript => _canvasKitScript; html.ScriptElement? _canvasKitScript; + Future? get canvasKitLoaded => _canvasKitLoaded; + Future? _canvasKitLoaded; + /// The element that contains the [sceneElement]. /// /// This element is created and inserted in the HTML DOM once. It is never @@ -441,7 +444,8 @@ flt-glass-pane * { _sceneHostElement = createElement('flt-scene-host'); - final html.Element semanticsHostElement = createElement('flt-semantics-host'); + final html.Element semanticsHostElement = + createElement('flt-semantics-host'); semanticsHostElement.style ..position = 'absolute' ..transformOrigin = '0 0 0'; @@ -514,6 +518,15 @@ flt-glass-pane * { _canvasKitScript = html.ScriptElement(); _canvasKitScript!.src = canvasKitJavaScriptBindingsUrl; + Completer canvasKitLoadCompleter = Completer(); + _canvasKitLoaded = canvasKitLoadCompleter.future; + + late StreamSubscription loadSubscription; + loadSubscription = _canvasKitScript!.onLoad.listen((_) { + loadSubscription.cancel(); + canvasKitLoadCompleter.complete(true); + }); + // TODO(hterkelsen): Rather than this monkey-patch hack, we should // build CanvasKit ourselves. See: // https://github.com/flutter/flutter/issues/52588 @@ -591,7 +604,8 @@ flt-glass-pane * { /// logical pixels. To compensate, we inject an inverse scale at the root /// level. void updateSemanticsScreenProperties() { - _semanticsHostElement!.style.transform = 'scale(${1 / html.window.devicePixelRatio})'; + _semanticsHostElement!.style.transform = + 'scale(${1 / html.window.devicePixelRatio})'; } /// Called immediately after browser window metrics change. diff --git a/lib/web_ui/test/canvaskit/hot_restart_test.dart b/lib/web_ui/test/canvaskit/hot_restart_test.dart new file mode 100644 index 0000000000000..f1aa63c23b3c8 --- /dev/null +++ b/lib/web_ui/test/canvaskit/hot_restart_test.dart @@ -0,0 +1,40 @@ +// 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: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('initialize', () { + test( + 're-uses the same initialized instance if it is already set on the window', + () async { + expect(windowFlutterCanvasKit, isNull); + + DomRenderer(); + await ui.webOnlyInitializePlatform( + assetManager: WebOnlyMockAssetManager()); + expect(windowFlutterCanvasKit, isNotNull); + + var firstCanvasKitInstance = windowFlutterCanvasKit; + + // Triggers a reset of the CanvasKit script element. + DomRenderer(); + await ui.webOnlyInitializePlatform( + assetManager: WebOnlyMockAssetManager()); + // The instance is the same. + expect(firstCanvasKitInstance, windowFlutterCanvasKit); + }); + // TODO: https://github.com/flutter/flutter/issues/60040 + }, skip: isIosSafari); +} diff --git a/lib/web_ui/test/canvaskit/initialization_test.dart b/lib/web_ui/test/canvaskit/initialization_test.dart index 0c09c2122f3c8..50792f1ae6610 100644 --- a/lib/web_ui/test/canvaskit/initialization_test.dart +++ b/lib/web_ui/test/canvaskit/initialization_test.dart @@ -20,7 +20,8 @@ void testMain() { test('populates flt-renderer and flt-build-mode', () { DomRenderer(); - expect(html.document.body!.attributes['flt-renderer'], 'canvaskit (requested explicitly)'); + expect(html.document.body!.attributes['flt-renderer'], + 'canvaskit (requested explicitly)'); expect(html.document.body!.attributes['flt-build-mode'], 'debug'); }); // TODO: https://github.com/flutter/flutter/issues/60040 From 0c249ecc6029c14e8b6b7b871b4a927c38b847fe Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 10 May 2021 16:13:43 -0700 Subject: [PATCH 2/4] Don't redownload script if it is already loaded. --- lib/web_ui/lib/src/engine/dom_renderer.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 200a5ffce3719..3362124380440 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -513,7 +513,8 @@ flt-glass-pane * { }); } - if (useCanvasKit) { + // Only reset CanvasKit if it wasn't already loaded. + if (useCanvasKit && windowFlutterCanvasKit == null) { _canvasKitScript?.remove(); _canvasKitScript = html.ScriptElement(); _canvasKitScript!.src = canvasKitJavaScriptBindingsUrl; From 50cd314add12d1ec1855694f277997b0861c9d6c Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 11 May 2021 09:54:51 -0700 Subject: [PATCH 3/4] trailing whitespace --- lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e73aab17378ab..2a6e2d539a5e9 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -16,7 +16,7 @@ late CanvasKit canvasKit; /// static APIs. /// /// See, e.g. [SkPaint]. -/// +/// /// This also acts as a cache of an initialized CanvasKit instance. We can use /// this, for example, to perform a hot restart without needing to redownload /// and reinitialized CanvasKit. From 640b30a8367df32df67486ab0a2b620dbbe69cf9 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Wed, 12 May 2021 12:35:31 -0700 Subject: [PATCH 4/4] Touch up comments --- lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart | 2 +- lib/web_ui/lib/src/engine/dom_renderer.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 2a6e2d539a5e9..ff623934c6176 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -19,7 +19,7 @@ late CanvasKit canvasKit; /// /// This also acts as a cache of an initialized CanvasKit instance. We can use /// this, for example, to perform a hot restart without needing to redownload -/// and reinitialized CanvasKit. +/// and reinitialize CanvasKit. @JS('window.flutterCanvasKit') external set windowFlutterCanvasKit(CanvasKit? value); diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 699ca777c24a9..c86d1029ec565 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -513,7 +513,7 @@ flt-glass-pane * { }); } - // Only reset CanvasKit if it wasn't already loaded. + // Only reset CanvasKit if it's not already available. if (useCanvasKit && windowFlutterCanvasKit == null) { _canvasKitScript?.remove(); _canvasKitScript = html.ScriptElement();