@@ -55,6 +55,21 @@ const cancelledByUs = new Set<number>();
5555const AUTHUSER_CANDIDATES = [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] ;
5656const CLASSROOM_URL_PATTERN = / ^ h t t p s : \/ \/ c l a s s r o o m \. g o o g l e \. c o m \/ / ;
5757
58+ /**
59+ * Detect if running in Firefox.
60+ * Firefox's downloads API doesn't pass auth cookies for cross-origin requests,
61+ * so we need to use bypass tab method instead.
62+ */
63+ function isFirefox ( ) : boolean {
64+ if ( typeof navigator === 'undefined' ) {
65+ console . log ( '[CQD:BG] isFirefox check: navigator is undefined' ) ;
66+ return false ;
67+ }
68+ const isFF = / F i r e f o x / i. test ( navigator . userAgent ) ;
69+ console . log ( '[CQD:BG] isFirefox check:' , { isFF, userAgent : navigator . userAgent } ) ;
70+ return isFF ;
71+ }
72+
5873/* ---------------------------------------------
5974 * Icon / tab context helpers
6075 * -------------------------------------------*/
@@ -81,10 +96,18 @@ const GRAY_ICON_PATHS: Record<number, string> = {
8196} ;
8297
8398function setActionIcon ( tabId : number , classroom : boolean ) {
84- if ( typeof chrome === 'undefined' || ! chrome . action ?. setIcon ) return ;
99+ if ( typeof chrome === 'undefined' ) return ;
100+
85101 const path = classroom ? COLOR_ICON_PATHS : GRAY_ICON_PATHS ;
102+
103+ // Firefox MV2 uses browserAction, Chrome MV3 uses action
104+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105+ const actionApi = ( chrome as any ) . action || ( chrome as any ) . browserAction ;
106+
107+ if ( ! actionApi ?. setIcon ) return ;
108+
86109 try {
87- chrome . action . setIcon ( { tabId, path } ) ;
110+ actionApi . setIcon ( { tabId, path } ) ;
88111 } catch {
89112 /* ignore */
90113 }
@@ -170,7 +193,9 @@ export default defineBackground(() => {
170193 } ) ;
171194
172195 // --- Icon Logic ---
173- if ( typeof chrome !== 'undefined' && chrome . tabs && chrome . action ) {
196+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
197+ const hasActionApi = ( chrome as any ) . action || ( chrome as any ) . browserAction ;
198+ if ( typeof chrome !== 'undefined' && chrome . tabs && hasActionApi ) {
174199 try {
175200 chrome . tabs . query ( { active : true , currentWindow : true } , ( tabs ) => {
176201 if ( tabs [ 0 ] ?. id != null ) updateIconForTab ( tabs [ 0 ] . id , tabs [ 0 ] . url ) ;
@@ -205,14 +230,15 @@ export default defineBackground(() => {
205230 * 1) Messages from drive_bypass.content.ts (Drive tab)
206231 * -----------------------------------------------------*/
207232 chrome . runtime . onMessage . addListener ( ( message , sender ) => {
208- if ( ! message || ! sender . tab || sender . tab . id == null ) return ;
233+ // Firefox fix: explicitly return false if we won't handle this message
234+ if ( ! message || ! sender . tab || sender . tab . id == null ) return false ;
209235
210236 const tabId = sender . tab . id ;
211237 const pending = pendingByBypassTabId . get ( tabId ) ;
212238
213239 // Not related to any bypass tab we're tracking
214240 if ( ! pending && typeof message . type === 'string' && message . type . startsWith ( 'CQD_' ) ) {
215- return ;
241+ return false ;
216242 }
217243
218244 // A) SUCCESS: Drive tab clicked Download / Download anyway
@@ -274,7 +300,9 @@ export default defineBackground(() => {
274300 /* -------------------------------------------------------
275301 * 2) onDeterminingFilename - SELF HEALING LOGIC ADDED
276302 * -----------------------------------------------------*/
277- chrome . downloads . onDeterminingFilename . addListener ( ( item , suggest ) => {
303+ // Firefox does not support onDeterminingFilename, so we guard this.
304+ if ( chrome . downloads && chrome . downloads . onDeterminingFilename ) {
305+ chrome . downloads . onDeterminingFilename . addListener ( ( item , suggest ) => {
278306 // 1. Try finding by ID first
279307 let pending = pendingByDownloadId . get ( item . id ) ;
280308
@@ -355,6 +383,7 @@ export default defineBackground(() => {
355383 suggest ( { conflictAction : 'uniquify' } ) ;
356384 }
357385 } ) ;
386+ }
358387
359388 /* -------------------------------------------------------
360389 * 3) onChanged: ANALYTICS TRIGGER
@@ -409,20 +438,26 @@ export default defineBackground(() => {
409438 * 4) CQD_DOWNLOAD HANDLER
410439 * -----------------------------------------------------*/
411440 chrome . runtime . onMessage . addListener ( ( message , sender , sendResponse ) => {
412- if ( ! message || message . type !== 'CQD_DOWNLOAD' ) return ;
441+ // Firefox fix: Only handle CQD_DOWNLOAD messages in this listener
442+ if ( ! message || message . type !== 'CQD_DOWNLOAD' ) return false ;
443+
444+ console . log ( '[CQD:BG] Received CQD_DOWNLOAD message:' , { url : message . url , requestId : message . requestId } ) ;
413445
414446 const rawUrl = message . url as string | undefined ;
415447 const fileMeta = message . fileMeta as FileMetaMsg | undefined ;
416448 const requestId = message . requestId || `req-${ Date . now ( ) } ` ;
417449
418450 if ( ! rawUrl ) {
451+ console . log ( '[CQD:BG] No rawUrl provided, sending error response' ) ;
419452 sendResponse ?.( {
420453 started : false ,
421454 userMessage : 'No valid link found.' ,
422455 } ) ;
423- return ;
456+ return true ; // Firefox: must return true even for sync responses
424457 }
425458
459+ console . log ( '[CQD:BG] Processing URL:' , rawUrl ) ;
460+
426461 const { baseUrl, isDrive } = normalizeUrl ( rawUrl ) ;
427462 const initialAuthUser = isDrive
428463 ? extractAuthUserFromUrl ( rawUrl )
@@ -459,20 +494,40 @@ export default defineBackground(() => {
459494 } ;
460495
461496 if ( isDrive ) {
497+ // FIREFOX WORKAROUND: Firefox's downloads API doesn't pass authentication
498+ // cookies for cross-origin requests (like Drive). Skip directly to bypass tab.
499+ if ( isFirefox ( ) ) {
500+ console . log ( '[CQD:BG] Firefox detected - using bypass tab for Drive download' ) ;
501+ pending . fallbackStarted = true ;
502+ openDriveBypassTab ( pending , pending . baseUrl ) ;
503+ respondOnce ( {
504+ started : true ,
505+ requestId,
506+ userMessage : 'Opening Drive tab for download…' ,
507+ } ) ;
508+ return true ;
509+ }
510+
462511 const firstUrl =
463512 typeof pending . currentAuthUser === 'number'
464513 ? buildUrlWithAuthUser ( pending . baseUrl , pending . currentAuthUser )
465514 : pending . baseUrl ;
466515
516+ console . log ( '[CQD:BG] Starting Drive download:' , { firstUrl, isDrive : true } ) ;
517+
467518 chrome . downloads . download (
468519 {
469520 url : firstUrl ,
470521 saveAs : false ,
471522 conflictAction : 'uniquify' ,
472523 } ,
473524 ( id ) => {
525+ console . log ( '[CQD:BG] Download callback:' , { id, lastError : chrome . runtime . lastError ?. message } ) ;
526+
474527 if ( chrome . runtime . lastError || ! id ) {
475528 // download could not even start
529+ console . log ( '[CQD:BG] Download failed to start:' , chrome . runtime . lastError ?. message ) ;
530+
476531 recordDownloadEvent ( {
477532 type : pending . fileMeta ?. ext || 'unknown' ,
478533 status : 'fail' ,
@@ -498,6 +553,7 @@ export default defineBackground(() => {
498553 return ;
499554 }
500555
556+ console . log ( '[CQD:BG] Download started successfully:' , { id, requestId } ) ;
501557 pending . currentDownloadId = id ;
502558 pendingByDownloadId . set ( id , pending ) ;
503559 respondOnce ( { started : true , requestId, downloadId : id } ) ;
@@ -519,15 +575,21 @@ function startSingleAttempt(
519575 pending : PendingDownload ,
520576 respondOnce ?: ( payload : any ) => void ,
521577) {
578+ console . log ( '[CQD:BG] startSingleAttempt (non-Drive):' , { url : pending . baseUrl } ) ;
579+
522580 chrome . downloads . download (
523581 {
524582 url : pending . baseUrl ,
525583 saveAs : false ,
526584 conflictAction : 'uniquify' ,
527585 } ,
528586 ( downloadId ) => {
587+ console . log ( '[CQD:BG] Non-Drive download callback:' , { downloadId, lastError : chrome . runtime . lastError ?. message } ) ;
588+
529589 if ( chrome . runtime . lastError || ! downloadId ) {
530590 // could not start a direct download at all
591+ console . log ( '[CQD:BG] Non-Drive download failed:' , chrome . runtime . lastError ?. message ) ;
592+
531593 recordDownloadEvent ( {
532594 type : pending . fileMeta ?. ext || 'unknown' ,
533595 status : 'fail' ,
0 commit comments