Skip to content

Commit 44c2eee

Browse files
committed
fix: check search engine access in MV3
1 parent 292d584 commit 44c2eee

File tree

2 files changed

+119
-26
lines changed

2 files changed

+119
-26
lines changed

src/utils/app.js

Lines changed: 101 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
getPlatform,
1111
isAndroid,
1212
getDayPrecisionEpoch,
13-
getDarkColorSchemeQuery
13+
getDarkColorSchemeQuery,
14+
getExtensionDomain,
15+
getRandomInt
1416
} from 'utils/common';
1517
import {
1618
targetEnv,
@@ -477,6 +479,35 @@ async function getStartupState({event = ''} = {}) {
477479
return startup;
478480
}
479481

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+
480511
function getEngineMenuIcon(engine, {variant = ''} = {}) {
481512
engine = engineIconAlias[engine] || engine;
482513

@@ -525,40 +556,83 @@ async function isContextMenuSupported() {
525556
}
526557

527558
async function checkSearchEngineAccess() {
528-
// Check if search engine access is enabled in Opera
529-
if (!mv3 && / opr\//i.test(navigator.userAgent)) {
559+
if (/ opr\//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+
530566
const {lastEngineAccessCheck} = await storage.get('lastEngineAccessCheck');
531567
// run at most once a week
532568
if (Date.now() - lastEngineAccessCheck > 604800000) {
533569
await storage.set({lastEngineAccessCheck: Date.now()});
534570

535571
const url = 'https://www.google.com/generate_204';
536572

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;
544605
}
545606

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+
}
562636

563637
if (!hasAccess) {
564638
await showNotification({messageId: 'error_noSearchEngineAccess'});
@@ -627,6 +701,7 @@ export {
627701
isSessionStartup,
628702
isStartup,
629703
getStartupState,
704+
getNetRequestRuleIds,
630705
getEngineIcon,
631706
getEngineMenuIcon,
632707
handleActionEscapeKey,

src/utils/common.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,22 @@ function isBackgroundPageContext() {
330330
return self.location.href === backgroundUrl;
331331
}
332332

333+
function getExtensionDomain() {
334+
try {
335+
const {hostname} = new URL(
336+
browser.runtime.getURL('/src/background/script.js')
337+
);
338+
339+
return hostname;
340+
} catch (err) {}
341+
342+
return null;
343+
}
344+
345+
function getRandomInt(min, max) {
346+
return Math.floor(Math.random() * (max - min + 1)) + min;
347+
}
348+
333349
function querySelectorXpath(selector, {rootNode = null} = {}) {
334350
rootNode = rootNode || document;
335351

@@ -522,6 +538,8 @@ export {
522538
getDarkColorSchemeQuery,
523539
getDayPrecisionEpoch,
524540
isBackgroundPageContext,
541+
getExtensionDomain,
542+
getRandomInt,
525543
querySelectorXpath,
526544
nodeQuerySelector,
527545
findNode,

0 commit comments

Comments
 (0)