Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 98 additions & 3 deletions lib/web_ui/lib/src/engine/canvaskit/initialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>? _canvasKitLoaded;

/// The currently used base URL for loading CanvasKit.
String? _currentCanvasKitBase;

/// Initialize CanvasKit.
///
/// This calls `CanvasKitInit` and assigns the global [canvasKit] object.
Future<void> initializeCanvasKit() {
Future<void> initializeCanvasKit({String? canvasKitBase}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where to we get and pass the value of canvasKitBase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking either pass it in setUpCanvasKitTests or read it from a <meta> tag in index.html

final Completer<void> canvasKitCompleter = Completer<void>();
if (windowFlutterCanvasKit != null) {
canvasKit = windowFlutterCanvasKit!;
canvasKitCompleter.complete();
} else {
domRenderer.canvasKitLoaded!.then((_) {
_startDownloadingCanvasKit(canvasKitBase);
_canvasKitLoaded!.then((_) {
final CanvasKitInitPromise canvasKitInitPromise =
CanvasKitInit(CanvasKitInitOptions(
locateFile: js.allowInterop(
Expand All @@ -123,6 +133,91 @@ Future<void> 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<void> canvasKitLoadCompleter = Completer<void>();
_canvasKitLoaded = canvasKitLoadCompleter.future;

late StreamSubscription<html.Event> 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(<String, dynamic>{
'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', <dynamic>[js.context, 'exports', exportsAccessor]);
}
if (js.context['module'] == null) {
final js.JsObject moduleAccessor = js.JsObject.jsify(<String, dynamic>{
'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', <dynamic>[js.context, 'module', moduleAccessor]);
}
html.document.head!.append(_canvasKitScript!);
}
}

/// The Skia font collection.
SkiaFontCollection get skiaFontCollection => _skiaFontCollection!;
SkiaFontCollection? _skiaFontCollection;
Expand Down
102 changes: 7 additions & 95 deletions lib/web_ui/lib/src/engine/dom_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@

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;

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';
Expand Down Expand Up @@ -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<void>? get canvasKitLoaded => _canvasKitLoaded;
Future<void>? _canvasKitLoaded;

/// The element that contains the [sceneElement].
///
/// This element is created and inserted in the HTML DOM once. It is never
Expand Down Expand Up @@ -140,7 +130,6 @@ class DomRenderer {
_glassPaneElement,
_styleElement,
_viewportMeta,
_canvasKitScript,
]);
});
}
Expand Down Expand Up @@ -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(),
);
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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<void> canvasKitLoadCompleter = Completer<void>();
_canvasKitLoaded = canvasKitLoadCompleter.future;

late StreamSubscription<html.Event> 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(<String, dynamic>{
'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',
<dynamic>[js.context, 'exports', exportsAccessor]);
}
if (js.context['module'] == null) {
final js.JsObject moduleAccessor = js.JsObject.jsify(<String, dynamic>{
'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', <dynamic>[js.context, 'module', moduleAccessor]);
}
html.document.head!.append(_canvasKitScript!);
}

if (html.window.visualViewport != null) {
_resizeSubscription =
html.window.visualViewport!.onResize.listen(_metricsDidChange);
Expand Down Expand Up @@ -652,7 +564,8 @@ class DomRenderer {
screenOrientation!.unlock();
return Future<bool>.value(true);
} else {
final String? lockType = _deviceOrientationToLockType(orientations.first);
final String? lockType =
_deviceOrientationToLockType(orientations.first);
if (lockType != null) {
final Completer<bool> completer = Completer<bool>();
try {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down