Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1c23c8f

Browse files
Reland (x2) "Output .js files as ES6 modules. (#52023)" (#53718)
Second attempt to reland #52023 Fixes since the previous reland attempt: * We need to pass the skwasm main JS URI when loading the module so that it can pass that along to the worker. Since the worker uses the workaround to allow a cross script worker, it has trouble locating the main JS URI in relation to itself in a way that actually works for dynamic imports, so passing it along fixes that issue. * Some of the Google3 tests relied on the relative default canvaskit path. Dynamic module imports seems to not handle relative paths the way we expect, so we do our own URL resolution using the URL constructor before passing it into the dynamic import API. Also cleaned up some of the other relative pathing stuff that we do around the base URI. in flutter.js
1 parent 928d34f commit 1c23c8f

File tree

12 files changed

+97
-146
lines changed

12 files changed

+97
-146
lines changed

DEPS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ allowed_hosts = [
277277
]
278278

279279
deps = {
280-
'src': 'https://github.com/flutter/buildroot.git' + '@' + '8c2d66fa4e6298894425f5bdd0591bc5b1154c53',
280+
'src': 'https://github.com/flutter/buildroot.git' + '@' + 'e265c359126b24351f534080fb22edaa159f2215',
281281

282282
'src/flutter/third_party/depot_tools':
283283
Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '580b4ff3f5cd0dcaa2eacda28cefe0f45320e8f7',

lib/web_ui/dev/test_platform.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ class BrowserPlatform extends PlatformPlugin {
575575
// Some of our tests rely on color emoji
576576
useColorEmoji: true,
577577
canvasKitVariant: "${getCanvasKitVariant()}",
578+
canvasKitBaseUrl: "/canvaskit",
578579
},
579580
});
580581
</script>

lib/web_ui/flutter_js/src/canvaskit_loader.js

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,33 @@
33
// found in the LICENSE file.
44

55
import { createWasmInstantiator } from "./instantiate_wasm.js";
6-
import { joinPathSegments } from "./utils.js";
6+
import { resolveUrlWithSegments } from "./utils.js";
77

