diff --git a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart index ade7e5611cb37..107fc15f05860 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart @@ -92,18 +92,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( @@ -123,6 +133,91 @@ Future initializeCanvasKit() { return canvasKitCompleter.future; } +/// Starts downloading the CanvasKit JavaScript file at [canvasKitBase] and sets +/// [_canvasKitLoaded]. +void _startDownloadingCanvasKit(String? canvasKitBase) { + final 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; + + final 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. + final js.JsFunction objectConstructor = js.context['Object']; + if (js.context['exports'] == null) { + final 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) { + final 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 74d7b82fb415a..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'; @@ -68,14 +66,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 @@ -140,7 +130,6 @@ class DomRenderer { _glassPaneElement, _styleElement, _viewportMeta, - _canvasKitScript, ]); }); } @@ -288,7 +277,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(), ); @@ -383,8 +373,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'); @@ -448,83 +437,6 @@ class DomRenderer { }); } - // Only reset CanvasKit if it's not already available. - if (useCanvasKit && windowFlutterCanvasKit == null) { - _canvasKitScript?.remove(); - _canvasKitScript = html.ScriptElement(); - _canvasKitScript!.src = canvasKitJavaScriptBindingsUrl; - - final 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. - final js.JsFunction objectConstructor = js.context['Object']; - if (js.context['exports'] == null) { - final 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) { - final 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); @@ -652,7 +564,8 @@ class DomRenderer { screenOrientation!.unlock(); return Future.value(true); } else { - final String? lockType = _deviceOrientationToLockType(orientations.first); + final String? lockType = + _deviceOrientationToLockType(orientations.first); if (lockType != null) { final Completer completer = Completer(); try { @@ -731,7 +644,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, @@ -839,8 +753,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