Skip to content

fix(chrome-extension): reliable audio, mic and webcam start in the extension recorder - #3549

Merged
richiemcilroy merged 28 commits into
mainfrom
fix/chrome-extension-reliability
Oct 29, 2026
Merged

richiemcilroy merged 28 commits into
mainfrom
fix/chrome-extension-reliability

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Reliability pass on the Chrome extension recorder: audio, mic, webcam start, recorder panel and start races. Only apps/chrome-extension changes.

Audio

flowchart LR
  mic[Mic track] --> mix["AudioContext({ sinkId: { type: 'none' } })"]
  tab[Tab audio] --> mix
  mix --> rec[MediaRecorder]
  tab --> play["Separate playback AudioContext<br/>(tab mode only)"] --> speakers[Speakers]
Loading
  • The recording mix no longer has an output device, so the mic is never played back into the speakers (echo) and the mix keeps running when the output device changes (src/offscreen/recorder.ts, createRecordingAudioContext).
  • Tab capture mutes the tab, so tab audio alone is played back from its own context that the recording does not depend on.
  • The dock mute button mutes only the live recording mic and shows only when the recording has one. It no longer flips the saved mic setting.
  • The silent-mic check reads the raw signal (echoCancellation, autoGainControl, noiseSuppression off) over a 1 s window, so a quiet room no longer triggers the "no sound" warning while a dead mic still does.

Mic and camera acquisition

// src/offscreen/recorder.ts
const getMicrophoneConstraints = (microphone, processing) => {
  const audio = { echoCancellation: processing, autoGainControl: processing, noiseSuppression: processing };
  return microphone.deviceId && microphone.deviceId !== DEFAULT_MICROPHONE_DEVICE_ID
    ? [{ audio: { ...audio, deviceId: { exact: microphone.deviceId } }, video: false }, { audio, video: false }]
    : [{ audio, video: false }];
};
  • A saved mic that is unplugged or whose id changed now records from the default mic. Before, the recording silently had no mic at all.
  • Camera mode opens the camera and mic separately. A stale mic id used to fail the whole Camera mode start.
  • openUserMedia retries a device once after 300 ms on NotReadableError / AbortError (a camera or mic still being released by the previous tab, the mic probe or another app). NotAllowedError is not retried.

Webcam preview ownership

sequenceDiagram
  participant SW as Service worker
  participant R as Recorded tab
  participant B as Background tab
  SW->>R: overlay-settings (activePreviewTabId = R)
  Note over B: overlay lazy-loads on "recording"
  B->>SW: webcam-preview-ready
  SW-->>B: overlay-hide (not owner, not pending)
  R->>SW: webcam-preview-ready
Loading
  • Every open tab loads the overlay when a recording starts. Tabs opened since the last recording each used to open their own camera preview on mount, and whichever reported ready last took ownership, hiding the preview in the recorded tab (and leaving the webcam out of fullscreen recordings).
  • The overlay now only opens the preview on mount when the tab is visible (src/content/overlay.tsx).
  • handlePreviewReady ignores a ready signal from a tab that is neither the owner nor the pending hand-off target, and hides its preview (src/background/service-worker.ts). Real hand-offs still go through showOverlayInTab.
  • The in-page recorder panel and the camera preview controls (shape, size, mirror, Picture in Picture) are restored, and the preview comes back when the recorder reopens.

Start races and state

  • Offscreen requests that reach another extension page before the recorder document is listening (The message port closed before a response was received) are retried instead of failing the start.
  • A cached free plan is re-checked (3 s cap, falls back to the cache) before starting, so an account that just upgraded is not cut off at the free limit.
  • Switching the panel to Camera mode picks the first camera instead of leaving Start greyed out.
  • The pre-roll countdown stays visible in the recorded tab, and the recording dock collapses when the pointer leaves it.

E2E

  • e2e/browser.ts runs every suite in Playwright Chromium or an installed Chrome, Edge or Brave (CAP_E2E_BROWSER, loaded over CDP Extensions.loadUnpacked).
  • Upload checks decode the uploaded recording: audio covers the whole video, the tail is not silent, and tab A/V offset is measured from flash and beep onsets.
  • camera and microphone start starts and stops webcam + mic recordings back to back (CAP_E2E_DEVICE_LOOPS) and checks a live preview, a live mic and moving, non-black camera frames in every upload. CAP_E2E_SYSTEM_DEVICES uses the OS camera and mic instead of Chromium's fake devices (a v4l2loopback camera and PulseAudio virtual mic on Linux).
  • A seeded fuzz test (CAP_E2E_FUZZ, CAP_E2E_FUZZ_SEED) interleaves start, stop, pause, resume, tab switches, reloads, service worker kills and offscreen document closes, and checks the worker always answers and a final recording uploads.
  • New coverage for the dock mute, Camera mode start, strict CSP pages, the quiet and silent mic check, the stale plan, and background tabs opened mid-recording.