88
export const loadCanvasKit = (deps, config, browserEnvironment, canvasKitBaseUrl) => {
9-
if (window.flutterCanvasKit) {
10-
// The user has set this global variable ahead of time, so we just return that.
11-
return Promise.resolve(window.flutterCanvasKit);
12-
}
13-
window.flutterCanvasKitLoaded = new Promise((resolve, reject) => {
9+
window.flutterCanvasKitLoaded = (async () => {
10+
if (window.flutterCanvasKit) {
11+
// The user has set this global variable ahead of time, so we just return that.
12+
return window.flutterCanvasKit;
13+
}
1414
const supportsChromiumCanvasKit = browserEnvironment.hasChromiumBreakIterators && browserEnvironment.hasImageCodecs;
1515
if (!supportsChromiumCanvasKit && config.canvasKitVariant == "chromium") {
1616
throw "Chromium CanvasKit variant specifically requested, but unsupported in this browser";
1717
}
1818
const useChromiumCanvasKit = supportsChromiumCanvasKit && (config.canvasKitVariant !== "full");
1919
let baseUrl = canvasKitBaseUrl;
2020
if (useChromiumCanvasKit) {
21-
baseUrl = joinPathSegments(baseUrl, "chromium");
21+
baseUrl = resolveUrlWithSegments(baseUrl, "chromium");
2222
}
23-
let canvasKitUrl = joinPathSegments(baseUrl, "canvaskit.js");
23+
let canvasKitUrl = resolveUrlWithSegments(baseUrl, "canvaskit.js");
2424
if (deps.flutterTT.policy) {
2525
canvasKitUrl = deps.flutterTT.policy.createScriptURL(canvasKitUrl);
2626
}
27-
const wasmInstantiator = createWasmInstantiator(joinPathSegments(baseUrl, "canvaskit.wasm"));
28-
const script = document.createElement("script");
29-
script.src = canvasKitUrl;
30-
if (config.nonce) {
31-
script.nonce = config.nonce;
32-
}
33-
script.addEventListener("load", async () => {
34-
try {
35-
const canvasKit = await CanvasKitInit({
36-
instantiateWasm: wasmInstantiator,
37-
});
38-
window.flutterCanvasKit = canvasKit;
39-
resolve(canvasKit);
40-
} catch (e) {
41-
reject(e);
42-
}
27+
const wasmInstantiator = createWasmInstantiator(resolveUrlWithSegments(baseUrl, "canvaskit.wasm"));
28+
const canvasKitModule = await import(canvasKitUrl);
29+
window.flutterCanvasKit = await canvasKitModule.default({
30+
instantiateWasm: wasmInstantiator,
4331
});
44-
script.addEventListener("error", reject);
45-
document.head.appendChild(script);
46-
});
32+
return window.flutterCanvasKit;
33+
})();
4734
return window.flutterCanvasKitLoaded;
4835
}

lib/web_ui/flutter_js/src/entrypoint_loader.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import { baseUri, joinPathSegments } from "./utils.js";
5+
import { resolveUrlWithSegments } from "./utils.js";
66

77
/**
88
* Handles injecting the main Flutter web entrypoint (main.dart.js), and notifying
@@ -37,7 +37,7 @@ export class FlutterEntrypointLoader {
3737
* Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`.
3838
*/
3939
async loadEntrypoint(options) {
40-
const { entrypointUrl = joinPathSegments(baseUri, "main.dart.js"), onEntrypointLoaded, nonce } =
40+
const { entrypointUrl = resolveUrlWithSegments("main.dart.js"), onEntrypointLoaded, nonce } =
4141
options || {};
4242
return this._loadJSEntrypoint(entrypointUrl, onEntrypointLoaded, nonce);
4343
}
@@ -68,7 +68,7 @@ export class FlutterEntrypointLoader {
6868
return this._loadWasmEntrypoint(build, deps, entryPointBaseUrl, onEntrypointLoaded);
6969
} else {
7070
const mainPath = build.mainJsPath ?? "main.dart.js";
71-
const entrypointUrl = joinPathSegments(baseUri, entryPointBaseUrl, mainPath);
71+
const entrypointUrl = resolveUrlWithSegments(entryPointBaseUrl, mainPath);
7272
return this._loadJSEntrypoint(entrypointUrl, onEntrypointLoaded, nonce);
7373
}
7474
}
@@ -148,8 +148,8 @@ export class FlutterEntrypointLoader {
148148

149149
this._onEntrypointLoaded = onEntrypointLoaded;
150150
const { mainWasmPath, jsSupportRuntimePath } = build;
151-
const moduleUri = joinPathSegments(baseUri, entrypointBaseUrl, mainWasmPath);
152-
let jsSupportRuntimeUri = joinPathSegments(baseUri, entrypointBaseUrl, jsSupportRuntimePath);
151+
const moduleUri = resolveUrlWithSegments(entrypointBaseUrl, mainWasmPath);
152+
let jsSupportRuntimeUri = resolveUrlWithSegments(entrypointBaseUrl, jsSupportRuntimePath);
153153
if (this._ttPolicy != null) {
154154
jsSupportRuntimeUri = this._ttPolicy.createScriptURL(jsSupportRuntimeUri);
155155
}

lib/web_ui/flutter_js/src/service_worker_loader.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import { baseUri, joinPathSegments } from "./utils.js";
5+
import { resolveUrlWithSegments } from "./utils.js";
66

77
/**
88
* Wraps `promise` in a timeout of the given `duration` in ms.
@@ -78,7 +78,7 @@ export class FlutterServiceWorkerLoader {
7878
}
7979
const {
8080
serviceWorkerVersion,
81-
serviceWorkerUrl = joinPathSegments(baseUri, `flutter_service_worker.js?v=${serviceWorkerVersion}`),
81+
serviceWorkerUrl = resolveUrlWithSegments(`flutter_service_worker.js?v=${serviceWorkerVersion}`),
8282
timeoutMillis = 4000,
8383
} = settings;
8484
// Apply the TrustedTypes policy, if present.

lib/web_ui/flutter_js/src/skwasm_loader.js

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,35 @@
33
// found in the LICENSE file.
44

55
import { createWasmInstantiator } from "./instantiate_wasm.js";
6-
import { joinPathSegments } from "./utils.js";
6+
import { resolveUrlWithSegments } from "./utils.js";
77

8-
export const loadSkwasm = (deps, config, browserEnvironment, baseUrl) => {
9-
return new Promise((resolve, reject) => {
10-
let skwasmUrl = joinPathSegments(baseUrl, "skwasm.js");
11-
if (deps.flutterTT.policy) {
12-
skwasmUrl = deps.flutterTT.policy.createScriptURL(skwasmUrl);
13-
}
14-
const wasmInstantiator = createWasmInstantiator(joinPathSegments(baseUrl, "skwasm.wasm"));
15-
const script = document.createElement("script");
16-
script.src = skwasmUrl;
17-
if (config.nonce) {
18-
script.nonce = config.nonce;
19-
}
20-
script.addEventListener("load", async () => {
21-
try {
22-
const skwasmInstance = await skwasm({
23-
instantiateWasm: wasmInstantiator,
24-
locateFile: (fileName, scriptDirectory) => {
25-
// When hosted via a CDN or some other url that is not the same
26-
// origin as the main script of the page, we will fail to create
27-
// a web worker with the .worker.js script. This workaround will
28-
// make sure that the worker JS can be loaded regardless of where
29-
// it is hosted.
30-
const url = scriptDirectory + fileName;
31-
if (url.endsWith(".worker.js")) {
32-
return URL.createObjectURL(new Blob(
33-
[`importScripts("${url}");`],
34-
{ "type": "application/javascript" }));
35-
}
36-
return url;
37-
}
38-
});
39-
resolve(skwasmInstance);
40-
} catch (e) {
41-
reject(e);
8+
export const loadSkwasm = async (deps, config, browserEnvironment, baseUrl) => {
9+
const rawSkwasmUrl = resolveUrlWithSegments(baseUrl, "skwasm.js")
10+
let skwasmUrl = rawSkwasmUrl;
11+
if (deps.flutterTT.policy) {
12+
skwasmUrl = deps.flutterTT.policy.createScriptURL(skwasmUrl);
13+
}
14+
const wasmInstantiator = createWasmInstantiator(resolveUrlWithSegments(baseUrl, "skwasm.wasm"));
15+
const skwasm = await import(skwasmUrl);
16+
return await skwasm.default({
17+
instantiateWasm: wasmInstantiator,
18+
locateFile: (fileName, scriptDirectory) => {
19+
// When hosted via a CDN or some other url that is not the same
20+
// origin as the main script of the page, we will fail to create
21+
// a web worker with the .worker.js script. This workaround will
22+
// make sure that the worker JS can be loaded regardless of where
23+
// it is hosted.
24+
const url = scriptDirectory + fileName;
25+
if (url.endsWith('.worker.js')) {
26+
return URL.createObjectURL(new Blob(
27+
[`importScripts('${url}');`],
28+
{ 'type': 'application/javascript' }));
4229
}
43-
});
44-
script.addEventListener("error", reject);
45-
document.head.appendChild(script);
30+
return url;
31+
},
32+
// Because of the above workaround, the worker is just a blob and
33+
// can't locate the main script using a relative path to itself,
34+
// so we pass the main script location in.
35+
mainScriptUrlOrBlob: rawSkwasmUrl,
4636
});
4737
}

lib/web_ui/flutter_js/src/utils.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
export const baseUri = getBaseURI();
6-
7-
function getBaseURI() {
8-
const base = document.querySelector("base");
9-
return (base && base.getAttribute("href")) || "";
5+
export function resolveUrlWithSegments(...segments) {
6+
return new URL(joinPathSegments(...segments), document.baseURI).toString()
107
}
118

12-
export function joinPathSegments(...segments) {
9+
function joinPathSegments(...segments) {
1310
return segments.filter((segment) => !!segment).map((segment, i) => {
1411
if (i === 0) {
1512
return stripRightSlashes(segment);
@@ -54,5 +51,5 @@ export function getCanvaskitBaseUrl(config, buildConfig) {
5451
if (buildConfig.engineRevision && !buildConfig.useLocalCanvasKit) {
5552
return joinPathSegments("https://www.gstatic.com/flutter-canvaskit", buildConfig.engineRevision);
5653
}
57-
return "/canvaskit";
54+
return "canvaskit";
5855
}

lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -259,12 +259,13 @@ extension CanvasKitExtension on CanvasKit {
259259
);
260260
}
261261

262-
@JS('window.CanvasKitInit')
263-
external JSAny _CanvasKitInit(CanvasKitInitOptions options);
262+
@JS()
263+
@staticInterop
264+
class CanvasKitModule {}
264265

265-
Future<CanvasKit> CanvasKitInit(CanvasKitInitOptions options) {
266-
return js_util.promiseToFuture<CanvasKit>(
267-
_CanvasKitInit(options).toObjectShallow);
266+
extension CanvasKitModuleExtension on CanvasKitModule {
267+
@JS('default')
268+
external JSPromise<JSAny> defaultExport(CanvasKitInitOptions options);
268269
}
269270

270271
typedef LocateFileCallback = String Function(String file, String unusedBase);
@@ -3661,11 +3662,11 @@ String canvasKitWasmModuleUrl(String file, String canvasKitBase) =>
36613662
/// Downloads the CanvasKit JavaScript, then calls `CanvasKitInit` to download
36623663
/// and intialize the CanvasKit wasm.
36633664
Future<CanvasKit> downloadCanvasKit() async {
3664-
await _downloadOneOf(_canvasKitJsUrls);
3665+
final CanvasKitModule canvasKitModule = await _downloadOneOf(_canvasKitJsUrls);
36653666

3666-
final CanvasKit canvasKit = await CanvasKitInit(CanvasKitInitOptions(
3667+
final CanvasKit canvasKit = (await canvasKitModule.defaultExport(CanvasKitInitOptions(
36673668
locateFile: createLocateFileCallback(canvasKitWasmModuleUrl),
3668-
));
3669+
)).toDart) as CanvasKit;
36693670

36703671
if (canvasKit.ParagraphBuilder.RequiresClientICU() && !browserSupportsCanvaskitChromium) {
36713672
throw Exception(
@@ -3681,10 +3682,12 @@ Future<CanvasKit> downloadCanvasKit() async {
36813682
/// downloads it.
36823683
///
36833684
/// If none of the URLs can be downloaded, throws an [Exception].
3684-
Future<void> _downloadOneOf(Iterable<String> urls) async {
3685+
Future<CanvasKitModule> _downloadOneOf(Iterable<String> urls) async {
36853686
for (final String url in urls) {
3686-
if (await _downloadCanvasKitJs(url)) {
3687-
return;
3687+
try {
3688+
return await _downloadCanvasKitJs(url);
3689+
} catch (_) {
3690+
continue;
36883691
}
36893692
}
36903693

@@ -3694,36 +3697,15 @@ Future<void> _downloadOneOf(Iterable<String> urls) async {
36943697
);
36953698
}
36963699

3700+
String _resolveUrl(String url) {
3701+
return createDomURL(url, domWindow.document.baseUri).toJSString().toDart;
3702+
}
3703+
36973704
/// Downloads the CanvasKit JavaScript file at [url].
36983705
///
36993706
/// Returns a [Future] that completes with `true` if the CanvasKit JavaScript
37003707
/// file was successfully downloaded, or `false` if it failed.
3701-
Future<bool> _downloadCanvasKitJs(String url) {
3702-
final DomHTMLScriptElement canvasKitScript =
3703-
createDomHTMLScriptElement(configuration.nonce);
3704-
canvasKitScript.src = createTrustedScriptUrl(url);
3705-
3706-
final Completer<bool> canvasKitLoadCompleter = Completer<bool>();
3707-
3708-
late final DomEventListener loadCallback;
3709-
late final DomEventListener errorCallback;
3710-
3711-
void loadEventHandler(DomEvent _) {
3712-
canvasKitScript.remove();
3713-
canvasKitLoadCompleter.complete(true);
3714-
}
3715-
void errorEventHandler(DomEvent errorEvent) {
3716-
canvasKitScript.remove();
3717-
canvasKitLoadCompleter.complete(false);
3718-
}
3719-
3720-
loadCallback = createDomEventListener(loadEventHandler);
3721-
errorCallback = createDomEventListener(errorEventHandler);
3722-
3723-
canvasKitScript.addEventListener('load', loadCallback);
3724-
canvasKitScript.addEventListener('error', errorCallback);
3725-
3726-
domDocument.head!.appendChild(canvasKitScript);
3727-
3728-
return canvasKitLoadCompleter.future;
3708+
Future<CanvasKitModule> _downloadCanvasKitJs(String url) async {
3709+
final JSAny scriptUrl = createTrustedScriptUrl(_resolveUrl(url));
3710+
return (await importModule(scriptUrl).toDart) as CanvasKitModule;
37293711
}

lib/web_ui/lib/src/engine/dom.dart

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2368,9 +2368,15 @@ extension DomPopStateEventExtension on DomPopStateEvent {
23682368
dynamic get state => _state?.toObjectDeep;
23692369
}
23702370

2371-
@JS()
2371+
@JS('URL')
23722372
@staticInterop
2373-
class DomURL {}
2373+
class DomURL {
2374+
external factory DomURL.arg1(JSString url);
2375+
external factory DomURL.arg2(JSString url, JSString? base);
2376+
}
2377+
2378+
DomURL createDomURL(String url, [String? base]) =>
2379+
base == null ? DomURL.arg1(url.toJS) : DomURL.arg2(url.toJS, base.toJS);
23742380

23752381
extension DomURLExtension on DomURL {
23762382
@JS('createObjectURL')
@@ -2381,6 +2387,9 @@ extension DomURLExtension on DomURL {
23812387
@JS('revokeObjectURL')
23822388
external JSVoid _revokeObjectURL(JSString url);
23832389
void revokeObjectURL(String url) => _revokeObjectURL(url.toJS);
2390+
2391+
@JS('toString')
2392+
external JSString toJSString();
23842393
}
23852394

23862395
@JS('Blob')
@@ -3383,16 +3392,16 @@ final DomTrustedTypePolicy _ttPolicy = domWindow.trustedTypes!.createPolicy(
33833392

33843393
/// Converts a String `url` into a [DomTrustedScriptURL] object when the
33853394
/// Trusted Types API is available, else returns the unmodified `url`.
3386-
Object createTrustedScriptUrl(String url) {
3395+
JSAny createTrustedScriptUrl(String url) {
33873396
if (domWindow.trustedTypes != null) {
33883397
// Pass `url` through Flutter Engine's TrustedType policy.
33893398
final DomTrustedScriptURL trustedUrl = _ttPolicy.createScriptURL(url);
33903399

33913400
assert(trustedUrl.url != '', 'URL: $url rejected by TrustedTypePolicy');
33923401

3393-
return trustedUrl;
3402+
return trustedUrl as JSAny;
33943403
}
3395-
return url;
3404+
return url.toJS;
33963405
}
33973406

33983407
DomMessageChannel createDomMessageChannel() => DomMessageChannel();

lib/web_ui/test/canvaskit/initialization/does_not_mock_module_exports_test.dart

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@ void testMain() {
1818
// Initialize CanvasKit...
1919
await bootstrapAndRunApp();
2020

21-
// CanvasKitInit should be defined...
22-
expect(
23-
js_util.hasProperty(domWindow, 'CanvasKitInit'),
24-
isTrue,
25-
reason: 'CanvasKitInit should be defined on Window',
26-
);
27-
2821
// window.exports and window.module should be undefined!
2922
expect(
3023
js_util.hasProperty(domWindow, 'exports'),

0 commit comments

Comments
 (0)