de18b40933
Exposes two new methods `hotRestartBegin()` and `hotRestartEnd()` in the `DartDevEmbedder`. This provides a more customizable loading of sources across the variety of environments we are supporting. Change-Id: Id7a35695234f0625fe4de1d67c9d5a8055bad461 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500240 Commit-Queue: Nicholas Shahan <nshahan@google.com> Reviewed-by: Srujan Gaddam <srujzs@google.com>
615 lines
20 KiB
Dart
615 lines
20 KiB
Dart
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
|
|
// for details. 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:ffi';
|
|
import 'dart:io';
|
|
|
|
/// Maps a generation to its modified libraries' ID and JS URL.
|
|
///
|
|
/// Example:
|
|
/// {
|
|
/// 0: [['main', '/some/path/main.0.js']],
|
|
/// 1: [['main', '/some/path/main.1.js'], ['lib', '/some/path/lib.1.js']]
|
|
/// }
|
|
typedef FileDataPerGeneration = Map<String, List<List<String>>>;
|
|
|
|
final _encoder = JsonEncoder.withIndent(' ');
|
|
|
|
class D8Configuration {
|
|
final Uri sdkRoot;
|
|
final Uri binary;
|
|
final Uri preamblesScript;
|
|
final Uri sealNativeObjectScript;
|
|
|
|
D8Configuration._(
|
|
this.sdkRoot,
|
|
this.binary,
|
|
this.preamblesScript,
|
|
this.sealNativeObjectScript,
|
|
);
|
|
|
|
factory D8Configuration(Uri sdkRoot) {
|
|
final preamblesScript = sdkRoot.resolve(
|
|
'sdk/lib/_internal/js_dev_runtime/private/preambles/d8.js',
|
|
);
|
|
final sealNativeObjectScript = sdkRoot.resolve(
|
|
'sdk/lib/_internal/js_runtime/lib/preambles/seal_native_object.js',
|
|
);
|
|
final arch = Abi.current().toString().split('_')[1];
|
|
final Uri binaryFromRoot;
|
|
if (Platform.isWindows) {
|
|
binaryFromRoot = Uri.file('third_party/d8/windows/$arch/d8.exe');
|
|
} else if (Platform.isLinux) {
|
|
binaryFromRoot = Uri.file('third_party/d8/linux/$arch/d8');
|
|
} else if (Platform.isMacOS) {
|
|
binaryFromRoot = Uri.file('third_party/d8/macos/$arch/d8');
|
|
} else {
|
|
throw UnsupportedError(
|
|
'Unsupported platform for running d8: '
|
|
'${Platform.operatingSystem}',
|
|
);
|
|
}
|
|
final binary = sdkRoot.resolveUri(binaryFromRoot);
|
|
return D8Configuration._(
|
|
sdkRoot,
|
|
binary,
|
|
preamblesScript,
|
|
sealNativeObjectScript,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Generates the JS bootstrapper for DDC with the DDC module system
|
|
/// (executed on D8).
|
|
///
|
|
/// `scriptDescriptors` maps module IDs to their JS script paths.
|
|
/// It has the form:
|
|
/// [
|
|
/// {
|
|
/// "id": "some__module__id.dart"
|
|
/// "src": "/path/to/file.js"
|
|
/// },
|
|
/// ...
|
|
/// ]
|
|
///
|
|
/// `modifiedFilesPerGeneration` maps generation ids to JS files modified in
|
|
/// that generation. It has the form:
|
|
/// {
|
|
/// "0": ["/path/to/file.js", "/path/to/file2.js", ...],
|
|
/// "1": ...
|
|
/// }
|
|
///
|
|
/// Note: All JS paths above are relative to `jsFileRoot`.
|
|
String generateD8Bootstrapper({
|
|
required String ddcModuleLoaderJsPath,
|
|
required String dartSdkJsPath,
|
|
required String entrypointModuleName,
|
|
String jsFileRoot = '',
|
|
String uuid = '00000000-0000-0000-0000-000000000000',
|
|
required String entrypointLibraryExportName,
|
|
required List<Map<String, String?>> scriptDescriptors,
|
|
required FileDataPerGeneration modifiedFilesPerGeneration,
|
|
}) {
|
|
final d8BootstrapJS =
|
|
'''
|
|
load("$ddcModuleLoaderJsPath");
|
|
load("$dartSdkJsPath");
|
|
|
|
var prerequisiteScripts = [
|
|
{
|
|
"id": "ddc_module_loader \\0",
|
|
"src": "$ddcModuleLoaderJsPath"
|
|
},
|
|
{
|
|
"id": "dart_sdk \\0",
|
|
"src": "$dartSdkJsPath"
|
|
}
|
|
];
|
|
|
|
let scripts = ${_encoder.convert(scriptDescriptors)};
|
|
|
|
let loadConfig = new self.\$dartLoader.LoadConfiguration();
|
|
loadConfig.isWindows = ${Platform.isWindows};
|
|
loadConfig.root = '$jsFileRoot';
|
|
// Loading the entrypoint late is only necessary in Chrome.
|
|
loadConfig.bootstrapScript = '';
|
|
loadConfig.loadScriptFn = function(loader) {
|
|
loader.addScriptsToQueue(scripts, null);
|
|
loader.loadEnqueuedModulesForD8();
|
|
}
|
|
loadConfig.ddcEventForLoadStart = /* LOAD_ALL_MODULES_START */ 1;
|
|
loadConfig.ddcEventForLoadedOk = /* LOAD_ALL_MODULES_END_OK */ 2;
|
|
loadConfig.ddcEventForLoadedError = /* LOAD_ALL_MODULES_END_ERROR */ 3;
|
|
let loader = new self.\$dartLoader.DDCLoader(loadConfig);
|
|
|
|
// Record prerequisite scripts' fully resolved URLs.
|
|
prerequisiteScripts.forEach(script => loader.registerScript(script));
|
|
|
|
// Note: these variables should only be used in non-multi-app scenarios since
|
|
// they can be arbitrarily overridden based on multi-app load order.
|
|
self.\$dartLoader.loadConfig = loadConfig;
|
|
self.\$dartLoader.loader = loader;
|
|
|
|
// Append hot reload runner-specific logic.
|
|
let modifiedFilesPerGeneration = ${_encoder.convert(modifiedFilesPerGeneration)};
|
|
let previousGenerations = new Set();
|
|
|
|
// Append a helper function to provide reloaded sources information.
|
|
self.\$injectedReloadedSourcesHelper = function() {
|
|
// Resolve the next generation's directory and load all modified files.
|
|
let nextGeneration = self.\$dartLoader.loader.intendedHotRestartGeneration;
|
|
if (previousGenerations.has(nextGeneration)) {
|
|
throw Error('Fatal error: Previous generations are being re-run.');
|
|
}
|
|
previousGenerations.add(nextGeneration);
|
|
|
|
let modifiedFilePaths = modifiedFilesPerGeneration[nextGeneration];
|
|
// Stop if the next generation does not exist.
|
|
if (modifiedFilePaths == void 0) {
|
|
return null;
|
|
}
|
|
|
|
// Collect reload generation resources.
|
|
let fileDescriptors = [];
|
|
for (let i = 0; i < modifiedFilePaths.length; i++) {
|
|
let modifiedFilePath = modifiedFilePaths[i][1];
|
|
fileDescriptors.push({src: modifiedFilePath});
|
|
}
|
|
return fileDescriptors;
|
|
}
|
|
|
|
// Append a helper function for hot restart.
|
|
self.\$dartReloadModifiedModules = function(filesToReload, appName) {
|
|
let expectedName = "$entrypointModuleName";
|
|
if (appName !== expectedName) {
|
|
throw Error("Unexpected app name " + appName
|
|
+ " (expected: " + expectedName + "). "
|
|
+ "Hot Reload Runner does not support multiple subapps, so only "
|
|
+ "one app name should be provided across reloads/restarts.");
|
|
}
|
|
return new Promise(function(resolve) {
|
|
// Load all modified files.
|
|
for (let i = 0; i < filesToReload.length; i++) {
|
|
let modifiedFilePath = filesToReload[i].src;
|
|
self.\$dartLoader.forceLoadScript(modifiedFilePath);
|
|
}
|
|
// D8 performs synchronous loads, but we return a Promise to match the
|
|
// semantics in Chrome.
|
|
resolve(filesToReload);
|
|
});
|
|
}
|
|
|
|
// Append a helper function for hot reload.
|
|
self.\$injectedFilesAndLibrariesToReload = function(fileGeneration) {
|
|
modifiedFilePaths = modifiedFilesPerGeneration[fileGeneration];
|
|
if (modifiedFilePaths == null) return null;
|
|
// Collect reload generation resources.
|
|
let fileUrls = [];
|
|
let libraryIds = [];
|
|
for (let i = 0; i < modifiedFilePaths.length; i++) {
|
|
let modifiedFileId = modifiedFilePaths[i][0];
|
|
let modifiedFilePath = modifiedFilePaths[i][1];
|
|
libraryIds.push(modifiedFileId);
|
|
fileUrls.push(modifiedFilePath);
|
|
}
|
|
return [fileUrls, libraryIds];
|
|
}
|
|
|
|
// D8 does not support the core Timer API methods, so our D8 preamble provides
|
|
// a custom implementation.
|
|
//
|
|
// Timers in this implementation are simulated, so they all complete before
|
|
// native JS `await` boundaries. If this boundary occurs before our runtime's
|
|
// `hotRestartIteration` counter increments, we can observe Futures not being
|
|
// cancelled in D8 when they might otherwise have been in Chrome.
|
|
//
|
|
// To resolve this, we record and increment hot restart generations early
|
|
// and wrap timer functions with custom cancellation logic.
|
|
self.setInterval = function(setInterval) {
|
|
return function(f, ms) {
|
|
var timerId;
|
|
let currentHotRestartIteration =
|
|
self.\$dartLoader.loader.intendedHotRestartGeneration;
|
|
var internalCallback = function() {
|
|
if (currentHotRestartIteration ==
|
|
self.\$dartLoader.loader.intendedHotRestartGeneration) {
|
|
f();
|
|
} else {
|
|
self.clearInterval(timerId);
|
|
}
|
|
}
|
|
timerId = setInterval(internalCallback, ms);
|
|
};
|
|
}(self.setInterval);
|
|
|
|
self.setTimeout = function(setTimeout) {
|
|
return function(f, ms) {
|
|
var timerId;
|
|
let currentHotRestartIteration =
|
|
self.\$dartLoader.loader.intendedHotRestartGeneration;
|
|
var internalCallback = function() {
|
|
if (currentHotRestartIteration ==
|
|
self.\$dartLoader.loader.intendedHotRestartGeneration) {
|
|
f();
|
|
} else {
|
|
self.clearTimeout(timerId);
|
|
}
|
|
}
|
|
timerId = setTimeout(internalCallback, ms);
|
|
};
|
|
}(self.setTimeout);
|
|
|
|
// DDC also has a runtime implementation of microtasks' `scheduleImmediate`
|
|
// that more closely matches Chrome's behavior. We enable this implementation
|
|
// by deleting the our custom implementation in D8's preamble.
|
|
self.scheduleImmediate = void 0;
|
|
|
|
// Set embedder configurations.
|
|
|
|
// Invoke main through the d8 preamble to ensure the code is running within the
|
|
// fake event loop.
|
|
dartDevEmbedder.config.capturedMainHandler =
|
|
dartDevEmbedder.config.capturedHotRestartMainHandler =
|
|
(runMain) => {
|
|
self.dartMainRunner((_) => {
|
|
runMain();
|
|
}, []);
|
|
};
|
|
|
|
|
|
// D8 timers execute synchronously, so periodic timers must be explicitly
|
|
// cancelled when an async main reaches an error state.
|
|
dartDevEmbedder.config.mainErrorCallback =
|
|
dartDevEmbedder.config.hotRestartMainErrorCallback =
|
|
() => {
|
|
self.clearAllTimers();
|
|
}
|
|
|
|
// Begin loading libraries
|
|
loader.nextAttempt();
|
|
|
|
dartDevEmbedder.runMain("$entrypointModuleName", {});
|
|
''';
|
|
|
|
return d8BootstrapJS;
|
|
}
|
|
|
|
/// Generates the two files required to bootstrap a chrome app: a main
|
|
/// entrypoint file and an app bootstrapper - respectively.
|
|
(String, String) generateChromeBootstrapperFiles({
|
|
required String ddcModuleLoaderJsPath,
|
|
required String dartSdkJsPath,
|
|
required String entrypointModuleName,
|
|
required String mainModuleEntrypointJsPath,
|
|
String jsFileRoot = '',
|
|
String uuid = '00000000-0000-0000-0000-000000000000',
|
|
required String entrypointLibraryExportName,
|
|
required List<Map<String, String?>> scriptDescriptors,
|
|
required FileDataPerGeneration modifiedFilesPerGeneration,
|
|
}) {
|
|
var mainModuleText = generateChromeMainEntrypoint(
|
|
entrypointModuleName: entrypointModuleName,
|
|
entrypointLibraryExportName: entrypointLibraryExportName,
|
|
);
|
|
var chromeBootstrapper = generateChromeBootstrapper(
|
|
ddcModuleLoaderJsPath: ddcModuleLoaderJsPath,
|
|
dartSdkJsPath: dartSdkJsPath,
|
|
entrypointModuleName: entrypointModuleName,
|
|
mainModuleEntrypointJsPath: mainModuleEntrypointJsPath,
|
|
jsFileRoot: jsFileRoot,
|
|
uuid: uuid,
|
|
scriptDescriptors: scriptDescriptors,
|
|
modifiedFilesPerGeneration: modifiedFilesPerGeneration,
|
|
);
|
|
return (mainModuleText, chromeBootstrapper);
|
|
}
|
|
|
|
/// Generates a bootstrap entrypoint script for calling 'main'.
|
|
///
|
|
/// Only used for the DDC module system when run in Chrome.
|
|
String generateChromeMainEntrypoint({
|
|
required String entrypointModuleName,
|
|
bool nullAssertions = true,
|
|
bool nativeNullAssertions = true,
|
|
String uuid = '00000000-0000-0000-0000-000000000000',
|
|
required String entrypointLibraryExportName,
|
|
}) {
|
|
return '''
|
|
(function() {
|
|
// Flutter Web uses a generated main entrypoint, which shares app and module names.
|
|
// We adopt their convention in this framework to make comparisons more direct.
|
|
let appName = "$entrypointModuleName";
|
|
let moduleName = "$entrypointModuleName";
|
|
|
|
// Multi-apps are not supported in this framework, so uuids are irrelevant.
|
|
let uuid = "$uuid";
|
|
|
|
let child = {};
|
|
child.main = function() {
|
|
dartDevEmbedder.runMain("$entrypointModuleName", {});
|
|
}
|
|
|
|
child.main();
|
|
})();
|
|
''';
|
|
}
|
|
|
|
/// Generates the JS bootstrapper for DDC with the DDC module system
|
|
/// (executed on Chrome).
|
|
///
|
|
/// `scriptDescriptors` maps module IDs to their JS script paths.
|
|
/// It has the form:
|
|
/// [
|
|
/// {
|
|
/// "id": "some__module__id.dart"
|
|
/// "src": "/path/to/file.js"
|
|
/// },
|
|
/// ...
|
|
/// ]
|
|
///
|
|
/// `modifiedFilesPerGeneration` maps generation ids to JS files modified in
|
|
/// that generation. It has the form:
|
|
/// {
|
|
/// "0": ["/path/to/file.js", "/path/to/file2.js", ...],
|
|
/// "1": ...
|
|
/// }
|
|
///
|
|
/// Note: All JS paths above are relative to `jsFileRoot`.
|
|
String generateChromeBootstrapper({
|
|
required String ddcModuleLoaderJsPath,
|
|
required String dartSdkJsPath,
|
|
required String entrypointModuleName,
|
|
required String mainModuleEntrypointJsPath,
|
|
String jsFileRoot = '',
|
|
String uuid = '00000000-0000-0000-0000-000000000000',
|
|
required List<Map<String, String?>> scriptDescriptors,
|
|
required FileDataPerGeneration modifiedFilesPerGeneration,
|
|
}) {
|
|
final bootstrapJS =
|
|
'''
|
|
var _currentDirectory = "$jsFileRoot";
|
|
|
|
window.\$dartCreateScript = (function() {
|
|
// Find the nonce value. (Note, this is only computed once.)
|
|
var scripts = Array.from(document.getElementsByTagName("script"));
|
|
var nonce;
|
|
scripts.some(
|
|
script => (nonce = script.nonce || script.getAttribute("nonce")));
|
|
// If present, return a closure that automatically appends the nonce.
|
|
if (nonce) {
|
|
return function() {
|
|
var script = document.createElement("script");
|
|
script.nonce = nonce;
|
|
return script;
|
|
};
|
|
} else {
|
|
return function() {
|
|
return document.createElement("script");
|
|
};
|
|
}
|
|
})();
|
|
|
|
// Creates and loads a script during hot restart.
|
|
var loadHotRestartScript = function(id, src, callback) {
|
|
var script = self.\$dartCreateScript();
|
|
let policy = {
|
|
createScriptURL: function(src) {return src;}
|
|
};
|
|
if (self.trustedTypes && self.trustedTypes.createPolicy) {
|
|
policy = self.trustedTypes.createPolicy('dartDdcModuleUrl', policy);
|
|
}
|
|
script.src = policy.createScriptURL(src);
|
|
script.async = false
|
|
script.defer = true;
|
|
script.id = id;
|
|
script.onload = callback;
|
|
document.head.appendChild(script);
|
|
}
|
|
|
|
// Loads a module [relativeUrl] relative to [root].
|
|
//
|
|
// If not specified, [root] defaults to the directory serving the main app.
|
|
//
|
|
// Used for appending pre-requisite modules to the page.
|
|
var forceLoadModule = function (relativeUrl, root) {
|
|
var actualRoot = root ?? _currentDirectory;
|
|
return new Promise(function(resolve, reject) {
|
|
var script = self.\$dartCreateScript();
|
|
let policy = {
|
|
createScriptURL: function(src) {return src;}
|
|
};
|
|
if (self.trustedTypes && self.trustedTypes.createPolicy) {
|
|
policy = self.trustedTypes.createPolicy('dartDdcModuleUrl', policy);
|
|
}
|
|
script.onload = resolve;
|
|
script.onerror = reject;
|
|
script.src = policy.createScriptURL(actualRoot + relativeUrl);
|
|
document.head.appendChild(script);
|
|
});
|
|
};
|
|
|
|
// A map containing the URLs for the bootstrap scripts in debug.
|
|
let _scriptUrls = {
|
|
"moduleLoader": "$ddcModuleLoaderJsPath"
|
|
};
|
|
|
|
(function() {
|
|
let appName = "$entrypointModuleName";
|
|
|
|
// A uuid that identifies a subapp. Unused for this framework.
|
|
let uuid = $uuid;
|
|
|
|
// Load pre-requisite DDC scripts.
|
|
// We intentionally use invalid names to avoid namespace clashes.
|
|
let prerequisiteScripts = [
|
|
{
|
|
"src": "$ddcModuleLoaderJsPath",
|
|
"id": "ddc_module_loader \x00"
|
|
}
|
|
];
|
|
|
|
// Load ddc_module_loader.js to access DDC's module loader API.
|
|
let prerequisiteLoads = [];
|
|
for (let i = 0; i < prerequisiteScripts.length; i++) {
|
|
prerequisiteLoads.push(forceLoadModule(prerequisiteScripts[i].src));
|
|
}
|
|
Promise.all(prerequisiteLoads).then((_) => afterPrerequisiteLogic());
|
|
|
|
// Save the current script so we can access it in a closure.
|
|
var _currentScript = document.currentScript;
|
|
|
|
var afterPrerequisiteLogic = function() {
|
|
window.\$dartLoader.rootDirectories.push(_currentDirectory);
|
|
let scripts = [
|
|
{
|
|
"src": "$dartSdkJsPath",
|
|
"id": "dart_sdk \\0"
|
|
},
|
|
].concat(${_encoder.convert(scriptDescriptors)});
|
|
|
|
let loadConfig = new window.\$dartLoader.LoadConfiguration();
|
|
loadConfig.isWindows = ${Platform.isWindows};
|
|
loadConfig.root = '$jsFileRoot';
|
|
loadConfig.bootstrapScript = {
|
|
"src": "$mainModuleEntrypointJsPath",
|
|
"id": "data-main"
|
|
};
|
|
scripts.push(loadConfig.bootstrapScript);
|
|
|
|
loadConfig.loadScriptFn = function(loader) {
|
|
loader.addScriptsToQueue(scripts, null);
|
|
loader.loadEnqueuedModules();
|
|
}
|
|
loadConfig.ddcEventForLoadStart = /* LOAD_ALL_MODULES_START */ 1;
|
|
loadConfig.ddcEventForLoadedOk = /* LOAD_ALL_MODULES_END_OK */ 2;
|
|
loadConfig.ddcEventForLoadedError = /* LOAD_ALL_MODULES_END_ERROR */ 3;
|
|
|
|
let loader = new window.\$dartLoader.DDCLoader(loadConfig);
|
|
|
|
// Record prerequisite scripts' fully resolved URLs.
|
|
prerequisiteScripts.forEach(script => loader.registerScript(script));
|
|
|
|
// Note: these variables should only be used in non-multi-app scenarios since
|
|
// they can be arbitrarily overridden based on multi-app load order.
|
|
window.\$dartLoader.loadConfig = loadConfig;
|
|
window.\$dartLoader.loader = loader;
|
|
|
|
// Append hot reload runner-specific logic.
|
|
let modifiedFilesPerGeneration = ${_encoder.convert(modifiedFilesPerGeneration)};
|
|
|
|
// Append a helper function for hot reload.
|
|
self.\$injectedFilesAndLibrariesToReload = function(fileGeneration) {
|
|
modifiedFilePaths = modifiedFilesPerGeneration[fileGeneration];
|
|
if (modifiedFilePaths == null) return null;
|
|
// Collect reload generation resources.
|
|
let fileUrls = [];
|
|
let libraryIds = [];
|
|
for (let i = 0; i < modifiedFilePaths.length; i++) {
|
|
let modifiedFileId = modifiedFilePaths[i][0];
|
|
let modifiedFilePath = modifiedFilePaths[i][1];
|
|
libraryIds.push(modifiedFileId);
|
|
fileUrls.push(modifiedFilePath);
|
|
}
|
|
return [fileUrls, libraryIds];
|
|
}
|
|
|
|
let previousGenerations = new Set();
|
|
// Append a helper function to provide reloaded sources information.
|
|
self.\$injectedReloadedSourcesHelper = function() {
|
|
// Resolve the next generation's directory and load all modified files.
|
|
let nextGeneration = self.\$dartLoader.loader.intendedHotRestartGeneration;
|
|
if (previousGenerations.has(nextGeneration)) {
|
|
throw Error('Fatal error: Previous generations are being re-run.');
|
|
}
|
|
previousGenerations.add(nextGeneration);
|
|
|
|
let modifiedFilePaths = modifiedFilesPerGeneration[nextGeneration];
|
|
// Stop if the next generation does not exist.
|
|
if (modifiedFilePaths == void 0) {
|
|
return null;
|
|
}
|
|
|
|
// Collect reload generation resources.
|
|
let fileDescriptors = [];
|
|
for (let i = 0; i < modifiedFilePaths.length; i++) {
|
|
let modifiedFileId = modifiedFilePaths[i][0];
|
|
let modifiedFilePath = modifiedFilePaths[i][1];
|
|
fileDescriptors.push({src: modifiedFilePath, id: modifiedFileId});
|
|
}
|
|
return fileDescriptors;
|
|
}
|
|
|
|
// Append a helper function for hot restart.
|
|
self.\$dartReloadModifiedModules = async function(filesToReload, subAppName) {
|
|
let expectedName = "$entrypointModuleName";
|
|
if (subAppName !== expectedName) {
|
|
throw Error("Unexpected app name " + subAppName
|
|
+ " (expected: " + expectedName + "). "
|
|
+ "Hot Reload Runner does not support multiple subapps, so only "
|
|
+ "one app name should be provided across reloads/restarts.");
|
|
}
|
|
// Load all modified files.
|
|
return new Promise(function(resolve) {
|
|
function callback() {
|
|
resolve(filesToReload);
|
|
}
|
|
var numToLoad = 0;
|
|
var numLoaded = 0;
|
|
for (let i = 0; i < filesToReload.length; i++) {
|
|
numToLoad++
|
|
let modifiedFileId = filesToReload[i].id;
|
|
let modifiedFilePath = filesToReload[i].src;
|
|
|
|
// Invalidate DDC state for hot restart.
|
|
self.\$dartLoader.moduleIdToUrl.set(modifiedFileId, modifiedFilePath);
|
|
self.\$dartLoader.urlToModuleId.set(modifiedFilePath, modifiedFileId);
|
|
|
|
// Remove the old script.
|
|
var el = document.getElementById(modifiedFileId);
|
|
if (el) el.remove();
|
|
|
|
loadHotRestartScript(modifiedFileId, modifiedFilePath, function() {
|
|
numLoaded++;
|
|
if (numToLoad == numLoaded) callback();
|
|
});
|
|
}
|
|
|
|
// Call the callback immediately if we found no updated scripts.
|
|
if (numToLoad == 0) callback();
|
|
});
|
|
}
|
|
|
|
// Begin loading libraries
|
|
loader.nextAttempt();
|
|
}
|
|
})();
|
|
''';
|
|
return bootstrapJS;
|
|
}
|
|
|
|
class ChromeConfiguration {
|
|
final Uri sdkRoot;
|
|
final Uri binary;
|
|
|
|
ChromeConfiguration._(this.sdkRoot, this.binary);
|
|
|
|
factory ChromeConfiguration(Uri sdkRoot) {
|
|
String chromeBinary;
|
|
if (Platform.isWindows) {
|
|
chromeBinary =
|
|
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe';
|
|
} else if (Platform.isMacOS) {
|
|
chromeBinary =
|
|
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
} else {
|
|
// Assume Linux
|
|
chromeBinary = 'google-chrome';
|
|
}
|
|
|
|
final binary = Uri.file(chromeBinary);
|
|
return ChromeConfiguration._(sdkRoot, binary);
|
|
}
|
|
}
|