Your machine is the apparatus · mechanism

The Clock in the Glass

There is no way to ask. Every browser deliberately withholds your display's refresh rate from the page, and this one is going to work it out anyway, from nothing but the times it is handed when it draws. Fifteen seconds is enough to separate from , two rates whose frame periods differ by . Then the part that costs something to admit: on displays whose true rate was known in advance, the number that comes back is not the display's. It is the display's rate with the frame period rounded down to a whole microsecond, and only one of the two browsers tested does the rounding.

Somewhere under your hands there is a piece of glass being rewritten from top to bottom, over and over, at a rate that was decided by a cable, a standard, and in one very common case by a committee in 1953. Nothing on the page you are reading is allowed to know that rate. It is a fingerprinting surface: the combination of screen size, colour depth and refresh rate is unusual enough to help identify you, so the platform keeps it back, on purpose, and has kept it back through two separate standards proposals that are still open.

What the page does get is a callback. You ask the browser to tell you when it is about to draw, and each time it does, it hands you a number: the time, in milliseconds, of the frame it is about to produce. Those numbers arrive at the rate the glass is being rewritten. So the rate is not exposed, but it is present, spread thinly across a few hundred timestamps, and the whole of this page is an argument about how much of it you can honestly get back.

1. First, confirm there is no back door

Before measuring anything it is worth establishing that measuring is necessary. The button below interrogates your own browser, live, for every route to a refresh rate that has ever been proposed or shipped. It reads the property names off Screen.prototype rather than guessing them, so if your browser has something this page has not heard of, it will still show up.

Instrument 1 · what your browser will admit to

Nothing here leaves your machine. The page makes no network requests at all after it loads.

The one thing CSS offers is @media (update), and it answers in three words rather than a number: none for print, slow for electronic paper, fast for everything else. Your screen is . That is the entire numeric surface, and it has no numbers in it.

2. The clock in your hand is not smooth

Before the display, the clock. The timestamps you are given are not the machine's honest opinion of the time: they are deliberately blunted, for the same fingerprinting reason, and the blunting is a constant written down in your browser's source code. You can read that constant back out of your own timestamps, because if every timestamp is a whole multiple of some quantity, then the greatest common divisor of the timestamps is that quantity.

Instrument 2 · the greatest common divisor of your own clock

The low bits of your timestamps. Each mark is one sample; a comb rather than a smear is a coarsened clock.

This is the one measurement on this page that every reader can make, whatever they are sitting in front of, and it reproduces a published number exactly. The candidates are few, and each is a line of code somebody wrote:

quantumenginewhich clockwhere it is written down

The two Firefox rows are worth a second look, because they are the same browser disagreeing with itself by a factor of fifty. performance.now() goes through one function and comes out on a millisecond grid; the animation-frame timestamp goes through a different one, three lines away in the same file, and comes out fifty times finer. The comment in the source says why, and it is not a flattering reason: "CSS Animations needs to have it's Time Reduction Logic refactored, so it's currently only clamping for RFP mode. RFP mode gives a much lower time precision, so we accept the security leak here for now." The leak is what this page measures with.

That table is a list of constants that are written down, and it is not a list of every grain a browser can produce. Turning Firefox's fingerprinting resistance on moves both of its clocks to , which is on none of those four rows, and this page has a real capture of it and no line of source to cite for it. So if the instrument above reports a grain that is not in the table, the useful question is not how many of your timestamps sit on it, which is a trick: every timestamp on a hundred-microsecond grid also sits on a two-microsecond one, so a grain finer than the truth scores a hundred percent by arithmetic and proves nothing. The useful question is whether it is coarser than everything in the table. Nothing accidental makes a clock coarser. Something finer than the table is one stray timestamp dragging a greatest common divisor down, which is what a loaded machine does.

3. Fifteen seconds, and a number you can check against your own settings

Now the measurement. The page will watch frames go by, which takes about fifteen seconds at sixty hertz and half that at a hundred and twenty, and then say what it thinks. Leave this tab in front and do not switch away: a hidden tab stops being drawn and the measurement will refuse rather than lie about it.

Before it tells you, it commits. The reader of a page like this has no ground truth in the browser, so the only honest structure is a prediction made in advance and then checked somewhere the page cannot reach. The instrument names the rate it thinks your operating system will report, tells you exactly where to look, and only then shows you the raw figure. If the two disagree, that is a result, and section 6 is mostly about the case where they should.

Instrument 3 · the measurement

The load button burns processor time inside each frame, which is what a heavy page does to you by accident. Run it once clean and once loaded, and watch which of the numbers below move.

Not started.

