feat(engine): DOM + AbortSignal + storage fixes surfaced by WPT conformance work (#219)
Engine-only changes pulled out of the WPT conformance branch. The tests, runner, and harness vendoring that drove these have moved to a separate benchmark repo (obscura-bench). Real bug fixes: - Tree::insert_before created a self-cycle when the inserted node was its own reference (the spec-conformant `child.after(x)` path that the testharness's ChildNode helpers exercise). The fixup wrote node.prev_sibling = node.next_sibling = own id; every subsequent childNodes / querySelectorAll walk then looped forever and obscura burned RAM until OOM. Per spec the op is a no-op; we now early-return. - Tree::text_content extended to return own data for direct CharacterData access (Text / Comment / ProcessingInstruction). - Element.namespaceURI now reflects the namespace passed to createElementNS instead of collapsing every non-svg namespace to xhtml. Without this, code that creates elements in MathML, custom XML namespaces, or the empty namespace gets misclassified as HTML. - AbortController / AbortSignal: the previous shim was a plain object whose addEventListener was a no-op, so listeners for 'abort' silently never fired and any AbortSignal.timeout / AbortSignal.any chains broke. New impl tracks aborted/reason, has a real addEventListener / removeEventListener / dispatchEvent surface, honors `once`, and implements the static abort / timeout / any factories crawlers use for budgeted fetches. - StorageEvent class added. localStorage / sessionStorage mutations now dispatch 'storage' events on the parent window and every iframe window so cross-frame onstorage listeners fire. - ProcessingInstruction (nodeType 7) and DocumentType (nodeType 10) constructors plus document.createProcessingInstruction and document.implementation.createDocumentType. textContent returns null for Document and DocumentType per spec. - insertAdjacentText / insertAdjacentElement on Element. Without these the testharness output renderer threw in a forEach without try/catch, breaking the completion callback chain for every test with subtests. - Window + Node addEventListener filter non-callable listeners, accept the EventListenerObject shape (object with handleEvent), dedupe by (type, callback, capture), honor the `once` option, and snapshot the listener list before dispatch so a `once: true` removal mid-iteration doesn't corrupt the loop. - Iframe HTML loader now extracts head <script> blocks and body on-handler attributes (`<body onstorage=...>`), evals scripts in the parent realm after the iframe window is built, re-attaches handlers as window listeners. Same iframe element reusing its src no longer double-registers in the iframe registry. - ops.rs validate_fetch_url honors the OBSCURA_ALLOW_PRIVATE_NETWORK env var, and the CLI --allow-private-network flag mirrors itself into that env so the js-side fetch gate matches the http_client gate. Without this, iframe loads of 127.0.0.1 resources were blocked even with the flag set. - ops.rs: create_processing_instruction, create_doctype, pi_target, doctype_name, doctype_public_id ops added; set_text_content extended to handle PI nodes.
This commit is contained in:
1 parent
fd2421940b
commit
ee10e8ddb5
5 files changed
+464
-21
No files matched your search
@@ -259,6 +259,7 @@ fn op_dom(state: &OpState, #[string] cmd: String, #[string] arg1: String, #[stri
|
||||
match &mut n.data {
|
||||
NodeData::Text { contents } => { *contents = arg2.clone(); }
|
||||
NodeData::Comment { contents } => { *contents = arg2.clone(); }
|
||||
NodeData::ProcessingInstruction { data, .. } => { *data = arg2.clone(); }
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
@@ -279,6 +280,47 @@ fn op_dom(state: &OpState, #[string] cmd: String, #[string] arg1: String, #[stri
|
||||
"create_comment_node" => {
|
||||
dom.new_node(NodeData::Comment { contents: arg1.clone() }).index().to_string()
|
||||
}
|
||||
"create_processing_instruction" => {
|
||||
// arg1 = target, arg2 = data
|
||||
dom.new_node(NodeData::ProcessingInstruction {
|
||||
target: arg1.clone(),
|
||||
data: arg2.clone(),
|
||||
}).index().to_string()
|
||||
}
|
||||
"create_doctype" => {
|
||||
// arg1 = name, arg2 = public_id. system_id stored only in the
|
||||
// JS wrapper since neither current WPT test reads it back from
|
||||
// the underlying tree.
|
||||
dom.new_node(NodeData::Doctype {
|
||||
name: arg1.clone(),
|
||||
public_id: arg2.clone(),
|
||||
system_id: String::new(),
|
||||
}).index().to_string()
|
||||
}
|
||||
"pi_target" => {
|
||||
let nid = arg1.parse::<u32>().unwrap_or(0);
|
||||
let val = dom.get_node(NodeId::new(nid)).and_then(|n| match &n.data {
|
||||
NodeData::ProcessingInstruction { target, .. } => Some(target.clone()),
|
||||
_ => None,
|
||||
}).unwrap_or_default();
|
||||
serde_json::to_string(&val).unwrap_or("\"\"".into())
|
||||
}
|
||||
"doctype_name" => {
|
||||
let nid = arg1.parse::<u32>().unwrap_or(0);
|
||||
let val = dom.get_node(NodeId::new(nid)).and_then(|n| match &n.data {
|
||||
NodeData::Doctype { name, .. } => Some(name.clone()),
|
||||
_ => None,
|
||||
}).unwrap_or_default();
|
||||
serde_json::to_string(&val).unwrap_or("\"\"".into())
|
||||
}
|
||||
"doctype_public_id" => {
|
||||
let nid = arg1.parse::<u32>().unwrap_or(0);
|
||||
let val = dom.get_node(NodeId::new(nid)).and_then(|n| match &n.data {
|
||||
NodeData::Doctype { public_id, .. } => Some(public_id.clone()),
|
||||
_ => None,
|
||||
}).unwrap_or_default();
|
||||
serde_json::to_string(&val).unwrap_or("\"\"".into())
|
||||
}
|
||||
"element_children" => {
|
||||
let nid = arg1.parse::<u32>().unwrap_or(0);
|
||||
let ids: Vec<i32> = dom.children(NodeId::new(nid)).iter()
|
||||
@@ -726,7 +768,7 @@ fn validate_fetch_url(url: &url::Url) -> Result<(), String> {
|
||||
));
|
||||
}
|
||||
|
||||
if scheme == "file" {
|
||||
if scheme == "file" || obscura_net::env_allows_private_network() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user