Fetch ordered dynamic scripts concurrently

This commit is contained in:
SGavrl committed 2026-08-04 09:53:24 +00:00
1 parent 3db63d9125
commit 99ff0601bf
2 files changed
+40 -15

No files matched your search

@@ -109,7 +109,7 @@ async fn serve_dynamic_order_fixture(explicitly_in_order: bool) -> (String, Arc<
"window.__dynamicOrder.push('slow');".to_string(), "window.__dynamicOrder.push('slow');".to_string(),
) )
} else if request.starts_with("GET /fast.js") { } else if request.starts_with("GET /fast.js") {
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(500)).await;
( (
"application/javascript", "application/javascript",
"window.__dynamicOrder.push('fast');".to_string(), "window.__dynamicOrder.push('fast');".to_string(),
@@ -199,12 +199,12 @@ async fn dynamic_classic_fetch_concurrency_matches_force_async_state() {
navigate_dynamic_order_fixture(true).await; navigate_dynamic_order_fixture(true).await;
assert_eq!(ordered_order, ["slow", "fast"]); assert_eq!(ordered_order, ["slow", "fast"]);
assert_eq!( assert_eq!(
ordered_peak, 1, ordered_peak, 2,
"explicit async=false scripts must retain their insertion-order queue" "explicit async=false scripts must overlap fetches while retaining their execution-order queue"
); );
assert!( assert!(
async_elapsed_ms + 50 < ordered_elapsed_ms, ordered_elapsed_ms < async_elapsed_ms + 300,
"concurrent fetches should avoid the ordered waterfall: async={async_elapsed_ms}ms ordered={ordered_elapsed_ms}ms" "execution ordering must not serialize network fetches: async={async_elapsed_ms}ms ordered={ordered_elapsed_ms}ms"
); );
} }
+35 -10
View File
@@ -274,11 +274,7 @@ function _decodeDataScriptUrl(url) {
globalThis.__markParserScripts = function(nids) { globalThis.__markParserScripts = function(nids) {
for (const nid of nids || []) Deno.core.ops.op_script_mark_started(+nid); for (const nid of nids || []) Deno.core.ops.op_script_mark_started(+nid);
}; };
async function __runDynScriptTask(task) { async function __fetchDynClassicScript(task) {
try {
if (task.isModule) {
await import(task.url);
} else {
let body; let body;
if (task.url.startsWith('data:')) { if (task.url.startsWith('data:')) {
body = _decodeDataScriptUrl(task.url); body = _decodeDataScriptUrl(task.url);
@@ -287,15 +283,35 @@ async function __runDynScriptTask(task) {
task.url, "GET", "{}", "", task.pageOrigin, "no-cors", "same-origin" task.url, "GET", "{}", "", task.pageOrigin, "no-cors", "same-origin"
); );
const parsed = JSON.parse(raw); const parsed = JSON.parse(raw);
// The HTML script-fetch algorithm treats an unsuccessful HTTP // The HTML script-fetch algorithm treats an unsuccessful HTTP response
// response as a network error. Evaluating its response body is both // as a network error. Evaluating its response body is both observably
// observably unlike browsers and dangerous: JSON error payloads and // unlike browsers and dangerous: JSON error payloads and diagnostic HTML
// diagnostic HTML must never become script source. // must never become script source.
if (!(parsed.status >= 200 && parsed.status <= 299)) { if (!(parsed.status >= 200 && parsed.status <= 299)) {
throw new Error('HTTP ' + (parsed.status || 0)); throw new Error('HTTP ' + (parsed.status || 0));
} }
body = parsed.body; body = parsed.body;
} }
return body;
}
function __startDynClassicFetch(task) {
// Attach both reactions immediately. An in-order script may finish fetching
// before an earlier queue member; retaining a settled value avoids an
// unhandled-rejection report while its execution turn is still blocked.
task.fetchResult = __fetchDynClassicScript(task).then(
body => ({ body }),
error => ({ error }),
);
}
async function __runDynScriptTask(task) {
try {
if (task.isModule) {
await import(task.url);
} else {
if (!task.fetchResult) __startDynClassicFetch(task);
const fetched = await task.fetchResult;
if (fetched.error) throw fetched.error;
const body = fetched.body;
if (body) { if (body) {
// A fetched async script is executed by a ScriptRunner task, not by // A fetched async script is executed by a ScriptRunner task, not by
// the fetch promise's microtask continuation. Besides matching event // the fetch promise's microtask continuation. Besides matching event
@@ -1374,8 +1390,17 @@ function __prepareInsertedScript(script) {
const explicitlyInOrder = !isModule const explicitlyInOrder = !isModule
&& Object.prototype.hasOwnProperty.call(script, 'async') && Object.prototype.hasOwnProperty.call(script, 'async')
&& script.async === false; && script.async === false;
if (!isModule && !explicitlyInOrder) { if (!isModule) {
// Fetch all dynamically inserted classics immediately. `async=false`
// changes only execution order: browsers still overlap their network
// requests, then hold a ready body behind earlier ordered scripts.
__startDynClassicFetch(task);
if (explicitlyInOrder) {
__dynScriptQueue.push(task);
__processDynScriptQueue();
} else {
__runAsyncClassicScript(task); __runAsyncClassicScript(task);
}
} else { } else {
__dynScriptQueue.push(task); __dynScriptQueue.push(task);
__processDynScriptQueue(); __processDynScriptQueue();