@@ -392,31 +392,51 @@ async function handleMessage(message, sender) {
392
392
return { status : "fulfilled" , result } ;
393
393
}
394
394
case "API_XHR" : {
395
- // parse details and set up for XMLHttpRequest
395
+ // initializing an xhr instance
396
+ const xhr = new XMLHttpRequest ( ) ;
397
+ // establish a long-lived port connection to content script
398
+ const port = browser . tabs . connect ( sender . tab . id , {
399
+ name : message . xhrPortName ,
400
+ } ) ;
401
+ // receive messages from content script and process them
402
+ port . onMessage . addListener ( ( msg ) => {
403
+ if ( msg . name === "ABORT" ) xhr . abort ( ) ;
404
+ if ( msg . name === "DISCONNECT" ) port . disconnect ( ) ;
405
+ } ) ;
406
+ // handle port disconnect and clean tasks
407
+ port . onDisconnect . addListener ( ( p ) => {
408
+ if ( p ?. error ) {
409
+ console . error (
410
+ `port disconnected due to an error: ${ p . error . message } ` ,
411
+ ) ;
412
+ }
413
+ } ) ;
414
+ // parse details and set up for xhr instance
396
415
const details = message . details ;
397
- const method = details . method ? details . method : "GET" ;
416
+ const method = details . method || "GET" ;
398
417
const user = details . user || null ;
399
418
const password = details . password || null ;
400
419
let body = details . data || null ;
401
- if ( body != null && details . binary != null ) {
420
+ if ( typeof body === "string" && details . binary ) {
402
421
const len = body . length ;
403
422
const arr = new Uint8Array ( len ) ;
404
423
for ( let i = 0 ; i < len ; i ++ ) {
405
424
arr [ i ] = body . charCodeAt ( i ) ;
406
425
}
407
426
body = new Blob ( [ arr ] , { type : "text/plain" } ) ;
408
427
}
409
- // establish a long-lived port connection to content script
410
- const port = browser . tabs . connect ( sender . tab . id , {
411
- name : message . xhrPortName ,
412
- } ) ;
413
- // set up XMLHttpRequest
414
- const xhr = new XMLHttpRequest ( ) ;
415
- xhr . withCredentials = details . user && details . password ;
416
- xhr . timeout = details . timeout || 0 ;
417
- if ( details . overrideMimeType ) {
418
- xhr . overrideMimeType ( details . overrideMimeType ) ;
419
- }
428
+
429
+ // xhr instances automatically filter out unexpected user values
430
+ xhr . timeout = details . timeout ;
431
+ xhr . responseType = details . responseType ;
432
+ // record parsed values for subsequent use
433
+ const responseType = xhr . responseType ;
434
+ // avoid unexpected behavior of legacy defaults such as parsing XML
435
+ if ( responseType === "" ) xhr . responseType = "text" ;
436
+ // transfer to content script via arraybuffer and then parse to blob
437
+ if ( responseType === "blob" ) xhr . responseType = "arraybuffer" ;
438
+ // transfer to content script via text and then parse to document
439
+ if ( responseType === "document" ) xhr . responseType = "text" ;
420
440
// add required listeners and send result back to the content script
421
441
for ( const e of message . events ) {
422
442
if ( ! details [ e ] ) continue ;
@@ -428,12 +448,11 @@ async function handleMessage(message, sender) {
428
448
readyState : xhr . readyState ,
429
449
response : xhr . response ,
430
450
responseHeaders : xhr . getAllResponseHeaders ( ) ,
431
- responseType : xhr . responseType ,
451
+ responseType,
432
452
responseURL : xhr . responseURL ,
433
453
status : xhr . status ,
434
454
statusText : xhr . statusText ,
435
455
timeout : xhr . timeout ,
436
- withCredentials : xhr . withCredentials ,
437
456
} ;
438
457
// get content-type when headers received
439
458
if ( xhr . readyState >= xhr . HEADERS_RECEIVED ) {
@@ -442,45 +461,17 @@ async function handleMessage(message, sender) {
442
461
// only process when xhr is complete and data exist
443
462
if ( xhr . readyState === xhr . DONE && xhr . response !== null ) {
444
463
// need to convert arraybuffer data to postMessage
445
- if ( xhr . responseType === "arraybuffer" ) {
446
- /** @type {ArrayBuffer } */
464
+ if (
465
+ xhr . responseType === "arraybuffer" &&
466
+ xhr . response instanceof ArrayBuffer
467
+ ) {
447
468
const buffer = xhr . response ;
448
469
x . response = Array . from ( new Uint8Array ( buffer ) ) ;
449
470
}
450
471
}
451
472
port . postMessage ( { name : e , event, response : x } ) ;
452
473
} ;
453
474
}
454
- xhr . open ( method , details . url , true , user , password ) ;
455
- xhr . responseType = details . responseType ;
456
- // avoid unexpected behavior of legacy defaults such as parsing XML
457
- if ( xhr . responseType === "" ) xhr . responseType = "text" ;
458
- // transfer to content script via arraybuffer and then parse to blob
459
- if ( xhr . responseType === "blob" ) xhr . responseType = "arraybuffer" ;
460
- // transfer to content script via text and then parse to document
461
- if ( xhr . responseType === "document" ) xhr . responseType = "text" ;
462
- if ( details . headers ) {
463
- for ( const key in details . headers ) {
464
- if ( ! key . startsWith ( "Proxy-" ) && ! key . startsWith ( "Sec-" ) ) {
465
- const val = details . headers [ key ] ;
466
- xhr . setRequestHeader ( key , val ) ;
467
- }
468
- }
469
- }
470
- // receive messages from content script and process them
471
- port . onMessage . addListener ( ( msg ) => {
472
- if ( msg . name === "ABORT" ) xhr . abort ( ) ;
473
- if ( msg . name === "DISCONNECT" ) port . disconnect ( ) ;
474
- } ) ;
475
- // handle port disconnect and clean tasks
476
- port . onDisconnect . addListener ( ( p ) => {
477
- if ( p ?. error ) {
478
- console . error (
479
- `port disconnected due to an error: ${ p . error . message } ` ,
480
- ) ;
481
- }
482
- } ) ;
483
- xhr . send ( body ) ;
484
475
// if onloadend not set in xhr details
485
476
// onloadend event won't be passed to content script
486
477
// if that happens port DISCONNECT message won't be posted
@@ -490,6 +481,17 @@ async function handleMessage(message, sender) {
490
481
port . postMessage ( { name : "onloadend" , event } ) ;
491
482
} ;
492
483
}
484
+ if ( details . overrideMimeType ) {
485
+ xhr . overrideMimeType ( details . overrideMimeType ) ;
486
+ }
487
+ xhr . open ( method , details . url , true , user , password ) ;
488
+ // must set headers after `xhr.open()`, but before `xhr.send()`
489
+ if ( typeof details . headers === "object" ) {
490
+ for ( const [ key , val ] of Object . entries ( details . headers ) ) {
491
+ xhr . setRequestHeader ( key , val ) ;
492
+ }
493
+ }
494
+ xhr . send ( body ) ;
493
495
return { status : "fulfilled" } ;
494
496
}
495
497
case "REFRESH_DNR_RULES" : {
0 commit comments