{"record":{"id":"317d7de0f03d5df4","repo":"pbakaus/impeccable","slug":"the-offscreen-adapter-is-asynchronous","errorCode":null,"errorMessage":"the offscreen adapter is asynchronous","messagePattern":"the offscreen adapter is asynchronous","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browser-bundle/60-offscreen.js","lineNumber":89,"sourceCode":"  }\n\n  // The visual-contrast IO over the snapshot: the core over the loaded\n  // snapshot (hit-test needs answered by the content script between calls),\n  // node = snapshot id, images and pixels read by the content script.\n  function createOffscreenVisualIO(wasm, session) {\n    async function core(fn, ...args) {\n      for (;;) {\n        const out = wasm[fn](...args);\n        if (!wasm.snapshot_has_needs()) return out;\n        const needs = JSON.parse(wasm.snapshot_take_needs());\n        const facts = await ask(session, { ask: { hitTests: needs.hitTests || [] } });\n        wasm.snapshot_add_facts(JSON.stringify(facts || { hits: [] }));\n      }\n    }\n    const media = (id) => JSON.parse(wasm.snapshot_media(id)) || {};\n    return {\n      core,\n      coreSync() { throw new Error('the offscreen adapter is asynchronous'); },\n      node: (handle) => handle,\n      handle: (id) => id,\n      parentOrBody: (id) => wasm.snapshot_parent_or_body(id),\n      intrinsicImg(id) { const m = media(id); return [m.nw || m.vw || m.w || 0, m.nh || m.vh || m.h || 0]; },\n      intrinsicRaster(id) { const m = media(id); return [m.w || m.vw || 0, m.h || m.vh || 0]; },\n      imgSrc(id) { const m = media(id); return m.cur || m.src || ''; },\n      loadImage: (src) => ask(session, { ask: { io: { kind: 'loadImage', src } } }),\n      readPixel: (ref, plan, px, py) => ask(session, { ask: { io: { kind: 'readPixel', ref, plan, px, py } } }),\n      // Scrolling the page from a snapshot is not meaningful; the extension\n      // never sets scrollOffscreen, and the lazy pass re-captures instead.\n      querySelector: () => null,\n      scroll() { const v = JSON.parse(wasm.snapshot_viewport()) || {}; return { x: v.scrollX || 0, y: v.scrollY || 0 }; },\n      scrollTo() {},\n      scrollIntoView: () => false,\n      waitForPaint: () => Promise.resolve(),\n    };\n  }\n","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/pbakaus/impeccable/blob/2bc2879276c1f321a53c4ca99d3371e411329b52/browser-bundle/60-offscreen.js#L71-L107","documentation":"In the extension's offscreen document, there is no live DOM to query; the visual-contrast adapter is asynchronous (it resolves element data from a loaded snapshot via WASM). Calling coreSync() — the synchronous core accessor of the adapter interface — is unsupported there, so it throws this explicit error rather than silently returning something wrong.","triggerScenarios":"Calling IO.coreSync() (or code paths that require the synchronous visual core) while running inside createOffscreenVisualIO, i.e. the offscreen scan pipeline in the Chrome extension.","commonSituations":"Sharing visual-contrast code between the live-page path (sync DOM available) and the offscreen path (snapshot only) and hitting the sync branch; calling coreSync from custom extensions of the scan.","solutions":["Use the async IO.core(name, argsJson) accessor instead of coreSync in offscreen contexts","Branch your code: use the sync adapter only on live pages, the offscreen adapter for snapshot-based scans","Refactor the calling code to await the async core call"],"exampleFix":"// before\nconst out = JSON.parse(IO.coreSync('collect_browser_findings', cfg));\n// after\nconst out = JSON.parse(await IO.core('collect_browser_findings', cfg));","handlingStrategy":"fallback","validationCode":"// offscreen contexts: never use the sync accessor\nconst useSync = typeof document !== 'undefined' && !isOffscreenDocument;\nif (!useSync) { /* route through await IO.core(...) */ }","typeGuard":"function supportsSyncCore(io) { return typeof io.coreSync === 'function' && !io.isOffscreen; }","tryCatchPattern":"let out;\ntry { out = JSON.parse(IO.coreSync('collect_browser_findings', cfg)); }\ncatch (e) {\n  if (String(e.message).includes('offscreen adapter is asynchronous')) {\n    out = JSON.parse(await IO.core('collect_browser_findings', cfg));\n  } else throw e;\n}","preventionTips":["Write shared scan code against the async IO.core API only","Tag the adapter object with its mode (live vs offscreen) and branch explicitly","Add lint/CI checks forbidding coreSync in offscreen modules"],"tags":["async","offscreen","browser-extension"],"backgroundTag":"unsupported-operation","analyzedSha":"2bc2879276c1f321a53c4ca99d3371e411329b52","analyzedAt":"2026-09-08T04:51:14.109Z","contentChangedAt":"2026-09-08T04:51:14.109Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}