[web] Changes to WebParagraph configuration (#187188)
Changes:
- Drop `experimental` from the path and config flags.
- **Reason**: Other experimental modes don't use the word "experimental"
(e.g. skwasm_heavy, wimp), and I don't want it to be a breaking change
to graduate out of the "experimental" phase.
<hr>
- Instead of having WebParagraph be a third CK variant, make it a global
`enableWebParagraph` flag.
- **Reason**: In the future, we will support Skwasm and Wimp, so it
doesn't make sense to limit the flag to CanvasKit.
<hr>
- Feature-detect Chrome's Text Cluster API and fallback to another CK
variant if it's not supported.
- **Reason**: To allow for a smoother adoption path. Without this, all
users will have to do their own feature detection to adopt WebParagraph.
<hr>
- Throw a clear message when Dart code is in WebParagraph mode but the
downloaded CK variant doesn't support WebParagraph.
- **Reason**: I was bit by this multiple times. It shouldn't happen
anymore, but if it does, I want the error message to be clear instead of
some random failure.
<hr>
Fixes https://github.com/flutter/flutter/issues/186215
Part of https://github.com/flutter/flutter/issues/172561
---------
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
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 e818bc9..54f200d 100644
--- a/engine/src/flutter/ci/builders/linux_web_engine_test.json
+++ b/engine/src/flutter/ci/builders/linux_web_engine_test.json
@@ -3,7 +3,7 @@
"_comment2": "See `generate_builder_json.dart` for the generator code",
"builds": [
{
- "name": "web_tests/test_bundles/dart2js-canvaskit-experimental-webparagraph",
+ "name": "web_tests/test_bundles/dart2js-canvaskit-webparagraph",
"drone_dimensions": [
"device_type=none",
"os=Linux"
@@ -11,11 +11,11 @@
"generators": {
"tasks": [
{
- "name": "compile bundle dart2js-canvaskit-experimental-webparagraph",
+ "name": "compile bundle dart2js-canvaskit-webparagraph",
"parameters": [
"test",
"--compile",
- "--bundle=dart2js-canvaskit-experimental-webparagraph"
+ "--bundle=dart2js-canvaskit-webparagraph"
],
"scripts": [
"flutter/lib/web_ui/dev/felt"
@@ -209,7 +209,7 @@
"download_jdk": false
},
"dependencies": [
- "web_tests/test_bundles/dart2js-canvaskit-experimental-webparagraph",
+ "web_tests/test_bundles/dart2js-canvaskit-webparagraph",
"web_tests/test_bundles/dart2wasm-skwasm-ui",
"web_tests/test_bundles/dart2wasm-skwasm-skwasm",
"web_tests/test_bundles/dart2js-canvaskit-engine",
@@ -234,7 +234,7 @@
"parameters": [
"test",
"--copy-artifacts",
- "--suite=chrome-dart2js-experimental-webparagraph-ui",
+ "--suite=chrome-dart2js-webparagraph-ui",
"--suite=chrome-dart2wasm-wimp-ui",
"--suite=chrome-dart2wasm-wimp-skwasm",
"--suite=chrome-dart2js-canvaskit-engine",
@@ -254,11 +254,11 @@
"script": "flutter/lib/web_ui/dev/felt"
},
{
- "name": "run suite chrome-dart2js-experimental-webparagraph-ui",
+ "name": "run suite chrome-dart2js-webparagraph-ui",
"parameters": [
"test",
"--run",
- "--suite=chrome-dart2js-experimental-webparagraph-ui"
+ "--suite=chrome-dart2js-webparagraph-ui"
],
"script": "flutter/lib/web_ui/dev/felt"
},
diff --git a/engine/src/flutter/lib/web_ui/dev/build.dart b/engine/src/flutter/lib/web_ui/dev/build.dart
index 6746c43..507e235 100644
--- a/engine/src/flutter/lib/web_ui/dev/build.dart
+++ b/engine/src/flutter/lib/web_ui/dev/build.dart
@@ -18,8 +18,7 @@
'web_sdk': 'flutter/web_sdk',
'canvaskit': 'flutter/third_party/canvaskit:canvaskit_group',
'canvaskit_chromium': 'flutter/third_party/canvaskit:canvaskit_chromium_group',
- 'canvaskit_webparagraph':
- 'flutter/third_party/canvaskit:canvaskit_experimental_webparagraph_group',
+ 'canvaskit_webparagraph': 'flutter/third_party/canvaskit:canvaskit_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',
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 7c7aa2b..e18fe18 100644
--- a/engine/src/flutter/lib/web_ui/dev/felt_config.dart
+++ b/engine/src/flutter/lib/web_ui/dev/felt_config.dart
@@ -32,7 +32,7 @@
final List<CompileConfiguration> compileConfigs;
}
-enum CanvasKitVariant { full, chromium, experimentalWebParagraph }
+enum CanvasKitVariant { full, chromium }
enum BrowserName { chrome, edge, firefox, safari }
@@ -44,6 +44,7 @@
this.variant,
this.crossOriginIsolated,
this.forceSingleThreadedSkwasm,
+ this.enableWebParagraph,
this.enableWimp,
this.wasmAllowList,
);
@@ -54,33 +55,33 @@
final CanvasKitVariant? variant;
final bool crossOriginIsolated;
final bool forceSingleThreadedSkwasm;
+ final bool enableWebParagraph;
final bool enableWimp;
final Map<String, bool> wasmAllowList;
}
class ArtifactDependencies {
ArtifactDependencies({
- required this.canvasKitExperimentalWebParagraph,
+ required this.canvasKitWebParagraph,
required this.canvasKit,
required this.canvasKitChromium,
required this.skwasm,
});
ArtifactDependencies.none()
- : canvasKitExperimentalWebParagraph = false,
+ : canvasKitWebParagraph = false,
canvasKit = false,
canvasKitChromium = false,
skwasm = false;
- final bool canvasKitExperimentalWebParagraph;
+ final bool canvasKitWebParagraph;
final bool canvasKit;
final bool canvasKitChromium;
final bool skwasm;
ArtifactDependencies operator |(ArtifactDependencies other) {
return ArtifactDependencies(
- canvasKitExperimentalWebParagraph:
- canvasKitExperimentalWebParagraph || other.canvasKitExperimentalWebParagraph,
+ canvasKitWebParagraph: canvasKitWebParagraph || other.canvasKitWebParagraph,
canvasKit: canvasKit || other.canvasKit,
canvasKitChromium: canvasKitChromium || other.canvasKitChromium,
skwasm: skwasm || other.skwasm,
@@ -89,8 +90,7 @@
ArtifactDependencies operator &(ArtifactDependencies other) {
return ArtifactDependencies(
- canvasKitExperimentalWebParagraph:
- canvasKitExperimentalWebParagraph && other.canvasKitExperimentalWebParagraph,
+ canvasKitWebParagraph: canvasKitWebParagraph && other.canvasKitWebParagraph,
canvasKit: canvasKit && other.canvasKit,
canvasKitChromium: canvasKitChromium && other.canvasKitChromium,
skwasm: skwasm && other.skwasm,
@@ -194,6 +194,7 @@
final bool crossOriginIsolated = runConfigYaml['cross-origin-isolated'] as bool? ?? false;
final bool forceSingleThreadedSkwasm =
runConfigYaml['force-single-threaded-skwasm'] as bool? ?? false;
+ final bool enableWebParagraph = runConfigYaml['enable-webparagraph'] as bool? ?? false;
final bool enableWimp = runConfigYaml['enable-wimp'] as bool? ?? false;
final YamlMap wasmAllowList = (runConfigYaml['wasm-allow-list'] as YamlMap?) ?? YamlMap();
final runConfig = RunConfiguration(
@@ -203,6 +204,7 @@
variant,
crossOriginIsolated,
forceSingleThreadedSkwasm,
+ enableWebParagraph,
enableWimp,
wasmAllowList.cast<String, bool>(),
);
@@ -231,7 +233,7 @@
'Run config not found with name: `$runConfigName` (referenced by test suite: `$name`)',
);
}
- var canvasKitExperimentalWebParagraph = false;
+ var canvasKitWebParagraph = false;
var canvasKit = false;
var canvasKitChromium = false;
var skwasm = false;
@@ -239,11 +241,11 @@
if (depsNode != null) {
for (final dynamic dep in depsNode as YamlList) {
switch (dep as String) {
- case 'canvaskit_experimental_webparagraph':
- if (canvasKitExperimentalWebParagraph) {
+ case 'canvaskit_webparagraph':
+ if (canvasKitWebParagraph) {
throw AssertionError('Artifact dep $dep listed twice in suite $name.');
}
- canvasKitExperimentalWebParagraph = true;
+ canvasKitWebParagraph = true;
case 'canvaskit':
if (canvasKit) {
throw AssertionError('Artifact dep $dep listed twice in suite $name.');
@@ -265,7 +267,7 @@
}
}
final artifactDeps = ArtifactDependencies(
- canvasKitExperimentalWebParagraph: canvasKitExperimentalWebParagraph,
+ canvasKitWebParagraph: canvasKitWebParagraph,
canvasKit: canvasKit,
canvasKitChromium: canvasKitChromium,
skwasm: skwasm,
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 e95e0b3..ed1b201 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
@@ -74,7 +74,7 @@
@override
Future<void> run() async {
final String flutterJsSourceDirectory;
- final String canvaskitExperimentalWebParagraphSourceDirectory;
+ final String canvaskitWebParagraphSourceDirectory;
final String canvaskitSourceDirectory;
final String canvaskitChromiumSourceDirectory;
final String skwasmSourceDirectory;
@@ -83,9 +83,9 @@
case LocalArtifactSource(:final RuntimeMode mode):
final String buildDirectory = getBuildDirectoryForRuntimeMode(mode).path;
flutterJsSourceDirectory = pathlib.join(buildDirectory, 'flutter_web_sdk', 'flutter_js');
- canvaskitExperimentalWebParagraphSourceDirectory = pathlib.join(
+ canvaskitWebParagraphSourceDirectory = pathlib.join(
buildDirectory,
- 'canvaskit_experimental_webparagraph',
+ 'canvaskit_webparagraph',
);
canvaskitSourceDirectory = pathlib.join(buildDirectory, 'canvaskit');
canvaskitChromiumSourceDirectory = pathlib.join(buildDirectory, 'canvaskit_chromium');
@@ -95,10 +95,10 @@
case GcsArtifactSource(:final LuciRealm realm):
final String artifactsDirectory = (await _downloadArtifacts(realm)).path;
flutterJsSourceDirectory = pathlib.join(artifactsDirectory, 'flutter_js');
- canvaskitExperimentalWebParagraphSourceDirectory = pathlib.join(
+ canvaskitWebParagraphSourceDirectory = pathlib.join(
artifactsDirectory,
'canvaskit',
- 'experimental_webparagraph',
+ 'webparagraph',
);
canvaskitSourceDirectory = pathlib.join(artifactsDirectory, 'canvaskit');
canvaskitChromiumSourceDirectory = pathlib.join(
@@ -116,12 +116,12 @@
await copySkiaTestImages();
await copyFlutterJsFiles(flutterJsSourceDirectory);
final copied = <String>[];
- if (artifactDeps.canvasKitExperimentalWebParagraph) {
- copied.add('CanvasKit (Experimental Web Paragraph)');
+ if (artifactDeps.canvasKitWebParagraph) {
+ copied.add('CanvasKit (Web Paragraph)');
await copyWasmLibrary(
'canvaskit',
- canvaskitExperimentalWebParagraphSourceDirectory,
- 'canvaskit/experimental_webparagraph',
+ canvaskitWebParagraphSourceDirectory,
+ 'canvaskit/webparagraph',
);
}
if (artifactDeps.canvasKit) {
diff --git a/engine/src/flutter/lib/web_ui/dev/steps/run_suite_step.dart b/engine/src/flutter/lib/web_ui/dev/steps/run_suite_step.dart
index 2657f60..c0b304d 100644
--- a/engine/src/flutter/lib/web_ui/dev/steps/run_suite_step.dart
+++ b/engine/src/flutter/lib/web_ui/dev/steps/run_suite_step.dart
@@ -201,12 +201,14 @@
rendererName = singleThreaded ? 'skwasm_st' : 'skwasm';
}
}
+ final bool isWebParagraph = suite.runConfig.enableWebParagraph;
final dimensions = <String, String>{
'Browser': suite.runConfig.browser.name,
if (isWasm) 'Wasm': 'true',
'Renderer': rendererName,
'CanvasKitVariant': ?variant?.name,
+ if (isWebParagraph) 'WebParagraph': 'true',
};
final skiaClient = SkiaGoldClient(workDirectory, dimensions: dimensions);
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 d66c374..f2bf537 100644
--- a/engine/src/flutter/lib/web_ui/dev/test_platform.dart
+++ b/engine/src/flutter/lib/web_ui/dev/test_platform.dart
@@ -475,7 +475,6 @@
return switch (suite.runConfig.variant) {
CanvasKitVariant.full => 'full',
CanvasKitVariant.chromium => 'chromium',
- CanvasKitVariant.experimentalWebParagraph => 'experimentalWebParagraph',
null => 'auto',
};
}
@@ -534,6 +533,7 @@
canvasKitBaseUrl: "/canvaskit",
forceSingleThreadedSkwasm: ${suite.runConfig.forceSingleThreadedSkwasm},
wasmAllowList: ${jsonEncode(suite.runConfig.wasmAllowList)},
+ preferWebParagraph: ${suite.runConfig.enableWebParagraph},
enableWimp: ${suite.runConfig.enableWimp},
},
});
diff --git a/engine/src/flutter/lib/web_ui/dev/test_runner.dart b/engine/src/flutter/lib/web_ui/dev/test_runner.dart
index ec6e3cb..3351a28 100644
--- a/engine/src/flutter/lib/web_ui/dev/test_runner.dart
+++ b/engine/src/flutter/lib/web_ui/dev/test_runner.dart
@@ -351,8 +351,8 @@
print(' ${bundle.name.ansiMagenta}');
}
print('Artifacts:');
- if (artifacts.canvasKitExperimentalWebParagraph) {
- print(' canvaskit_experimental_webparagraph'.ansiYellow);
+ if (artifacts.canvasKitWebParagraph) {
+ print(' canvaskit_webparagraph'.ansiYellow);
}
if (artifacts.canvasKit) {
print(' canvaskit'.ansiYellow);
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 8a3dd34..24d5e3d 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
@@ -49,6 +49,10 @@
(typeof Intl.Segmenter !== "undefined");
}
+const hasTextCluster = () => {
+ return (typeof window.TextCluster !== "undefined");
+}
+
const supportsWasmGC = () => {
// This attempts to instantiate a wasm module that only will validate if the
// final WasmGC spec is implemented in the browser.
@@ -83,6 +87,7 @@
browserEngine: browserEngine,
hasImageCodecs: hasImageCodecs(),
hasChromiumBreakIterators: hasChromiumBreakIterators(),
+ hasTextCluster: hasTextCluster(),
supportsWasmGC: supportsWasmGC(),
crossOriginIsolated: window.crossOriginIsolated,
webGLVersion: detectWebGLVersion(),
diff --git a/engine/src/flutter/lib/web_ui/flutter_js/src/canvaskit_loader.js b/engine/src/flutter/lib/web_ui/flutter_js/src/canvaskit_loader.js
index 9c52318..cbb1317 100644
--- a/engine/src/flutter/lib/web_ui/flutter_js/src/canvaskit_loader.js
+++ b/engine/src/flutter/lib/web_ui/flutter_js/src/canvaskit_loader.js
@@ -16,9 +16,10 @@
throw "Chromium CanvasKit variant specifically requested, but unsupported in this browser";
}
const useChromiumCanvasKit = supportsChromiumCanvasKit && (config.canvasKitVariant !== "full");
+ const useWebParagraphCanvasKit = useChromiumCanvasKit && config.preferWebParagraph && browserEnvironment.hasTextCluster;
let baseUrl = canvasKitBaseUrl;
- if (config.canvasKitVariant == "experimentalWebParagraph") {
- baseUrl = resolveUrlWithSegments(baseUrl, "experimental_webparagraph");
+ if (useWebParagraphCanvasKit) {
+ baseUrl = resolveUrlWithSegments(baseUrl, "webparagraph");
} else if (useChromiumCanvasKit) {
baseUrl = resolveUrlWithSegments(baseUrl, "chromium");
}
@@ -27,8 +28,8 @@
canvasKitUrl = deps.flutterTT.policy.createScriptURL(canvasKitUrl);
}
let filename = "canvaskit.wasm";
- if (config.canvasKitVariant == "experimentalWebParagraph") {
- filename = "experimental_webparagraph/canvaskit.wasm";
+ if (useWebParagraphCanvasKit) {
+ filename = "webparagraph/canvaskit.wasm";
} else if (useChromiumCanvasKit) {
filename = "chromium/canvaskit.wasm";
}
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 0f7a3be..f69d6c8 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
@@ -59,6 +59,7 @@
canvasKitBaseUrl?: string;
canvasKitVariant?: CanvasKitVariant;
renderer?: WebRenderer;
+ preferWebParagraph?: boolean;
enableWimp?: boolean;
hostElement?: HTMLElement;
fontFallbackBaseUrl?: string;
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/browser_detection.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/browser_detection.dart
index 56fa893..efdd374 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/browser_detection.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/browser_detection.dart
@@ -140,6 +140,10 @@
bool get browserSupportsCanvaskitChromium =>
domIntl.v8BreakIterator != null && domIntl.Segmenter != null && browserSupportsImageDecoder;
+/// Whether the current browser supports the WebParagraph implementation.
+bool get browserSupportsWebParagraph =>
+ browserSupportsCanvaskitChromium && browserSupportsTextCluster;
+
/// Whether the current browser is Safari 17.4 or newer.
///
/// Safari 17.4 introduced support for aria-description.
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
index 8fe11ed..2ff0e23 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
@@ -95,9 +95,14 @@
Uint16List? indices,
) => _MakeVertices(mode, positions.toJS, textureCoordinates?.toJS, colors?.toJS, indices?.toJS);
- external BidiNamespace get Bidi;
-
- external CodeUnitsNamespace get CodeUnits;
+ @JS('Bidi')
+ external BidiNamespace? get _Bidi;
+ BidiNamespace get Bidi {
+ if (_Bidi == null) {
+ throw StateError('The downloaded CanvasKit version does not support WebParagraph');
+ }
+ return _Bidi!;
+ }
external SkParagraphBuilderNamespace get ParagraphBuilder;
external SkParagraphStyle ParagraphStyle(SkParagraphStyleProperties properties);
@@ -1849,16 +1854,6 @@
_reorderVisual(visuals.toJS).toDart.cast<BidiIndex>();
}
-extension type CodeUnitInfo(JSObject _) implements JSObject {
- external int get flags;
-}
-
-extension type CodeUnitsNamespace(JSObject _) implements JSObject {
- @JS('compute')
- external JSArray<JSAny?> _compute(String text);
- List<CodeUnitInfo> compute(String text) => _compute(text).toDart.cast<CodeUnitInfo>();
-}
-
extension type SkParagraphBuilderNamespace(JSObject _) implements JSObject {
external SkParagraphBuilder MakeFromFontCollection(
SkParagraphStyle paragraphStyle,
@@ -2425,21 +2420,25 @@
const String _kFullCanvasKitJsFileName = 'canvaskit.js';
const String _kChromiumCanvasKitJsFileName = 'chromium/canvaskit.js';
-const String _kWebParagraphCanvasKitJsFileName = 'experimental_webparagraph/canvaskit.js';
+const String _kWebParagraphCanvasKitJsFileName = 'webparagraph/canvaskit.js';
String get _canvasKitBaseUrl => configuration.canvasKitBaseUrl;
@visibleForTesting
List<String> getCanvasKitJsFileNames(CanvasKitVariant variant) {
- return switch (variant) {
- CanvasKitVariant.auto => <String>[
- if (_enableCanvasKitChromiumInAutoMode) _kChromiumCanvasKitJsFileName,
- _kFullCanvasKitJsFileName,
- ],
- CanvasKitVariant.full => <String>[_kFullCanvasKitJsFileName],
- CanvasKitVariant.chromium => <String>[_kChromiumCanvasKitJsFileName],
- CanvasKitVariant.experimentalWebParagraph => <String>[_kWebParagraphCanvasKitJsFileName],
- };
+ if (isWebParagraphEnabled) {
+ return <String>[_kWebParagraphCanvasKitJsFileName];
+ }
+ return [
+ ...switch (variant) {
+ CanvasKitVariant.auto => <String>[
+ if (_enableCanvasKitChromiumInAutoMode) _kChromiumCanvasKitJsFileName,
+ _kFullCanvasKitJsFileName,
+ ],
+ CanvasKitVariant.full => <String>[_kFullCanvasKitJsFileName],
+ CanvasKitVariant.chromium => <String>[_kChromiumCanvasKitJsFileName],
+ },
+ ];
}
Iterable<String> get _canvasKitJsUrls {
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart
index ee49833..e9e9819 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart
@@ -11,8 +11,7 @@
import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
-bool get isExperimentalWebParagraph =>
- configuration.canvasKitVariant == CanvasKitVariant.experimentalWebParagraph;
+bool get isWebParagraphEnabled => configuration.preferWebParagraph && browserSupportsWebParagraph;
class CanvasKitRenderer extends Renderer {
static CanvasKitRenderer get instance => _instance;
@@ -26,7 +25,7 @@
/// Whether the renderer is using software rendering.
bool get isSoftware => _pictureToImageSurface.isSoftware;
- late final FlutterFontCollection _fontCollection = isExperimentalWebParagraph
+ late final FlutterFontCollection _fontCollection = isWebParagraphEnabled
? WebFontCollection()
: SkiaFontCollection();
@@ -332,7 +331,7 @@
List<ui.Shadow>? shadows,
List<ui.FontFeature>? fontFeatures,
List<ui.FontVariation>? fontVariations,
- }) => isExperimentalWebParagraph
+ }) => isWebParagraphEnabled
? WebTextStyle(
color: color,
decoration: decoration,
@@ -394,7 +393,7 @@
ui.StrutStyle? strutStyle,
String? ellipsis,
ui.Locale? locale,
- }) => isExperimentalWebParagraph
+ }) => isWebParagraphEnabled
? WebParagraphStyle(
textAlign: textAlign,
textDirection: textDirection,
@@ -435,7 +434,7 @@
ui.FontWeight? fontWeight,
ui.FontStyle? fontStyle,
bool? forceStrutHeight,
- }) => isExperimentalWebParagraph
+ }) => isWebParagraphEnabled
? WebStrutStyle(
fontFamily: fontFamily,
fontFamilyFallback: fontFamilyFallback,
@@ -461,7 +460,7 @@
@override
ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) =>
- isExperimentalWebParagraph ? WebParagraphBuilder(style) : CkParagraphBuilder(style);
+ isWebParagraphEnabled ? WebParagraphBuilder(style) : CkParagraphBuilder(style);
@override
WebParagraphPainter createWebParagraphPainter(WebParagraph paragraph) =>
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/configuration.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/configuration.dart
index cb350d8..ad12566 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/configuration.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/configuration.dart
@@ -63,12 +63,6 @@
/// WARNING: In most cases, you should use [auto] instead of this variant. Using
/// this variant in a non-Chromium browser will result in a broken app.
chromium,
-
- /// The variant that contains the new WebParagraph implementation on top of Chrome's Text Clusters
- /// API: https://github.com/fserb/canvas2D/blob/master/spec/enhanced-textmetrics.md
- ///
- /// WARNING: This is an experimental variant that's not yet ready for production use.
- experimentalWebParagraph,
}
/// The Web Engine configuration for the current application.
@@ -312,6 +306,14 @@
return maxSurfaces;
}
+ /// Enables the new WebParagraph implementation built on top of Chrome's Text Clusters
+ /// API: https://github.com/fserb/canvas2D/blob/master/spec/enhanced-textmetrics.md
+ ///
+ /// If the browser doesn't support WebParagraph, this flag will have no effect.
+ ///
+ /// WARNING: This is an experimental feature.
+ bool get preferWebParagraph => _configuration?.preferWebParagraph ?? false;
+
/// Set this flag to `true` to cause the engine to visualize the semantics tree
/// on the screen for debugging.
///
@@ -402,6 +404,7 @@
external bool? get canvasKitForceCpuOnly;
external bool? get canvasKitForceMultiSurfaceRasterizer;
external double? get canvasKitMaximumSurfaces;
+ external bool? get preferWebParagraph;
external bool? get debugShowSemanticsNodes;
external DomElement? get hostElement;
external bool? get multiViewEnabled;
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart
index 35605c8..dbe8c12 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart
@@ -2702,6 +2702,11 @@
external int get length;
}
+@JS('window.TextCluster')
+external JSAny? get _textClusterConstructor;
+
+bool browserSupportsTextCluster = _textClusterConstructor != null;
+
@JS('TextCluster')
extension type DomTextCluster._(JSObject _) implements JSObject {
@JS('begin')
diff --git a/engine/src/flutter/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/engine/src/flutter/lib/web_ui/test/canvaskit/canvaskit_api_test.dart
index ceb6750..fda7d75 100644
--- a/engine/src/flutter/lib/web_ui/test/canvaskit/canvaskit_api_test.dart
+++ b/engine/src/flutter/lib/web_ui/test/canvaskit/canvaskit_api_test.dart
@@ -1839,6 +1839,56 @@
expect(getCanvasKitJsFileNames(CanvasKitVariant.chromium), <String>['chromium/canvaskit.js']);
expect(getCanvasKitJsFileNames(CanvasKitVariant.auto), <String>['canvaskit.js']);
});
+
+ test('with preferWebParagraph enabled and supported', () {
+ v8BreakIterator = Object().toJSBox; // Any non-null value.
+ intlSegmenter = Object().toJSBox; // Any non-null value.
+ browserSupportsImageDecoder = true;
+ browserSupportsTextCluster = true;
+
+ debugOverrideJsConfiguration(
+ <String, Object?>{'preferWebParagraph': true}.jsify() as JsFlutterConfiguration?,
+ );
+
+ try {
+ expect(getCanvasKitJsFileNames(CanvasKitVariant.full), <String>[
+ 'webparagraph/canvaskit.js',
+ ]);
+ expect(getCanvasKitJsFileNames(CanvasKitVariant.chromium), <String>[
+ 'webparagraph/canvaskit.js',
+ ]);
+ expect(getCanvasKitJsFileNames(CanvasKitVariant.auto), <String>[
+ 'webparagraph/canvaskit.js',
+ ]);
+ } finally {
+ debugOverrideJsConfiguration(null);
+ browserSupportsTextCluster = false;
+ }
+ });
+
+ test('with preferWebParagraph enabled but NOT supported', () {
+ v8BreakIterator = Object().toJSBox; // Any non-null value.
+ intlSegmenter = Object().toJSBox; // Any non-null value.
+ browserSupportsImageDecoder = true;
+ browserSupportsTextCluster = false; // Not supported!
+
+ debugOverrideJsConfiguration(
+ <String, Object?>{'preferWebParagraph': true}.jsify() as JsFlutterConfiguration?,
+ );
+
+ try {
+ expect(getCanvasKitJsFileNames(CanvasKitVariant.full), <String>['canvaskit.js']);
+ expect(getCanvasKitJsFileNames(CanvasKitVariant.chromium), <String>[
+ 'chromium/canvaskit.js',
+ ]);
+ expect(getCanvasKitJsFileNames(CanvasKitVariant.auto), <String>[
+ 'chromium/canvaskit.js',
+ 'canvaskit.js',
+ ]);
+ } finally {
+ debugOverrideJsConfiguration(null);
+ }
+ });
});
test('respects actual location of canvaskit files', () {
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 d564332..e79794f 100644
--- a/engine/src/flutter/lib/web_ui/test/felt_config.yaml
+++ b/engine/src/flutter/lib/web_ui/test/felt_config.yaml
@@ -19,7 +19,7 @@
test-sets:
# Tests for the new WebParagraph
- - name: experimental-webparagraph
+ - name: webparagraph
directory: webparagraph
# Tests for non-renderer logic
@@ -43,8 +43,8 @@
directory: fallbacks
test-bundles:
- - name: dart2js-canvaskit-experimental-webparagraph
- test-set: experimental-webparagraph
+ - name: dart2js-canvaskit-webparagraph
+ test-set: webparagraph
compile-configs: dart2js-canvaskit
- name: dart2js-canvaskit-engine
@@ -78,14 +78,14 @@
- dart2js-canvaskit
run-configs:
- - name: chrome-experimental-webparagraph
+ - name: chrome-webparagraph
browser: chrome
browser-flags:
- --enable-experimental-web-platform-features
# This is supposed to be the right flag, but for some reason it doesn't work at the moment so
# we are using the more general one above.
# - --enable-features=ExtendedTextMetrics
- canvaskit-variant: experimentalWebParagraph
+ enable-webparagraph: true
- name: chrome
browser: chrome
@@ -142,10 +142,10 @@
test-suites:
- - name: chrome-dart2js-experimental-webparagraph-ui
- test-bundle: dart2js-canvaskit-experimental-webparagraph
- run-config: chrome-experimental-webparagraph
- artifact-deps: [ canvaskit_experimental_webparagraph ]
+ - name: chrome-dart2js-webparagraph-ui
+ test-bundle: dart2js-canvaskit-webparagraph
+ run-config: chrome-webparagraph
+ artifact-deps: [ canvaskit_webparagraph ]
- name: chrome-dart2wasm-wimp-ui
test-bundle: dart2wasm-skwasm-ui
diff --git a/engine/src/flutter/third_party/canvaskit/BUILD.gn b/engine/src/flutter/third_party/canvaskit/BUILD.gn
index abe4ea0..92536dd 100644
--- a/engine/src/flutter/third_party/canvaskit/BUILD.gn
+++ b/engine/src/flutter/third_party/canvaskit/BUILD.gn
@@ -65,8 +65,8 @@
]
}
-# This toolchain is only to be used by canvaskit_experimental_webparagraph_group below.
-wasm_toolchain("canvaskit_experimental_webparagraph") {
+# This toolchain is only to be used by canvaskit_webparagraph_group below.
+wasm_toolchain("canvaskit_webparagraph") {
extra_toolchain_args = {
# In Chromium browsers, we can use the browser's APIs to get the necessary
# ICU data.
@@ -99,18 +99,16 @@
}
}
-copy("canvaskit_experimental_webparagraph_group") {
+copy("canvaskit_webparagraph_group") {
visibility = [ "//flutter/web_sdk:*" ]
- public_deps = [
- "//flutter/skia/modules/canvaskit(:canvaskit_experimental_webparagraph)",
- ]
+ public_deps = [ "//flutter/skia/modules/canvaskit(:canvaskit_webparagraph)" ]
sources = [
- "$root_out_dir/canvaskit_experimental_webparagraph/canvaskit.js",
- "$root_out_dir/canvaskit_experimental_webparagraph/canvaskit.js.symbols",
- "$root_out_dir/canvaskit_experimental_webparagraph/canvaskit.wasm",
+ "$root_out_dir/canvaskit_webparagraph/canvaskit.js",
+ "$root_out_dir/canvaskit_webparagraph/canvaskit.js.symbols",
+ "$root_out_dir/canvaskit_webparagraph/canvaskit.wasm",
]
- outputs = [ "$root_out_dir/flutter_web_sdk/canvaskit/experimental_webparagraph/{{source_file_part}}" ]
+ outputs = [ "$root_out_dir/flutter_web_sdk/canvaskit/webparagraph/{{source_file_part}}" ]
}
# This toolchain is only to be used by skwasm_group below.
diff --git a/engine/src/flutter/web_sdk/BUILD.gn b/engine/src/flutter/web_sdk/BUILD.gn
index 679f053..a7bd3f4 100644
--- a/engine/src/flutter/web_sdk/BUILD.gn
+++ b/engine/src/flutter/web_sdk/BUILD.gn
@@ -402,8 +402,8 @@
if (is_wasm) {
deps += [
"//flutter/third_party/canvaskit:canvaskit_chromium_group",
- "//flutter/third_party/canvaskit:canvaskit_experimental_webparagraph_group",
"//flutter/third_party/canvaskit:canvaskit_group",
+ "//flutter/third_party/canvaskit:canvaskit_webparagraph_group",
"//flutter/third_party/canvaskit:skwasm_group",
"//flutter/third_party/canvaskit:skwasm_heavy_group",
"//flutter/third_party/canvaskit:wimp_group",
@@ -428,15 +428,15 @@
"$root_out_dir/flutter_web_sdk/canvaskit/chromium/canvaskit.js",
"$root_out_dir/flutter_web_sdk/canvaskit/chromium/canvaskit.js.symbols",
"$root_out_dir/flutter_web_sdk/canvaskit/chromium/canvaskit.wasm",
- "$root_out_dir/flutter_web_sdk/canvaskit/experimental_webparagraph/canvaskit.js",
- "$root_out_dir/flutter_web_sdk/canvaskit/experimental_webparagraph/canvaskit.js.symbols",
- "$root_out_dir/flutter_web_sdk/canvaskit/experimental_webparagraph/canvaskit.wasm",
"$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",
+ "$root_out_dir/flutter_web_sdk/canvaskit/webparagraph/canvaskit.js",
+ "$root_out_dir/flutter_web_sdk/canvaskit/webparagraph/canvaskit.js.symbols",
+ "$root_out_dir/flutter_web_sdk/canvaskit/webparagraph/canvaskit.wasm",
"$root_out_dir/flutter_web_sdk/canvaskit/wimp.js",
"$root_out_dir/flutter_web_sdk/canvaskit/wimp.js.symbols",
"$root_out_dir/flutter_web_sdk/canvaskit/wimp.wasm",