| // Copyright (c) 2011, 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 'package:path/path.dart' as p; | |
| import 'configuration.dart' show Compiler; | |
| import 'utils.dart'; | |
| // The native JavaScript Object prototype is sealed before loading the Dart | |
| // SDK module to guard against prototype pollution. | |
| final _sealNativeObjectScript = | |
| '/root_dart/sdk/lib/_internal/js_runtime/lib/preambles/' | |
| 'seal_native_object.js'; | |
| String dart2jsHtml(String title, String scriptPath) { | |
| return """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
| <meta charset="utf-8"> | |
| <meta name="dart.unittest" content="full-stack-traces"> | |
| <title> Test $title </title> | |
| <style> | |
| .unittest-table { font-family:monospace; border:1px; } | |
| .unittest-pass { background: #6b3;} | |
| .unittest-fail { background: #d55;} | |
| .unittest-error { background: #a11;} | |
| </style> | |
| <script type="text/javascript" src="$_sealNativeObjectScript"></script> | |
| </head> | |
| <body> | |
| <h1> Running $title </h1> | |
| <script type="text/javascript" | |
| src="/root_dart/pkg/test_runner/lib/src/test_controller.js"> | |
| </script> | |
| <script type="text/javascript" src="$scriptPath" | |
| onerror="scriptTagOnErrorCallback(null)" | |
| defer> | |
| </script> | |
| </body> | |
| </html>"""; | |
| } | |
| /// Transforms a path to a valid JS identifier. | |
| /// | |
| /// This logic must be synchronized with [pathToJSIdentifier] in DDC at: | |
| /// pkg/dev_compiler/lib/src/compiler/module_builder.dart | |
| String pathToJSIdentifier(String path) { | |
| path = p.normalize(path); | |
| if (path.startsWith('/') || path.startsWith('\\')) { | |
| path = path.substring(1, path.length); | |
| } | |
| return _toJSIdentifier( | |
| path | |
| .replaceAll('\\', '__') | |
| .replaceAll('/', '__') | |
| .replaceAll('..', '__') | |
| .replaceAll('-', '_'), | |
| ); | |
| } | |
| final _digitPattern = RegExp(r'\d'); | |
| /// Escape [name] to make it into a valid identifier. | |
| String _toJSIdentifier(String name) { | |
| if (name.isEmpty) return r'$'; | |
| // Escape any invalid characters | |
| var result = name.replaceAllMapped( | |
| _invalidCharInIdentifier, | |
| (match) => '\$${match[0]!.codeUnits.join("")}', | |
| ); | |
| // Ensure the identifier first character is not numeric and that the whole | |
| // identifier is not a keyword. | |
| if (result.startsWith(_digitPattern) || _invalidVariableName(result)) { | |
| return '\$$result'; | |
| } | |
| return result; | |
| } | |
| // Invalid characters for identifiers, which would need to be escaped. | |
| final _invalidCharInIdentifier = RegExp(r'[^A-Za-z_\d]'); | |
| bool _invalidVariableName(String keyword, {bool strictMode = true}) { | |
| switch (keyword) { | |
| // http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words | |
| case "await": | |
| case "break": | |
| case "case": | |
| case "catch": | |
| case "class": | |
| case "const": | |
| case "continue": | |
| case "debugger": | |
| case "default": | |
| case "delete": | |
| case "do": | |
| case "else": | |
| case "enum": | |
| case "export": | |
| case "extends": | |
| case "finally": | |
| case "for": | |
| case "function": | |
| case "if": | |
| case "import": | |
| case "in": | |
| case "instanceof": | |
| case "let": | |
| case "new": | |
| case "return": | |
| case "super": | |
| case "switch": | |
| case "this": | |
| case "throw": | |
| case "try": | |
| case "typeof": | |
| case "var": | |
| case "void": | |
| case "while": | |
| case "with": | |
| return true; | |
| case "arguments": | |
| case "eval": | |
| // http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words | |
| // http://www.ecma-international.org/ecma-262/6.0/#sec-identifiers-static-semantics-early-errors | |
| case "implements": | |
| case "interface": | |
| case "package": | |
| case "private": | |
| case "protected": | |
| case "public": | |
| case "static": | |
| case "yield": | |
| return strictMode; | |
| } | |
| return false; | |
| } | |
| /// Generates the HTML template file needed to load and run a ddc test in | |
| /// the browser. | |
| /// | |
| /// [testName] is the short name of the test without any subdirectory path | |
| /// or extension, like "math_test". [testNameAlias] is the alias of the | |
| /// test variable used for import/export (usually relative to its module root). | |
| /// [testJSDir] is the relative path to the build directory where the | |
| /// ddc-generated JS file is stored. [ddcModuleFormat] determines whether to | |
| /// emit a template that works with the DDC module format or one that works with | |
| /// the AMD module format. [canaryMode] is whether DDC is running in canary | |
| /// mode. If this flag and [ddcModuleFormat] is enabled, a template that works | |
| /// with the DDC hot reload format will be emitted. | |
| String ddcHtml( | |
| String testName, | |
| String testNameAlias, | |
| String testJSDir, | |
| Compiler compiler, | |
| String genDir, | |
| bool nativeNonNullAsserts, | |
| bool jsInteropNonNullAsserts, { | |
| bool ddcModuleFormat = false, | |
| bool canaryMode = false, | |
| }) { | |
| var testId = pathToJSIdentifier(testName); | |
| var testIdAlias = pathToJSIdentifier(testNameAlias); | |
| var ddcGenDir = '/root_build/$genDir'; | |
| var hotReloadFormat = ddcModuleFormat && canaryMode; | |
| var sdkAndAsyncHelperSetup = """ | |
| _debugger.registerDevtoolsFormatter(); | |
| testErrorToStackTrace = function(error) { | |
| var stackTrace = getStackTraceString(error); | |
| var lines = stackTrace.split("\\n"); | |
| // Remove the first line, which is just "Error". | |
| lines = lines.slice(1); | |
| // Strip off all of the lines for the bowels of the test runner. | |
| for (var i = 0; i < lines.length; i++) { | |
| if (lines[i].indexOf("dartMainRunner") != -1) { | |
| lines = lines.slice(0, i); | |
| break; | |
| } | |
| } | |
| // TODO(rnystrom): It would be nice to shorten the URLs of the remaining | |
| // lines too. | |
| return lines.join("\\n"); | |
| }; | |
| """; | |
| var sdkFlagSetup = | |
| """ | |
| runtime.nativeNonNullAsserts($nativeNonNullAsserts); | |
| runtime.jsInteropNonNullAsserts($jsInteropNonNullAsserts); | |
| """; | |
| String script; | |
| if (ddcModuleFormat) { | |
| var appName = '/root_dart/$testJSDir'; | |
| // Used in the DDC module system for multi-app workflows, and are simply | |
| // placeholder values here. | |
| var uuid = '00000000-0000-0000-0000-000000000000'; | |
| var loadPackagesScript = [ | |
| for (var p in testPackages) | |
| """<script defer type="text/javascript" | |
| src="$ddcGenDir/pkg/ddc/$p.js"></script>""", | |
| ].join('\n'); | |
| String libraryImports; | |
| String startCode; | |
| if (hotReloadFormat) { | |
| libraryImports = """ | |
| let _debugger = dartDevEmbedder.debugger; | |
| let getStackTraceString = function(error) { | |
| return _debugger.stackTrace(error); | |
| } | |
| """; | |
| sdkFlagSetup = | |
| """ | |
| let sdkOptions = { | |
| nativeNonNullAsserts: $nativeNonNullAsserts, | |
| jsInteropNonNullAsserts: $jsInteropNonNullAsserts, | |
| }; | |
| """; | |
| startCode = | |
| """dartDevEmbedder.runMain("org-dartlang-app:/$testNameAlias.dart", | |
| sdkOptions);"""; | |
| } else { | |
| libraryImports = | |
| """ | |
| let sdk = dart_library.import("dart_sdk", "$appName"); | |
| let runtime = sdk.dart; | |
| let getStackTraceString = function(error) { | |
| return runtime.stackTrace(error).toString(); | |
| } | |
| let _debugger = sdk._debugger; | |
| """; | |
| startCode = """dart_library.start("$appName", "$uuid", "$testName", | |
| "$testIdAlias", false)"""; | |
| } | |
| script = | |
| """ | |
| <script defer type="text/javascript" | |
| src="/root_dart/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js"></script> | |
| <script defer type="text/javascript" src="$ddcGenDir/sdk/ddc/dart_sdk.js"></script> | |
| $loadPackagesScript | |
| <script defer type="text/javascript" src="$appName/$testName.js"></script> | |
| <script type="text/javascript">""" | |
| // DDC module format doesn't defer the execution until the document is finished | |
| // parsing. We can defer scripts, but only if they are in separate files and not | |
| // inline JS like below. In order to make sure everything is loaded and be | |
| // consistent with the AMD module format, we should wait until a | |
| // `DOMContentLoaded` event is fired. Other options are using `type = "module"` | |
| // or putting this in a separate JS file, but this is the simplest solution. | |
| """ | |
| document.addEventListener("DOMContentLoaded", (e) => { | |
| $libraryImports | |
| $sdkAndAsyncHelperSetup | |
| $sdkFlagSetup | |
| dartMainRunner(function () { | |
| return $startCode; | |
| }); | |
| }); | |
| </script> | |
| """; | |
| } else { | |
| var packagePaths = [ | |
| for (var p in testPackages) ' "$p": "$ddcGenDir/pkg/amd/$p",', | |
| ].join("\n"); | |
| script = | |
| """ | |
| <script> | |
| var require = { | |
| baseUrl: "/root_dart/$testJSDir", | |
| paths: { | |
| "dart_sdk": "$ddcGenDir/sdk/amd/dart_sdk", | |
| $packagePaths | |
| }, | |
| waitSeconds: 45, | |
| }; | |
| </script> | |
| <script type="text/javascript" | |
| src="/root_dart/third_party/requirejs/require.js"></script> | |
| <script type="text/javascript"> | |
| requirejs(["$testName", "dart_sdk"], function($testId, sdk) { | |
| let runtime = sdk.dart; | |
| let getStackTraceString = function(error) { | |
| return runtime.stackTrace(error).toString(); | |
| } | |
| let _debugger = sdk._debugger; | |
| $sdkAndAsyncHelperSetup | |
| $sdkFlagSetup | |
| dartMainRunner(function testMainWrapper() { | |
| return $testId.$testIdAlias.main(); | |
| }); | |
| }); | |
| </script> | |
| """; | |
| } | |
| return """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
| <meta charset="utf-8"> | |
| <meta name="dart.unittest" content="full-stack-traces"> | |
| <title>Test $testName</title> | |
| <style> | |
| .unittest-table { font-family:monospace; border:1px; } | |
| .unittest-pass { background: #6b3;} | |
| .unittest-fail { background: #d55;} | |
| .unittest-error { background: #a11;} | |
| </style> | |
| <script type="text/javascript" src="$_sealNativeObjectScript"></script> | |
| </head> | |
| <body> | |
| <h1>Running $testName</h1> | |
| <script type="text/javascript" | |
| src="/root_dart/pkg/test_runner/lib/src/test_controller.js"> | |
| </script> | |
| $script | |
| </body> | |
| </html> | |
| """; | |
| } | |
| /* | |
| Helper function to turn WebAssembly arrays into strings (which isn't possible | |
| with JavaScript alone). Generated from this WAT module via `wasm-as --enable-gc | |
| --enable-reference-types` and `base64 -w 0`: | |
| (module | |
| (type $i8array (array (mut i8))) | |
| (type $i16array (array (mut i16))) | |
| (type $externarray (array (mut (ref extern)))) | |
| (import "wasm:js-string" "fromCharCodeArray" | |
| (func $fromCharCodeArray (param (ref null $i16array) i32 i32) (result (ref extern))) | |
| ) | |
| (import "wasm:js-string" "intoCharCodeArray" | |
| (func $intoCharCodeArray (param (ref null extern) (ref null $i16array) i32) (result i32)) | |
| ) | |
| (func (export "stringFromAsciiBytes") | |
| (param $arr (ref $i8array)) | |
| (param $start i32) | |
| (param $length i32) | |
| (result (ref extern)) | |
| (local $i i32) | |
| (local $expanded (ref $i16array)) | |
| ;; Copy i8 array into an i16 array | |
| (local.set $i (local.get $length)) | |
| (local.set $expanded (array.new $i16array (i32.const 0) (local.get $length))) | |
| ;; do { i--; expanded[i] = arr[i + start]; } while (i >= 0); | |
| (block $break | |
| (loop $loop | |
| (local.set $i (i32.add (local.get $i) (i32.const -1))) | |
| (br_if $break | |
| (i32.lt_s | |
| (local.get $i) | |
| (i32.const 0) | |
| ) | |
| ) | |
| (array.set $i16array | |
| (local.get $expanded) | |
| (local.get $i) | |
| (array.get_u $i8array | |
| (local.get $arr) | |
| (i32.add (local.get $i) (local.get $start)) | |
| ) | |
| ) | |
| br $loop | |
| ) | |
| ) | |
| (call $fromCharCodeArray | |
| (local.get $expanded) | |
| (i32.const 0) | |
| (array.len (local.get $expanded)) | |
| ) | |
| ) | |
| (func (export "stringFromCharCodeArray") | |
| (param $arr (ref $i16array)) | |
| (param $start i32) | |
| (param $length i32) | |
| (result (ref extern)) | |
| (call $fromCharCodeArray | |
| (local.get $arr) | |
| (local.get $start) | |
| (i32.add | |
| (local.get $start) | |
| (local.get $length) | |
| ) | |
| ) | |
| ) | |
| (func (export "stringToCharCodeArray") | |
| (param $str (ref extern)) | |
| (param $array (ref $i16array)) | |
| (param $start i32) | |
| (drop (call $intoCharCodeArray | |
| (local.get $str) | |
| (local.get $array) | |
| (local.get $start) | |
| )) | |
| ) | |
| (func (export "emptyExternRefArray") | |
| (result (ref $externarray)) | |
| (array.new $externarray (extern.convert_any (ref.i31 (i32.const 0))) (i32.const 0)) | |
| ) | |
| ) | |
| */ | |
| const _wasmStandaloneArrayHelper = | |
| 'AGFzbQEAAAABOwledwFeeAFeZG8BYANjAH9/AWRvYANvYwB/AX9gA2QBf38BZG9gA2QAf38BZG9gA2RvZAB/AGAAAWQCAkcCDndhc206anMtc3RyaW5nEWZyb21DaGFyQ29kZUFycmF5AAMOd2FzbTpqcy1zdHJpbmcRaW50b0NoYXJDb2RlQXJyYXkABAMFBAUGBwgHYAQUc3RyaW5nRnJvbUFzY2lpQnl0ZXMAAhdzdHJpbmdGcm9tQ2hhckNvZGVBcnJheQADFXN0cmluZ1RvQ2hhckNvZGVBcnJheQAEE2VtcHR5RXh0ZXJuUmVmQXJyYXkABQpwBEYCAX8BZAAgAiEDQQAgAvsGACEEAkADQCADQX9qIQMgA0EASA0BIAQgAyAAIAMgAWr7DQH7DgAMAAsACyAEQQAgBPsPEAALDQAgACABIAEgAmoQAAsLACAAIAEgAhABGgsNAEEA+xz7G0EA+wYCCw=='; | |
| /* | |
| */ | |
| String dart2WasmStandaloneHtml(String title, String wasmPath) { | |
| const standaloneEmbedder = | |
| """ | |
| const { instance: helperInstance } = await WebAssembly.instantiate(Uint8Array.fromBase64('$_wasmStandaloneArrayHelper'), {}, { | |
| builtins: ['js-string'] | |
| }); | |
| const dartEmbedder = { | |
| // See sdk/lib/_internal/wasm_standalone/lib/embedder.dart for required definitions. | |
| scheduleOnce: (delayInMicros, callback, arg) => { | |
| const timeout = setTimeout(() => callback(arg), Number(delayInMicros / 1000n)); | |
| return {timeout}; | |
| }, | |
| scheduleRepeated: (intervalMicros, callback, arg) => { | |
| const timeout = setInterval(() => callback(arg), Number(intervalMicros / 1000n)); | |
| return {timeout}; | |
| }, | |
| queueMicrotask: (callback, arg) => { | |
| queueMicrotask(() => callback(arg)); | |
| }, | |
| clearSchedule: (schedule) => { | |
| clearTimeout(schedule.timeout); | |
| }, | |
| currentTime: () => BigInt(Date.now()) * 1000n, | |
| stringFromAsciiBytes: (chars, start, length) => { | |
| const str = helperInstance.exports.stringFromAsciiBytes(chars, start, length); | |
| return str; | |
| }, | |
| stringFromCharCodeArray: (chars, start, length) => { | |
| const str = helperInstance.exports.stringFromCharCodeArray(chars, start, length); | |
| return str; | |
| }, | |
| stringLength: (s) => s.length, | |
| stringEquals: (a, b) => a === b, | |
| stringCompare: (a, b) => a === b ? 0 : (a < b ? -1 : 1), | |
| stringCodeUnitAt: (str, idx) => str.charCodeAt(idx), | |
| stringIndexOfString: (a, b, start) => a.indexOf(b, start), | |
| stringLastIndexOfString: (a, b, start) => a.lastIndexOf(b, start), | |
| stringReplaceAllString: (str, needle, replace) => str.replaceAll(needle, () => replace), | |
| stringReplaceAllRegExp: (str, regex, replace) => str.replaceAll(regex.regular, () => replace), | |
| stringSubstring: (str, start, end) => str.substring(start, end), | |
| stringToLowerCase: (str) => str.toLowerCase(), | |
| stringToUpperCase: (str) => str.toUpperCase(), | |
| stringConcat: (a, b) => a + b, | |
| stringRepeat: (str, amount) => str.repeat(amount), | |
| stringReplaceRange: (str, start, end, replacement) => { | |
| const before = str.substring(0, start); | |
| const after = str.substring(end); | |
| return before + replacement + after; | |
| }, | |
| stringToCodeUnits: (str, array, startIndex) => { | |
| helperInstance.exports.stringToCharCodeArray(str, array, startIndex); | |
| }, | |
| monotonicClockFrequency: () => 1_000_000, | |
| monotonicClockTicks: () => BigInt(Math.round(performance.now() * 1000)), | |
| weakRefCreate: (dartValue) => new WeakRef(dartValue), | |
| weakRefGet: (weakRef) => weakRef.deref() ?? null, | |
| expandoCreate: () => new WeakMap(), | |
| expandoGet: (expando, target, _hash) => expando.get(target) ?? null, | |
| expandoSet: (expando, target, _hash, value) => expando.set(target, value), | |
| finalizerCreate: (callback, firstParameter) => { | |
| return new FinalizationRegistry((heldValue) => { | |
| callback(heldValue, firstParameter); | |
| }); | |
| }, | |
| finalizerAttach: (finalizer, object, token, detachToken) => { | |
| if (detachToken) { | |
| finalizer.register(object, token, detachToken); | |
| } else { | |
| finalizer.register(object, token); | |
| } | |
| }, | |
| finalizerDetach: (finalizer, detachToken) => finalizer.unregister(detachToken), | |
| baseUri: () => globalThis.location.href, | |
| isWindows: () => false, | |
| stackTraceGetCurrent: () => new Error().stack, | |
| stackTraceToString: (trace) => { | |
| const stackString = trace.toString(); | |
| const frames = stackString.split('\\n'); | |
| // Format of stack traces is: | |
| // 1. stackTraceGetCurrent (from this embedder object) | |
| // 2. module0.StackTrace.current <noInline> | |
| // 3. The callsite we care about. | |
| const drop = 1 + frames.findIndex((l) => l.indexOf('stackTraceGetCurrent') > 0); | |
| return frames.slice(drop).join('\\n'); | |
| }, | |
| doubleTryParse: (source) => { | |
| if (!/${r'^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$'}/.test(source)) { | |
| const trimmed = source.trim(); | |
| // parseFloat is more lenient than double.tryParse, see wasm/lib/double_patch.dart for details. | |
| if (!(trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN')) { | |
| return null; | |
| } | |
| return { result: NaN }; | |
| } else { | |
| return { result: parseFloat(source) }; | |
| } | |
| }, | |
| tryParseResultGetDouble: ({result}) => result, | |
| doubleParseInfallible: (str) => +str, | |
| i64ToString: (source, radix) => source.toString(radix), | |
| f64ToExponential: (source) => source.toExponential(), | |
| f64ToExponentialWithFractionDigits: (source, digits) => { | |
| return source.toExponential(digits); | |
| }, | |
| f64ToPrecision: (source, digits) => source.toPrecision(digits), | |
| f64ToFixed: (source, digits) => source.toFixed(digits), | |
| f64ToString: (source) => { | |
| if (Object.is(source, -0)) return '-0.0'; | |
| if (Number.isNaN(source)) return 'NaN'; | |
| if (source == Number.NEGATIVE_INFINITY) return '-Infinity'; | |
| if (source == Number.POSITIVE_INFINITY) return 'Infinity'; | |
| let formatted = source.toString(); | |
| if (source % 1.0 == 0 && formatted.indexOf('e') == -1) { | |
| formatted += '.0'; | |
| } | |
| return formatted; | |
| }, | |
| stringBufferCreate: () => ({ contents: '' }), | |
| stringBufferWriteString: (buffer, append) => { | |
| buffer.contents += append; | |
| }, | |
| stringBufferWriteCharCode: (buffer, code) => { | |
| buffer.contents += String.fromCodePoint(code); | |
| }, | |
| stringBufferClear: (buffer) => { | |
| buffer.contents = '' | |
| }, | |
| stringBufferLength: (buffer) => buffer.contents.length, | |
| stringBufferToString: ({contents}) => contents, | |
| regexpCreateOrFailWithString: (pattern, multiLine, caseSensitive, unicode, dotAll) => { | |
| let flags = ''; | |
| if (multiLine) flags += 'm'; | |
| if (!caseSensitive) flags += 'i'; | |
| if (unicode) flags += 'u'; | |
| if (dotAll) flags += 's'; | |
| try { | |
| // Prepare two regular expressions, one for regular matches and one | |
| // for matchAsPrefix. | |
| return { | |
| regular: new RegExp(pattern, flags + 'g'), | |
| asPrefix: new RegExp(pattern, flags + 'y'), | |
| }; | |
| } catch (e) { | |
| return String(e); | |
| } | |
| }, | |
| regexpIsRegexp: (source) => typeof(source) !== 'string', | |
| regexpEscape: (source) => { | |
| // Note: We can't use RegExp.escape here, it escapes too much and we | |
| // have tests expecting that e.g. \t isn't escaped. | |
| if (/${r'[[\]{}()*+?.\\^$|]'}/.test(source)) { | |
| return source.replace(/${r'[[\]{}()*+?.\\^$|]'}/g, "${r'\\$&'}"); | |
| } else { | |
| return source; | |
| } | |
| }, | |
| regexpMatch: (regexp, string, start, asPrefix) => { | |
| const regex = asPrefix ? regexp.asPrefix : regexp.regular; | |
| regex.lastIndex = start; | |
| const match = regex.exec(string); | |
| if (match) { | |
| return { | |
| start: match.index, | |
| end: match.index + match[0].length, | |
| groupNames: match.groups ? Object.keys(match.groups) : [], | |
| groups: match, | |
| }; | |
| } else { | |
| return null; | |
| } | |
| }, | |
| regexpMatchGetStart: (match) => match.start, | |
| regexpMatchGetEnd: (match) => match.end, | |
| regexpMatchGetGroupCount: (match) => match.groups.length - 1, | |
| regexpMatchGetGroup: (match, index) => match.groups[index] ?? null, | |
| regexpMatchGetNamedGroups: (match) => match.groupNames.length, | |
| regexpMatchGetGroupName: (match, index) => match.groupNames[index], | |
| regexpMatchGetGroupByName: (match, index) => match.groups.groups[match.groupNames[index]] ?? null, | |
| timeZoneNameForClampedSeconds: (secondsSinceEpoch) => { | |
| const date = new Date(Number(secondsSinceEpoch * 1000n)); | |
| const match = /\\((.*)\\)/.exec(date.toString()); | |
| if (match == null) { | |
| // This should never happen on any recent browser. | |
| return ''; | |
| } | |
| return match[1]; | |
| }, | |
| timeZoneOffsetInSecondsForClampedSeconds: (secondsSinceEpoch) => { | |
| const date = new Date(Number(secondsSinceEpoch * 1000n)); | |
| // This needs to be negated because Dart wants the difference between | |
| // local time and UTC. | |
| return -date.getTimezoneOffset() * 60; | |
| }, | |
| mathPow: Math.pow, | |
| mathAtan2: Math.atan2, | |
| mathSin: Math.sin, | |
| mathCos: Math.cos, | |
| mathTan: Math.tan, | |
| mathAcos: Math.acos, | |
| mathAsin: Math.asin, | |
| mathAtan: Math.atan, | |
| mathExp: Math.exp, | |
| mathLog: Math.log, | |
| randomInt: () => { | |
| const low = (Math.random() * 4294967295.0) | 0; | |
| const high = (Math.random() * 4294967295.0) | 0; | |
| return (BigInt(high) << 32n) | BigInt(low); | |
| }, | |
| randomIntSecure: () => { | |
| const typedArray = new BigUint64Array(1); | |
| crypto.getRandomValues(typedArray); | |
| return typedArray[0]; | |
| }, | |
| print: (line) => { | |
| if (typeof dartPrint == "function") { | |
| dartPrint(line); | |
| } else { | |
| console.log(line); | |
| } | |
| }, | |
| jsonEncodeString: JSON.stringify, | |
| debugger: () => { | |
| debugger; | |
| }, | |
| inspect: (ref) => {}, | |
| timelineStreamEnabled: () => false, | |
| reportTaskEvent: (taskId, flowId, type, name, jsonArgs) => {}, | |
| }; | |
| """; | |
| return """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="dart.unittest" content="full-stack-traces"> | |
| <title> Test $title </title> | |
| <link rel="preload" href="$wasmPath" as="fetch" crossorigin> | |
| <style> | |
| .unittest-table { font-family:monospace; border:1px; } | |
| .unittest-pass { background: #6b3;} | |
| .unittest-fail { background: #d55;} | |
| .unittest-error { background: #a11;} | |
| </style> | |
| </head> | |
| <body> | |
| <h1> Running $title</h1> | |
| <script type="text/javascript" | |
| src="/root_dart/pkg/test_runner/lib/src/test_controller.js"> | |
| </script> | |
| <script type="module"> | |
| $standaloneEmbedder | |
| // Default stack trace limit in V8 is 10, which hides some of the stack frames | |
| // we check in stack trace tests. | |
| Error.stackTraceLimit = 20; | |
| async function loadAndRun(wasmPath) { | |
| const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmPath), {dart: dartEmbedder}); | |
| window.loadData = async (relativeToWasmFileUri) => { | |
| const path = '$wasmPath'.slice(0, wasmPath.lastIndexOf('/')); | |
| const response = await fetch(`\${path}/\${relativeToWasmFileUri}`); | |
| return response.arrayBuffer(); | |
| }; | |
| dartMainRunner(() => { | |
| instance.exports.\$invokeMain(helperInstance.exports.emptyExternRefArray()); | |
| }); | |
| } | |
| loadAndRun('$wasmPath'); | |
| </script> | |
| </body> | |
| </html>"""; | |
| } | |
| String dart2wasmHtml( | |
| String title, | |
| String wasmPath, | |
| String mjsPath, | |
| String supportJsPath, | |
| ) { | |
| return """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
| <meta charset="utf-8"> | |
| <meta name="dart.unittest" content="full-stack-traces"> | |
| <title> Test $title </title> | |
| <link rel="preload" href="$wasmPath" as="fetch" crossorigin> | |
| <style> | |
| .unittest-table { font-family:monospace; border:1px; } | |
| .unittest-pass { background: #6b3;} | |
| .unittest-fail { background: #d55;} | |
| .unittest-error { background: #a11;} | |
| </style> | |
| </head> | |
| <body> | |
| <h1> Running $title </h1> | |
| <script type="text/javascript" | |
| src="/root_dart/pkg/test_runner/lib/src/test_controller.js"> | |
| </script> | |
| <script type="module"> | |
| // Default stack trace limit in V8 is 10, which hides some of the stack frames | |
| // we check in stack trace tests. | |
| Error.stackTraceLimit = 20; | |
| async function loadAndRun(mjsPath, wasmPath, supportJsPath) { | |
| const supportJSExpression = await (await fetch(supportJsPath)).text(); | |
| const support = eval(supportJSExpression); | |
| if (support !== true) { | |
| dartMainRunner(() => { | |
| throw 'This browser does not support the required features to run the dart2wasm compiled app!'; | |
| }); | |
| return; | |
| } | |
| const mjs = await import(mjsPath); | |
| const compiledApp = await mjs.compileStreaming(fetch(wasmPath)); | |
| window.loadData = async (relativeToWasmFileUri) => { | |
| const path = '$wasmPath'.slice(0, wasmPath.lastIndexOf('/')); | |
| const response = await fetch(`\${path}/\${relativeToWasmFileUri}`); | |
| return response.arrayBuffer(); | |
| }; | |
| const appInstance = await compiledApp.instantiate({}, { | |
| loadDeferredModules: (modules, handleWasmBytes) => | |
| Promise.all(modules.map((m) => handleWasmBytes(m, fetch(m)))), | |
| }); | |
| window.appInstanceForTesting = appInstance; | |
| dartMainRunner(() => { | |
| appInstance.invokeMain(); | |
| }); | |
| } | |
| loadAndRun('$mjsPath', '$wasmPath', '$supportJsPath'); | |
| </script> | |
| </body> | |
| </html>"""; | |
| } |