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));