From 9ad9ec319165f5fd1d1a5793aab3f319968a768b Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 12 Jul 2021 16:11:58 -0700 Subject: [PATCH 1/4] Add the ability to change the CanvasKit url at runtime. --- .../src/engine/canvaskit/initialization.dart | 101 +++++++++++++++++- lib/web_ui/lib/src/engine/dom_renderer.dart | 97 +---------------- 2 files changed, 103 insertions(+), 95 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart index 11a55a17f247a..832823cb44a60 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart @@ -91,18 +91,28 @@ final String canvasKitBuildUrl = canvasKitBaseUrl + (kProfileMode ? 'profiling/' : ''); final String canvasKitJavaScriptBindingsUrl = canvasKitBuildUrl + 'canvaskit.js'; -String canvasKitWasmModuleUrl(String file) => canvasKitBuildUrl + file; +String canvasKitWasmModuleUrl(String file) => _currentCanvasKitBase! + file; + +/// The script element which CanvasKit is loaded from. +html.ScriptElement? _canvasKitScript; + +/// A [Future] which completes when the CanvasKit script has been loaded. +Future? _canvasKitLoaded; + +/// The currently used base URL for loading CanvasKit. +String? _currentCanvasKitBase; /// Initialize CanvasKit. /// /// This calls `CanvasKitInit` and assigns the global [canvasKit] object. -Future initializeCanvasKit() { +Future initializeCanvasKit({String? canvasKitBase}) { final Completer canvasKitCompleter = Completer(); if (windowFlutterCanvasKit != null) { canvasKit = windowFlutterCanvasKit!; canvasKitCompleter.complete(); } else { - domRenderer.canvasKitLoaded!.then((_) { + _startDownloadingCanvasKit(canvasKitBase); + _canvasKitLoaded!.then((_) { final CanvasKitInitPromise canvasKitInitPromise = CanvasKitInit(CanvasKitInitOptions( locateFile: js.allowInterop( @@ -122,6 +132,91 @@ Future initializeCanvasKit() { return canvasKitCompleter.future; } +/// Starts downloading the CanvasKit JavaScript file at [canvasKitBase] and sets +/// [_canvasKitLoaded]. +void _startDownloadingCanvasKit(String? canvasKitBase) { + String canvasKitJavaScriptUrl = canvasKitBase != null + ? canvasKitBase + 'canvaskit.js' + : canvasKitJavaScriptBindingsUrl; + _currentCanvasKitBase = canvasKitBase ?? canvasKitBuildUrl; + // Only reset CanvasKit if it's not already available. + if (windowFlutterCanvasKit == null) { + _canvasKitScript?.remove(); + _canvasKitScript = html.ScriptElement(); + _canvasKitScript!.src = canvasKitJavaScriptUrl; + + Completer canvasKitLoadCompleter = Completer(); + _canvasKitLoaded = canvasKitLoadCompleter.future; + + late StreamSubscription loadSubscription; + loadSubscription = _canvasKitScript!.onLoad.listen((_) { + loadSubscription.cancel(); + canvasKitLoadCompleter.complete(); + }); + + // TODO(hterkelsen): Rather than this monkey-patch hack, we should + // build CanvasKit ourselves. See: + // https://github.com/flutter/flutter/issues/52588 + + // Monkey-patch the top-level `module` and `exports` objects so that + // CanvasKit doesn't attempt to register itself as an anonymous module. + // + // The idea behind making these fake `exports` and `module` objects is + // that `canvaskit.js` contains the following lines of code: + // + // if (typeof exports === 'object' && typeof module === 'object') + // module.exports = CanvasKitInit; + // else if (typeof define === 'function' && define['amd']) + // define([], function() { return CanvasKitInit; }); + // + // We need to avoid hitting the case where CanvasKit defines an anonymous + // module, since this breaks RequireJS, which DDC and some plugins use. + // Temporarily removing the `define` function won't work because RequireJS + // could load in between this code running and the CanvasKit code running. + // Also, we cannot monkey-patch the `define` function because it is + // non-configurable (it is a top-level 'var'). + + // First check if `exports` and `module` are already defined. If so, then + // CommonJS is being used, and we shouldn't have any problems. + js.JsFunction objectConstructor = js.context['Object']; + if (js.context['exports'] == null) { + js.JsObject exportsAccessor = js.JsObject.jsify({ + 'get': js.allowInterop(() { + if (html.document.currentScript == _canvasKitScript) { + return js.JsObject(objectConstructor); + } else { + return js.context['_flutterWebCachedExports']; + } + }), + 'set': js.allowInterop((dynamic value) { + js.context['_flutterWebCachedExports'] = value; + }), + 'configurable': true, + }); + objectConstructor.callMethod( + 'defineProperty', [js.context, 'exports', exportsAccessor]); + } + if (js.context['module'] == null) { + js.JsObject moduleAccessor = js.JsObject.jsify({ + 'get': js.allowInterop(() { + if (html.document.currentScript == _canvasKitScript) { + return js.JsObject(objectConstructor); + } else { + return js.context['_flutterWebCachedModule']; + } + }), + 'set': js.allowInterop((dynamic value) { + js.context['_flutterWebCachedModule'] = value; + }), + 'configurable': true, + }); + objectConstructor.callMethod( + 'defineProperty', [js.context, 'module', moduleAccessor]); + } + html.document.head!.append(_canvasKitScript!); + } +} + /// The Skia font collection. SkiaFontCollection get skiaFontCollection => _skiaFontCollection!; SkiaFontCollection? _skiaFontCollection; diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 02a0c7ebfc068..5f48a040c3099 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -49,14 +49,6 @@ class DomRenderer { /// Configures the screen, such as scaling. html.MetaElement? _viewportMeta; - /// The canvaskit script, downloaded from a CDN. Only created if - /// [useCanvasKit] is set to true. - 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 @@ -121,7 +113,6 @@ class DomRenderer { _glassPaneElement, _styleElement, _viewportMeta, - _canvasKitScript, ]); }); } @@ -269,7 +260,8 @@ class DomRenderer { _styleElement = html.StyleElement(); html.document.head!.append(_styleElement!); final html.CssStyleSheet sheet = _styleElement!.sheet as html.CssStyleSheet; - applyGlobalCssRulesToSheet(sheet, + applyGlobalCssRulesToSheet( + sheet, browserEngine: browserEngine, hasAutofillOverlay: browserHasAutofillOverlay(), ); @@ -364,8 +356,7 @@ class DomRenderer { // Don't allow the scene to receive pointer events. _sceneHostElement = createElement('flt-scene-host') - ..style - .pointerEvents = 'none'; + ..style.pointerEvents = 'none'; final html.Element semanticsHostElement = createElement('flt-semantics-host'); @@ -429,83 +420,6 @@ class DomRenderer { }); } - // Only reset CanvasKit if it's not already available. - if (useCanvasKit && windowFlutterCanvasKit == null) { - _canvasKitScript?.remove(); - _canvasKitScript = html.ScriptElement(); - _canvasKitScript!.src = canvasKitJavaScriptBindingsUrl; - - Completer canvasKitLoadCompleter = Completer(); - _canvasKitLoaded = canvasKitLoadCompleter.future; - - late StreamSubscription loadSubscription; - loadSubscription = _canvasKitScript!.onLoad.listen((_) { - loadSubscription.cancel(); - canvasKitLoadCompleter.complete(); - }); - - // TODO(hterkelsen): Rather than this monkey-patch hack, we should - // build CanvasKit ourselves. See: - // https://github.com/flutter/flutter/issues/52588 - - // Monkey-patch the top-level `module` and `exports` objects so that - // CanvasKit doesn't attempt to register itself as an anonymous module. - // - // The idea behind making these fake `exports` and `module` objects is - // that `canvaskit.js` contains the following lines of code: - // - // if (typeof exports === 'object' && typeof module === 'object') - // module.exports = CanvasKitInit; - // else if (typeof define === 'function' && define['amd']) - // define([], function() { return CanvasKitInit; }); - // - // We need to avoid hitting the case where CanvasKit defines an anonymous - // module, since this breaks RequireJS, which DDC and some plugins use. - // Temporarily removing the `define` function won't work because RequireJS - // could load in between this code running and the CanvasKit code running. - // Also, we cannot monkey-patch the `define` function because it is - // non-configurable (it is a top-level 'var'). - - // First check if `exports` and `module` are already defined. If so, then - // CommonJS is being used, and we shouldn't have any problems. - js.JsFunction objectConstructor = js.context['Object']; - if (js.context['exports'] == null) { - js.JsObject exportsAccessor = js.JsObject.jsify({ - 'get': js.allowInterop(() { - if (html.document.currentScript == _canvasKitScript) { - return js.JsObject(objectConstructor); - } else { - return js.context['_flutterWebCachedExports']; - } - }), - 'set': js.allowInterop((dynamic value) { - js.context['_flutterWebCachedExports'] = value; - }), - 'configurable': true, - }); - objectConstructor.callMethod('defineProperty', - [js.context, 'exports', exportsAccessor]); - } - if (js.context['module'] == null) { - js.JsObject moduleAccessor = js.JsObject.jsify({ - 'get': js.allowInterop(() { - if (html.document.currentScript == _canvasKitScript) { - return js.JsObject(objectConstructor); - } else { - return js.context['_flutterWebCachedModule']; - } - }), - 'set': js.allowInterop((dynamic value) { - js.context['_flutterWebCachedModule'] = value; - }), - 'configurable': true, - }); - objectConstructor.callMethod( - 'defineProperty', [js.context, 'module', moduleAccessor]); - } - html.document.head!.append(_canvasKitScript!); - } - if (html.window.visualViewport != null) { _resizeSubscription = html.window.visualViewport!.onResize.listen(_metricsDidChange); @@ -712,7 +626,8 @@ class DomRenderer { } // Applies the required global CSS to an incoming [html.CssStyleSheet] `sheet`. -void applyGlobalCssRulesToSheet(html.CssStyleSheet sheet, { +void applyGlobalCssRulesToSheet( + html.CssStyleSheet sheet, { required BrowserEngine browserEngine, required bool hasAutofillOverlay, String glassPaneTagName = DomRenderer._glassPaneTagName, @@ -820,8 +735,6 @@ $glassPaneTagName * { } } - - /// Miscellaneous statistics collecting during a single frame's execution. /// /// This is useful when profiling the app. This class should only be used when From ecd042527ae4ead854b1411d50c2c88c8796297d Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 13 Jul 2021 12:54:35 -0700 Subject: [PATCH 2/4] Fix analysis warnings --- lib/web_ui/lib/src/engine.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index d8405c9657108..157af2e92ef4b 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -22,6 +22,7 @@ import 'dart:collection' import 'dart:convert' hide Codec; import 'dart:developer' as developer; import 'dart:html' as html; +// ignore: unused_import import 'dart:js' as js; import 'dart:js_util' as js_util; // ignore: unused_import @@ -254,7 +255,6 @@ export 'engine/web_experiments.dart'; export 'engine/canvaskit/canvas.dart'; -import 'engine/canvaskit/canvaskit_api.dart'; export 'engine/canvaskit/canvaskit_api.dart'; export 'engine/canvaskit/canvaskit_canvas.dart'; From 1fec8912c2a041faefbe3de9f875b42a1be930f3 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 15 Jul 2021 11:03:05 -0700 Subject: [PATCH 3/4] Fix analysis issues --- .../lib/src/engine/canvaskit/initialization.dart | 10 +++++----- lib/web_ui/lib/src/engine/dom_renderer.dart | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart index 4d192ba5b85a6..b8acc3761413b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart @@ -136,7 +136,7 @@ Future initializeCanvasKit({String? canvasKitBase}) { /// Starts downloading the CanvasKit JavaScript file at [canvasKitBase] and sets /// [_canvasKitLoaded]. void _startDownloadingCanvasKit(String? canvasKitBase) { - String canvasKitJavaScriptUrl = canvasKitBase != null + final String canvasKitJavaScriptUrl = canvasKitBase != null ? canvasKitBase + 'canvaskit.js' : canvasKitJavaScriptBindingsUrl; _currentCanvasKitBase = canvasKitBase ?? canvasKitBuildUrl; @@ -146,7 +146,7 @@ void _startDownloadingCanvasKit(String? canvasKitBase) { _canvasKitScript = html.ScriptElement(); _canvasKitScript!.src = canvasKitJavaScriptUrl; - Completer canvasKitLoadCompleter = Completer(); + final Completer canvasKitLoadCompleter = Completer(); _canvasKitLoaded = canvasKitLoadCompleter.future; late StreamSubscription loadSubscription; @@ -179,9 +179,9 @@ void _startDownloadingCanvasKit(String? canvasKitBase) { // First check if `exports` and `module` are already defined. If so, then // CommonJS is being used, and we shouldn't have any problems. - js.JsFunction objectConstructor = js.context['Object']; + final js.JsFunction objectConstructor = js.context['Object']; if (js.context['exports'] == null) { - js.JsObject exportsAccessor = js.JsObject.jsify({ + final js.JsObject exportsAccessor = js.JsObject.jsify({ 'get': js.allowInterop(() { if (html.document.currentScript == _canvasKitScript) { return js.JsObject(objectConstructor); @@ -198,7 +198,7 @@ void _startDownloadingCanvasKit(String? canvasKitBase) { 'defineProperty', [js.context, 'exports', exportsAccessor]); } if (js.context['module'] == null) { - js.JsObject moduleAccessor = js.JsObject.jsify({ + final js.JsObject moduleAccessor = js.JsObject.jsify({ 'get': js.allowInterop(() { if (html.document.currentScript == _canvasKitScript) { return js.JsObject(objectConstructor); diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 8790427d5e155..20065b48dd3ed 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -4,7 +4,6 @@ import 'dart:async'; import 'dart:html' as html; -import 'dart:js' as js; import 'dart:js_util' as js_util; import 'package:ui/ui.dart' as ui; @@ -12,7 +11,6 @@ import 'package:ui/ui.dart' as ui; import '../engine.dart' show buildMode, registerHotRestartListener; import 'browser_detection.dart'; import 'canvaskit/initialization.dart'; -import 'canvaskit/canvaskit_api.dart'; import 'host_node.dart'; import 'keyboard_binding.dart'; import 'platform_dispatcher.dart'; From ac7de944dcf840c7ad7861ed0669286615b6fe95 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 15 Jul 2021 13:34:16 -0700 Subject: [PATCH 4/4] Fix analysis warnings --- lib/web_ui/lib/src/engine/canvaskit/initialization.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart index b8acc3761413b..107fc15f05860 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart @@ -181,7 +181,7 @@ void _startDownloadingCanvasKit(String? canvasKitBase) { // CommonJS is being used, and we shouldn't have any problems. final js.JsFunction objectConstructor = js.context['Object']; if (js.context['exports'] == null) { - final js.JsObject exportsAccessor = js.JsObject.jsify({ + final js.JsObject exportsAccessor = js.JsObject.jsify({ 'get': js.allowInterop(() { if (html.document.currentScript == _canvasKitScript) { return js.JsObject(objectConstructor); @@ -198,7 +198,7 @@ void _startDownloadingCanvasKit(String? canvasKitBase) { 'defineProperty', [js.context, 'exports', exportsAccessor]); } if (js.context['module'] == null) { - final js.JsObject moduleAccessor = js.JsObject.jsify({ + final js.JsObject moduleAccessor = js.JsObject.jsify({ 'get': js.allowInterop(() { if (html.document.currentScript == _canvasKitScript) { return js.JsObject(objectConstructor);