diff --git a/DEPS b/DEPS index 9711c8bf0e574..88c515d04607b 100644 --- a/DEPS +++ b/DEPS @@ -277,7 +277,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + '8c2d66fa4e6298894425f5bdd0591bc5b1154c53', + 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'e265c359126b24351f534080fb22edaa159f2215', 'src/flutter/third_party/depot_tools': Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '580b4ff3f5cd0dcaa2eacda28cefe0f45320e8f7', diff --git a/lib/web_ui/dev/test_platform.dart b/lib/web_ui/dev/test_platform.dart index 34954329d30f7..dd871b16ddcaf 100644 --- a/lib/web_ui/dev/test_platform.dart +++ b/lib/web_ui/dev/test_platform.dart @@ -575,6 +575,7 @@ class BrowserPlatform extends PlatformPlugin { // Some of our tests rely on color emoji useColorEmoji: true, canvasKitVariant: "${getCanvasKitVariant()}", + canvasKitBaseUrl: "/canvaskit", }, }); diff --git a/lib/web_ui/flutter_js/src/canvaskit_loader.js b/lib/web_ui/flutter_js/src/canvaskit_loader.js index 64dc6734ec273..eb79fa24b8a52 100644 --- a/lib/web_ui/flutter_js/src/canvaskit_loader.js +++ b/lib/web_ui/flutter_js/src/canvaskit_loader.js @@ -3,14 +3,14 @@ // found in the LICENSE file. import { createWasmInstantiator } from "./instantiate_wasm.js"; -import { joinPathSegments } from "./utils.js"; +import { resolveUrlWithSegments } from "./utils.js"; export const loadCanvasKit = (deps, config, browserEnvironment, canvasKitBaseUrl) => { - if (window.flutterCanvasKit) { - // The user has set this global variable ahead of time, so we just return that. - return Promise.resolve(window.flutterCanvasKit); - } - window.flutterCanvasKitLoaded = new Promise((resolve, reject) => { + window.flutterCanvasKitLoaded = (async () => { + if (window.flutterCanvasKit) { + // The user has set this global variable ahead of time, so we just return that. + return window.flutterCanvasKit; + } const supportsChromiumCanvasKit = browserEnvironment.hasChromiumBreakIterators && browserEnvironment.hasImageCodecs; if (!supportsChromiumCanvasKit && config.canvasKitVariant == "chromium") { throw "Chromium CanvasKit variant specifically requested, but unsupported in this browser"; @@ -18,31 +18,18 @@ export const loadCanvasKit = (deps, config, browserEnvironment, canvasKitBaseUrl const useChromiumCanvasKit = supportsChromiumCanvasKit && (config.canvasKitVariant !== "full"); let baseUrl = canvasKitBaseUrl; if (useChromiumCanvasKit) { - baseUrl = joinPathSegments(baseUrl, "chromium"); + baseUrl = resolveUrlWithSegments(baseUrl, "chromium"); } - let canvasKitUrl = joinPathSegments(baseUrl, "canvaskit.js"); + let canvasKitUrl = resolveUrlWithSegments(baseUrl, "canvaskit.js"); if (deps.flutterTT.policy) { canvasKitUrl = deps.flutterTT.policy.createScriptURL(canvasKitUrl); } - const wasmInstantiator = createWasmInstantiator(joinPathSegments(baseUrl, "canvaskit.wasm")); - const script = document.createElement("script"); - script.src = canvasKitUrl; - if (config.nonce) { - script.nonce = config.nonce; - } - script.addEventListener("load", async () => { - try { - const canvasKit = await CanvasKitInit({ - instantiateWasm: wasmInstantiator, - }); - window.flutterCanvasKit = canvasKit; - resolve(canvasKit); - } catch (e) { - reject(e); - } + const wasmInstantiator = createWasmInstantiator(resolveUrlWithSegments(baseUrl, "canvaskit.wasm")); + const canvasKitModule = await import(canvasKitUrl); + window.flutterCanvasKit = await canvasKitModule.default({ + instantiateWasm: wasmInstantiator, }); - script.addEventListener("error", reject); - document.head.appendChild(script); - }); + return window.flutterCanvasKit; + })(); return window.flutterCanvasKitLoaded; } diff --git a/lib/web_ui/flutter_js/src/entrypoint_loader.js b/lib/web_ui/flutter_js/src/entrypoint_loader.js index 3e789c05e718b..dfc769d12a8af 100644 --- a/lib/web_ui/flutter_js/src/entrypoint_loader.js +++ b/lib/web_ui/flutter_js/src/entrypoint_loader.js @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import { baseUri, joinPathSegments } from "./utils.js"; +import { resolveUrlWithSegments } from "./utils.js"; /** * Handles injecting the main Flutter web entrypoint (main.dart.js), and notifying @@ -37,7 +37,7 @@ export class FlutterEntrypointLoader { * Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`. */ async loadEntrypoint(options) { - const { entrypointUrl = joinPathSegments(baseUri, "main.dart.js"), onEntrypointLoaded, nonce } = + const { entrypointUrl = resolveUrlWithSegments("main.dart.js"), onEntrypointLoaded, nonce } = options || {}; return this._loadJSEntrypoint(entrypointUrl, onEntrypointLoaded, nonce); } @@ -68,7 +68,7 @@ export class FlutterEntrypointLoader { return this._loadWasmEntrypoint(build, deps, entryPointBaseUrl, onEntrypointLoaded); } else { const mainPath = build.mainJsPath ?? "main.dart.js"; - const entrypointUrl = joinPathSegments(baseUri, entryPointBaseUrl, mainPath); + const entrypointUrl = resolveUrlWithSegments(entryPointBaseUrl, mainPath); return this._loadJSEntrypoint(entrypointUrl, onEntrypointLoaded, nonce); } } @@ -148,8 +148,8 @@ export class FlutterEntrypointLoader { this._onEntrypointLoaded = onEntrypointLoaded; const { mainWasmPath, jsSupportRuntimePath } = build; - const moduleUri = joinPathSegments(baseUri, entrypointBaseUrl, mainWasmPath); - let jsSupportRuntimeUri = joinPathSegments(baseUri, entrypointBaseUrl, jsSupportRuntimePath); + const moduleUri = resolveUrlWithSegments(entrypointBaseUrl, mainWasmPath); + let jsSupportRuntimeUri = resolveUrlWithSegments(entrypointBaseUrl, jsSupportRuntimePath); if (this._ttPolicy != null) { jsSupportRuntimeUri = this._ttPolicy.createScriptURL(jsSupportRuntimeUri); } diff --git a/lib/web_ui/flutter_js/src/service_worker_loader.js b/lib/web_ui/flutter_js/src/service_worker_loader.js index 20d9f25f68728..15302f53eb64d 100644 --- a/lib/web_ui/flutter_js/src/service_worker_loader.js +++ b/lib/web_ui/flutter_js/src/service_worker_loader.js @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import { baseUri, joinPathSegments } from "./utils.js"; +import { resolveUrlWithSegments } from "./utils.js"; /** * Wraps `promise` in a timeout of the given `duration` in ms. @@ -78,7 +78,7 @@ export class FlutterServiceWorkerLoader { } const { serviceWorkerVersion, - serviceWorkerUrl = joinPathSegments(baseUri, `flutter_service_worker.js?v=${serviceWorkerVersion}`), + serviceWorkerUrl = resolveUrlWithSegments(`flutter_service_worker.js?v=${serviceWorkerVersion}`), timeoutMillis = 4000, } = settings; // Apply the TrustedTypes policy, if present. diff --git a/lib/web_ui/flutter_js/src/skwasm_loader.js b/lib/web_ui/flutter_js/src/skwasm_loader.js index 241f4e8c81e02..181c65bd7c4f8 100644 --- a/lib/web_ui/flutter_js/src/skwasm_loader.js +++ b/lib/web_ui/flutter_js/src/skwasm_loader.js @@ -3,45 +3,35 @@ // found in the LICENSE file. import { createWasmInstantiator } from "./instantiate_wasm.js"; -import { joinPathSegments } from "./utils.js"; +import { resolveUrlWithSegments } from "./utils.js"; -export const loadSkwasm = (deps, config, browserEnvironment, baseUrl) => { - return new Promise((resolve, reject) => { - let skwasmUrl = joinPathSegments(baseUrl, "skwasm.js"); - if (deps.flutterTT.policy) { - skwasmUrl = deps.flutterTT.policy.createScriptURL(skwasmUrl); - } - const wasmInstantiator = createWasmInstantiator(joinPathSegments(baseUrl, "skwasm.wasm")); - const script = document.createElement("script"); - script.src = skwasmUrl; - if (config.nonce) { - script.nonce = config.nonce; - } - script.addEventListener("load", async () => { - try { - const skwasmInstance = await skwasm({ - instantiateWasm: wasmInstantiator, - locateFile: (fileName, scriptDirectory) => { - // When hosted via a CDN or some other url that is not the same - // origin as the main script of the page, we will fail to create - // a web worker with the .worker.js script. This workaround will - // make sure that the worker JS can be loaded regardless of where - // it is hosted. - const url = scriptDirectory + fileName; - if (url.endsWith(".worker.js")) { - return URL.createObjectURL(new Blob( - [`importScripts("${url}");`], - { "type": "application/javascript" })); - } - return url; - } - }); - resolve(skwasmInstance); - } catch (e) { - reject(e); +export const loadSkwasm = async (deps, config, browserEnvironment, baseUrl) => { + const rawSkwasmUrl = resolveUrlWithSegments(baseUrl, "skwasm.js") + let skwasmUrl = rawSkwasmUrl; + if (deps.flutterTT.policy) { + skwasmUrl = deps.flutterTT.policy.createScriptURL(skwasmUrl); + } + const wasmInstantiator = createWasmInstantiator(resolveUrlWithSegments(baseUrl, "skwasm.wasm")); + const skwasm = await import(skwasmUrl); + return await skwasm.default({ + instantiateWasm: wasmInstantiator, + locateFile: (fileName, scriptDirectory) => { + // When hosted via a CDN or some other url that is not the same + // origin as the main script of the page, we will fail to create + // a web worker with the .worker.js script. This workaround will + // make sure that the worker JS can be loaded regardless of where + // it is hosted. + const url = scriptDirectory + fileName; + if (url.endsWith('.worker.js')) { + return URL.createObjectURL(new Blob( + [`importScripts('${url}');`], + { 'type': 'application/javascript' })); } - }); - script.addEventListener("error", reject); - document.head.appendChild(script); + return url; + }, + // Because of the above workaround, the worker is just a blob and + // can't locate the main script using a relative path to itself, + // so we pass the main script location in. + mainScriptUrlOrBlob: rawSkwasmUrl, }); } diff --git a/lib/web_ui/flutter_js/src/utils.js b/lib/web_ui/flutter_js/src/utils.js index 690783b6d9431..f216b0246ab93 100644 --- a/lib/web_ui/flutter_js/src/utils.js +++ b/lib/web_ui/flutter_js/src/utils.js @@ -2,14 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -export const baseUri = getBaseURI(); - -function getBaseURI() { - const base = document.querySelector("base"); - return (base && base.getAttribute("href")) || ""; +export function resolveUrlWithSegments(...segments) { + return new URL(joinPathSegments(...segments), document.baseURI).toString() } -export function joinPathSegments(...segments) { +function joinPathSegments(...segments) { return segments.filter((segment) => !!segment).map((segment, i) => { if (i === 0) { return stripRightSlashes(segment); @@ -54,5 +51,5 @@ export function getCanvaskitBaseUrl(config, buildConfig) { if (buildConfig.engineRevision && !buildConfig.useLocalCanvasKit) { return joinPathSegments("https://www.gstatic.com/flutter-canvaskit", buildConfig.engineRevision); } - return "/canvaskit"; + return "canvaskit"; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index c41da41b36aac..7a847220972c0 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -259,12 +259,13 @@ extension CanvasKitExtension on CanvasKit { ); } -@JS('window.CanvasKitInit') -external JSAny _CanvasKitInit(CanvasKitInitOptions options); +@JS() +@staticInterop +class CanvasKitModule {} -Future CanvasKitInit(CanvasKitInitOptions options) { - return js_util.promiseToFuture( - _CanvasKitInit(options).toObjectShallow); +extension CanvasKitModuleExtension on CanvasKitModule { + @JS('default') + external JSPromise defaultExport(CanvasKitInitOptions options); } typedef LocateFileCallback = String Function(String file, String unusedBase); @@ -3661,11 +3662,11 @@ String canvasKitWasmModuleUrl(String file, String canvasKitBase) => /// Downloads the CanvasKit JavaScript, then calls `CanvasKitInit` to download /// and intialize the CanvasKit wasm. Future downloadCanvasKit() async { - await _downloadOneOf(_canvasKitJsUrls); + final CanvasKitModule canvasKitModule = await _downloadOneOf(_canvasKitJsUrls); - final CanvasKit canvasKit = await CanvasKitInit(CanvasKitInitOptions( + final CanvasKit canvasKit = (await canvasKitModule.defaultExport(CanvasKitInitOptions( locateFile: createLocateFileCallback(canvasKitWasmModuleUrl), - )); + )).toDart) as CanvasKit; if (canvasKit.ParagraphBuilder.RequiresClientICU() && !browserSupportsCanvaskitChromium) { throw Exception( @@ -3681,10 +3682,12 @@ Future downloadCanvasKit() async { /// downloads it. /// /// If none of the URLs can be downloaded, throws an [Exception]. -Future _downloadOneOf(Iterable urls) async { +Future _downloadOneOf(Iterable urls) async { for (final String url in urls) { - if (await _downloadCanvasKitJs(url)) { - return; + try { + return await _downloadCanvasKitJs(url); + } catch (_) { + continue; } } @@ -3694,36 +3697,15 @@ Future _downloadOneOf(Iterable urls) async { ); } +String _resolveUrl(String url) { + return createDomURL(url, domWindow.document.baseUri).toJSString().toDart; +} + /// Downloads the CanvasKit JavaScript file at [url]. /// /// Returns a [Future] that completes with `true` if the CanvasKit JavaScript /// file was successfully downloaded, or `false` if it failed. -Future _downloadCanvasKitJs(String url) { - final DomHTMLScriptElement canvasKitScript = - createDomHTMLScriptElement(configuration.nonce); - canvasKitScript.src = createTrustedScriptUrl(url); - - final Completer canvasKitLoadCompleter = Completer(); - - late final DomEventListener loadCallback; - late final DomEventListener errorCallback; - - void loadEventHandler(DomEvent _) { - canvasKitScript.remove(); - canvasKitLoadCompleter.complete(true); - } - void errorEventHandler(DomEvent errorEvent) { - canvasKitScript.remove(); - canvasKitLoadCompleter.complete(false); - } - - loadCallback = createDomEventListener(loadEventHandler); - errorCallback = createDomEventListener(errorEventHandler); - - canvasKitScript.addEventListener('load', loadCallback); - canvasKitScript.addEventListener('error', errorCallback); - - domDocument.head!.appendChild(canvasKitScript); - - return canvasKitLoadCompleter.future; +Future _downloadCanvasKitJs(String url) async { + final JSAny scriptUrl = createTrustedScriptUrl(_resolveUrl(url)); + return (await importModule(scriptUrl).toDart) as CanvasKitModule; } diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index b81bba41245f2..3239c62173a94 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -2368,9 +2368,15 @@ extension DomPopStateEventExtension on DomPopStateEvent { dynamic get state => _state?.toObjectDeep; } -@JS() +@JS('URL') @staticInterop -class DomURL {} +class DomURL { + external factory DomURL.arg1(JSString url); + external factory DomURL.arg2(JSString url, JSString? base); +} + +DomURL createDomURL(String url, [String? base]) => + base == null ? DomURL.arg1(url.toJS) : DomURL.arg2(url.toJS, base.toJS); extension DomURLExtension on DomURL { @JS('createObjectURL') @@ -2381,6 +2387,9 @@ extension DomURLExtension on DomURL { @JS('revokeObjectURL') external JSVoid _revokeObjectURL(JSString url); void revokeObjectURL(String url) => _revokeObjectURL(url.toJS); + + @JS('toString') + external JSString toJSString(); } @JS('Blob') @@ -3383,16 +3392,16 @@ final DomTrustedTypePolicy _ttPolicy = domWindow.trustedTypes!.createPolicy( /// Converts a String `url` into a [DomTrustedScriptURL] object when the /// Trusted Types API is available, else returns the unmodified `url`. -Object createTrustedScriptUrl(String url) { +JSAny createTrustedScriptUrl(String url) { if (domWindow.trustedTypes != null) { // Pass `url` through Flutter Engine's TrustedType policy. final DomTrustedScriptURL trustedUrl = _ttPolicy.createScriptURL(url); assert(trustedUrl.url != '', 'URL: $url rejected by TrustedTypePolicy'); - return trustedUrl; + return trustedUrl as JSAny; } - return url; + return url.toJS; } DomMessageChannel createDomMessageChannel() => DomMessageChannel(); diff --git a/lib/web_ui/test/canvaskit/initialization/does_not_mock_module_exports_test.dart b/lib/web_ui/test/canvaskit/initialization/does_not_mock_module_exports_test.dart index b6676f2af4fcf..96e0c8a6d14d5 100644 --- a/lib/web_ui/test/canvaskit/initialization/does_not_mock_module_exports_test.dart +++ b/lib/web_ui/test/canvaskit/initialization/does_not_mock_module_exports_test.dart @@ -18,13 +18,6 @@ void testMain() { // Initialize CanvasKit... await bootstrapAndRunApp(); - // CanvasKitInit should be defined... - expect( - js_util.hasProperty(domWindow, 'CanvasKitInit'), - isTrue, - reason: 'CanvasKitInit should be defined on Window', - ); - // window.exports and window.module should be undefined! expect( js_util.hasProperty(domWindow, 'exports'), diff --git a/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart b/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart index d8960ad204237..bb91d9afe2bc6 100644 --- a/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart +++ b/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart @@ -56,15 +56,7 @@ Future bootstrapAndExtractConfig() { initializeEngine: ([JsFlutterConfiguration? config]) async => configCompleter.complete(config), runApp: () async {} ); - final FlutterLoader? loader = flutter?.loader; - if (loader == null || loader.isAutoStart) { - // TODO(jacksongardner): Unit tests under dart2wasm still use the old way which - // doesn't invoke flutter.js directly, so we autostart here. Once dart2wasm tests - // work with flutter.js, we can remove this code path. - bootstrap.autoStart(); - } else { - loader.didCreateEngineInitializer(bootstrap.prepareEngineInitializer()); - } + flutter!.loader!.didCreateEngineInitializer(bootstrap.prepareEngineInitializer()); return configCompleter.future; } diff --git a/lib/web_ui/test/engine/configuration_test.dart b/lib/web_ui/test/engine/configuration_test.dart index 24f748f42f3ef..7e4ba6caea4f4 100644 --- a/lib/web_ui/test/engine/configuration_test.dart +++ b/lib/web_ui/test/engine/configuration_test.dart @@ -28,10 +28,10 @@ void testMain() { test('legacy constructor initializes with a Js Object', () async { final FlutterConfiguration config = FlutterConfiguration.legacy( js_util.jsify({ - 'canvasKitBaseUrl': 'some_other_url/', + 'canvasKitBaseUrl': '/some_other_url/', }) as JsFlutterConfiguration); - expect(config.canvasKitBaseUrl, 'some_other_url/'); + expect(config.canvasKitBaseUrl, '/some_other_url/'); }); }); @@ -39,13 +39,13 @@ void testMain() { test('throws assertion error if already initialized from JS', () async { final FlutterConfiguration config = FlutterConfiguration.legacy( js_util.jsify({ - 'canvasKitBaseUrl': 'some_other_url/', + 'canvasKitBaseUrl': '/some_other_url/', }) as JsFlutterConfiguration); expect(() { config.setUserConfiguration( js_util.jsify({ - 'canvasKitBaseUrl': 'yet_another_url/', + 'canvasKitBaseUrl': '/yet_another_url/', }) as JsFlutterConfiguration); }, throwsAssertionError); }); @@ -55,10 +55,10 @@ void testMain() { config.setUserConfiguration( js_util.jsify({ - 'canvasKitBaseUrl': 'one_more_url/', + 'canvasKitBaseUrl': '/one_more_url/', }) as JsFlutterConfiguration); - expect(config.canvasKitBaseUrl, 'one_more_url/'); + expect(config.canvasKitBaseUrl, '/one_more_url/'); }); test('can receive non-existing properties without crashing', () async {