@@ -10,7 +10,9 @@ import {
10
10
getPlatform ,
11
11
isAndroid ,
12
12
getDayPrecisionEpoch ,
13
- getDarkColorSchemeQuery
13
+ getDarkColorSchemeQuery ,
14
+ getExtensionDomain ,
15
+ getRandomInt
14
16
} from 'utils/common' ;
15
17
import {
16
18
targetEnv ,
@@ -477,6 +479,35 @@ async function getStartupState({event = ''} = {}) {
477
479
return startup ;
478
480
}
479
481
482
+ function getNetRequestRuleIds ( { count = 1 , reserved = false } = { } ) {
483
+ const ruleIds = [ ] ;
484
+
485
+ let minValue , maxValue ;
486
+ if ( reserved ) {
487
+ minValue = 1 ;
488
+ maxValue = 100_000_000 ;
489
+ } else {
490
+ minValue = 100_000_001 ;
491
+ maxValue = [ 'firefox' , 'safari' ] . includes ( targetEnv )
492
+ ? Number . MAX_SAFE_INTEGER
493
+ : 2_147_483_647 ;
494
+ }
495
+
496
+ while ( true ) {
497
+ const ruleId = getRandomInt ( minValue , maxValue ) ;
498
+
499
+ if ( ! ruleIds . includes ( ruleId ) ) {
500
+ ruleIds . push ( ruleId ) ;
501
+ }
502
+
503
+ if ( ruleIds . length === count ) {
504
+ break ;
505
+ }
506
+ }
507
+
508
+ return ruleIds ;
509
+ }
510
+
480
511
function getEngineMenuIcon ( engine , { variant = '' } = { } ) {
481
512
engine = engineIconAlias [ engine ] || engine ;
482
513
@@ -525,40 +556,83 @@ async function isContextMenuSupported() {
525
556
}
526
557
527
558
async function checkSearchEngineAccess ( ) {
528
- // Check if search engine access is enabled in Opera
529
- if ( ! mv3 && / o p r \/ / i. test ( navigator . userAgent ) ) {
559
+ if ( / o p r \/ / i. test ( navigator . userAgent ) ) {
560
+ // Check if the "Allow access to search page results" option
561
+ // is enabled in Opera. Host access is needed for redirecting a request
562
+ // with declarativeNetRequest (but not for blocking), so a request is made
563
+ // with manual redirection. If host access has been granted, the request
564
+ // does not reach the server and the response type will be 'opaqueredirect'.
565
+
530
566
const { lastEngineAccessCheck} = await storage . get ( 'lastEngineAccessCheck' ) ;
531
567
// run at most once a week
532
568
if ( Date . now ( ) - lastEngineAccessCheck > 604800000 ) {
533
569
await storage . set ( { lastEngineAccessCheck : Date . now ( ) } ) ;
534
570
535
571
const url = 'https://www.google.com/generate_204' ;
536
572
537
- const hasAccess = await new Promise ( resolve => {
538
- let access = false ;
539
-
540
- function requestCallback ( ) {
541
- access = true ;
542
- removeCallback ( ) ;
543
- return { cancel : true } ;
573
+ let hasAccess = false ;
574
+
575
+ if ( mv3 ) {
576
+ const ruleIds = getNetRequestRuleIds ( { reserved : true } ) ;
577
+
578
+ await browser . declarativeNetRequest . updateSessionRules ( {
579
+ removeRuleIds : ruleIds ,
580
+ addRules : [
581
+ {
582
+ id : ruleIds [ 0 ] ,
583
+ action : {
584
+ type : 'redirect' ,
585
+ redirect : {
586
+ transform : { path : '' }
587
+ }
588
+ } ,
589
+ condition : {
590
+ urlFilter : url ,
591
+ initiatorDomains : [ getExtensionDomain ( ) ] ,
592
+ resourceTypes : [ 'xmlhttprequest' ]
593
+ }
594
+ }
595
+ ]
596
+ } ) ;
597
+
598
+ const rsp = await fetch ( url , {
599
+ redirect : 'manual' ,
600
+ credentials : 'omit'
601
+ } ) . catch ( err => null ) ;
602
+
603
+ if ( rsp && rsp . type === 'opaqueredirect' ) {
604
+ hasAccess = true ;
544
605
}
545
606
546
- const removeCallback = function ( ) {
547
- window . clearTimeout ( timeoutId ) ;
548
- browser . webRequest . onBeforeRequest . removeListener ( requestCallback ) ;
549
-
550
- resolve ( access ) ;
551
- } ;
552
- const timeoutId = window . setTimeout ( removeCallback , 3000 ) ; // 3 seconds
553
-
554
- browser . webRequest . onBeforeRequest . addListener (
555
- requestCallback ,
556
- { urls : [ url ] , types : [ 'xmlhttprequest' ] } ,
557
- [ 'blocking' ]
558
- ) ;
559
-
560
- fetch ( url ) . catch ( err => null ) ;
561
- } ) ;
607
+ await browser . declarativeNetRequest . updateSessionRules ( {
608
+ removeRuleIds : ruleIds
609
+ } ) ;
610
+ } else {
611
+ await new Promise ( resolve => {
612
+ function requestCallback ( ) {
613
+ hasAccess = true ;
614
+ removeCallback ( ) ;
615
+
616
+ return { cancel : true } ;
617
+ }
618
+
619
+ const removeCallback = function ( ) {
620
+ self . clearTimeout ( timeoutId ) ;
621
+ browser . webRequest . onBeforeRequest . removeListener ( requestCallback ) ;
622
+
623
+ resolve ( ) ;
624
+ } ;
625
+ const timeoutId = self . setTimeout ( removeCallback , 3000 ) ; // 3 seconds
626
+
627
+ browser . webRequest . onBeforeRequest . addListener (
628
+ requestCallback ,
629
+ { urls : [ url ] , types : [ 'xmlhttprequest' ] } ,
630
+ [ 'blocking' ]
631
+ ) ;
632
+
633
+ fetch ( url ) . catch ( err => null ) ;
634
+ } ) ;
635
+ }
562
636
563
637
if ( ! hasAccess ) {
564
638
await showNotification ( { messageId : 'error_noSearchEngineAccess' } ) ;
@@ -627,6 +701,7 @@ export {
627
701
isSessionStartup ,
628
702
isStartup ,
629
703
getStartupState ,
704
+ getNetRequestRuleIds ,
630
705
getEngineIcon ,
631
706
getEngineMenuIcon ,
632
707
handleActionEscapeKey ,
0 commit comments