Unverified Commit 921c8030 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] support hot reload of font assets (#109091)

parent f9391522
......@@ -20,6 +20,8 @@ import 'compile.dart';
import 'convert.dart' show base64, utf8;
import 'vmservice.dart';
const String _kFontManifest = 'FontManifest.json';
class DevFSConfig {
/// Should DevFS assume that symlink targets are stable?
bool cacheSymlinks = false;
......@@ -485,6 +487,9 @@ class DevFS {
// A flag to indicate whether we have called `setAssetDirectory` on the target device.
bool hasSetAssetDirectory = false;
/// Whether the font manifest was uploaded during [update].
bool didUpdateFontManifest = false;
List<Uri> sources = <Uri>[];
DateTime? lastCompiled;
DateTime? _previousCompiled;
......@@ -589,6 +594,7 @@ class DevFS {
assert(trackWidgetCreation != null);
assert(generator != null);
final DateTime candidateCompileTime = DateTime.now();
didUpdateFontManifest = false;
lastPackageConfig = packageConfig;
_widgetCacheOutputFile = _fileSystem.file('$dillOutputPath.incremental.dill.widget_cache');
......@@ -644,6 +650,11 @@ class DevFS {
if (deviceUri.path.startsWith(assetBuildDirPrefix)) {
archivePath = deviceUri.path.substring(assetBuildDirPrefix.length);
}
// If the font manifest is updated, mark this as true so the hot runner
// can invoke a service extension to force the engine to reload fonts.
if (archivePath == _kFontManifest) {
didUpdateFontManifest = true;
}
if (bundle.entryKinds[archivePath] == AssetKind.shader) {
final Future<DevFSContent?> pending = shaderCompiler.recompileShader(content);
......
......@@ -677,6 +677,9 @@ class WebDevFS implements DevFS {
@override
bool hasSetAssetDirectory = false;
@override
bool didUpdateFontManifest = false;
Future<DebugConnection>? _cachedExtensionFuture;
StreamSubscription<void>? _connectedApps;
......
......@@ -1032,7 +1032,7 @@ class HotRunner extends ResidentRunner {
@visibleForTesting
Future<void> evictDirtyAssets() async {
final List<Future<Map<String, dynamic>?>> futures = <Future<Map<String, dynamic>>>[];
final List<Future<void>> futures = <Future<void>>[];
for (final FlutterDevice? device in flutterDevices) {
if (device!.devFS!.assetPathsToEvict.isEmpty && device.devFS!.shaderPathsToEvict.isEmpty) {
continue;
......@@ -1060,6 +1060,14 @@ class HotRunner extends ResidentRunner {
globals.printError('Application isolate not found for $device');
continue;
}
if (device.devFS!.didUpdateFontManifest) {
futures.add(device.vmService!.reloadAssetFonts(
isolateId: views.first.uiIsolate!.id!,
viewId: views.first.id,
));
}
for (final String assetPath in device.devFS!.assetPathsToEvict) {
futures.add(
device.vmService!
......@@ -1081,7 +1089,7 @@ class HotRunner extends ResidentRunner {
device.devFS!.assetPathsToEvict.clear();
device.devFS!.shaderPathsToEvict.clear();
}
await Future.wait<Map<String, Object?>?>(futures);
await Future.wait<void>(futures);
}
@override
......
......@@ -24,6 +24,7 @@ const String kListViewsMethod = '_flutter.listViews';
const String kScreenshotSkpMethod = '_flutter.screenshotSkp';
const String kScreenshotMethod = '_flutter.screenshot';
const String kRenderFrameWithRasterStatsMethod = '_flutter.renderFrameWithRasterStats';
const String kReloadAssetFonts = '_flutter.reloadAssetFonts';
/// The error response code from an unrecoverable compilation failure.
const int kIsolateReloadBarred = 1005;
......@@ -880,6 +881,20 @@ class FlutterVmService {
}
}
/// Tell the provided flutter view that the font manifest has been updated
/// and asset fonts should be reloaded.
Future<void> reloadAssetFonts({
required String isolateId,
required String viewId,
}) async {
await callMethodWrapper(
kReloadAssetFonts,
isolateId: isolateId, args: <String, Object?>{
'viewId': viewId,
},
);
}
/// Waits for a signal from the VM service that [extensionName] is registered.
///
/// Looks at the list of loaded extensions for first Flutter view, as well as
......
......@@ -643,6 +643,57 @@ void main() {
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('DevFS tracks when FontManifest is updated', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[createDevFSRequest],
httpAddress: Uri.parse('http://localhost'),
);
final BufferLogger logger = BufferLogger.test();
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: logger,
osUtils: FakeOperatingSystemUtils(),
httpClient: FakeHttpClient.any(),
);
await devFS.create();
expect(devFS.didUpdateFontManifest, false);
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
..onRecompile = (Uri mainUri, List<Uri>? invalidatedFiles) async {
fileSystem.file('lib/foo.dill')
..createSync(recursive: true)
..writeAsBytesSync(<int>[1, 2, 3, 4, 5]);
return const CompilerOutput('lib/foo.dill', 0, <Uri>[]);
};
final FakeBundle bundle = FakeBundle()
..entries['FontManifest.json'] = DevFSByteContent(<int>[1, 2, 3, 4]);
final UpdateFSReport report = await devFS.update(
mainUri: Uri.parse('lib/main.dart'),
generator: residentCompiler,
dillOutputPath: 'lib/foo.dill',
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
packageConfig: PackageConfig.empty,
shaderCompiler: const FakeShaderCompiler(),
bundle: bundle,
);
expect(report.success, true);
expect(devFS.shaderPathsToEvict, <String>{});
expect(devFS.assetPathsToEvict, <String>{'FontManifest.json'});
expect(devFS.didUpdateFontManifest, true);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
});
}
......
......@@ -2633,6 +2633,9 @@ class FakeDevFS extends Fake implements DevFS {
@override
Set<String> shaderPathsToEvict = <String>{};
@override
bool didUpdateFontManifest = false;
UpdateFSReport nextUpdateReport = UpdateFSReport(success: true);
@override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment