Wait until all scripts are loaded in the page before running main for the DDC library bundle format (#162707)

https://github.com/flutter/flutter/issues/162567

- Uses the `bootstrapScript` field in `loadConfig` to run a script after
all scripts have loaded.
- This script just calls a callback that is set up beforehand and calls
main.
- Modifies the callback that calls `dartDevEmbedder.runMain` to wait
until both DWDS called main and all scripts have loaded.
- Unskips hot reload tests now that the race condition should no longer
exist.

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.
This commit is contained in:
Srujan Gaddam 2025-02-04 17:12:29 -08:00 committed by GitHub
parent 44785daf01
commit d3c96c65e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 52 additions and 36 deletions

View File

@ -1087,6 +1087,10 @@ class WebDevFS implements DevFS {
generateLoadingIndicator: enableDwds, generateLoadingIndicator: enableDwds,
), ),
); );
const String onLoadEndBootstrap = 'on_load_end_bootstrap.js';
if (ddcModuleSystem) {
webAssetServer.writeFile(onLoadEndBootstrap, generateDDCLibraryBundleOnLoadEndBootstrap());
}
webAssetServer.writeFile( webAssetServer.writeFile(
'main_module.bootstrap.js', 'main_module.bootstrap.js',
ddcModuleSystem ddcModuleSystem
@ -1094,6 +1098,7 @@ class WebDevFS implements DevFS {
entrypoint: entrypoint, entrypoint: entrypoint,
nullAssertions: nullAssertions, nullAssertions: nullAssertions,
nativeNullAssertions: nativeNullAssertions, nativeNullAssertions: nativeNullAssertions,
onLoadEndBootstrap: onLoadEndBootstrap,
) )
: generateMainModule( : generateMainModule(
entrypoint: entrypoint, entrypoint: entrypoint,

View File

@ -505,10 +505,13 @@ String generateDDCMainModule({
'''; ''';
} }
const String _onLoadEndCallback = r'$onLoadEndCallback';
String generateDDCLibraryBundleMainModule({ String generateDDCLibraryBundleMainModule({
required String entrypoint, required String entrypoint,
required bool nullAssertions, required bool nullAssertions,
required bool nativeNullAssertions, required bool nativeNullAssertions,
required String onLoadEndBootstrap,
}) { }) {
// The typo below in "EXTENTION" is load-bearing, package:build depends on it. // The typo below in "EXTENTION" is load-bearing, package:build depends on it.
return ''' return '''
@ -519,21 +522,48 @@ String generateDDCLibraryBundleMainModule({
dartDevEmbedder.debugger.registerDevtoolsFormatter(); dartDevEmbedder.debugger.registerDevtoolsFormatter();
let child = {}; // Set up a final script that lets us know when all scripts have been loaded.
child.main = function() { let onLoadEndSrc = '$onLoadEndBootstrap';
let sdkOptions = { window.\$dartLoader.loadConfig.bootstrapScript = {
nonNullAsserts: $nullAssertions, src: onLoadEndSrc,
nativeNonNullAsserts: $nativeNullAssertions, id: onLoadEndSrc,
}; };
dartDevEmbedder.runMain(appName, sdkOptions); window.\$dartLoader.loadConfig.tryLoadBootstrapScript = true;
let dwdsCalledMain = false;
let dartSrcsLoaded = false;
let runMainWhenBoth = function() {
// Only run once both all the scripts are loaded and DWDS triggers main.
if (dwdsCalledMain && dartSrcsLoaded) {
let sdkOptions = {
nonNullAsserts: $nullAssertions,
nativeNonNullAsserts: $nativeNullAssertions,
};
dartDevEmbedder.runMain(appName, sdkOptions);
}
}
// DWDS expects the main function to be lowercase.
// TODO(srujzs): DWDS should be more robust to not have to require that.
dwdsmain = function() {
dwdsCalledMain = true;
runMainWhenBoth();
}
// Should be called by $onLoadEndBootstrap once all the scripts have been
// loaded.
window.$_onLoadEndCallback = function() {
dartSrcsLoaded = true;
runMainWhenBoth();
} }
/* MAIN_EXTENSION_MARKER */ /* MAIN_EXTENSION_MARKER */
child.main(); dwdsmain();
})(); })();
'''; ''';
} }
String generateDDCLibraryBundleOnLoadEndBootstrap() {
return '''window.$_onLoadEndCallback();''';
}
/// Generate a synthetic main module which captures the application's main /// Generate a synthetic main module which captures the application's main
/// method. /// method.
/// ///

View File

@ -273,6 +273,7 @@ void main() {
entrypoint: 'main.js', entrypoint: 'main.js',
nullAssertions: false, nullAssertions: false,
nativeNullAssertions: false, nativeNullAssertions: false,
onLoadEndBootstrap: 'on_load_end_bootstrap.js',
); );
// bootstrap main module has correct defined module. // bootstrap main module has correct defined module.
expect(result, contains('let appName = "org-dartlang-app:/main.js";')); expect(result, contains('let appName = "org-dartlang-app:/main.js";'));
@ -284,6 +285,7 @@ void main() {
entrypoint: 'main.js', entrypoint: 'main.js',
nullAssertions: true, nullAssertions: true,
nativeNullAssertions: true, nativeNullAssertions: true,
onLoadEndBootstrap: 'on_load_end_bootstrap.js',
); );
expect(result, contains('nonNullAsserts: true')); expect(result, contains('nonNullAsserts: true'));
@ -295,6 +297,7 @@ void main() {
entrypoint: 'main.js', entrypoint: 'main.js',
nullAssertions: false, nullAssertions: false,
nativeNullAssertions: false, nativeNullAssertions: false,
onLoadEndBootstrap: 'on_load_end_bootstrap.js',
); );
expect(result, contains('nonNullAsserts: false')); expect(result, contains('nonNullAsserts: false'));

View File

@ -12,11 +12,7 @@ import '../test_driver.dart';
import '../test_utils.dart'; import '../test_utils.dart';
import 'hot_reload_project.dart'; import 'hot_reload_project.dart';
void testAll({ void testAll({bool chrome = false, List<String> additionalCommandArgs = const <String>[]}) {
bool chrome = false,
List<String> additionalCommandArgs = const <String>[],
Object? skip = false,
}) {
group('chrome: $chrome' group('chrome: $chrome'
'${additionalCommandArgs.isEmpty ? '' : ' with args: $additionalCommandArgs'}', () { '${additionalCommandArgs.isEmpty ? '' : ' with args: $additionalCommandArgs'}', () {
late Directory tempDir; late Directory tempDir;
@ -235,7 +231,7 @@ void testAll({
// isolates, so this test will wait forever. // isolates, so this test will wait forever.
skip: chrome, skip: chrome,
); );
}, skip: skip); });
} }
bool _isHotReloadCompletionEvent(Map<String, Object?>? event) { bool _isHotReloadCompletionEvent(Map<String, Object?>? event) {

View File

@ -11,11 +11,7 @@ import '../test_driver.dart';
import '../test_utils.dart'; import '../test_utils.dart';
import 'hot_reload_with_asset.dart'; import 'hot_reload_with_asset.dart';
void testAll({ void testAll({bool chrome = false, List<String> additionalCommandArgs = const <String>[]}) {
bool chrome = false,
List<String> additionalCommandArgs = const <String>[],
Object? skip = false,
}) {
group('chrome: $chrome' group('chrome: $chrome'
'${additionalCommandArgs.isEmpty ? '' : ' with args: $additionalCommandArgs'}', () { '${additionalCommandArgs.isEmpty ? '' : ' with args: $additionalCommandArgs'}', () {
late Directory tempDir; late Directory tempDir;
@ -86,5 +82,5 @@ void testAll({
await flutter.hotRestart(); await flutter.hotRestart();
await onSecondLoad.future; await onSecondLoad.future;
}); });
}, skip: skip); });
} }

View File

@ -13,11 +13,7 @@ import '../test_utils.dart';
// This test verifies that we can hot reload a stateless widget into a // This test verifies that we can hot reload a stateless widget into a
// stateful one and back. // stateful one and back.
void testAll({ void testAll({bool chrome = false, List<String> additionalCommandArgs = const <String>[]}) {
bool chrome = false,
List<String> additionalCommandArgs = const <String>[],
Object? skip = false,
}) {
group('chrome: $chrome' group('chrome: $chrome'
'${additionalCommandArgs.isEmpty ? '' : ' with args: $additionalCommandArgs'}', () { '${additionalCommandArgs.isEmpty ? '' : ' with args: $additionalCommandArgs'}', () {
late Directory tempDir; late Directory tempDir;
@ -60,5 +56,5 @@ void testAll({
expect(logs, contains('STATEFUL')); expect(logs, contains('STATEFUL'));
await subscription.cancel(); await subscription.cancel();
}); });
}, skip: skip); });
} }

View File

@ -5,8 +5,6 @@
@Tags(<String>['flutter-test-driver']) @Tags(<String>['flutter-test-driver'])
library; library;
import 'dart:io';
import '../integration.shard/test_data/hot_reload_errors_common.dart'; import '../integration.shard/test_data/hot_reload_errors_common.dart';
import '../src/common.dart'; import '../src/common.dart';
@ -19,7 +17,5 @@ void main() {
// TODO(srujzs): Remove this custom message once we have the delta inspector emitting the same // TODO(srujzs): Remove this custom message once we have the delta inspector emitting the same
// string as the VM. // string as the VM.
constClassFieldRemovalErrorMessage: 'Const class cannot remove fields', constClassFieldRemovalErrorMessage: 'Const class cannot remove fields',
// https://github.com/flutter/flutter/issues/162567
skip: Platform.isWindows,
); );
} }

View File

@ -14,7 +14,5 @@ void main() {
additionalCommandArgs: <String>[ additionalCommandArgs: <String>[
'--extra-front-end-options=--dartdevc-canary,--dartdevc-module-format=ddc', '--extra-front-end-options=--dartdevc-canary,--dartdevc-module-format=ddc',
], ],
// https://github.com/flutter/flutter/issues/162567
skip: true,
); );
} }

View File

@ -14,7 +14,5 @@ void main() {
additionalCommandArgs: <String>[ additionalCommandArgs: <String>[
'--extra-front-end-options=--dartdevc-canary,--dartdevc-module-format=ddc', '--extra-front-end-options=--dartdevc-canary,--dartdevc-module-format=ddc',
], ],
// https://github.com/flutter/flutter/issues/162567
skip: true,
); );
} }

View File

@ -14,7 +14,5 @@ void main() {
additionalCommandArgs: <String>[ additionalCommandArgs: <String>[
'--extra-front-end-options=--dartdevc-canary,--dartdevc-module-format=ddc', '--extra-front-end-options=--dartdevc-canary,--dartdevc-module-format=ddc',
], ],
// https://github.com/flutter/flutter/issues/162567
skip: true,
); );
} }