pbakaus/impeccable · error · Error

the offscreen adapter is asynchronous

Error message

the offscreen adapter is asynchronous

What it means

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.

Source

Thrown at browser-bundle/60-offscreen.js:89

  }

  // The visual-contrast IO over the snapshot: the core over the loaded
  // snapshot (hit-test needs answered by the content script between calls),
  // node = snapshot id, images and pixels read by the content script.
  function createOffscreenVisualIO(wasm, session) {
    async function core(fn, ...args) {
      for (;;) {
        const out = wasm[fn](...args);
        if (!wasm.snapshot_has_needs()) return out;
        const needs = JSON.parse(wasm.snapshot_take_needs());
        const facts = await ask(session, { ask: { hitTests: needs.hitTests || [] } });
        wasm.snapshot_add_facts(JSON.stringify(facts || { hits: [] }));
      }
    }
    const media = (id) => JSON.parse(wasm.snapshot_media(id)) || {};
    return {
      core,
      coreSync() { throw new Error('the offscreen adapter is asynchronous'); },
      node: (handle) => handle,
      handle: (id) => id,
      parentOrBody: (id) => wasm.snapshot_parent_or_body(id),
      intrinsicImg(id) { const m = media(id); return [m.nw || m.vw || m.w || 0, m.nh || m.vh || m.h || 0]; },
      intrinsicRaster(id) { const m = media(id); return [m.w || m.vw || 0, m.h || m.vh || 0]; },
      imgSrc(id) { const m = media(id); return m.cur || m.src || ''; },
      loadImage: (src) => ask(session, { ask: { io: { kind: 'loadImage', src } } }),
      readPixel: (ref, plan, px, py) => ask(session, { ask: { io: { kind: 'readPixel', ref, plan, px, py } } }),
      // Scrolling the page from a snapshot is not meaningful; the extension
      // never sets scrollOffscreen, and the lazy pass re-captures instead.
      querySelector: () => null,
      scroll() { const v = JSON.parse(wasm.snapshot_viewport()) || {}; return { x: v.scrollX || 0, y: v.scrollY || 0 }; },
      scrollTo() {},
      scrollIntoView: () => false,
      waitForPaint: () => Promise.resolve(),
    };
  }

View on GitHub (pinned to 2bc2879276)

Solutions

  1. Use the async IO.core(name, argsJson) accessor instead of coreSync in offscreen contexts
  2. Branch your code: use the sync adapter only on live pages, the offscreen adapter for snapshot-based scans
  3. Refactor the calling code to await the async core call

Example fix

// before
const out = JSON.parse(IO.coreSync('collect_browser_findings', cfg));
// after
const out = JSON.parse(await IO.core('collect_browser_findings', cfg));
Defensive patterns

Strategy: fallback

Validate before calling

// offscreen contexts: never use the sync accessor
const useSync = typeof document !== 'undefined' && !isOffscreenDocument;
if (!useSync) { /* route through await IO.core(...) */ }

Type guard

function supportsSyncCore(io) { return typeof io.coreSync === 'function' && !io.isOffscreen; }

Try / catch

let out;
try { out = JSON.parse(IO.coreSync('collect_browser_findings', cfg)); }
catch (e) {
  if (String(e.message).includes('offscreen adapter is asynchronous')) {
    out = JSON.parse(await IO.core('collect_browser_findings', cfg));
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of pbakaus/impeccable@2bc2879276 (2026-09-08). Data as JSON: /api/errors/317d7de0f03d5df4. Report an issue: GitHub.