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.
This commit is contained in:
Jackson Gardner 2025-05-05 10:56:26 -07:00 committed by GitHub
parent 018c6d44df
commit 2b5ef64fc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 758 additions and 253 deletions

View File

@ -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"

View File

@ -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

View File

@ -22,6 +22,8 @@ const Map<String, String> targetAliases = <String, String>{
'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',
};

View File

@ -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<String, bool> 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<String, bool>(),
);
runConfigs.add(runConfig);
if (runConfigsByName.containsKey(name)) {

View File

@ -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<String> args = <String>[
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<String>(const Utf8Decoder(allowMalformed: true))
.listen((String string) => print('[Firefox:stdout] $string'));
process.stderr
.transform<String>(const Utf8Decoder(allowMalformed: true))
.listen((String string) => print('[Firefox:stderr] $string'));
remoteDebuggerCompleter.complete(
getRemoteDebuggerUrl(Uri.parse('http://localhost:$kDevtoolsPort')),

View File

@ -114,8 +114,9 @@ class GenerateBuilderJsonCommand extends Command<bool> {
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 <String, dynamic>{
'name': '$platform run ${browser.name} suites',
@ -149,7 +150,7 @@ class GenerateBuilderJsonCommand extends Command<bool> {
'parameters': <String>[
'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',
},

View File

@ -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(', ')}');
}

View File

@ -536,6 +536,7 @@ class BrowserPlatform extends PlatformPlugin {
canvasKitVariant: "${getCanvasKitVariant()}",
canvasKitBaseUrl: "/canvaskit",
forceSingleThreadedSkwasm: ${suite.runConfig.forceSingleThreadedSkwasm},
wasmAllowList: ${jsonEncode(suite.runConfig.wasmAllowList)},
},
});
</script>

View File

@ -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(),

View File

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

View File

@ -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,

View File

@ -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 {

View File

@ -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<ui.Image> createImageFromHTMLImageElement(
DomHTMLImageElement image,
int naturalWidth,
int naturalHeight,

View File

@ -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';

View File

@ -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<ui.Image> 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<Int8> dataPointer = skDataGetPointer(data).cast<Int8>();
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<ui.FrameInfo> getNextFrame() async {
final Duration duration = Duration(
milliseconds: animatedImageGetCurrentFrameDurationMilliseconds(handle),
);
final SkwasmImage image = SkwasmImage(animatedImageGetCurrentFrame(handle));
final ui.FrameInfo frameInfo = AnimatedImageFrameInfo(duration, image);
return frameInfo;
}
}

View File

@ -1080,7 +1080,9 @@ class SkwasmParagraphBuilder extends SkwasmObjectWrapper<RawParagraphBuilder>
@override
ui.Paragraph build() {
_addSegmenterData();
if (!skwasmIsHeavy()) {
_addSegmenterData();
}
return SkwasmParagraph(paragraphBuilderBuild(handle));
}

View File

@ -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<RawAnimatedImage>;
@Native<AnimatedImageHandle Function(SkDataHandle, Int32, Int32)>(
symbol: 'animatedImage_create',
isLeaf: true,
)
external AnimatedImageHandle animatedImageCreate(SkDataHandle handle, int width, int height);
@Native<Void Function(AnimatedImageHandle)>(symbol: 'animatedImage_dispose', isLeaf: true)
external void animatedImageDispose(AnimatedImageHandle handle);
@Native<Int32 Function(AnimatedImageHandle)>(symbol: 'animatedImage_getFrameCount', isLeaf: true)
external int animatedImageGetFrameCount(AnimatedImageHandle handle);
@Native<Int32 Function(AnimatedImageHandle)>(
symbol: 'animatedImage_getRepetitionCount',
isLeaf: true,
)
external int animatedImageGetRepetitionCount(AnimatedImageHandle handle);
@Native<Int32 Function(AnimatedImageHandle)>(
symbol: 'animatedImage_getCurrentFrameDurationMilliseconds',
isLeaf: true,
)
external int animatedImageGetCurrentFrameDurationMilliseconds(AnimatedImageHandle handle);
@Native<Void Function(AnimatedImageHandle)>(symbol: 'animatedImage_decodeNextFrame', isLeaf: true)
external void animatedImageDecodeNextFrame(AnimatedImageHandle handle);
@Native<ImageHandle Function(AnimatedImageHandle)>(
symbol: 'animatedImage_getCurrentFrame',
isLeaf: true,
)
external ImageHandle animatedImageGetCurrentFrame(AnimatedImageHandle handle);

View File

@ -25,3 +25,6 @@ external WasmI32 addFunction(WasmFuncRef function);
@Native<Bool Function()>(symbol: 'skwasm_isMultiThreaded', isLeaf: true)
external bool skwasmIsMultiThreaded();
@Native<Bool Function()>(symbol: 'skwasm_isHeavy', isLeaf: true)
external bool skwasmIsHeavy();

View File

@ -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(<ByteBuffer>[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(<ByteBuffer>[buffer]);
return SkwasmDomImageDecoder(blob);
}
}
}
@override

View File

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

View File

@ -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 <memory>
namespace {
std::unique_ptr<SkCodec> 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();
}

View File

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

View File

@ -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<SkUnicode> clientICU =
SkUnicodes::Client::Make(text, words, graphemeBreaks, lineBreaks);
builder->SetUnicode(clientICU);
return builder->Build().release();
}
SKWASM_EXPORT std::vector<SkUnicode::Position>* unicodePositionBuffer_create(
size_t length) {
return new std::vector<SkUnicode::Position>(length);
@ -92,21 +76,3 @@ SKWASM_EXPORT void lineBreakBuffer_free(
std::vector<SkUnicode::LineBreakBefore>* buffer) {
delete buffer;
}
SKWASM_EXPORT void paragraphBuilder_setGraphemeBreaksUtf16(
ParagraphBuilder* builder,
std::vector<SkUnicode::Position>* breaks) {
builder->setGraphemeBreaksUtf16(std::move(*breaks));
}
SKWASM_EXPORT void paragraphBuilder_setWordBreaksUtf16(
ParagraphBuilder* builder,
std::vector<SkUnicode::Position>* breaks) {
builder->setWordsUtf16(std::move(*breaks));
}
SKWASM_EXPORT void paragraphBuilder_setLineBreaksUtf16(
ParagraphBuilder* builder,
std::vector<SkUnicode::LineBreakBefore>* breaks) {
builder->setLineBreaksUtf16(std::move(*breaks));
}

View File

@ -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<SkUnicode::Position>* breaks) {
emscripten_console_warn(
"warning: setGraphemeBreaksUtf16 not implemented in skwasm_heavy\n");
}
SKWASM_EXPORT void paragraphBuilder_setWordBreaksUtf16(
ParagraphBuilder* builder,
std::vector<SkUnicode::Position>* breaks) {
emscripten_console_warn(
"warning: setWordBreaksUtf16 not implemented in skwasm_heavy\n");
}
SKWASM_EXPORT void paragraphBuilder_setLineBreaksUtf16(
ParagraphBuilder* builder,
std::vector<SkUnicode::LineBreakBefore>* breaks) {
emscripten_console_warn(
"warning: setLineBreaksUtf16 not implemented in skwasm_heavy\n");
}

View File

@ -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<SkUnicode> clientICU =
SkUnicodes::Client::Make(text, words, graphemeBreaks, lineBreaks);
builder->SetUnicode(clientICU);
return builder->Build().release();
}
SKWASM_EXPORT void paragraphBuilder_setGraphemeBreaksUtf16(
ParagraphBuilder* builder,
std::vector<SkUnicode::Position>* breaks) {
builder->setGraphemeBreaksUtf16(std::move(*breaks));
}
SKWASM_EXPORT void paragraphBuilder_setWordBreaksUtf16(
ParagraphBuilder* builder,
std::vector<SkUnicode::Position>* breaks) {
builder->setWordsUtf16(std::move(*breaks));
}
SKWASM_EXPORT void paragraphBuilder_setLineBreaksUtf16(
ParagraphBuilder* builder,
std::vector<SkUnicode::LineBreakBefore>* breaks) {
builder->setLineBreaksUtf16(std::move(*breaks));
}

View File

@ -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

View File

@ -73,7 +73,6 @@ Future<void> 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<void> 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<void> 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<void> testMain() async {
ui.Offset(0, 100),
ui.Offset(0, 50),
];
final double imageWidth = image.width.toDouble();
final double imageHeight = image.height.toDouble();
final List<ui.Offset> texCoords = <ui.Offset>[
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: <int>[
0, 1, 2, //
0, 2, 3, //
@ -299,7 +316,7 @@ Future<void> 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<void> 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;
});
}
}

View File

@ -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}}" ]
}

View File

@ -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",
]
}

View File

@ -200,11 +200,6 @@ MultiChannel<dynamic> _connectToServer() {
StreamChannel<dynamic> _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<dynamic> controller = StreamChannelController<dynamic>(sync: true);
@ -225,14 +220,6 @@ StreamChannel<dynamic> _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<dynamic> _connectToIframe(String url, int id) {
),
);
iframe
..src = url
..width = '1000'
..height = '1000';
domDocument.body!.appendChild(iframe);
return controller.foreign;
}