Skip to content

Commit 921c803

Browse files
author
Jonah Williams
authored
[flutter_tools] support hot reload of font assets (#109091)
1 parent f939152 commit 921c803

File tree

6 files changed

+93
-2
lines changed

6 files changed

+93
-2
lines changed

packages/flutter_tools/lib/src/devfs.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import 'compile.dart';
2020
import 'convert.dart' show base64, utf8;
2121
import 'vmservice.dart';
2222

23+
const String _kFontManifest = 'FontManifest.json';
24+
2325
class DevFSConfig {
2426
/// Should DevFS assume that symlink targets are stable?
2527
bool cacheSymlinks = false;
@@ -485,6 +487,9 @@ class DevFS {
485487
// A flag to indicate whether we have called `setAssetDirectory` on the target device.
486488
bool hasSetAssetDirectory = false;
487489

490+
/// Whether the font manifest was uploaded during [update].
491+
bool didUpdateFontManifest = false;
492+
488493
List<Uri> sources = <Uri>[];
489494
DateTime? lastCompiled;
490495
DateTime? _previousCompiled;
@@ -589,6 +594,7 @@ class DevFS {
589594
assert(trackWidgetCreation != null);
590595
assert(generator != null);
591596
final DateTime candidateCompileTime = DateTime.now();
597+
didUpdateFontManifest = false;
592598
lastPackageConfig = packageConfig;
593599
_widgetCacheOutputFile = _fileSystem.file('$dillOutputPath.incremental.dill.widget_cache');
594600

@@ -644,6 +650,11 @@ class DevFS {
644650
if (deviceUri.path.startsWith(assetBuildDirPrefix)) {
645651
archivePath = deviceUri.path.substring(assetBuildDirPrefix.length);
646652
}
653+
// If the font manifest is updated, mark this as true so the hot runner
654+
// can invoke a service extension to force the engine to reload fonts.
655+
if (archivePath == _kFontManifest) {
656+
didUpdateFontManifest = true;
657+
}
647658

648659
if (bundle.entryKinds[archivePath] == AssetKind.shader) {
649660
final Future<DevFSContent?> pending = shaderCompiler.recompileShader(content);

packages/flutter_tools/lib/src/isolated/devfs_web.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,9 @@ class WebDevFS implements DevFS {
677677
@override
678678
bool hasSetAssetDirectory = false;
679679

680+
@override
681+
bool didUpdateFontManifest = false;
682+
680683
Future<DebugConnection>? _cachedExtensionFuture;
681684
StreamSubscription<void>? _connectedApps;
682685

packages/flutter_tools/lib/src/run_hot.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ class HotRunner extends ResidentRunner {
10321032

10331033
@visibleForTesting
10341034
Future<void> evictDirtyAssets() async {
1035-
final List<Future<Map<String, dynamic>?>> futures = <Future<Map<String, dynamic>>>[];
1035+
final List<Future<void>> futures = <Future<void>>[];
10361036
for (final FlutterDevice? device in flutterDevices) {
10371037
if (device!.devFS!.assetPathsToEvict.isEmpty && device.devFS!.shaderPathsToEvict.isEmpty) {
10381038
continue;
@@ -1060,6 +1060,14 @@ class HotRunner extends ResidentRunner {
10601060
globals.printError('Application isolate not found for $device');
10611061
continue;
10621062
}
1063+
1064+
if (device.devFS!.didUpdateFontManifest) {
1065+
futures.add(device.vmService!.reloadAssetFonts(
1066+
isolateId: views.first.uiIsolate!.id!,
1067+
viewId: views.first.id,
1068+
));
1069+
}
1070+
10631071
for (final String assetPath in device.devFS!.assetPathsToEvict) {
10641072
futures.add(
10651073
device.vmService!
@@ -1081,7 +1089,7 @@ class HotRunner extends ResidentRunner {
10811089
device.devFS!.assetPathsToEvict.clear();
10821090
device.devFS!.shaderPathsToEvict.clear();
10831091
}
1084-
await Future.wait<Map<String, Object?>?>(futures);
1092+
await Future.wait<void>(futures);
10851093
}
10861094

10871095
@override

packages/flutter_tools/lib/src/vmservice.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const String kListViewsMethod = '_flutter.listViews';
2424
const String kScreenshotSkpMethod = '_flutter.screenshotSkp';
2525
const String kScreenshotMethod = '_flutter.screenshot';
2626
const String kRenderFrameWithRasterStatsMethod = '_flutter.renderFrameWithRasterStats';
27+
const String kReloadAssetFonts = '_flutter.reloadAssetFonts';
2728

2829
/// The error response code from an unrecoverable compilation failure.
2930
const int kIsolateReloadBarred = 1005;
@@ -880,6 +881,20 @@ class FlutterVmService {
880881
}
881882
}
882883

884+
/// Tell the provided flutter view that the font manifest has been updated
885+
/// and asset fonts should be reloaded.
886+
Future<void> reloadAssetFonts({
887+
required String isolateId,
888+
required String viewId,
889+
}) async {
890+
await callMethodWrapper(
891+
kReloadAssetFonts,
892+
isolateId: isolateId, args: <String, Object?>{
893+
'viewId': viewId,
894+
},
895+
);
896+
}
897+
883898
/// Waits for a signal from the VM service that [extensionName] is registered.
884899
///
885900
/// Looks at the list of loaded extensions for first Flutter view, as well as

packages/flutter_tools/test/general.shard/devfs_test.dart

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,57 @@ void main() {
643643
FileSystem: () => fileSystem,
644644
ProcessManager: () => processManager,
645645
});
646+
647+
testUsingContext('DevFS tracks when FontManifest is updated', () async {
648+
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
649+
requests: <VmServiceExpectation>[createDevFSRequest],
650+
httpAddress: Uri.parse('http://localhost'),
651+
);
652+
final BufferLogger logger = BufferLogger.test();
653+
final DevFS devFS = DevFS(
654+
fakeVmServiceHost.vmService,
655+
'test',
656+
fileSystem.currentDirectory,
657+
fileSystem: fileSystem,
658+
logger: logger,
659+
osUtils: FakeOperatingSystemUtils(),
660+
httpClient: FakeHttpClient.any(),
661+
);
662+
663+
await devFS.create();
664+
665+
expect(devFS.didUpdateFontManifest, false);
666+
667+
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
668+
..onRecompile = (Uri mainUri, List<Uri>? invalidatedFiles) async {
669+
fileSystem.file('lib/foo.dill')
670+
..createSync(recursive: true)
671+
..writeAsBytesSync(<int>[1, 2, 3, 4, 5]);
672+
return const CompilerOutput('lib/foo.dill', 0, <Uri>[]);
673+
};
674+
final FakeBundle bundle = FakeBundle()
675+
..entries['FontManifest.json'] = DevFSByteContent(<int>[1, 2, 3, 4]);
676+
677+
final UpdateFSReport report = await devFS.update(
678+
mainUri: Uri.parse('lib/main.dart'),
679+
generator: residentCompiler,
680+
dillOutputPath: 'lib/foo.dill',
681+
pathToReload: 'lib/foo.txt.dill',
682+
trackWidgetCreation: false,
683+
invalidatedFiles: <Uri>[],
684+
packageConfig: PackageConfig.empty,
685+
shaderCompiler: const FakeShaderCompiler(),
686+
bundle: bundle,
687+
);
688+
689+
expect(report.success, true);
690+
expect(devFS.shaderPathsToEvict, <String>{});
691+
expect(devFS.assetPathsToEvict, <String>{'FontManifest.json'});
692+
expect(devFS.didUpdateFontManifest, true);
693+
}, overrides: <Type, Generator>{
694+
FileSystem: () => fileSystem,
695+
ProcessManager: () => processManager,
696+
});
646697
});
647698
}
648699

packages/flutter_tools/test/general.shard/resident_runner_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2633,6 +2633,9 @@ class FakeDevFS extends Fake implements DevFS {
26332633
@override
26342634
Set<String> shaderPathsToEvict = <String>{};
26352635

2636+
@override
2637+
bool didUpdateFontManifest = false;
2638+
26362639
UpdateFSReport nextUpdateReport = UpdateFSReport(success: true);
26372640

26382641
@override

0 commit comments

Comments
 (0)