mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Improve error message when CanvasKit is unable to parse a font (flutter/engine#24827)
This commit is contained in:
parent
87f4e5a35a
commit
e8a33f1be4
@ -61,14 +61,13 @@ class AssetManager {
|
||||
final html.EventTarget? target = e.target;
|
||||
if (target is html.HttpRequest) {
|
||||
if (target.status == 404 && asset == 'AssetManifest.json') {
|
||||
html.window.console
|
||||
.warn('Asset manifest does not exist at `$url` – ignoring.');
|
||||
printWarning('Asset manifest does not exist at `$url` – ignoring.');
|
||||
return Uint8List.fromList(utf8.encode('{}')).buffer.asByteData();
|
||||
}
|
||||
throw AssetManagerException(url, target.status!);
|
||||
}
|
||||
|
||||
html.window.console.warn('Caught ProgressEvent with target: $target');
|
||||
printWarning('Caught ProgressEvent with target: $target');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1652,7 +1652,7 @@ class SkFont {
|
||||
class SkFontMgr {
|
||||
external String? getFamilyName(int fontId);
|
||||
external void delete();
|
||||
external SkTypeface MakeTypefaceFromData(Uint8List font);
|
||||
external SkTypeface? MakeTypefaceFromData(Uint8List font);
|
||||
}
|
||||
|
||||
@JS('window.flutterCanvasKit.TypefaceFontProvider')
|
||||
|
||||
@ -60,11 +60,18 @@ class FontFallbackData {
|
||||
final Map<String, int> fontFallbackCounts = <String, int>{};
|
||||
|
||||
void registerFallbackFont(String family, Uint8List bytes) {
|
||||
final SkTypeface? typeface =
|
||||
canvasKit.FontMgr.RefDefault().MakeTypefaceFromData(bytes);
|
||||
if (typeface == null) {
|
||||
printWarning('Failed to parse fallback font $family as a font.');
|
||||
return;
|
||||
}
|
||||
fontFallbackCounts.putIfAbsent(family, () => 0);
|
||||
int fontFallbackTag = fontFallbackCounts[family]!;
|
||||
fontFallbackCounts[family] = fontFallbackCounts[family]! + 1;
|
||||
String countedFamily = '$family $fontFallbackTag';
|
||||
registeredFallbackFonts.add(_RegisteredFont(bytes, countedFamily));
|
||||
registeredFallbackFonts
|
||||
.add(_RegisteredFont(bytes, countedFamily, typeface));
|
||||
globalFontFallbacks.add(countedFamily);
|
||||
}
|
||||
}
|
||||
@ -123,7 +130,7 @@ Future<void> findFontsForMissingCodeunits(List<int> codeUnits) async {
|
||||
_registerSymbolsAndEmoji();
|
||||
} else {
|
||||
if (!notoDownloadQueue.isPending) {
|
||||
html.window.console.log(
|
||||
printWarning(
|
||||
'Could not find a set of Noto fonts to display all missing '
|
||||
'characters. Please add a font asset for the missing characters.'
|
||||
' See: https://flutter.dev/docs/cookbook/design/fonts');
|
||||
@ -179,7 +186,7 @@ _ResolvedNotoFont? _makeResolvedNotoFontFromCss(String css, String name) {
|
||||
if (line.startsWith(' src:')) {
|
||||
int urlStart = line.indexOf('url(');
|
||||
if (urlStart == -1) {
|
||||
html.window.console.warn('Unable to resolve Noto font URL: $line');
|
||||
printWarning('Unable to resolve Noto font URL: $line');
|
||||
return null;
|
||||
}
|
||||
int urlEnd = line.indexOf(')');
|
||||
@ -207,7 +214,7 @@ _ResolvedNotoFont? _makeResolvedNotoFontFromCss(String css, String name) {
|
||||
}
|
||||
} else if (line == '}') {
|
||||
if (fontFaceUrl == null || fontFaceUnicodeRanges == null) {
|
||||
html.window.console.warn('Unable to parse Google Fonts CSS: $css');
|
||||
printWarning('Unable to parse Google Fonts CSS: $css');
|
||||
return null;
|
||||
}
|
||||
subsets
|
||||
@ -220,7 +227,7 @@ _ResolvedNotoFont? _makeResolvedNotoFontFromCss(String css, String name) {
|
||||
}
|
||||
|
||||
if (resolvingFontFace) {
|
||||
html.window.console.warn('Unable to parse Google Fonts CSS: $css');
|
||||
printWarning('Unable to parse Google Fonts CSS: $css');
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -233,7 +240,7 @@ _ResolvedNotoFont? _makeResolvedNotoFontFromCss(String css, String name) {
|
||||
}
|
||||
|
||||
if (rangesMap.isEmpty) {
|
||||
html.window.console.warn('Parsed Google Fonts CSS was empty: $css');
|
||||
printWarning('Parsed Google Fonts CSS was empty: $css');
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -267,14 +274,14 @@ Future<void> _registerSymbolsAndEmoji() async {
|
||||
if (line.startsWith(' src:')) {
|
||||
int urlStart = line.indexOf('url(');
|
||||
if (urlStart == -1) {
|
||||
html.window.console.warn('Unable to resolve Noto font URL: $line');
|
||||
printWarning('Unable to resolve Noto font URL: $line');
|
||||
return null;
|
||||
}
|
||||
int urlEnd = line.indexOf(')');
|
||||
return line.substring(urlStart + 4, urlEnd);
|
||||
}
|
||||
}
|
||||
html.window.console.warn('Unable to determine URL for Noto font');
|
||||
printWarning('Unable to determine URL for Noto font');
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -285,14 +292,14 @@ Future<void> _registerSymbolsAndEmoji() async {
|
||||
notoDownloadQueue.add(_ResolvedNotoSubset(
|
||||
symbolsFontUrl, 'Noto Sans Symbols', const <CodeunitRange>[]));
|
||||
} else {
|
||||
html.window.console.warn('Error parsing CSS for Noto Symbols font.');
|
||||
printWarning('Error parsing CSS for Noto Symbols font.');
|
||||
}
|
||||
|
||||
if (emojiFontUrl != null) {
|
||||
notoDownloadQueue.add(_ResolvedNotoSubset(
|
||||
emojiFontUrl, 'Noto Color Emoji Compat', const <CodeunitRange>[]));
|
||||
} else {
|
||||
html.window.console.warn('Error parsing CSS for Noto Emoji font.');
|
||||
printWarning('Error parsing CSS for Noto Emoji font.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -724,9 +731,8 @@ class FallbackFontDownloadQueue {
|
||||
debugDescription: subset.family);
|
||||
} catch (e) {
|
||||
pendingSubsets.remove(subset.url);
|
||||
html.window.console
|
||||
.warn('Failed to load font ${subset.family} at ${subset.url}');
|
||||
html.window.console.warn(e);
|
||||
printWarning('Failed to load font ${subset.family} at ${subset.url}');
|
||||
printWarning(e.toString());
|
||||
return;
|
||||
}
|
||||
downloadedSubsets.add(subset);
|
||||
|
||||
@ -70,14 +70,20 @@ class SkiaFontCollection {
|
||||
if (fontFamily == null) {
|
||||
fontFamily = _readActualFamilyName(list);
|
||||
if (fontFamily == null) {
|
||||
html.window.console
|
||||
.warn('Failed to read font family name. Aborting font load.');
|
||||
printWarning('Failed to read font family name. Aborting font load.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_registeredFonts.add(_RegisteredFont(list, fontFamily));
|
||||
await ensureFontsLoaded();
|
||||
final SkTypeface? typeface =
|
||||
canvasKit.FontMgr.RefDefault().MakeTypefaceFromData(list);
|
||||
if (typeface != null) {
|
||||
_registeredFonts.add(_RegisteredFont(list, fontFamily, typeface));
|
||||
await ensureFontsLoaded();
|
||||
} else {
|
||||
printWarning('Failed to parse font family "$fontFamily"');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> registerFonts(AssetManager assetManager) async {
|
||||
@ -87,8 +93,7 @@ class SkiaFontCollection {
|
||||
byteData = await assetManager.load('FontManifest.json');
|
||||
} on AssetManagerException catch (e) {
|
||||
if (e.httpStatus == 404) {
|
||||
html.window.console
|
||||
.warn('Font manifest does not exist at `${e.url}` – ignoring.');
|
||||
printWarning('Font manifest does not exist at `${e.url}` – ignoring.');
|
||||
return;
|
||||
} else {
|
||||
rethrow;
|
||||
@ -135,13 +140,21 @@ class SkiaFontCollection {
|
||||
try {
|
||||
buffer = await html.window.fetch(url).then(_getArrayBuffer);
|
||||
} catch (e) {
|
||||
html.window.console.warn('Failed to load font $family at $url');
|
||||
html.window.console.warn(e);
|
||||
printWarning('Failed to load font $family at $url');
|
||||
printWarning(e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
final Uint8List bytes = buffer.asUint8List();
|
||||
return _RegisteredFont(bytes, family);
|
||||
SkTypeface? typeface =
|
||||
canvasKit.FontMgr.RefDefault().MakeTypefaceFromData(bytes);
|
||||
if (typeface != null) {
|
||||
return _RegisteredFont(bytes, family, typeface);
|
||||
} else {
|
||||
printWarning('Failed to load font $family at $url');
|
||||
printWarning('Verify that $url contains a valid font.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? _readActualFamilyName(Uint8List bytes) {
|
||||
@ -175,9 +188,7 @@ class _RegisteredFont {
|
||||
/// This is used to determine which code points are supported by this font.
|
||||
final SkTypeface typeface;
|
||||
|
||||
_RegisteredFont(this.bytes, this.family)
|
||||
: this.typeface =
|
||||
canvasKit.FontMgr.RefDefault().MakeTypefaceFromData(bytes) {
|
||||
_RegisteredFont(this.bytes, this.family, this.typeface) {
|
||||
// This is a hack which causes Skia to cache the decoded font.
|
||||
SkFont skFont = SkFont(typeface);
|
||||
skFont.getGlyphBounds([0], null, null);
|
||||
|
||||
@ -180,7 +180,7 @@ class CkImage implements ui.Image, StackTraceDebugger {
|
||||
colorSpace: SkColorSpaceSRGB,
|
||||
);
|
||||
if (originalBytes == null) {
|
||||
html.window.console.warn('Unable to encode image to bytes. We will not '
|
||||
printWarning('Unable to encode image to bytes. We will not '
|
||||
'be able to resurrect it once it has been garbage collected.');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ class Surface {
|
||||
}
|
||||
|
||||
void _syncCacheBytes() {
|
||||
if(_skiaCacheBytes != null) {
|
||||
if (_skiaCacheBytes != null) {
|
||||
_grContext?.setResourceCacheLimitBytes(_skiaCacheBytes!);
|
||||
}
|
||||
}
|
||||
@ -130,12 +130,12 @@ class Surface {
|
||||
|
||||
_currentDevicePixelRatio = window.devicePixelRatio;
|
||||
_currentSize = _currentSize == null
|
||||
// First frame. Allocate a canvas of the exact size as the window. The
|
||||
// window is frequently never resized, particularly on mobile, so using
|
||||
// the exact size is most optimal.
|
||||
? size
|
||||
// The window is growing. Overallocate to prevent frequent reallocations.
|
||||
: size * 1.4;
|
||||
// First frame. Allocate a canvas of the exact size as the window. The
|
||||
// window is frequently never resized, particularly on mobile, so using
|
||||
// the exact size is most optimal.
|
||||
? size
|
||||
// The window is growing. Overallocate to prevent frequent reallocations.
|
||||
: size * 1.4;
|
||||
|
||||
_surface?.dispose();
|
||||
_surface = null;
|
||||
@ -199,9 +199,11 @@ class Surface {
|
||||
htmlElement.append(htmlCanvas);
|
||||
|
||||
if (webGLVersion == -1) {
|
||||
return _makeSoftwareCanvasSurface(htmlCanvas, 'WebGL support not detected');
|
||||
return _makeSoftwareCanvasSurface(
|
||||
htmlCanvas, 'WebGL support not detected');
|
||||
} else if (canvasKitForceCpuOnly) {
|
||||
return _makeSoftwareCanvasSurface(htmlCanvas, 'CPU rendering forced by application');
|
||||
return _makeSoftwareCanvasSurface(
|
||||
htmlCanvas, 'CPU rendering forced by application');
|
||||
} else {
|
||||
// Try WebGL first.
|
||||
final int glContext = canvasKit.GetWebGLContext(
|
||||
@ -215,13 +217,15 @@ class Surface {
|
||||
);
|
||||
|
||||
if (glContext == 0) {
|
||||
return _makeSoftwareCanvasSurface(htmlCanvas, 'Failed to initialize WebGL context');
|
||||
return _makeSoftwareCanvasSurface(
|
||||
htmlCanvas, 'Failed to initialize WebGL context');
|
||||
}
|
||||
|
||||
_grContext = canvasKit.MakeGrContext(glContext);
|
||||
|
||||
if (_grContext == null) {
|
||||
throw CanvasKitError('Failed to initialize CanvasKit. CanvasKit.MakeGrContext returned null.');
|
||||
throw CanvasKitError(
|
||||
'Failed to initialize CanvasKit. CanvasKit.MakeGrContext returned null.');
|
||||
}
|
||||
|
||||
// Set the cache byte limit for this grContext, if not specified it will use
|
||||
@ -236,7 +240,8 @@ class Surface {
|
||||
);
|
||||
|
||||
if (skSurface == null) {
|
||||
return _makeSoftwareCanvasSurface(htmlCanvas, 'Failed to initialize WebGL surface');
|
||||
return _makeSoftwareCanvasSurface(
|
||||
htmlCanvas, 'Failed to initialize WebGL surface');
|
||||
}
|
||||
|
||||
return CkSurface(skSurface, _grContext, glContext);
|
||||
@ -245,11 +250,10 @@ class Surface {
|
||||
|
||||
static bool _didWarnAboutWebGlInitializationFailure = false;
|
||||
|
||||
CkSurface _makeSoftwareCanvasSurface(html.CanvasElement htmlCanvas, String reason) {
|
||||
CkSurface _makeSoftwareCanvasSurface(
|
||||
html.CanvasElement htmlCanvas, String reason) {
|
||||
if (!_didWarnAboutWebGlInitializationFailure) {
|
||||
html.window.console.warn(
|
||||
'WARNING: Falling back to CPU-only rendering. $reason.'
|
||||
);
|
||||
printWarning('WARNING: Falling back to CPU-only rendering. $reason.');
|
||||
_didWarnAboutWebGlInitializationFailure = true;
|
||||
}
|
||||
return CkSurface(
|
||||
|
||||
@ -572,7 +572,7 @@ class CkParagraph extends ManagedSkiaObject<SkParagraph>
|
||||
try {
|
||||
skiaObject.layout(constraints.width);
|
||||
} catch (e) {
|
||||
html.window.console.warn('CanvasKit threw an exception while laying '
|
||||
printWarning('CanvasKit threw an exception while laying '
|
||||
'out the paragraph. The font was "${_paragraphStyle._fontFamily}". '
|
||||
'Exception:\n$e');
|
||||
rethrow;
|
||||
@ -775,7 +775,7 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {
|
||||
List<SkTypeface>? typefacesForFamily =
|
||||
skiaFontCollection.familyToTypefaceMap[font];
|
||||
if (typefacesForFamily == null) {
|
||||
html.window.console.warn('A fallback font was registered but we '
|
||||
printWarning('A fallback font was registered but we '
|
||||
'cannot retrieve the typeface for it.');
|
||||
continue;
|
||||
}
|
||||
@ -864,7 +864,7 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {
|
||||
if (_styleStack.length <= 1) {
|
||||
// The top-level text style is paragraph-level. We don't pop it off.
|
||||
if (assertionsEnabled) {
|
||||
html.window.console.warn(
|
||||
printWarning(
|
||||
'Cannot pop text style in ParagraphBuilder. '
|
||||
'Already popped all text styles from the style stack.',
|
||||
);
|
||||
|
||||
@ -52,7 +52,7 @@ class PersistedPlatformView extends PersistedLeafSurface {
|
||||
if (platformView != null) {
|
||||
_shadowRoot.append(platformView);
|
||||
} else {
|
||||
html.window.console.warn('No platform view created for id $viewId');
|
||||
printWarning('No platform view created for id $viewId');
|
||||
}
|
||||
return element;
|
||||
}
|
||||
@ -93,7 +93,10 @@ class PersistedPlatformView extends PersistedLeafSurface {
|
||||
|
||||
@override
|
||||
void update(PersistedPlatformView oldSurface) {
|
||||
assert(viewId == oldSurface.viewId, 'PersistedPlatformView with different viewId should never be updated. Check the canUpdateAsMatch method.',);
|
||||
assert(
|
||||
viewId == oldSurface.viewId,
|
||||
'PersistedPlatformView with different viewId should never be updated. Check the canUpdateAsMatch method.',
|
||||
);
|
||||
super.update(oldSurface);
|
||||
// Only update if the view has been resized
|
||||
if (dx != oldSurface.dx ||
|
||||
|
||||
@ -65,7 +65,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
double dy, {
|
||||
ui.OffsetEngineLayer? oldLayer,
|
||||
}) {
|
||||
return _pushSurface<PersistedOffset>(PersistedOffset(oldLayer as PersistedOffset?, dx, dy));
|
||||
return _pushSurface<PersistedOffset>(
|
||||
PersistedOffset(oldLayer as PersistedOffset?, dx, dy));
|
||||
}
|
||||
|
||||
/// Pushes a transform operation onto the operation stack.
|
||||
@ -90,13 +91,14 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
// logical device pixels.
|
||||
if (!ui.debugEmulateFlutterTesterEnvironment) {
|
||||
assert(matrix4[0] == window.devicePixelRatio &&
|
||||
matrix4[5] == window.devicePixelRatio);
|
||||
matrix4[5] == window.devicePixelRatio);
|
||||
}
|
||||
matrix = Matrix4.identity().storage;
|
||||
} else {
|
||||
matrix = toMatrix32(matrix4);
|
||||
}
|
||||
return _pushSurface<PersistedTransform>(PersistedTransform(oldLayer as PersistedTransform?, matrix));
|
||||
return _pushSurface<PersistedTransform>(
|
||||
PersistedTransform(oldLayer as PersistedTransform?, matrix));
|
||||
}
|
||||
|
||||
/// Pushes a rectangular clip operation onto the operation stack.
|
||||
@ -113,7 +115,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
}) {
|
||||
assert(clipBehavior != null); // ignore: unnecessary_null_comparison
|
||||
assert(clipBehavior != ui.Clip.none);
|
||||
return _pushSurface<PersistedClipRect>(PersistedClipRect(oldLayer as PersistedClipRect?, rect, clipBehavior));
|
||||
return _pushSurface<PersistedClipRect>(
|
||||
PersistedClipRect(oldLayer as PersistedClipRect?, rect, clipBehavior));
|
||||
}
|
||||
|
||||
/// Pushes a rounded-rectangular clip operation onto the operation stack.
|
||||
@ -127,7 +130,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
ui.Clip? clipBehavior,
|
||||
ui.ClipRRectEngineLayer? oldLayer,
|
||||
}) {
|
||||
return _pushSurface<PersistedClipRRect>(PersistedClipRRect(oldLayer, rrect, clipBehavior));
|
||||
return _pushSurface<PersistedClipRRect>(
|
||||
PersistedClipRRect(oldLayer, rrect, clipBehavior));
|
||||
}
|
||||
|
||||
/// Pushes a path clip operation onto the operation stack.
|
||||
@ -143,7 +147,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
}) {
|
||||
assert(clipBehavior != null); // ignore: unnecessary_null_comparison
|
||||
assert(clipBehavior != ui.Clip.none);
|
||||
return _pushSurface<PersistedClipPath>(PersistedClipPath(oldLayer as PersistedClipPath?, path, clipBehavior));
|
||||
return _pushSurface<PersistedClipPath>(
|
||||
PersistedClipPath(oldLayer as PersistedClipPath?, path, clipBehavior));
|
||||
}
|
||||
|
||||
/// Pushes an opacity operation onto the operation stack.
|
||||
@ -160,7 +165,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
ui.Offset offset = ui.Offset.zero,
|
||||
ui.OpacityEngineLayer? oldLayer,
|
||||
}) {
|
||||
return _pushSurface<PersistedOpacity>(PersistedOpacity(oldLayer as PersistedOpacity?, alpha, offset));
|
||||
return _pushSurface<PersistedOpacity>(
|
||||
PersistedOpacity(oldLayer as PersistedOpacity?, alpha, offset));
|
||||
}
|
||||
|
||||
/// Pushes a color filter operation onto the operation stack.
|
||||
@ -179,7 +185,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
ui.ColorFilterEngineLayer? oldLayer,
|
||||
}) {
|
||||
assert(filter != null); // ignore: unnecessary_null_comparison
|
||||
return _pushSurface<PersistedColorFilter>(PersistedColorFilter(oldLayer as PersistedColorFilter?, filter));
|
||||
return _pushSurface<PersistedColorFilter>(
|
||||
PersistedColorFilter(oldLayer as PersistedColorFilter?, filter));
|
||||
}
|
||||
|
||||
/// Pushes an image filter operation onto the operation stack.
|
||||
@ -198,7 +205,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
ui.ImageFilterEngineLayer? oldLayer,
|
||||
}) {
|
||||
assert(filter != null); // ignore: unnecessary_null_comparison
|
||||
return _pushSurface<PersistedImageFilter>(PersistedImageFilter(oldLayer as PersistedImageFilter?, filter));
|
||||
return _pushSurface<PersistedImageFilter>(
|
||||
PersistedImageFilter(oldLayer as PersistedImageFilter?, filter));
|
||||
}
|
||||
|
||||
/// Pushes a backdrop filter operation onto the operation stack.
|
||||
@ -212,7 +220,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
ui.ImageFilter filter, {
|
||||
ui.BackdropFilterEngineLayer? oldLayer,
|
||||
}) {
|
||||
return _pushSurface<PersistedBackdropFilter>(PersistedBackdropFilter(oldLayer as PersistedBackdropFilter?, filter as EngineImageFilter));
|
||||
return _pushSurface<PersistedBackdropFilter>(PersistedBackdropFilter(
|
||||
oldLayer as PersistedBackdropFilter?, filter as EngineImageFilter));
|
||||
}
|
||||
|
||||
/// Pushes a shader mask operation onto the operation stack.
|
||||
@ -228,10 +237,9 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
ui.BlendMode blendMode, {
|
||||
ui.ShaderMaskEngineLayer? oldLayer,
|
||||
}) {
|
||||
assert(shader != null && maskRect != null && blendMode != null); // ignore: unnecessary_null_comparison
|
||||
assert(blendMode != null); // ignore: unnecessary_null_comparison
|
||||
return _pushSurface<PersistedShaderMask>(PersistedShaderMask(
|
||||
oldLayer as PersistedShaderMask?,
|
||||
shader, maskRect, blendMode));
|
||||
oldLayer as PersistedShaderMask?, shader, maskRect, blendMode));
|
||||
}
|
||||
|
||||
/// Pushes a physical layer operation for an arbitrary shape onto the
|
||||
@ -255,7 +263,6 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
ui.Clip clipBehavior = ui.Clip.none,
|
||||
ui.PhysicalShapeEngineLayer? oldLayer,
|
||||
}) {
|
||||
assert(color != null, 'color must not be null'); // ignore: unnecessary_null_comparison
|
||||
return _pushSurface<PersistedPhysicalShape>(PersistedPhysicalShape(
|
||||
oldLayer as PersistedPhysicalShape?,
|
||||
path as SurfacePath,
|
||||
@ -276,7 +283,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
/// no need to call [addToScene] for its children layers.
|
||||
@override
|
||||
void addRetained(ui.EngineLayer retainedLayer) {
|
||||
final PersistedContainerSurface retainedSurface = retainedLayer as PersistedContainerSurface;
|
||||
final PersistedContainerSurface retainedSurface =
|
||||
retainedLayer as PersistedContainerSurface;
|
||||
if (assertionsEnabled) {
|
||||
assert(debugAssertSurfaceState(retainedSurface,
|
||||
PersistedSurfaceState.active, PersistedSurfaceState.released));
|
||||
@ -340,8 +348,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
) {
|
||||
if (!_webOnlyDidWarnAboutPerformanceOverlay) {
|
||||
_webOnlyDidWarnAboutPerformanceOverlay = true;
|
||||
html.window.console
|
||||
.warn('The performance overlay isn\'t supported on the web');
|
||||
printWarning('The performance overlay isn\'t supported on the web');
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,7 +369,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
if (willChangeHint) {
|
||||
hints |= 2;
|
||||
}
|
||||
_addSurface(PersistedPicture(offset.dx, offset.dy, picture as EnginePicture, hints));
|
||||
_addSurface(PersistedPicture(
|
||||
offset.dx, offset.dy, picture as EnginePicture, hints));
|
||||
}
|
||||
|
||||
/// Adds a backend texture to the scene.
|
||||
@ -378,12 +386,12 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
bool freeze = false,
|
||||
ui.FilterQuality filterQuality = ui.FilterQuality.low,
|
||||
}) {
|
||||
assert(offset != null, 'Offset argument was null'); // ignore: unnecessary_null_comparison
|
||||
_addTexture(offset.dx, offset.dy, width, height, textureId, filterQuality.index);
|
||||
_addTexture(
|
||||
offset.dx, offset.dy, width, height, textureId, filterQuality.index);
|
||||
}
|
||||
|
||||
void _addTexture(
|
||||
double dx, double dy, double width, double height, int textureId, int filterQuality) {
|
||||
void _addTexture(double dx, double dy, double width, double height,
|
||||
int textureId, int filterQuality) {
|
||||
// In test mode, allow this to be a no-op.
|
||||
if (!ui.debugEmulateFlutterTesterEnvironment) {
|
||||
throw UnimplementedError('Textures are not supported in Flutter Web');
|
||||
@ -413,7 +421,6 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
double width = 0.0,
|
||||
double height = 0.0,
|
||||
}) {
|
||||
assert(offset != null, 'Offset argument was null'); // ignore: unnecessary_null_comparison
|
||||
_addPlatformView(offset.dx, offset.dy, width, height, viewId);
|
||||
}
|
||||
|
||||
|
||||
@ -25,16 +25,19 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
|
||||
/// The [EnginePlatformDispatcher] singleton.
|
||||
static EnginePlatformDispatcher get instance => _instance;
|
||||
static final EnginePlatformDispatcher _instance = EnginePlatformDispatcher._();
|
||||
static final EnginePlatformDispatcher _instance =
|
||||
EnginePlatformDispatcher._();
|
||||
|
||||
/// The current platform configuration.
|
||||
@override
|
||||
ui.PlatformConfiguration get configuration => _configuration;
|
||||
ui.PlatformConfiguration _configuration = ui.PlatformConfiguration(locales: parseBrowserLanguages());
|
||||
ui.PlatformConfiguration _configuration =
|
||||
ui.PlatformConfiguration(locales: parseBrowserLanguages());
|
||||
|
||||
/// Receives all events related to platform configuration changes.
|
||||
@override
|
||||
ui.VoidCallback? get onPlatformConfigurationChanged => _onPlatformConfigurationChanged;
|
||||
ui.VoidCallback? get onPlatformConfigurationChanged =>
|
||||
_onPlatformConfigurationChanged;
|
||||
ui.VoidCallback? _onPlatformConfigurationChanged;
|
||||
Zone? _onPlatformConfigurationChangedZone;
|
||||
@override
|
||||
@ -46,7 +49,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
/// Engine code should use this method instead of the callback directly.
|
||||
/// Otherwise zones won't work properly.
|
||||
void invokeOnPlatformConfigurationChanged() {
|
||||
invoke(_onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone);
|
||||
invoke(
|
||||
_onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone);
|
||||
}
|
||||
|
||||
/// The current list of windows,
|
||||
@ -57,7 +61,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
///
|
||||
/// This should be considered a protected member, only to be used by
|
||||
/// [PlatformDispatcher] subclasses.
|
||||
Map<Object, ui.ViewConfiguration> _windowConfigurations = <Object, ui.ViewConfiguration>{};
|
||||
Map<Object, ui.ViewConfiguration> _windowConfigurations =
|
||||
<Object, ui.ViewConfiguration>{};
|
||||
|
||||
/// A callback that is invoked whenever the platform's [devicePixelRatio],
|
||||
/// [physicalSize], [padding], [viewInsets], or [systemGestureInsets]
|
||||
@ -169,7 +174,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
/// Engine code should use this method instead of the callback directly.
|
||||
/// Otherwise zones won't work properly.
|
||||
void invokeOnPointerDataPacket(ui.PointerDataPacket dataPacket) {
|
||||
invoke1<ui.PointerDataPacket>(_onPointerDataPacket, _onPointerDataPacketZone, dataPacket);
|
||||
invoke1<ui.PointerDataPacket>(
|
||||
_onPointerDataPacket, _onPointerDataPacketZone, dataPacket);
|
||||
}
|
||||
|
||||
/// A callback that is invoked when key data is available.
|
||||
@ -239,7 +245,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
/// Engine code should use this method instead of the callback directly.
|
||||
/// Otherwise zones won't work properly.
|
||||
void invokeOnReportTimings(List<ui.FrameTiming> timings) {
|
||||
invoke1<List<ui.FrameTiming>>(_onReportTimings, _onReportTimingsZone, timings);
|
||||
invoke1<List<ui.FrameTiming>>(
|
||||
_onReportTimings, _onReportTimingsZone, timings);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -248,7 +255,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
ByteData? data,
|
||||
ui.PlatformMessageResponseCallback? callback,
|
||||
) {
|
||||
_sendPlatformMessage(name, data, _zonedPlatformMessageResponseCallback(callback));
|
||||
_sendPlatformMessage(
|
||||
name, data, _zonedPlatformMessageResponseCallback(callback));
|
||||
}
|
||||
|
||||
// TODO(ianh): Deprecate onPlatformMessage once the framework is moved over
|
||||
@ -293,10 +301,11 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
/// Wraps the given [callback] in another callback that ensures that the
|
||||
/// original callback is called in the zone it was registered in.
|
||||
static ui.PlatformMessageResponseCallback?
|
||||
_zonedPlatformMessageResponseCallback(
|
||||
ui.PlatformMessageResponseCallback? callback) {
|
||||
if (callback == null)
|
||||
_zonedPlatformMessageResponseCallback(
|
||||
ui.PlatformMessageResponseCallback? callback) {
|
||||
if (callback == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Store the zone in which the callback is being registered.
|
||||
final Zone registrationZone = Zone.current;
|
||||
@ -327,6 +336,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
|
||||
/// This should be in sync with shell/common/shell.cc
|
||||
case 'flutter/skia':
|
||||
const MethodCodec codec = JSONMethodCodec();
|
||||
@ -346,19 +356,18 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
|
||||
// Also respond in HTML mode. Otherwise, apps would have to detect
|
||||
// CanvasKit vs HTML before invoking this method.
|
||||
_replyToPlatformMessage(callback, codec.encodeSuccessEnvelope([true]));
|
||||
_replyToPlatformMessage(
|
||||
callback, codec.encodeSuccessEnvelope([true]));
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
case 'flutter/assets':
|
||||
assert(ui.webOnlyAssetManager != null); // ignore: unnecessary_null_comparison
|
||||
final String url = utf8.decode(data!.buffer.asUint8List());
|
||||
ui.webOnlyAssetManager.load(url).then((ByteData assetData) {
|
||||
_replyToPlatformMessage(callback, assetData);
|
||||
}, onError: (dynamic error) {
|
||||
html.window.console
|
||||
.warn('Error while trying to load an asset: $error');
|
||||
printWarning('Error while trying to load an asset: $error');
|
||||
_replyToPlatformMessage(callback, null);
|
||||
});
|
||||
return;
|
||||
@ -371,7 +380,10 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
// TODO(gspencergoog): As multi-window support expands, the pop call
|
||||
// will need to include the window ID. Right now only one window is
|
||||
// supported.
|
||||
(_windows[0] as EngineFlutterWindow).browserHistory.exit().then((_) {
|
||||
(_windows[0] as EngineFlutterWindow)
|
||||
.browserHistory
|
||||
.exit()
|
||||
.then((_) {
|
||||
_replyToPlatformMessage(
|
||||
callback, codec.encodeSuccessEnvelope(true));
|
||||
});
|
||||
@ -379,13 +391,15 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
case 'HapticFeedback.vibrate':
|
||||
final String type = decoded.arguments;
|
||||
domRenderer.vibrate(_getHapticFeedbackDuration(type));
|
||||
_replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
|
||||
_replyToPlatformMessage(
|
||||
callback, codec.encodeSuccessEnvelope(true));
|
||||
return;
|
||||
case 'SystemChrome.setApplicationSwitcherDescription':
|
||||
final Map<String, dynamic> arguments = decoded.arguments;
|
||||
domRenderer.setTitle(arguments['label']);
|
||||
domRenderer.setThemeColor(ui.Color(arguments['primaryColor']));
|
||||
_replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
|
||||
_replyToPlatformMessage(
|
||||
callback, codec.encodeSuccessEnvelope(true));
|
||||
return;
|
||||
case 'SystemChrome.setPreferredOrientations':
|
||||
final List<dynamic> arguments = decoded.arguments;
|
||||
@ -396,7 +410,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
return;
|
||||
case 'SystemSound.play':
|
||||
// There are no default system sounds on web.
|
||||
_replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
|
||||
_replyToPlatformMessage(
|
||||
callback, codec.encodeSuccessEnvelope(true));
|
||||
return;
|
||||
case 'Clipboard.setData':
|
||||
ClipboardMessageHandler().setDataMethodCall(decoded, callback);
|
||||
@ -454,10 +469,13 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
// TODO(gspencergoog): As multi-window support expands, the navigation call
|
||||
// will need to include the window ID. Right now only one window is
|
||||
// supported.
|
||||
(_windows[0] as EngineFlutterWindow).handleNavigationMessage(data).then((bool handled) {
|
||||
(_windows[0] as EngineFlutterWindow)
|
||||
.handleNavigationMessage(data)
|
||||
.then((bool handled) {
|
||||
if (handled) {
|
||||
const MethodCodec codec = JSONMethodCodec();
|
||||
_replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
|
||||
_replyToPlatformMessage(
|
||||
callback, codec.encodeSuccessEnvelope(true));
|
||||
} else {
|
||||
callback?.call(null);
|
||||
}
|
||||
@ -481,8 +499,6 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
_replyToPlatformMessage(callback, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _getHapticFeedbackDuration(String type) {
|
||||
switch (type) {
|
||||
case 'HapticFeedbackType.lightImpact':
|
||||
@ -508,8 +524,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
@override
|
||||
void scheduleFrame() {
|
||||
if (scheduleFrameCallback == null) {
|
||||
throw new Exception(
|
||||
'scheduleFrameCallback must be initialized first.');
|
||||
throw new Exception('scheduleFrameCallback must be initialized first.');
|
||||
}
|
||||
scheduleFrameCallback!();
|
||||
}
|
||||
@ -561,13 +576,15 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
}
|
||||
|
||||
/// Additional accessibility features that may be enabled by the platform.
|
||||
ui.AccessibilityFeatures get accessibilityFeatures => configuration.accessibilityFeatures;
|
||||
ui.AccessibilityFeatures get accessibilityFeatures =>
|
||||
configuration.accessibilityFeatures;
|
||||
|
||||
/// A callback that is invoked when the value of [accessibilityFeatures] changes.
|
||||
///
|
||||
/// The framework invokes this callback in the same zone in which the
|
||||
/// callback was set.
|
||||
ui.VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged;
|
||||
ui.VoidCallback? get onAccessibilityFeaturesChanged =>
|
||||
_onAccessibilityFeaturesChanged;
|
||||
ui.VoidCallback? _onAccessibilityFeaturesChanged;
|
||||
Zone? _onAccessibilityFeaturesChangedZone;
|
||||
set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) {
|
||||
@ -578,7 +595,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
/// Engine code should use this method instead of the callback directly.
|
||||
/// Otherwise zones won't work properly.
|
||||
void invokeOnAccessibilityFeaturesChanged() {
|
||||
invoke(_onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone);
|
||||
invoke(
|
||||
_onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone);
|
||||
}
|
||||
|
||||
/// Change the retained semantics data about this window.
|
||||
@ -604,7 +622,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
///
|
||||
/// * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages,
|
||||
/// which explains browser quirks in the implementation notes.
|
||||
ui.Locale get locale => locales.isEmpty ? const ui.Locale.fromSubtags() : locales.first;
|
||||
ui.Locale get locale =>
|
||||
locales.isEmpty ? const ui.Locale.fromSubtags() : locales.first;
|
||||
|
||||
/// The full system-reported supported locales of the device.
|
||||
///
|
||||
@ -796,7 +815,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
///
|
||||
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
|
||||
/// observe when this callback is invoked.
|
||||
ui.VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged;
|
||||
ui.VoidCallback? get onPlatformBrightnessChanged =>
|
||||
_onPlatformBrightnessChanged;
|
||||
ui.VoidCallback? _onPlatformBrightnessChanged;
|
||||
Zone? _onPlatformBrightnessChangedZone;
|
||||
set onPlatformBrightnessChanged(ui.VoidCallback? callback) {
|
||||
@ -890,7 +910,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
/// * [SystemChannels.navigation], which handles subsequent navigation
|
||||
/// requests from the embedder.
|
||||
String get defaultRouteName {
|
||||
return _defaultRouteName ??= (_windows[0]! as EngineFlutterWindow).browserHistory.currentPath;
|
||||
return _defaultRouteName ??=
|
||||
(_windows[0]! as EngineFlutterWindow).browserHistory.currentPath;
|
||||
}
|
||||
|
||||
/// Lazily initialized when the `defaultRouteName` getter is invoked.
|
||||
@ -961,7 +982,8 @@ void invoke1<A>(void callback(A a)?, Zone? zone, A arg) {
|
||||
}
|
||||
|
||||
/// Invokes [callback] inside the given [zone] passing it [arg1] and [arg2].
|
||||
void invoke2<A1, A2>(void Function(A1 a1, A2 a2)? callback, Zone? zone, A1 arg1, A2 arg2) {
|
||||
void invoke2<A1, A2>(
|
||||
void Function(A1 a1, A2 a2)? callback, Zone? zone, A1 arg1, A2 arg2) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
@ -978,7 +1000,8 @@ void invoke2<A1, A2>(void Function(A1 a1, A2 a2)? callback, Zone? zone, A1 arg1,
|
||||
}
|
||||
|
||||
/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3].
|
||||
void invoke3<A1, A2, A3>(void Function(A1 a1, A2 a2, A3 a3)? callback, Zone? zone, A1 arg1, A2 arg2, A3 arg3) {
|
||||
void invoke3<A1, A2, A3>(void Function(A1 a1, A2 a2, A3 a3)? callback,
|
||||
Zone? zone, A1 arg1, A2 arg2, A3 arg3) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -29,8 +29,7 @@ class FontCollection {
|
||||
byteData = await assetManager.load('FontManifest.json');
|
||||
} on AssetManagerException catch (e) {
|
||||
if (e.httpStatus == 404) {
|
||||
html.window.console
|
||||
.warn('Font manifest does not exist at `${e.url}` – ignoring.');
|
||||
printWarning('Font manifest does not exist at `${e.url}` – ignoring.');
|
||||
return;
|
||||
} else {
|
||||
rethrow;
|
||||
@ -50,7 +49,8 @@ class FontCollection {
|
||||
_assetFontManager = _PolyfillFontManager();
|
||||
}
|
||||
|
||||
for (Map<String, dynamic> fontFamily in fontManifest.cast<Map<String, dynamic>>()) {
|
||||
for (Map<String, dynamic> fontFamily
|
||||
in fontManifest.cast<Map<String, dynamic>>()) {
|
||||
final String? family = fontFamily['family'];
|
||||
final List<dynamic> fontAssets = fontFamily['fonts'];
|
||||
|
||||
@ -78,8 +78,8 @@ class FontCollection {
|
||||
_testFontManager = FontManager();
|
||||
_testFontManager!.registerAsset(
|
||||
_ahemFontFamily, 'url($_ahemFontUrl)', const <String, String>{});
|
||||
_testFontManager!.registerAsset(
|
||||
_robotoFontFamily, 'url($_robotoTestFontUrl)', const <String, String>{});
|
||||
_testFontManager!.registerAsset(_robotoFontFamily,
|
||||
'url($_robotoTestFontUrl)', const <String, String>{});
|
||||
}
|
||||
|
||||
/// Returns a [Future] that completes when the registered fonts are loaded
|
||||
@ -180,12 +180,10 @@ class FontManager {
|
||||
_fontLoadingFutures.add(fontFace.load().then((_) {
|
||||
html.document.fonts!.add(fontFace);
|
||||
}, onError: (dynamic e) {
|
||||
html.window.console
|
||||
.warn('Error while trying to load font family "$family":\n$e');
|
||||
printWarning('Error while trying to load font family "$family":\n$e');
|
||||
}));
|
||||
} catch (e) {
|
||||
html.window.console
|
||||
.warn('Error while loading font family "$family":\n$e');
|
||||
printWarning('Error while loading font family "$family":\n$e');
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,8 +239,8 @@ class _PolyfillFontManager extends FontManager {
|
||||
paragraph.style.position = 'absolute';
|
||||
paragraph.style.visibility = 'hidden';
|
||||
paragraph.style.fontSize = '72px';
|
||||
final String fallbackFontName = browserEngine == BrowserEngine.ie11 ?
|
||||
'Times New Roman' : 'sans-serif';
|
||||
final String fallbackFontName =
|
||||
browserEngine == BrowserEngine.ie11 ? 'Times New Roman' : 'sans-serif';
|
||||
paragraph.style.fontFamily = fallbackFontName;
|
||||
if (descriptors['style'] != null) {
|
||||
paragraph.style.fontStyle = descriptors['style'];
|
||||
@ -309,5 +307,8 @@ class _PolyfillFontManager extends FontManager {
|
||||
}
|
||||
}
|
||||
|
||||
final bool supportsFontLoadingApi = js_util.hasProperty(html.window, 'FontFace');
|
||||
final bool supportsFontsClearApi = js_util.hasProperty(html.document, 'fonts') && js_util.hasProperty(html.document.fonts!, 'clear');
|
||||
final bool supportsFontLoadingApi =
|
||||
js_util.hasProperty(html.window, 'FontFace');
|
||||
final bool supportsFontsClearApi =
|
||||
js_util.hasProperty(html.document, 'fonts') &&
|
||||
js_util.hasProperty(html.document.fonts!, 'clear');
|
||||
|
||||
@ -595,3 +595,9 @@ int clampInt(int value, int min, int max) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a warning message to the console.
|
||||
///
|
||||
/// This function can be overridden in tests. This could be useful, for example,
|
||||
/// to verify that warnings are printed under certain circumstances.
|
||||
void Function(String) printWarning = html.window.console.warn;
|
||||
|
||||
@ -16,7 +16,7 @@ void main() {
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
group('LayerScene', () {
|
||||
group('$LayerScene', () {
|
||||
setUpAll(() async {
|
||||
await ui.webOnlyInitializePlatform();
|
||||
});
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.6
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
group('$SkiaFontCollection', () {
|
||||
List<String> warnings = <String>[];
|
||||
void Function(String) oldPrintWarning;
|
||||
|
||||
setUpAll(() async {
|
||||
await initializeCanvasKit();
|
||||
oldPrintWarning = printWarning;
|
||||
printWarning = (String warning) {
|
||||
warnings.add(warning);
|
||||
};
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
printWarning = oldPrintWarning;
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
warnings.clear();
|
||||
});
|
||||
|
||||
test('logs no warnings with the default mock asset manager', () {
|
||||
final SkiaFontCollection fontCollection = SkiaFontCollection();
|
||||
final WebOnlyMockAssetManager mockAssetManager =
|
||||
WebOnlyMockAssetManager();
|
||||
expect(fontCollection.registerFonts(mockAssetManager), completes);
|
||||
expect(fontCollection.ensureFontsLoaded(), completes);
|
||||
expect(warnings, isEmpty);
|
||||
});
|
||||
|
||||
test('logs a warning if one of the registered fonts is invalid', () async {
|
||||
final SkiaFontCollection fontCollection = SkiaFontCollection();
|
||||
final WebOnlyMockAssetManager mockAssetManager =
|
||||
WebOnlyMockAssetManager();
|
||||
mockAssetManager.defaultFontManifest = '''
|
||||
[
|
||||
{
|
||||
"family":"Roboto",
|
||||
"fonts":[{"asset":"packages/ui/assets/Roboto-Regular.ttf"}]
|
||||
},
|
||||
{
|
||||
"family": "BrokenFont",
|
||||
"fonts":[{"asset":"packages/bogus/BrokenFont.ttf"}]
|
||||
}
|
||||
]
|
||||
''';
|
||||
// It should complete without error, but emit a warning about BrokenFont.
|
||||
await fontCollection.registerFonts(mockAssetManager);
|
||||
await fontCollection.ensureFontsLoaded();
|
||||
expect(
|
||||
warnings,
|
||||
containsAllInOrder(
|
||||
<String>[
|
||||
'Failed to load font BrokenFont at packages/bogus/BrokenFont.ttf',
|
||||
'Verify that packages/bogus/BrokenFont.ttf contains a valid font.',
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
// TODO: https://github.com/flutter/flutter/issues/60040
|
||||
}, skip: isIosSafari);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user