diff --git a/src/test-hooks/offscreen-hooks.ts b/src/test-hooks/offscreen-hooks.ts index bf2463b..c0c7a90 100644 --- a/src/test-hooks/offscreen-hooks.ts +++ b/src/test-hooks/offscreen-hooks.ts @@ -300,6 +300,10 @@ globalThis.__mokoshTest = { // op='install-fake-display-media' → { ok: true } OR { ok: false, error } // op='dispatch-ended' → { ok: true } OR { ok: false, error: 'no stream' } // op='has-stream' → { hasStream: boolean } +// op='get-display-surface' → { displaySurface: string|null } OR { ok: false, error } +// — Plan 01-13 Wave 3A A3 contract. Returns the active track's +// `getSettings().displaySurface` value (monkey-patched to 'monitor' +// by `installFakeDisplayMedia`); returns null when no stream is live. // Unknown ops respond { ok: false, error: 'unknown-op' }. // // The bridge handler MUST run BEFORE the production offscreen bridge @@ -349,6 +353,35 @@ chrome.runtime.onMessage.addListener((rawMessage, _sender, sendResponse) => { sendResponse({ hasStream: currentStream !== null }); return false; } + if (op === 'get-display-surface') { + // Plan 01-13 Wave 3A A3 contract — return the active track's + // displaySurface so the harness can verify the offscreen-hooks + // monkey-patched getSettings() correctly reports 'monitor'. + // Production code in src/offscreen/recorder.ts enforces this same + // invariant (tears down + throws 'wrong-display-surface' otherwise), + // so if recording is live the value is guaranteed monitor — the + // harness asserts === 'monitor' for explicit empirical verification. + try { + if (currentStream === null) { + sendResponse({ displaySurface: null }); + return false; + } + const track = currentStream.getVideoTracks()[0]; + if (track === undefined) { + sendResponse({ displaySurface: null }); + return false; + } + const settings = track.getSettings(); + const displaySurface = settings.displaySurface ?? null; + sendResponse({ displaySurface }); + } catch (err) { + sendResponse({ + ok: false, + error: err instanceof Error ? err.message : String(err), + }); + } + return false; + } sendResponse({ ok: false, error: 'unknown-op' }); return false; }); diff --git a/tests/background/no-test-hooks-in-prod-bundle.test.ts b/tests/background/no-test-hooks-in-prod-bundle.test.ts index 4eb41ee..1073b0b 100644 --- a/tests/background/no-test-hooks-in-prod-bundle.test.ts +++ b/tests/background/no-test-hooks-in-prod-bundle.test.ts @@ -58,8 +58,9 @@ // (replaces Approach-A `simulateUserStop`) // - `getSegmentCount` — Plan 01-11 Task 7 segments-count getter (retained) // - `__mokoshOffscreenQuery` — 01-13 page→offscreen bridge message type +// - `get-display-surface` — 01-13 Wave 3A bridge op string (A3 contract) // -// Total: 8 surface strings. Each MUST be absent from EVERY file under +// Total: 9 surface strings. Each MUST be absent from EVERY file under // `dist/` post-build. The list is mirrored by the harness's A0 // assertion (tests/uat/harness.test.ts in Wave 3A) so the same // invariant is enforced at unit-test time (fast, every CI run) AND @@ -109,6 +110,7 @@ const FORBIDDEN_HOOK_STRINGS: ReadonlyArray = [ 'dispatchEndedOnTrack', 'getSegmentCount', '__mokoshOffscreenQuery', + 'get-display-surface', ]; /** How long the build child has to finish (`npm run build` is ~10s).