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
5import 'package:package_config/package_config.dart';
6
7/// Used to load prerequisite scripts such as ddc_module_loader.js
8const String _simpleLoaderScript = r'''
9window.$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.
32var 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.
51String 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.
62var _currentDirectory = "$appRootDirectory";
63
64$_simpleLoaderScript
65
66// A map containing the URLs for the bootstrap scripts in debug.
67let _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
143String 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.
153var _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. The [entrypoint] is the
312/// actual main.dart file.
313///
314/// This file is served when the browser requests "main.dart.js" in debug mode,
315/// and is responsible for bootstrapping the RequireJS modules and attaching
316/// the hot reload hooks.
317///
318/// If `generateLoadingIndicator` is true, embeds a loading indicator onto the
319/// web page that's visible while the Flutter app is loading.
320String generateBootstrapScript({
321 required String requireUrl,
322 required String mapperUrl,
323 required bool generateLoadingIndicator,
324}) {
325 return '''
326"use strict";
327
328${generateLoadingIndicator ? _generateLoadingIndicator() : ''}
329
330// A map containing the URLs for the bootstrap scripts in debug.
331let _scriptUrls = {
332 "mapper": "$mapperUrl",
333 "requireJs": "$requireUrl"
334};
335
336// Create a TrustedTypes policy so we can attach Scripts...
337let _ttPolicy;
338if (window.trustedTypes) {
339 _ttPolicy = trustedTypes.createPolicy("flutter-tools-bootstrap", {
340 createScriptURL: (url) => {
341 let scriptUrl = _scriptUrls[url];
342 if (!scriptUrl) {
343 console.error("Unknown Flutter Web bootstrap resource!", url);
344 }
345 return scriptUrl;
346 }
347 });
348}
349
350// Creates a TrustedScriptURL for a given `scriptName`.
351// See `_scriptUrls` and `_ttPolicy` above.
352function getTTScriptUrl(scriptName) {
353 let defaultUrl = _scriptUrls[scriptName];
354 return _ttPolicy ? _ttPolicy.createScriptURL(scriptName) : defaultUrl;
355}
356
357// Attach source mapping.
358var mapperEl = document.createElement("script");
359mapperEl.defer = true;
360mapperEl.async = false;
361mapperEl.src = getTTScriptUrl("mapper");
362document.head.appendChild(mapperEl);
363
364// Attach require JS.
365var requireEl = document.createElement("script");
366requireEl.defer = true;
367requireEl.async = false;
368requireEl.src = getTTScriptUrl("requireJs");
369// This attribute tells require JS what to load as main (defined below).
370requireEl.setAttribute("data-main", "main_module.bootstrap");
371document.head.appendChild(requireEl);
372''';
373}
374
375/// Creates a visual animated loading indicator and puts it on the page to
376/// provide feedback to the developer that the app is being loaded. Otherwise,
377/// the developer would be staring at a blank page wondering if the app will
378/// come up or not.
379///
380/// This indicator should only be used when DWDS is enabled, e.g. with the
381/// `-d chrome` option. Debug builds without DWDS, e.g. `flutter run -d web-server`
382/// or `flutter build web --debug` should not use this indicator.
383String _generateLoadingIndicator() {
384 return '''
385var styles = `
386 .flutter-loader {
387 width: 100%;
388 height: 8px;
389 background-color: #13B9FD;
390 position: absolute;
391 top: 0px;
392 left: 0px;
393 overflow: hidden;
394 }
395
396 .indeterminate {
397 position: relative;
398 width: 100%;
399 height: 100%;
400 }
401
402 .indeterminate:before {
403 content: '';
404 position: absolute;
405 height: 100%;
406 background-color: #0175C2;
407 animation: indeterminate_first 2.0s infinite ease-out;
408 }
409
410 .indeterminate:after {
411 content: '';
412 position: absolute;
413 height: 100%;
414 background-color: #02569B;
415 animation: indeterminate_second 2.0s infinite ease-in;
416 }
417
418 @keyframes indeterminate_first {
419 0% {
420 left: -100%;
421 width: 100%;
422 }
423 100% {
424 left: 100%;
425 width: 10%;
426 }
427 }
428
429 @keyframes indeterminate_second {
430 0% {
431 left: -150%;
432 width: 100%;
433 }
434 100% {
435 left: 100%;
436 width: 10%;
437 }
438 }
439`;
440
441var styleSheet = document.createElement("style")
442styleSheet.type = "text/css";
443styleSheet.innerText = styles;
444document.head.appendChild(styleSheet);
445
446var loader = document.createElement('div');
447loader.className = "flutter-loader";
448document.body.append(loader);
449
450var indeterminate = document.createElement('div');
451indeterminate.className = "indeterminate";
452loader.appendChild(indeterminate);
453
454document.addEventListener('dart-app-ready', function (e) {
455 loader.parentNode.removeChild(loader);
456 styleSheet.parentNode.removeChild(styleSheet);
457});
458''';
459}
460
461const String _onLoadEndCallback = r'$onLoadEndCallback';
462
463String generateDDCLibraryBundleMainModule({
464 required String entrypoint,
465 required bool nativeNullAssertions,
466 required String onLoadEndBootstrap,
467}) {
468 // The typo below in "EXTENTION" is load-bearing, package:build depends on it.
469 return '''
470/* ENTRYPOINT_EXTENTION_MARKER */
471
472(function() {
473 let appName = "org-dartlang-app:/$entrypoint";
474
475 dartDevEmbedder.debugger.registerDevtoolsFormatter();
476
477 // Set up a final script that lets us know when all scripts have been loaded.
478 // Only then can we call the main method.
479 let onLoadEndSrc = '$onLoadEndBootstrap';
480 window.\$dartLoader.loadConfig.bootstrapScript = {
481 src: onLoadEndSrc,
482 id: onLoadEndSrc,
483 };
484 window.\$dartLoader.loadConfig.tryLoadBootstrapScript = true;
485 // Should be called by $onLoadEndBootstrap once all the scripts have been
486 // loaded.
487 window.$_onLoadEndCallback = function() {
488 let child = {};
489 child.main = function() {
490 let sdkOptions = {
491 nativeNonNullAsserts: $nativeNullAssertions,
492 };
493 dartDevEmbedder.runMain(appName, sdkOptions);
494 }
495 /* MAIN_EXTENSION_MARKER */
496 child.main();
497 }
498})();
499''';
500}
501
502String generateDDCLibraryBundleOnLoadEndBootstrap() {
503 return '''window.$_onLoadEndCallback();''';
504}
505
506/// Generate a synthetic main module which captures the application's main
507/// method.
508///
509/// If a [bootstrapModule] name is not provided, defaults to 'main_module.bootstrap'.
510///
511/// RE: Object.keys usage in app.main:
512/// This attaches the main entrypoint and hot reload functionality to the window.
513/// The app module will have a single property which contains the actual application
514/// code. The property name is based off of the entrypoint that is generated, for example
515/// the file `foo/bar/baz.dart` will generate a property named approximately
516/// `foo__bar__baz`. Rather than attempt to guess, we assume the first property of
517/// this object is the module.
518String generateMainModule({
519 required String entrypoint,
520 required bool nativeNullAssertions,
521 String bootstrapModule = 'main_module.bootstrap',
522 String loaderRootDirectory = '',
523}) {
524 // The typo below in "EXTENTION" is load-bearing, package:build depends on it.
525 return '''
526/* ENTRYPOINT_EXTENTION_MARKER */
527// Disable require module timeout
528require.config({
529 waitSeconds: 0
530});
531// Create the main module loaded below.
532define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) {
533 dart_sdk.dart.setStartAsyncSynchronously(true);
534 dart_sdk._debugger.registerDevtoolsFormatter();
535 dart_sdk.dart.nativeNonNullAsserts($nativeNullAssertions);
536
537 // See the generateMainModule doc comment.
538 var child = {};
539 child.main = app[Object.keys(app)[0]].main;
540
541 /* MAIN_EXTENSION_MARKER */
542 child.main();
543
544 window.\$dartLoader = {};
545 window.\$dartLoader.rootDirectories = ["$loaderRootDirectory"];
546 if (window.\$requireLoader) {
547 window.\$requireLoader.getModuleLibraries = dart_sdk.dart.getModuleLibraries;
548 }
549 if (window.\$dartStackTraceUtility && !window.\$dartStackTraceUtility.ready) {
550 window.\$dartStackTraceUtility.ready = true;
551 let dart = dart_sdk.dart;
552 window.\$dartStackTraceUtility.setSourceMapProvider(function(url) {
553 var baseUrl = window.location.protocol + '//' + window.location.host;
554 url = url.replace(baseUrl + '/', '');
555 if (url == 'dart_sdk.js') {
556 return dart.getSourceMap('dart_sdk');
557 }
558 url = url.replace(".lib.js", "");
559 return dart.getSourceMap(url);
560 });
561 }
562 // Prevent DDC's requireJS to interfere with modern bundling.
563 if (typeof define === 'function' && define.amd) {
564 // Preserve a copy just in case...
565 define._amd = define.amd;
566 delete define.amd;
567 }
568});
569''';
570}
571
572typedef WebTestInfo = ({String entryPoint, Uri goldensUri, String? configFile});
573
574/// Generates the bootstrap logic required for running a group of unit test
575/// files in the browser.
576///
577/// This creates one "switchboard" main function that imports all the main
578/// functions of the unit test files that need to be run. The javascript code
579/// that starts the test sets a `window.testSelector` that specifies which main
580/// function to invoke. This allows us to compile all the unit test files as a
581/// single web application and invoke that with a different selector for each
582/// test.
583String generateTestEntrypoint({
584 required List<WebTestInfo> testInfos,
585 required LanguageVersion languageVersion,
586}) {
587 final List<String> importMainStatements = <String>[];
588 final List<String> importTestConfigStatements = <String>[];
589 final List<String> webTestPairs = <String>[];
590
591 for (int index = 0; index < testInfos.length; index++) {
592 final WebTestInfo testInfo = testInfos[index];
593 final String entryPointPath = testInfo.entryPoint;
594 importMainStatements.add(
595 "import 'org-dartlang-app:///${Uri.file(entryPointPath)}' as test_$index show main;",
596 );
597
598 final String? testConfigPath = testInfo.configFile;
599 String? testConfigFunction = 'null';
600 if (testConfigPath != null) {
601 importTestConfigStatements.add(
602 "import 'org-dartlang-app:///${Uri.file(testConfigPath)}' as test_config_$index show testExecutable;",
603 );
604 testConfigFunction = 'test_config_$index.testExecutable';
605 }
606 webTestPairs.add('''
607 '$entryPointPath': (
608 entryPoint: test_$index.main,
609 entryPointRunner: $testConfigFunction,
610 goldensUri: Uri.parse('${testInfo.goldensUri}'),
611 ),
612''');
613 }
614 return '''
615// @dart = ${languageVersion.major}.${languageVersion.minor}
616
617${importMainStatements.join('\n')}
618
619${importTestConfigStatements.join('\n')}
620
621import 'package:flutter_test/flutter_test.dart';
622
623Map<String, WebTest> webTestMap = <String, WebTest>{
624 ${webTestPairs.join('\n')}
625};
626
627Future<void> main() {
628 final WebTest? webTest = webTestMap[testSelector];
629 if (webTest == null) {
630 throw Exception('Web test for \${testSelector} not found');
631 }
632 return runWebTest(webTest);
633}
634 ''';
635}
636
637/// Generate the unit test bootstrap file.
638String generateTestBootstrapFileContents(String mainUri, String requireUrl, String mapperUrl) {
639 return '''
640(function() {
641 if (typeof document != 'undefined') {
642 var el = document.createElement("script");
643 el.defer = true;
644 el.async = false;
645 el.src = '$mapperUrl';
646 document.head.appendChild(el);
647
648 el = document.createElement("script");
649 el.defer = true;
650 el.async = false;
651 el.src = '$requireUrl';
652 el.setAttribute("data-main", '$mainUri');
653 document.head.appendChild(el);
654 } else {
655 importScripts('$mapperUrl', '$requireUrl');
656 require.config({
657 baseUrl: baseUrl,
658 });
659 window = self;
660 require(['$mainUri']);
661 }
662})();
663''';
664}
665
666String generateDefaultFlutterBootstrapScript() {
667 return '''
668{{flutter_js}}
669{{flutter_build_config}}
670
671_flutter.loader.load({
672 serviceWorkerSettings: {
673 serviceWorkerVersion: {{flutter_service_worker_version}}
674 }
675});
676''';
677}
678

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com