From 53bd6ecd9a648213bdcad38d729c314f4547ebec Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 11 Nov 2026 09:19:21 -0800 Subject: [PATCH 1/2] JavaScript: new XML copies a one-item list's node, and insertChildAfter and insertChildBefore take fuel per child scanned new XML(list) now returns a deep copy of the list's single item, as ECMA-357 section 13.4.2 specifies for an XMLList argument; before, it returned the item itself, so changes to the new value changed the list. insertChildAfter and insertChildBefore take one unit of fuel for each child they compare with the reference, as childIndex does. The test for the missing xpath method now checks the XML and XMLList prototypes; reading .xpath on a value selects child elements and returned an empty list in every case. --- crates/js/src/xml_builtins.rs | 13 ++++++++++--- crates/js/src/xml_tests.rs | 24 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/crates/js/src/xml_builtins.rs b/crates/js/src/xml_builtins.rs index 35c578830..07e2a1d12 100644 --- a/crates/js/src/xml_builtins.rs +++ b/crates/js/src/xml_builtins.rs @@ -330,7 +330,7 @@ fn xml_ctor(interp: &mut Interp, _: Value, args: &[Value], construct: bool) -> J let v = arg(args, 0); interp.rooted(std::slice::from_ref(&v), |i| { let x = to_xml(i, &v)?; - if construct && matches!(i.classify(&v), Xv::Node(_)) { + if construct && matches!(i.classify(&v), Xv::Node(_) | Xv::List(_)) { return Ok(Value::Object(i.node_copy(x)?)); } Ok(Value::Object(x)) @@ -883,8 +883,15 @@ fn insert_relative(interp: &mut Interp, this: Value, args: &[Value], after: bool if after { 0 } else { len } } else { let Xv::Node(r) = interp.classify(&reference) else { return Ok(Value::Undefined) }; - interp.use_fuel()?; - let Some(i) = interp.xml_node(x)?.children.iter().position(|&c| c == r) else { return Ok(Value::Undefined) }; + let mut found = None; + for i in 0..len { + interp.use_fuel()?; + if interp.xml_node(x)?.children.get(i) == Some(&r) { + found = Some(i); + break; + } + } + let Some(i) = found else { return Ok(Value::Undefined) }; if after { i + 1 } else { i } }; interp.rooted(std::slice::from_ref(&v), |i| i.xml_insert(x, at, &c))?; diff --git a/crates/js/src/xml_tests.rs b/crates/js/src/xml_tests.rs index 051c676a9..bdcf79917 100644 --- a/crates/js/src/xml_tests.rs +++ b/crates/js/src/xml_tests.rs @@ -290,6 +290,8 @@ fn the_constructors_keep_identity_or_copy_as_specified() { truth("XML().nodeKind() === 'text' && XML(null) == '' && XML(undefined) == ''"); truth("var x = ; XML(x) === x && new XML(x) !== x && new XML(x) == x"); truth("var l = <>; XML(l) === l[0]"); + truth("var l = <>; var y = new XML(l); y !== l[0] && y == l[0] && y.parent() === null"); + truth("var l = <>; var y = new XML(l); y.@p = '1'; y.appendChild(); l[0].@p.length() === 0 && l[0].*.length() === 0"); assert_eq!(thrown_name("XML(<>)"), "TypeError"); assert_eq!(thrown_name("XML('')"), "SyntaxError"); assert_eq!(thrown_name("XML('')"), "SyntaxError"); @@ -368,7 +370,7 @@ fn the_xml_methods_behave_as_specified() { assert_eq!(string("(1).toString()"), "1"); assert_eq!(string("().toString()"), "\n \n"); truth("var x = ; x.valueOf() === x"); - truth("().xpath === undefined || ().xpath.length() === 0"); + truth("!Object.prototype.hasOwnProperty.call(XML.prototype, 'xpath') && !Object.prototype.hasOwnProperty.call(XMLList.prototype, 'xpath')"); truth("var x = ; x.b.attributes !== undefined"); } @@ -716,6 +718,26 @@ fn catastrophic_queries_end_with_the_fuel_limit() { assert_eq!(kind_of(limits, &serialize), RunErrorKind::Limit(LimitKind::Fuel)); } +/// The fuel that `action` takes when it runs after `setup`. +fn fuel_of(setup: &str, action: &str) -> u64 { + let mut r = realm(); + r.run(setup).unwrap(); + r.run(action).unwrap(); + Limits::default().fuel - r.fuel_left() +} + +#[test] +fn insert_child_before_and_after_take_fuel_for_each_child_they_scan() { + let narrow = "var x = ; var last = x.b[0];"; + let wide = "var x = ; for (var i = 0; i < 2000; i++) x.appendChild(); var last = x.b[1999];"; + for action in + ["x.insertChildAfter(, )", "x.insertChildBefore(, )", "x.insertChildAfter(last, )", "x.insertChildBefore(last, )"] + { + let (n, w) = (fuel_of(narrow, action), fuel_of(wide, action)); + assert!(w >= n + 1999, "{action}: {n} with one child, {w} with 2000"); + } +} + #[test] fn limits_cannot_be_caught_by_the_script() { assert_eq!(kind_of(TIGHT, "try { for (;;) ().toXMLString(); } catch (e) { 1 }"), RunErrorKind::Limit(LimitKind::Fuel)); From 28042c2e928024b48234827d3d8f871201f95dac Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 11 Nov 2026 09:47:33 -0800 Subject: [PATCH 2/2] JavaScript: test that new XML copies a list item's attributes and children The test for new XML with a one-item list now starts from an item that has a parent, an attribute and a child. It checks that the copy has no parent and that changing the copy's attribute and child leaves the original unchanged, so a copy that drops or shares the children fails. --- crates/js/src/xml_tests.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/js/src/xml_tests.rs b/crates/js/src/xml_tests.rs index bdcf79917..dce24d9d9 100644 --- a/crates/js/src/xml_tests.rs +++ b/crates/js/src/xml_tests.rs @@ -290,8 +290,10 @@ fn the_constructors_keep_identity_or_copy_as_specified() { truth("XML().nodeKind() === 'text' && XML(null) == '' && XML(undefined) == ''"); truth("var x = ; XML(x) === x && new XML(x) !== x && new XML(x) == x"); truth("var l = <>; XML(l) === l[0]"); - truth("var l = <>; var y = new XML(l); y !== l[0] && y == l[0] && y.parent() === null"); - truth("var l = <>; var y = new XML(l); y.@p = '1'; y.appendChild(); l[0].@p.length() === 0 && l[0].*.length() === 0"); + truth("var p =

; var l = p.a; var y = new XML(l); y !== l[0] && y == l[0] && y.parent() === null && l[0].parent() === p"); + truth( + "var p =

; var l = p.a; var y = new XML(l); y.@q = '2'; y.c.@r = '3'; l[0].@q == '1' && l[0].c.@r.length() === 0 && y.c.@r == '3'", + ); assert_eq!(thrown_name("XML(<>)"), "TypeError"); assert_eq!(thrown_name("XML('')"), "SyntaxError"); assert_eq!(thrown_name("XML('')"), "SyntaxError");