The estimator is not naive differencing, and the reason matters. Differencing throws away almost everything: the gap between the first frame and the nine-hundredth is known just as precisely as any single gap, and it is nine hundred times longer, so the rate's uncertainty falls as N-1.5 rather than N-0.5. What is used instead is a straight line fitted through timestamp against integer frame number, where the integers are recovered by growing the window a doubling at a time and refusing to grow whenever an index would have to move by more than of a frame. That refusal is what a cycle slip looks like from the inside, and reporting one is worth more than surviving it.

4. The 0.06 hertz that came from 1953

If your display reports 60 Hz and the measurement above came back near , nothing is broken. You have met the most consequential rounding error in the history of broadcasting.

American television was monochrome and ran at exactly 60 fields per second, locked to the mains. Adding colour meant adding a subcarrier, and a subcarrier beats against the sound carrier and against the line rate, and the beat shows up on a black-and-white set as a crawling pattern across the picture. The fix chosen in 1953 was to stop treating 60 as sacred: nudge everything down by a factor of exactly 1000/1001 so the three frequencies fall into a relationship that hides the beat. Every set already built kept working, because the change was one part in a thousand.

The consequence is that a great deal of video equipment, and a great many computer displays that were designed to talk to it, run at a rate that is not a round number and never was. Here is the arithmetic, from the standard, recomputed in front of you by three routes that share no steps:

Instrument 4 · the field rate, three ways

routearithmetichertz

11.1 Color frequency (subcarrier) — fsc = 5 MHz × 63/88 = 3.579545.. .MHz ± 10 Hz
11.2 Line frequency (horizontal) — fH = 2/455 × fSC = 15,734.265 ...Hz
11.3 Field frequency (horizontal) — fV = 2/525 × fH = 59.9400599 4...Hz

SMPTE 170M-2004, clause 11, transcribed from the standard. The heading on 11.3 says "(horizontal)" where it plainly means vertical; the error is the standard's and is left as printed. The same pair appears in Rec. ITU-R BT.1700, but not where you would look for it: Part A, the NTSC part, has no frequency table at all and simply refers you to SMPTE 170M-2004. The table is in Part B, which is the PAL part, and the numbers are in its 525 PAL column, 15 734.26 Hz ± 0.0003% and 2fH/525 (60/1.001). PAL-M shares the 525-line, 59.94 Hz raster with NTSC and differs at the subcarrier, which is why that column is right about these two numbers and wrong about the third.

Two rates apart in period is a fine thing to claim and a harder thing to earn, so here is what it actually costs. Set two rates and a jitter and the instrument says how many frames you need before the interval around one excludes the other, using the same arithmetic the estimator uses on you.

Instrument 5 · how much clock you have to buy

The nominal figure assumes the residuals are independent. Section 5 measures what they really cost, on real traces, and the answer is that the interval is optimistic by up to on the worst of them. Treat the number this instrument prints as a floor.

5. Eighteen traces, and the ones that must be refused

Everything above is a claim about a reduction, and a reduction can be tested in a way a live measurement never can: by running it on timestamps whose correct answer was decided before they were recorded. This page ships such traces, and the code that runs on them is not a copy of the code that ran on you a moment ago. It is the same file, the same function, the same line numbers.

of them were captured through a real browser on a real kernel modesetting display whose timing was programmed by hand, so their ground truth is arithmetic on the pixel clock and the line and frame totals and owes nothing whatsoever to this page's estimator. are negative controls, built to be wrong in a specific way. come from browsers with no display attached at all, which is a real condition with no correct answer, so they are shown and not scored. One of those last is a real capture from Firefox with privacy.resistFingerprinting switched on, and it is the trace that found a bug in this page.

Instrument 6 · the golden traces, replayed through the same reduction

Two of these deserve to be found by hand rather than described. The clamped clock is a synthetic trace of what a coarsened clock does to a real cadence: timestamps rounded onto a grid roughly one frame wide. The result is a trace of perfect apparent quality. Its residual is zero. Its coherence is . Its reported confidence interval is , which is not a small interval so much as the arithmetic reporting that there is nothing left to be uncertain about. And the rate it reports is , which is not the rate the display in that trace was running at and is not a rate any display has ever run at. A page that gates on its error bar prints that number. Only the third gate, the one that compares the grain of the clock against the length of a frame, can see it.

The other is the thrashing 144 Hz panel, where a quarter of the frames never arrive and the rest are three milliseconds out. It reports for a display running at 144, an error of , with a confidence interval of . The interval is wrong by a factor of several thousand. Coherence catches this one at , far under the floor, which is exactly the division of labour the three gates are for: they fail on different things, and no one of them is enough.

controlcoherencereportsintervalcaught by

Then the trace that is not built at all, and which caught this page telling a reader something false. Firefox with privacy.resistFingerprinting switched on was captured directly, and its clock is coarsened to : not one of the four constants in section 2, a whole frame wide, and with of its timestamps sitting exactly on it. Because the grain is as wide as a frame, consecutive frames land on the same grid point, so of the nine hundred timestamps repeat the one before them exactly. The first version of this page tested for a frame clock by asking whether any interval was less than or equal to zero, counted every one of those repeats as a step backwards in time, and told a reader running fingerprinting resistance that their timestamps were arriving out of order. None of them were. A repeat is a clock too coarse to tell two frames apart, which is the thing the quantum gate exists to name, and the gates that now refuse this trace are . It is kept in the shipped set so that fault can never come back.

