From 2b5ef64fc4198f6ec541774b1127eafea220107b Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 5 May 2025 10:56:26 -0700 Subject: [PATCH] Skwasm heavy (#166619) This produces a build of Skwasm that works on Firefox and Safari. This means we use `SkAnimatedImage` for animated gifs and webps and use builtin ICU data in Skia. I have unit test suites for Safari and Firefox with dart2wasm and both `ui` and `engine` test sets. However, there are a few issues with running these on CI: * Safari+dart2wasm doesn't work yet until the CI bots are upgraded to macOS 15, so these have been disabled on CI for now (but you can run the unit test suite locally). * Firefox+ui doesn't work because our Linux bots have no GPU and therefore no WebGL2 support, so that one is disabled. Firefox+dart2wasm with the `engine` suite is enabled on CI though. I did make some changes to the host page for our unit test harness so that Safari actually works though. Even though we're not running on CI, you can still run locally if you have macOS 15. --- .../ci/builders/linux_web_engine_test.json | 68 ++------ .../ci/licenses_golden/licenses_flutter | 10 ++ engine/src/flutter/lib/web_ui/dev/build.dart | 2 + .../flutter/lib/web_ui/dev/felt_config.dart | 4 + .../src/flutter/lib/web_ui/dev/firefox.dart | 15 +- .../lib/web_ui/dev/generate_builder_json.dart | 7 +- .../web_ui/dev/steps/copy_artifacts_step.dart | 4 + .../flutter/lib/web_ui/dev/test_platform.dart | 1 + .../flutter_js/src/browser_environment.js | 38 ++++- .../lib/web_ui/flutter_js/src/loader.js | 9 +- .../web_ui/flutter_js/src/skwasm_loader.js | 6 +- .../lib/web_ui/flutter_js/src/types.d.ts | 34 ++-- .../src/engine/html_image_element_codec.dart | 4 +- .../lib/src/engine/skwasm/skwasm_impl.dart | 1 + .../src/engine/skwasm/skwasm_impl/codecs.dart | 74 ++++++++- .../engine/skwasm/skwasm_impl/paragraph.dart | 4 +- .../skwasm_impl/raw/raw_animated_image.dart | 47 ++++++ .../skwasm/skwasm_impl/raw/skwasm_module.dart | 3 + .../engine/skwasm/skwasm_impl/renderer.dart | 66 +++++--- engine/src/flutter/lib/web_ui/skwasm/BUILD.gn | 145 ++++++++++-------- .../lib/web_ui/skwasm/animated_image.cpp | 77 ++++++++++ .../lib/web_ui/skwasm/animated_image_stub.cpp | 49 ++++++ .../web_ui/skwasm/text/paragraph_builder.cpp | 34 ---- .../text/paragraph_builder_builtin_icu.cpp | 48 ++++++ .../text/paragraph_builder_client_icu.cpp | 49 ++++++ .../flutter/lib/web_ui/test/felt_config.yaml | 36 +++++ .../lib/web_ui/test/ui/image_golden_test.dart | 90 +++++++++-- .../flutter/third_party/canvaskit/BUILD.gn | 63 ++++++-- engine/src/flutter/web_sdk/BUILD.gn | 4 + .../web_engine_tester/lib/static/host.dart | 19 +-- 30 files changed, 758 insertions(+), 253 deletions(-) create mode 100644 engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_animated_image.dart create mode 100644 engine/src/flutter/lib/web_ui/skwasm/animated_image.cpp create mode 100644 engine/src/flutter/lib/web_ui/skwasm/animated_image_stub.cpp create mode 100644 engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder_builtin_icu.cpp create mode 100644 engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder_client_icu.cpp diff --git a/engine/src/flutter/ci/builders/linux_web_engine_test.json b/engine/src/flutter/ci/builders/linux_web_engine_test.json index 8822049fc02..0b6f268918d 100644 --- a/engine/src/flutter/ci/builders/linux_web_engine_test.json +++ b/engine/src/flutter/ci/builders/linux_web_engine_test.json @@ -193,25 +193,12 @@ "--suite=chrome-dart2js-canvaskit-ui", "--suite=chrome-full-dart2js-canvaskit-canvaskit", "--suite=chrome-full-dart2js-canvaskit-ui", - "--suite=edge-dart2js-canvaskit-engine", - "--suite=edge-dart2js-canvaskit-canvaskit", - "--suite=edge-dart2js-canvaskit-ui", - "--suite=edge-full-dart2js-canvaskit-canvaskit", - "--suite=edge-full-dart2js-canvaskit-ui", - "--suite=firefox-dart2js-canvaskit-engine", - "--suite=firefox-dart2js-canvaskit-canvaskit", - "--suite=firefox-dart2js-canvaskit-ui", - "--suite=safari-dart2js-canvaskit-engine", - "--suite=safari-dart2js-canvaskit-canvaskit", - "--suite=safari-dart2js-canvaskit-ui", "--suite=chrome-dart2wasm-canvaskit-engine", "--suite=chrome-coi-dart2wasm-skwasm-ui", "--suite=chrome-force-st-dart2wasm-skwasm-ui", "--suite=chrome-fallbacks", "--suite=chrome-coi-fallbacks", - "--suite=chrome-force-st-fallbacks", - "--suite=firefox-fallbacks", - "--suite=safari-fallbacks" + "--suite=chrome-force-st-fallbacks" ], "script": "flutter/lib/web_ui/dev/felt" }, @@ -331,6 +318,7 @@ "web_tests/test_bundles/dart2js-canvaskit-engine", "web_tests/test_bundles/dart2js-canvaskit-canvaskit", "web_tests/test_bundles/dart2js-canvaskit-ui", + "web_tests/test_bundles/dart2wasm-canvaskit-engine", "web_tests/test_bundles/fallbacks" ], "test_dependencies": [ @@ -349,30 +337,11 @@ "parameters": [ "test", "--copy-artifacts", - "--suite=chrome-dart2js-canvaskit-engine", - "--suite=chrome-dart2js-canvaskit-canvaskit", - "--suite=chrome-dart2js-canvaskit-ui", - "--suite=chrome-full-dart2js-canvaskit-canvaskit", - "--suite=chrome-full-dart2js-canvaskit-ui", - "--suite=edge-dart2js-canvaskit-engine", - "--suite=edge-dart2js-canvaskit-canvaskit", - "--suite=edge-dart2js-canvaskit-ui", - "--suite=edge-full-dart2js-canvaskit-canvaskit", - "--suite=edge-full-dart2js-canvaskit-ui", "--suite=firefox-dart2js-canvaskit-engine", "--suite=firefox-dart2js-canvaskit-canvaskit", "--suite=firefox-dart2js-canvaskit-ui", - "--suite=safari-dart2js-canvaskit-engine", - "--suite=safari-dart2js-canvaskit-canvaskit", - "--suite=safari-dart2js-canvaskit-ui", - "--suite=chrome-dart2wasm-canvaskit-engine", - "--suite=chrome-coi-dart2wasm-skwasm-ui", - "--suite=chrome-force-st-dart2wasm-skwasm-ui", - "--suite=chrome-fallbacks", - "--suite=chrome-coi-fallbacks", - "--suite=chrome-force-st-fallbacks", - "--suite=firefox-fallbacks", - "--suite=safari-fallbacks" + "--suite=firefox-dart2wasm-canvaskit-engine", + "--suite=firefox-fallbacks" ], "script": "flutter/lib/web_ui/dev/felt" }, @@ -403,6 +372,15 @@ ], "script": "flutter/lib/web_ui/dev/felt" }, + { + "name": "run suite firefox-dart2wasm-canvaskit-engine", + "parameters": [ + "test", + "--run", + "--suite=firefox-dart2wasm-canvaskit-engine" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, { "name": "run suite firefox-fallbacks", "parameters": [ @@ -444,29 +422,9 @@ "parameters": [ "test", "--copy-artifacts", - "--suite=chrome-dart2js-canvaskit-engine", - "--suite=chrome-dart2js-canvaskit-canvaskit", - "--suite=chrome-dart2js-canvaskit-ui", - "--suite=chrome-full-dart2js-canvaskit-canvaskit", - "--suite=chrome-full-dart2js-canvaskit-ui", - "--suite=edge-dart2js-canvaskit-engine", - "--suite=edge-dart2js-canvaskit-canvaskit", - "--suite=edge-dart2js-canvaskit-ui", - "--suite=edge-full-dart2js-canvaskit-canvaskit", - "--suite=edge-full-dart2js-canvaskit-ui", - "--suite=firefox-dart2js-canvaskit-engine", - "--suite=firefox-dart2js-canvaskit-canvaskit", - "--suite=firefox-dart2js-canvaskit-ui", "--suite=safari-dart2js-canvaskit-engine", "--suite=safari-dart2js-canvaskit-canvaskit", "--suite=safari-dart2js-canvaskit-ui", - "--suite=chrome-dart2wasm-canvaskit-engine", - "--suite=chrome-coi-dart2wasm-skwasm-ui", - "--suite=chrome-force-st-dart2wasm-skwasm-ui", - "--suite=chrome-fallbacks", - "--suite=chrome-coi-fallbacks", - "--suite=chrome-force-st-fallbacks", - "--suite=firefox-fallbacks", "--suite=safari-fallbacks" ], "script": "flutter/lib/web_ui/dev/felt" diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 1ff4c1b6f86..b6c2992eb7e 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -52200,6 +52200,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph. ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path_metrics.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_animated_image.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_filters.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart + ../../../flutter/LICENSE @@ -52271,6 +52272,8 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/platform_view_registry ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/plugins.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/testing.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/window.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/animated_image.cpp + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/animated_image_stub.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/canvas.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/contour_measure.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/data.cpp + ../../../flutter/LICENSE @@ -52291,6 +52294,8 @@ ORIGIN: ../../../flutter/lib/web_ui/skwasm/surface.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/line_metrics.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/paragraph.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/paragraph_builder.cpp + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/paragraph_builder_builtin_icu.cpp + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/paragraph_builder_client_icu.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/paragraph_style.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/strut_style.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/text_style.cpp + ../../../flutter/LICENSE @@ -55199,6 +55204,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.da FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path_metrics.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_animated_image.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_filters.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart @@ -55270,6 +55276,8 @@ FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/platform_view_registry.d FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/plugins.dart FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/testing.dart FILE: ../../../flutter/lib/web_ui/lib/window.dart +FILE: ../../../flutter/lib/web_ui/skwasm/animated_image.cpp +FILE: ../../../flutter/lib/web_ui/skwasm/animated_image_stub.cpp FILE: ../../../flutter/lib/web_ui/skwasm/canvas.cpp FILE: ../../../flutter/lib/web_ui/skwasm/contour_measure.cpp FILE: ../../../flutter/lib/web_ui/skwasm/data.cpp @@ -55290,6 +55298,8 @@ FILE: ../../../flutter/lib/web_ui/skwasm/surface.h FILE: ../../../flutter/lib/web_ui/skwasm/text/line_metrics.cpp FILE: ../../../flutter/lib/web_ui/skwasm/text/paragraph.cpp FILE: ../../../flutter/lib/web_ui/skwasm/text/paragraph_builder.cpp +FILE: ../../../flutter/lib/web_ui/skwasm/text/paragraph_builder_builtin_icu.cpp +FILE: ../../../flutter/lib/web_ui/skwasm/text/paragraph_builder_client_icu.cpp FILE: ../../../flutter/lib/web_ui/skwasm/text/paragraph_style.cpp FILE: ../../../flutter/lib/web_ui/skwasm/text/strut_style.cpp FILE: ../../../flutter/lib/web_ui/skwasm/text/text_style.cpp diff --git a/engine/src/flutter/lib/web_ui/dev/build.dart b/engine/src/flutter/lib/web_ui/dev/build.dart index 13d8e6a3f4b..c522d13fbca 100644 --- a/engine/src/flutter/lib/web_ui/dev/build.dart +++ b/engine/src/flutter/lib/web_ui/dev/build.dart @@ -22,6 +22,8 @@ const Map targetAliases = { 'flutter/third_party/canvaskit:canvaskit_experimental_webparagraph_group', 'skwasm': 'flutter/third_party/canvaskit:skwasm_group', 'skwasm_st': 'flutter/third_party/canvaskit:skwasm_st_group', + 'skwasm_heavy': 'flutter/third_party/canvaskit:skwasm_heavy_group', + 'skwasm_heavy_st': 'flutter/third_party/canvaskit:skwasm_heavy_st_group', 'archive': 'flutter/web_sdk:flutter_web_sdk_archive', }; diff --git a/engine/src/flutter/lib/web_ui/dev/felt_config.dart b/engine/src/flutter/lib/web_ui/dev/felt_config.dart index d4694b88045..d873e60f7a3 100644 --- a/engine/src/flutter/lib/web_ui/dev/felt_config.dart +++ b/engine/src/flutter/lib/web_ui/dev/felt_config.dart @@ -44,6 +44,7 @@ class RunConfiguration { this.variant, this.crossOriginIsolated, this.forceSingleThreadedSkwasm, + this.wasmAllowList, ); final String name; @@ -52,6 +53,7 @@ class RunConfiguration { final CanvasKitVariant? variant; final bool crossOriginIsolated; final bool forceSingleThreadedSkwasm; + final Map wasmAllowList; } class ArtifactDependencies { @@ -190,6 +192,7 @@ class FeltConfig { final bool crossOriginIsolated = runConfigYaml['cross-origin-isolated'] as bool? ?? false; final bool forceSingleThreadedSkwasm = runConfigYaml['force-single-threaded-skwasm'] as bool? ?? false; + final YamlMap wasmAllowList = (runConfigYaml['wasm-allow-list'] as YamlMap?) ?? YamlMap(); final RunConfiguration runConfig = RunConfiguration( name, browser, @@ -197,6 +200,7 @@ class FeltConfig { variant, crossOriginIsolated, forceSingleThreadedSkwasm, + wasmAllowList.cast(), ); runConfigs.add(runConfig); if (runConfigsByName.containsKey(name)) { diff --git a/engine/src/flutter/lib/web_ui/dev/firefox.dart b/engine/src/flutter/lib/web_ui/dev/firefox.dart index a1e7b7951ca..63590b62c2a 100644 --- a/engine/src/flutter/lib/web_ui/dev/firefox.dart +++ b/engine/src/flutter/lib/web_ui/dev/firefox.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as path; @@ -65,6 +66,8 @@ class Firefox extends Browser { user_pref("browser.shell.checkDefaultBrowser", false); user_pref("dom.disable_open_during_load", false); user_pref("dom.max_script_run_time", 0); +user_pref("trailhead.firstrun.branches", "nofirstrun-empty"); +user_pref("browser.aboutwelcome.enabled", false); '''; final Directory temporaryProfileDirectory = Directory( @@ -80,7 +83,6 @@ user_pref("dom.max_script_run_time", 0); temporaryProfileDirectory.createSync(recursive: true); File(path.join(temporaryProfileDirectory.path, 'prefs.js')).writeAsStringSync(profile); - final bool isMac = Platform.isMacOS; final List args = [ url.toString(), '--profile', @@ -88,13 +90,18 @@ user_pref("dom.max_script_run_time", 0); if (!debug) '--headless', '-width $kMaxScreenshotWidth', '-height $kMaxScreenshotHeight', - // On Mac Firefox uses the -- option prefix, while elsewhere it uses the - prefix. - '${isMac ? '-' : ''}-new-window', - '${isMac ? '-' : ''}-new-instance', + '-new-window', + '-new-instance', '--start-debugger-server $kDevtoolsPort', ]; final Process process = await Process.start(installation.executable, args); + process.stdout + .transform(const Utf8Decoder(allowMalformed: true)) + .listen((String string) => print('[Firefox:stdout] $string')); + process.stderr + .transform(const Utf8Decoder(allowMalformed: true)) + .listen((String string) => print('[Firefox:stderr] $string')); remoteDebuggerCompleter.complete( getRemoteDebuggerUrl(Uri.parse('http://localhost:$kDevtoolsPort')), diff --git a/engine/src/flutter/lib/web_ui/dev/generate_builder_json.dart b/engine/src/flutter/lib/web_ui/dev/generate_builder_json.dart index 7cddfa3edef..aec873e7ce8 100644 --- a/engine/src/flutter/lib/web_ui/dev/generate_builder_json.dart +++ b/engine/src/flutter/lib/web_ui/dev/generate_builder_json.dart @@ -114,8 +114,9 @@ class GenerateBuilderJsonCommand extends Command { String? specificOS, String? cpu, }) { - final enabledSuites = suites.where((suite) => suite.enableCi); - final filteredSuites = enabledSuites.where((suite) => suite.runConfig.browser == browser); + final filteredSuites = suites.where( + (suite) => suite.enableCi && suite.runConfig.browser == browser, + ); final bundles = filteredSuites.map((suite) => suite.testBundle).toSet(); return { 'name': '$platform run ${browser.name} suites', @@ -149,7 +150,7 @@ class GenerateBuilderJsonCommand extends Command { 'parameters': [ 'test', '--copy-artifacts', - for (final TestSuite suite in enabledSuites) '--suite=${suite.name}', + for (final TestSuite suite in filteredSuites) '--suite=${suite.name}', ], 'script': 'flutter/lib/web_ui/dev/felt', }, diff --git a/engine/src/flutter/lib/web_ui/dev/steps/copy_artifacts_step.dart b/engine/src/flutter/lib/web_ui/dev/steps/copy_artifacts_step.dart index 16c7cc4c45a..589214930b7 100644 --- a/engine/src/flutter/lib/web_ui/dev/steps/copy_artifacts_step.dart +++ b/engine/src/flutter/lib/web_ui/dev/steps/copy_artifacts_step.dart @@ -77,6 +77,7 @@ class CopyArtifactsStep implements PipelineStep { final String canvaskitSourceDirectory; final String canvaskitChromiumSourceDirectory; final String skwasmSourceDirectory; + final String skwasmHeavySourceDirectory; switch (source) { case LocalArtifactSource(:final mode): final buildDirectory = getBuildDirectoryForRuntimeMode(mode).path; @@ -88,6 +89,7 @@ class CopyArtifactsStep implements PipelineStep { canvaskitSourceDirectory = pathlib.join(buildDirectory, 'canvaskit'); canvaskitChromiumSourceDirectory = pathlib.join(buildDirectory, 'canvaskit_chromium'); skwasmSourceDirectory = pathlib.join(buildDirectory, 'skwasm'); + skwasmHeavySourceDirectory = pathlib.join(buildDirectory, 'skwasm_heavy'); case GcsArtifactSource(:final realm): final artifactsDirectory = (await _downloadArtifacts(realm)).path; @@ -104,6 +106,7 @@ class CopyArtifactsStep implements PipelineStep { 'chromium', ); skwasmSourceDirectory = pathlib.join(artifactsDirectory, 'canvaskit'); + skwasmHeavySourceDirectory = pathlib.join(artifactsDirectory, 'canvaskit'); } await environment.webTestsArtifactsDir.create(recursive: true); @@ -131,6 +134,7 @@ class CopyArtifactsStep implements PipelineStep { if (artifactDeps.skwasm) { copied.add('Skwasm'); await copyWasmLibrary('skwasm', skwasmSourceDirectory, 'canvaskit'); + await copyWasmLibrary('skwasm_heavy', skwasmHeavySourceDirectory, 'canvaskit'); } print('Copied artifacts: ${copied.join(', ')}'); } diff --git a/engine/src/flutter/lib/web_ui/dev/test_platform.dart b/engine/src/flutter/lib/web_ui/dev/test_platform.dart index 0a1f4cad1a5..003bd7ae00e 100644 --- a/engine/src/flutter/lib/web_ui/dev/test_platform.dart +++ b/engine/src/flutter/lib/web_ui/dev/test_platform.dart @@ -536,6 +536,7 @@ class BrowserPlatform extends PlatformPlugin { canvasKitVariant: "${getCanvasKitVariant()}", canvasKitBaseUrl: "/canvaskit", forceSingleThreadedSkwasm: ${suite.runConfig.forceSingleThreadedSkwasm}, + wasmAllowList: ${jsonEncode(suite.runConfig.wasmAllowList)}, }, }); diff --git a/engine/src/flutter/lib/web_ui/flutter_js/src/browser_environment.js b/engine/src/flutter/lib/web_ui/flutter_js/src/browser_environment.js index 64baabc0242..3284f9bf716 100644 --- a/engine/src/flutter/lib/web_ui/flutter_js/src/browser_environment.js +++ b/engine/src/flutter/lib/web_ui/flutter_js/src/browser_environment.js @@ -2,13 +2,36 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -const isBlink = () => { - return (navigator.vendor === 'Google Inc.') || - (navigator.agent === 'Edg/'); +/** @type {import("./types").WasmAllowList} */ +export const defaultWasmSupport = { + "blink": true, + "gecko": false, + "webkit": false, + "unknown": false, } +/** + * @returns {import("./types").BrowserEngine} + */ +const getBrowserEngine = () => { + if ((navigator.vendor === 'Google Inc.') || + (navigator.userAgent.includes('Edg/'))) { + return "blink"; + } + if (navigator.vendor === "Apple Computer, Inc.") { + return "webkit"; + } + if (navigator.vendor === "" && navigator.userAgent.includes('Firefox')) { + return "gecko"; + } + return "unknown"; +} + +/** @type {import("./types").BrowserEnvironment} */ +const browserEngine = getBrowserEngine(); + const hasImageCodecs = () => { - if (typeof ImageDecoder === 'undefined') { + if (typeof ImageDecoder === "undefined") { return false; } // TODO(yjbanov): https://github.com/flutter/flutter/issues/122761 @@ -18,7 +41,7 @@ const hasImageCodecs = () => { // up potentially incompatible implementations of ImagdeDecoder API. Instead, // when a new browser engine launches the API, we'll evaluate it and enable it // explicitly. - return isBlink(); + return browserEngine === "blink"; } const hasChromiumBreakIterators = () => { @@ -35,10 +58,9 @@ const supportsWasmGC = () => { return WebAssembly.validate(new Uint8Array(bytes)); } -/** - * @returns {import("./types").BrowserEnvironment} - */ +/** @type {import("./types").BrowserEnvironment} */ export const browserEnvironment = { + browserEngine: browserEngine, hasImageCodecs: hasImageCodecs(), hasChromiumBreakIterators: hasChromiumBreakIterators(), supportsWasmGC: supportsWasmGC(), diff --git a/engine/src/flutter/lib/web_ui/flutter_js/src/loader.js b/engine/src/flutter/lib/web_ui/flutter_js/src/loader.js index 97a6a4cd0bc..d49ba7b52de 100644 --- a/engine/src/flutter/lib/web_ui/flutter_js/src/loader.js +++ b/engine/src/flutter/lib/web_ui/flutter_js/src/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 { browserEnvironment } from './browser_environment.js'; +import { browserEnvironment, defaultWasmSupport } from './browser_environment.js'; import { FlutterEntrypointLoader } from './entrypoint_loader.js'; import { FlutterServiceWorkerLoader } from './service_worker_loader.js'; import { FlutterTrustedTypesPolicy } from './trusted_types.js'; @@ -55,7 +55,7 @@ export class FlutterLoader { * Settings for the service worker to be loaded. Can pass `undefined` or * `null` to not launch a service worker at all. * @param {import("/.types".OnEntryPointLoadedCallback)} options.onEntrypointLoaded - * An optional callback to invoke + * An optional callback to invoke * @param {string} options.nonce * A nonce to be applied to the main JS script when loading it, which may * be required by the sites Content-Security-Policy. @@ -76,12 +76,11 @@ export class FlutterLoader { throw "FlutterLoader.load requires _flutter.buildConfig to be set"; } + const enableWasm = config.wasmAllowList?.[browserEnvironment.browserEngine] ?? defaultWasmSupport[browserEnvironment.browserEngine]; const rendererIsCompatible = (renderer) => { switch (renderer) { case "skwasm": - return browserEnvironment.hasChromiumBreakIterators - && browserEnvironment.hasImageCodecs - && browserEnvironment.supportsWasmGC; + return browserEnvironment.supportsWasmGC && enableWasm; default: return true; } diff --git a/engine/src/flutter/lib/web_ui/flutter_js/src/skwasm_loader.js b/engine/src/flutter/lib/web_ui/flutter_js/src/skwasm_loader.js index 39928f82abd..d2ae8a8ae18 100644 --- a/engine/src/flutter/lib/web_ui/flutter_js/src/skwasm_loader.js +++ b/engine/src/flutter/lib/web_ui/flutter_js/src/skwasm_loader.js @@ -6,12 +6,14 @@ import { createWasmInstantiator } from "./instantiate_wasm.js"; import { resolveUrlWithSegments } from "./utils.js"; export const loadSkwasm = async (deps, config, browserEnvironment, baseUrl) => { - const rawSkwasmUrl = resolveUrlWithSegments(baseUrl, 'skwasm.js') + const needsHeavy = (!browserEnvironment.hasImageCodecs || !browserEnvironment.hasChromiumBreakIterators) + const fileStem = needsHeavy ? 'skwasm_heavy' : 'skwasm'; + const rawSkwasmUrl = resolveUrlWithSegments(baseUrl, `${fileStem}.js`) let skwasmUrl = rawSkwasmUrl; if (deps.flutterTT.policy) { skwasmUrl = deps.flutterTT.policy.createScriptURL(skwasmUrl); } - const wasmInstantiator = createWasmInstantiator(resolveUrlWithSegments(baseUrl, 'skwasm.wasm')); + const wasmInstantiator = createWasmInstantiator(resolveUrlWithSegments(baseUrl, `${fileStem}.wasm`)); const skwasm = await import(skwasmUrl); return await skwasm.default({ skwasmSingleThreaded: !browserEnvironment.crossOriginIsolated || config.forceSingleThreadedSkwasm, diff --git a/engine/src/flutter/lib/web_ui/flutter_js/src/types.d.ts b/engine/src/flutter/lib/web_ui/flutter_js/src/types.d.ts index bb07a64f505..ccde8e1215f 100644 --- a/engine/src/flutter/lib/web_ui/flutter_js/src/types.d.ts +++ b/engine/src/flutter/lib/web_ui/flutter_js/src/types.d.ts @@ -6,10 +6,8 @@ type JSCompileTarget = "dart2js" | "dartdevc"; type WasmCompileTarget = "dart2wasm"; export type CompileTarget = JSCompileTarget | WasmCompileTarget; - -export type WebRenderer = - "canvaskit" | - "skwasm"; +export type WebRenderer = "canvaskit" | "skwasm"; +export type BrowserEngine = "blink" | "gecko" | "webkit" | "unknown"; interface ApplicationBuildBase { renderer: WebRenderer; @@ -31,11 +29,12 @@ export type ApplicationBuild = JSApplicationBuild | WasmApplicationBuild; export interface BuildConfig { serviceWorkerVersion: string; engineRevision: string; - useLocalCanvasKit: bool?; + useLocalCanvasKit?: boolean; builds: ApplicationBuild[]; } export interface BrowserEnvironment { + browserEngine: BrowserEngine; hasImageCodecs: boolean; hasChromiumBreakIterators: boolean; supportsWasmGC: boolean; @@ -47,21 +46,26 @@ type CanvasKitVariant = "full" | "chromium"; +type WasmAllowList = { + [k in BrowserEngine]?: boolean; +} + export interface FlutterConfiguration { - assetBase: string?; - canvasKitBaseUrl: string?; - canvasKitVariant: CanvasKitVariant?; - renderer: WebRenderer?; - hostElement: HtmlElement?; - fontFallbackBaseUrl: string?; - entryPointBaseUrl: string?; - forceSingleThreadedSkwasm: boolean?; + assetBase?: string; + canvasKitBaseUrl?: string; + canvasKitVariant?: CanvasKitVariant; + renderer?: WebRenderer; + hostElement?: HTMLElement; + fontFallbackBaseUrl?: string; + entryPointBaseUrl?: string; + forceSingleThreadedSkwasm?: boolean; + wasmAllowList?: WasmAllowList; } export interface ServiceWorkerSettings { serviceWorkerVersion: string; - serviceWorkerUrl: string?; - timeoutMillis: number?; + serviceWorkerUrl?: string; + timeoutMillis?: number; } export interface AppRunner { diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/html_image_element_codec.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/html_image_element_codec.dart index 19365b28583..81f6af4c1cc 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/html_image_element_codec.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/html_image_element_codec.dart @@ -72,7 +72,7 @@ abstract class HtmlImageElementCodec implements ui.Codec { naturalWidth = kDefaultImageSizeFallback; naturalHeight = kDefaultImageSizeFallback; } - final ui.Image image = createImageFromHTMLImageElement( + final ui.Image image = await createImageFromHTMLImageElement( imgElement!, naturalWidth, naturalHeight, @@ -81,7 +81,7 @@ abstract class HtmlImageElementCodec implements ui.Codec { } /// Creates a [ui.Image] from an [HTMLImageElement] that has been loaded. - ui.Image createImageFromHTMLImageElement( + FutureOr createImageFromHTMLImageElement( DomHTMLImageElement image, int naturalWidth, int naturalHeight, diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart index 0485667c369..b3667e9eb3d 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart @@ -19,6 +19,7 @@ export 'skwasm_impl/paragraph.dart'; export 'skwasm_impl/path.dart'; export 'skwasm_impl/path_metrics.dart'; export 'skwasm_impl/picture.dart'; +export 'skwasm_impl/raw/raw_animated_image.dart'; export 'skwasm_impl/raw/raw_canvas.dart'; export 'skwasm_impl/raw/raw_filters.dart'; export 'skwasm_impl/raw/raw_fonts.dart'; diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/codecs.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/codecs.dart index 0ce497165d4..acb0f4c67f4 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/codecs.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/codecs.dart @@ -2,12 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; +import 'dart:ffi'; +import 'dart:typed_data'; + import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; -class SkwasmImageDecoder extends BrowserImageDecoder { - SkwasmImageDecoder({ +class SkwasmBrowserImageDecoder extends BrowserImageDecoder { + SkwasmBrowserImageDecoder({ required super.contentType, required super.dataSource, required super.debugSource, @@ -21,3 +25,69 @@ class SkwasmImageDecoder extends BrowserImageDecoder { return SkwasmImage(imageCreateFromTextureSource(frame, width, height, surface.handle)); } } + +class SkwasmDomImageDecoder extends HtmlBlobCodec { + SkwasmDomImageDecoder(super.blob, [this.width, this.height]); + + final int? width; + final int? height; + + @override + FutureOr createImageFromHTMLImageElement( + DomHTMLImageElement image, + int naturalWidth, + int naturalHeight, + ) { + return renderer.createImageFromTextureSource( + image, + width: width ?? naturalWidth, + height: height ?? naturalHeight, + transferOwnership: false, + ); + } +} + +class SkwasmAnimatedImageDecoder implements ui.Codec { + factory SkwasmAnimatedImageDecoder(Uint8List imageData, [int? width, int? height]) { + final SkDataHandle data = skDataCreate(imageData.length); + final Pointer dataPointer = skDataGetPointer(data).cast(); + for (int i = 0; i < imageData.length; i++) { + dataPointer[i] = imageData[i]; + } + final AnimatedImageHandle handle = animatedImageCreate(data, width ?? 0, height ?? 0); + skDataDispose(data); + return SkwasmAnimatedImageDecoder._(handle); + } + + SkwasmAnimatedImageDecoder._(this.handle); + + AnimatedImageHandle handle; + + @override + void dispose() { + if (handle != nullptr) { + animatedImageDispose(handle); + handle = nullptr; + } + } + + @override + int get frameCount { + return animatedImageGetFrameCount(handle); + } + + @override + int get repetitionCount { + return animatedImageGetRepetitionCount(handle); + } + + @override + Future getNextFrame() async { + final Duration duration = Duration( + milliseconds: animatedImageGetCurrentFrameDurationMilliseconds(handle), + ); + final SkwasmImage image = SkwasmImage(animatedImageGetCurrentFrame(handle)); + final ui.FrameInfo frameInfo = AnimatedImageFrameInfo(duration, image); + return frameInfo; + } +} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index a9df744d8e7..7d51a5c837f 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -1080,7 +1080,9 @@ class SkwasmParagraphBuilder extends SkwasmObjectWrapper @override ui.Paragraph build() { - _addSegmenterData(); + if (!skwasmIsHeavy()) { + _addSegmenterData(); + } return SkwasmParagraph(paragraphBuilderBuild(handle)); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_animated_image.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_animated_image.dart new file mode 100644 index 00000000000..d91056cb759 --- /dev/null +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_animated_image.dart @@ -0,0 +1,47 @@ +// 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. + +@DefaultAsset('skwasm') +library skwasm_impl; + +import 'dart:ffi'; + +import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; + +final class RawAnimatedImage extends Opaque {} + +typedef AnimatedImageHandle = Pointer; + +@Native( + symbol: 'animatedImage_create', + isLeaf: true, +) +external AnimatedImageHandle animatedImageCreate(SkDataHandle handle, int width, int height); + +@Native(symbol: 'animatedImage_dispose', isLeaf: true) +external void animatedImageDispose(AnimatedImageHandle handle); + +@Native(symbol: 'animatedImage_getFrameCount', isLeaf: true) +external int animatedImageGetFrameCount(AnimatedImageHandle handle); + +@Native( + symbol: 'animatedImage_getRepetitionCount', + isLeaf: true, +) +external int animatedImageGetRepetitionCount(AnimatedImageHandle handle); + +@Native( + symbol: 'animatedImage_getCurrentFrameDurationMilliseconds', + isLeaf: true, +) +external int animatedImageGetCurrentFrameDurationMilliseconds(AnimatedImageHandle handle); + +@Native(symbol: 'animatedImage_decodeNextFrame', isLeaf: true) +external void animatedImageDecodeNextFrame(AnimatedImageHandle handle); + +@Native( + symbol: 'animatedImage_getCurrentFrame', + isLeaf: true, +) +external ImageHandle animatedImageGetCurrentFrame(AnimatedImageHandle handle); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart index 34584f9a51f..fe45e99cacc 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart @@ -25,3 +25,6 @@ external WasmI32 addFunction(WasmFuncRef function); @Native(symbol: 'skwasm_isMultiThreaded', isLeaf: true) external bool skwasmIsMultiThreaded(); + +@Native(symbol: 'skwasm_isHeavy', isLeaf: true) +external bool skwasmIsHeavy(); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index e2fc138d32c..c067820604e 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -333,21 +333,30 @@ class SkwasmRenderer implements Renderer { if (contentType == null) { throw Exception('Could not determine content type of image from data'); } - final SkwasmImageDecoder baseDecoder = SkwasmImageDecoder( - contentType: contentType.mimeType, - dataSource: list.toJS, - debugSource: 'encoded image bytes', - ); - await baseDecoder.initialize(); - if (targetWidth == null && targetHeight == null) { - return baseDecoder; + if (browserSupportsImageDecoder) { + final SkwasmBrowserImageDecoder baseDecoder = SkwasmBrowserImageDecoder( + contentType: contentType.mimeType, + dataSource: list.toJS, + debugSource: 'encoded image bytes', + ); + await baseDecoder.initialize(); + if (targetWidth == null && targetHeight == null) { + return baseDecoder; + } + return ResizingCodec( + baseDecoder, + targetWidth: targetWidth, + targetHeight: targetHeight, + allowUpscaling: allowUpscaling, + ); + } else { + if (contentType.isAnimated) { + return SkwasmAnimatedImageDecoder(list, targetWidth, targetHeight); + } else { + final DomBlob blob = createDomBlob([list.buffer]); + return SkwasmDomImageDecoder(blob, targetWidth, targetHeight); + } } - return ResizingCodec( - baseDecoder, - targetWidth: targetWidth, - targetHeight: targetHeight, - allowUpscaling: allowUpscaling, - ); } @override @@ -360,13 +369,28 @@ class SkwasmRenderer implements Renderer { if (contentType == null) { throw Exception('Could not determine content type of image at url $uri'); } - final SkwasmImageDecoder decoder = SkwasmImageDecoder( - contentType: contentType, - dataSource: response.body, - debugSource: uri.toString(), - ); - await decoder.initialize(); - return decoder; + if (browserSupportsImageDecoder) { + final SkwasmBrowserImageDecoder decoder = SkwasmBrowserImageDecoder( + contentType: contentType, + dataSource: response.body, + debugSource: uri.toString(), + ); + await decoder.initialize(); + return decoder; + } else { + final ByteBuffer buffer = await response.arrayBuffer(); + final Uint8List data = buffer.asUint8List(); + final ImageType? parsedContentType = detectImageType(data); + if (parsedContentType == null) { + throw Exception('Could not determine content type of image from data'); + } + if (parsedContentType.isAnimated) { + return SkwasmAnimatedImageDecoder(data); + } else { + final DomBlob blob = createDomBlob([buffer]); + return SkwasmDomImageDecoder(blob); + } + } } @override diff --git a/engine/src/flutter/lib/web_ui/skwasm/BUILD.gn b/engine/src/flutter/lib/web_ui/skwasm/BUILD.gn index 1e4221cf71a..850c24f081f 100644 --- a/engine/src/flutter/lib/web_ui/skwasm/BUILD.gn +++ b/engine/src/flutter/lib/web_ui/skwasm/BUILD.gn @@ -4,69 +4,90 @@ import("//build/toolchain/wasm.gni") -wasm_lib("skwasm") { - public_configs = [ "//flutter:config" ] +template("skwasm_variant") { + wasm_lib(target_name) { + public_configs = [ "//flutter:config" ] - sources = [ - "canvas.cpp", - "contour_measure.cpp", - "data.cpp", - "export.h", - "filters.cpp", - "fonts.cpp", - "helpers.h", - "image.cpp", - "paint.cpp", - "path.cpp", - "picture.cpp", - "shaders.cpp", - "skwasm_support.h", - "string.cpp", - "surface.cpp", - "text/line_metrics.cpp", - "text/paragraph.cpp", - "text/paragraph_builder.cpp", - "text/paragraph_style.cpp", - "text/strut_style.cpp", - "text/text_style.cpp", - "vertices.cpp", - "wrappers.h", - ] - - cflags = [ "-mreference-types" ] - - ldflags = [ - "-std=c++20", - "-lGL", - "-sUSE_WEBGL2=1", - "-sMAX_WEBGL_VERSION=2", - "-sOFFSCREENCANVAS_SUPPORT", - "-sALLOW_MEMORY_GROWTH", - "-sALLOW_TABLE_GROWTH", - "-lexports.js", - "-sEXPORTED_FUNCTIONS=[stackAlloc]", - "-sEXPORTED_RUNTIME_METHODS=[addFunction,wasmExports,wasmMemory,stackAlloc]", - "-sINCOMING_MODULE_JS_API=[instantiateWasm,locateFile,noExitRuntime,mainScriptUrlOrBlob,wasmMemory,wasm,skwasmSingleThreaded]", - "-sUSE_ES6_IMPORT_META=0", - "--js-library", - rebase_path("library_skwasm_support.js"), - ] - - inputs = [ rebase_path("library_skwasm_support.js") ] - - if (is_debug) { - ldflags += [ - "-sASSERTIONS=1", - "-sGL_ASSERTIONS=1", - "-sSTACK_OVERFLOW_CHECK=2", + sources = [ + "canvas.cpp", + "contour_measure.cpp", + "data.cpp", + "export.h", + "filters.cpp", + "fonts.cpp", + "helpers.h", + "image.cpp", + "paint.cpp", + "path.cpp", + "picture.cpp", + "shaders.cpp", + "skwasm_support.h", + "string.cpp", + "surface.cpp", + "text/line_metrics.cpp", + "text/paragraph.cpp", + "text/paragraph_builder.cpp", + "text/paragraph_style.cpp", + "text/strut_style.cpp", + "text/text_style.cpp", + "vertices.cpp", + "wrappers.h", ] - } else { - ldflags += [ "--closure=1" ] - } - deps = [ - "//flutter/skia", - "//flutter/skia/modules/skparagraph", - "//flutter/skia/modules/skunicode", - ] + cflags = [ "-mreference-types" ] + + ldflags = [ + "-std=c++20", + "-lGL", + "-sUSE_WEBGL2=1", + "-sMAX_WEBGL_VERSION=2", + "-sOFFSCREENCANVAS_SUPPORT", + "-sALLOW_MEMORY_GROWTH", + "-sALLOW_TABLE_GROWTH", + "-lexports.js", + "-sEXPORTED_FUNCTIONS=[stackAlloc]", + "-sEXPORTED_RUNTIME_METHODS=[addFunction,wasmExports,wasmMemory,stackAlloc]", + "-sINCOMING_MODULE_JS_API=[instantiateWasm,locateFile,noExitRuntime,mainScriptUrlOrBlob,wasmMemory,wasm,skwasmSingleThreaded]", + "-sUSE_ES6_IMPORT_META=0", + "--js-library", + rebase_path("library_skwasm_support.js"), + ] + + inputs = [ rebase_path("library_skwasm_support.js") ] + if (invoker.is_heavy) { + sources += [ + "animated_image.cpp", + "text/paragraph_builder_builtin_icu.cpp", + ] + } else { + sources += [ + "animated_image_stub.cpp", + "text/paragraph_builder_client_icu.cpp", + ] + } + + if (is_debug) { + ldflags += [ + "-sDEMANGLE_SUPPORT=1", + "-sASSERTIONS=1", + "-sGL_ASSERTIONS=1", + ] + } else { + ldflags += [ "--closure=1" ] + } + + deps = [ + "//flutter/skia", + "//flutter/skia/modules/skparagraph", + "//flutter/skia/modules/skunicode", + ] + } +} + +skwasm_variant("skwasm") { + is_heavy = false +} + +skwasm_variant("skwasm_heavy") { + is_heavy = true } diff --git a/engine/src/flutter/lib/web_ui/skwasm/animated_image.cpp b/engine/src/flutter/lib/web_ui/skwasm/animated_image.cpp new file mode 100644 index 00000000000..f61507c30e8 --- /dev/null +++ b/engine/src/flutter/lib/web_ui/skwasm/animated_image.cpp @@ -0,0 +1,77 @@ +// 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. + +#include "export.h" +#include "skwasm_support.h" + +#include "third_party/skia/include/android/SkAnimatedImage.h" +#include "third_party/skia/include/codec/SkAndroidCodec.h" +#include "third_party/skia/include/codec/SkCodec.h" +#include "third_party/skia/include/codec/SkGifDecoder.h" +#include "third_party/skia/include/codec/SkWebpDecoder.h" + +#include + +namespace { +std::unique_ptr getCodecForData(SkData* data) { + if (SkGifDecoder::IsGif(data->data(), data->size())) { + return SkGifDecoder::Decode(sk_ref_sp(data), nullptr); + } + if (SkWebpDecoder::IsWebp(data->data(), data->size())) { + return SkWebpDecoder::Decode(sk_ref_sp(data), nullptr); + } + return nullptr; +} +} // namespace + +SKWASM_EXPORT SkAnimatedImage* animatedImage_create(SkData* data, + int targetWidth, + int targetHeight) { + auto codec = getCodecForData(data); + if (!codec) { + printf("Failed to create codec for animated image.\n"); + return nullptr; + } + + auto aCodec = SkAndroidCodec::MakeFromCodec(std::move(codec)); + if (aCodec == nullptr) { + printf("Failed to create codec for animated image.\n"); + return nullptr; + } + + if (targetWidth == 0 || targetHeight == 0) { + return SkAnimatedImage::Make(std::move(aCodec)).release(); + } + + return SkAnimatedImage::Make( + std::move(aCodec), + SkImageInfo::MakeUnknown(targetWidth, targetHeight), + SkIRect::MakeWH(targetWidth, targetHeight), nullptr) + .release(); +} + +SKWASM_EXPORT void animatedImage_dispose(SkAnimatedImage* image) { + image->unref(); +} + +SKWASM_EXPORT int animatedImage_getFrameCount(SkAnimatedImage* image) { + return image->getFrameCount(); +} + +SKWASM_EXPORT int animatedImage_getRepetitionCount(SkAnimatedImage* image) { + return image->getRepetitionCount(); +} + +SKWASM_EXPORT int animatedImage_getCurrentFrameDurationMilliseconds( + SkAnimatedImage* image) { + return image->currentFrameDuration(); +} + +SKWASM_EXPORT void animatedImage_decodeNextFrame(SkAnimatedImage* image) { + image->decodeNextFrame(); +} + +SKWASM_EXPORT SkImage* animatedImage_getCurrentFrame(SkAnimatedImage* image) { + return image->getCurrentFrame().release(); +} diff --git a/engine/src/flutter/lib/web_ui/skwasm/animated_image_stub.cpp b/engine/src/flutter/lib/web_ui/skwasm/animated_image_stub.cpp new file mode 100644 index 00000000000..3f6f45b5d45 --- /dev/null +++ b/engine/src/flutter/lib/web_ui/skwasm/animated_image_stub.cpp @@ -0,0 +1,49 @@ +// 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. + +#include "export.h" +#include "skwasm_support.h" + +SKWASM_EXPORT void* animatedImage_create(SkData* data, + int targetWidth, + int targetHeight) { + emscripten_console_warn( + "Animated image not implemented in non-heavy skwasm build."); + return nullptr; +} + +SKWASM_EXPORT void animatedImage_dispose(void* image) { + emscripten_console_warn( + "Animated image not implemented in non-heavy skwasm build."); +} + +SKWASM_EXPORT int animatedImage_getFrameCount(void* image) { + emscripten_console_warn( + "Animated image not implemented in non-heavy skwasm build."); + return 0; +} + +SKWASM_EXPORT int animatedImage_getRepetitionCount(void* image) { + emscripten_console_warn( + "Animated image not implemented in non-heavy skwasm build."); + return 0; +} + +SKWASM_EXPORT int animatedImage_getCurrentFrameDurationMilliseconds( + void* image) { + emscripten_console_warn( + "Animated image not implemented in non-heavy skwasm build."); + return 0; +} + +SKWASM_EXPORT void animatedImage_decodeNextFrame(void* image) { + emscripten_console_warn( + "Animated image not implemented in non-heavy skwasm build."); +} + +SKWASM_EXPORT void* animatedImage_getCurrentFrame(void* image) { + emscripten_console_warn( + "Animated image not implemented in non-heavy skwasm build."); + return nullptr; +} diff --git a/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder.cpp b/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder.cpp index b573d31e022..d638f3e5b23 100644 --- a/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder.cpp +++ b/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder.cpp @@ -10,13 +10,6 @@ using namespace skia::textlayout; using namespace Skwasm; -SKWASM_EXPORT ParagraphBuilder* paragraphBuilder_create( - ParagraphStyle* style, - FlutterFontCollection* collection) { - return ParagraphBuilder::make(*style, collection->collection, nullptr) - .release(); -} - SKWASM_EXPORT void paragraphBuilder_dispose(ParagraphBuilder* builder) { delete builder; } @@ -53,15 +46,6 @@ SKWASM_EXPORT void paragraphBuilder_pop(ParagraphBuilder* builder) { builder->pop(); } -SKWASM_EXPORT Paragraph* paragraphBuilder_build(ParagraphBuilder* builder) { - auto [words, graphemeBreaks, lineBreaks] = builder->getClientICUData(); - auto text = builder->getText(); - sk_sp clientICU = - SkUnicodes::Client::Make(text, words, graphemeBreaks, lineBreaks); - builder->SetUnicode(clientICU); - return builder->Build().release(); -} - SKWASM_EXPORT std::vector* unicodePositionBuffer_create( size_t length) { return new std::vector(length); @@ -92,21 +76,3 @@ SKWASM_EXPORT void lineBreakBuffer_free( std::vector* buffer) { delete buffer; } - -SKWASM_EXPORT void paragraphBuilder_setGraphemeBreaksUtf16( - ParagraphBuilder* builder, - std::vector* breaks) { - builder->setGraphemeBreaksUtf16(std::move(*breaks)); -} - -SKWASM_EXPORT void paragraphBuilder_setWordBreaksUtf16( - ParagraphBuilder* builder, - std::vector* breaks) { - builder->setWordsUtf16(std::move(*breaks)); -} - -SKWASM_EXPORT void paragraphBuilder_setLineBreaksUtf16( - ParagraphBuilder* builder, - std::vector* breaks) { - builder->setLineBreaksUtf16(std::move(*breaks)); -} diff --git a/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder_builtin_icu.cpp b/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder_builtin_icu.cpp new file mode 100644 index 00000000000..bccc1c3b291 --- /dev/null +++ b/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder_builtin_icu.cpp @@ -0,0 +1,48 @@ +// 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. + +#include "../export.h" +#include "../wrappers.h" +#include "modules/skunicode/include/SkUnicode_icu.h" +#include "third_party/skia/modules/skparagraph/include/ParagraphBuilder.h" + +using namespace skia::textlayout; +using namespace Skwasm; + +SKWASM_EXPORT bool skwasm_isHeavy() { + return true; +} + +SKWASM_EXPORT ParagraphBuilder* paragraphBuilder_create( + ParagraphStyle* style, + FlutterFontCollection* collection) { + return ParagraphBuilder::make(*style, collection->collection, + SkUnicodes::ICU::Make()) + .release(); +} + +SKWASM_EXPORT Paragraph* paragraphBuilder_build(ParagraphBuilder* builder) { + return builder->Build().release(); +} + +SKWASM_EXPORT void paragraphBuilder_setGraphemeBreaksUtf16( + ParagraphBuilder* builder, + std::vector* breaks) { + emscripten_console_warn( + "warning: setGraphemeBreaksUtf16 not implemented in skwasm_heavy\n"); +} + +SKWASM_EXPORT void paragraphBuilder_setWordBreaksUtf16( + ParagraphBuilder* builder, + std::vector* breaks) { + emscripten_console_warn( + "warning: setWordBreaksUtf16 not implemented in skwasm_heavy\n"); +} + +SKWASM_EXPORT void paragraphBuilder_setLineBreaksUtf16( + ParagraphBuilder* builder, + std::vector* breaks) { + emscripten_console_warn( + "warning: setLineBreaksUtf16 not implemented in skwasm_heavy\n"); +} diff --git a/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder_client_icu.cpp b/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder_client_icu.cpp new file mode 100644 index 00000000000..a157afc10ac --- /dev/null +++ b/engine/src/flutter/lib/web_ui/skwasm/text/paragraph_builder_client_icu.cpp @@ -0,0 +1,49 @@ +// 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. + +#include "../export.h" +#include "../wrappers.h" +#include "third_party/skia/modules/skparagraph/include/ParagraphBuilder.h" +#include "third_party/skia/modules/skunicode/include/SkUnicode_client.h" + +using namespace skia::textlayout; +using namespace Skwasm; + +SKWASM_EXPORT bool skwasm_isHeavy() { + return false; +} + +SKWASM_EXPORT ParagraphBuilder* paragraphBuilder_create( + ParagraphStyle* style, + FlutterFontCollection* collection) { + return ParagraphBuilder::make(*style, collection->collection, nullptr) + .release(); +} + +SKWASM_EXPORT Paragraph* paragraphBuilder_build(ParagraphBuilder* builder) { + auto [words, graphemeBreaks, lineBreaks] = builder->getClientICUData(); + auto text = builder->getText(); + sk_sp clientICU = + SkUnicodes::Client::Make(text, words, graphemeBreaks, lineBreaks); + builder->SetUnicode(clientICU); + return builder->Build().release(); +} + +SKWASM_EXPORT void paragraphBuilder_setGraphemeBreaksUtf16( + ParagraphBuilder* builder, + std::vector* breaks) { + builder->setGraphemeBreaksUtf16(std::move(*breaks)); +} + +SKWASM_EXPORT void paragraphBuilder_setWordBreaksUtf16( + ParagraphBuilder* builder, + std::vector* breaks) { + builder->setWordsUtf16(std::move(*breaks)); +} + +SKWASM_EXPORT void paragraphBuilder_setLineBreaksUtf16( + ParagraphBuilder* builder, + std::vector* breaks) { + builder->setLineBreaksUtf16(std::move(*breaks)); +} diff --git a/engine/src/flutter/lib/web_ui/test/felt_config.yaml b/engine/src/flutter/lib/web_ui/test/felt_config.yaml index a44a628f7fe..e50e66a70b7 100644 --- a/engine/src/flutter/lib/web_ui/test/felt_config.yaml +++ b/engine/src/flutter/lib/web_ui/test/felt_config.yaml @@ -99,10 +99,23 @@ run-configs: browser: firefox canvaskit-variant: full + - name: firefox-wasm + browser: firefox + canvaskit-variant: full + wasm-allow-list: + gecko: true + - name: safari browser: safari canvaskit-variant: full + - name: safari-wasm + browser: safari + canvaskit-variant: full + wasm-allow-list: + webkit: true + + test-suites: - name: chrome-dart2js-experimental-webparagraph-ui test-bundle: dart2js-canvaskit-ui @@ -175,6 +188,12 @@ test-suites: run-config: firefox artifact-deps: [ canvaskit ] + - name: firefox-dart2wasm-skwasm-ui + test-bundle: dart2wasm-skwasm-ui + run-config: firefox-wasm + artifact-deps: [ skwasm ] + enable-ci: false + - name: safari-dart2js-canvaskit-engine test-bundle: dart2js-canvaskit-engine run-config: safari @@ -190,11 +209,28 @@ test-suites: run-config: safari artifact-deps: [ canvaskit ] + - name: safari-dart2wasm-canvaskit-engine + test-bundle: dart2wasm-canvaskit-engine + run-config: safari-wasm + artifact-deps: [ canvaskit ] + enable-ci: false + + - name: safari-dart2wasm-skwasm-ui + test-bundle: dart2wasm-skwasm-ui + run-config: safari-wasm + artifact-deps: [ skwasm ] + enable-ci: false + - name: chrome-dart2wasm-canvaskit-engine test-bundle: dart2wasm-canvaskit-engine run-config: chrome artifact-deps: [ canvaskit_chromium ] + - name: firefox-dart2wasm-canvaskit-engine + test-bundle: dart2wasm-canvaskit-engine + run-config: firefox-wasm + artifact-deps: [ canvaskit ] + - name: chrome-coi-dart2wasm-skwasm-ui test-bundle: dart2wasm-skwasm-ui run-config: chrome-coi diff --git a/engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart b/engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart index 38063cde230..967289b129f 100644 --- a/engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart +++ b/engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart @@ -73,7 +73,6 @@ Future testMain() async { }); const ui.Rect drawRegion = ui.Rect.fromLTWH(0, 0, 300, 300); - const ui.Rect imageRegion = ui.Rect.fromLTWH(0, 0, 150, 150); // Emits a set of rendering tests for an image // `imageGenerator` should produce an image that is 150x150 pixels. @@ -126,7 +125,12 @@ Future testMain() async { final ui.PictureRecorder recorder = ui.PictureRecorder(); final ui.Canvas canvas = ui.Canvas(recorder, drawRegion); - const ui.Rect srcRect = ui.Rect.fromLTRB(50, 50, 100, 100); + final ui.Rect srcRect = ui.Rect.fromLTRB( + image.width / 3, + image.height / 3, + 2 * image.width / 3, + 2 * image.height / 3, + ); canvas.drawImageRect( image, srcRect, @@ -159,15 +163,16 @@ Future testMain() async { test('drawImageNine', () async { final ui.Image image = await generateImage(); + final ui.Rect srcRect = ui.Rect.fromLTRB( + image.width / 3, + image.height / 3, + 2 * image.width / 3, + 2 * image.height / 3, + ); final ui.PictureRecorder recorder = ui.PictureRecorder(); final ui.Canvas canvas = ui.Canvas(recorder, drawRegion); - canvas.drawImageNine( - image, - const ui.Rect.fromLTRB(50, 50, 100, 100), - drawRegion, - ui.Paint(), - ); + canvas.drawImageNine(image, srcRect, drawRegion, ui.Paint()); await drawPictureUsingCurrentRenderer(recorder.endRecording()); @@ -253,10 +258,22 @@ Future testMain() async { ui.Offset(0, 100), ui.Offset(0, 50), ]; + final double imageWidth = image.width.toDouble(); + final double imageHeight = image.height.toDouble(); + final List texCoords = [ + ui.Offset(imageWidth / 3, 0), + ui.Offset(2 * imageWidth / 3, 0), + ui.Offset(imageWidth, imageHeight / 3), + ui.Offset(imageWidth, 2 * imageHeight / 3), + ui.Offset(2 * imageWidth / 3, imageHeight), + ui.Offset(imageWidth / 3, imageHeight), + ui.Offset(0, 2 * imageHeight / 3), + ui.Offset(0, imageHeight / 3), + ]; final ui.Vertices vertices = ui.Vertices( ui.VertexMode.triangles, vertexValues, - textureCoordinates: vertexValues, + textureCoordinates: texCoords, indices: [ 0, 1, 2, // 0, 2, 3, // @@ -299,7 +316,7 @@ Future testMain() async { emitImageTests('picture_toImage', () { final ui.PictureRecorder recorder = ui.PictureRecorder(); - final ui.Canvas canvas = ui.Canvas(recorder, imageRegion); + final ui.Canvas canvas = ui.Canvas(recorder, const ui.Rect.fromLTWH(0, 0, 150, 150)); for (int y = 0; y < 15; y++) { for (int x = 0; x < 15; x++) { final ui.Offset center = ui.Offset(x * 10 + 5, y * 10 + 5); @@ -488,4 +505,57 @@ Future testMain() async { codec.dispose(); return info.image; }); + + emitImageTests('animated_gif_list', () async { + final ByteBuffer data = await httpFetchByteBuffer('/test_images/alphabetAnim.gif'); + final ui.Codec codec = await renderer.instantiateImageCodec(data.asUint8List()); + expect(codec.frameCount, 13); + + // The second frame of this gif is more interesting to test than the first, + // so skip the first frame. + await codec.getNextFrame(); + final ui.FrameInfo info = await codec.getNextFrame(); + codec.dispose(); + return info.image; + }); + + emitImageTests('animated_webp_list', () async { + final ByteBuffer data = await httpFetchByteBuffer('/test_images/stoplight.webp'); + final ui.Codec codec = await renderer.instantiateImageCodec(data.asUint8List()); + expect(codec.frameCount, 3); + + final ui.FrameInfo info = await codec.getNextFrame(); + codec.dispose(); + return info.image; + }); + + if (!isCanvasKit) { + // CanvasKit doesn't do the correct thing for animated images for the + // `instantiateImageCodecFromUrl` code path. + // See https://github.com/flutter/flutter/issues/166803 + emitImageTests('animated_gif_uri', () async { + final ui.Codec codec = await renderer.instantiateImageCodecFromUrl( + Uri(path: '/test_images/alphabetAnim.gif'), + ); + expect(codec.frameCount, 13); + + // The second frame of this gif is more interesting to test than the first, + // so skip the first frame. + await codec.getNextFrame(); + final ui.FrameInfo info = await codec.getNextFrame(); + codec.dispose(); + return info.image; + }); + + emitImageTests('animated_webp_uri', () async { + final ui.Codec codec = await renderer.instantiateImageCodecFromUrl( + Uri(path: '/test_images/stoplight.webp'), + ); + expect(codec.frameCount, 3); + + final ui.FrameInfo info = await codec.getNextFrame(); + codec.dispose(); + return info.image; + }); + } } diff --git a/engine/src/flutter/third_party/canvaskit/BUILD.gn b/engine/src/flutter/third_party/canvaskit/BUILD.gn index 922dd315e39..dbcd180d0fd 100644 --- a/engine/src/flutter/third_party/canvaskit/BUILD.gn +++ b/engine/src/flutter/third_party/canvaskit/BUILD.gn @@ -110,28 +110,42 @@ copy("canvaskit_experimental_webparagraph_group") { } # This toolchain is only to be used by skwasm_group below. -wasm_toolchain("skwasm") { - extra_toolchain_args = { - # In Chromium browsers, we can use the browser's APIs to get the necessary - # ICU data. - skia_use_icu = false - skia_use_client_icu = true - skia_icu_bidi_third_party_dir = "//flutter/third_party/canvaskit/icu_bidi" +template("skwasm_toolchain") { + assert(defined(invoker.skwasm_is_heavy)) + forward_variables_from(invoker, ["skwasm_is_mt", "skwasm_is_heavy"]) + wasm_toolchain(target_name) { + extra_toolchain_args = { + # In Chromium browsers, we can use the browser's APIs to get the necessary + # ICU data. So we use built-in ICU data only in the heavy (non-chromium) case + skia_use_icu = skwasm_is_heavy + skia_use_client_icu = !skwasm_is_heavy + if (!skwasm_is_heavy) { + skia_icu_bidi_third_party_dir = "//flutter/third_party/canvaskit/icu_bidi" + } - skia_use_libjpeg_turbo_decode = false - skia_use_libpng_decode = false - skia_use_libwebp_decode = false + skia_use_libjpeg_turbo_decode = skwasm_is_heavy + skia_use_libpng_decode = skwasm_is_heavy + skia_use_libwebp_decode = skwasm_is_heavy - # We use OffscreenCanvas to produce PNG data instead of skia - skia_use_no_png_encode = true - skia_use_libpng_encode = false + # We use OffscreenCanvas to produce PNG data instead of skia + skia_use_no_png_encode = true + skia_use_libpng_encode = false - # skwasm is multithreaded - wasm_use_workers = true - wasm_prioritize_size = true + # skwasm is multithreaded + wasm_use_workers = true + wasm_prioritize_size = true + } } } +skwasm_toolchain("skwasm") { + skwasm_is_heavy = false +} + +skwasm_toolchain("skwasm_heavy") { + skwasm_is_heavy = true +} + copy("skwasm_group") { visibility = [ "//flutter/web_sdk:*" ] public_deps = [ "//flutter/lib/web_ui/skwasm(:skwasm)" ] @@ -148,3 +162,20 @@ copy("skwasm_group") { } outputs = [ "$root_out_dir/flutter_web_sdk/canvaskit/{{source_file_part}}" ] } + +copy("skwasm_heavy_group") { + visibility = [ "//flutter/web_sdk:*" ] + public_deps = [ "//flutter/lib/web_ui/skwasm:skwasm_heavy(:skwasm_heavy)" ] + + sources = [ + "$root_out_dir/skwasm_heavy/skwasm_heavy.js", + "$root_out_dir/skwasm_heavy/skwasm_heavy.js.symbols", + "$root_out_dir/skwasm_heavy/skwasm_heavy.wasm", + ] + if (is_debug) { + if (!wasm_use_dwarf) { + sources += [ "$root_out_dir/skwasm_heavy/skwasm_heavy.wasm.map" ] + } + } + outputs = [ "$root_out_dir/flutter_web_sdk/canvaskit/{{source_file_part}}" ] +} diff --git a/engine/src/flutter/web_sdk/BUILD.gn b/engine/src/flutter/web_sdk/BUILD.gn index 67a77da0005..d24dc7d69da 100644 --- a/engine/src/flutter/web_sdk/BUILD.gn +++ b/engine/src/flutter/web_sdk/BUILD.gn @@ -414,6 +414,7 @@ if (!is_fuchsia) { "//flutter/third_party/canvaskit:canvaskit_chromium_group", "//flutter/third_party/canvaskit:canvaskit_group", "//flutter/third_party/canvaskit:skwasm_group", + "//flutter/third_party/canvaskit:skwasm_heavy_group", ] } deps += [ "//flutter/lib/web_ui/flutter_js" ] @@ -438,6 +439,9 @@ if (!is_fuchsia) { "$root_out_dir/flutter_web_sdk/canvaskit/skwasm.js", "$root_out_dir/flutter_web_sdk/canvaskit/skwasm.js.symbols", "$root_out_dir/flutter_web_sdk/canvaskit/skwasm.wasm", + "$root_out_dir/flutter_web_sdk/canvaskit/skwasm_heavy.js", + "$root_out_dir/flutter_web_sdk/canvaskit/skwasm_heavy.js.symbols", + "$root_out_dir/flutter_web_sdk/canvaskit/skwasm_heavy.wasm", ] } diff --git a/engine/src/flutter/web_sdk/web_engine_tester/lib/static/host.dart b/engine/src/flutter/web_sdk/web_engine_tester/lib/static/host.dart index c515ed7360d..ac68abf1b57 100644 --- a/engine/src/flutter/web_sdk/web_engine_tester/lib/static/host.dart +++ b/engine/src/flutter/web_sdk/web_engine_tester/lib/static/host.dart @@ -200,11 +200,6 @@ MultiChannel _connectToServer() { StreamChannel _connectToIframe(String url, int id) { final DomHTMLIFrameElement iframe = createDomHTMLIFrameElement(); _iframes[id] = iframe; - iframe - ..src = url - ..width = '1000' - ..height = '1000'; - domDocument.body!.appendChild(iframe); final StreamChannelController controller = StreamChannelController(sync: true); @@ -225,14 +220,6 @@ StreamChannel _connectToIframe(String url, int id) { if (message.origin != domWindow.location.origin) { return; } - // We have to do these ugly casts because the message is cross-origin - // which isn't handled cleanly by dart:js_interop. - if (((message.source as DomMessageEventSource?)?.location as DomMessageEventLocation?) - ?.href != - iframe.src) { - return; - } - message.stopPropagation(); if (message.data == 'port') { @@ -277,5 +264,11 @@ StreamChannel _connectToIframe(String url, int id) { ), ); + iframe + ..src = url + ..width = '1000' + ..height = '1000'; + domDocument.body!.appendChild(iframe); + return controller.foreign; }