blob: 704896a6e062fed586c7f5542829687d903eb457 [file] [log] [blame]
// 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.
@JS()
library canvaskit_initialization;
import 'dart:async';
import 'dart:html' as html;
import 'dart:js' as js;
import 'package:js/js.dart';
import '../../engine.dart' show kProfileMode;
import '../browser_detection.dart';
import '../configuration.dart';
import '../dom_renderer.dart';
import 'canvaskit_api.dart';
import 'fonts.dart';
/// A JavaScript entrypoint that allows developer to set rendering backend
/// at runtime before launching the application.
@JS('window.flutterWebRenderer')
external String? get requestedRendererType;
/// Whether to use CanvasKit as the rendering backend.
bool get useCanvasKit => FlutterConfiguration.flutterWebAutoDetect ? _detectRenderer() : FlutterConfiguration.useSkia;
/// Returns true if CanvasKit is used.
///
/// Otherwise, returns false.
bool _detectRenderer() {
if (requestedRendererType != null) {
return requestedRendererType! == 'canvaskit';
}
// If requestedRendererType is not specified, use CanvasKit for desktop and
// html for mobile.
return isDesktop;
}
String get canvasKitBuildUrl =>
configuration.canvasKitBaseUrl + (kProfileMode ? 'profiling/' : '');
String get canvasKitJavaScriptBindingsUrl =>
canvasKitBuildUrl + 'canvaskit.js';
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({String? canvasKitBase}) {
final Completer<void> canvasKitCompleter = Completer<void>();
if (windowFlutterCanvasKit != null) {
canvasKit = windowFlutterCanvasKit!;
canvasKitCompleter.complete();
} else {
_startDownloadingCanvasKit(canvasKitBase);
_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');
domRenderer.renderScene(skiaSceneHost);
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'] as js.JsFunction;
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;
/// Initializes [skiaFontCollection].
void ensureSkiaFontCollectionInitialized() {
_skiaFontCollection ??= SkiaFontCollection();
}
/// The scene host, where the root canvas and overlay canvases are added to.
html.Element? skiaSceneHost;