RetriggerView in GreptileConfidence Score: 5/5

The changes since the previous review appear safe to merge.

Summary

This PR improves extension recording reliability and adds browser coverage for audio, microphones, cameras, and recorder controls.

Since the previous review:

  • Camera and microphone capture keep working when devices are busy or unavailable.
  • The recording dock mutes the live microphone and shows when it is active.
  • The recorded tab keeps the webcam preview while the in-page recorder panel returns.
  • Recording starts retry brief message gaps and use fresh plan data when available.
  • E2E runs in more Chrome-family browsers and checks uploaded recordings more closely.

The three earlier threads were manually resolved without explanatory replies. Their current changes were checked; no duplicate comments were added. None had a supplied Comment number.

No new actionable issues were found.

Reviews (3) · Last reviewed commit: "test(chrome-extension): reopen the panel..." · Reviewed by Greptile

…ices and cover background tabs during a recording
@vercel

vercel Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
cap-status Skipped Skipped Oct 29, 2026 6:59pm UTC

@vercel
vercel Bot temporarily deployed to Preview – cap-status October 29, 2026 18:24 Inactive
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

Comment thread apps/chrome-extension/src/content/overlay.tsx Outdated
Comment thread apps/chrome-extension/src/content/recording-bar.tsx Outdated
Comment thread apps/chrome-extension/e2e/recording-upload.spec.ts Outdated
@vercel
vercel Bot temporarily deployed to Preview – cap-status October 29, 2026 18:59 Inactive
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@richiemcilroy
richiemcilroy marked this pull request as ready for review October 29, 2026 19:33
@richiemcilroy
richiemcilroy merged commit c2e42c5 into main Oct 29, 2026
47 of 48 checks passed

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 1 security concern(s).

style={{ width: `${PANEL_WIDTH}px`, height: `${height}px` }}
>
<iframe
src={PANEL_SRC}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Embedded recorder panel leaks its registered token to the recorded page, defeating panel clickjacking protection

New panel iframe posts its registered access token to the host window, letting the recorded page reuse it to authorize its own popup.html iframe.

Stop sending the panel token to window.parent; use chrome.runtime for size/dismiss and bind panel auth to the sender tab/frame.

AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.

<file name="apps/chrome-extension/src/content/overlay.tsx">
<violation number="1" location="apps/chrome-extension/src/content/overlay.tsx:447">
<priority>P2</priority>
<title>Embedded recorder panel leaks its registered token to the recorded page, defeating panel clickjacking protection</title>
<evidence>This PR adds RecorderPanelOverlay, which registers a new PANEL_TOKEN with the service worker (overlay.tsx added lines 76-78 and 99-107) and embeds chrome-extension://&lt;id&gt;/popup.html#PANEL_TOKEN in a page iframe (added lines 446-451). The panel page posts its token to window.parent with targetOrigin "*" on mount/resize (popup/main.tsx lines 63-74, pre-existing but now reachable). The parent window is the recorded host page, so the host page's own scripts can listen for that message and read the bearer token. They can then create their own popup.html#&lt;stolen-token&gt; iframe: popup/main.tsx lines 228-243 validate only that the URL-hash token is registered, so the service worker returns valid and the page renders the fully authorized recorder UI. This bypasses the anti-clickjacking check documented at popup/main.tsx lines 96-99 ("only the extension overlay can do that"). The host page cannot script the cross-origin panel, but it can position and cover its own authorized copy, using UI redress to trick the user into starting/stopping recordings or changing camera/microphone settings, including granting screen or media capture. The base checkout registered only PREVIEW_TOKEN and had no RecorderPanelOverlay or popup.html iframe, and camera-preview messages use chrome.runtime rather than window.postMessage, so no equivalent panel-token exposure existed before this PR.</evidence>
<recommendation>Do not send PANEL_TOKEN to window.parent. Either remove the token from postPanelMessage and authenticate the message by event.source === the overlay's panel iframe.contentWindow (plus the extension-origin check), or route panel size/dismiss messages through chrome.runtime like the camera preview does. Additionally bind panel authorization to the embedding tab/frame registered by the content script rather than to a bearer token that a host page can copy, and add a test showing a host-page message listener can no longer obtain a reusable panel token.</recommendation>
</violation>
</file>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

View 1 other finding in Capy.

Open in Capy

This branch was successfully deployed

1 active and 1 inactive deployments
Preview – cap-web — 7dc45d84 Deployed Oct 29, 2026 by vercel[bot]
Preview – cap-status — 7dc45d84 Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant