mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 16:00:54 +00:00
fix(parser): restore child insertion points before script error handlers
This commit is contained in:
@@ -13,6 +13,9 @@ use moli_core::{
|
||||
use support::FixtureServer;
|
||||
use tokio::time::Duration;
|
||||
|
||||
#[path = "scripts/child_error_insertion.rs"]
|
||||
mod child_error_insertion;
|
||||
|
||||
fn diagnostic_global<'a>(
|
||||
page: &'a moli_core::page::Page,
|
||||
name: &str,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
use super::*;
|
||||
|
||||
async fn child_script_error_write_probe(
|
||||
nested: bool,
|
||||
unknown_scheme: bool,
|
||||
) -> Result<serde_json::Value> {
|
||||
let server = FixtureServer::spawn().await?;
|
||||
let browser = Browser::new(AppConfig::default())?;
|
||||
let mut page = browser.fetch(&server.url("/static")).await?;
|
||||
let source = if unknown_scheme {
|
||||
"no-such-script-scheme:missing".to_owned()
|
||||
} else {
|
||||
server.url("/missing-child-parser-script.js")
|
||||
};
|
||||
let expression = r#"new Promise(resolve => {
|
||||
let during = null;
|
||||
globalThis.childErrorObserved = value => { during = value; };
|
||||
const frame = document.createElement('iframe');
|
||||
frame.onload = () => resolve(JSON.stringify({
|
||||
during, after: frame.contentDocument.body.textContent,
|
||||
nestedRan: frame.contentWindow.nestedRan === true
|
||||
}));
|
||||
const written = NESTED
|
||||
? '<script>globalThis.nestedRan = true; document.write("text");<' + '/script>'
|
||||
: 'text';
|
||||
const handler = `function failed(event) {
|
||||
document.write(${JSON.stringify(written).replaceAll('<', '\\u003c')});
|
||||
parent.childErrorObserved({text: document.body.textContent,
|
||||
currentScriptIsNull: document.currentScript === null,
|
||||
tailMissing: document.getElementById('tail') === null,
|
||||
eventType: event.type,
|
||||
nestedRan: globalThis.nestedRan === true});
|
||||
}`;
|
||||
frame.srcdoc = '<!doctype html><head><script>' + handler + '<' + '/script></head>' +
|
||||
'<body>Some <script src="' + SOURCE + '" onerror="failed(event)"><' + '/script>' +
|
||||
'<span id="tail"> tail</span>';
|
||||
document.body.append(frame);
|
||||
})"#
|
||||
.replace("NESTED", if nested { "true" } else { "false" })
|
||||
.replace("SOURCE", &serde_json::to_string(&source)?);
|
||||
let result = page
|
||||
.evaluate_runtime_expression_with_await_async(&expression, true)
|
||||
.await?;
|
||||
server.shutdown().await;
|
||||
serde_json::from_str(result["value"].as_str().expect("child error probe")).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_parser_fetch_error_write_is_synchronous() -> Result<()> {
|
||||
let result = child_script_error_write_probe(false, false).await?;
|
||||
assert_eq!(result["during"]["text"], "Some text", "{result}");
|
||||
assert_eq!(result["during"]["currentScriptIsNull"], true, "{result}");
|
||||
assert_eq!(result["during"]["tailMissing"], true, "{result}");
|
||||
assert_eq!(result["during"]["eventType"], "error", "{result}");
|
||||
assert_eq!(result["after"], "Some text tail", "{result}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_parser_fetch_error_write_handles_unknown_schemes() -> Result<()> {
|
||||
let result = child_script_error_write_probe(false, true).await?;
|
||||
assert_eq!(result["during"]["text"], "Some text", "{result}");
|
||||
assert_eq!(result["during"]["tailMissing"], true, "{result}");
|
||||
assert_eq!(result["after"], "Some text tail", "{result}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_parser_fetch_error_write_executes_nested_inline_scripts() -> Result<()> {
|
||||
let result = child_script_error_write_probe(true, false).await?;
|
||||
assert_eq!(result["during"]["nestedRan"], true, "{result}");
|
||||
assert_eq!(result["during"]["tailMissing"], true, "{result}");
|
||||
assert_eq!(result["nestedRan"], true, "{result}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use crate::frame_owner_model::{
|
||||
FrameDocumentClassicCompletionScriptEventAction,
|
||||
FrameDocumentClassicCompletionScriptEventFollowup, FrameDocumentClassicParserResumeApplication,
|
||||
FrameDocumentClassicScriptCompletionAction, FrameDocumentClassicScriptCompletionTarget,
|
||||
FrameDocumentClassicScriptScheduling,
|
||||
FrameDocumentClassicScriptScheduling, FrameDocumentScriptElementEventKind,
|
||||
};
|
||||
|
||||
use super::super::{ScriptVm, child_document_script_owner_hooks::ChildDocumentScriptOwnerHooks};
|
||||
@@ -80,15 +80,37 @@ impl<'vm> ChildClassicCompletionOwner<'vm> {
|
||||
let target = action.target();
|
||||
let event = action.event();
|
||||
let realm_id = target.realm_id();
|
||||
let _parser_script_nesting = matches!(
|
||||
let parser_blocking = matches!(
|
||||
target.scheduling(),
|
||||
FrameDocumentClassicScriptScheduling::ParserBlocking
|
||||
)
|
||||
.then(|| {
|
||||
ChildDocumentScriptOwnerHooks::new(self.vm)
|
||||
.enter_parser_script_nesting(target.child_handle(), target.task_owner())
|
||||
})
|
||||
.flatten();
|
||||
);
|
||||
// Fetch failures bypass script execution, which normally unblocks the
|
||||
// tokenizer before invoking page code. The error handler still runs at
|
||||
// that parser insertion point, including synchronous document.write().
|
||||
// Do not resume again for load: the script may have installed a new
|
||||
// nested parser blocker while executing.
|
||||
if parser_blocking
|
||||
&& matches!(event.kind, FrameDocumentScriptElementEventKind::Error)
|
||||
&& !ChildDocumentScriptOwnerHooks::new(self.vm).resume_parser_for_classic_execution(
|
||||
target.child_handle(),
|
||||
target.task_owner(),
|
||||
event.script_handle,
|
||||
)
|
||||
{
|
||||
tracing::debug!(
|
||||
?target,
|
||||
script_handle = ?event.script_handle,
|
||||
"dropping child classic error event with a stale parser resume permit"
|
||||
);
|
||||
followup.note_script_event_dispatch_failed();
|
||||
return;
|
||||
}
|
||||
let _parser_script_nesting = parser_blocking
|
||||
.then(|| {
|
||||
ChildDocumentScriptOwnerHooks::new(self.vm)
|
||||
.enter_parser_script_nesting(target.child_handle(), target.task_owner())
|
||||
})
|
||||
.flatten();
|
||||
let dispatch = ChildDocumentScriptOwnerHooks::new(self.vm)
|
||||
.dispatch_script_element_event_for_parts_selected_task_body(
|
||||
target.task_owner(),
|
||||
|
||||
Reference in New Issue
Block a user