One control is here because it does not behave the way you would guess, and saying so is cheaper than being caught. Shuffling a trace's timestamps into random order leaves the coherence unchanged, because the coherence is a sum over an unordered set and addition does not care what order it is done in. In exact arithmetic it is identical; in a browser the two differ by , which is the order the additions happened in and nothing else. It would have been easy and wrong to claim that coherence catches a shuffled trace. What catches it is the dullest test on the page, which is that time is supposed to go forwards.

6. You are not measuring the glass

Here is the objection that ends most pages like this one, and it is correct. You are not measuring your panel. You are measuring how often a compositor chooses to hand your tab a frame, and the compositor's opinion of your panel is a number it was told, stored in a variable, in a unit somebody picked. Between the liquid crystal and the callback there are at least four pieces of software, and any of them can round.

Rather than argue with that, the thing to do is to catch one of them at it.

A display's rate is not a label, it is a division. The graphics card pushes out pixels at a fixed clock; a line is a certain number of pixel-times wide, including the blanking that is scanned but never shown; a frame is a certain number of lines tall, including the lines that are not shown either. The rate is the clock divided by both totals, and it lands wherever it lands. So a display can be given a rate on purpose, and that rate is known to as many decimals as you care to write.

Six such displays were built, in software, using the kernel's virtual modesetting driver, which produces a real vertical-blank interrupt on a real timer at whatever timing the mode says. A real browser was opened on each. Eight captures came back, half a minute apiece except one of twenty seconds, and a little under four minutes of frames in all, and the first of each capture are what this page carries and recomputes in front of you. Two of the six modelines were chosen specifically to break the model that the other four suggested.

Instrument 7 · the modeline, and what the browser makes of it

The model is one sentence with nothing adjustable in it: the browser turns the rate into a frame period and stores that period as a whole number of microseconds, discarding the rest. There is no parameter to tune. For any modeline it predicts a different number, and it can be wrong in a way you can see. Against the measured captures, with no statistical assumption anywhere in the comparison, only the offset from the modeline and the spread the estimate actually shows on that same trace:

displaybrowsermodeline saysmodel saysmeasuredoffsetvs scatter

Read the two rows that were built to kill it. The whole-microsecond modeline was chosen because its period is exactly, already a round number, so the model predicts no offset at all: if the browser had simply been running fast, this trace would have been high like the others, and it is not, it sits from the modeline. The rounding-breaker was chosen at the other extreme, a period of , so the model has to ask for nearly a full microsecond and gets it: the measured offset is against a predicted , which is the scatter of that trace.

And then the row that settles whose rounding it is. Firefox, on the same virtual display, at the same modeline, minutes later in the same X session, does not do it. It lands on the modeline. Each capture records the X mode id its output was switched to, and across both engine pairs those ids are identical, so nothing was reprogrammed in between. So this is not the kernel's timer being coarse and it is not the virtual display being a fake: the same vertical-blank interrupts produced both sets of timestamps, and one browser reports the rate and the other reports the rate with the period truncated.

Which means the number in section 3 has quietly changed what it is. It is not a fact about a piece of glass. It is a fact about a negotiation, and the browser is one of the parties, and it rounds. If you are reading this on a phone or a recent laptop the negotiation is live and continuous: variable-refresh panels change rate constantly, by design, against what is on screen and how warm the battery is, and for those there is no single true rate to recover and any page that prints one is wrong by construction. That is what the load button in section 3 is for. Turn it on, measure again, and watch the coherence fall as the thing you were measuring stops holding still.

The honest form of the claim, then, is the long one. Fifteen seconds of animation-frame timestamps recover the frame period your compositor is currently using, to within about on the committed 60 Hz capture, and that figure is the measured scatter of the estimate from one window of that capture to the next, not the reported interval, which this page has just spent a paragraph refusing to trust. That period, on Chromium, is your display's period rounded down to a whole microsecond; on Firefox it appears to be your display's period; and on a variable-refresh panel under load it is not a constant at all. The published rate of the display underneath is recoverable from that, most of the time, by rounding to the nearest standard. Most of the time is not always, and the difference between those two phrases is this section.

The check

running…

These run in your browser, now, against the same module the page uses. A red line means this page is wrong, not that the check is. The exception is the lines marked OFFLINE:. Those are quotations from documents and from a machine you are not sitting at, they are written as an assertion that is true by construction, and they cannot fail. They are labelled and counted separately because a quoted check is worth less than a run one, and each of them names where it was read instead.