| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import 'package:package_config/package_config.dart' ; |
| 6 | |
| 7 | /// Used to load prerequisite scripts such as ddc_module_loader.js |
| 8 | const _simpleLoaderScript = r''' |
| 9 | window.$dartCreateScript = (function() { |
| 10 | // Find the nonce value. (Note, this is only computed once.) |
| 11 | var scripts = Array.from(document.getElementsByTagName("script")); |
| 12 | var nonce; |
| 13 | scripts.some( |
| 14 | script => (nonce = script.nonce || script.getAttribute("nonce"))); |
| 15 | // If present, return a closure that automatically appends the nonce. |
| 16 | if (nonce) { |
| 17 | return function() { |
| 18 | var script = document.createElement("script"); |
| 19 | script.nonce = nonce; |
| 20 | return script; |
| 21 | }; |
| 22 | } else { |
| 23 | return function() { |
| 24 | return document.createElement("script"); |
| 25 | }; |
| 26 | } |
| 27 | })(); |
| 28 | |
| 29 | // Loads a module [relativeUrl] relative to [root]. |
| 30 | // |
| 31 | // If not specified, [root] defaults to the directory serving the main app. |
| 32 | var forceLoadModule = function (relativeUrl, root) { |
| 33 | var actualRoot = root ?? _currentDirectory; |
| 34 | return new Promise(function(resolve, reject) { |
| 35 | var script = self.$dartCreateScript(); |
| 36 | let policy = { |
| 37 | createScriptURL: function(src) {return src;} |
| 38 | }; |
| 39 | if (self.trustedTypes && self.trustedTypes.createPolicy) { |
| 40 | policy = self.trustedTypes.createPolicy('dartDdcModuleUrl', policy); |
| 41 | } |
| 42 | script.onload = resolve; |
| 43 | script.onerror = reject; |
| 44 | script.src = policy.createScriptURL(actualRoot + relativeUrl); |
| 45 | document.head.appendChild(script); |
| 46 | }); |
| 47 | }; |
| 48 | ''' ; |
| 49 | |
| 50 | // TODO(srujzs): Delete this once it's no longer used internally. |
| 51 | String generateDDCBootstrapScript({ |
| 52 | required String entrypoint, |
| 53 | required String ddcModuleLoaderUrl, |
| 54 | required String mapperUrl, |
| 55 | required bool generateLoadingIndicator, |
| 56 | String appRootDirectory = '/' , |
| 57 | }) { |
| 58 | return ''' |
| 59 | ${generateLoadingIndicator ? _generateLoadingIndicator() : "" } |
| 60 | // TODO(markzipan): This is safe if Flutter app roots are always equal to the |
| 61 | // host root '/'. Validate if this is true. |
| 62 | var _currentDirectory = " $appRootDirectory"; |
| 63 | |
| 64 | $_simpleLoaderScript |
| 65 | |
| 66 | // A map containing the URLs for the bootstrap scripts in debug. |
| 67 | let _scriptUrls = { |
| 68 | "mapper": " $mapperUrl", |
| 69 | "moduleLoader": " $ddcModuleLoaderUrl" |
| 70 | }; |
| 71 | |
| 72 | (function() { |
| 73 | let appName = " $entrypoint"; |
| 74 | |
| 75 | // A uuid that identifies a subapp. |
| 76 | // Stubbed out since subapps aren't supported in Flutter. |
| 77 | let uuid = "00000000-0000-0000-0000-000000000000"; |
| 78 | |
| 79 | window.postMessage( |
| 80 | {type: "DDC_STATE_CHANGE", state: "initial_load", targetUuid: uuid}, "*"); |
| 81 | |
| 82 | // Load pre-requisite DDC scripts. |
| 83 | // We intentionally use invalid names to avoid namespace clashes. |
| 84 | let prerequisiteScripts = [ |
| 85 | { |
| 86 | "src": " $ddcModuleLoaderUrl", |
| 87 | "id": "ddc_module_loader \x00" |
| 88 | }, |
| 89 | { |
| 90 | "src": " $mapperUrl", |
| 91 | "id": "dart_stack_trace_mapper \x00" |
| 92 | } |
| 93 | ]; |
| 94 | |
| 95 | // Load ddc_module_loader.js to access DDC's module loader API. |
| 96 | let prerequisiteLoads = []; |
| 97 | for (let i = 0; i < prerequisiteScripts.length; i++) { |
| 98 | prerequisiteLoads.push(forceLoadModule(prerequisiteScripts[i].src)); |
| 99 | } |
| 100 | Promise.all(prerequisiteLoads).then((_) => afterPrerequisiteLogic()); |
| 101 | |
| 102 | // Save the current script so we can access it in a closure. |
| 103 | var _currentScript = document.currentScript; |
| 104 | |
| 105 | var afterPrerequisiteLogic = function() { |
| 106 | window.\$dartLoader.rootDirectories.push(_currentDirectory); |
| 107 | let scripts = [ |
| 108 | { |
| 109 | "src": "dart_sdk.js", |
| 110 | "id": "dart_sdk" |
| 111 | }, |
| 112 | { |
| 113 | "src": "main_module.bootstrap.js", |
| 114 | "id": "data-main" |
| 115 | } |
| 116 | ]; |
| 117 | let loadConfig = new window.\$dartLoader.LoadConfiguration(); |
| 118 | loadConfig.bootstrapScript = scripts[scripts.length - 1]; |
| 119 | |
| 120 | loadConfig.loadScriptFn = function(loader) { |
| 121 | loader.addScriptsToQueue(scripts, null); |
| 122 | loader.loadEnqueuedModules(); |
| 123 | } |
| 124 | loadConfig.ddcEventForLoadStart = /* LOAD_ALL_MODULES_START */ 1; |
| 125 | loadConfig.ddcEventForLoadedOk = /* LOAD_ALL_MODULES_END_OK */ 2; |
| 126 | loadConfig.ddcEventForLoadedError = /* LOAD_ALL_MODULES_END_ERROR */ 3; |
| 127 | |
| 128 | let loader = new window.\$dartLoader.DDCLoader(loadConfig); |
| 129 | |
| 130 | // Record prerequisite scripts' fully resolved URLs. |
| 131 | prerequisiteScripts.forEach(script => loader.registerScript(script)); |
| 132 | |
| 133 | // Note: these variables should only be used in non-multi-app scenarios since |
| 134 | // they can be arbitrarily overridden based on multi-app load order. |
| 135 | window.\$dartLoader.loadConfig = loadConfig; |
| 136 | window.\$dartLoader.loader = loader; |
| 137 | loader.nextAttempt(); |
| 138 | } |
| 139 | })(); |
| 140 | ''' ; |
| 141 | } |
| 142 | |
| 143 | String generateDDCLibraryBundleBootstrapScript({ |
| 144 | required String entrypoint, |
| 145 | required String ddcModuleLoaderUrl, |
| 146 | required String mapperUrl, |
| 147 | required bool generateLoadingIndicator, |
| 148 | required bool isWindows, |
| 149 | }) { |
| 150 | return ''' |
| 151 | ${generateLoadingIndicator ? _generateLoadingIndicator() : "" } |
| 152 | // Save the current directory so we can access it in a closure. |
| 153 | var _currentDirectory = (function () { |
| 154 | var _url = document.currentScript.src; |
| 155 | var lastSlash = _url.lastIndexOf('/'); |
| 156 | if (lastSlash == -1) return _url; |
| 157 | var currentDirectory = _url.substring(0, lastSlash + 1); |
| 158 | return currentDirectory; |
| 159 | })(); |
| 160 | |
| 161 | $_simpleLoaderScript |
| 162 | |
| 163 | (function() { |
| 164 | let appName = "org-dartlang-app:/ $entrypoint"; |
| 165 | |
| 166 | // Load pre-requisite DDC scripts. We intentionally use invalid names to avoid |
| 167 | // namespace clashes. |
| 168 | let prerequisiteScripts = [ |
| 169 | { |
| 170 | "src": " $ddcModuleLoaderUrl", |
| 171 | "id": "ddc_module_loader \x00" |
| 172 | }, |
| 173 | { |
| 174 | "src": " $mapperUrl", |
| 175 | "id": "dart_stack_trace_mapper \x00" |
| 176 | } |
| 177 | ]; |
| 178 | |
| 179 | // Load ddc_module_loader.js to access DDC's module loader API. |
| 180 | let prerequisiteLoads = []; |
| 181 | for (let i = 0; i < prerequisiteScripts.length; i++) { |
| 182 | prerequisiteLoads.push(forceLoadModule(prerequisiteScripts[i].src)); |
| 183 | } |
| 184 | Promise.all(prerequisiteLoads).then((_) => afterPrerequisiteLogic()); |
| 185 | |
| 186 | // Save the current script so we can access it in a closure. |
| 187 | var _currentScript = document.currentScript; |
| 188 | |
| 189 | // Create a policy if needed to load the files during a hot restart. |
| 190 | let policy = { |
| 191 | createScriptURL: function(src) {return src;} |
| 192 | }; |
| 193 | if (self.trustedTypes && self.trustedTypes.createPolicy) { |
| 194 | policy = self.trustedTypes.createPolicy('dartDdcModuleUrl', policy); |
| 195 | } |
| 196 | |
| 197 | var afterPrerequisiteLogic = function() { |
| 198 | window.\$dartLoader.rootDirectories.push(_currentDirectory); |
| 199 | let scripts = [ |
| 200 | { |
| 201 | "src": "dart_sdk.js", |
| 202 | "id": "dart_sdk" |
| 203 | }, |
| 204 | { |
| 205 | "src": "main_module.bootstrap.js", |
| 206 | "id": "data-main" |
| 207 | } |
| 208 | ]; |
| 209 | |
| 210 | let loadConfig = new window.\$dartLoader.LoadConfiguration(); |
| 211 | // TODO(srujzs): Verify this is sufficient for Windows. |
| 212 | loadConfig.isWindows = $isWindows; |
| 213 | loadConfig.bootstrapScript = scripts[scripts.length - 1]; |
| 214 | |
| 215 | loadConfig.loadScriptFn = function(loader) { |
| 216 | loader.addScriptsToQueue(scripts, null); |
| 217 | loader.loadEnqueuedModules(); |
| 218 | } |
| 219 | loadConfig.ddcEventForLoadStart = /* LOAD_ALL_MODULES_START */ 1; |
| 220 | loadConfig.ddcEventForLoadedOk = /* LOAD_ALL_MODULES_END_OK */ 2; |
| 221 | loadConfig.ddcEventForLoadedError = /* LOAD_ALL_MODULES_END_ERROR */ 3; |
| 222 | |
| 223 | let loader = new window.\$dartLoader.DDCLoader(loadConfig); |
| 224 | |
| 225 | // Record prerequisite scripts' fully resolved URLs. |
| 226 | prerequisiteScripts.forEach(script => loader.registerScript(script)); |
| 227 | |
| 228 | // Note: these variables should only be used in non-multi-app scenarios |
| 229 | // since they can be arbitrarily overridden based on multi-app load order. |
| 230 | window.\$dartLoader.loadConfig = loadConfig; |
| 231 | window.\$dartLoader.loader = loader; |
| 232 | |
| 233 | // Begin loading libraries |
| 234 | loader.nextAttempt(); |
| 235 | |
| 236 | // Set up stack trace mapper. |
| 237 | if (window.\$dartStackTraceUtility && |
| 238 | !window.\$dartStackTraceUtility.ready) { |
| 239 | window.\$dartStackTraceUtility.ready = true; |
| 240 | window.\$dartStackTraceUtility.setSourceMapProvider(function(url) { |
| 241 | var baseUrl = window.location.protocol + '//' + window.location.host; |
| 242 | url = url.replace(baseUrl + '/', ''); |
| 243 | if (url == 'dart_sdk.js') { |
| 244 | return dartDevEmbedder.debugger.getSourceMap('dart_sdk'); |
| 245 | } |
| 246 | url = url.replace(".lib.js", ""); |
| 247 | return dartDevEmbedder.debugger.getSourceMap(url); |
| 248 | }); |
| 249 | } |
| 250 | |
| 251 | let currentUri = _currentScript.src; |
| 252 | // We should have written a file containing all the scripts that need to be |
| 253 | // reloaded into the page. This is then read when a hot restart is triggered |
| 254 | // in DDC via the `\$dartReloadModifiedModules` callback. |
| 255 | let restartScripts = _currentDirectory + 'restart_scripts.json'; |
| 256 | |
| 257 | if (!window.\$dartReloadModifiedModules) { |
| 258 | window.\$dartReloadModifiedModules = (function(appName, callback) { |
| 259 | var xhttp = new XMLHttpRequest(); |
| 260 | xhttp.withCredentials = true; |
| 261 | xhttp.onreadystatechange = function() { |
| 262 | // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState |
| 263 | if (this.readyState == 4 && this.status == 200 || this.status == 304) { |
| 264 | var scripts = JSON.parse(this.responseText); |
| 265 | var numToLoad = 0; |
| 266 | var numLoaded = 0; |
| 267 | for (var i = 0; i < scripts.length; i++) { |
| 268 | var script = scripts[i]; |
| 269 | if (script.id == null) continue; |
| 270 | var src = _currentDirectory + script.src.toString(); |
| 271 | var oldSrc = window.\$dartLoader.moduleIdToUrl.get(script.id); |
| 272 | |
| 273 | // We might actually load from a different uri, delete the old one |
| 274 | // just to be sure. |
| 275 | window.\$dartLoader.urlToModuleId.delete(oldSrc); |
| 276 | |
| 277 | window.\$dartLoader.moduleIdToUrl.set(script.id, src); |
| 278 | window.\$dartLoader.urlToModuleId.set(src, script.id); |
| 279 | |
| 280 | numToLoad++; |
| 281 | |
| 282 | var el = document.getElementById(script.id); |
| 283 | if (el) el.remove(); |
| 284 | el = window.\$dartCreateScript(); |
| 285 | el.src = policy.createScriptURL(src); |
| 286 | el.async = false; |
| 287 | el.defer = true; |
| 288 | el.id = script.id; |
| 289 | el.onload = function() { |
| 290 | numLoaded++; |
| 291 | if (numToLoad == numLoaded) callback(); |
| 292 | }; |
| 293 | document.head.appendChild(el); |
| 294 | } |
| 295 | // Call `callback` right away if we found no updated scripts. |
| 296 | if (numToLoad == 0) callback(); |
| 297 | } |
| 298 | }; |
| 299 | xhttp.open("GET", restartScripts, true); |
| 300 | xhttp.send(); |
| 301 | }); |
| 302 | } |
| 303 | }; |
| 304 | })(); |
| 305 | ''' ; |
| 306 | } |
| 307 | |
| 308 | /// The JavaScript bootstrap script to support in-browser hot restart. |
| 309 | /// |
| 310 | /// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl] |
| 311 | /// loads the special Dart stack trace mapper. |
| 312 | /// |
| 313 | /// This file is served when the browser requests "main.dart.js" in debug mode, |
| 314 | /// and is responsible for bootstrapping the RequireJS modules and attaching |
| 315 | /// the hot reload hooks. |
| 316 | /// |
| 317 | /// If [generateLoadingIndicator] is `true`, embeds a loading indicator onto the |
| 318 | /// web page that's visible while the Flutter app is loading. |
| 319 | String generateBootstrapScript({ |
| 320 | required String requireUrl, |
| 321 | required String mapperUrl, |
| 322 | required bool generateLoadingIndicator, |
| 323 | }) { |
| 324 | return ''' |
| 325 | "use strict"; |
| 326 | |
| 327 | ${generateLoadingIndicator ? _generateLoadingIndicator() : '' } |
| 328 | |
| 329 | // A map containing the URLs for the bootstrap scripts in debug. |
| 330 | let _scriptUrls = { |
| 331 | "mapper": " $mapperUrl", |
| 332 | "requireJs": " $requireUrl" |
| 333 | }; |
| 334 | |
| 335 | // Create a TrustedTypes policy so we can attach Scripts... |
| 336 | let _ttPolicy; |
| 337 | if (window.trustedTypes) { |
| 338 | _ttPolicy = trustedTypes.createPolicy("flutter-tools-bootstrap", { |
| 339 | createScriptURL: (url) => { |
| 340 | let scriptUrl = _scriptUrls[url]; |
| 341 | if (!scriptUrl) { |
| 342 | console.error("Unknown Flutter Web bootstrap resource!", url); |
| 343 | } |
| 344 | return scriptUrl; |
| 345 | } |
| 346 | }); |
| 347 | } |
| 348 | |
| 349 | // Creates a TrustedScriptURL for a given `scriptName`. |
| 350 | // See `_scriptUrls` and `_ttPolicy` above. |
| 351 | function getTTScriptUrl(scriptName) { |
| 352 | let defaultUrl = _scriptUrls[scriptName]; |
| 353 | return _ttPolicy ? _ttPolicy.createScriptURL(scriptName) : defaultUrl; |
| 354 | } |
| 355 | |
| 356 | // Attach source mapping. |
| 357 | var mapperEl = document.createElement("script"); |
| 358 | mapperEl.defer = true; |
| 359 | mapperEl.async = false; |
| 360 | mapperEl.src = getTTScriptUrl("mapper"); |
| 361 | document.head.appendChild(mapperEl); |
| 362 | |
| 363 | // Attach require JS. |
| 364 | var requireEl = document.createElement("script"); |
| 365 | requireEl.defer = true; |
| 366 | requireEl.async = false; |
| 367 | requireEl.src = getTTScriptUrl("requireJs"); |
| 368 | // This attribute tells require JS what to load as main (defined below). |
| 369 | requireEl.setAttribute("data-main", "main_module.bootstrap"); |
| 370 | document.head.appendChild(requireEl); |
| 371 | ''' ; |
| 372 | } |
| 373 | |
| 374 | /// Creates a visual animated loading indicator and puts it on the page to |
| 375 | /// provide feedback to the developer that the app is being loaded. Otherwise, |
| 376 | /// the developer would be staring at a blank page wondering if the app will |
| 377 | /// come up or not. |
| 378 | /// |
| 379 | /// This indicator should only be used when DWDS is enabled, e.g. with the |
| 380 | /// `-d chrome` option. Debug builds without DWDS, e.g. `flutter run -d web-server` |
| 381 | /// or `flutter build web --debug` should not use this indicator. |
| 382 | String _generateLoadingIndicator() { |
| 383 | return ''' |
| 384 | var styles = ` |
| 385 | .flutter-loader { |
| 386 | width: 100%; |
| 387 | height: 8px; |
| 388 | background-color: #13B9FD; |
| 389 | position: absolute; |
| 390 | top: 0px; |
| 391 | left: 0px; |
| 392 | overflow: hidden; |
| 393 | } |
| 394 | |
| 395 | .indeterminate { |
| 396 | position: relative; |
| 397 | width: 100%; |
| 398 | height: 100%; |
| 399 | } |
| 400 | |
| 401 | .indeterminate:before { |
| 402 | content: ''; |
| 403 | position: absolute; |
| 404 | height: 100%; |
| 405 | background-color: #0175C2; |
| 406 | animation: indeterminate_first 2.0s infinite ease-out; |
| 407 | } |
| 408 | |
| 409 | .indeterminate:after { |
| 410 | content: ''; |
| 411 | position: absolute; |
| 412 | height: 100%; |
| 413 | background-color: #02569B; |
| 414 | animation: indeterminate_second 2.0s infinite ease-in; |
| 415 | } |
| 416 | |
| 417 | @keyframes indeterminate_first { |
| 418 | 0% { |
| 419 | left: -100%; |
| 420 | width: 100%; |
| 421 | } |
| 422 | 100% { |
| 423 | left: 100%; |
| 424 | width: 10%; |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | @keyframes indeterminate_second { |
| 429 | 0% { |
| 430 | left: -150%; |
| 431 | width: 100%; |
| 432 | } |
| 433 | 100% { |
| 434 | left: 100%; |
| 435 | width: 10%; |
| 436 | } |
| 437 | } |
| 438 | `; |
| 439 | |
| 440 | var styleSheet = document.createElement("style") |
| 441 | styleSheet.type = "text/css"; |
| 442 | styleSheet.innerText = styles; |
| 443 | document.head.appendChild(styleSheet); |
| 444 | |
| 445 | var loader = document.createElement('div'); |
| 446 | loader.className = "flutter-loader"; |
| 447 | document.body.append(loader); |
| 448 | |
| 449 | var indeterminate = document.createElement('div'); |
| 450 | indeterminate.className = "indeterminate"; |
| 451 | loader.appendChild(indeterminate); |
| 452 | |
| 453 | document.addEventListener('dart-app-ready', function (e) { |
| 454 | loader.parentNode.removeChild(loader); |
| 455 | styleSheet.parentNode.removeChild(styleSheet); |
| 456 | }); |
| 457 | ''' ; |
| 458 | } |
| 459 | |
| 460 | const _onLoadEndCallback = r'$onLoadEndCallback' ; |
| 461 | |
| 462 | String generateDDCLibraryBundleMainModule({ |
| 463 | required String entrypoint, |
| 464 | required bool nativeNullAssertions, |
| 465 | required String onLoadEndBootstrap, |
| 466 | required bool isCi, |
| 467 | }) { |
| 468 | // Chrome in CI seems to hang when there are too many requests at once, so we |
| 469 | // limit the max number of script requests for that environment. |
| 470 | // https://github.com/flutter/flutter/issues/169574 |
| 471 | final setMaxRequests = isCi ? r'window.$dartLoader.loadConfig.maxRequestPoolSize = 100;' : '' ; |
| 472 | // The typo below in "EXTENTION" is load-bearing, package:build depends on it. |
| 473 | return ''' |
| 474 | /* ENTRYPOINT_EXTENTION_MARKER */ |
| 475 | |
| 476 | (function() { |
| 477 | let appName = "org-dartlang-app:/ $entrypoint"; |
| 478 | |
| 479 | dartDevEmbedder.debugger.registerDevtoolsFormatter(); |
| 480 | |
| 481 | $setMaxRequests |
| 482 | // Set up a final script that lets us know when all scripts have been loaded. |
| 483 | // Only then can we call the main method. |
| 484 | let onLoadEndSrc = ' $onLoadEndBootstrap'; |
| 485 | window.\$dartLoader.loadConfig.bootstrapScript = { |
| 486 | src: onLoadEndSrc, |
| 487 | id: onLoadEndSrc, |
| 488 | }; |
| 489 | window.\$dartLoader.loadConfig.tryLoadBootstrapScript = true; |
| 490 | // Should be called by $onLoadEndBootstrap once all the scripts have been |
| 491 | // loaded. |
| 492 | window.$_onLoadEndCallback = function() { |
| 493 | let child = {}; |
| 494 | child.main = function() { |
| 495 | let sdkOptions = { |
| 496 | nativeNonNullAsserts: $nativeNullAssertions, |
| 497 | }; |
| 498 | dartDevEmbedder.runMain(appName, sdkOptions); |
| 499 | } |
| 500 | /* MAIN_EXTENSION_MARKER */ |
| 501 | child.main(); |
| 502 | } |
| 503 | })(); |
| 504 | '''; |
| 505 | } |
| 506 | |
| 507 | String generateDDCLibraryBundleOnLoadEndBootstrap() { |
| 508 | return '''window.$_onLoadEndCallback();'''; |
| 509 | } |
| 510 | |
| 511 | /// Generate a synthetic main module which captures the application's main |
| 512 | /// method. |
| 513 | /// |
| 514 | /// If a [bootstrapModule] name is not provided, defaults to 'main_module.bootstrap'. |
| 515 | /// |
| 516 | /// RE: Object.keys usage in app.main: |
| 517 | /// This attaches the main entrypoint and hot reload functionality to the window. |
| 518 | /// The app module will have a single property which contains the actual application |
| 519 | /// code. The property name is based off of the entrypoint that is generated, for example |
| 520 | /// the file `foo/bar/baz.dart` will generate a property named approximately |
| 521 | /// `foo__bar__baz`. Rather than attempt to guess, we assume the first property of |
| 522 | /// this object is the module. |
| 523 | String generateMainModule({ |
| 524 | required String entrypoint, |
| 525 | required bool nativeNullAssertions, |
| 526 | String bootstrapModule = 'main_module.bootstrap', |
| 527 | String loaderRootDirectory = '', |
| 528 | }) { |
| 529 | // The typo below in "EXTENTION" is load-bearing, package:build depends on it. |
| 530 | return ''' |
| 531 | /* ENTRYPOINT_EXTENTION_MARKER */ |
| 532 | // Disable require module timeout |
| 533 | require.config({ |
| 534 | waitSeconds: 0 |
| 535 | }); |
| 536 | // Create the main module loaded below. |
| 537 | define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) { |
| 538 | dart_sdk.dart.setStartAsyncSynchronously(true); |
| 539 | dart_sdk._debugger.registerDevtoolsFormatter(); |
| 540 | dart_sdk.dart.nativeNonNullAsserts($nativeNullAssertions); |
| 541 | |
| 542 | // See the generateMainModule doc comment. |
| 543 | var child = {}; |
| 544 | child.main = app[Object.keys(app)[0]].main; |
| 545 | |
| 546 | /* MAIN_EXTENSION_MARKER */ |
| 547 | child.main(); |
| 548 | |
| 549 | window.\$dartLoader = {}; |
| 550 | window.\$dartLoader.rootDirectories = ["$loaderRootDirectory"]; |
| 551 | if (window.\$requireLoader) { |
| 552 | window.\$requireLoader.getModuleLibraries = dart_sdk.dart.getModuleLibraries; |
| 553 | } |
| 554 | if (window.\$dartStackTraceUtility && !window.\$dartStackTraceUtility.ready) { |
| 555 | window.\$dartStackTraceUtility.ready = true; |
| 556 | let dart = dart_sdk.dart; |
| 557 | window.\$dartStackTraceUtility.setSourceMapProvider(function(url) { |
| 558 | var baseUrl = window.location.protocol + '//' + window.location.host; |
| 559 | url = url.replace(baseUrl + '/', ''); |
| 560 | if (url == 'dart_sdk.js') { |
| 561 | return dart.getSourceMap('dart_sdk'); |
| 562 | } |
| 563 | url = url.replace(".lib.js", ""); |
| 564 | return dart.getSourceMap(url); |
| 565 | }); |
| 566 | } |
| 567 | // Prevent DDC's requireJS to interfere with modern bundling. |
| 568 | if (typeof define === 'function' && define.amd) { |
| 569 | // Preserve a copy just in case... |
| 570 | define._amd = define.amd; |
| 571 | delete define.amd; |
| 572 | } |
| 573 | }); |
| 574 | '''; |
| 575 | } |
| 576 | |
| 577 | typedef WebTestInfo = ({String entryPoint, Uri goldensUri, String? configFile}); |
| 578 | |
| 579 | /// Generates the bootstrap logic required for running a group of unit test |
| 580 | /// files in the browser. |
| 581 | /// |
| 582 | /// This creates one "switchboard" main function that imports all the main |
| 583 | /// functions of the unit test files that need to be run. The javascript code |
| 584 | /// that starts the test sets a `window.testSelector` that specifies which main |
| 585 | /// function to invoke. This allows us to compile all the unit test files as a |
| 586 | /// single web application and invoke that with a different selector for each |
| 587 | /// test. |
| 588 | String generateTestEntrypoint({ |
| 589 | required List<WebTestInfo> testInfos, |
| 590 | required LanguageVersion languageVersion, |
| 591 | }) { |
| 592 | final importMainStatements = <String>[]; |
| 593 | final importTestConfigStatements = <String>[]; |
| 594 | final webTestPairs = <String>[]; |
| 595 | |
| 596 | for (var index = 0; index < testInfos.length; index++) { |
| 597 | final WebTestInfo testInfo = testInfos[index]; |
| 598 | final String entryPointPath = testInfo.entryPoint; |
| 599 | importMainStatements.add( |
| 600 | "import 'org-dartlang-app:///${Uri.file(entryPointPath)}' as test_$index show main;", |
| 601 | ); |
| 602 | |
| 603 | final String? testConfigPath = testInfo.configFile; |
| 604 | String? testConfigFunction = 'null'; |
| 605 | if (testConfigPath != null) { |
| 606 | importTestConfigStatements.add( |
| 607 | "import 'org-dartlang-app:///${Uri.file(testConfigPath)}' as test_config_$index show testExecutable;", |
| 608 | ); |
| 609 | testConfigFunction = 'test_config_$index.testExecutable'; |
| 610 | } |
| 611 | webTestPairs.add(''' |
| 612 | '$entryPointPath': ( |
| 613 | entryPoint: test_$index.main, |
| 614 | entryPointRunner: $testConfigFunction, |
| 615 | goldensUri: Uri.parse('${testInfo.goldensUri}'), |
| 616 | ), |
| 617 | '''); |
| 618 | } |
| 619 | return ''' |
| 620 | // @dart = ${languageVersion.major}.${languageVersion.minor} |
| 621 | |
| 622 | ${importMainStatements.join('\n')} |
| 623 | |
| 624 | ${importTestConfigStatements.join('\n')} |
| 625 | |
| 626 | import 'package:flutter_test/flutter_test.dart'; |
| 627 | |
| 628 | Map<String, WebTest> webTestMap = <String, WebTest>{ |
| 629 | ${webTestPairs.join('\n')} |
| 630 | }; |
| 631 | |
| 632 | Future<void> main() { |
| 633 | final WebTest? webTest = webTestMap[testSelector]; |
| 634 | if (webTest == null) { |
| 635 | throw Exception('Web test for \${testSelector} not found'); |
| 636 | } |
| 637 | return runWebTest(webTest); |
| 638 | } |
| 639 | '''; |
| 640 | } |
| 641 | |
| 642 | /// Generate the unit test bootstrap file. |
| 643 | String generateTestBootstrapFileContents(String mainUri, String requireUrl, String mapperUrl) { |
| 644 | return ''' |
| 645 | (function() { |
| 646 | if (typeof document != 'undefined') { |
| 647 | var el = document.createElement("script"); |
| 648 | el.defer = true; |
| 649 | el.async = false; |
| 650 | el.src = '$mapperUrl'; |
| 651 | document.head.appendChild(el); |
| 652 | |
| 653 | el = document.createElement("script"); |
| 654 | el.defer = true; |
| 655 | el.async = false; |
| 656 | el.src = '$requireUrl'; |
| 657 | el.setAttribute("data-main", '$mainUri'); |
| 658 | document.head.appendChild(el); |
| 659 | } else { |
| 660 | importScripts('$mapperUrl', '$requireUrl'); |
| 661 | require.config({ |
| 662 | baseUrl: baseUrl, |
| 663 | }); |
| 664 | window = self; |
| 665 | require(['$mainUri']); |
| 666 | } |
| 667 | })(); |
| 668 | '''; |
| 669 | } |
| 670 | |
| 671 | String generateDefaultFlutterBootstrapScript({required bool includeServiceWorkerSettings}) { |
| 672 | final serviceWorkerSettings = includeServiceWorkerSettings |
| 673 | ? ''' |
| 674 | { |
| 675 | serviceWorkerSettings: { |
| 676 | serviceWorkerVersion: {{flutter_service_worker_version}} |
| 677 | } |
| 678 | }''' |
| 679 | : ''; |
| 680 | return ''' |
| 681 | {{flutter_js}} |
| 682 | {{flutter_build_config}} |
| 683 | _flutter.loader.load($serviceWorkerSettings); |
| 684 | '''; |
| 685 | } |
| 686 | |