It is challenging to implement dual-clock jitter based noise source in modern systems, given that timing-related APIs are all backed by TSC or TSC-like clocks underneath nowadays, especially in web browsers, where our access to hardware is heavily restricted. But I think I’ve just found a way to implement it. We can exploit the browser’s VSync jitter by measuring the time between browser content repaints. The two JavaScript functions we can use to implement this are performance.now() and requestAnimationFrame().
The performance.now() function returns a high-resolution, monotonic timestamp in milliseconds, with microsecond precision in the decimal part on some browsers. It is based on the OS-provided timer, which is usually backed by a TSC or TSC-like clock. It is well-suited for precise interval measurements in web browsers.
The requestAnimationFrame(), on the other hand, is used for synchronizing code execution with the display’s refresh cycle. When a callback function is scheduled with requestAnimationFrame(), the browser invokes it just before the next repaint of the browser content. On a standard 60 Hz monitor, requestAnimationFrame() will call the specified callback function ~60 times per second when scheduled repeatedly. Internally, this scheduling is tied to the display’s vertical synchronization (VSync) interrupt, which means its timing is controlled by the display’s hardware clock.
To extract noise, we can measure the elapsed time between consecutive requestAnimationFrame() callbacks using performance.now(). Since requestAnimationFrame() callback timing is tied to the display’s hardware clock, while performance.now() advances on a separate clock, there are tiny variations (jitter) that can be used as a noise source.
Basic Implementation
let samples = [];
let previousTimestamp = null;
function vSyncTick(_) {
let currentTimestamp = performance.now();
if (previousTimestamp !== null) {
let timeDifference = currentTimestamp - previousTimestamp;
samples.push(timeDifference);
previousTimestamp = null;
} else {
previousTimestamp = currentTimestamp;
}
requestAnimationFrame(vSyncTick);
}
requestAnimationFrame(vSyncTick);
Note: Notice that I sample at every other frame (resetting previousTimestamp to null every time timeDifference is measured and saved instead of setting it to currentTimestamp). This is for eliminating the strong negative lag-1 autocorrelation that I previously encountered when I first implemented a sensor timing jitter based noise generator. It is caused by the compensatory behavior of the interval samples produced using that method. If one interval is long, its next interval is likely short, and vice versa.
Analysis
These data are generated using Chrome 139 and Firefox 141 running on Windows 10 connected to a 60 Hz DELL S2721H monitor.
I observed that Chrome’s performance.now() has a resolution of 100 microseconds, while Firefox’s performance.now() has a resolution of 20 microseconds.
vsync-jitter-chrome-multipliedby10.csv (4.0 KB)
vsync-jitter-firefox-multipliedby50.csv (5.0 KB)
Time Series Plot & Histogram
Chrome
Firefox
Autocorrelation
Chrome
Firefox
FFT
Chrome
Firefox
Unlike the noise from sensor timing jitter I implemented and shared before which is mostly stable, the noise in this one often surges and drops sharply, creating a lot of outliers. The FFTs also have more pronounced peaks. While many randomness extraction methods won’t mind them, I don’t know if these characteristics can have an impact on MMI.

















