diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js index 2683dea..ca85f03 100644 --- a/crates/obscura-js/js/bootstrap.js +++ b/crates/obscura-js/js/bootstrap.js @@ -5361,14 +5361,14 @@ globalThis.XMLSerializer = class XMLSerializer { globalThis.performance = globalThis.performance || { now: (function() { // Monotonically non-decreasing: return the wall-clock offset, but never a - // value below the last one. The previous version added an uncapped per-call - // sub-millisecond bump that could exceed 1.0, so when the millisecond then - // ticked over and the bump reset to 0 the result went backwards. - var _last = 0; + // value below the last one. Equal readings are allowed, and avoiding a + // synthetic per-call increment keeps tight loops from advancing the clock + // faster than real elapsed time. + var _last = -Infinity; return function() { var ms = Date.now() - (globalThis.performance.timeOrigin || 0); - if (ms > _last) _last = ms; - else _last += 0.001; // same/earlier ms: advance by a tiny epsilon + if (ms < _last) return _last; + _last = ms; return _last; }; })(), diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs index 5dca703..f6a058e 100644 --- a/crates/obscura-js/src/runtime.rs +++ b/crates/obscura-js/src/runtime.rs @@ -1330,6 +1330,20 @@ mod tests { assert_eq!(violations.as_f64(), Some(0.0), "performance.now() went backwards"); } + #[test] + fn performance_now_does_not_outrun_elapsed_time() { + let mut rt = setup_runtime("
"); + let lead = rt + .evaluate( + "(function(){for(var i=0;i<500000;i++)performance.now(); return performance.now()-(Date.now()-performance.timeOrigin);})()", + ) + .unwrap(); + assert!( + lead.as_f64().unwrap() <= 1.0, + "performance.now() advanced ahead of elapsed time: {lead}" + ); + } + #[test] fn test_document_title() { let mut rt = setup_runtime("