Fetch ordered dynamic scripts concurrently
This commit is contained in:
1 parent
3db63d9125
commit
99ff0601bf
2 files changed
+49
-24
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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+44
-19
@@ -274,28 +274,44 @@ 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 __fetchDynClassicScript(task) {
|
||||||
|
let body;
|
||||||
|
if (task.url.startsWith('data:')) {
|
||||||
|
body = _decodeDataScriptUrl(task.url);
|
||||||
|
} else {
|
||||||
|
const raw = await Deno.core.ops.op_fetch_url(
|
||||||
|
task.url, "GET", "{}", "", task.pageOrigin, "no-cors", "same-origin"
|
||||||
|
);
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
// The HTML script-fetch algorithm treats an unsuccessful HTTP response
|
||||||
|
// as a network error. Evaluating its response body is both observably
|
||||||
|
// unlike browsers and dangerous: JSON error payloads and diagnostic HTML
|
||||||
|
// must never become script source.
|
||||||
|
if (!(parsed.status >= 200 && parsed.status <= 299)) {
|
||||||
|
throw new Error('HTTP ' + (parsed.status || 0));
|
||||||
|
}
|
||||||
|
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) {
|
async function __runDynScriptTask(task) {
|
||||||
try {
|
try {
|
||||||
if (task.isModule) {
|
if (task.isModule) {
|
||||||
await import(task.url);
|
await import(task.url);
|
||||||
} else {
|
} else {
|
||||||
let body;
|
if (!task.fetchResult) __startDynClassicFetch(task);
|
||||||
if (task.url.startsWith('data:')) {
|
const fetched = await task.fetchResult;
|
||||||
body = _decodeDataScriptUrl(task.url);
|
if (fetched.error) throw fetched.error;
|
||||||
} else {
|
const body = fetched.body;
|
||||||
const raw = await Deno.core.ops.op_fetch_url(
|
|
||||||
task.url, "GET", "{}", "", task.pageOrigin, "no-cors", "same-origin"
|
|
||||||
);
|
|
||||||
const parsed = JSON.parse(raw);
|
|
||||||
// The HTML script-fetch algorithm treats an unsuccessful HTTP
|
|
||||||
// response as a network error. Evaluating its response body is both
|
|
||||||
// observably unlike browsers and dangerous: JSON error payloads and
|
|
||||||
// diagnostic HTML must never become script source.
|
|
||||||
if (!(parsed.status >= 200 && parsed.status <= 299)) {
|
|
||||||
throw new Error('HTTP ' + (parsed.status || 0));
|
|
||||||
}
|
|
||||||
body = parsed.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) {
|
||||||
__runAsyncClassicScript(task);
|
// 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);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
__dynScriptQueue.push(task);
|
__dynScriptQueue.push(task);
|
||||||
__processDynScriptQueue();
|
__processDynScriptQueue();
|
||||||
|
|||||||
Reference in new issue
Block a user