1+ import { EXTENSION_PROTOCOL } from "../platform/extension-protocol" ;
12import {
23 ApiRequestError ,
34 createAuthStart ,
@@ -56,14 +57,14 @@ import type {
5657 ServiceWorkerRequest ,
5758 ServiceWorkerResponse ,
5859} from "../shared/types" ;
60+ import { ensureRecorderHost , hasRecorderHost } from "./recorder-host" ;
5961
6062// popup.html is web-accessible with use_dynamic_url so sites cannot fingerprint
6163// the extension via the overlay iframe's static URL; that same flag makes its
6264// static chrome-extension:// URL fail with ERR_BLOCKED_BY_CLIENT when opened as
6365// a window. The standalone fallback therefore loads a privileged twin that is
6466// not in web_accessible_resources.
6567const POPUP_URL = "popup-window.html" ;
66- const OFFSCREEN_URL = "offscreen.html" ;
6768const AUTH_TIMEOUT_MS = 10 * 60 * 1000 ;
6869const OFFSCREEN_MESSAGE_ATTEMPTS = 3 ;
6970const OFFSCREEN_MESSAGE_RETRY_DELAY_MS = 75 ;
@@ -81,7 +82,7 @@ let mediaPermissionsCache: MediaPermissionSnapshot = {
8182let uploadProgressTabId : number | null = null ;
8283let activePreviewTabId : number | null = null ;
8384let pendingPreviewTabId : number | null = null ;
84- let offscreenDocumentCreation : Promise < void > | null = null ;
85+ let readyPreviewTabId : number | null = null ;
8586let browserWindowFocused = true ;
8687let externalCaptureAutoPipPending = false ;
8788let recordingStartInFlight : Promise < OffscreenResponse > | null = null ;
@@ -188,58 +189,6 @@ const focusTab = async (tabId: number) => {
188189 await activateTab ( tabId ) ;
189190} ;
190191
191- const getOffscreenDocumentContexts = async ( ) => {
192- const offscreenUrl = chrome . runtime . getURL ( OFFSCREEN_URL ) ;
193- return new Promise < Array < { documentUrl ?: string } > > ( ( resolve ) => {
194- chrome . runtime . getContexts (
195- {
196- contextTypes : [ chrome . runtime . ContextType . OFFSCREEN_DOCUMENT ] ,
197- documentUrls : [ offscreenUrl ] ,
198- } ,
199- ( contexts ) => resolve ( contexts ) ,
200- ) ;
201- } ) ;
202- } ;
203-
204- const hasOffscreenDocument = async ( ) =>
205- ( await getOffscreenDocumentContexts ( ) ) . length > 0 ;
206-
207- const createOffscreenDocument = ( ) =>
208- new Promise < void > ( ( resolve , reject ) => {
209- chrome . offscreen . createDocument (
210- {
211- url : OFFSCREEN_URL ,
212- reasons : [ "USER_MEDIA" , "DISPLAY_MEDIA" , "BLOBS" , "AUDIO_PLAYBACK" ] ,
213- justification : "Record and upload Cap videos from an extension page." ,
214- } ,
215- ( ) => {
216- const error = chrome . runtime . lastError ;
217- if ( ! error ) {
218- resolve ( ) ;
219- return ;
220- }
221-
222- const message = error . message ?? "Failed to create offscreen document" ;
223- if ( message . toLowerCase ( ) . includes ( "single offscreen document" ) ) {
224- resolve ( ) ;
225- return ;
226- }
227-
228- reject ( new Error ( message ) ) ;
229- } ,
230- ) ;
231- } ) ;
232-
233- const ensureOffscreenDocument = async ( ) => {
234- const contexts = await getOffscreenDocumentContexts ( ) ;
235- if ( contexts . length > 0 ) return ;
236-
237- offscreenDocumentCreation ??= createOffscreenDocument ( ) . finally ( ( ) => {
238- offscreenDocumentCreation = null ;
239- } ) ;
240- await offscreenDocumentCreation ;
241- } ;
242-
243192const wait = ( durationMs : number ) =>
244193 new Promise < void > ( ( resolve ) => {
245194 globalThis . setTimeout ( resolve , durationMs ) ;
@@ -270,12 +219,12 @@ const sendOffscreen = async (
270219 options : { createIfMissing ?: boolean } = { } ,
271220) => {
272221 if ( options . createIfMissing === false ) {
273- const hasDocument = await hasOffscreenDocument ( ) ;
222+ const hasDocument = await hasRecorderHost ( ) ;
274223 if ( ! hasDocument ) {
275224 return { ok : true , status : recordingStatus } satisfies OffscreenResponse ;
276225 }
277226 } else {
278- await ensureOffscreenDocument ( ) ;
227+ await ensureRecorderHost ( ) ;
279228 }
280229
281230 let lastError : unknown ;
@@ -292,7 +241,7 @@ const sendOffscreen = async (
292241 break ;
293242 }
294243 await wait ( OFFSCREEN_MESSAGE_RETRY_DELAY_MS ) ;
295- await ensureOffscreenDocument ( ) ;
244+ await ensureRecorderHost ( ) ;
296245 }
297246 }
298247
@@ -372,7 +321,7 @@ const canInjectIntoTab = (tab: chrome.tabs.Tab) => {
372321const isWebPageSender = ( sender : chrome . runtime . MessageSender ) => {
373322 if ( ! sender . tab ) return false ;
374323 const senderUrl = sender . url ?? "" ;
375- return ! senderUrl . startsWith ( "chrome-extension:" ) ;
324+ return ! senderUrl . startsWith ( EXTENSION_PROTOCOL ) ;
376325} ;
377326
378327// camera-preview.html is web accessible, so any site can load it in an
@@ -387,7 +336,7 @@ const isCameraPreviewRequestAllowed = async (
387336 if ( ! ( await isOverlayTokenRegistered ( token ) ) ) return false ;
388337
389338 const senderUrl = sender . url ?? "" ;
390- if ( senderUrl . startsWith ( "chrome-extension:" ) ) {
339+ if ( senderUrl . startsWith ( EXTENSION_PROTOCOL ) ) {
391340 // The camera preview document is the only extension page that drives
392341 // the camera.
393342 try {
@@ -412,7 +361,7 @@ const isCameraPreviewEventAllowed = async (
412361) => {
413362 if ( ! token || ! ( await isOverlayTokenRegistered ( token ) ) ) return false ;
414363 const senderUrl = sender . url ?? "" ;
415- if ( ! senderUrl . startsWith ( "chrome-extension:" ) ) return false ;
364+ if ( ! senderUrl . startsWith ( EXTENSION_PROTOCOL ) ) return false ;
416365 try {
417366 return new URL ( senderUrl ) . pathname === "/camera-preview.html" ;
418367 } catch {
@@ -1244,7 +1193,7 @@ const forwardToOffscreen = (type: OffscreenRequest["type"]) =>
12441193 sendOffscreen ( { target : "offscreen" , type } as OffscreenRequest ) ;
12451194
12461195const syncRecordingStatus = async ( ) => {
1247- const hasDocument = await hasOffscreenDocument ( ) ;
1196+ const hasDocument = await hasRecorderHost ( ) ;
12481197 if ( ! hasDocument ) {
12491198 if (
12501199 isActiveRecordingStatus ( recordingStatus ) ||
0 commit comments