diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ffcb36dbe7f3f..a1989101ea127 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -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 diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 6c152e9512ad1..a6c832c188754 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -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'; diff --git a/lib/web_ui/lib/src/engine/assets.dart b/lib/web_ui/lib/src/engine/assets.dart index b200f824e4145..585f9d4f39dad 100644 --- a/lib/web_ui/lib/src/engine/assets.dart +++ b/lib/web_ui/lib/src/engine/assets.dart @@ -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 testFontUrls = { - 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 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 load(String asset) { - if (asset == getAssetUrl('AssetManifest.json')) { - return Future.value( - _toByteData(utf8.encode(defaultAssetManifest))); - } - if (asset == getAssetUrl('FontManifest.json')) { - return Future.value( - _toByteData(utf8.encode(defaultFontManifest))); - } - throw HttpFetchNoPayloadError(asset, status: 404); - } - - ByteData _toByteData(List bytes) { - final ByteData byteData = ByteData(bytes.length); - for (int i = 0; i < bytes.length; i++) { - byteData.setUint8(i, bytes[i]); - } - return byteData; - } -} diff --git a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart index 66deb4940bd40..63827fcaeb1b6 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart @@ -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(); } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart index 86c5ab5e2edf7..5f4fbabb0a723 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart @@ -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 loadFontFromList(Uint8List list, {String? fontFamily}) async { + Future 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 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? fontManifest = json.decode(utf8.decode(data)) as List?; - if (fontManifest == null) { - throw AssertionError( - 'There was a problem trying to load FontManifest.json'); - } - - final List> pendingFonts = >[]; - - for (final Map fontFamily - in fontManifest.cast>()) { - final String family = fontFamily.readString('family'); - final List fontAssets = fontFamily.readList('fonts'); - for (final dynamic fontAssetItem in fontAssets) { - final Map fontAsset = fontAssetItem as Map; - final String asset = fontAsset.readString('asset'); - _downloadFont(pendingFonts, assetManager.getAssetUrl(asset), family); + Future loadAssetFonts(FontManifest manifest) async { + final List> pendingDownloads = >[]; + 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 completedPendingFonts = await Future.wait(pendingFonts); - _unregisteredFonts.addAll(completedPendingFonts.whereType()); + final Map fontFailures = {}; + 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 loadedFonts = []; + 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 debugDownloadTestFonts() async { - final List> pendingFonts = >[]; - for (final MapEntry fontEntry in testFontUrls.entries) { - if (!_isFontFamilyDownloaded(fontEntry.key)) { - _downloadFont(pendingFonts, fontEntry.value, fontEntry.key); - } - } - final List completedPendingFonts = await Future.wait(pendingFonts); - final List fonts = [ - UnregisteredFont( - EmbeddedTestFont.flutterTest.data.buffer, - '', - EmbeddedTestFont.flutterTest.fontFamily, - ), - ...completedPendingFonts.whereType(), - ]; - _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> waitUnregisteredFonts, + Future _downloadFont( + String assetName, String url, - String family - ) { - Future 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; + + // 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)); } - } - _downloadedFontFamilies.add(family); - waitUnregisteredFonts.add(downloadFont()); + 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; +} diff --git a/lib/web_ui/lib/src/engine/canvaskit/image.dart b/lib/web_ui/lib/src/engine/canvaskit/image.dart index f527294bb4f99..d100f5a83c5c1 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image.dart @@ -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 skiaInstantiateImageCodec(Uint8List list, [int? targetWidth, int? targetHeight]) { @@ -214,16 +203,16 @@ Future fetchImage(String url, WebOnlyImageCodecChunkCallback? chunkCa /// /// See: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API Future 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 chunk) { - cumulativeBytesLoaded += chunk.lengthInBytes; + await payload.read((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. diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart b/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart index 604ac004c8980..bf96471ff1f8a 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart @@ -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 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(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 encodeVideoFrameAsPng(VideoFrame videoFrame) async { diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index 06db1bc9a2a61..cec642f014188 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -48,6 +48,8 @@ class CanvasKitRenderer implements Renderer { static CanvasKitRenderer get instance => _instance; static late CanvasKitRenderer _instance; + Future? _initialized; + @override String get rendererTag => 'canvaskit'; @@ -66,14 +68,16 @@ class CanvasKitRenderer implements Renderer { @override Future 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 diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index ca7056d05672b..f427cb7d43880 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -1397,7 +1397,7 @@ class DomXMLHttpRequestEventTarget extends DomEventTarget {} Future<_DomResponse> _rawHttpGet(String url) => js_util.promiseToFuture<_DomResponse>(domWindow._fetch1(url.toJS)); -typedef MockHttpFetchResponseFactory = Future Function( +typedef MockHttpFetchResponseFactory = Future Function( String url); MockHttpFetchResponseFactory? mockHttpFetchResponseFactory; @@ -1417,7 +1417,10 @@ MockHttpFetchResponseFactory? mockHttpFetchResponseFactory; /// [httpFetchText] instead. Future 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 Function(HttpFetchReader 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 read(HttpFetchReader callback) => _onRead!(callback); + Future read(HttpFetchReader 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 asByteBuffer() async => _byteBuffer!; + Future asByteBuffer() async => _byteBuffer; @override - Future json() async => _json!; + Future json() async => throw AssertionError('json not supported by mock'); @override - Future text() async => _text!; + Future 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() diff --git a/lib/web_ui/lib/src/engine/fonts.dart b/lib/web_ui/lib/src/engine/fonts.dart index cf3b60f6e9319..41dac23c68470 100644 --- a/lib/web_ui/lib/src/engine/fonts.dart +++ b/lib/web_ui/lib/src/engine/fonts.dart @@ -3,32 +3,126 @@ // 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 descriptors; +} + +class FontFamily { + FontFamily(this.name, this.fontAssets); + + final String name; + final List fontAssets; +} + +class FontManifest { + FontManifest(this.families); + + final List families; +} + +Future 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([]); + } + + final Converter, Object?> decoder = const Utf8Decoder().fuse(const JsonDecoder()); + Object? fontManifestJson; + final Sink> inputSink = decoder.startChunkedConversion( + ChunkedConversionSink.withCallback( + (List 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 families = (fontManifestJson! as List).map( + (dynamic fontFamilyJson) { + final Map fontFamily = fontFamilyJson as Map; + final String familyName = fontFamily.readString('family'); + final List fontAssets = fontFamily.readList('fonts'); + return FontFamily(familyName, fontAssets.map((dynamic fontAssetJson) { + String? asset; + final Map descriptors = {}; + for (final MapEntry descriptor in (fontAssetJson as Map).entries) { + if (descriptor.key == 'asset') { + asset = descriptor.value as String; + } else { + descriptors[descriptor.key] = descriptor.value as String; + } + } + 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 loadedFonts; + + /// A map of the asset keys to failures for fonts that failed to load. + final Map fontFailures; +} abstract class FlutterFontCollection { + /// Loads a font directly from font data. + Future loadFontFromList(Uint8List list, {String? fontFamily}); + + /// Completes when fonts from FontManifest.json have been loaded. + Future loadAssetFonts(FontManifest manifest); - /// Fonts loaded with [loadFontFromList] do not need to be registered - /// with [registerDownloadedFonts]. Fonts are both downloaded and registered - /// with [loadFontFromList] calls. - Future loadFontFromList(Uint8List list, {String? fontFamily}); - - /// Completes when fonts from FontManifest.json have been downloaded. - Future 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 debugDownloadTestFonts(); + // Unregisters all fonts. void clear(); } diff --git a/lib/web_ui/lib/src/engine/initialization.dart b/lib/web_ui/lib/src/engine/initialization.dart index f55255c092e92..18cb6597f90d5 100644 --- a/lib/web_ui/lib/src/engine/initialization.dart +++ b/lib/web_ui/lib/src/engine/initialization.dart @@ -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 initializeEngineServices({ Future initializeRendererCallback () async => renderer.initialize(); await Future.wait(>[initializeRendererCallback(), _downloadAssetFonts()]); - renderer.fontCollection.registerDownloadedFonts(); _initializationState = DebugEngineInitializationState.initializedServices; } @@ -248,12 +248,17 @@ void _setAssetManager(AssetManager assetManager) { Future _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)); } } diff --git a/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart b/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart new file mode 100644 index 0000000000000..a6bb9e2041485 --- /dev/null +++ b/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart @@ -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); diff --git a/lib/web_ui/lib/src/engine/renderer.dart b/lib/web_ui/lib/src/engine/renderer.dart index bdb1d0169b7ce..5dbaaad432bfb 100644 --- a/lib/web_ui/lib/src/engine/renderer.dart +++ b/lib/web_ui/lib/src/engine/renderer.dart @@ -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 { + 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 { - // If requestedRendererType is not specified, use CanvasKit for desktop and - // html for mobile. - useCanvasKit = isDesktop; + useCanvasKit = FlutterConfiguration.useSkia; } - } else { - useCanvasKit = FlutterConfiguration.useSkia; - } - return useCanvasKit ? CanvasKitRenderer() : HtmlRenderer(); + return useCanvasKit ? CanvasKitRenderer() : HtmlRenderer(); + } } String get rendererTag; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart index 9d489df3ab489..13d223e640888 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart @@ -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'; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/font_collection.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/font_collection.dart index 0d3a2612988ff..f99c01f484e65 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/font_collection.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/font_collection.dart @@ -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 debugDownloadTestFonts() { - // TODO(jacksongardner): implement debugDownloadTestFonts - } + Future loadAssetFonts(FontManifest manifest) async { + final List> fontFutures = >[]; + final List loadedFonts = []; + final Map fontFailures = {}; - @override - Future downloadAssetFonts(AssetManager assetManager) async { - // TODO(jacksongardner): implement downloadAssetFonts + // We can't restore the pointers directly due to a bug in dart2wasm + // https://github.com/dart-lang/sdk/issues/52142 + final List familyHandles = []; + for (final FontFamily family in manifest.families) { + final List rawUtf8Bytes = utf8.encode(family.name); + final SkStringHandle stringHandle = skStringAllocate(rawUtf8Bytes.length); + final Pointer 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); } - @override - Future loadFontFromList(Uint8List list, {String? fontFamily}) async { - // TODO(jacksongardner): implement loadFontFromList + Future _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 chunks = []; + await response.read((JSUint8Array1 chunk) { + length += chunk.length.toDart.toInt(); + chunks.add(chunk); + }); + final SkDataHandle fontData = skDataCreate(length); + int dataAddress = skDataGetPointer(fontData).cast().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 - void registerDownloadedFonts() { - // TODO(jacksongardner): implement registerDownloadedFonts + Future loadFontFromList(Uint8List list, {String? fontFamily}) async { + final SkDataHandle dataHandle = skDataCreate(list.length); + final Pointer dataPointer = skDataGetPointer(dataHandle).cast(); + for (int i = 0; i < list.length; i++) { + dataPointer[i] = list[i]; + } + bool success; + if (fontFamily != null) { + final List rawUtf8Bytes = utf8.encode(fontFamily); + final SkStringHandle stringHandle = skStringAllocate(rawUtf8Bytes.length); + final Pointer 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; } } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart new file mode 100644 index 0000000000000..5bbd928d1ce2e --- /dev/null +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart @@ -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; + +@Native(symbol: 'fontCollection_create', isLeaf: true) +external FontCollectionHandle fontCollectionCreate(); + +@Native(symbol: 'fontCollection_dispose', isLeaf: true) +external void fontCollectionDispose(FontCollectionHandle handle); + +@Native(symbol: 'fontCollection_registerFont', isLeaf: true) +external bool fontCollectionRegisterFont( + FontCollectionHandle handle, + SkDataHandle fontData, + SkStringHandle fontName, +); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart index beadca256a28d..a999ddad28b30 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart @@ -12,12 +12,6 @@ import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; final class RawShader extends Opaque {} typedef ShaderHandle = Pointer; -final class RawSkString extends Opaque {} -typedef SkStringHandle = Pointer; - -final class RawSkData extends Opaque {} -typedef SkDataHandle = Pointer; - final class RawRuntimeEffect extends Opaque {} typedef RuntimeEffectHandle = Pointer; @@ -106,15 +100,6 @@ external ShaderHandle shaderCreateSweepGradient( @Native(symbol: 'shader_dispose', isLeaf: true) external void shaderDispose(ShaderHandle handle); -@Native(symbol: 'shaderSource_allocate', isLeaf: true) -external SkStringHandle shaderSourceAllocate(int size); - -@Native Function(SkStringHandle)>(symbol: 'shaderSource_getData', isLeaf: true) -external Pointer shaderSourceGetData(SkStringHandle handle); - -@Native(symbol: 'shaderSource_free', isLeaf: true) -external void shaderSourceFree(SkStringHandle handle); - @Native(symbol: 'runtimeEffect_create', isLeaf: true) external RuntimeEffectHandle runtimeEffectCreate(SkStringHandle source); @@ -124,15 +109,6 @@ external void runtimeEffectDispose(RuntimeEffectHandle handle); @Native(symbol: 'runtimeEffect_getUniformSize', isLeaf: true) external int runtimeEffectGetUniformSize(RuntimeEffectHandle handle); -@Native(symbol: 'data_create', isLeaf: true) -external SkDataHandle dataCreate(int size); - -@Native Function(SkDataHandle)>(symbol: 'data_getPointer', isLeaf: true) -external Pointer dataGetPointer(SkDataHandle handle); - -@Native(symbol: 'data_dispose', isLeaf: true) -external void dataDispose(SkDataHandle handle); - @Native; + +@Native(symbol: 'skData_create', isLeaf: true) +external SkDataHandle skDataCreate(int size); + +@Native Function(SkDataHandle)>(symbol: 'skData_getPointer', isLeaf: true) +external Pointer skDataGetPointer(SkDataHandle handle); + +@Native(symbol: 'skData_dispose', isLeaf: true) +external void skDataDispose(SkDataHandle handle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart new file mode 100644 index 0000000000000..1b5af811542ad --- /dev/null +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart @@ -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; + +@Native(symbol: 'skString_allocate', isLeaf: true) +external SkStringHandle skStringAllocate(int size); + +@Native Function(SkStringHandle)>(symbol: 'skString_getData', isLeaf: true) +external Pointer skStringGetData(SkStringHandle handle); + +@Native(symbol: 'skString_free', isLeaf: true) +external void skStringFree(SkStringHandle handle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/js_functions.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart similarity index 68% rename from lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/js_functions.dart rename to lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart index 54a42bfc6c31b..e12b43ddcd0e9 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/js_functions.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart @@ -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') diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index 4740a2f4e9724..73b8e4e21fc83 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -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.delayed(const Duration(milliseconds: 100)); } @override diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart index dc37b92666143..ef2bf5ce2f1c5 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart @@ -162,15 +162,15 @@ class SkwasmFragmentProgram implements ui.FragmentProgram { // TODO(jacksongardner): Can we avoid this copy? final List sourceData = utf8.encode(shaderData.source); - final SkStringHandle sourceString = shaderSourceAllocate(sourceData.length); - final Pointer sourceBuffer = shaderSourceGetData(sourceString); + final SkStringHandle sourceString = skStringAllocate(sourceData.length); + final Pointer 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? 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 dataPointer = dataGetPointer(_uniformData).cast(); + final Pointer dataPointer = skDataGetPointer(_uniformData).cast(); 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; } } diff --git a/lib/web_ui/lib/src/engine/test_embedding.dart b/lib/web_ui/lib/src/engine/test_embedding.dart index 103830680348e..18febe3f233ff 100644 --- a/lib/web_ui/lib/src/engine/test_embedding.dart +++ b/lib/web_ui/lib/src/engine/test_embedding.dart @@ -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? _platformInitializedFuture; - -Future 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 { diff --git a/lib/web_ui/lib/src/engine/text/font_collection.dart b/lib/web_ui/lib/src/engine/text/font_collection.dart index 5af290c6630de..c6666183a3fbf 100644 --- a/lib/web_ui/lib/src/engine/text/font_collection.dart +++ b/lib/web_ui/lib/src/engine/text/font_collection.dart @@ -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 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? fontManifest = json.decode(utf8.decode(data)) as List?; - if (fontManifest == null) { - throw AssertionError( - 'There was a problem trying to load FontManifest.json'); + Future loadAssetFonts(FontManifest manifest) async { + final List> pendingFonts = >[]; + 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) + ); + }()); + } } - _assetFontManager = FontManager(); - - for (final Map fontFamily - in fontManifest.cast>()) { - final String? family = fontFamily.tryString('family'); - final List> fontAssets = fontFamily.castList>('fonts'); - - for (final Map fontAsset in fontAssets) { - final String asset = fontAsset.readString('asset'); - final Map descriptors = {}; - for (final String descriptor in fontAsset.keys) { - if (descriptor != 'asset') { - descriptors[descriptor] = '${fontAsset[descriptor]}'; - } - } - _assetFontManager!.downloadAsset( - family!, 'url(${assetManager.getAssetUrl(asset)})', descriptors); + final List loadedFonts = []; + final Map fontFailures = {}; + for (final (String asset, FontLoadError? error) in await Future.wait(pendingFonts)) { + if (error == null) { + loadedFonts.add(asset); + } else { + fontFailures[asset] = error; } } - await _assetFontManager!.downloadAllFonts(); + return AssetFontsResult(loadedFonts, fontFailures); } @override - Future loadFontFromList(Uint8List list, {String? fontFamily}) { + Future 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 debugDownloadTestFonts() async { - final FontManager fontManager = _testFontManager = FontManager(); - fontManager._downloadedFonts.add(createDomFontFace( - EmbeddedTestFont.flutterTest.fontFamily, - EmbeddedTestFont.flutterTest.data, - )); - for (final MapEntry fontEntry in testFontUrls.entries) { - fontManager.downloadAsset(fontEntry.key, 'url(${fontEntry.value})', const {}); - } - 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> _fontLoadingFutures = >[]; - - final List _downloadedFonts = []; // 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 _loadFontAsset( String family, String asset, Map 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 fontFaces = []; + final List errors = []; + 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 _loadFontFace( String family, String asset, Map descriptors, - ) { - Future 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 downloadAllFonts() async { - final List loadedFonts = await Future.wait(_fontLoadingFutures); - _downloadedFonts.addAll(loadedFonts.whereType()); - } - // Loads a font from bytes, surfacing errors through the future. - Future _loadFontFaceBytes(String family, Uint8List list) { + Future _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; } } diff --git a/lib/web_ui/skwasm/BUILD.gn b/lib/web_ui/skwasm/BUILD.gn index 7ad61d87fefcd..7fb55ceab5bc9 100644 --- a/lib/web_ui/skwasm/BUILD.gn +++ b/lib/web_ui/skwasm/BUILD.gn @@ -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", + ] } diff --git a/lib/web_ui/skwasm/canvas.cpp b/lib/web_ui/skwasm/canvas.cpp index 79cd4d2f06213..9a07aef09bba1 100644 --- a/lib/web_ui/skwasm/canvas.cpp +++ b/lib/web_ui/skwasm/canvas.cpp @@ -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 #include "export.h" #include "helpers.h" #include "wrappers.h" diff --git a/lib/web_ui/skwasm/contour_measure.cpp b/lib/web_ui/skwasm/contour_measure.cpp index fcc30ad407967..642d1fa36924a 100644 --- a/lib/web_ui/skwasm/contour_measure.cpp +++ b/lib/web_ui/skwasm/contour_measure.cpp @@ -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 #include "export.h" #include "helpers.h" diff --git a/lib/web_ui/skwasm/data.cpp b/lib/web_ui/skwasm/data.cpp new file mode 100644 index 0000000000000..9c5e860d6d635 --- /dev/null +++ b/lib/web_ui/skwasm/data.cpp @@ -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(); +} diff --git a/lib/web_ui/skwasm/export.h b/lib/web_ui/skwasm/export.h index cb0172e0bf621..fe4711aac20f6 100644 --- a/lib/web_ui/skwasm/export.h +++ b/lib/web_ui/skwasm/export.h @@ -4,4 +4,6 @@ #pragma once +#include + #define SKWASM_EXPORT extern "C" EMSCRIPTEN_KEEPALIVE diff --git a/lib/web_ui/skwasm/fonts.cpp b/lib/web_ui/skwasm/fonts.cpp new file mode 100644 index 0000000000000..22b66419cf8e9 --- /dev/null +++ b/lib/web_ui/skwasm/fonts.cpp @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "export.h" +#include "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 collection; + sk_sp provider; +}; + +SKWASM_EXPORT FlutterFontCollection* fontCollection_create() { + auto collection = sk_make_sp(); + auto provider = sk_make_sp(); + 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(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; +} diff --git a/lib/web_ui/skwasm/paint.cpp b/lib/web_ui/skwasm/paint.cpp index eb66fff6e292a..026a7a4d80b5f 100644 --- a/lib/web_ui/skwasm/paint.cpp +++ b/lib/web_ui/skwasm/paint.cpp @@ -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 #include "export.h" #include "helpers.h" #include "third_party/skia/include/core/SkPaint.h" diff --git a/lib/web_ui/skwasm/path.cpp b/lib/web_ui/skwasm/path.cpp index f8fe6e1452e30..63690f1370108 100644 --- a/lib/web_ui/skwasm/path.cpp +++ b/lib/web_ui/skwasm/path.cpp @@ -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 #include "export.h" #include "helpers.h" #include "third_party/skia/include/core/SkPath.h" diff --git a/lib/web_ui/skwasm/picture.cpp b/lib/web_ui/skwasm/picture.cpp index 2d80a985ab9bb..c12d7d42fc53b 100644 --- a/lib/web_ui/skwasm/picture.cpp +++ b/lib/web_ui/skwasm/picture.cpp @@ -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 #include "export.h" #include "helpers.h" #include "third_party/skia/include/core/SkPictureRecorder.h" diff --git a/lib/web_ui/skwasm/shaders.cpp b/lib/web_ui/skwasm/shaders.cpp index 1c29c10e844fb..5a521f8184512 100644 --- a/lib/web_ui/skwasm/shaders.cpp +++ b/lib/web_ui/skwasm/shaders.cpp @@ -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 #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, diff --git a/lib/web_ui/skwasm/string.cpp b/lib/web_ui/skwasm/string.cpp new file mode 100644 index 0000000000000..77e66d705dcfc --- /dev/null +++ b/lib/web_ui/skwasm/string.cpp @@ -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; +} diff --git a/lib/web_ui/test/canvaskit/canvas_golden_test.dart b/lib/web_ui/test/canvaskit/canvas_golden_test.dart index f581af87035d3..f58485f1f0124 100644 --- a/lib/web_ui/test/canvaskit/canvas_golden_test.dart +++ b/lib/web_ui/test/canvaskit/canvas_golden_test.dart @@ -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({ - 'useColorEmoji': true, - }) as JsFlutterConfiguration); - debugSetConfiguration(config); - - FontFallbackData.debugReset(); notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'; }); diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index 36db2c0003938..b37d01076dc44 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -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 diff --git a/lib/web_ui/test/canvaskit/common.dart b/lib/web_ui/test/canvaskit/common.dart index 407e0b4226609..842d3a4a70a92 100644 --- a/lib/web_ui/test/canvaskit/common.dart +++ b/lib/web_ui/test/canvaskit/common.dart @@ -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(), 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(() { diff --git a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart b/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart index a55382a27b9a3..f8bff49380d7f 100644 --- a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart +++ b/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart @@ -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({ - 'useColorEmoji': true, - }) as JsFlutterConfiguration); - debugSetConfiguration(config); - FontFallbackData.debugReset(); notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'; savedCallback = ui.window.onPlatformMessage; diff --git a/lib/web_ui/test/canvaskit/skia_font_collection_test.dart b/lib/web_ui/test/canvaskit/skia_font_collection_test.dart index 75764a8c35f4f..1527e707fab91 100644 --- a/lib/web_ui/test/canvaskit/skia_font_collection_test.dart +++ b/lib/web_ui/test/canvaskit/skia_font_collection_test.dart @@ -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 warnings = []; 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([ - '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'); }); diff --git a/lib/web_ui/test/common/fake_asset_manager.dart b/lib/web_ui/test/common/fake_asset_manager.dart new file mode 100644 index 0000000000000..2aea72c30315d --- /dev/null +++ b/lib/web_ui/test/common/fake_asset_manager.dart @@ -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 load(String assetKey) async { + final ByteData? data = _assetMap[assetKey]; + if (data == null) { + throw HttpFetchNoPayloadError(assetKey, status: 404); + } + return data; + } + + @override + Future 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 _assetMap = {}; +} + +class FakeAssetScope { + FakeAssetScope._(this._parent); + + final FakeAssetScope? _parent; + final Map Function()> _assetFetcherMap = 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? getAssetData(String assetKey) { + final Future 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 testFontUrls = { + 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; +} diff --git a/lib/web_ui/test/common/test_initialization.dart b/lib/web_ui/test/common/test_initialization.dart new file mode 100644 index 0000000000000..812210624111e --- /dev/null +++ b/lib/web_ui/test/common/test_initialization.dart @@ -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({ + '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); + }); +} diff --git a/lib/web_ui/test/engine/clipboard_test.dart b/lib/web_ui/test/engine/clipboard_test.dart index 48578bf246b5b..baeca91df6c7e 100644 --- a/lib/web_ui/test/engine/clipboard_test.dart +++ b/lib/web_ui/test/engine/clipboard_test.dart @@ -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 testMain() async { - await initializeTestFlutterViewEmbedder(); + setUpUnitTests(); group('message handler', () { const String testText = 'test text'; diff --git a/lib/web_ui/test/engine/dom_http_fetch_test.dart b/lib/web_ui/test/engine/dom_http_fetch_test.dart index 1d1b8628ccdc5..606753ba4636b 100644 --- a/lib/web_ui/test/engine/dom_http_fetch_test.dart +++ b/lib/web_ui/test/engine/dom_http_fetch_test.dart @@ -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 _testSuccessfulPayloads() async { expect(response.url, url); final List result = []; - await response.payload.read(result.addAll); + await response.payload.read((JSUint8Array chunk) => result.addAll(chunk.toDart)); expect(result, hasLength(length)); expect( result, diff --git a/lib/web_ui/test/engine/image/html_image_codec_test.dart b/lib/web_ui/test/engine/image/html_image_codec_test.dart index 9017fde35d081..1e0381ee0ec5f 100644 --- a/lib/web_ui/test/engine/image/html_image_codec_test.dart +++ b/lib/web_ui/test/engine/image/html_image_codec_test.dart @@ -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 testMain() async { - await initializeTestFlutterViewEmbedder(); + setUpUnitTests(); group('HtmCodec', () { test('supports raw images - RGBA8888', () async { final Completer completer = Completer(); diff --git a/lib/web_ui/test/engine/image_to_byte_data_test.dart b/lib/web_ui/test/engine/image_to_byte_data_test.dart index a9bb8a791f2ea..306e50c560ec2 100644 --- a/lib/web_ui/test/engine/image_to_byte_data_test.dart +++ b/lib/web_ui/test/engine/image_to_byte_data_test.dart @@ -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 testMain() async { - setUpAll(() async { - await webOnlyInitializePlatform(); - await renderer.fontCollection.debugDownloadTestFonts(); - renderer.fontCollection.registerDownloadedFonts(); - }); + setUpUnitTests(); Future createTestImageByColor(Color color) async { final EnginePictureRecorder recorder = EnginePictureRecorder(); diff --git a/lib/web_ui/test/engine/recording_canvas_test.dart b/lib/web_ui/test/engine/recording_canvas_test.dart index 72c63f4143fa6..6ff41a2bff902 100644 --- a/lib/web_ui/test/engine/recording_canvas_test.dart +++ b/lib/web_ui/test/engine/recording_canvas_test.dart @@ -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; diff --git a/lib/web_ui/test/engine/text_editing_test.dart b/lib/web_ui/test/engine/text_editing_test.dart index 4e6a3763063a4..c3c00a07c1c43 100644 --- a/lib/web_ui/test/engine/text_editing_test.dart +++ b/lib/web_ui/test/engine/text_editing_test.dart @@ -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 testMain() async { - await initializeEngine(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false + ); tearDown(() { lastEditingState = null; diff --git a/lib/web_ui/test/html/bitmap_canvas_golden_test.dart b/lib/web_ui/test/html/bitmap_canvas_golden_test.dart index 1852988f64029..1ede2389e02ba 100644 --- a/lib/web_ui/test/html/bitmap_canvas_golden_test.dart +++ b/lib/web_ui/test/html/bitmap_canvas_golden_test.dart @@ -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 testMain() async { flutterViewEmbedder.glassPaneShadow.querySelector('flt-scene-host')!.append(testScene); } - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); tearDown(() { flutterViewEmbedder.glassPaneShadow.querySelector('flt-scene')?.remove(); diff --git a/lib/web_ui/test/html/canvas_clip_path_golden_test.dart b/lib/web_ui/test/html/canvas_clip_path_golden_test.dart index 7242600b361e6..a0ea6e9c4adf4 100644 --- a/lib/web_ui/test/html/canvas_clip_path_golden_test.dart +++ b/lib/web_ui/test/html/canvas_clip_path_golden_test.dart @@ -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 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. diff --git a/lib/web_ui/test/html/canvas_context_golden_test.dart b/lib/web_ui/test/html/canvas_context_golden_test.dart index 53a9d30a2e201..282bd2590d712 100644 --- a/lib/web_ui/test/html/canvas_context_golden_test.dart +++ b/lib/web_ui/test/html/canvas_context_golden_test.dart @@ -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 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. diff --git a/lib/web_ui/test/html/canvas_reuse_golden_test.dart b/lib/web_ui/test/html/canvas_reuse_golden_test.dart index 0757f0851e879..ded21b4350e42 100644 --- a/lib/web_ui/test/html/canvas_reuse_golden_test.dart +++ b/lib/web_ui/test/html/canvas_reuse_golden_test.dart @@ -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 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 { diff --git a/lib/web_ui/test/html/clip_op_golden_test.dart b/lib/web_ui/test/html/clip_op_golden_test.dart index 2d2b34535dc61..9153643f944d3 100644 --- a/lib/web_ui/test/html/clip_op_golden_test.dart +++ b/lib/web_ui/test/html/clip_op_golden_test.dart @@ -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 testMain() async { - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); /// Regression test for https://github.com/flutter/flutter/issues/64734. test('Clips using difference', () async { diff --git a/lib/web_ui/test/html/compositing/backdrop_filter_golden_test.dart b/lib/web_ui/test/html/compositing/backdrop_filter_golden_test.dart index 41743319b73ef..dd7d329f2ac83 100644 --- a/lib/web_ui/test/html/compositing/backdrop_filter_golden_test.dart +++ b/lib/web_ui/test/html/compositing/backdrop_filter_golden_test.dart @@ -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 testMain() async { - setUpAll(() async { - await webOnlyInitializePlatform(); - await renderer.fontCollection.debugDownloadTestFonts(); - renderer.fontCollection.registerDownloadedFonts(); - }); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); setUp(() async { debugShowClipLayers = true; diff --git a/lib/web_ui/test/html/compositing/canvas_blend_golden_test.dart b/lib/web_ui/test/html/compositing/canvas_blend_golden_test.dart index b1851aa4e51b6..816219f879e7e 100644 --- a/lib/web_ui/test/html/compositing/canvas_blend_golden_test.dart +++ b/lib/web_ui/test/html/compositing/canvas_blend_golden_test.dart @@ -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 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 = diff --git a/lib/web_ui/test/html/compositing/canvas_image_blend_mode_golden_test.dart b/lib/web_ui/test/html/compositing/canvas_image_blend_mode_golden_test.dart index 3b985102d4f4c..69a92c88a0689 100644 --- a/lib/web_ui/test/html/compositing/canvas_image_blend_mode_golden_test.dart +++ b/lib/web_ui/test/html/compositing/canvas_image_blend_mode_golden_test.dart @@ -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 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); diff --git a/lib/web_ui/test/html/compositing/canvas_mask_filter_golden_test.dart b/lib/web_ui/test/html/compositing/canvas_mask_filter_golden_test.dart index 03e8b8d2ccf5b..2dda8c5d48009 100644 --- a/lib/web_ui/test/html/compositing/canvas_mask_filter_golden_test.dart +++ b/lib/web_ui/test/html/compositing/canvas_mask_filter_golden_test.dart @@ -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 testMain() async { - setUpAll(() async { - ui.debugEmulateFlutterTesterEnvironment = true; - await ui.webOnlyInitializePlatform(); - await renderer.fontCollection.debugDownloadTestFonts(); - renderer.fontCollection.registerDownloadedFonts(); - }); + setUpUnitTests( + setUpTestViewDimensions: false, + ); tearDown(() { ContextStateHandle.debugEmulateWebKitMaskFilter = false; diff --git a/lib/web_ui/test/html/compositing/color_filter_golden_test.dart b/lib/web_ui/test/html/compositing/color_filter_golden_test.dart index a522d59684e40..faf6260620e75 100644 --- a/lib/web_ui/test/html/compositing/color_filter_golden_test.dart +++ b/lib/web_ui/test/html/compositing/color_filter_golden_test.dart @@ -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 testMain() async { - setUpAll(() async { - await webOnlyInitializePlatform(); - await renderer.fontCollection.debugDownloadTestFonts(); - renderer.fontCollection.registerDownloadedFonts(); - }); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); setUp(() async { debugShowClipLayers = true; diff --git a/lib/web_ui/test/html/compositing/compositing_golden_test.dart b/lib/web_ui/test/html/compositing/compositing_golden_test.dart index bc86da523bf1d..6881c47a2e996 100644 --- a/lib/web_ui/test/html/compositing/compositing_golden_test.dart +++ b/lib/web_ui/test/html/compositing/compositing_golden_test.dart @@ -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 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 diff --git a/lib/web_ui/test/html/compositing/dom_mask_filter_golden_test.dart b/lib/web_ui/test/html/compositing/dom_mask_filter_golden_test.dart index bf7e63d20fb0e..db41df3e346ad 100644 --- a/lib/web_ui/test/html/compositing/dom_mask_filter_golden_test.dart +++ b/lib/web_ui/test/html/compositing/dom_mask_filter_golden_test.dart @@ -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 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 = diff --git a/lib/web_ui/test/html/drawing/canvas_draw_color_golden_test.dart b/lib/web_ui/test/html/drawing/canvas_draw_color_golden_test.dart index 8b26b7342f6c1..14be3d83c1914 100644 --- a/lib/web_ui/test/html/drawing/canvas_draw_color_golden_test.dart +++ b/lib/web_ui/test/html/drawing/canvas_draw_color_golden_test.dart @@ -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 testMain() async { + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); + setUp(() async { debugShowClipLayers = true; SurfaceSceneBuilder.debugForgetFrameScene(); - await webOnlyInitializePlatform(); - await renderer.fontCollection.debugDownloadTestFonts(); - renderer.fontCollection.registerDownloadedFonts(); }); tearDown(() { diff --git a/lib/web_ui/test/html/drawing/canvas_draw_image_golden_test.dart b/lib/web_ui/test/html/drawing/canvas_draw_image_golden_test.dart index ad86f7ad77b9e..a3bfcca3e633c 100644 --- a/lib/web_ui/test/html/drawing/canvas_draw_image_golden_test.dart +++ b/lib/web_ui/test/html/drawing/canvas_draw_image_golden_test.dart @@ -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 testMain() async { - - setUp(() async { - debugEmulateFlutterTesterEnvironment = true; - }); - - setUpStableTestFonts(); + setUpUnitTests( + setUpTestViewDimensions: false, + ); test('Paints image', () async { final RecordingCanvas rc = diff --git a/lib/web_ui/test/html/drawing/canvas_draw_picture_golden_test.dart b/lib/web_ui/test/html/drawing/canvas_draw_picture_golden_test.dart index a2cfffb29e7b2..6b6974b937ceb 100644 --- a/lib/web_ui/test/html/drawing/canvas_draw_picture_golden_test.dart +++ b/lib/web_ui/test/html/drawing/canvas_draw_picture_golden_test.dart @@ -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 testMain() async { + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); + setUpAll(() async { debugShowClipLayers = true; - await webOnlyInitializePlatform(); - await renderer.fontCollection.debugDownloadTestFonts(); - renderer.fontCollection.registerDownloadedFonts(); }); setUp(() async { diff --git a/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart b/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart index 4790ff9bd4e43..94548a1745aad 100644 --- a/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart +++ b/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart @@ -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 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(); diff --git a/lib/web_ui/test/html/paragraph/bidi_golden_test.dart b/lib/web_ui/test/html/paragraph/bidi_golden_test.dart index 8f1c6f2060568..07a7f91c49f1d 100644 --- a/lib/web_ui/test/html/paragraph/bidi_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/bidi_golden_test.dart @@ -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 testMain() async { - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); void paintBasicBidiStartingWithLtr( EngineCanvas canvas, diff --git a/lib/web_ui/test/html/paragraph/general_golden_test.dart b/lib/web_ui/test/html/paragraph/general_golden_test.dart index 82d2e4da230f0..34325eb7b7ab1 100644 --- a/lib/web_ui/test/html/paragraph/general_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/general_golden_test.dart @@ -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 testMain() async { - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); test('paints spans and lines correctly', () { final BitmapCanvas canvas = BitmapCanvas(bounds, RenderStrategy()); diff --git a/lib/web_ui/test/html/paragraph/justify_golden_test.dart b/lib/web_ui/test/html/paragraph/justify_golden_test.dart index 00a0033df3b0a..148274ea4f01e 100644 --- a/lib/web_ui/test/html/paragraph/justify_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/justify_golden_test.dart @@ -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 testMain() async { - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); void testJustifyWithMultipleSpans(EngineCanvas canvas) { void build(CanvasParagraphBuilder builder) { diff --git a/lib/web_ui/test/html/paragraph/overflow_golden_test.dart b/lib/web_ui/test/html/paragraph/overflow_golden_test.dart index eebfde0c1489e..ee787940ef557 100644 --- a/lib/web_ui/test/html/paragraph/overflow_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/overflow_golden_test.dart @@ -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 testMain() async { - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); void testEllipsis(EngineCanvas canvas) { Offset offset = Offset.zero; diff --git a/lib/web_ui/test/html/paragraph/placeholders_golden_test.dart b/lib/web_ui/test/html/paragraph/placeholders_golden_test.dart index 7f90cde8a3243..cf9cdc8372166 100644 --- a/lib/web_ui/test/html/paragraph/placeholders_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/placeholders_golden_test.dart @@ -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 testMain() async { - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); test('draws paragraphs with placeholders', () { final BitmapCanvas canvas = BitmapCanvas(bounds, RenderStrategy()); diff --git a/lib/web_ui/test/html/paragraph/shadows_golden_test.dart b/lib/web_ui/test/html/paragraph/shadows_golden_test.dart index 3c6f30fec8fcd..f5119e1c62209 100644 --- a/lib/web_ui/test/html/paragraph/shadows_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/shadows_golden_test.dart @@ -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 testMain() async { - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); test('paints multiple shadows', () { final BitmapCanvas canvas = BitmapCanvas(bounds, RenderStrategy()); diff --git a/lib/web_ui/test/html/paragraph/text_multiline_clipping_golden_test.dart b/lib/web_ui/test/html/paragraph/text_multiline_clipping_golden_test.dart index a4164b2dd6b35..3350c212c97a3 100644 --- a/lib/web_ui/test/html/paragraph/text_multiline_clipping_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/text_multiline_clipping_golden_test.dart @@ -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 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); diff --git a/lib/web_ui/test/html/paragraph/text_overflow_golden_test.dart b/lib/web_ui/test/html/paragraph/text_overflow_golden_test.dart index a97bce79940f6..6d922396d99fe 100644 --- a/lib/web_ui/test/html/paragraph/text_overflow_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/text_overflow_golden_test.dart @@ -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 testMain() async { viewportSize: const Size(800, 800), ); - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); testEachCanvas('maxLines clipping', (EngineCanvas canvas) { Offset offset = Offset.zero; diff --git a/lib/web_ui/test/html/paragraph/text_placeholders_golden_test.dart b/lib/web_ui/test/html/paragraph/text_placeholders_golden_test.dart index 4474bba0b9f33..15a961ced6343 100644 --- a/lib/web_ui/test/html/paragraph/text_placeholders_golden_test.dart +++ b/lib/web_ui/test/html/paragraph/text_placeholders_golden_test.dart @@ -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 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); diff --git a/lib/web_ui/test/html/path_metrics_golden_test.dart b/lib/web_ui/test/html/path_metrics_golden_test.dart index 4a3ea96f7288d..6ce6bd660da06 100644 --- a/lib/web_ui/test/html/path_metrics_golden_test.dart +++ b/lib/web_ui/test/html/path_metrics_golden_test.dart @@ -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 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(); diff --git a/lib/web_ui/test/html/path_transform_golden_test.dart b/lib/web_ui/test/html/path_transform_golden_test.dart index cb5b398169540..68a2b417c0a22 100644 --- a/lib/web_ui/test/html/path_transform_golden_test.dart +++ b/lib/web_ui/test/html/path_transform_golden_test.dart @@ -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 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 = diff --git a/lib/web_ui/test/html/recording_canvas_golden_test.dart b/lib/web_ui/test/html/recording_canvas_golden_test.dart index 3a32e02830ed7..47fa8dabd0f5b 100644 --- a/lib/web_ui/test/html/recording_canvas_golden_test.dart +++ b/lib/web_ui/test/html/recording_canvas_golden_test.dart @@ -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 testMain() async { - setUpAll(() async { - debugEmulateFlutterTesterEnvironment = true; - }); - - setUpStableTestFonts(); + setUpUnitTests( + setUpTestViewDimensions: false, + ); const double screenWidth = 600.0; const double screenHeight = 800.0; diff --git a/lib/web_ui/test/html/screenshot.dart b/lib/web_ui/test/html/screenshot.dart index 3e2226ae68803..445744c51cfb4 100644 --- a/lib/web_ui/test/html/screenshot.dart +++ b/lib/web_ui/test/html/screenshot.dart @@ -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 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(); - }); -} diff --git a/lib/web_ui/test/html/shaders/gradient_golden_test.dart b/lib/web_ui/test/html/shaders/gradient_golden_test.dart index 5aebc78ef0070..8dfa8fc66503f 100644 --- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -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 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 = diff --git a/lib/web_ui/test/html/shaders/image_shader_golden_test.dart b/lib/web_ui/test/html/shaders/image_shader_golden_test.dart index 405250062d462..ba7d7fc52ab23 100644 --- a/lib/web_ui/test/html/shaders/image_shader_golden_test.dart +++ b/lib/web_ui/test/html/shaders/image_shader_golden_test.dart @@ -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 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. diff --git a/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart b/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart index 742067e4914e0..bf1ab3752cebd 100644 --- a/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart @@ -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 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 = diff --git a/lib/web_ui/test/html/shaders/radial_gradient_golden_test.dart b/lib/web_ui/test/html/shaders/radial_gradient_golden_test.dart index 51bfab70965a1..27f64b3022a7a 100644 --- a/lib/web_ui/test/html/shaders/radial_gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/radial_gradient_golden_test.dart @@ -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 testMain() async { - - setUpAll(() async { - debugEmulateFlutterTesterEnvironment = true; - await webOnlyInitializePlatform(); - await renderer.fontCollection.debugDownloadTestFonts(); - renderer.fontCollection.registerDownloadedFonts(); - }); + setUpUnitTests( + setUpTestViewDimensions: false, + ); Future testGradient(String fileName, Shader shader, {Rect paintRect = const Rect.fromLTRB(50, 50, 300, 300), diff --git a/lib/web_ui/test/html/shaders/shader_mask_golden_test.dart b/lib/web_ui/test/html/shaders/shader_mask_golden_test.dart index 6c11494b9f8f0..b2fd11110fd59 100644 --- a/lib/web_ui/test/html/shaders/shader_mask_golden_test.dart +++ b/lib/web_ui/test/html/shaders/shader_mask_golden_test.dart @@ -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 main() async { // https://github.com/flutter/flutter/issues/86623 Future testMain() async { + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); + setUpAll(() async { debugShowClipLayers = true; - await webOnlyInitializePlatform(); }); setUp(() async { @@ -41,8 +47,6 @@ Future testMain() async { scene.remove(); } initWebGl(); - await renderer.fontCollection.debugDownloadTestFonts(); - renderer.fontCollection.registerDownloadedFonts(); }); /// Should render the picture unmodified. diff --git a/lib/web_ui/test/html/shadow_golden_test.dart b/lib/web_ui/test/html/shadow_golden_test.dart index 7d9e1e04308e9..ca0b7ccee366b 100644 --- a/lib/web_ui/test/html/shadow_golden_test.dart +++ b/lib/web_ui/test/html/shadow_golden_test.dart @@ -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 testMain() async { late SurfaceSceneBuilder builder; - setUpStableTestFonts(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); setUp(() { builder = SurfaceSceneBuilder(); diff --git a/lib/web_ui/test/text/canvas_paragraph_builder_test.dart b/lib/web_ui/test/html/text/canvas_paragraph_builder_test.dart similarity index 99% rename from lib/web_ui/test/text/canvas_paragraph_builder_test.dart rename to lib/web_ui/test/html/text/canvas_paragraph_builder_test.dart index 0989d248bdb53..7d51f2b4964f6 100644 --- a/lib/web_ui/test/text/canvas_paragraph_builder_test.dart +++ b/lib/web_ui/test/html/text/canvas_paragraph_builder_test.dart @@ -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 testMain() async { - await initializeTestFlutterViewEmbedder(); + setUpUnitTests(); test('empty paragraph', () { final CanvasParagraph paragraph1 = rich( diff --git a/lib/web_ui/test/text/canvas_paragraph_test.dart b/lib/web_ui/test/html/text/canvas_paragraph_test.dart similarity index 99% rename from lib/web_ui/test/text/canvas_paragraph_test.dart rename to lib/web_ui/test/html/text/canvas_paragraph_test.dart index c7dbbecabf059..dd652d32d82f7 100644 --- a/lib/web_ui/test/text/canvas_paragraph_test.dart +++ b/lib/web_ui/test/html/text/canvas_paragraph_test.dart @@ -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 testMain() async { - await initializeTestFlutterViewEmbedder(); + setUpUnitTests(); group('$CanvasParagraph.getBoxesForRange', () { test('return empty list for invalid ranges', () { diff --git a/lib/web_ui/test/text/font_collection_test.dart b/lib/web_ui/test/html/text/font_collection_test.dart similarity index 69% rename from lib/web_ui/test/text/font_collection_test.dart rename to lib/web_ui/test/html/text/font_collection_test.dart index caf27b9c722cf..3f7485ff71c40 100644 --- a/lib/web_ui/test/text/font_collection_test.dart +++ b/lib/web_ui/test/html/text/font_collection_test.dart @@ -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 fontFamilyList = []; - - fontManager.downloadAsset( - testFontFamily, 'url($testFontUrl)', const {}); - await fontManager.downloadAllFonts(); - fontManager.registerDownloadedFonts(); + final HtmlFontCollection collection = HtmlFontCollection(); + await collection.loadAssetFonts(FontManifest([ + FontFamily(testFontFamily, [ + FontAsset(testFontUrl, {}) + ]) + ])); 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 fontFamilyList = []; - fontManager.downloadAsset( - testFontFamily, 'url($testFontUrl)', const {}); - await fontManager.downloadAllFonts(); - fontManager.registerDownloadedFonts(); + final HtmlFontCollection collection = HtmlFontCollection(); + await collection.loadAssetFonts(FontManifest([ + FontFamily(testFontFamily, [ + FontAsset(testFontUrl, {}) + ]) + ])); 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 fontFamilyList = []; - fontManager.downloadAsset( - testFontFamily, 'url($testFontUrl)', const {}); - await fontManager.downloadAllFonts(); - fontManager.registerDownloadedFonts(); + final HtmlFontCollection collection = HtmlFontCollection(); + await collection.loadAssetFonts(FontManifest([ + FontFamily(testFontFamily, [ + FontAsset(testFontUrl, {}) + ]) + ])); 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 fontFamilyList = []; + final HtmlFontCollection collection = HtmlFontCollection(); + await collection.loadAssetFonts(FontManifest([ + FontFamily(testFontFamily, [ + FontAsset(testFontUrl, { + 'weight': 'bold' + }) + ]) + ])); - fontManager.downloadAsset( - testFontFamily, 'url($testFontUrl)', const { - '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 fontFamilyList = []; - fontManager.downloadAsset( - testFontFamily, 'url($testFontUrl)', const {}); - await fontManager.downloadAllFonts(); - fontManager.registerDownloadedFonts(); + final HtmlFontCollection collection = HtmlFontCollection(); + await collection.loadAssetFonts(FontManifest([ + FontFamily(testFontFamily, [ + FontAsset(testFontUrl, {}) + ]) + ])); 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 fontFamilyList = []; - fontManager.downloadAsset( - testFontFamily, 'url($testFontUrl)', const {}); - await fontManager.downloadAllFonts(); - fontManager.registerDownloadedFonts(); + final HtmlFontCollection collection = HtmlFontCollection(); + await collection.loadAssetFonts(FontManifest([ + FontFamily(testFontFamily, [ + FontAsset(testFontUrl, {}) + ]) + ])); 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 fontFamilyList = []; - fontManager.downloadAsset( - testFontFamily, 'url($testFontUrl)', const {}); - await fontManager.downloadAllFonts(); - fontManager.registerDownloadedFonts(); + final HtmlFontCollection collection = HtmlFontCollection(); + await collection.loadAssetFonts(FontManifest([ + FontFamily(testFontFamily, [ + FontAsset(testFontUrl, {}) + ]) + ])); 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 fontFamilyList = []; - fontManager.downloadAsset( - testFontFamily, 'url($testFontUrl)', const {}); - await fontManager.downloadAllFonts(); - fontManager.registerDownloadedFonts(); + final HtmlFontCollection collection = HtmlFontCollection(); + await collection.loadAssetFonts(FontManifest([ + FontFamily(testFontFamily, [ + FontAsset(testFontUrl, {}) + ]) + ])); domDocument.fonts! .forEach((DomFontFace f, DomFontFace f2, DomFontFaceSet s) { fontFamilyList.add(f.family!); diff --git a/lib/web_ui/test/text/font_loading_test.dart b/lib/web_ui/test/html/text/font_loading_test.dart similarity index 93% rename from lib/web_ui/test/text/font_loading_test.dart rename to lib/web_ui/test/html/text/font_loading_test.dart index d96ca6acb22f0..702c70b66ff3d 100644 --- a/lib/web_ui/test/text/font_loading_test.dart +++ b/lib/web_ui/test/html/text/font_loading_test.dart @@ -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 testMain() async { - await initializeTestFlutterViewEmbedder(); + setUpUnitTests(); group('loadFontFromList', () { const String testFontUrl = '/assets/fonts/ahem.ttf'; @@ -24,10 +26,11 @@ Future 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())); + () async => ui.loadFontFromList(Uint8List(0), fontFamily: 'test-font'), + returnsNormally + ); }, // TODO(hterkelsen): https://github.com/flutter/flutter/issues/56702 skip: browserEngine == BrowserEngine.webkit); diff --git a/lib/web_ui/test/text/layout_fragmenter_test.dart b/lib/web_ui/test/html/text/layout_fragmenter_test.dart similarity index 99% rename from lib/web_ui/test/text/layout_fragmenter_test.dart rename to lib/web_ui/test/html/text/layout_fragmenter_test.dart index a36dc89433db7..fe8a8ca5a2852 100644 --- a/lib/web_ui/test/text/layout_fragmenter_test.dart +++ b/lib/web_ui/test/html/text/layout_fragmenter_test.dart @@ -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), diff --git a/lib/web_ui/test/text/layout_service_helper.dart b/lib/web_ui/test/html/text/layout_service_helper.dart similarity index 100% rename from lib/web_ui/test/text/layout_service_helper.dart rename to lib/web_ui/test/html/text/layout_service_helper.dart diff --git a/lib/web_ui/test/text/layout_service_plain_test.dart b/lib/web_ui/test/html/text/layout_service_plain_test.dart similarity index 99% rename from lib/web_ui/test/text/layout_service_plain_test.dart rename to lib/web_ui/test/html/text/layout_service_plain_test.dart index 96c3e047cecd3..d08d88b15e493 100644 --- a/lib/web_ui/test/text/layout_service_plain_test.dart +++ b/lib/web_ui/test/html/text/layout_service_plain_test.dart @@ -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 testMain() async { - await initializeTestFlutterViewEmbedder(); + setUpUnitTests(); test('no text', () { final CanvasParagraph paragraph = CanvasParagraphBuilder(ahemStyle).build(); diff --git a/lib/web_ui/test/text/layout_service_rich_test.dart b/lib/web_ui/test/html/text/layout_service_rich_test.dart similarity index 98% rename from lib/web_ui/test/text/layout_service_rich_test.dart rename to lib/web_ui/test/html/text/layout_service_rich_test.dart index 381da5e228183..2d6173caa0ad1 100644 --- a/lib/web_ui/test/text/layout_service_rich_test.dart +++ b/lib/web_ui/test/html/text/layout_service_rich_test.dart @@ -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 testMain() async { - await initializeTestFlutterViewEmbedder(); + setUpUnitTests(); test('does not crash on empty spans', () { final CanvasParagraph paragraph = rich(ahemStyle, (CanvasParagraphBuilder builder) { diff --git a/lib/web_ui/test/text/line_breaker_test.dart b/lib/web_ui/test/html/text/line_breaker_test.dart similarity index 99% rename from lib/web_ui/test/text/line_breaker_test.dart rename to lib/web_ui/test/html/text/line_breaker_test.dart index 275c4de47b60f..2bd99fcc20e4d 100644 --- a/lib/web_ui/test/text/line_breaker_test.dart +++ b/lib/web_ui/test/html/text/line_breaker_test.dart @@ -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'; diff --git a/lib/web_ui/test/text/line_breaker_test_helper.dart b/lib/web_ui/test/html/text/line_breaker_test_helper.dart similarity index 100% rename from lib/web_ui/test/text/line_breaker_test_helper.dart rename to lib/web_ui/test/html/text/line_breaker_test_helper.dart diff --git a/lib/web_ui/test/text/line_breaker_test_raw_data.dart b/lib/web_ui/test/html/text/line_breaker_test_raw_data.dart similarity index 100% rename from lib/web_ui/test/text/line_breaker_test_raw_data.dart rename to lib/web_ui/test/html/text/line_breaker_test_raw_data.dart diff --git a/lib/web_ui/test/text/text_direction_test.dart b/lib/web_ui/test/html/text/text_direction_test.dart similarity index 99% rename from lib/web_ui/test/text/text_direction_test.dart rename to lib/web_ui/test/html/text/text_direction_test.dart index 3c39220c0159e..9049c0948ef54 100644 --- a/lib/web_ui/test/text/text_direction_test.dart +++ b/lib/web_ui/test/html/text/text_direction_test.dart @@ -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); diff --git a/lib/web_ui/test/text/word_breaker_test.dart b/lib/web_ui/test/html/text/word_breaker_test.dart similarity index 100% rename from lib/web_ui/test/text/word_breaker_test.dart rename to lib/web_ui/test/html/text/word_breaker_test.dart diff --git a/lib/web_ui/test/html/text_test.dart b/lib/web_ui/test/html/text_test.dart index 2b7e629fb2917..72a624a6335c0 100644 --- a/lib/web_ui/test/html/text_test.dart +++ b/lib/web_ui/test/html/text_test.dart @@ -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 testMain() async { const double baselineRatio = 1.1662499904632568; - await initializeTestFlutterViewEmbedder(); + setUpUnitTests(); late String fallback; setUp(() { diff --git a/lib/web_ui/test/ui/README.md b/lib/web_ui/test/ui/README.md index acd49655295e0..9ce5ea6d15f44 100644 --- a/lib/web_ui/test/ui/README.md +++ b/lib/web_ui/test/ui/README.md @@ -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. diff --git a/lib/web_ui/test/ui/canvas_curves_golden_test.dart b/lib/web_ui/test/ui/canvas_curves_golden_test.dart index e067e9f944b8f..f96de710c13ba 100644 --- a/lib/web_ui/test/ui/canvas_curves_golden_test.dart +++ b/lib/web_ui/test/ui/canvas_curves_golden_test.dart @@ -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 testMain() async { - setUpUiTest(); + setUpUnitTests( + setUpTestViewDimensions: false, + ); const Rect region = Rect.fromLTWH(0, 0, 300, 300); diff --git a/lib/web_ui/test/ui/canvas_lines_golden_test.dart b/lib/web_ui/test/ui/canvas_lines_golden_test.dart index df3c00bee99a0..58c7abe6d7689 100644 --- a/lib/web_ui/test/ui/canvas_lines_golden_test.dart +++ b/lib/web_ui/test/ui/canvas_lines_golden_test.dart @@ -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 testMain() async { - setUpUiTest(); + setUpUnitTests( + setUpTestViewDimensions: false, + ); const Rect region = Rect.fromLTWH(0, 0, 300, 300); diff --git a/lib/web_ui/test/ui/canvas_test.dart b/lib/web_ui/test/ui/canvas_test.dart index 1cb3db0ac3a99..484a40f1be63b 100644 --- a/lib/web_ui/test/ui/canvas_test.dart +++ b/lib/web_ui/test/ui/canvas_test.dart @@ -11,14 +11,16 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import '../common/matchers.dart'; -import 'utils.dart'; +import '../common/test_initialization.dart'; void main() { internalBootstrapBrowserTest(() => testMain); } Future testMain() async { - setUpUiTest(); + setUpUnitTests( + setUpTestViewDimensions: false, + ); final bool deviceClipRoundsOut = renderer is! HtmlRenderer; runCanvasTests(deviceClipRoundsOut: deviceClipRoundsOut); diff --git a/lib/web_ui/test/ui/color_test.dart b/lib/web_ui/test/ui/color_test.dart index 7236628b1f6cf..2ac2df7595d02 100644 --- a/lib/web_ui/test/ui/color_test.dart +++ b/lib/web_ui/test/ui/color_test.dart @@ -6,7 +6,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/ui.dart'; -import 'utils.dart'; +import '../common/test_initialization.dart'; void main() { internalBootstrapBrowserTest(() => testMain); @@ -17,7 +17,7 @@ class NotAColor extends Color { } Future testMain() async { - setUpUiTest(); + setUpUnitTests(); test('color accessors should work', () { const Color foo = Color(0x12345678); diff --git a/lib/web_ui/test/ui/font_collection_test.dart b/lib/web_ui/test/ui/font_collection_test.dart new file mode 100644 index 0000000000000..a999bedd15b4d --- /dev/null +++ b/lib/web_ui/test/ui/font_collection_test.dart @@ -0,0 +1,156 @@ +// 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:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; + +import '../common/fake_asset_manager.dart'; +import '../common/test_initialization.dart'; +import 'utils.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +Future testMain() async { + setUpUnitTests(); + + late FakeAssetScope testScope; + setUp(() { + mockHttpFetchResponseFactory = null; + testScope = fakeAssetManager.pushAssetScope(); + }); + + tearDown(() { + fakeAssetManager.popAssetScope(testScope); + }); + + test('Loading valid font from data succeeds without family name (except in HTML renderer)', () async { + final FlutterFontCollection collection = renderer.fontCollection; + final ByteBuffer ahemData = await httpFetchByteBuffer('/assets/fonts/ahem.ttf'); + expect( + await collection.loadFontFromList(ahemData.asUint8List()), + !isHtml, // HtmlFontCollection requires family name + ); + }); + + test('Loading valid font from data succeeds with family name', () async { + final FlutterFontCollection collection = renderer.fontCollection; + final ByteBuffer ahemData = await httpFetchByteBuffer('/assets/fonts/ahem.ttf'); + expect( + await collection.loadFontFromList(ahemData.asUint8List(), fontFamily: 'FamilyName'), + true + ); + }); + + test('Loading invalid font from data returns false', () async { + final FlutterFontCollection collection = renderer.fontCollection; + final List invalidFontData = utf8.encode('This is not valid font data'); + expect( + await collection.loadFontFromList(Uint8List.fromList(invalidFontData), fontFamily: 'FamilyName'), + false + ); + }); + + test('Loading valid asset fonts succeds', () async { + testScope.setAssetPassthrough(robotoVariableFontUrl); + testScope.setAssetPassthrough(robotoTestFontUrl); + testScope.setAssetPassthrough(ahemFontUrl); + + final FlutterFontCollection collection = renderer.fontCollection; + final AssetFontsResult result = await collection.loadAssetFonts(FontManifest([ + FontFamily(robotoFontFamily, [ + FontAsset(robotoVariableFontUrl, {}), + FontAsset(robotoTestFontUrl, {'weight': 'bold'}), + ]), + FontFamily(ahemFontFamily, [ + FontAsset(ahemFontUrl, {}) + ]), + ])); + expect(result.loadedFonts, [ + robotoVariableFontUrl, + robotoTestFontUrl, + ahemFontUrl, + ]); + expect(result.fontFailures, isEmpty); + }); + + test('Loading asset fonts reports when font not found', () async { + testScope.setAssetPassthrough(robotoVariableFontUrl); + testScope.setAssetPassthrough(robotoTestFontUrl); + + const String invalidFontUrl = 'assets/invalid_font_url.ttf'; + + final FlutterFontCollection collection = renderer.fontCollection; + final AssetFontsResult result = await collection.loadAssetFonts(FontManifest([ + FontFamily(robotoFontFamily, [ + FontAsset(robotoVariableFontUrl, {}), + FontAsset(robotoTestFontUrl, {'weight': 'bold'}), + ]), + FontFamily(ahemFontFamily, [ + FontAsset(invalidFontUrl, {}) + ]), + ])); + expect(result.loadedFonts, [ + robotoVariableFontUrl, + robotoTestFontUrl, + ]); + expect(result.fontFailures, hasLength(1)); + if (isHtml) { + // The HTML renderer doesn't have a way to differentiate 404's from other + // download errors. + expect(result.fontFailures[invalidFontUrl], isA()); + } else { + expect(result.fontFailures[invalidFontUrl], isA()); + } + }); + + test('Loading asset fonts reports when a font has invalid data', () async { + const String invalidFontUrl = 'assets/invalid_font_data.ttf'; + + testScope.setAssetPassthrough(robotoVariableFontUrl); + testScope.setAssetPassthrough(robotoTestFontUrl); + testScope.setAssetPassthrough(invalidFontUrl); + + mockHttpFetchResponseFactory = (String url) async { + if (url == invalidFontUrl) { + return MockHttpFetchResponse( + url: url, + status: 200, + payload: MockHttpFetchPayload( + byteBuffer: stringAsUtf8Data('this is invalid data').buffer + ), + ); + } + return null; + }; + + final FlutterFontCollection collection = renderer.fontCollection; + final AssetFontsResult result = await collection.loadAssetFonts(FontManifest([ + FontFamily(robotoFontFamily, [ + FontAsset(robotoVariableFontUrl, {}), + FontAsset(robotoTestFontUrl, {'weight': 'bold'}), + ]), + FontFamily(ahemFontFamily, [ + FontAsset(invalidFontUrl, {}) + ]), + ])); + expect(result.loadedFonts, [ + robotoVariableFontUrl, + robotoTestFontUrl, + ]); + expect(result.fontFailures, hasLength(1)); + if (isHtml) { + // The HTML renderer doesn't have a way to differentiate invalid data + // from other download errors. + expect(result.fontFailures[invalidFontUrl], isA()); + } else { + expect(result.fontFailures[invalidFontUrl], isA()); + } + }); +} diff --git a/lib/web_ui/test/ui/fragment_shader_test.dart b/lib/web_ui/test/ui/fragment_shader_test.dart index 1fef73e8e0f55..da69653d0a237 100644 --- a/lib/web_ui/test/ui/fragment_shader_test.dart +++ b/lib/web_ui/test/ui/fragment_shader_test.dart @@ -11,6 +11,8 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:web_engine_tester/golden_tester.dart'; +import '../common/fake_asset_manager.dart'; +import '../common/test_initialization.dart'; import 'utils.dart'; void main() { @@ -40,7 +42,9 @@ const String kVoronoiShaderSksl = r''' '''; Future testMain() async { - setUpUiTest(); + setUpUnitTests( + setUpTestViewDimensions: false, + ); const ui.Rect region = ui.Rect.fromLTWH(0, 0, 300, 300); diff --git a/lib/web_ui/test/ui/gradient_golden_test.dart b/lib/web_ui/test/ui/gradient_golden_test.dart index 96ba0f7d7fd20..f678f4d4dbb82 100644 --- a/lib/web_ui/test/ui/gradient_golden_test.dart +++ b/lib/web_ui/test/ui/gradient_golden_test.dart @@ -10,6 +10,7 @@ import 'package:ui/src/engine/browser_detection.dart'; import 'package:ui/ui.dart'; import 'package:web_engine_tester/golden_tester.dart'; +import '../common/test_initialization.dart'; import 'utils.dart'; void main() { @@ -17,7 +18,9 @@ void main() { } Future testMain() async { - setUpUiTest(); + setUpUnitTests( + setUpTestViewDimensions: false, + ); const Rect region = Rect.fromLTWH(0, 0, 300, 300); diff --git a/lib/web_ui/test/ui/gradient_test.dart b/lib/web_ui/test/ui/gradient_test.dart index 69d0ba9d7aa08..ae13bb05a9355 100644 --- a/lib/web_ui/test/ui/gradient_test.dart +++ b/lib/web_ui/test/ui/gradient_test.dart @@ -7,14 +7,14 @@ import 'package:test/test.dart'; import 'package:ui/ui.dart'; -import 'utils.dart'; +import '../common/test_initialization.dart'; void main() { internalBootstrapBrowserTest(() => testMain); } Future testMain() async { - setUpUiTest(); + setUpUnitTests(); test('Gradient.radial with no focal point', () { expect( @@ -26,7 +26,7 @@ Future testMain() async { TileMode.mirror), isNotNull, ); - }, skip: isSkwasm); + }); // this is just a radial gradient, focal point is discarded. test('radial center and focal == Offset.zero and focalRadius == 0.0 is ok', @@ -42,7 +42,7 @@ Future testMain() async { Offset.zero, ), isNotNull); - }, skip: isSkwasm); + }); test('radial center != focal and focalRadius == 0.0 is ok', () { expect( @@ -56,7 +56,7 @@ Future testMain() async { const Offset(2.0, 2.0), ), isNotNull); - }, skip: isSkwasm); + }); // this would result in div/0 on skia side. test('radial center and focal == Offset.zero and focalRadius != 0.0 assert', @@ -74,5 +74,5 @@ Future testMain() async { ), throwsA(const TypeMatcher()), ); - }, skip: isSkwasm); + }); } diff --git a/lib/web_ui/test/ui/paragraph_builder_test.dart b/lib/web_ui/test/ui/paragraph_builder_test.dart index 51db06b23e719..fe31eed072861 100644 --- a/lib/web_ui/test/ui/paragraph_builder_test.dart +++ b/lib/web_ui/test/ui/paragraph_builder_test.dart @@ -6,6 +6,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/ui.dart'; +import '../common/test_initialization.dart'; import 'utils.dart'; void main() { @@ -13,7 +14,10 @@ void main() { } Future testMain() async { - setUpUiTest(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); test('Should be able to build and layout a paragraph', () { final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle()); diff --git a/lib/web_ui/test/ui/path_metrics_test.dart b/lib/web_ui/test/ui/path_metrics_test.dart index f211b8a6b3219..ba820f7590c59 100644 --- a/lib/web_ui/test/ui/path_metrics_test.dart +++ b/lib/web_ui/test/ui/path_metrics_test.dart @@ -9,7 +9,7 @@ import 'package:test/test.dart'; import 'package:ui/ui.dart'; import '../common/matchers.dart'; -import 'utils.dart'; +import '../common/test_initialization.dart'; const double kTolerance = 0.1; @@ -18,7 +18,7 @@ void main() { } Future testMain() async { - setUpUiTest(); + setUpUnitTests(); group('PathMetric length', () { test('empty path', () { final Path path = Path(); diff --git a/lib/web_ui/test/ui/path_test.dart b/lib/web_ui/test/ui/path_test.dart index 5b78add83b697..94094a03b50ba 100644 --- a/lib/web_ui/test/ui/path_test.dart +++ b/lib/web_ui/test/ui/path_test.dart @@ -8,6 +8,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/ui.dart'; +import '../common/test_initialization.dart'; import 'utils.dart'; void main() { @@ -15,7 +16,7 @@ void main() { } Future testMain() async { - setUpUiTest(); + setUpUnitTests(); test('path getBounds', () { const Rect r = Rect.fromLTRB(1.0, 3.0, 5.0, 7.0); final Path p = Path()..addRect(r); diff --git a/lib/web_ui/test/ui/picture_test.dart b/lib/web_ui/test/ui/picture_test.dart index daa9f9d8075c9..297d8b50f403a 100644 --- a/lib/web_ui/test/ui/picture_test.dart +++ b/lib/web_ui/test/ui/picture_test.dart @@ -6,14 +6,14 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/ui.dart' as ui; -import 'utils.dart'; +import '../common/test_initialization.dart'; void main() { internalBootstrapBrowserTest(() => testMain); } Future testMain() async { - setUpUiTest(); + setUpUnitTests(); test('Picture construction invokes onCreate once', () async { int onCreateInvokedCount = 0; diff --git a/lib/web_ui/test/ui/rect_test.dart b/lib/web_ui/test/ui/rect_test.dart index d25c7af56fa82..3fa4eb0d14d01 100644 --- a/lib/web_ui/test/ui/rect_test.dart +++ b/lib/web_ui/test/ui/rect_test.dart @@ -6,14 +6,14 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/ui.dart'; -import 'utils.dart'; +import '../common/test_initialization.dart'; void main() { internalBootstrapBrowserTest(() => testMain); } Future testMain() async { - setUpUiTest(); + setUpUnitTests(); test('rect accessors', () { const Rect r = Rect.fromLTRB(1.0, 3.0, 5.0, 7.0); expect(r.left, equals(1.0)); diff --git a/lib/web_ui/test/ui/rrect_test.dart b/lib/web_ui/test/ui/rrect_test.dart index 00f8750e6a048..958c7aa137cfa 100644 --- a/lib/web_ui/test/ui/rrect_test.dart +++ b/lib/web_ui/test/ui/rrect_test.dart @@ -6,14 +6,14 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; import 'package:ui/ui.dart'; -import 'utils.dart'; +import '../common/test_initialization.dart'; void main() { internalBootstrapBrowserTest(() => testMain); } Future testMain() async { - setUpUiTest(); + setUpUnitTests(); test('RRect.contains()', () { final RRect rrect = RRect.fromRectAndCorners( const Rect.fromLTRB(1.0, 1.0, 2.0, 2.0), diff --git a/lib/web_ui/test/ui/scene_builder_test.dart b/lib/web_ui/test/ui/scene_builder_test.dart index 18bb6a4ab7076..b8f59b67f3522 100644 --- a/lib/web_ui/test/ui/scene_builder_test.dart +++ b/lib/web_ui/test/ui/scene_builder_test.dart @@ -10,6 +10,7 @@ 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'; import 'utils.dart'; void main() { @@ -17,7 +18,10 @@ void main() { } Future testMain() async { - setUpUiTest(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); group('${ui.SceneBuilder}', () { const ui.Rect region = ui.Rect.fromLTWH(0, 0, 300, 300); @@ -33,7 +37,6 @@ Future testMain() async { })); await renderer.renderScene(sceneBuilder.build()); - await awaitNextFrame(); await matchGoldenFile('scene_builder_centered_circle.png', region: region); }); @@ -58,7 +61,6 @@ Future testMain() async { })); await renderer.renderScene(sceneBuilder.build()); - await awaitNextFrame(); await matchGoldenFile('scene_builder_rotated_rounded_square.png', region: region); }); @@ -74,7 +76,6 @@ Future testMain() async { })); await renderer.renderScene(sceneBuilder.build()); - await awaitNextFrame(); await matchGoldenFile('scene_builder_circle_clip_rect.png', region: region); }); @@ -93,7 +94,6 @@ Future testMain() async { })); await renderer.renderScene(sceneBuilder.build()); - await awaitNextFrame(); await matchGoldenFile('scene_builder_circle_clip_rrect.png', region: region); }); @@ -110,7 +110,6 @@ Future testMain() async { })); await renderer.renderScene(sceneBuilder.build()); - await awaitNextFrame(); await matchGoldenFile('scene_builder_rectangle_clip_circular_path.png', region: region); }); @@ -139,7 +138,6 @@ Future testMain() async { })); await renderer.renderScene(sceneBuilder.build()); - await awaitNextFrame(); await matchGoldenFile('scene_builder_opacity_circles_on_square.png', region: region); }); @@ -180,7 +178,6 @@ Future testMain() async { })); await renderer.renderScene(sceneBuilder.build()); - await awaitNextFrame(); await matchGoldenFile('scene_builder_shader_mask.png', region: region); }, skip: isFirefox && isHtml); // https://github.com/flutter/flutter/issues/86623 }); diff --git a/lib/web_ui/test/ui/shadow_test.dart b/lib/web_ui/test/ui/shadow_test.dart index b1bb65ad0c924..eb6b364a76947 100644 --- a/lib/web_ui/test/ui/shadow_test.dart +++ b/lib/web_ui/test/ui/shadow_test.dart @@ -7,6 +7,7 @@ import 'package:test/test.dart'; import 'package:ui/ui.dart' as ui; import 'package:web_engine_tester/golden_tester.dart'; +import '../common/test_initialization.dart'; import 'utils.dart'; void main() { @@ -16,7 +17,10 @@ void main() { Future testMain() async { const ui.Rect region = ui.Rect.fromLTWH(0, 0, 300, 300); - setUpUiTest(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); test('Test drawing a shadow of an opaque object', () async { final ui.Picture picture = drawPicture((ui.Canvas canvas) { diff --git a/lib/web_ui/test/ui/utils.dart b/lib/web_ui/test/ui/utils.dart index 01b42125383bb..38f976ad91f3f 100644 --- a/lib/web_ui/test/ui/utils.dart +++ b/lib/web_ui/test/ui/utils.dart @@ -3,57 +3,11 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:js_interop'; -import 'dart:typed_data'; -import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_stub.dart' if (dart.library.ffi) 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart'; -class FakeAssetManager implements AssetManager { - FakeAssetManager(this._parent); - - @override - String get assetsDir => 'assets'; - - @override - String getAssetUrl(String asset) => asset; - - @override - Future load(String assetKey) async { - final ByteData? data = _assetMap[assetKey]; - if (data == null) { - return _parent.load(assetKey); - } - return data; - } - - @override - Future loadAsset(String asset) { - return _parent.loadAsset(asset); - } - - void setAsset(String assetKey, ByteData assetData) { - _assetMap[assetKey] = assetData; - } - - final Map _assetMap = {}; - final AssetManager _parent; -} - -FakeAssetManager fakeAssetManager = FakeAssetManager(WebOnlyMockAssetManager()); - -/// Initializes the renderer for this test. -void setUpUiTest() { - setUpAll(() async { - debugEmulateFlutterTesterEnvironment = true; - await initializeEngine(assetManager: fakeAssetManager); - await renderer.fontCollection.debugDownloadTestFonts(); - renderer.fontCollection.registerDownloadedFonts(); - }); -} - Picture drawPicture(void Function(Canvas) drawCommands) { final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -67,13 +21,6 @@ Future drawPictureUsingCurrentRenderer(Picture picture) async { sb.pushOffset(0, 0); sb.addPicture(Offset.zero, picture); await renderer.renderScene(sb.build()); - await awaitNextFrame(); -} - -Future awaitNextFrame() { - final Completer completer = Completer(); - domWindow.requestAnimationFrame((JSNumber time) => completer.complete()); - return completer.future; } /// Returns [true] if this test is running in the CanvasKit renderer. diff --git a/third_party/canvaskit/BUILD.gn b/third_party/canvaskit/BUILD.gn index 062aa10433ac3..af6b617bd8624 100644 --- a/third_party/canvaskit/BUILD.gn +++ b/third_party/canvaskit/BUILD.gn @@ -71,7 +71,15 @@ copy("canvaskit_chromium_group") { # This toolchain is only to be used by skwasm_group below. wasm_toolchain("skwasm") { extra_toolchain_args = { - skia_enable_skparagraph = false + # In Chromium browsers, we can use the browser's APIs to get the necessary + # ICU data. + skia_use_icu = false + skia_use_client_icu = true + skia_icu_bidi_third_party_dir = "//flutter/third_party/canvaskit/icu_bidi" + + skia_use_libjpeg_turbo_decode = false + skia_use_libpng_decode = false + skia_use_libwebp_decode = false # skwasm is multithreaded wasm_use_pthreads = true diff --git a/web_sdk/web_engine_tester/lib/golden_tester.dart b/web_sdk/web_engine_tester/lib/golden_tester.dart index 904a7eb91ed28..5e743b4ab7fc6 100644 --- a/web_sdk/web_engine_tester/lib/golden_tester.dart +++ b/web_sdk/web_engine_tester/lib/golden_tester.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:js_interop'; import 'package:test/test.dart'; // ignore: implementation_imports @@ -49,6 +50,14 @@ enum PixelComparison { /// [pixelComparison] determines the algorithm used to compare pixels. Uses /// fuzzy comparison by default. Future matchGoldenFile(String filename, {Rect? region}) async { + // It is difficult to deterministically tell when rendered content is actually + // visible to the user, so we pump 15 frames to make sure that the content is + // has reached the screen. This is at the recommendation of the Chrome team, + // and they use this same thing in their screenshot unit tests. + for (int i = 0; i < 15; i++) { + await awaitNextFrame(); + } + if (!filename.endsWith('.png')) { throw ArgumentError('Filename must end in .png or SkiaGold will ignore it.'); } @@ -76,3 +85,10 @@ Future matchGoldenFile(String filename, {Rect? region}) async { } fail(response); } + +/// Waits for one frame to complete rendering +Future awaitNextFrame() { + final Completer completer = Completer(); + domWindow.requestAnimationFrame((JSNumber time) => completer.complete()); + return completer.future; +}