@jsenv/core 28.0.0 → 28.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllable_child_process.mjs +1 -2
- package/dist/controllable_worker_thread.mjs +1 -2
- package/dist/js/autoreload.js +27 -11
- package/dist/js/execute_using_dynamic_import.js +804 -1
- package/dist/js/script_type_module_supervisor.js +129 -0
- package/dist/js/supervisor.js +921 -0
- package/dist/js/{wrapper.mjs → ws.js} +0 -0
- package/dist/main.js +555 -616
- package/package.json +13 -13
- package/readme.md +2 -2
- package/src/build/build.js +8 -8
- package/src/build/inject_global_version_mappings.js +3 -3
- package/src/build/{resync_ressource_hints.js → resync_resource_hints.js} +10 -12
- package/src/build/start_build_server.js +4 -7
- package/src/dev/start_dev_server.js +2 -2
- package/src/execute/execute.js +1 -1
- package/src/execute/run.js +26 -38
- package/src/execute/runtimes/browsers/from_playwright.js +51 -77
- package/src/execute/runtimes/node/node_child_process.js +36 -36
- package/src/execute/runtimes/node/node_worker_thread.js +36 -36
- package/src/omega/kitchen.js +33 -14
- package/src/omega/omega_server.js +2 -2
- package/src/omega/server/file_service.js +5 -5
- package/src/omega/url_graph/url_graph_load.js +4 -4
- package/src/omega/url_graph/url_info_transformations.js +8 -1
- package/src/omega/url_graph.js +3 -3
- package/src/plugins/autoreload/client/reload.js +22 -9
- package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +5 -5
- package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +3 -3
- package/src/plugins/autoreload/jsenv_plugin_hmr.js +1 -1
- package/src/plugins/explorer/jsenv_plugin_explorer.js +1 -1
- package/src/plugins/import_meta_hot/html_hot_dependencies.js +6 -6
- package/src/plugins/importmap/jsenv_plugin_importmap.js +5 -3
- package/src/plugins/inject_globals/inject_globals.js +3 -3
- package/src/plugins/inline/jsenv_plugin_data_urls.js +1 -1
- package/src/plugins/inline/jsenv_plugin_html_inline_content.js +10 -5
- package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +2 -13
- package/src/plugins/plugin_controller.js +2 -2
- package/src/plugins/plugins.js +5 -5
- package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +4 -4
- package/src/plugins/supervisor/client/script_type_module_supervisor.js +108 -0
- package/src/plugins/supervisor/client/supervisor.js +921 -0
- package/src/plugins/{html_supervisor/jsenv_plugin_html_supervisor.js → supervisor/jsenv_plugin_supervisor.js} +131 -105
- package/src/plugins/toolbar/client/execution/toolbar_execution.js +1 -1
- package/src/plugins/toolbar/jsenv_plugin_toolbar.js +5 -5
- package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +9 -7
- package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +8 -7
- package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +13 -7
- package/src/plugins/transpilation/babel/new_stylesheet/babel_plugin_new_stylesheet_as_jsenv_import.js +6 -4
- package/src/plugins/transpilation/jsenv_plugin_transpilation.js +4 -2
- package/src/plugins/url_analysis/html/html_urls.js +13 -12
- package/src/plugins/url_analysis/jsenv_plugin_url_analysis.js +1 -1
- package/src/test/coverage/babel_plugin_instrument.js +1 -35
- package/src/test/coverage/empty_coverage_factory.js +1 -1
- package/src/test/execute_plan.js +7 -3
- package/src/test/execute_test_plan.js +2 -1
- package/src/test/logs_file_execution.js +49 -8
- package/dist/js/html_supervisor_installer.js +0 -1091
- package/dist/js/html_supervisor_setup.js +0 -89
- package/dist/js/uneval.js +0 -804
- package/src/plugins/html_supervisor/client/error_formatter.js +0 -426
- package/src/plugins/html_supervisor/client/error_in_notification.js +0 -21
- package/src/plugins/html_supervisor/client/error_overlay.js +0 -191
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +0 -315
- package/src/plugins/html_supervisor/client/html_supervisor_setup.js +0 -89
- package/src/plugins/html_supervisor/client/perf_browser.js +0 -17
- package/src/plugins/html_supervisor/client/uneval_exception.js +0 -8
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
window.__supervisor__ = (() => {
|
|
2
|
+
const notImplemented = () => {
|
|
3
|
+
throw new Error(`window.__supervisor__.setup() not called`)
|
|
4
|
+
}
|
|
5
|
+
const executionResults = {}
|
|
6
|
+
const supervisor = {
|
|
7
|
+
reportError: notImplemented,
|
|
8
|
+
superviseScript: notImplemented,
|
|
9
|
+
reloadSupervisedScript: notImplemented,
|
|
10
|
+
collectScriptResults: notImplemented,
|
|
11
|
+
getScriptExecutionResults: () => {
|
|
12
|
+
// wait for page to load before collecting script execution results
|
|
13
|
+
const htmlReadyPromise = new Promise((resolve) => {
|
|
14
|
+
if (document.readyState === "complete") {
|
|
15
|
+
resolve()
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
const loadCallback = () => {
|
|
19
|
+
window.removeEventListener("load", loadCallback)
|
|
20
|
+
resolve()
|
|
21
|
+
}
|
|
22
|
+
window.addEventListener("load", loadCallback)
|
|
23
|
+
})
|
|
24
|
+
return htmlReadyPromise.then(() => {
|
|
25
|
+
return supervisor.collectScriptResults()
|
|
26
|
+
})
|
|
27
|
+
},
|
|
28
|
+
executionResults,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
supervisor.setupReportException = ({
|
|
32
|
+
rootDirectoryUrl,
|
|
33
|
+
errorNotification,
|
|
34
|
+
errorOverlay,
|
|
35
|
+
errorBaseUrl,
|
|
36
|
+
openInEditor,
|
|
37
|
+
}) => {
|
|
38
|
+
const DYNAMIC_IMPORT_FETCH_ERROR = "dynamic_import_fetch_error"
|
|
39
|
+
const DYNAMIC_IMPORT_EXPORT_MISSING = "dynamic_import_export_missing"
|
|
40
|
+
const DYNAMIC_IMPORT_SYNTAX_ERROR = "dynamic_import_syntax_error"
|
|
41
|
+
|
|
42
|
+
const createException = ({
|
|
43
|
+
reason,
|
|
44
|
+
reportedBy,
|
|
45
|
+
url,
|
|
46
|
+
line,
|
|
47
|
+
column,
|
|
48
|
+
} = {}) => {
|
|
49
|
+
const exception = {
|
|
50
|
+
reason,
|
|
51
|
+
reportedBy,
|
|
52
|
+
isError: false, // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
|
|
53
|
+
code: null,
|
|
54
|
+
message: null,
|
|
55
|
+
stack: null,
|
|
56
|
+
stackFormatIsV8: null,
|
|
57
|
+
stackSourcemapped: null,
|
|
58
|
+
originalStack: null,
|
|
59
|
+
meta: null,
|
|
60
|
+
site: {
|
|
61
|
+
isInline: null,
|
|
62
|
+
url: null,
|
|
63
|
+
line: null,
|
|
64
|
+
column: null,
|
|
65
|
+
originalUrl: null,
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const writeBasicProperties = () => {
|
|
70
|
+
if (reason === undefined) {
|
|
71
|
+
exception.message = "undefined"
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
if (reason === null) {
|
|
75
|
+
exception.message = "null"
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
if (typeof reason === "string") {
|
|
79
|
+
exception.message = reason
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
if (reason instanceof Error) {
|
|
83
|
+
const error = reason
|
|
84
|
+
let message = error.message
|
|
85
|
+
exception.isError = true
|
|
86
|
+
if (Error.captureStackTrace) {
|
|
87
|
+
// stackTrace formatted by V8
|
|
88
|
+
exception.message = message
|
|
89
|
+
exception.stack = getErrorStackWithoutErrorMessage(error)
|
|
90
|
+
exception.stackFormatIsV8 = true
|
|
91
|
+
exception.stackSourcemapped = true
|
|
92
|
+
} else {
|
|
93
|
+
exception.message = message
|
|
94
|
+
exception.stack = error.stack ? ` ${error.stack}` : null
|
|
95
|
+
exception.stackFormatIsV8 = false
|
|
96
|
+
exception.stackSourcemapped = false
|
|
97
|
+
}
|
|
98
|
+
if (error.reportedBy) {
|
|
99
|
+
exception.reportedBy = error.reportedBy
|
|
100
|
+
}
|
|
101
|
+
if (error.url) {
|
|
102
|
+
Object.assign(exception.site, resolveUrlSite({ url: error.url }))
|
|
103
|
+
}
|
|
104
|
+
if (error.needsReport) {
|
|
105
|
+
exception.needsReport = true
|
|
106
|
+
}
|
|
107
|
+
export_missing: {
|
|
108
|
+
// chrome
|
|
109
|
+
if (message.includes("does not provide an export named")) {
|
|
110
|
+
exception.code = DYNAMIC_IMPORT_EXPORT_MISSING
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
// firefox
|
|
114
|
+
if (message.startsWith("import not found:")) {
|
|
115
|
+
exception.code = DYNAMIC_IMPORT_EXPORT_MISSING
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
// safari
|
|
119
|
+
if (message.startsWith("import binding name")) {
|
|
120
|
+
exception.code = DYNAMIC_IMPORT_EXPORT_MISSING
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
js_syntax_error: {
|
|
125
|
+
if (error.name === "SyntaxError" && typeof line === "number") {
|
|
126
|
+
exception.code = DYNAMIC_IMPORT_SYNTAX_ERROR
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
if (typeof reason === "object") {
|
|
133
|
+
exception.code = reason.code
|
|
134
|
+
exception.message = reason.message
|
|
135
|
+
exception.stack = reason.stack
|
|
136
|
+
if (reason.reportedBy) {
|
|
137
|
+
exception.reportedBy = reason.reportedBy
|
|
138
|
+
}
|
|
139
|
+
if (reason.url) {
|
|
140
|
+
Object.assign(exception.site, resolveUrlSite({ url: reason.url }))
|
|
141
|
+
}
|
|
142
|
+
if (reason.needsReport) {
|
|
143
|
+
exception.needsReport = true
|
|
144
|
+
}
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
exception.message = JSON.stringify(reason)
|
|
148
|
+
}
|
|
149
|
+
writeBasicProperties()
|
|
150
|
+
|
|
151
|
+
// first create a version of the stack with file://
|
|
152
|
+
// (and use it to locate exception url+line+column)
|
|
153
|
+
if (exception.stack) {
|
|
154
|
+
exception.originalStack = exception.stack
|
|
155
|
+
exception.stack = replaceUrls(
|
|
156
|
+
exception.originalStack,
|
|
157
|
+
(serverUrlSite) => {
|
|
158
|
+
const fileUrlSite = resolveUrlSite(serverUrlSite)
|
|
159
|
+
if (exception.site.url === null) {
|
|
160
|
+
Object.assign(exception.site, fileUrlSite)
|
|
161
|
+
}
|
|
162
|
+
return stringifyUrlSite(fileUrlSite)
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
// then if it fails, use url+line+column passed
|
|
167
|
+
if (exception.site.url === null && url) {
|
|
168
|
+
if (typeof line === "string") {
|
|
169
|
+
line = parseInt(line)
|
|
170
|
+
}
|
|
171
|
+
if (typeof column === "string") {
|
|
172
|
+
column = parseInt(column)
|
|
173
|
+
}
|
|
174
|
+
const fileUrlSite = resolveUrlSite({ url, line, column })
|
|
175
|
+
if (
|
|
176
|
+
fileUrlSite.isInline &&
|
|
177
|
+
exception.code === DYNAMIC_IMPORT_SYNTAX_ERROR
|
|
178
|
+
) {
|
|
179
|
+
// syntax error on inline script need line-1 for some reason
|
|
180
|
+
if (Error.captureStackTrace) {
|
|
181
|
+
fileUrlSite.line--
|
|
182
|
+
} else {
|
|
183
|
+
// firefox and safari need line-2
|
|
184
|
+
fileUrlSite.line -= 2
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
Object.assign(exception.site, fileUrlSite)
|
|
188
|
+
}
|
|
189
|
+
exception.text = stringifyMessageAndStack(exception)
|
|
190
|
+
return exception
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const stringifyMessageAndStack = ({ message, stack }) => {
|
|
194
|
+
if (message && stack) {
|
|
195
|
+
return `${message}\n${stack}`
|
|
196
|
+
}
|
|
197
|
+
if (stack) {
|
|
198
|
+
return stack
|
|
199
|
+
}
|
|
200
|
+
return message
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const stringifyUrlSite = ({ url, line, column }) => {
|
|
204
|
+
return typeof line === "number" && typeof column === "number"
|
|
205
|
+
? `${url}:${line}:${column}`
|
|
206
|
+
: typeof line === "number"
|
|
207
|
+
? `${url}:${line}`
|
|
208
|
+
: url
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const resolveUrlSite = ({ url, line, column }) => {
|
|
212
|
+
const inlineUrlMatch = url.match(
|
|
213
|
+
/@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/,
|
|
214
|
+
)
|
|
215
|
+
if (inlineUrlMatch) {
|
|
216
|
+
const htmlUrl = url.slice(0, inlineUrlMatch.index)
|
|
217
|
+
const tagLineStart = parseInt(inlineUrlMatch[1])
|
|
218
|
+
const tagColumnStart = parseInt(inlineUrlMatch[2])
|
|
219
|
+
const tagLineEnd = parseInt(inlineUrlMatch[3])
|
|
220
|
+
const tagColumnEnd = parseInt(inlineUrlMatch[4])
|
|
221
|
+
const extension = inlineUrlMatch[5]
|
|
222
|
+
url = htmlUrl
|
|
223
|
+
line = tagLineStart + (typeof line === "number" ? line : 0)
|
|
224
|
+
// stackTrace formatted by V8 (chrome)
|
|
225
|
+
if (Error.captureStackTrace) {
|
|
226
|
+
line--
|
|
227
|
+
}
|
|
228
|
+
column = tagColumnStart + (typeof column === "number" ? column : 0)
|
|
229
|
+
const fileUrl = resolveFileUrl(url)
|
|
230
|
+
return {
|
|
231
|
+
isInline: true,
|
|
232
|
+
serverUrl: url,
|
|
233
|
+
originalUrl: `${fileUrl}@L${tagLineStart}C${tagColumnStart}-L${tagLineEnd}C${tagColumnEnd}${extension}`,
|
|
234
|
+
url: fileUrl,
|
|
235
|
+
line,
|
|
236
|
+
column,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
isInline: false,
|
|
241
|
+
serverUrl: url,
|
|
242
|
+
url: resolveFileUrl(url),
|
|
243
|
+
line,
|
|
244
|
+
column,
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const getErrorStackWithoutErrorMessage = (error) => {
|
|
249
|
+
let stack = error.stack
|
|
250
|
+
const messageInStack = `${error.name}: ${error.message}`
|
|
251
|
+
if (stack.startsWith(messageInStack)) {
|
|
252
|
+
stack = stack.slice(messageInStack.length)
|
|
253
|
+
}
|
|
254
|
+
const nextLineIndex = stack.indexOf("\n")
|
|
255
|
+
if (nextLineIndex > -1) {
|
|
256
|
+
stack = stack.slice(nextLineIndex + 1)
|
|
257
|
+
}
|
|
258
|
+
return stack
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const resolveFileUrl = (url) => {
|
|
262
|
+
let urlObject = new URL(url)
|
|
263
|
+
if (urlObject.origin === window.origin) {
|
|
264
|
+
urlObject = new URL(
|
|
265
|
+
`${urlObject.pathname.slice(1)}${urlObject.search}`,
|
|
266
|
+
rootDirectoryUrl,
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
if (urlObject.href.startsWith("file:")) {
|
|
270
|
+
const atFsIndex = urlObject.pathname.indexOf("/@fs/")
|
|
271
|
+
if (atFsIndex > -1) {
|
|
272
|
+
const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length)
|
|
273
|
+
return new URL(afterAtFs, "file:///").href
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return urlObject.href
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// `Error: yo
|
|
280
|
+
// at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
|
|
281
|
+
// at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
|
|
282
|
+
// at postOrderExec (http://127.0.0.1:3000/src/__test__/file-throw.js:448:16)
|
|
283
|
+
// at http://127.0.0.1:3000/src/__test__/file-throw.js:399:18`.replace(/(?:https?|ftp|file):\/\/(.*+)$/gm, (...args) => {
|
|
284
|
+
// debugger
|
|
285
|
+
// })
|
|
286
|
+
const replaceUrls = (source, replace) => {
|
|
287
|
+
return source.replace(/(?:https?|ftp|file):\/\/\S+/gm, (match) => {
|
|
288
|
+
let replacement = ""
|
|
289
|
+
const lastChar = match[match.length - 1]
|
|
290
|
+
|
|
291
|
+
// hotfix because our url regex sucks a bit
|
|
292
|
+
const endsWithSeparationChar = lastChar === ")" || lastChar === ":"
|
|
293
|
+
if (endsWithSeparationChar) {
|
|
294
|
+
match = match.slice(0, -1)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const lineAndColumnPattern = /:([0-9]+):([0-9]+)$/
|
|
298
|
+
const lineAndColumMatch = match.match(lineAndColumnPattern)
|
|
299
|
+
if (lineAndColumMatch) {
|
|
300
|
+
const lineAndColumnString = lineAndColumMatch[0]
|
|
301
|
+
const lineString = lineAndColumMatch[1]
|
|
302
|
+
const columnString = lineAndColumMatch[2]
|
|
303
|
+
replacement = replace({
|
|
304
|
+
url: match.slice(0, -lineAndColumnString.length),
|
|
305
|
+
line: lineString ? parseInt(lineString) : null,
|
|
306
|
+
column: columnString ? parseInt(columnString) : null,
|
|
307
|
+
})
|
|
308
|
+
} else {
|
|
309
|
+
const linePattern = /:([0-9]+)$/
|
|
310
|
+
const lineMatch = match.match(linePattern)
|
|
311
|
+
if (lineMatch) {
|
|
312
|
+
const lineString = lineMatch[0]
|
|
313
|
+
replacement = replace({
|
|
314
|
+
url: match.slice(0, -lineString.length),
|
|
315
|
+
line: lineString ? parseInt(lineString) : null,
|
|
316
|
+
})
|
|
317
|
+
} else {
|
|
318
|
+
replacement = replace({
|
|
319
|
+
url: match,
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (endsWithSeparationChar) {
|
|
324
|
+
return `${replacement}${lastChar}`
|
|
325
|
+
}
|
|
326
|
+
return replacement
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let formatError
|
|
331
|
+
error_formatter: {
|
|
332
|
+
formatError = (exceptionInfo) => {
|
|
333
|
+
const errorParts = {
|
|
334
|
+
theme: "dark",
|
|
335
|
+
title: "An error occured",
|
|
336
|
+
text: "",
|
|
337
|
+
tip: "",
|
|
338
|
+
errorDetailsPromise: null,
|
|
339
|
+
}
|
|
340
|
+
const tips = []
|
|
341
|
+
tips.push("Click outside to close.")
|
|
342
|
+
errorParts.tip = tips.join(`\n <br />\n `)
|
|
343
|
+
|
|
344
|
+
const generateClickableText = (text) => {
|
|
345
|
+
const textWithHtmlLinks = makeLinksClickable(text, {
|
|
346
|
+
createLink: ({ url, line, column }) => {
|
|
347
|
+
const urlSite = resolveUrlSite({ url, line, column })
|
|
348
|
+
if (errorBaseUrl) {
|
|
349
|
+
if (urlSite.url.startsWith(rootDirectoryUrl)) {
|
|
350
|
+
urlSite.url = `${errorBaseUrl}${urlSite.url.slice(
|
|
351
|
+
rootDirectoryUrl.length,
|
|
352
|
+
)}`
|
|
353
|
+
} else {
|
|
354
|
+
urlSite.url = "file:///mocked_for_snapshots"
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const urlWithLineAndColumn = stringifyUrlSite(urlSite)
|
|
358
|
+
return {
|
|
359
|
+
href:
|
|
360
|
+
urlSite.url.startsWith("file:") && openInEditor
|
|
361
|
+
? `javascript:window.fetch('/__open_in_editor__/${encodeURIComponent(
|
|
362
|
+
urlWithLineAndColumn,
|
|
363
|
+
)}')`
|
|
364
|
+
: urlSite.url,
|
|
365
|
+
text: urlWithLineAndColumn,
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
})
|
|
369
|
+
return textWithHtmlLinks
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
errorParts.text = stringifyMessageAndStack({
|
|
373
|
+
message: exceptionInfo.message
|
|
374
|
+
? generateClickableText(exceptionInfo.message)
|
|
375
|
+
: "",
|
|
376
|
+
stack: exceptionInfo.stack
|
|
377
|
+
? generateClickableText(exceptionInfo.stack)
|
|
378
|
+
: "",
|
|
379
|
+
})
|
|
380
|
+
if (exceptionInfo.site.url) {
|
|
381
|
+
errorParts.errorDetailsPromise = (async () => {
|
|
382
|
+
try {
|
|
383
|
+
if (
|
|
384
|
+
exceptionInfo.code === DYNAMIC_IMPORT_FETCH_ERROR ||
|
|
385
|
+
exceptionInfo.reportedBy === "script_error_event"
|
|
386
|
+
) {
|
|
387
|
+
const response = await window.fetch(
|
|
388
|
+
`/__get_error_cause__/${encodeURIComponent(
|
|
389
|
+
exceptionInfo.site.isInline
|
|
390
|
+
? exceptionInfo.site.originalUrl
|
|
391
|
+
: exceptionInfo.site.url,
|
|
392
|
+
)}`,
|
|
393
|
+
)
|
|
394
|
+
if (response.status !== 200) {
|
|
395
|
+
return null
|
|
396
|
+
}
|
|
397
|
+
const causeInfo = await response.json()
|
|
398
|
+
if (!causeInfo) {
|
|
399
|
+
return null
|
|
400
|
+
}
|
|
401
|
+
const causeText =
|
|
402
|
+
causeInfo.code === "NOT_FOUND"
|
|
403
|
+
? stringifyMessageAndStack({
|
|
404
|
+
message: generateClickableText(causeInfo.reason),
|
|
405
|
+
stack: generateClickableText(causeInfo.codeFrame),
|
|
406
|
+
})
|
|
407
|
+
: stringifyMessageAndStack({
|
|
408
|
+
message: generateClickableText(causeInfo.stack),
|
|
409
|
+
stack: generateClickableText(causeInfo.codeFrame),
|
|
410
|
+
})
|
|
411
|
+
return {
|
|
412
|
+
cause: causeText,
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (
|
|
416
|
+
exceptionInfo.site.line !== undefined &&
|
|
417
|
+
// code frame showing internal window.reportError is pointless
|
|
418
|
+
!exceptionInfo.site.url.endsWith(
|
|
419
|
+
`script_type_module_supervisor.js`,
|
|
420
|
+
)
|
|
421
|
+
) {
|
|
422
|
+
const urlToFetch = new URL(
|
|
423
|
+
`/__get_code_frame__/${encodeURIComponent(
|
|
424
|
+
stringifyUrlSite(exceptionInfo.site),
|
|
425
|
+
)}`,
|
|
426
|
+
window.origin,
|
|
427
|
+
)
|
|
428
|
+
if (!exceptionInfo.stackSourcemapped) {
|
|
429
|
+
urlToFetch.searchParams.set("remap", "")
|
|
430
|
+
}
|
|
431
|
+
const response = await window.fetch(urlToFetch)
|
|
432
|
+
if (response.status !== 200) {
|
|
433
|
+
return null
|
|
434
|
+
}
|
|
435
|
+
const codeFrame = await response.text()
|
|
436
|
+
return {
|
|
437
|
+
codeFrame: generateClickableText(codeFrame),
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
} catch (e) {
|
|
441
|
+
// happens if server is closed for instance
|
|
442
|
+
return null
|
|
443
|
+
}
|
|
444
|
+
return null
|
|
445
|
+
})()
|
|
446
|
+
}
|
|
447
|
+
return errorParts
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const makeLinksClickable = (
|
|
451
|
+
string,
|
|
452
|
+
{ createLink = ({ url }) => url },
|
|
453
|
+
) => {
|
|
454
|
+
// normalize line breaks
|
|
455
|
+
string = string.replace(/\n/g, "\n")
|
|
456
|
+
string = escapeHtml(string)
|
|
457
|
+
// render links
|
|
458
|
+
string = replaceUrls(string, ({ url, line, column }) => {
|
|
459
|
+
const { href, text } = createLink({ url, line, column })
|
|
460
|
+
return link({ href, text })
|
|
461
|
+
})
|
|
462
|
+
return string
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const escapeHtml = (string) => {
|
|
466
|
+
return string
|
|
467
|
+
.replace(/&/g, "&")
|
|
468
|
+
.replace(/</g, "<")
|
|
469
|
+
.replace(/>/g, ">")
|
|
470
|
+
.replace(/"/g, """)
|
|
471
|
+
.replace(/'/g, "'")
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const link = ({ href, text = href }) => `<a href="${href}">${text}</a>`
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
let displayErrorNotification
|
|
478
|
+
error_notification: {
|
|
479
|
+
const { Notification } = window
|
|
480
|
+
displayErrorNotification =
|
|
481
|
+
typeof Notification === "function"
|
|
482
|
+
? ({ title, text, icon }) => {
|
|
483
|
+
if (Notification.permission === "granted") {
|
|
484
|
+
const notification = new Notification(title, {
|
|
485
|
+
lang: "en",
|
|
486
|
+
body: text,
|
|
487
|
+
icon,
|
|
488
|
+
})
|
|
489
|
+
notification.onclick = () => {
|
|
490
|
+
window.focus()
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
: () => {}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay"
|
|
498
|
+
let displayJsenvErrorOverlay
|
|
499
|
+
error_overlay: {
|
|
500
|
+
displayJsenvErrorOverlay = (params) => {
|
|
501
|
+
let jsenvErrorOverlay = new JsenvErrorOverlay(params)
|
|
502
|
+
document
|
|
503
|
+
.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME)
|
|
504
|
+
.forEach((node) => {
|
|
505
|
+
node.parentNode.removeChild(node)
|
|
506
|
+
})
|
|
507
|
+
document.body.appendChild(jsenvErrorOverlay)
|
|
508
|
+
const removeErrorOverlay = () => {
|
|
509
|
+
if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
|
|
510
|
+
document.body.removeChild(jsenvErrorOverlay)
|
|
511
|
+
jsenvErrorOverlay = null
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return removeErrorOverlay
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
class JsenvErrorOverlay extends HTMLElement {
|
|
518
|
+
constructor({ theme, title, text, tip, errorDetailsPromise }) {
|
|
519
|
+
super()
|
|
520
|
+
this.root = this.attachShadow({ mode: "open" })
|
|
521
|
+
this.root.innerHTML = `
|
|
522
|
+
<style>
|
|
523
|
+
${overlayCSS}
|
|
524
|
+
</style>
|
|
525
|
+
<div class="backdrop"></div>
|
|
526
|
+
<div class="overlay" data-theme=${theme}>
|
|
527
|
+
<h1 class="title">
|
|
528
|
+
${title}
|
|
529
|
+
</h1>
|
|
530
|
+
<pre class="text">${text}</pre>
|
|
531
|
+
<div class="tip">
|
|
532
|
+
${tip}
|
|
533
|
+
</div>
|
|
534
|
+
</div>`
|
|
535
|
+
this.root.querySelector(".backdrop").onclick = () => {
|
|
536
|
+
if (!this.parentNode) {
|
|
537
|
+
// not in document anymore
|
|
538
|
+
return
|
|
539
|
+
}
|
|
540
|
+
this.root.querySelector(".backdrop").onclick = null
|
|
541
|
+
this.parentNode.removeChild(this)
|
|
542
|
+
}
|
|
543
|
+
if (errorDetailsPromise) {
|
|
544
|
+
errorDetailsPromise.then((errorDetails) => {
|
|
545
|
+
if (!errorDetails || !this.parentNode) {
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
const { codeFrame, cause } = errorDetails
|
|
549
|
+
if (codeFrame) {
|
|
550
|
+
this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`
|
|
551
|
+
}
|
|
552
|
+
if (cause) {
|
|
553
|
+
const causeIndented = prefixRemainingLines(cause, " ")
|
|
554
|
+
this.root.querySelector(
|
|
555
|
+
".text",
|
|
556
|
+
).innerHTML += `\n [cause]: ${causeIndented}`
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const prefixRemainingLines = (text, prefix) => {
|
|
564
|
+
const lines = text.split(/\r?\n/)
|
|
565
|
+
const firstLine = lines.shift()
|
|
566
|
+
let result = firstLine
|
|
567
|
+
let i = 0
|
|
568
|
+
while (i < lines.length) {
|
|
569
|
+
const line = lines[i]
|
|
570
|
+
i++
|
|
571
|
+
result += line.length ? `\n${prefix}${line}` : `\n`
|
|
572
|
+
}
|
|
573
|
+
return result
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
|
|
577
|
+
customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const overlayCSS = `
|
|
581
|
+
:host {
|
|
582
|
+
position: fixed;
|
|
583
|
+
z-index: 99999;
|
|
584
|
+
top: 0;
|
|
585
|
+
left: 0;
|
|
586
|
+
width: 100%;
|
|
587
|
+
height: 100%;
|
|
588
|
+
overflow-y: scroll;
|
|
589
|
+
margin: 0;
|
|
590
|
+
background: rgba(0, 0, 0, 0.66);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.backdrop {
|
|
594
|
+
position: absolute;
|
|
595
|
+
left: 0;
|
|
596
|
+
right: 0;
|
|
597
|
+
top: 0;
|
|
598
|
+
bottom: 0;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.overlay {
|
|
602
|
+
position: relative;
|
|
603
|
+
background: rgba(0, 0, 0, 0.95);
|
|
604
|
+
width: 800px;
|
|
605
|
+
margin: 30px auto;
|
|
606
|
+
padding: 25px 40px;
|
|
607
|
+
padding-top: 0;
|
|
608
|
+
overflow: hidden; /* for h1 margins */
|
|
609
|
+
border-radius: 4px 8px;
|
|
610
|
+
box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
|
|
611
|
+
box-sizing: border-box;
|
|
612
|
+
font-family: monospace;
|
|
613
|
+
direction: ltr;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
h1 {
|
|
617
|
+
color: red;
|
|
618
|
+
text-align: center;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
pre {
|
|
622
|
+
overflow: auto;
|
|
623
|
+
max-width: 100%;
|
|
624
|
+
/* padding is nice + prevents scrollbar from hiding the text behind it */
|
|
625
|
+
/* does not work nicely on firefox though https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */
|
|
626
|
+
padding: 20px;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.tip {
|
|
630
|
+
border-top: 1px solid #999;
|
|
631
|
+
padding-top: 12px;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
[data-theme="dark"] {
|
|
635
|
+
color: #999;
|
|
636
|
+
}
|
|
637
|
+
[data-theme="dark"] pre {
|
|
638
|
+
background: #111;
|
|
639
|
+
border: 1px solid #333;
|
|
640
|
+
color: #eee;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
[data-theme="light"] {
|
|
644
|
+
color: #EEEEEE;
|
|
645
|
+
}
|
|
646
|
+
[data-theme="light"] pre {
|
|
647
|
+
background: #1E1E1E;
|
|
648
|
+
border: 1px solid white;
|
|
649
|
+
color: #EEEEEE;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
pre a {
|
|
653
|
+
color: inherit;
|
|
654
|
+
}`
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
supervisor.createException = createException
|
|
658
|
+
supervisor.reportException = (exception) => {
|
|
659
|
+
const { theme, title, text, tip, errorDetailsPromise } =
|
|
660
|
+
formatError(exception)
|
|
661
|
+
|
|
662
|
+
if (errorOverlay) {
|
|
663
|
+
const removeErrorOverlay = displayJsenvErrorOverlay({
|
|
664
|
+
theme,
|
|
665
|
+
title,
|
|
666
|
+
text,
|
|
667
|
+
tip,
|
|
668
|
+
errorDetailsPromise,
|
|
669
|
+
})
|
|
670
|
+
if (window.__reloader__) {
|
|
671
|
+
window.__reloader__.onstatuschange = () => {
|
|
672
|
+
if (window.__reloader__.status === "reloading") {
|
|
673
|
+
removeErrorOverlay()
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (errorNotification) {
|
|
679
|
+
displayErrorNotification({
|
|
680
|
+
title,
|
|
681
|
+
text,
|
|
682
|
+
})
|
|
683
|
+
}
|
|
684
|
+
return exception
|
|
685
|
+
}
|
|
686
|
+
window.addEventListener("error", (errorEvent) => {
|
|
687
|
+
if (!errorEvent.isTrusted) {
|
|
688
|
+
// ignore custom error event (not sent by browser)
|
|
689
|
+
return
|
|
690
|
+
}
|
|
691
|
+
const { error, message, filename, lineno, colno } = errorEvent
|
|
692
|
+
const exception = supervisor.createException({
|
|
693
|
+
// when error is reported within a worker error is null
|
|
694
|
+
// but there is a message property on errorEvent
|
|
695
|
+
reason: error || message,
|
|
696
|
+
reportedBy: "window_error_event",
|
|
697
|
+
url: filename,
|
|
698
|
+
line: lineno,
|
|
699
|
+
column: colno,
|
|
700
|
+
})
|
|
701
|
+
supervisor.reportException(exception)
|
|
702
|
+
})
|
|
703
|
+
window.addEventListener("unhandledrejection", (event) => {
|
|
704
|
+
if (event.defaultPrevented) {
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
const exception = supervisor.createException({
|
|
708
|
+
reason: event.reason,
|
|
709
|
+
reportedBy: "window_unhandledrejection_event",
|
|
710
|
+
})
|
|
711
|
+
supervisor.reportException(exception)
|
|
712
|
+
})
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
supervisor.setup = ({
|
|
716
|
+
rootDirectoryUrl,
|
|
717
|
+
logs,
|
|
718
|
+
measurePerf,
|
|
719
|
+
errorOverlay,
|
|
720
|
+
errorBaseUrl,
|
|
721
|
+
openInEditor,
|
|
722
|
+
}) => {
|
|
723
|
+
supervisor.setupReportException({
|
|
724
|
+
rootDirectoryUrl,
|
|
725
|
+
errorOverlay,
|
|
726
|
+
errorBaseUrl,
|
|
727
|
+
openInEditor,
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
const supervisedScripts = []
|
|
731
|
+
const executionPromises = []
|
|
732
|
+
supervisor.createExecution = ({ type, src, execute }) => {
|
|
733
|
+
const execution = {
|
|
734
|
+
type,
|
|
735
|
+
src,
|
|
736
|
+
execute,
|
|
737
|
+
}
|
|
738
|
+
execution.start = () => {
|
|
739
|
+
return superviseExecution(execution, { isReload: false })
|
|
740
|
+
}
|
|
741
|
+
execution.reload = () => {
|
|
742
|
+
return superviseExecution(execution, { isReload: true })
|
|
743
|
+
}
|
|
744
|
+
supervisedScripts.push(execution)
|
|
745
|
+
return execution
|
|
746
|
+
}
|
|
747
|
+
const superviseExecution = async (execution, { isReload }) => {
|
|
748
|
+
if (logs) {
|
|
749
|
+
console.group(`[jsenv] loading ${execution.type} ${execution.src}`)
|
|
750
|
+
}
|
|
751
|
+
if (measurePerf) {
|
|
752
|
+
performance.mark(`execution_start`)
|
|
753
|
+
}
|
|
754
|
+
const executionResult = {
|
|
755
|
+
status: "pending",
|
|
756
|
+
exception: null,
|
|
757
|
+
namespace: null,
|
|
758
|
+
coverage: null,
|
|
759
|
+
}
|
|
760
|
+
executionResults[execution.src] = executionResult
|
|
761
|
+
let resolvePromise
|
|
762
|
+
const promise = new Promise((resolve) => {
|
|
763
|
+
resolvePromise = resolve
|
|
764
|
+
})
|
|
765
|
+
executionPromises.push(promise)
|
|
766
|
+
try {
|
|
767
|
+
const result = await execution.execute({ isReload })
|
|
768
|
+
if (measurePerf) {
|
|
769
|
+
performance.measure(`execution`, `execution_start`)
|
|
770
|
+
}
|
|
771
|
+
executionResult.status = "completed"
|
|
772
|
+
executionResult.namespace = result
|
|
773
|
+
executionResult.coverage = window.__coverage__
|
|
774
|
+
if (logs) {
|
|
775
|
+
console.log(`${execution.type} load ended`)
|
|
776
|
+
console.groupEnd()
|
|
777
|
+
}
|
|
778
|
+
resolvePromise()
|
|
779
|
+
} catch (e) {
|
|
780
|
+
if (measurePerf) {
|
|
781
|
+
performance.measure(`execution`, `execution_start`)
|
|
782
|
+
}
|
|
783
|
+
executionResult.status = "errored"
|
|
784
|
+
const exception = supervisor.createException({
|
|
785
|
+
reason: e,
|
|
786
|
+
})
|
|
787
|
+
if (exception.needsReport) {
|
|
788
|
+
supervisor.reportException(exception)
|
|
789
|
+
}
|
|
790
|
+
executionResult.exception = exception
|
|
791
|
+
executionResult.coverage = window.__coverage__
|
|
792
|
+
if (logs) {
|
|
793
|
+
console.groupEnd()
|
|
794
|
+
}
|
|
795
|
+
resolvePromise()
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
let previousExecutionPromise
|
|
799
|
+
supervisor.superviseScript = async ({ src, async }) => {
|
|
800
|
+
const { currentScript } = document
|
|
801
|
+
const parentNode = currentScript.parentNode
|
|
802
|
+
const startExecution = () => {
|
|
803
|
+
let nodeToReplace
|
|
804
|
+
let currentScriptClone
|
|
805
|
+
const execution = supervisor.createExecution({
|
|
806
|
+
src,
|
|
807
|
+
type: "js_classic",
|
|
808
|
+
execute: async ({ isReload }) => {
|
|
809
|
+
const urlObject = new URL(src, window.location)
|
|
810
|
+
const loadPromise = new Promise((resolve, reject) => {
|
|
811
|
+
// do not use script.cloneNode()
|
|
812
|
+
// bcause https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
|
|
813
|
+
currentScriptClone = document.createElement("script")
|
|
814
|
+
Array.from(currentScript.attributes).forEach((attribute) => {
|
|
815
|
+
currentScriptClone.setAttribute(
|
|
816
|
+
attribute.nodeName,
|
|
817
|
+
attribute.nodeValue,
|
|
818
|
+
)
|
|
819
|
+
})
|
|
820
|
+
if (isReload) {
|
|
821
|
+
urlObject.searchParams.set("hmr", Date.now())
|
|
822
|
+
nodeToReplace = currentScriptClone
|
|
823
|
+
currentScriptClone.src = urlObject.href
|
|
824
|
+
} else {
|
|
825
|
+
currentScriptClone.removeAttribute("jsenv-plugin-owner")
|
|
826
|
+
currentScriptClone.removeAttribute("jsenv-plugin-action")
|
|
827
|
+
currentScriptClone.removeAttribute("inlined-from-src")
|
|
828
|
+
currentScriptClone.removeAttribute("original-position")
|
|
829
|
+
currentScriptClone.removeAttribute("original-src-position")
|
|
830
|
+
nodeToReplace = currentScript
|
|
831
|
+
currentScriptClone.src = src
|
|
832
|
+
}
|
|
833
|
+
currentScriptClone.addEventListener("error", reject)
|
|
834
|
+
currentScriptClone.addEventListener("load", resolve)
|
|
835
|
+
parentNode.replaceChild(currentScriptClone, nodeToReplace)
|
|
836
|
+
})
|
|
837
|
+
try {
|
|
838
|
+
await loadPromise
|
|
839
|
+
} catch (e) {
|
|
840
|
+
// eslint-disable-next-line no-throw-literal
|
|
841
|
+
throw {
|
|
842
|
+
message: `Failed to fetch script: ${urlObject.href}`,
|
|
843
|
+
reportedBy: "script_error_event",
|
|
844
|
+
url: urlObject.href,
|
|
845
|
+
// window.error won't be dispatched for this error
|
|
846
|
+
needsReport: true,
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
})
|
|
851
|
+
return execution.start()
|
|
852
|
+
}
|
|
853
|
+
if (async) {
|
|
854
|
+
startExecution()
|
|
855
|
+
return
|
|
856
|
+
}
|
|
857
|
+
if (previousExecutionPromise) {
|
|
858
|
+
await previousExecutionPromise
|
|
859
|
+
previousExecutionPromise = null
|
|
860
|
+
}
|
|
861
|
+
previousExecutionPromise = startExecution()
|
|
862
|
+
}
|
|
863
|
+
supervisor.reloadSupervisedScript = ({ type, src }) => {
|
|
864
|
+
const supervisedScript = supervisedScripts.find(
|
|
865
|
+
(supervisedScriptCandidate) => {
|
|
866
|
+
if (type && supervisedScriptCandidate.type !== type) {
|
|
867
|
+
return false
|
|
868
|
+
}
|
|
869
|
+
if (supervisedScriptCandidate.src !== src) {
|
|
870
|
+
return false
|
|
871
|
+
}
|
|
872
|
+
return true
|
|
873
|
+
},
|
|
874
|
+
)
|
|
875
|
+
if (supervisedScript) {
|
|
876
|
+
supervisedScript.reload()
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
supervisor.collectScriptResults = async () => {
|
|
880
|
+
// just to be super safe and ensure any <script type="module"> got a chance to execute
|
|
881
|
+
const scriptTypeModuleLoaded = new Promise((resolve) => {
|
|
882
|
+
const scriptTypeModule = document.createElement("script")
|
|
883
|
+
scriptTypeModule.type = "module"
|
|
884
|
+
scriptTypeModule.innerText =
|
|
885
|
+
"window.__supervisor__.scriptModuleCallback()"
|
|
886
|
+
window.__supervisor__.scriptModuleCallback = () => {
|
|
887
|
+
scriptTypeModule.parentNode.removeChild(scriptTypeModule)
|
|
888
|
+
resolve()
|
|
889
|
+
}
|
|
890
|
+
document.body.appendChild(scriptTypeModule)
|
|
891
|
+
})
|
|
892
|
+
await scriptTypeModuleLoaded
|
|
893
|
+
|
|
894
|
+
const waitPendingExecutions = async () => {
|
|
895
|
+
if (executionPromises.length) {
|
|
896
|
+
const promisesToWait = executionPromises.slice()
|
|
897
|
+
executionPromises.length = 0
|
|
898
|
+
await Promise.all(promisesToWait)
|
|
899
|
+
await waitPendingExecutions()
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
await waitPendingExecutions()
|
|
903
|
+
return {
|
|
904
|
+
status: "completed",
|
|
905
|
+
executionResults,
|
|
906
|
+
startTime: getNavigationStartTime(),
|
|
907
|
+
endTime: Date.now(),
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const getNavigationStartTime = () => {
|
|
912
|
+
try {
|
|
913
|
+
return window.performance.timing.navigationStart
|
|
914
|
+
} catch (e) {
|
|
915
|
+
return Date.now()
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return supervisor
|
|
921
|
+
})()
|