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