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; }