Skip to main content

The capture cascade

Screenshots are taken in two stages, and it is worth understanding which one you get, because their fidelity differs.

Stage one — the browser's own screen capture. Pixel-exact: it is a picture of what the screen actually showed, including everything CSS can do. The browser asks the user for permission once per capture, and that prompt is expected behavior, not a failure — it appears only after the user clicks "Capture screenshot", never on page load.

Stage two — the DOM renderer. No permission prompt, works everywhere including iOS Safari, and reconstructs the page from the DOM rather than photographing it. That reconstruction is very good and has named limits, listed under what the DOM stage does not reproduce.

screenshot.strategy picks the behavior:

ValueBehavior
auto (default)try stage one, fall back to stage two when it is unavailable or declined
nativestage one only
domstage two only — no permission prompt ever
offno screenshot button at all

Which stage produced a given screenshot is recorded on the report, so a maintainer can tell an exact picture from a reconstruction.

Whatever the stage, the reporter sees the screenshot before sending and can discard or retake it. Nothing is uploaded until they attach it.

Which is why Send is refused while a capture is still sitting in that preview. It exists only in their browser at that point, so sending would file the report without the one thing they took a screenshot for — and the old behavior did exactly that, silently, followed by a success message. The widget now says which of the two ways out there is and puts focus on the Attach button. It does not attach for them: Discard is there because a capture is sometimes deliberately not sent, and choosing on their behalf overrides that.

If you drive the capture component yourself rather than through the shipped widget, that refusal is a seam you have to fill: onPending sits beside onStage and is called when a capture starts waiting in the preview, and cleared when it is attached, discarded or fails. The shipped implementation sets the component's screenshotPending property, which is what the submit reads. A custom integration that never calls it simply keeps the older behavior, where Send goes through and the capture is dropped.

What ends up in the frame

The visible viewport, not the whole page. screenshot.viewport_only is on by default and both stages honor it, so a report about something further down the page arrives as the band the reporter was actually looking at. That is usually the point — but it is worth knowing before you go looking for the rest of the page in the image.

Stage one captures whatever the reporter agreed to share. The browser owns that choice, and this package does not get to make it: Chromium narrows the picker to the current tab, elsewhere the reporter can pick another window or the whole screen, and what they pick is what arrives. Two consequences worth carrying: a report can contain more of their desktop than either of you intended, and region redaction covers regions of your page — it has no reach into a window that is not yours.

The byte cap is enforced on the server only

screenshot.max_bytes (8 MB) is checked after the upload lands, and the browser is never told the number. Lowering it is the one change here that can strand a reporter: the capture is produced, uploaded, refused, and nothing on the client knows to try again smaller, so they get a capture-and-reject loop with no way out of it. If your captures really are too large, lower screenshot.scale — that is the knob the browser obeys — and leave the cap where the renderer's own output was sized against it.