Reland "Skwasm Font Loading (flutter/engine#41756)

This relands https://github.com/flutter/engine/pull/41246, which had to be reverted due to some issues parsing the font manifest.
This commit is contained in:
Jackson Gardner
2023-05-08 11:17:22 -07:00
committed by GitHub
parent c89a6c51f6
commit 16360e9e25
117 changed files with 1437 additions and 919 deletions

View File

@@ -1949,6 +1949,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart + ../..
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/initialization.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_loader.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_promise.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart + ../../../flutter/LICENSE
@@ -2001,8 +2002,8 @@ 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/js_functions.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_fonts.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_geometry.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_paint.dart + ../../../flutter/LICENSE
@@ -2010,7 +2011,10 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_pa
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_path_metrics.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_picture.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skdata.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/scene_builder.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart + ../../../flutter/LICENSE
@@ -2060,12 +2064,15 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/url_strategy.dart + ..
ORIGIN: ../../../flutter/lib/web_ui/lib/window.dart + ../../../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
ORIGIN: ../../../flutter/lib/web_ui/skwasm/export.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/fonts.cpp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/helpers.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/paint.cpp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/path.cpp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/picture.cpp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/shaders.cpp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/string.cpp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/surface.cpp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/wrappers.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/runtime/dart_isolate.cc + ../../../flutter/LICENSE
@@ -4545,6 +4552,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/initialization.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_loader.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_promise.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart
@@ -4597,8 +4605,8 @@ 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/js_functions.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_fonts.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_geometry.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_paint.dart
@@ -4606,7 +4614,10 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_path
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_path_metrics.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_picture.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skdata.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/scene_builder.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart
@@ -4656,12 +4667,15 @@ FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/url_strategy.dart
FILE: ../../../flutter/lib/web_ui/lib/window.dart
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
FILE: ../../../flutter/lib/web_ui/skwasm/export.h
FILE: ../../../flutter/lib/web_ui/skwasm/fonts.cpp
FILE: ../../../flutter/lib/web_ui/skwasm/helpers.h
FILE: ../../../flutter/lib/web_ui/skwasm/paint.cpp
FILE: ../../../flutter/lib/web_ui/skwasm/path.cpp
FILE: ../../../flutter/lib/web_ui/skwasm/picture.cpp
FILE: ../../../flutter/lib/web_ui/skwasm/shaders.cpp
FILE: ../../../flutter/lib/web_ui/skwasm/string.cpp
FILE: ../../../flutter/lib/web_ui/skwasm/surface.cpp
FILE: ../../../flutter/lib/web_ui/skwasm/wrappers.h
FILE: ../../../flutter/runtime/dart_isolate.cc

View File

@@ -108,6 +108,7 @@ export 'engine/html_image_codec.dart';
export 'engine/initialization.dart';
export 'engine/js_interop/js_loader.dart';
export 'engine/js_interop/js_promise.dart';
export 'engine/js_interop/js_typed_data.dart';
export 'engine/key_map.g.dart';
export 'engine/keyboard_binding.dart';
export 'engine/mouse_cursor.dart';

View File

@@ -8,20 +8,6 @@ import 'dart:typed_data';
import 'dom.dart';
import 'util.dart';
const String ahemFontFamily = 'Ahem';
const String ahemFontUrl = '/assets/fonts/ahem.ttf';
const String robotoFontFamily = 'Roboto';
const String robotoTestFontUrl = '/assets/fonts/Roboto-Regular.ttf';
/// The list of test fonts, in the form of font family name - font file url pairs.
/// This list does not include embedded test fonts, which need to be loaded and
/// registered separately in [FontCollection.debugDownloadTestFonts].
const Map<String, String> testFontUrls = <String, String>{
ahemFontFamily: ahemFontUrl,
robotoFontFamily: robotoTestFontUrl,
'RobotoVariable': '/assets/fonts/RobotoSlab-VariableFont_wght.ttf',
};
/// This class downloads assets over the network.
///
/// Assets are resolved relative to [assetsDir] inside the absolute base
@@ -110,79 +96,3 @@ class AssetManager {
return (await response.payload.asByteBuffer()).asByteData();
}
}
/// An asset manager that gives fake empty responses for assets.
class WebOnlyMockAssetManager extends AssetManager {
/// Mock asset directory relative to base url.
String defaultAssetsDir = '';
/// Mock empty asset manifest.
String defaultAssetManifest = '{}';
/// Mock font manifest overridable for unit testing.
String defaultFontManifest = '''
[
{
"family":"$robotoFontFamily",
"fonts":[{"asset":"$robotoTestFontUrl"}]
},
{
"family":"$ahemFontFamily",
"fonts":[{"asset":"$ahemFontUrl"}]
}
]''';
@override
String get assetsDir => defaultAssetsDir;
@override
String getAssetUrl(String asset) => asset;
@override
Future<HttpFetchResponse> loadAsset(String asset) async {
if (asset == getAssetUrl('AssetManifest.json')) {
return MockHttpFetchResponse(
url: asset,
status: 200,
payload: MockHttpFetchPayload(
byteBuffer: _toByteData(utf8.encode(defaultAssetManifest)).buffer,
),
);
}
if (asset == getAssetUrl('FontManifest.json')) {
return MockHttpFetchResponse(
url: asset,
status: 200,
payload: MockHttpFetchPayload(
byteBuffer: _toByteData(utf8.encode(defaultFontManifest)).buffer,
),
);
}
return MockHttpFetchResponse(
url: asset,
status: 404,
);
}
@override
Future<ByteData> load(String asset) {
if (asset == getAssetUrl('AssetManifest.json')) {
return Future<ByteData>.value(
_toByteData(utf8.encode(defaultAssetManifest)));
}
if (asset == getAssetUrl('FontManifest.json')) {
return Future<ByteData>.value(
_toByteData(utf8.encode(defaultFontManifest)));
}
throw HttpFetchNoPayloadError(asset, status: 404);
}
ByteData _toByteData(List<int> bytes) {
final ByteData byteData = ByteData(bytes.length);
for (int i = 0; i < bytes.length; i++) {
byteData.setUint8(i, bytes[i]);
}
return byteData;
}
}

View File

@@ -456,7 +456,7 @@ class FallbackFontDownloadQueue {
final Uint8List bytes = downloadedData[url]!;
FontFallbackData.instance.registerFallbackFont(font.name, bytes);
if (pendingFonts.isEmpty) {
renderer.fontCollection.registerDownloadedFonts();
(renderer.fontCollection as SkiaFontCollection).registerDownloadedFonts();
sendFontChangeMessage();
}
}

View File

