Summary
captureDOM's image-preload step waits on every <img> with no timeout. An <img loading="lazy"> far below the fold is never fetched by the browser, so it fires neither load nor error and complete stays false forever. The Promise.all never settles, the capture promise never resolves, and — because the dialog is set to closed before capture — the reporter dialog never reopens. To the user the widget simply vanishes mid-report, with no error and no way back to the draft.
Where
In the built widget (v1.2.0) this is the image-preload helper:
async function Nm(e){
const t = e.querySelectorAll("img"), r = [];
t.forEach(i => {
i.complete
? (i.decode && r.push(i.decode().catch(()=>{})))
: r.push(new Promise(o => { // <-- resolves ONLY on load/error
const s = async () => { /* ... */ o() },
n = () => { /* ... */ o() };
i.addEventListener("load", s, {once:true});
i.addEventListener("error", n, {once:true});
}))
});
r.length > 0 && await Promise.all(r)
}
The dialog is closed before this runs and only reopened from Image.onload / Image.onerror / the catch in the capture handler — none of which are ever reached:
W(!1), f(!0), h("closed"), await new Promise(P=>setTimeout(P,100));
try { const P = await o({...}); /* ... */ it.onload = () => { ... h("form") } }
catch (P) { ... h("form") }
So the failure mode is silent and unrecoverable rather than a caught error with a toast.
Reproduction
- Any page taller than the viewport with an
<img loading="lazy"> several thousand px below the fold (far enough that the browser does not pre-fetch it).
- Open the reporter, go to Screenshots, click Capture Screenshot.
- The dialog closes and never comes back. No console error, no
toast.error.capture.
Observed on production with Chrome. In the hung state: document.fonts.status === "loaded", 25 images on the page, exactly 1 with complete === false — the lazy one, getBoundingClientRect().top === 3325. Still closed after 70 seconds.
It presents as intermittent because it depends on viewport height: at a 2053px-tall viewport the same image fell inside Chrome's lazy-load threshold, loaded, and capture completed in ~4s; at 1440×810 it hung every time the image was still pending.
Suggested fix
Bound the wait, so a pending image degrades the screenshot rather than deadlocking the widget:
const settled = new Promise(o => { /* load/error as today */ });
r.push(Promise.race([settled, new Promise(o => setTimeout(o, 3000))]));
Two things worth considering alongside it:
- Force-load pending lazy images before capture (
img.loading = "eager" on any !img.complete && img.loading === "lazy", restored afterwards) so they are actually included rather than blank.
- Guarantee the dialog is restored — e.g. reopen in a
finally — so no future hang in the capture path can strand a reporter mid-report.
Happy to send a PR if the shape above looks right.
Summary
captureDOM's image-preload step waits on every<img>with no timeout. An<img loading="lazy">far below the fold is never fetched by the browser, so it fires neitherloadnorerrorandcompletestaysfalseforever. ThePromise.allnever settles, the capture promise never resolves, and — because the dialog is set toclosedbefore capture — the reporter dialog never reopens. To the user the widget simply vanishes mid-report, with no error and no way back to the draft.Where
In the built widget (v1.2.0) this is the image-preload helper:
The dialog is closed before this runs and only reopened from
Image.onload/Image.onerror/ thecatchin the capture handler — none of which are ever reached:So the failure mode is silent and unrecoverable rather than a caught error with a toast.
Reproduction
<img loading="lazy">several thousand px below the fold (far enough that the browser does not pre-fetch it).toast.error.capture.Observed on production with Chrome. In the hung state:
document.fonts.status === "loaded", 25 images on the page, exactly 1 withcomplete === false— the lazy one,getBoundingClientRect().top === 3325. Still closed after 70 seconds.It presents as intermittent because it depends on viewport height: at a 2053px-tall viewport the same image fell inside Chrome's lazy-load threshold, loaded, and capture completed in ~4s; at 1440×810 it hung every time the image was still pending.
Suggested fix
Bound the wait, so a pending image degrades the screenshot rather than deadlocking the widget:
Two things worth considering alongside it:
img.loading = "eager"on any!img.complete && img.loading === "lazy", restored afterwards) so they are actually included rather than blank.finally— so no future hang in the capture path can strand a reporter mid-report.Happy to send a PR if the shape above looks right.