use browser_oxide::Page;
async fn page() -> Page {
Page::from_html(
"<!DOCTYPE html><html><body></body></html>",
None::<browser_oxide::stealth::StealthProfile>,
)
.await
.unwrap()
}
#[tokio::test]
async fn direct_recursion_throws_range_error() {
let mut p = page().await;
let r = p
.evaluate(
r#"
try {
(function f() { f(); })();
"no-error"
} catch (e) {
e instanceof RangeError ? "RangeError" : ("Other:" + e.message)
}
"#,
)
.unwrap();
assert_eq!(
r, "RangeError",
"deep direct recursion must surface as JS RangeError, not crash"
);
}
#[tokio::test]
async fn shim_style_mutual_recursion_throws_range_error() {
let mut p = page().await;
let r = p
.evaluate(
r#"
try {
const a = function() { return b(); };
const b = function() { return a(); };
a();
"no-error"
} catch (e) {
e instanceof RangeError ? "RangeError" : ("Other:" + e.message)
}
"#,
)
.unwrap();
assert_eq!(r, "RangeError");
}
#[tokio::test]
async fn apply_chain_recursion_throws_range_error() {
let mut p = page().await;
let r = p
.evaluate(
r#"
try {
const f = function() { return f.apply(this, arguments); };
f();
"no-error"
} catch (e) {
e instanceof RangeError ? "RangeError" : ("Other:" + e.message)
}
"#,
)
.unwrap();
assert_eq!(r, "RangeError");
}
#[tokio::test]
async fn isolate_recovers_after_range_error() {
let mut p = page().await;
p.evaluate(
r#"
try { (function f() { f(); })(); } catch (e) {}
"#,
)
.unwrap();
let r = p.evaluate("1 + 1").unwrap();
assert_eq!(
r, "2",
"isolate must execute new scripts after a RangeError"
);
}
#[tokio::test]
async fn proxy_trap_recursion_throws_range_error() {
let mut p = page().await;
let r = p
.evaluate(
r#"
try {
const p = new Proxy({}, { get(t, k) { return p[k]; } });
p.foo;
"no-error"
} catch (e) {
e instanceof RangeError ? "RangeError" : ("Other:" + e.message)
}
"#,
)
.unwrap();
assert_eq!(r, "RangeError");
}
#[tokio::test]
async fn getter_chain_recursion_throws_range_error() {
let mut p = page().await;
let r = p
.evaluate(
r#"
try {
const o = {};
Object.defineProperty(o, 'x', { get() { return o.x; } });
o.x;
"no-error"
} catch (e) {
e instanceof RangeError ? "RangeError" : ("Other:" + e.message)
}
"#,
)
.unwrap();
assert_eq!(r, "RangeError");
}
#[test]
fn test_thread_stack_is_at_least_16mb() {
fn recurse(depth: u32, max: u32) -> u32 {
let _local: [u8; 4096] = [0; 4096];
if depth == max {
std::hint::black_box(_local[0]) as u32
} else {
recurse(depth + 1, max)
}
}
let r = recurse(0, 4096);
assert_eq!(
r, 0,
"recurse to 16 MB stack depth must succeed — proves RUST_MIN_STACK env reached this thread"
);
}
#[tokio::test]
#[allow(non_snake_case, reason = "mirrors JS API name under test")]
async fn function_toString_recursion_throws_range_error() {
let mut p = page().await;
let r = p
.evaluate(
r#"
try {
const orig = Function.prototype.toString;
Function.prototype.toString = function() {
return Function.prototype.toString.call(this); // recurses through patched version
};
try {
(function dummy() {}).toString();
} finally {
Function.prototype.toString = orig;
}
"no-error"
} catch (e) {
// Restore in case the catch is reached before `finally`
"Caught:" + (e instanceof RangeError ? "RangeError" : e.message)
}
"#,
)
.unwrap();
assert_eq!(r, "Caught:RangeError");
}