@@ -3,17 +3,9 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:web_test_fonts/web_test_fonts.dart';
import '../assets.dart';
import '../dom.dart';
import '../fonts.dart';
import '../util.dart';
import 'canvaskit_api.dart';
import 'font_fallbacks.dart';
import 'package:ui/src/engine.dart';
// This URL was found by using the Google Fonts Developer API to find the URL
// for Roboto. The API warns that this URL is not stable. In order to update
@@ -77,15 +69,18 @@ class SkiaFontCollection implements FlutterFontCollection {
}
@override
Future<void> loadFontFromList(Uint8List list, {String? fontFamily}) async {
Future<bool> loadFontFromList(Uint8List list, {String? fontFamily}) async {
if (fontFamily == null) {
fontFamily = _readActualFamilyName(list);
if (fontFamily == null) {
printWarning('Failed to read font family name. Aborting font load.');
return;
return false;
}
}
// Make sure CanvasKit is actually loaded
await renderer.initialize();
final SkTypeface? typeface =
canvasKit.Typeface.MakeFreeTypeFaceFromData(list.buffer);
if (typeface != null) {
@@ -93,53 +88,65 @@ class SkiaFontCollection implements FlutterFontCollection {
_registerWithFontProvider();
} else {
printWarning('Failed to parse font family "$fontFamily"');
return;
return false;
}
return true;
}
/// Loads fonts from `FontManifest.json`.
@override
Future<void> downloadAssetFonts(AssetManager assetManager) async {
final HttpFetchResponse response = await assetManager.loadAsset('FontManifest.json');
if (!response.hasPayload) {
printWarning('Font manifest does not exist at `${response.url}` - ignoring.');
return;
}
final Uint8List data = await response.asUint8List();
final List<dynamic>? fontManifest = json.decode(utf8.decode(data)) as List<dynamic>?;
if (fontManifest == null) {
throw AssertionError(
'There was a problem trying to load FontManifest.json');
}
final List<Future<UnregisteredFont?>> pendingFonts = <Future<UnregisteredFont?>>[];
for (final Map<String, dynamic> fontFamily
in fontManifest.cast<Map<String, dynamic>>()) {
final String family = fontFamily.readString('family');
final List<dynamic> fontAssets = fontFamily.readList('fonts');
for (final dynamic fontAssetItem in fontAssets) {
final Map<String, dynamic> fontAsset = fontAssetItem as Map<String, dynamic>;
final String asset = fontAsset.readString('asset');
_downloadFont(pendingFonts, assetManager.getAssetUrl(asset), family);
Future<AssetFontsResult> loadAssetFonts(FontManifest manifest) async {
final List<Future<FontDownloadResult>> pendingDownloads = <Future<FontDownloadResult>>[];
bool loadedRoboto = false;
for (final FontFamily family in manifest.families) {
if (family.name == 'Roboto') {
loadedRoboto = true;
}
for (final FontAsset fontAsset in family.fontAssets) {
final String url = assetManager.getAssetUrl(fontAsset.asset);
pendingDownloads.add(_downloadFont(fontAsset.asset, url, family.name));
}
}
/// We need a default fallback font for CanvasKit, in order to
/// avoid crashing while laying out text with an unregistered font. We chose
/// Roboto to match Android.
if (!_isFontFamilyDownloaded('Roboto')) {
if (!loadedRoboto) {
// Download Roboto and add it to the font buffers.
_downloadFont(pendingFonts, _robotoUrl, 'Roboto');
pendingDownloads.add(_downloadFont('Roboto', _robotoUrl, 'Roboto'));
}
final List<UnregisteredFont?> completedPendingFonts = await Future.wait(pendingFonts);
_unregisteredFonts.addAll(completedPendingFonts.whereType<UnregisteredFont>());
final Map<String, FontLoadError> fontFailures = <String, FontLoadError>{};
final List<(String, UnregisteredFont)> downloadedFonts = <(String, UnregisteredFont)>[];
for (final FontDownloadResult result in await Future.wait(pendingDownloads)) {
if (result.font != null) {
downloadedFonts.add((result.assetName, result.font!));
} else {
fontFailures[result.assetName] = result.error!;
}
}
// Make sure CanvasKit is actually loaded
await renderer.initialize();
final List<String> loadedFonts = <String>[];
for (final (String assetName, UnregisteredFont unregisteredFont) in downloadedFonts) {
final Uint8List bytes = unregisteredFont.bytes.asUint8List();
final SkTypeface? typeface =
canvasKit.Typeface.MakeFreeTypeFaceFromData(bytes.buffer);
if (typeface != null) {
loadedFonts.add(assetName);
_registeredFonts.add(RegisteredFont(bytes, unregisteredFont.family, typeface));
} else {
printWarning('Failed to load font ${unregisteredFont.family} at ${unregisteredFont.url}');
printWarning('Verify that ${unregisteredFont.url} contains a valid font.');
fontFailures[assetName] = FontInvalidDataError(unregisteredFont.url);
}
}
registerDownloadedFonts();
return AssetFontsResult(loadedFonts, fontFailures);
}
@override
void registerDownloadedFonts() {
RegisteredFont? makeRegisterFont(ByteBuffer buffer, String url, String family) {
final Uint8List bytes = buffer.asUint8List();
@@ -169,61 +176,30 @@ class SkiaFontCollection implements FlutterFontCollection {
_registerWithFontProvider();
}
/// Whether the [fontFamily] was registered and/or loaded.
bool _isFontFamilyDownloaded(String fontFamily) {
return _downloadedFontFamilies.contains(fontFamily);
}
/// Loads the Ahem font, unless it's already been loaded using
/// `FontManifest.json` (see [downloadAssetFonts]).
///
/// `FontManifest.json` has higher priority than the default test font URLs.
/// This allows customizing test environments where fonts are loaded from
/// different URLs.
@override
Future<void> debugDownloadTestFonts() async {
final List<Future<UnregisteredFont?>> pendingFonts = <Future<UnregisteredFont?>>[];
for (final MapEntry<String, String> fontEntry in testFontUrls.entries) {
if (!_isFontFamilyDownloaded(fontEntry.key)) {
_downloadFont(pendingFonts, fontEntry.value, fontEntry.key);
}
}
final List<UnregisteredFont?> completedPendingFonts = await Future.wait(pendingFonts);
final List<UnregisteredFont> fonts = <UnregisteredFont>[
UnregisteredFont(
EmbeddedTestFont.flutterTest.data.buffer,
'<embedded>',
EmbeddedTestFont.flutterTest.fontFamily,
),
...completedPendingFonts.whereType<UnregisteredFont>(),
];
_unregisteredFonts.addAll(fonts);
// Ahem must be added to font fallbacks list regardless of where it was
// downloaded from.
FontFallbackData.instance.globalFontFallbacks.add(ahemFontFamily);
}
void _downloadFont(
List<Future<UnregisteredFont?>> waitUnregisteredFonts,
Future<FontDownloadResult> _downloadFont(
String assetName,
String url,
String family
) {
Future<UnregisteredFont?> downloadFont() async {
// Try to get the font leniently. Do not crash the app when failing to
// fetch the font in the spirit of "gradual degradation of functionality".
try {
final ByteBuffer data = await httpFetchByteBuffer(url);
return UnregisteredFont(data, url, family);
} catch (e) {
printWarning('Failed to load font $family at $url');
printWarning(e.toString());
return null;
}
}
String fontFamily
) async {
final ByteBuffer fontData;
_downloadedFontFamilies.add(family);
waitUnregisteredFonts.add(downloadFont());
// Try to get the font leniently. Do not crash the app when failing to
// fetch the font in the spirit of "gradual degradation of functionality".
try {
final HttpFetchResponse response = await httpFetch(url);
if (!response.hasPayload) {
printWarning('Font family $fontFamily not found (404) at $url');
return FontDownloadResult.fromError(assetName, FontNotFoundError(url));
}
fontData = await response.asByteBuffer();
} catch (e) {
printWarning('Failed to load font $fontFamily at $url');
printWarning(e.toString());
return FontDownloadResult.fromError(assetName, FontDownloadError(url, e));
}
_downloadedFontFamilies.add(fontFamily);
return FontDownloadResult.fromFont(assetName, UnregisteredFont(fontData, url, fontFamily));
}
@@ -269,3 +245,12 @@ class UnregisteredFont {
final String url;
final String family;
}
class FontDownloadResult {
FontDownloadResult.fromFont(this.assetName, UnregisteredFont this.font) : error = null;
FontDownloadResult.fromError(this.assetName, FontLoadError this.error) : font = null;
final String assetName;
final UnregisteredFont? font;
final FontLoadError? error;
}

View File

@@ -3,23 +3,12 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:js_interop';
import 'dart:typed_data';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import '../dom.dart';
import '../html_image_codec.dart';
import '../safe_browser_api.dart';
import '../util.dart';
import 'canvas.dart';
import 'canvaskit_api.dart';
import 'image_wasm_codecs.dart';
import 'image_web_codecs.dart';
import 'native_memory.dart';
import 'painting.dart';
import 'picture.dart';
import 'picture_recorder.dart';
/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia.
FutureOr<ui.Codec> skiaInstantiateImageCodec(Uint8List list,
[int? targetWidth, int? targetHeight]) {
@@ -214,16 +203,16 @@ Future<Uint8List> fetchImage(String url, WebOnlyImageCodecChunkCallback? chunkCa
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API
Future<Uint8List> readChunked(HttpFetchPayload payload, int contentLength, WebOnlyImageCodecChunkCallback chunkCallback) async {
final Uint8List result = Uint8List(contentLength);
final JSUint8Array1 result = createUint8ArrayFromLength(contentLength);
int position = 0;
int cumulativeBytesLoaded = 0;
await payload.read<Uint8List>((Uint8List chunk) {
cumulativeBytesLoaded += chunk.lengthInBytes;
await payload.read<JSUint8Array1>((JSUint8Array1 chunk) {
cumulativeBytesLoaded += chunk.length.toDart.toInt();
chunkCallback(cumulativeBytesLoaded, contentLength);
result.setAll(position, chunk);
position += chunk.lengthInBytes;
result.set(chunk, position.toJS);
position += chunk.length.toDart.toInt();
});
return result;
return (result as JSUint8Array).toDart;
}
/// A [ui.Image] backed by an `SkImage` from Skia.

View File

@@ -15,15 +15,9 @@ import 'dart:math' as math;
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import '../alarm_clock.dart';
import '../dom.dart';
import '../safe_browser_api.dart';
import '../util.dart';
import 'canvaskit_api.dart';
import 'image.dart';
Duration _kDefaultWebDecoderExpireDuration = const Duration(seconds: 3);
Duration _kWebDecoderExpireDuration = _kDefaultWebDecoderExpireDuration;
@@ -439,24 +433,18 @@ bool _shouldReadPixelsUnmodified(VideoFrame videoFrame, ui.ImageByteFormat forma
return format == ui.ImageByteFormat.rawRgba && isRgbFrame;
}
@JS('Uint8Array')
@staticInterop
class _JSUint8Array {
external factory _JSUint8Array(JSNumber length);
}
Future<ByteBuffer> readVideoFramePixelsUnmodified(VideoFrame videoFrame) async {
final int size = videoFrame.allocationSize().toInt();
// In dart2wasm, Uint8List is not the same as a JS Uint8Array. So we
// explicitly construct the JS object here.
final JSUint8Array destination = _JSUint8Array(size.toJS) as JSUint8Array;
final JSUint8Array1 destination = createUint8ArrayFromLength(size);
final JsPromise copyPromise = videoFrame.copyTo(destination);
await promiseToFuture<void>(copyPromise);
// In dart2wasm, `toDart` incurs a copy here. On JS backends, this is a
// no-op.
return destination.toDart.buffer;
return (destination as JSUint8Array).toDart.buffer;
}
Future<Uint8List> encodeVideoFrameAsPng(VideoFrame videoFrame) async {

View File

@@ -48,6 +48,8 @@ class CanvasKitRenderer implements Renderer {
static CanvasKitRenderer get instance => _instance;
static late CanvasKitRenderer _instance;
Future<void>? _initialized;
@override
String get rendererTag => 'canvaskit';
@@ -66,14 +68,16 @@ class CanvasKitRenderer implements Renderer {
@override
Future<void> initialize() async {
if (windowFlutterCanvasKit != null) {
canvasKit = windowFlutterCanvasKit!;
} else {
canvasKit = await downloadCanvasKit();
windowFlutterCanvasKit = canvasKit;
}
_instance = this;
_initialized ??= () async {
if (windowFlutterCanvasKit != null) {
canvasKit = windowFlutterCanvasKit!;
} else {
canvasKit = await downloadCanvasKit();
windowFlutterCanvasKit = canvasKit;
}
_instance = this;
}();
return _initialized;
}
@override

View File

@@ -1397,7 +1397,7 @@ class DomXMLHttpRequestEventTarget extends DomEventTarget {}
Future<_DomResponse> _rawHttpGet(String url) =>
js_util.promiseToFuture<_DomResponse>(domWindow._fetch1(url.toJS));
typedef MockHttpFetchResponseFactory = Future<MockHttpFetchResponse> Function(
typedef MockHttpFetchResponseFactory = Future<MockHttpFetchResponse?> Function(
String url);
MockHttpFetchResponseFactory? mockHttpFetchResponseFactory;
@@ -1417,7 +1417,10 @@ MockHttpFetchResponseFactory? mockHttpFetchResponseFactory;
/// [httpFetchText] instead.
Future<HttpFetchResponse> httpFetch(String url) async {
if (mockHttpFetchResponseFactory != null) {
return mockHttpFetchResponseFactory!(url);
final MockHttpFetchResponse? response = await mockHttpFetchResponseFactory!(url);
if (response != null) {
return response;
}
}
try {
final _DomResponse domResponse = await _rawHttpGet(url);
@@ -1656,31 +1659,36 @@ typedef MockOnRead = Future<void> Function<T>(HttpFetchReader<T> callback);
class MockHttpFetchPayload implements HttpFetchPayload {
MockHttpFetchPayload({
ByteBuffer? byteBuffer,
Object? json,
String? text,
MockOnRead? onRead,
required ByteBuffer byteBuffer,
int? chunkSize,
}) : _byteBuffer = byteBuffer,
_json = json,
_text = text,
_onRead = onRead;
_chunkSize = chunkSize ?? 64;
final ByteBuffer? _byteBuffer;
final Object? _json;
final String? _text;
final MockOnRead? _onRead;
final ByteBuffer _byteBuffer;
final int _chunkSize;
@override
Future<void> read<T>(HttpFetchReader<T> callback) => _onRead!(callback);
Future<void> read<T>(HttpFetchReader<T> callback) async {
final int totalLength = _byteBuffer.lengthInBytes;
int currentIndex = 0;
while (currentIndex < totalLength) {
final int chunkSize = math.min(_chunkSize, totalLength - currentIndex);
final Uint8List chunk = Uint8List.sublistView(
_byteBuffer.asByteData(), currentIndex, currentIndex + chunkSize
);
callback(chunk.toJS as T);
currentIndex += chunkSize;
}
}
@override
Future<ByteBuffer> asByteBuffer() async => _byteBuffer!;
Future<ByteBuffer> asByteBuffer() async => _byteBuffer;
@override
Future<dynamic> json() async => _json!;
Future<dynamic> json() async => throw AssertionError('json not supported by mock');
@override
Future<String> text() async => _text!;
Future<String> text() async => throw AssertionError('text not supported by mock');
}
/// Indicates a missing HTTP payload when one was expected, such as when
@@ -1794,9 +1802,7 @@ extension _DomStreamReaderExtension on _DomStreamReader {
class _DomStreamChunk {}
extension _DomStreamChunkExtension on _DomStreamChunk {
@JS('value')
external JSAny? get _value;
Object? get value => _value?.toObjectShallow;
external JSAny? get value;
@JS('done')
external JSBoolean get _done;
@@ -1918,6 +1924,10 @@ extension DomFontFaceExtension on DomFontFace {
@JS('weight')
external JSString? get _weight;
String? get weight => _weight?.toDart;
@JS('status')
external JSString? get _status;
String? get status => _status?.toDart;
}
@JS()

View File

@@ -3,32 +3,127 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:js_interop';
import 'dart:typed_data';
import 'assets.dart';
import 'package:ui/src/engine.dart';
class FontAsset {
FontAsset(this.asset, this.descriptors);
final String asset;
final Map<String, String> descriptors;
}
class FontFamily {
FontFamily(this.name, this.fontAssets);
final String name;
final List<FontAsset> fontAssets;
}
class FontManifest {
FontManifest(this.families);
final List<FontFamily> families;
}
Future<FontManifest> fetchFontManifest(AssetManager assetManager) async {
final HttpFetchResponse response = await assetManager.loadAsset('FontManifest.json');
if (!response.hasPayload) {
printWarning('Font manifest does not exist at `${response.url}` - ignoring.');
return FontManifest(<FontFamily>[]);
}
final Converter<List<int>, Object?> decoder = const Utf8Decoder().fuse(const JsonDecoder());
Object? fontManifestJson;
final Sink<List<int>> inputSink = decoder.startChunkedConversion(
ChunkedConversionSink<Object?>.withCallback(
(List<Object?> accumulated) {
if (accumulated.length != 1) {
throw AssertionError('There was a problem trying to load FontManifest.json');
}
fontManifestJson = accumulated.first;
}
));
await response.read((JSUint8Array chunk) => inputSink.add(chunk.toDart));
inputSink.close();
if (fontManifestJson == null) {
throw AssertionError('There was a problem trying to load FontManifest.json');
}
final List<FontFamily> families = (fontManifestJson! as List<dynamic>).map(
(dynamic fontFamilyJson) {
final Map<String, dynamic> fontFamily = fontFamilyJson as Map<String, dynamic>;
final String familyName = fontFamily.readString('family');
final List<dynamic> fontAssets = fontFamily.readList('fonts');
return FontFamily(familyName, fontAssets.map((dynamic fontAssetJson) {
String? asset;
final Map<String, String> descriptors = <String, String>{};
for (final MapEntry<String, dynamic> descriptor in (fontAssetJson as Map<String, dynamic>).entries) {
if (descriptor.key == 'asset') {
asset = descriptor.value as String;
} else {
// Sometimes these descriptors are strings, and sometimes numbers, so we stringify them here.
descriptors[descriptor.key] = '${descriptor.value}';
}
}
if (asset == null) {
throw AssertionError("Invalid Font manifest, missing 'asset' key on font.");
}
return FontAsset(asset, descriptors);
}).toList());
}).toList();
return FontManifest(families);
}
abstract class FontLoadError extends Error {
FontLoadError(this.url);
String url;
String get message;
}
class FontNotFoundError extends FontLoadError {
FontNotFoundError(super.url);
@override
String get message => 'Font asset not found at url $url.';
}
class FontDownloadError extends FontLoadError {
FontDownloadError(super.url, this.error);
dynamic error;
@override
String get message => 'Failed to download font asset at url $url with error: $error.';
}
class FontInvalidDataError extends FontLoadError {
FontInvalidDataError(super.url);
@override
String get message => 'Invalid data for font asset at url $url.';
}
class AssetFontsResult {
AssetFontsResult(this.loadedFonts, this.fontFailures);
/// A list of asset keys for fonts that were successfully loaded.
final List<String> loadedFonts;
/// A map of the asset keys to failures for fonts that failed to load.
final Map<String, FontLoadError> fontFailures;
}
abstract class FlutterFontCollection {
/// Loads a font directly from font data.
Future<bool> loadFontFromList(Uint8List list, {String? fontFamily});
/// Fonts loaded with [loadFontFromList] do not need to be registered
/// with [registerDownloadedFonts]. Fonts are both downloaded and registered
/// with [loadFontFromList] calls.
Future<void> loadFontFromList(Uint8List list, {String? fontFamily});
/// Completes when fonts from FontManifest.json have been loaded.
Future<AssetFontsResult> loadAssetFonts(FontManifest manifest);
/// Completes when fonts from FontManifest.json have been downloaded.
Future<void> downloadAssetFonts(AssetManager assetManager);
/// Registers both downloaded fonts and fallback fonts with the TypefaceFontProvider.
///
/// Downloading of fonts happens separately from registering of fonts so that
/// the download step can happen concurrently with the initalization of the renderer.
///
/// The correct order of calls to register downloaded fonts:
/// 1) [downloadAssetFonts]
/// 2) [registerDownloadedFonts]
///
/// For fallbackFonts, call registerFallbackFont (see font_fallbacks.dart)
/// for each fallback font before calling [registerDownloadedFonts]
void registerDownloadedFonts();
FutureOr<void> debugDownloadTestFonts();
// Unregisters all fonts.
void clear();
}

View File

@@ -8,6 +8,7 @@ import 'dart:js_interop';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import 'package:web_test_fonts/web_test_fonts.dart';
/// The mode the app is running in.
/// Keep these in sync with the same constants on the framework-side under foundation/constants.dart.
@@ -201,7 +202,6 @@ Future<void> initializeEngineServices({
Future<void> initializeRendererCallback () async => renderer.initialize();
await Future.wait<void>(<Future<void>>[initializeRendererCallback(), _downloadAssetFonts()]);
renderer.fontCollection.registerDownloadedFonts();
_initializationState = DebugEngineInitializationState.initializedServices;
}
@@ -248,12 +248,17 @@ void _setAssetManager(AssetManager assetManager) {
Future<void> _downloadAssetFonts() async {
renderer.fontCollection.clear();
if (_assetManager != null) {
await renderer.fontCollection.downloadAssetFonts(_assetManager!);
if (ui.debugEmulateFlutterTesterEnvironment) {
// Load the embedded test font before loading fonts from the assets so that
// the embedded test font is the default (first) font.
await renderer.fontCollection.loadFontFromList(
EmbeddedTestFont.flutterTest.data,
fontFamily: EmbeddedTestFont.flutterTest.fontFamily
);
}
if (ui.debugEmulateFlutterTesterEnvironment) {
await renderer.fontCollection.debugDownloadTestFonts();
if (_assetManager != null) {
await renderer.fontCollection.loadAssetFonts(await fetchFontManifest(assetManager));
}
}

View File

@@ -0,0 +1,30 @@
// 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.
import 'dart:js_interop';
@JS()
@staticInterop
class ArrayBuffer {}
@JS()
@staticInterop
class TypedArray {}
extension TypedArrayExtension on TypedArray {
external void set(JSUint8Array1 source, JSNumber start);
external JSNumber get length;
}
// Due to some differences between wasm and JS backends, we can't use the
// JSUint8Array object provided by the dart sdk. So for now, we can define this
// as an opaque JS object.
@JS('Uint8Array')
@staticInterop
class JSUint8Array1 extends TypedArray {
external factory JSUint8Array1._(JSAny bufferOrLength);
}
JSUint8Array1 createUint8ArrayFromBuffer(ArrayBuffer buffer) => JSUint8Array1._(buffer as JSObject);
JSUint8Array1 createUint8ArrayFromLength(int length) => JSUint8Array1._(length.toJS);

View File

@@ -29,21 +29,22 @@ abstract class Renderer {
factory Renderer._internal() {
if (FlutterConfiguration.flutterWebUseSkwasm) {
return SkwasmRenderer();
}
bool useCanvasKit;
if (FlutterConfiguration.flutterWebAutoDetect) {
if (configuration.requestedRendererType != null) {
useCanvasKit = configuration.requestedRendererType == 'canvaskit';
} else {
// If requestedRendererType is not specified, use CanvasKit for desktop and
// html for mobile.
useCanvasKit = isDesktop;
}
} else {
useCanvasKit = FlutterConfiguration.useSkia;
}
bool useCanvasKit;
if (FlutterConfiguration.flutterWebAutoDetect) {
if (configuration.requestedRendererType != null) {
useCanvasKit = configuration.requestedRendererType == 'canvaskit';
} else {
// If requestedRendererType is not specified, use CanvasKit for desktop and
// html for mobile.
useCanvasKit = isDesktop;
}
} else {
useCanvasKit = FlutterConfiguration.useSkia;
}
return useCanvasKit ? CanvasKitRenderer() : HtmlRenderer();
return useCanvasKit ? CanvasKitRenderer() : HtmlRenderer();
}
}
String get rendererTag;

View File

@@ -18,8 +18,8 @@ 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/js_functions.dart';
export 'skwasm_impl/raw/raw_canvas.dart';
export 'skwasm_impl/raw/raw_fonts.dart';
export 'skwasm_impl/raw/raw_geometry.dart';
export 'skwasm_impl/raw/raw_memory.dart';
export 'skwasm_impl/raw/raw_paint.dart';
@@ -27,7 +27,10 @@ export 'skwasm_impl/raw/raw_path.dart';
export 'skwasm_impl/raw/raw_path_metrics.dart';
export 'skwasm_impl/raw/raw_picture.dart';
export 'skwasm_impl/raw/raw_shaders.dart';
export 'skwasm_impl/raw/raw_skdata.dart';
export 'skwasm_impl/raw/raw_skstring.dart';
export 'skwasm_impl/raw/raw_surface.dart';
export 'skwasm_impl/raw/skwasm_module.dart';
export 'skwasm_impl/renderer.dart';
export 'skwasm_impl/scene_builder.dart';
export 'skwasm_impl/shaders.dart';

View File

@@ -3,34 +3,116 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:js_interop';
import 'dart:typed_data';
import 'package:ui/src/engine.dart';
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
class SkwasmFontCollection implements FlutterFontCollection {
SkwasmFontCollection() : _handle = fontCollectionCreate();
FontCollectionHandle _handle;
@override
void clear() {
// TODO(jacksongardner): implement clear
fontCollectionDispose(_handle);
_handle = fontCollectionCreate();
}
@override
FutureOr<void> debugDownloadTestFonts() {
// TODO(jacksongardner): implement debugDownloadTestFonts
Future<AssetFontsResult> loadAssetFonts(FontManifest manifest) async {
final List<Future<void>> fontFutures = <Future<void>>[];
final List<String> loadedFonts = <String>[];
final Map<String, FontLoadError> fontFailures = <String, FontLoadError>{};
// We can't restore the pointers directly due to a bug in dart2wasm
// https://github.com/dart-lang/sdk/issues/52142
final List<int> familyHandles = <int>[];
for (final FontFamily family in manifest.families) {
final List<int> rawUtf8Bytes = utf8.encode(family.name);
final SkStringHandle stringHandle = skStringAllocate(rawUtf8Bytes.length);
final Pointer<Int8> stringDataPointer = skStringGetData(stringHandle);
for (int i = 0; i < rawUtf8Bytes.length; i++) {
stringDataPointer[i] = rawUtf8Bytes[i];
}
familyHandles.add(stringHandle.address);
for (final FontAsset fontAsset in family.fontAssets) {
fontFutures.add(() async {
final FontLoadError? error = await _downloadFontAsset(fontAsset, stringHandle);
if (error == null) {
loadedFonts.add(fontAsset.asset);
} else {
fontFailures[fontAsset.asset] = error;
}
}());
}
}
await Future.wait(fontFutures);
// Wait until all the downloading and registering is complete before
// freeing the handles to the family name strings.
familyHandles
.map((int address) => SkStringHandle.fromAddress(address))
.forEach(skStringFree);
return AssetFontsResult(loadedFonts, fontFailures);
}
Future<FontLoadError?> _downloadFontAsset(FontAsset asset, SkStringHandle familyNameHandle) async {
final HttpFetchResponse response;
try {
response = await assetManager.loadAsset(asset.asset);
} catch (error) {
return FontDownloadError(assetManager.getAssetUrl(asset.asset), error);
}
if (!response.hasPayload) {
return FontNotFoundError(assetManager.getAssetUrl(asset.asset));
}
int length = 0;
final List<JSUint8Array1> chunks = <JSUint8Array1>[];
await response.read((JSUint8Array1 chunk) {
length += chunk.length.toDart.toInt();
chunks.add(chunk);
});
final SkDataHandle fontData = skDataCreate(length);
int dataAddress = skDataGetPointer(fontData).cast<Int8>().address;
final JSUint8Array1 wasmMemory = createUint8ArrayFromBuffer(skwasmInstance.wasmMemory.buffer);
for (final JSUint8Array1 chunk in chunks) {
wasmMemory.set(chunk, dataAddress.toJS);
dataAddress += chunk.length.toDart.toInt();
}
final bool result = fontCollectionRegisterFont(_handle, fontData, familyNameHandle);
skDataDispose(fontData);
if (!result) {
return FontInvalidDataError(assetManager.getAssetUrl(asset.asset));
}
return null;
}
@override
Future<void> downloadAssetFonts(AssetManager assetManager) async {
// TODO(jacksongardner): implement downloadAssetFonts
}
@override
Future<void> loadFontFromList(Uint8List list, {String? fontFamily}) async {
// TODO(jacksongardner): implement loadFontFromList
}
@override
void registerDownloadedFonts() {
// TODO(jacksongardner): implement registerDownloadedFonts
Future<bool> loadFontFromList(Uint8List list, {String? fontFamily}) async {
final SkDataHandle dataHandle = skDataCreate(list.length);
final Pointer<Int8> dataPointer = skDataGetPointer(dataHandle).cast<Int8>();
for (int i = 0; i < list.length; i++) {
dataPointer[i] = list[i];
}
bool success;
if (fontFamily != null) {
final List<int> rawUtf8Bytes = utf8.encode(fontFamily);
final SkStringHandle stringHandle = skStringAllocate(rawUtf8Bytes.length);
final Pointer<Int8> stringDataPointer = skStringGetData(stringHandle);
for (int i = 0; i < rawUtf8Bytes.length; i++) {
stringDataPointer[i] = rawUtf8Bytes[i];
}
success = fontCollectionRegisterFont(_handle, dataHandle, stringHandle);
skStringFree(stringHandle);
} else {
success = fontCollectionRegisterFont(_handle, dataHandle, nullptr);
}
skDataDispose(dataHandle);
return success;
}
}

View File

@@ -0,0 +1,30 @@
// 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 RawFontCollection extends Opaque {}
typedef FontCollectionHandle = Pointer<RawFontCollection>;
@Native<FontCollectionHandle Function()>(symbol: 'fontCollection_create', isLeaf: true)
external FontCollectionHandle fontCollectionCreate();
@Native<Void Function(FontCollectionHandle)>(symbol: 'fontCollection_dispose', isLeaf: true)
external void fontCollectionDispose(FontCollectionHandle handle);
@Native<Bool Function(
FontCollectionHandle,
SkDataHandle,
SkStringHandle,
)>(symbol: 'fontCollection_registerFont', isLeaf: true)
external bool fontCollectionRegisterFont(
FontCollectionHandle handle,
SkDataHandle fontData,
SkStringHandle fontName,
);

View File

@@ -12,12 +12,6 @@ import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
final class RawShader extends Opaque {}
typedef ShaderHandle = Pointer<RawShader>;
final class RawSkString extends Opaque {}
typedef SkStringHandle = Pointer<RawSkString>;
final class RawSkData extends Opaque {}
typedef SkDataHandle = Pointer<RawSkData>;
final class RawRuntimeEffect extends Opaque {}
typedef RuntimeEffectHandle = Pointer<RawRuntimeEffect>;
@@ -106,15 +100,6 @@ external ShaderHandle shaderCreateSweepGradient(
@Native<Void Function(ShaderHandle)>(symbol: 'shader_dispose', isLeaf: true)
external void shaderDispose(ShaderHandle handle);
@Native<SkStringHandle Function(Size)>(symbol: 'shaderSource_allocate', isLeaf: true)
external SkStringHandle shaderSourceAllocate(int size);
@Native<Pointer<Int8> Function(SkStringHandle)>(symbol: 'shaderSource_getData', isLeaf: true)
external Pointer<Int8> shaderSourceGetData(SkStringHandle handle);
@Native<Void Function(SkStringHandle)>(symbol: 'shaderSource_free', isLeaf: true)
external void shaderSourceFree(SkStringHandle handle);
@Native<RuntimeEffectHandle Function(SkStringHandle)>(symbol: 'runtimeEffect_create', isLeaf: true)
external RuntimeEffectHandle runtimeEffectCreate(SkStringHandle source);
@@ -124,15 +109,6 @@ external void runtimeEffectDispose(RuntimeEffectHandle handle);
@Native<Size Function(RuntimeEffectHandle)>(symbol: 'runtimeEffect_getUniformSize', isLeaf: true)
external int runtimeEffectGetUniformSize(RuntimeEffectHandle handle);
@Native<SkDataHandle Function(Size)>(symbol: 'data_create', isLeaf: true)
external SkDataHandle dataCreate(int size);
@Native<Pointer<Void> Function(SkDataHandle)>(symbol: 'data_getPointer', isLeaf: true)
external Pointer<Void> dataGetPointer(SkDataHandle handle);
@Native<Void Function(SkDataHandle)>(symbol: 'data_dispose', isLeaf: true)
external void dataDispose(SkDataHandle handle);
@Native<ShaderHandle Function(
RuntimeEffectHandle,
SkDataHandle,

View File

@@ -0,0 +1,20 @@
// 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';
final class RawSkData extends Opaque {}
typedef SkDataHandle = Pointer<RawSkData>;
@Native<SkDataHandle Function(Size)>(symbol: 'skData_create', isLeaf: true)
external SkDataHandle skDataCreate(int size);
@Native<Pointer<Void> Function(SkDataHandle)>(symbol: 'skData_getPointer', isLeaf: true)
external Pointer<Void> skDataGetPointer(SkDataHandle handle);
@Native<Void Function(SkDataHandle)>(symbol: 'skData_dispose', isLeaf: true)
external void skDataDispose(SkDataHandle handle);

View File

@@ -0,0 +1,20 @@
// 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';
final class RawSkString extends Opaque {}
typedef SkStringHandle = Pointer<RawSkString>;
@Native<SkStringHandle Function(Size)>(symbol: 'skString_allocate', isLeaf: true)
external SkStringHandle skStringAllocate(int size);
@Native<Pointer<Int8> Function(SkStringHandle)>(symbol: 'skString_getData', isLeaf: true)
external Pointer<Int8> skStringGetData(SkStringHandle handle);
@Native<Void Function(SkStringHandle)>(symbol: 'skString_free', isLeaf: true)
external void skStringFree(SkStringHandle handle);

View File

@@ -4,6 +4,16 @@
import 'dart:js_interop';
import 'package:ui/src/engine.dart';
@JS()
@staticInterop
class WebAssemblyMemory {}
extension WebAssemblyMemoryExtension on WebAssemblyMemory {
external ArrayBuffer get buffer;
}
@JS()
@staticInterop
class SkwasmInstance {}
@@ -11,6 +21,7 @@ class SkwasmInstance {}
extension SkwasmInstanceExtension on SkwasmInstance {
external JSNumber addFunction(JSFunction function, JSString signature);
external void removeFunction(JSNumber functionPointer);
external WebAssemblyMemory get wasmMemory;
}
@JS('window._flutter_skwasmInstance')

View File

@@ -283,9 +283,6 @@ class SkwasmRenderer implements Renderer {
}
final SkwasmPicture picture = (scene as SkwasmScene).picture as SkwasmPicture;
await surface.renderPicture(picture);
// TODO(jacksongardner): Remove this hack. See https://github.com/flutter/flutter/issues/124616
await Future<void>.delayed(const Duration(milliseconds: 100));
}
@override

View File

@@ -162,15 +162,15 @@ class SkwasmFragmentProgram implements ui.FragmentProgram {
// TODO(jacksongardner): Can we avoid this copy?
final List<int> sourceData = utf8.encode(shaderData.source);
final SkStringHandle sourceString = shaderSourceAllocate(sourceData.length);
final Pointer<Int8> sourceBuffer = shaderSourceGetData(sourceString);
final SkStringHandle sourceString = skStringAllocate(sourceData.length);
final Pointer<Int8> sourceBuffer = skStringGetData(sourceString);
int i = 0;
for (final int byte in sourceData) {
sourceBuffer[i] = byte;
i++;
}
final RuntimeEffectHandle handle = runtimeEffectCreate(sourceString);
shaderSourceFree(sourceString);
skStringFree(sourceString);
return SkwasmFragmentProgram._(name, handle);
}
@@ -193,7 +193,7 @@ class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader {
SkwasmFragmentProgram program, {
List<SkwasmShader>? childShaders,
}) : _program = program,
_uniformData = dataCreate(program.uniformSize),
_uniformData = skDataCreate(program.uniformSize),
_childShaders = childShaders;
@override
@@ -234,7 +234,7 @@ class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader {
shaderDispose(_handle);
_handle = nullptr;
}
final Pointer<Float> dataPointer = dataGetPointer(_uniformData).cast<Float>();
final Pointer<Float> dataPointer = skDataGetPointer(_uniformData).cast<Float>();
dataPointer[index] = value;
}
@@ -247,7 +247,7 @@ class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader {
void dispose() {
super.dispose();
if (_uniformData != nullptr) {
dataDispose(_uniformData);
skDataDispose(_uniformData);
_uniformData = nullptr;
}
}

View File

@@ -10,31 +10,6 @@ import 'dart:async';
import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
import '../engine.dart';
Future<void>? _platformInitializedFuture;
Future<void> initializeTestFlutterViewEmbedder({double devicePixelRatio = 3.0}) {
// Force-initialize FlutterViewEmbedder so it doesn't overwrite test pixel ratio.
ensureFlutterViewEmbedderInitialized();
// The following parameters are hard-coded in Flutter's test embedder. Since
// we don't have an embedder yet this is the lowest-most layer we can put
// this stuff in.
window.debugOverrideDevicePixelRatio(devicePixelRatio);
window.webOnlyDebugPhysicalSizeOverride =
ui.Size(800 * devicePixelRatio, 600 * devicePixelRatio);
scheduleFrameCallback = () {};
ui.debugEmulateFlutterTesterEnvironment = true;
// Initialize platform once and reuse across all tests.
if (_platformInitializedFuture != null) {
return _platformInitializedFuture!;
}
return _platformInitializedFuture =
initializeEngine(assetManager: WebOnlyMockAssetManager());
}
const bool _debugLogHistoryActions = false;
class TestHistoryEntry {

View File

@@ -3,16 +3,9 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:ui/src/engine/fonts.dart';
import 'package:web_test_fonts/web_test_fonts.dart';
import '../assets.dart';
import '../dom.dart';
import '../util.dart';
import 'layout_service.dart';
import 'package:ui/src/engine.dart';
/// This class is responsible for registering and loading fonts.
///
@@ -21,95 +14,48 @@ import 'layout_service.dart';
/// font manifest. If test fonts are enabled, then call
/// [debugDownloadTestFonts] as well.
class HtmlFontCollection implements FlutterFontCollection {
FontManager? _assetFontManager;
FontManager? _testFontManager;
/// Reads the font manifest using the [assetManager] and downloads all of the
/// fonts declared within.
@override
Future<void> downloadAssetFonts(AssetManager assetManager) async {
final HttpFetchResponse response = await assetManager.loadAsset('FontManifest.json');
if (!response.hasPayload) {
printWarning('Font manifest does not exist at `${response.url}` - ignoring.');
return;
}
final Uint8List data = await response.asUint8List();
final List<dynamic>? fontManifest = json.decode(utf8.decode(data)) as List<dynamic>?;
if (fontManifest == null) {
throw AssertionError(
'There was a problem trying to load FontManifest.json');
}
_assetFontManager = FontManager();
for (final Map<String, dynamic> fontFamily
in fontManifest.cast<Map<String, dynamic>>()) {
final String? family = fontFamily.tryString('family');
final List<Map<String, dynamic>> fontAssets = fontFamily.castList<Map<String, dynamic>>('fonts');
for (final Map<String, dynamic> fontAsset in fontAssets) {
final String asset = fontAsset.readString('asset');
final Map<String, String> descriptors = <String, String>{};
for (final String descriptor in fontAsset.keys) {
if (descriptor != 'asset') {
descriptors[descriptor] = '${fontAsset[descriptor]}';
}
}
_assetFontManager!.downloadAsset(
family!, 'url(${assetManager.getAssetUrl(asset)})', descriptors);
Future<AssetFontsResult> loadAssetFonts(FontManifest manifest) async {
final List<Future<(String, FontLoadError?)>> pendingFonts = <Future<(String, FontLoadError?)>>[];
for (final FontFamily family in manifest.families) {
for (final FontAsset fontAsset in family.fontAssets) {
pendingFonts.add(() async {
return (
fontAsset.asset,
await _loadFontAsset(family.name, fontAsset.asset, fontAsset.descriptors)
);
}());
}
}
await _assetFontManager!.downloadAllFonts();
final List<String> loadedFonts = <String>[];
final Map<String, FontLoadError> fontFailures = <String, FontLoadError>{};
for (final (String asset, FontLoadError? error) in await Future.wait(pendingFonts)) {
if (error == null) {
loadedFonts.add(asset);
} else {
fontFailures[asset] = error;
}
}
return AssetFontsResult(loadedFonts, fontFailures);
}
@override
Future<void> loadFontFromList(Uint8List list, {String? fontFamily}) {
Future<bool> loadFontFromList(Uint8List list, {String? fontFamily}) async {
if (fontFamily == null) {
throw AssertionError('Font family must be provided to HtmlFontCollection.');
printWarning('Font family must be provided to HtmlFontCollection.');
return false;
}
return _assetFontManager!._loadFontFaceBytes(fontFamily, list);
}
/// Downloads fonts that are used by tests.
@override
Future<void> debugDownloadTestFonts() async {
final FontManager fontManager = _testFontManager = FontManager();
fontManager._downloadedFonts.add(createDomFontFace(
EmbeddedTestFont.flutterTest.fontFamily,
EmbeddedTestFont.flutterTest.data,
));
for (final MapEntry<String, String> fontEntry in testFontUrls.entries) {
fontManager.downloadAsset(fontEntry.key, 'url(${fontEntry.value})', const <String, String>{});
}
await fontManager.downloadAllFonts();
}
@override
void registerDownloadedFonts() {
_assetFontManager?.registerDownloadedFonts();
_testFontManager?.registerDownloadedFonts();
return _loadFontFaceBytes(fontFamily, list);
}
/// Unregister all fonts that have been registered.
@override
void clear() {
_assetFontManager = null;
_testFontManager = null;
domDocument.fonts!.clear();
}
}
/// Manages a collection of fonts and ensures they are loaded.
class FontManager {
/// Fonts that started the downloading process. Once the fonts have downloaded
/// without error, they are moved to [_downloadedFonts]. Those fonts
/// are subsequently registered by [registerDownloadedFonts].
final List<Future<DomFontFace?>> _fontLoadingFutures = <Future<DomFontFace?>>[];
final List<DomFontFace> _downloadedFonts = <DomFontFace>[];
// Regular expression to detect a string with no punctuations.
// For example font family 'Ahem!' does not fall into this category
@@ -152,79 +98,77 @@ class FontManager {
///
/// * https://developer.mozilla.org/en-US/docs/Web/CSS/font-family#Valid_family_names
/// * https://drafts.csswg.org/css-fonts-3/#font-family-prop
void downloadAsset(
Future<FontLoadError?> _loadFontAsset(
String family,
String asset,
Map<String, String> descriptors,
) {
if (startWithDigit.hasMatch(family) ||
notPunctuation.stringMatch(family) != family) {
// Load a font family name with special characters once here wrapped in
// quotes.
_loadFontFace("'$family'", asset, descriptors);
) async {
final List<DomFontFace> fontFaces = <DomFontFace>[];
final List<FontLoadError> errors = <FontLoadError>[];
try {
if (startWithDigit.hasMatch(family) ||
notPunctuation.stringMatch(family) != family) {
// Load a font family name with special characters once here wrapped in
// quotes.
fontFaces.add(await _loadFontFace("'$family'", asset, descriptors));
}
} on FontLoadError catch (error) {
errors.add(error);
}
// Load all fonts, without quoted family names.
_loadFontFace(family, asset, descriptors);
try {
// Load all fonts, without quoted family names.
fontFaces.add(await _loadFontFace(family, asset, descriptors));
} on FontLoadError catch (error) {
errors.add(error);
}
if (fontFaces.isEmpty) {
// We failed to load either font face. Return the first error.
return errors.first;
}
try {
fontFaces.forEach(domDocument.fonts!.add);
} catch (e) {
return FontInvalidDataError(asset);
}
return null;
}
void _loadFontFace(
Future<DomFontFace> _loadFontFace(
String family,
String asset,
Map<String, String> descriptors,
) {
Future<DomFontFace?> fontFaceLoad(DomFontFace fontFace) async {
try {
final DomFontFace loadedFontFace = await fontFace.load();
return loadedFontFace;
} catch (e) {
printWarning('Error while trying to load font family "$family":\n$e');
return null;
}
}
) async {
// try/catch because `new FontFace` can crash with an improper font family.
try {
final DomFontFace fontFace = createDomFontFace(family, asset, descriptors);
_fontLoadingFutures.add(fontFaceLoad(fontFace));
final DomFontFace fontFace = createDomFontFace(family, 'url(${assetManager.getAssetUrl(asset)})', descriptors);
return await fontFace.load();
} catch (e) {
printWarning('Error while loading font family "$family":\n$e');
throw FontDownloadError(asset, e);
}
}
void registerDownloadedFonts() {
if (_downloadedFonts.isEmpty) {
return;
}
// Since we can't use tear-offs for interop members, this code is faster and
// easier to read with a for loop instead of forEach.
// ignore: prefer_foreach
for (final DomFontFace font in _downloadedFonts) {
domDocument.fonts!.add(font);
}
}
Future<void> downloadAllFonts() async {
final List<DomFontFace?> loadedFonts = await Future.wait(_fontLoadingFutures);
_downloadedFonts.addAll(loadedFonts.whereType<DomFontFace>());
}
// Loads a font from bytes, surfacing errors through the future.
Future<void> _loadFontFaceBytes(String family, Uint8List list) {
Future<bool> _loadFontFaceBytes(String family, Uint8List list) async {
// Since these fonts are loaded by user code, surface the error
// through the returned future.
final DomFontFace fontFace = createDomFontFace(family, list);
return fontFace.load().then((_) {
try {
final DomFontFace fontFace = createDomFontFace(family, list);
if (fontFace.status == 'error') {
// Font failed to load.
return false;
}
domDocument.fonts!.add(fontFace);
// There might be paragraph measurements for this new font before it is
// loaded. They were measured using fallback font, so we should clear the
// cache.
Spanometer.clearRulersCache();
}, onError: (dynamic exception) {
// Failures here will throw an DomException which confusingly
// does not implement Exception or Error. Rethrow an Exception so it can
// be caught in user code without depending on dart:html or requiring a
// catch block without "on".
throw Exception(exception.toString());
});
} catch (exception) {
// Failures here will throw an DomException. Return false.
return false;
}
return true;
}
}

View File

@@ -8,12 +8,15 @@ wasm_lib("skwasm") {
sources = [
"canvas.cpp",
"contour_measure.cpp",
"data.cpp",
"export.h",
"fonts.cpp",
"helpers.h",
"paint.cpp",
"path.cpp",
"picture.cpp",
"shaders.cpp",
"string.cpp",
"surface.cpp",
"wrappers.h",
]
@@ -46,5 +49,8 @@ wasm_lib("skwasm") {
]
}
deps = [ "//third_party/skia" ]
deps = [
"//third_party/skia",
"//third_party/skia/modules/skparagraph",
]
}

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <emscripten.h>
#include "export.h"
#include "helpers.h"
#include "wrappers.h"

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <emscripten.h>
#include "export.h"
#include "helpers.h"

View File

@@ -0,0 +1,19 @@
// 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 "third_party/skia/include/core/SkData.h"
SKWASM_EXPORT SkData* skData_create(size_t size) {
return SkData::MakeUninitialized(size).release();
}
SKWASM_EXPORT void* skData_getPointer(SkData* data) {
return data->writable_data();
}
SKWASM_EXPORT void skData_dispose(SkData* data) {
return data->unref();
}

View File

@@ -4,4 +4,6 @@
#pragma once
#include <emscripten.h>
#define SKWASM_EXPORT extern "C" EMSCRIPTEN_KEEPALIVE

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 "third_party/skia/include/core/SkFontMgr.h"
#include "third_party/skia/modules/skparagraph/include/FontCollection.h"
#include "third_party/skia/modules/skparagraph/include/TypefaceFontProvider.h"
using namespace skia::textlayout;
struct FlutterFontCollection {
sk_sp<FontCollection> collection;
sk_sp<TypefaceFontProvider> provider;
};
SKWASM_EXPORT FlutterFontCollection* fontCollection_create() {
auto collection = sk_make_sp<FontCollection>();
auto provider = sk_make_sp<TypefaceFontProvider>();
collection->enableFontFallback();
collection->setDefaultFontManager(provider);
return new FlutterFontCollection{
std::move(collection),
std::move(provider),
};
}
SKWASM_EXPORT void fontCollection_dispose(FlutterFontCollection* collection) {
delete collection;
}
SKWASM_EXPORT bool fontCollection_registerFont(
FlutterFontCollection* collection,
SkData* fontData,
SkString* fontName) {
fontData->ref();
auto typeFace =
SkFontMgr::RefDefault()->makeFromData(sk_sp<SkData>(fontData));
if (!typeFace) {
return false;
}
if (fontName) {
SkString alias = *fontName;
collection->provider->registerTypeface(std::move(typeFace), alias);
} else {
collection->provider->registerTypeface(std::move(typeFace));
}
return true;
}

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <emscripten.h>
#include "export.h"
#include "helpers.h"
#include "third_party/skia/include/core/SkPaint.h"

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <emscripten.h>
#include "export.h"
#include "helpers.h"
#include "third_party/skia/include/core/SkPath.h"

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <emscripten.h>
#include "export.h"
#include "helpers.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <emscripten.h>
#include "export.h"
#include "helpers.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
@@ -102,18 +101,6 @@ SKWASM_EXPORT void shader_dispose(SkShader* shader) {
shader->unref();
}
SKWASM_EXPORT SkString* shaderSource_allocate(size_t length) {
return new SkString(length);
}
SKWASM_EXPORT char* shaderSource_getData(SkString* string) {
return string->data();
}
SKWASM_EXPORT void shaderSource_free(SkString* string) {
return delete string;
}
SKWASM_EXPORT SkRuntimeEffect* runtimeEffect_create(SkString* source) {
auto result = SkRuntimeEffect::MakeForShader(*source);
if (result.effect == nullptr) {
@@ -133,18 +120,6 @@ SKWASM_EXPORT size_t runtimeEffect_getUniformSize(SkRuntimeEffect* effect) {
return effect->uniformSize();
}
SKWASM_EXPORT SkData* data_create(size_t size) {
return SkData::MakeUninitialized(size).release();
}
SKWASM_EXPORT void* data_getPointer(SkData* data) {
return data->writable_data();
}
SKWASM_EXPORT void data_dispose(SkData* data) {
return data->unref();
}
SKWASM_EXPORT SkShader* shader_createRuntimeEffectShader(
SkRuntimeEffect* runtimeEffect,
SkData* uniforms,

View File

@@ -0,0 +1,19 @@
// 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 "third_party/skia/include/core/SkString.h"
SKWASM_EXPORT SkString* skString_allocate(size_t length) {
return new SkString(length);
}
SKWASM_EXPORT char* skString_getData(SkString* string) {
return string->data();
}
SKWASM_EXPORT void skString_free(SkString* string) {
return delete string;
}

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:js_util' as js_util;
import 'dart:math' as math;
import 'dart:typed_data';
@@ -29,15 +28,6 @@ void testMain() {
expect(notoDownloadQueue.downloader.debugActiveDownloadCount, 0);
expect(notoDownloadQueue.isPending, isFalse);
// We render some color emojis in this test.
final FlutterConfiguration config = FlutterConfiguration()
..setUserConfiguration(
js_util.jsify(<String, Object?>{
'useColorEmoji': true,
}) as JsFlutterConfiguration);
debugSetConfiguration(config);
FontFallbackData.debugReset();
notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/';
});

View File

@@ -1527,11 +1527,6 @@ void _textStyleTests() {
}
void _paragraphTests() {
setUpAll(() async {
await CanvasKitRenderer.instance.fontCollection.debugDownloadTestFonts();
CanvasKitRenderer.instance.fontCollection.registerDownloadedFonts();
});
// This test is just a kitchen sink that blasts CanvasKit with all paragraph
// properties all at once, making sure CanvasKit doesn't choke on anything.
// In particular, this tests that our JS bindings are correct, such as that

View File

@@ -11,14 +11,21 @@ import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import 'package:web_engine_tester/golden_tester.dart';
import '../common/test_initialization.dart';
const MethodCodec codec = StandardMethodCodec();
/// Common test setup for all CanvasKit unit-tests.
void setUpCanvasKitTest() {
setUpAll(() async {
expect(renderer, isA<CanvasKitRenderer>(), reason: 'This test must run in CanvasKit mode.');
debugDisableFontFallbacks = false;
await initializeEngine(assetManager: WebOnlyMockAssetManager());
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
setUpAll(() {
// Ahem must be added to font fallbacks list regardless of where it was
// downloaded from.
FontFallbackData.instance.globalFontFallbacks.add('Ahem');
});
tearDown(() {

View File

@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:js_util' as js_util;
import 'dart:math' as math;
import 'dart:typed_data';
@@ -25,18 +24,14 @@ void testMain() {
group('Font fallbacks', () {
setUpCanvasKitTest();
setUpAll(() {
debugDisableFontFallbacks = false;
});
/// Used to save and restore [ui.window.onPlatformMessage] after each test.
ui.PlatformMessageCallback? savedCallback;
setUp(() {
// We render some color emojis in this test.
final FlutterConfiguration config = FlutterConfiguration()
..setUserConfiguration(
js_util.jsify(<String, Object?>{
'useColorEmoji': true,
}) as JsFlutterConfiguration);
debugSetConfiguration(config);
FontFallbackData.debugReset();
notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/';
savedCallback = ui.window.onPlatformMessage;

View File

@@ -9,18 +9,22 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import '../common/fake_asset_manager.dart';
import '../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
group('$SkiaFontCollection', () {
setUpUnitTests();
final List<String> warnings = <String>[];
late void Function(String) oldPrintWarning;
late FakeAssetScope testAssetScope;
setUpAll(() async {
ensureFlutterViewEmbedderInitialized();
await renderer.initialize();
oldPrintWarning = printWarning;
printWarning = (String warning) {
warnings.add(warning);
@@ -32,20 +36,19 @@ void testMain() {
});
setUp(() {
testAssetScope = fakeAssetManager.pushAssetScope();
mockHttpFetchResponseFactory = null;
warnings.clear();
});
tearDown(() {
fakeAssetManager.popAssetScope(testAssetScope);
mockHttpFetchResponseFactory = null;
});
test('logs no warnings with the default mock asset manager', () async {
final SkiaFontCollection fontCollection = SkiaFontCollection();
final WebOnlyMockAssetManager mockAssetManager =
WebOnlyMockAssetManager();
await fontCollection.downloadAssetFonts(mockAssetManager);
fontCollection.registerDownloadedFonts();
await fontCollection.loadAssetFonts(await fetchFontManifest(fakeAssetManager));
expect(warnings, isEmpty);
});
@@ -61,9 +64,7 @@ void testMain() {
);
};
final SkiaFontCollection fontCollection = SkiaFontCollection();
final WebOnlyMockAssetManager mockAssetManager =
WebOnlyMockAssetManager();
mockAssetManager.defaultFontManifest = '''
testAssetScope.setAsset('FontManifest.json', stringAsUtf8Data('''
[
{
"family":"Roboto",
@@ -74,10 +75,9 @@ void testMain() {
"fonts":[{"asset":"packages/bogus/BrokenFont.ttf"}]
}
]
''';
'''));
// It should complete without error, but emit a warning about BrokenFont.
await fontCollection.downloadAssetFonts(mockAssetManager);
fontCollection.registerDownloadedFonts();
await fontCollection.loadAssetFonts(await fetchFontManifest(fakeAssetManager));
expect(
warnings,
containsAllInOrder(
@@ -91,9 +91,7 @@ void testMain() {
test('logs an HTTP warning if one of the registered fonts is missing (404 file not found)', () async {
final SkiaFontCollection fontCollection = SkiaFontCollection();
final WebOnlyMockAssetManager mockAssetManager =
WebOnlyMockAssetManager();
mockAssetManager.defaultFontManifest = '''
testAssetScope.setAsset('FontManifest.json', stringAsUtf8Data('''
[
{
"family":"Roboto",
@@ -104,38 +102,32 @@ void testMain() {
"fonts":[{"asset":"packages/bogus/ThisFontDoesNotExist.ttf"}]
}
]
''';
'''));
// It should complete without error, but emit a warning about ThisFontDoesNotExist.
await fontCollection.downloadAssetFonts(mockAssetManager);
fontCollection.registerDownloadedFonts();
await fontCollection.loadAssetFonts(await fetchFontManifest(fakeAssetManager));
expect(
warnings,
containsAllInOrder(<String>[
'Failed to load font ThisFontDoesNotExist at packages/bogus/ThisFontDoesNotExist.ttf',
'Flutter Web engine failed to fetch "packages/bogus/ThisFontDoesNotExist.ttf". HTTP request succeeded, but the server responded with HTTP status 404.',
'Font family ThisFontDoesNotExist not found (404) at packages/bogus/ThisFontDoesNotExist.ttf'
]),
);
});
test('prioritizes Ahem loaded via FontManifest.json', () async {
final SkiaFontCollection fontCollection = SkiaFontCollection();
final WebOnlyMockAssetManager mockAssetManager =
WebOnlyMockAssetManager();
mockAssetManager.defaultFontManifest = '''
testAssetScope.setAsset('FontManifest.json', stringAsUtf8Data('''
[
{
"family":"Ahem",
"fonts":[{"asset":"/assets/fonts/Roboto-Regular.ttf"}]
}
]
'''.trim();
'''.trim()));
final ByteBuffer robotoData = await httpFetchByteBuffer('/assets/fonts/Roboto-Regular.ttf');
await fontCollection.downloadAssetFonts(mockAssetManager);
await fontCollection.debugDownloadTestFonts();
fontCollection.registerDownloadedFonts();
await fontCollection.loadAssetFonts(await fetchFontManifest(fakeAssetManager));
expect(warnings, isEmpty);
// Use `singleWhere` to make sure only one version of 'Ahem' is loaded.
@@ -148,18 +140,10 @@ void testMain() {
});
test('falls back to default Ahem URL', () async {
final SkiaFontCollection fontCollection = SkiaFontCollection();
final WebOnlyMockAssetManager mockAssetManager =
WebOnlyMockAssetManager();
mockAssetManager.defaultFontManifest = '[]';
final SkiaFontCollection fontCollection = renderer.fontCollection as SkiaFontCollection;
final ByteBuffer ahemData = await httpFetchByteBuffer('/assets/fonts/ahem.ttf');
await fontCollection.downloadAssetFonts(mockAssetManager);
await fontCollection.debugDownloadTestFonts();
fontCollection.registerDownloadedFonts();
expect(warnings, isEmpty);
// Use `singleWhere` to make sure only one version of 'Ahem' is loaded.
final RegisteredFont ahem = fontCollection.debugRegisteredFonts!
.singleWhere((RegisteredFont font) => font.family == 'Ahem');
@@ -169,24 +153,9 @@ void testMain() {
expect(ahem.bytes.length, ahemData.lengthInBytes);
});
test('download fonts separately from registering', () async {
final SkiaFontCollection fontCollection = SkiaFontCollection();
await fontCollection.debugDownloadTestFonts();
/// Fonts should have been downloaded, but not yet registered
expect(fontCollection.debugRegisteredFonts, isEmpty);
fontCollection.registerDownloadedFonts();
/// Fonts should now be registered and _registeredFonts should be filled
expect(fontCollection.debugRegisteredFonts, isNotEmpty);
expect(warnings, isEmpty);
});
test('FlutterTest is the default test font', () async {
final SkiaFontCollection fontCollection = SkiaFontCollection();
final SkiaFontCollection fontCollection = renderer.fontCollection as SkiaFontCollection;
await fontCollection.debugDownloadTestFonts();
fontCollection.registerDownloadedFonts();
expect(fontCollection.debugRegisteredFonts, isNotEmpty);
expect(fontCollection.debugRegisteredFonts!.first.family, 'FlutterTest');
});

View File

@@ -0,0 +1,138 @@
// 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.
import 'dart:convert';
import 'dart:typed_data';
import 'package:ui/src/engine.dart';
class FakeAssetManager implements AssetManager {
FakeAssetManager();
@override
String get assetsDir => 'assets';
@override
String getAssetUrl(String asset) => asset;
@override
Future<ByteData> load(String assetKey) async {
final ByteData? data = _assetMap[assetKey];
if (data == null) {
throw HttpFetchNoPayloadError(assetKey, status: 404);
}
return data;
}
@override
Future<HttpFetchResponse> loadAsset(String asset) async {
final ByteData? assetData = await _currentScope?.getAssetData(asset);
if (assetData != null) {
return MockHttpFetchResponse(
url: asset,
status: 200,
payload: MockHttpFetchPayload(
byteBuffer: assetData.buffer,
),
);
} else {
return MockHttpFetchResponse(
url: asset,
status: 404,
);
}
}
FakeAssetScope pushAssetScope() {
final FakeAssetScope scope = FakeAssetScope._(_currentScope);
_currentScope = scope;
return scope;
}
void popAssetScope(FakeAssetScope scope) {
assert(_currentScope == scope);
_currentScope = scope._parent;
}
void setAsset(String assetKey, ByteData assetData) {
_assetMap[assetKey] = assetData;
}
FakeAssetScope? _currentScope;
final Map<String, ByteData> _assetMap = <String, ByteData>{};
}
class FakeAssetScope {
FakeAssetScope._(this._parent);
final FakeAssetScope? _parent;
final Map<String, Future<ByteData> Function()> _assetFetcherMap = <String, Future<ByteData> Function()>{};
void setAsset(String assetKey, ByteData assetData) {
_assetFetcherMap[assetKey] = () async => assetData;
}
void setAssetPassthrough(String assetKey) {
_assetFetcherMap[assetKey] = () async {
return ByteData.view(await httpFetchByteBuffer(assetKey));
};
}
Future<ByteData>? getAssetData(String assetKey) {
final Future<ByteData> Function()? fetcher = _assetFetcherMap[assetKey];
if (fetcher != null) {
return fetcher();
}
if (_parent != null) {
return _parent!.getAssetData(assetKey);
}
return null;
}
}
FakeAssetManager fakeAssetManager = FakeAssetManager();
ByteData stringAsUtf8Data(String string) {
return ByteData.view(Uint8List.fromList(utf8.encode(string)).buffer);
}
const String ahemFontFamily = 'Ahem';
const String ahemFontUrl = '/assets/fonts/ahem.ttf';
const String robotoFontFamily = 'Roboto';
const String robotoTestFontUrl = '/assets/fonts/Roboto-Regular.ttf';
const String robotoVariableFontFamily = 'RobotoVariable';
const String robotoVariableFontUrl = '/assets/fonts/RobotoSlab-VariableFont_wght.ttf';
/// The list of test fonts, in the form of font family name - font file url pairs.
/// This list does not include embedded test fonts, which need to be loaded and
/// registered separately in [FontCollection.debugDownloadTestFonts].
const Map<String, String> testFontUrls = <String, String>{
ahemFontFamily: ahemFontUrl,
robotoFontFamily: robotoTestFontUrl,
robotoVariableFontFamily: robotoVariableFontUrl,
};
FakeAssetScope configureDebugFontsAssetScope(FakeAssetManager manager) {
final FakeAssetScope scope = manager.pushAssetScope();
scope.setAsset('AssetManifest.json', stringAsUtf8Data('{}'));
scope.setAsset('FontManifest.json', stringAsUtf8Data('''
[
{
"family":"$robotoFontFamily",
"fonts":[{"asset":"$robotoTestFontUrl"}]
},
{
"family":"$ahemFontFamily",
"fonts":[{"asset":"$ahemFontUrl"}]
},
{
"family":"$robotoVariableFontFamily",
"fonts":[{"asset":"$robotoVariableFontUrl"}]
}
]'''));
scope.setAssetPassthrough(robotoTestFontUrl);
scope.setAssetPassthrough(ahemFontUrl);
scope.setAssetPassthrough(robotoVariableFontUrl);
return scope;
}

View File

@@ -0,0 +1,53 @@
// 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.
import 'dart:js_util' as js_util;
import 'package:test/test.dart';
import 'package:ui/src/engine.dart' as engine;
import 'package:ui/ui.dart' as ui;
import 'fake_asset_manager.dart';
void setUpUnitTests({
bool emulateTesterEnvironment = true,
bool setUpTestViewDimensions = true,
}) {
late final FakeAssetScope debugFontsScope;
setUpAll(() async {
if (emulateTesterEnvironment) {
ui.debugEmulateFlutterTesterEnvironment = true;
}
// Some of our tests rely on color emoji
final engine.FlutterConfiguration config = engine.FlutterConfiguration()
..setUserConfiguration(
js_util.jsify(<String, Object?>{
'useColorEmoji': true,
}) as engine.JsFlutterConfiguration);
engine.debugSetConfiguration(config);
engine.notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/';
debugFontsScope = configureDebugFontsAssetScope(fakeAssetManager);
await engine.initializeEngine(assetManager: fakeAssetManager);
if (setUpTestViewDimensions) {
// Force-initialize FlutterViewEmbedder so it doesn't overwrite test pixel ratio.
engine.ensureFlutterViewEmbedderInitialized();
// The following parameters are hard-coded in Flutter's test embedder. Since
// we don't have an embedder yet this is the lowest-most layer we can put
// this stuff in.
const double devicePixelRatio = 3.0;
engine.window.debugOverrideDevicePixelRatio(devicePixelRatio);
engine.window.webOnlyDebugPhysicalSizeOverride =
const ui.Size(800 * devicePixelRatio, 600 * devicePixelRatio);
engine.scheduleFrameCallback = () {};
}
});
tearDownAll(() async {
fakeAssetManager.popAssetScope(debugFontsScope);
});
}

View File

@@ -9,12 +9,14 @@ import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import '../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
await initializeTestFlutterViewEmbedder();
setUpUnitTests();
group('message handler', () {
const String testText = 'test text';

View File

@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:js_interop';
import 'dart:typed_data';
import 'package:test/bootstrap/browser.dart';
@@ -116,7 +117,7 @@ Future<void> _testSuccessfulPayloads() async {
expect(response.url, url);
final List<int> result = <int>[];
await response.payload.read<Uint8List>(result.addAll);
await response.payload.read<JSUint8Array>((JSUint8Array chunk) => result.addAll(chunk.toDart));
expect(result, hasLength(length));
expect(
result,

View File

@@ -8,15 +8,16 @@ import 'dart:typed_data';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine/html_image_codec.dart';
import 'package:ui/src/engine/test_embedding.dart';
import 'package:ui/ui.dart' as ui;
import '../../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
await initializeTestFlutterViewEmbedder();
setUpUnitTests();
group('HtmCodec', () {
test('supports raw images - RGBA8888', () async {
final Completer<ui.Image> completer = Completer<ui.Image>();

View File

@@ -9,16 +9,14 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
setUpAll(() async {
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests();
Future<Image> createTestImageByColor(Color color) async {
final EnginePictureRecorder recorder = EnginePictureRecorder();

View File

@@ -8,15 +8,16 @@ import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../common/mock_engine_canvas.dart';
import '../html/screenshot.dart';
import '../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
debugEmulateFlutterTesterEnvironment = true;
setUpStableTestFonts();
setUpUnitTests(
setUpTestViewDimensions: false,
);
late RecordingCanvas underTest;
late MockEngineCanvas mockCanvas;

View File

@@ -12,7 +12,6 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart' show flutterViewEmbedder;
import 'package:ui/src/engine/browser_detection.dart';
import 'package:ui/src/engine/dom.dart';
import 'package:ui/src/engine/initialization.dart';
import 'package:ui/src/engine/services.dart';
import 'package:ui/src/engine/text_editing/autofill_hint.dart';
import 'package:ui/src/engine/text_editing/input_type.dart';
@@ -21,6 +20,7 @@ import 'package:ui/src/engine/util.dart';
import 'package:ui/src/engine/vector_math.dart';
import '../common/spy.dart';
import '../common/test_initialization.dart';
/// The `keyCode` of the "Enter" key.
const int _kReturnKeyCode = 13;
@@ -60,7 +60,10 @@ void main() {
}
Future<void> testMain() async {
await initializeEngine();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false
);
tearDown(() {
lastEditingState = null;

View File

@@ -10,8 +10,8 @@ import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import 'package:web_engine_tester/golden_tester.dart';
import '../common/test_initialization.dart';
import 'paragraph/helper.dart';
import 'screenshot.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
@@ -35,7 +35,10 @@ Future<void> testMain() async {
flutterViewEmbedder.glassPaneShadow.querySelector('flt-scene-host')!.append(testScene);
}
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
tearDown(() {
flutterViewEmbedder.glassPaneShadow.querySelector('flt-scene')?.remove();

View File

@@ -7,8 +7,10 @@ import 'dart:js_util' as js_util;
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart' as engine;
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
import '../common/test_initialization.dart';
import 'screenshot.dart';
void main() {
@@ -16,12 +18,9 @@ void main() {
}
Future<void> testMain() async {
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await engine.renderer.fontCollection.debugDownloadTestFonts();
engine.renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
// Regression test for https://github.com/flutter/flutter/issues/48683
// Should clip image with oval.

View File

@@ -7,6 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart' as engine;
import 'package:ui/ui.dart' hide TextStyle;
import '../common/test_initialization.dart';
import 'screenshot.dart';
void main() {
@@ -19,12 +20,9 @@ Future<void> testMain() async {
const double screenHeight = 800.0;
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await engine.renderer.fontCollection.debugDownloadTestFonts();
engine.renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
// Regression test for https://github.com/flutter/flutter/issues/49429
// Should clip with correct transform.

View File

@@ -9,6 +9,8 @@ import 'package:ui/ui.dart' hide TextStyle;
import 'package:web_engine_tester/golden_tester.dart';
import '../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
@@ -22,12 +24,7 @@ Future<void> testMain() async {
..strokeWidth = 2.0
..color = const Color(0xFFFF00FF);
setUp(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests();
// Regression test for https://github.com/flutter/flutter/issues/51514
test("Canvas is reused and z-index doesn't leak across paints", () async {

View File

@@ -7,6 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../common/test_initialization.dart';
import 'paragraph/helper.dart';
import 'screenshot.dart';
@@ -15,7 +16,10 @@ void main() {
}
Future<void> testMain() async {
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
/// Regression test for https://github.com/flutter/flutter/issues/64734.
test('Clips using difference', () async {

View File

@@ -9,16 +9,17 @@ import 'package:ui/ui.dart';
import 'package:web_engine_tester/golden_tester.dart';
import '../../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
setUpAll(() async {
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
setUp(() async {
debugShowClipLayers = true;

View File

@@ -9,6 +9,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
import '../../common/test_initialization.dart';
import '../screenshot.dart';
void main() {
@@ -16,13 +17,9 @@ void main() {
}
Future<void> testMain() async {
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
test('Blend circles with difference and color', () async {
final RecordingCanvas rc =

View File

@@ -7,6 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
import '../../common/test_initialization.dart';
import '../screenshot.dart';
import '../testimage.dart';
@@ -17,12 +18,9 @@ void main() {
SurfacePaint makePaint() => Paint() as SurfacePaint;
Future<void> testMain() async {
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
const Color red = Color(0xFFFF0000);
const Color green = Color(0xFF00FF00);

View File

@@ -10,6 +10,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import '../../common/test_initialization.dart';
import '../screenshot.dart';
void main() {
@@ -17,12 +18,9 @@ void main() {
}
Future<void> testMain() async {
setUpAll(() async {
ui.debugEmulateFlutterTesterEnvironment = true;
await ui.webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
tearDown(() {
ContextStateHandle.debugEmulateWebKitMaskFilter = false;

View File

@@ -11,6 +11,8 @@ import 'package:ui/ui.dart';
import 'package:web_engine_tester/golden_tester.dart';
import '../../common/test_initialization.dart';
const Rect region = Rect.fromLTWH(0, 0, 500, 500);
void main() {
@@ -18,11 +20,10 @@ void main() {
}
Future<void> testMain() async {
setUpAll(() async {
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
setUp(() async {
debugShowClipLayers = true;

View File

@@ -11,6 +11,7 @@ import 'package:ui/ui.dart' as ui;
import 'package:web_engine_tester/golden_tester.dart';
import '../../common/matchers.dart';
import '../../common/test_initialization.dart';
const ui.Rect region = ui.Rect.fromLTWH(0, 0, 500, 100);
@@ -19,11 +20,10 @@ void main() {
}
Future<void> testMain() async {
setUpAll(() async {
await ui.webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
setUp(() async {
// To debug test failures uncomment the following to visualize clipping

View File

@@ -6,6 +6,7 @@ import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
import '../../common/test_initialization.dart';
import '../screenshot.dart';
void main() {
@@ -13,12 +14,9 @@ void main() {
}
Future<void> testMain() async {
setUp(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
test('Should blur rectangles based on sigma.', () async {
final RecordingCanvas rc =

View File

@@ -7,6 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../../common/test_initialization.dart';
import '../screenshot.dart';
void main() {
@@ -14,12 +15,14 @@ void main() {
}
Future<void> testMain() async {
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
setUp(() async {
debugShowClipLayers = true;
SurfaceSceneBuilder.debugForgetFrameScene();
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
tearDown(() {

View File

@@ -14,6 +14,7 @@ import 'package:ui/ui.dart';
import 'package:web_engine_tester/golden_tester.dart';
import '../../common/test_initialization.dart';
import '../screenshot.dart';
void main() {
@@ -21,12 +22,9 @@ void main() {
}
Future<void> testMain() async {
setUp(() async {
debugEmulateFlutterTesterEnvironment = true;
});
setUpStableTestFonts();
setUpUnitTests(
setUpTestViewDimensions: false,
);
test('Paints image', () async {
final RecordingCanvas rc =

View File

@@ -7,6 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../../common/test_initialization.dart';
import '../screenshot.dart';
const Rect region = Rect.fromLTWH(0, 0, 500, 100);
@@ -18,11 +19,13 @@ void main() {
SurfacePaint makePaint() => Paint() as SurfacePaint;
Future<void> testMain() async {
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
setUpAll(() async {
debugShowClipLayers = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUp(() async {

View File

@@ -11,6 +11,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide ImageShader, TextStyle;
import '../../common/test_initialization.dart';
import '../screenshot.dart';
void main() {
@@ -22,12 +23,9 @@ Future<void> testMain() async {
const double screenHeight = 800.0;
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
setUp(() {
GlContextCache.dispose();

View File

@@ -7,7 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide window;
import '../screenshot.dart';
import '../../common/test_initialization.dart';
import 'helper.dart';
const String _rtlWord1 = 'واحد';
@@ -18,7 +18,10 @@ void main() {
}
Future<void> testMain() async {
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
void paintBasicBidiStartingWithLtr(
EngineCanvas canvas,

View File

@@ -10,7 +10,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide window;
import '../screenshot.dart';
import '../../common/test_initialization.dart';
import 'helper.dart';
const Rect bounds = Rect.fromLTWH(0, 0, 800, 600);
@@ -20,7 +20,10 @@ void main() {
}
Future<void> testMain() async {
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
test('paints spans and lines correctly', () {
final BitmapCanvas canvas = BitmapCanvas(bounds, RenderStrategy());

View File

@@ -9,7 +9,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide window;
import '../screenshot.dart';
import '../../common/test_initialization.dart';
import 'helper.dart';
void main() {
@@ -17,7 +17,10 @@ void main() {
}
Future<void> testMain() async {
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
void testJustifyWithMultipleSpans(EngineCanvas canvas) {
void build(CanvasParagraphBuilder builder) {

View File

@@ -9,7 +9,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide window;
import '../screenshot.dart';
import '../../common/test_initialization.dart';
import 'helper.dart';
void main() {
@@ -17,7 +17,10 @@ void main() {
}
Future<void> testMain() async {
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
void testEllipsis(EngineCanvas canvas) {
Offset offset = Offset.zero;

View File

@@ -9,7 +9,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide window;
import '../screenshot.dart';
import '../../common/test_initialization.dart';
import 'helper.dart';
const Rect bounds = Rect.fromLTWH(0, 0, 800, 600);
@@ -19,7 +19,10 @@ void main() {
}
Future<void> testMain() async {
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
test('draws paragraphs with placeholders', () {
final BitmapCanvas canvas = BitmapCanvas(bounds, RenderStrategy());

View File

@@ -7,7 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide window;
import '../screenshot.dart';
import '../../common/test_initialization.dart';
import 'helper.dart';
const Rect bounds = Rect.fromLTWH(0, 0, 800, 600);
@@ -17,7 +17,10 @@ void main() {
}
Future<void> testMain() async {
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
test('paints multiple shadows', () {
final BitmapCanvas canvas = BitmapCanvas(bounds, RenderStrategy());

View File

@@ -8,7 +8,7 @@ import 'package:test/bootstrap/browser.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../screenshot.dart';
import '../../common/test_initialization.dart';
import 'text_scuba.dart';
typedef PaintTest = void Function(RecordingCanvas recordingCanvas);
@@ -23,7 +23,10 @@ Future<void> testMain() async {
viewportSize: const Size(600, 600),
);
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
void paintTest(EngineCanvas canvas, PaintTest painter) {
const Rect screenRect = Rect.fromLTWH(0, 0, 600, 600);

View File

@@ -8,7 +8,7 @@ import 'package:test/bootstrap/browser.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide window;
import '../screenshot.dart';
import '../../common/test_initialization.dart';
import 'text_scuba.dart';
const String threeLines = 'First\nSecond\nThird';
@@ -25,7 +25,10 @@ Future<void> testMain() async {
viewportSize: const Size(800, 800),
);
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
testEachCanvas('maxLines clipping', (EngineCanvas canvas) {
Offset offset = Offset.zero;

View File

@@ -6,7 +6,7 @@ import 'package:test/bootstrap/browser.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../screenshot.dart';
import '../../common/test_initialization.dart';
import 'helper.dart';
import 'text_scuba.dart';
@@ -19,8 +19,10 @@ Future<void> testMain() async {
viewportSize: const Size(600, 600),
);
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
testEachCanvas('draws paragraphs with placeholders', (EngineCanvas canvas) {
const Rect screenRect = Rect.fromLTWH(0, 0, 600, 600);

View File

@@ -8,6 +8,7 @@ import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
import '../common/matchers.dart';
import '../common/test_initialization.dart';
import 'screenshot.dart';
void main() {
@@ -22,12 +23,9 @@ Future<void> testMain() async {
const Color redAccentColor = Color(0xFFFF1744);
const double kDashLength = 5.0;
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
test('Should calculate tangent on line', () async {
final Path path = Path();

View File

@@ -9,6 +9,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
import '../common/test_initialization.dart';
import 'screenshot.dart';
void main() {
@@ -20,12 +21,9 @@ Future<void> testMain() async {
const double screenHeight = 800.0;
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
test('Should draw transformed line.', () async {
final RecordingCanvas rc =

View File

@@ -12,18 +12,16 @@ import 'package:ui/ui.dart' hide TextStyle;
import 'package:web_engine_tester/golden_tester.dart';
import '../common/matchers.dart';
import 'screenshot.dart';
import '../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
});
setUpStableTestFonts();
setUpUnitTests(
setUpTestViewDimensions: false,
);
const double screenWidth = 600.0;
const double screenHeight = 800.0;

View File

@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'dart:async';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import 'package:web_engine_tester/golden_tester.dart';
@@ -70,14 +69,3 @@ Future<void> sceneScreenshot(SurfaceSceneBuilder sceneBuilder, String fileName,
sceneElement?.remove();
}
}
/// Configures the test to use bundled Roboto and Ahem fonts to avoid golden
/// screenshot differences due to differences in the preinstalled system fonts.
void setUpStableTestFonts() {
setUpAll(() async {
await ui.webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
}

View File

@@ -10,6 +10,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../../common/test_initialization.dart';
import '../screenshot.dart';
// TODO(yjbanov): unskip Firefox tests when Firefox implements WebGL in headless mode.
@@ -25,11 +26,7 @@ Future<void> testMain() async {
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
const Rect region = Rect.fromLTWH(0, 0, 500, 240);
setUp(() async {
debugEmulateFlutterTesterEnvironment = true;
});
setUpStableTestFonts();
setUpUnitTests();
test('Paints sweep gradient rectangles', () async {
final RecordingCanvas canvas =

View File

@@ -9,6 +9,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
import '../../common/test_initialization.dart';
import '../screenshot.dart';
// TODO(yjbanov): unskip Firefox tests when Firefox implements WebGL in headless mode.
@@ -24,12 +25,9 @@ Future<void> testMain() async {
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
final HtmlImage testImage = createTestImage();
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
void drawShapes(RecordingCanvas rc, SurfacePaint paint, Rect shaderRect) {
/// Rect.

View File

@@ -8,6 +8,7 @@ import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
import '../../common/test_initialization.dart';
import '../screenshot.dart';
// TODO(yjbanov): unskip Firefox tests when Firefox implements WebGL in headless mode.
@@ -18,12 +19,9 @@ void main() {
}
Future<void> testMain() async {
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
test('Should draw linear gradient using rectangle.', () async {
final RecordingCanvas rc =

View File

@@ -6,6 +6,7 @@ import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
import '../../common/test_initialization.dart';
import '../screenshot.dart';
void main() {
@@ -13,13 +14,9 @@ void main() {
}
Future<void> testMain() async {
setUpAll(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
setUpUnitTests(
setUpTestViewDimensions: false,
);
Future<void> testGradient(String fileName, Shader shader,
{Rect paintRect = const Rect.fromLTRB(50, 50, 300, 300),

View File

@@ -10,6 +10,8 @@ import 'package:ui/ui.dart';
import 'package:web_engine_tester/golden_tester.dart';
import '../../common/test_initialization.dart';
/// To debug compositing failures on browsers, set this flag to true and run
/// flutter run -d chrome --web-renderer=html
/// test/golden_tests/engine/shader_mask_golden_test.dart --profile
@@ -29,9 +31,13 @@ Future<void> main() async {
// https://github.com/flutter/flutter/issues/86623
Future<void> testMain() async {
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
setUpAll(() async {
debugShowClipLayers = true;
await webOnlyInitializePlatform();
});
setUp(() async {
@@ -41,8 +47,6 @@ Future<void> testMain() async {
scene.remove();
}
initWebGl();
await renderer.fontCollection.debugDownloadTestFonts();
renderer.fontCollection.registerDownloadedFonts();
});
/// Should render the picture unmodified.

View File

@@ -9,7 +9,7 @@ import 'package:ui/ui.dart';
import 'package:web_engine_tester/golden_tester.dart';
import 'screenshot.dart';
import '../common/test_initialization.dart';
const Color _kShadowColor = Color.fromARGB(255, 0, 0, 0);
@@ -22,7 +22,10 @@ Future<void> testMain() async {
late SurfaceSceneBuilder builder;
setUpStableTestFonts();
setUpUnitTests(
emulateTesterEnvironment: false,
setUpTestViewDimensions: false,
);
setUp(() {
builder = SurfaceSceneBuilder();

View File

@@ -7,7 +7,8 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../html/paragraph/helper.dart';
import '../../common/test_initialization.dart';
import '../paragraph/helper.dart';
/// Some text measurements are sensitive to browser implementations. Position
/// info in the following tests only pass in Chrome, they are slightly different
@@ -31,7 +32,7 @@ void main() {
}
Future<void> testMain() async {
await initializeTestFlutterViewEmbedder();
setUpUnitTests();
test('empty paragraph', () {
final CanvasParagraph paragraph1 = rich(

View File

@@ -7,14 +7,15 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import '../html/paragraph/helper.dart';
import '../../common/test_initialization.dart';
import '../paragraph/helper.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
await initializeTestFlutterViewEmbedder();
setUpUnitTests();
group('$CanvasParagraph.getBoxesForRange', () {
test('return empty list for invalid ranges', () {

View File

@@ -7,32 +7,43 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import '../../common/fake_asset_manager.dart';
import '../../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
group('$FontManager', () {
late FontManager fontManager;
group('$HtmlFontCollection', () {
setUpUnitTests();
const String testFontUrl = '/assets/fonts/ahem.ttf';
late FakeAssetScope testScope;
setUp(() {
fontManager = FontManager();
testScope = fakeAssetManager.pushAssetScope();
testScope.setAssetPassthrough(testFontUrl);
// Clear the fonts before the test begins to wipe out the fonts from the
// test initialization.
domDocument.fonts!.clear();
});
tearDown(() {
domDocument.fonts!.clear();
fakeAssetManager.popAssetScope(testScope);
});
group('regular special characters', () {
test('Register Asset with no special characters', () async {
const String testFontFamily = 'Ahem';
final List<String> fontFamilyList = <String>[];
fontManager.downloadAsset(
testFontFamily, 'url($testFontUrl)', const <String, String>{});
await fontManager.downloadAllFonts();
fontManager.registerDownloadedFonts();
final HtmlFontCollection collection = HtmlFontCollection();
await collection.loadAssetFonts(FontManifest(<FontFamily>[
FontFamily(testFontFamily, <FontAsset>[
FontAsset(testFontUrl, <String, String>{})
])
]));
domDocument.fonts!
.forEach((DomFontFace f, DomFontFace f2, DomFontFaceSet s) {
fontFamilyList.add(f.family!);
@@ -46,10 +57,12 @@ void testMain() {
const String testFontFamily = 'Ahem ahem ahem';
final List<String> fontFamilyList = <String>[];
fontManager.downloadAsset(
testFontFamily, 'url($testFontUrl)', const <String, String>{});
await fontManager.downloadAllFonts();
fontManager.registerDownloadedFonts();
final HtmlFontCollection collection = HtmlFontCollection();
await collection.loadAssetFonts(FontManifest(<FontFamily>[
FontFamily(testFontFamily, <FontAsset>[
FontAsset(testFontUrl, <String, String>{})
])
]));
domDocument.fonts!
.forEach((DomFontFace f, DomFontFace f2, DomFontFaceSet s) {
fontFamilyList.add(f.family!);
@@ -65,10 +78,12 @@ void testMain() {
const String testFontFamily = 'AhEm';
final List<String> fontFamilyList = <String>[];
fontManager.downloadAsset(
testFontFamily, 'url($testFontUrl)', const <String, String>{});
await fontManager.downloadAllFonts();
fontManager.registerDownloadedFonts();
final HtmlFontCollection collection = HtmlFontCollection();
await collection.loadAssetFonts(FontManifest(<FontFamily>[
FontFamily(testFontFamily, <FontAsset>[
FontAsset(testFontUrl, <String, String>{})
])
]));
domDocument.fonts!
.forEach((DomFontFace f, DomFontFace f2, DomFontFaceSet s) {
fontFamilyList.add(f.family!);
@@ -81,13 +96,15 @@ void testMain() {
test('Register Asset with descriptor', () async {
const String testFontFamily = 'Ahem';
final List<String> fontFamilyList = <String>[];
final HtmlFontCollection collection = HtmlFontCollection();
await collection.loadAssetFonts(FontManifest(<FontFamily>[
FontFamily(testFontFamily, <FontAsset>[
FontAsset(testFontUrl, <String, String>{
'weight': 'bold'
})
])
]));
fontManager.downloadAsset(
testFontFamily, 'url($testFontUrl)', const <String, String>{
'weight': 'bold',
});
await fontManager.downloadAllFonts();
fontManager.registerDownloadedFonts();
domDocument.fonts!
.forEach((DomFontFace f, DomFontFace f2, DomFontFaceSet s) {
expect(f.weight, 'bold');
@@ -105,10 +122,12 @@ void testMain() {
const String testFontFamily = '/Ahem';
final List<String> fontFamilyList = <String>[];
fontManager.downloadAsset(
testFontFamily, 'url($testFontUrl)', const <String, String>{});
await fontManager.downloadAllFonts();
fontManager.registerDownloadedFonts();
final HtmlFontCollection collection = HtmlFontCollection();
await collection.loadAssetFonts(FontManifest(<FontFamily>[
FontFamily(testFontFamily, <FontAsset>[
FontAsset(testFontUrl, <String, String>{})
])
]));
domDocument.fonts!
.forEach((DomFontFace f, DomFontFace f2, DomFontFaceSet s) {
fontFamilyList.add(f.family!);
@@ -130,10 +149,12 @@ void testMain() {
const String testFontFamily = 'Ahem!!ahem';
final List<String> fontFamilyList = <String>[];
fontManager.downloadAsset(
testFontFamily, 'url($testFontUrl)', const <String, String>{});
await fontManager.downloadAllFonts();
fontManager.registerDownloadedFonts();
final HtmlFontCollection collection = HtmlFontCollection();
await collection.loadAssetFonts(FontManifest(<FontFamily>[
FontFamily(testFontFamily, <FontAsset>[
FontAsset(testFontUrl, <String, String>{})
])
]));
domDocument.fonts!
.forEach((DomFontFace f, DomFontFace f2, DomFontFaceSet s) {
fontFamilyList.add(f.family!);
@@ -155,10 +176,12 @@ void testMain() {
const String testFontFamily = 'Ahem ,ahem';
final List<String> fontFamilyList = <String>[];
fontManager.downloadAsset(
testFontFamily, 'url($testFontUrl)', const <String, String>{});
await fontManager.downloadAllFonts();
fontManager.registerDownloadedFonts();
final HtmlFontCollection collection = HtmlFontCollection();
await collection.loadAssetFonts(FontManifest(<FontFamily>[
FontFamily(testFontFamily, <FontAsset>[
FontAsset(testFontUrl, <String, String>{})
])
]));
domDocument.fonts!
.forEach((DomFontFace f, DomFontFace f2, DomFontFaceSet s) {
fontFamilyList.add(f.family!);
@@ -181,10 +204,12 @@ void testMain() {
const String testFontFamily = 'Ahem 1998';
final List<String> fontFamilyList = <String>[];
fontManager.downloadAsset(
testFontFamily, 'url($testFontUrl)', const <String, String>{});
await fontManager.downloadAllFonts();
fontManager.registerDownloadedFonts();
final HtmlFontCollection collection = HtmlFontCollection();
await collection.loadAssetFonts(FontManifest(<FontFamily>[
FontFamily(testFontFamily, <FontAsset>[
FontAsset(testFontUrl, <String, String>{})
])
]));
domDocument.fonts!
.forEach((DomFontFace f, DomFontFace f2, DomFontFaceSet s) {
fontFamilyList.add(f.family!);

View File

@@ -11,12 +11,14 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import '../../common/test_initialization.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
await initializeTestFlutterViewEmbedder();
setUpUnitTests();
group('loadFontFromList', () {
const String testFontUrl = '/assets/fonts/ahem.ttf';
@@ -24,10 +26,11 @@ Future<void> testMain() async {
domDocument.fonts!.clear();
});
test('surfaces error from invalid font buffer', () async {
test('returns normally from invalid font buffer', () async {
await expectLater(
ui.loadFontFromList(Uint8List(0), fontFamily: 'test-font'),
throwsA(const TypeMatcher<Exception>()));
() async => ui.loadFontFromList(Uint8List(0), fontFamily: 'test-font'),
returnsNormally
);
},
// TODO(hterkelsen): https://github.com/flutter/flutter/issues/56702
skip: browserEngine == BrowserEngine.webkit);

View File

@@ -7,7 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../html/paragraph/helper.dart';
import '../paragraph/helper.dart';
final EngineTextStyle defaultStyle = EngineTextStyle.only(
color: const Color(0xFFFF0000),

View File

@@ -7,7 +7,8 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import '../html/paragraph/helper.dart';
import '../../common/test_initialization.dart';
import '../paragraph/helper.dart';
import 'layout_service_helper.dart';
const bool skipWordSpacing = true;
@@ -17,7 +18,7 @@ void main() {
}
Future<void> testMain() async {
await initializeTestFlutterViewEmbedder();
setUpUnitTests();
test('no text', () {
final CanvasParagraph paragraph = CanvasParagraphBuilder(ahemStyle).build();

View File

@@ -8,7 +8,8 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import '../html/paragraph/helper.dart';
import '../../common/test_initialization.dart';
import '../paragraph/helper.dart';
import 'layout_service_helper.dart';
void main() {
@@ -16,7 +17,7 @@ void main() {
}
Future<void> testMain() async {
await initializeTestFlutterViewEmbedder();
setUpUnitTests();
test('does not crash on empty spans', () {
final CanvasParagraph paragraph = rich(ahemStyle, (CanvasParagraphBuilder builder) {

View File

@@ -8,7 +8,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../html/paragraph/helper.dart';
import '../paragraph/helper.dart';
import 'line_breaker_test_helper.dart';
import 'line_breaker_test_raw_data.dart';

View File

@@ -7,7 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../html/paragraph/helper.dart';
import '../paragraph/helper.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);

View File

@@ -7,11 +7,11 @@
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../common/matchers.dart';
import '../common/test_initialization.dart';
import 'paragraph/helper.dart';
void main() {
@@ -21,7 +21,7 @@ void main() {
Future<void> testMain() async {
const double baselineRatio = 1.1662499904632568;
await initializeTestFlutterViewEmbedder();
setUpUnitTests();
late String fallback;
setUp(() {

View File

@@ -8,5 +8,5 @@ In practice, this means these tests should only use `dart:ui` APIs or
## Notes
These tests should call `setUpUiTest()` at the top level to initialize the
These tests should call `setUpUnitTests()` at the top level to initialize the
renderer they are expected to run.

View File

@@ -9,6 +9,7 @@ import 'package:test/test.dart';
import 'package:ui/ui.dart';
import 'package:web_engine_tester/golden_tester.dart';
import '../common/test_initialization.dart';
import 'utils.dart';
void main() {
@@ -16,7 +17,9 @@ void main() {
}
Future<void> testMain() async {
setUpUiTest();
setUpUnitTests(
setUpTestViewDimensions: false,
);
const Rect region = Rect.fromLTWH(0, 0, 300, 300);

View File

@@ -7,6 +7,7 @@ import 'package:test/test.dart';
import 'package:ui/ui.dart';
import 'package:web_engine_tester/golden_tester.dart';
import '../common/test_initialization.dart';
import 'utils.dart';
void main() {
@@ -14,7 +15,9 @@ void main() {
}
Future<void> testMain() async {
setUpUiTest();
setUpUnitTests(
setUpTestViewDimensions: false,
);
const Rect region = Rect.fromLTWH(0, 0, 300, 300);

Some files were not shown because too many files have changed in this diff Show More