@@ -21,14 +21,6 @@ function userscriptSort(a, b) {
21
21
return Number ( a . scriptObject . weight ) < Number ( b . scriptObject . weight ) ;
22
22
}
23
23
24
- async function readAsDataURL ( blob ) {
25
- return new Promise ( ( resolve ) => {
26
- const reader = new FileReader ( ) ;
27
- reader . readAsDataURL ( blob ) ;
28
- reader . onloadend = ( ) => resolve ( reader . result ) ; // base64data
29
- } ) ;
30
- }
31
-
32
24
async function getPlatform ( ) {
33
25
let platform = localStorage . getItem ( "platform" ) ;
34
26
if ( ! platform ) {
@@ -400,95 +392,82 @@ async function handleMessage(message, sender) {
400
392
return { status : "fulfilled" , result } ;
401
393
}
402
394
case "API_XHR" : {
403
- // parse details and set up for XMLHttpRequest
404
- const details = message . details ;
405
- const method = details . method ? details . method : "GET" ;
406
- const user = details . user || null ;
407
- const password = details . password || null ;
408
- let body = details . data || null ;
409
- if ( body != null && details . binary != null ) {
410
- const len = body . length ;
411
- const arr = new Uint8Array ( len ) ;
412
- for ( let i = 0 ; i < len ; i ++ ) {
413
- arr [ i ] = body . charCodeAt ( i ) ;
414
- }
415
- body = new Blob ( [ arr ] , { type : "text/plain" } ) ;
416
- }
395
+ // initializing an xhr instance
396
+ const xhr = new XMLHttpRequest ( ) ;
417
397
// establish a long-lived port connection to content script
418
398
const port = browser . tabs . connect ( sender . tab . id , {
419
399
name : message . xhrPortName ,
420
400
} ) ;
421
- // set up XMLHttpRequest
422
- const xhr = new XMLHttpRequest ( ) ;
423
- xhr . withCredentials = details . user && details . password ;
424
- xhr . timeout = details . timeout || 0 ;
425
- if ( details . overrideMimeType ) {
426
- xhr . overrideMimeType ( details . overrideMimeType ) ;
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
415
+ const details = message . details ;
416
+ const method = details . method || "GET" ;
417
+ const user = details . user || null ;
418
+ const password = details . password || null ;
419
+ let body = details . data || null ;
420
+ // deprecate once body supports more data types
421
+ // the `binary` key will no longer needed
422
+ if ( typeof body === "string" && details . binary ) {
423
+ body = new TextEncoder ( ) . encode ( body ) ;
427
424
}
425
+ // xhr instances automatically filter out unexpected user values
426
+ xhr . timeout = details . timeout ;
427
+ xhr . responseType = details . responseType ;
428
+ // record parsed values for subsequent use
429
+ const responseType = xhr . responseType ;
430
+ // avoid unexpected behavior of legacy defaults such as parsing XML
431
+ if ( responseType === "" ) xhr . responseType = "text" ;
432
+ // transfer to content script via arraybuffer and then parse to blob
433
+ if ( responseType === "blob" ) xhr . responseType = "arraybuffer" ;
434
+ // transfer to content script via text and then parse to document
435
+ if ( responseType === "document" ) xhr . responseType = "text" ;
428
436
// add required listeners and send result back to the content script
429
437
for ( const e of message . events ) {
430
438
if ( ! details [ e ] ) continue ;
431
439
xhr [ e ] = async ( event ) => {
432
440
// can not send xhr through postMessage
433
441
// construct new object to be sent as "response"
434
442
const x = {
443
+ contentType : undefined , // non-standard
435
444
readyState : xhr . readyState ,
436
445
response : xhr . response ,
437
446
responseHeaders : xhr . getAllResponseHeaders ( ) ,
438
- responseType : xhr . responseType ,
447
+ responseType,
439
448
responseURL : xhr . responseURL ,
440
449
status : xhr . status ,
441
450
statusText : xhr . statusText ,
442
451
timeout : xhr . timeout ,
443
- withCredentials : xhr . withCredentials ,
444
452
} ;
445
- // only include responseText when needed
446
- if ( [ "" , "text" ] . indexOf ( xhr . responseType ) !== - 1 ) {
447
- x . responseText = xhr . responseText ;
453
+ // get content-type when headers received
454
+ if ( xhr . readyState >= xhr . HEADERS_RECEIVED ) {
455
+ x . contentType = xhr . getResponseHeader ( "Content-Type" ) ;
448
456
}
449
457
// only process when xhr is complete and data exist
450
- if ( xhr . readyState === 4 && xhr . response !== null ) {
458
+ if ( xhr . readyState === xhr . DONE && xhr . response !== null ) {
451
459
// need to convert arraybuffer data to postMessage
452
- if ( xhr . responseType === "arraybuffer" ) {
453
- const arr = Array . from ( new Uint8Array ( xhr . response ) ) ;
454
- x . response = arr ;
455
- }
456
- // need to convert blob data to postMessage
457
- if ( xhr . responseType === "blob" ) {
458
- const base64data = await readAsDataURL ( xhr . response ) ;
459
- x . response = {
460
- data : base64data ,
461
- type : xhr . responseType ,
462
- } ;
460
+ if (
461
+ xhr . responseType === "arraybuffer" &&
462
+ xhr . response instanceof ArrayBuffer
463
+ ) {
464
+ const buffer = xhr . response ;
465
+ x . response = Array . from ( new Uint8Array ( buffer ) ) ;
463
466
}
464
467
}
465
468
port . postMessage ( { name : e , event, response : x } ) ;
466
469
} ;
467
470
}
468
- xhr . open ( method , details . url , true , user , password ) ;
469
- xhr . responseType = details . responseType || "" ;
470
- if ( details . headers ) {
471
- for ( const key in details . headers ) {
472
- if ( ! key . startsWith ( "Proxy-" ) && ! key . startsWith ( "Sec-" ) ) {
473
- const val = details . headers [ key ] ;
474
- xhr . setRequestHeader ( key , val ) ;
475
- }
476
- }
477
- }
478
- // receive messages from content script and process them
479
- port . onMessage . addListener ( ( msg ) => {
480
- if ( msg . name === "ABORT" ) xhr . abort ( ) ;
481
- if ( msg . name === "DISCONNECT" ) port . disconnect ( ) ;
482
- } ) ;
483
- // handle port disconnect and clean tasks
484
- port . onDisconnect . addListener ( ( p ) => {
485
- if ( p ?. error ) {
486
- console . error (
487
- `port disconnected due to an error: ${ p . error . message } ` ,
488
- ) ;
489
- }
490
- } ) ;
491
- xhr . send ( body ) ;
492
471
// if onloadend not set in xhr details
493
472
// onloadend event won't be passed to content script
494
473
// if that happens port DISCONNECT message won't be posted
@@ -498,6 +477,17 @@ async function handleMessage(message, sender) {
498
477
port . postMessage ( { name : "onloadend" , event } ) ;
499
478
} ;
500
479
}
480
+ if ( details . overrideMimeType ) {
481
+ xhr . overrideMimeType ( details . overrideMimeType ) ;
482
+ }
483
+ xhr . open ( method , details . url , true , user , password ) ;
484
+ // must set headers after `xhr.open()`, but before `xhr.send()`
485
+ if ( typeof details . headers === "object" ) {
486
+ for ( const [ key , val ] of Object . entries ( details . headers ) ) {
487
+ xhr . setRequestHeader ( key , val ) ;
488
+ }
489
+ }
490
+ xhr . send ( body ) ;
501
491
return { status : "fulfilled" } ;
502
492
}
503
493
case "REFRESH_DNR_RULES" : {
0 commit comments