A canvas that fails without failing

DHSeaDev — Chrome Extensions, Windows Tools, & Idle Games

The failure that shaped this project does not throw. Chrome’s 2D canvas has a maximum side length and, separately, a maximum total area. Ask for a canvas past either and you do not get an exception, and you do not get null. You get a canvas object. It reports the width and height you asked for. Every drawing call on it succeeds. And every drawing call does nothing at all, so what comes out the other end is a valid PNG, of exactly the right dimensions, that is completely blank.

For a full-page screenshot extension that is close to the worst possible failure mode. The user clicks once, waits through a capture that takes real seconds, gets a file, and finds out it is empty later — possibly much later, possibly after the page it was of has changed. Nothing in the process signals a problem, because as far as every API involved is concerned, nothing went wrong.

You cannot ask, so you have to check

There is no property that tells you whether a canvas is real. The only way to know is to use it: write a single pixel of a known colour into the corner, read that pixel back, and see whether the value that comes out is the value that went in. If it is, the canvas draws. If it is not, it is a well-behaved object that happens to be a black hole.

Sealshot runs that proof before it captures a single tile, which matters more than it sounds. Capturing first and discovering the problem afterwards would mean spending the whole capture — Chrome permits two screenshots per second, so a long page is genuinely slow — before finding out it was never going to work. Proving the canvas up front turns a wasted minute into an immediate, readable refusal, and lets the failure be handled the honest way: scale the page down until it fits, do the capture at that scale, and say so in a sentence on the result page. A screenshot that has been reduced and admits it is useful. One that has been silently cropped is a trap.

The assertion that passed a bug

The other thing worth writing down happened in the test suite, and it is the more uncomfortable of the two.

Fixed and sticky elements are the hard part of stitching a page together. A header that stays put while you scroll will otherwise appear in every tile, so it gets hidden during the capture and drawn once at the top. The same logic in reverse handles footers. So the suite grew an assertion: capture a fixture with a sticky header, then check the header’s colour does not appear repeatedly down the image.

Then a fixture with a full-height fixed sidebar went in, and the sidebar disappeared from the finished capture entirely. Not misplaced. Absent. The assertion passed.

It passed because it only asked whether the element was repeated, and something that has vanished is not repeated. A one-sided assertion is not a weak test, it is a test that reports the wrong answer, and it will keep reporting it with a green tick for as long as you leave it there. The fix was two parts and the order matters: rewrite the assertion to check presence first and position second, watch it fail, and only then fix the classifier that was putting a viewport-height sidebar in the bottom-anchored group where the overlay pass then skipped it for being too big. Fixing the code first would have left a test that could not tell the difference.

Three more that only stress found

Fractional display scaling broke every capture at 1.25, 1.5 and 2.5. The browser snaps a scroll to whole device pixels, so asking for 800 lands you at 800.4, and a strict check on the resulting offset read that fraction as an impossible position and gave up. The fix is a sub-pixel tolerance, which sounds sloppy and is in fact the only correct reading of what the platform guarantees.

A page zoomed out reported its own height in two different coordinate systems depending on which element you asked, and taking the larger of the two invented a column of empty space that never existed. Zoomed in, the same code worked — by accident — which is why several earlier passes never caught it. The document extent has exactly one correct source, and it is not the maximum of two guesses.

And scroll snapping moved the scroll after it had been set, quietly, by a couple of dozen pixels, leaving a band of the page that belonged to no tile at all — a seam of content silently missing from the middle of an otherwise perfect image. Snapping is now neutralised for the duration of the capture and restored afterwards.

Why any of this is the product

Sealshot exists because a screenshot of a page is a copy of that page, and most extensions in this category either upload your captures to an account or hold standing permission to talk to servers you have never inspected. Its manifest declares no host permissions at all and the package contains no code capable of a network request, which is a claim you can check in about thirty seconds rather than take on trust.

But a privacy posture is worth nothing if the tool quietly hands you an empty file. The four defects above are all the same shape — a failure that produces a plausible, well-formed, wrong result — and that shape is exactly the one that survives casual use. Which is the argument for testing geometry against known answers in a pure module, for driving a real browser at more than one display scale, and for treating a one-sided assertion as a defect in its own right.

Version 1.2.0 is packaged and gated; nothing is submitted to the Chrome Web Store yet. The full write-up, the permission-by-permission reasoning and the privacy policy are on the Sealshot project page.

One credit, because the occasion deserves naming. The feature list Sealshot was built to match is GoFullPage’s, whose Chrome listing was removed in August 2026 over a copyright complaint its developers say was made in error and unrelated to security, and which remains available elsewhere while they work on restoring it. No code was taken and none was needed; its published behaviour was read as a specification. It is a good piece of software and this is not a dance on its grave.