mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 01:02:08 +01:00
[mv3] Add support for admin configurations
See `managed_storage.json` for available settings. Currently only `noFiltering` setting is availale. `noFiltering` is an array of strings, each being a domain for which no filtering should occur. Related discussion: - https://github.com/uBlockOrigin/uBOL-issues/discussions/35
This commit is contained in:
parent
0541ddbcfd
commit
d19e62a595
9 changed files with 362 additions and 375 deletions
|
@ -38,6 +38,9 @@
|
|||
"storage"
|
||||
],
|
||||
"short_name": "uBO Lite",
|
||||
"storage": {
|
||||
"managed_schema": "managed_storage.json"
|
||||
},
|
||||
"version": "1.0",
|
||||
"web_accessible_resources": []
|
||||
}
|
||||
|
|
|
@ -34,9 +34,7 @@ import {
|
|||
} from './ext.js';
|
||||
|
||||
import {
|
||||
CURRENT_CONFIG_BASE_RULE_ID,
|
||||
getRulesetDetails,
|
||||
getDynamicRules,
|
||||
defaultRulesetsFromLanguage,
|
||||
enableRulesets,
|
||||
getEnabledRulesetsDetails,
|
||||
|
@ -70,6 +68,7 @@ const rulesetConfig = {
|
|||
const UBOL_ORIGIN = runtime.getURL('').replace(/\/$/, '');
|
||||
|
||||
let firstRun = false;
|
||||
let wakeupRun = false;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
@ -83,53 +82,23 @@ async function loadRulesetConfig() {
|
|||
rulesetConfig.version = data.version;
|
||||
rulesetConfig.enabledRulesets = data.enabledRulesets;
|
||||
rulesetConfig.autoReload = data.autoReload;
|
||||
return false;
|
||||
wakeupRun = true;
|
||||
return;
|
||||
}
|
||||
data = await localRead('rulesetConfig');
|
||||
if ( data ) {
|
||||
rulesetConfig.version = data.version;
|
||||
rulesetConfig.enabledRulesets = data.enabledRulesets;
|
||||
rulesetConfig.autoReload = data.autoReload;
|
||||
return false;
|
||||
}
|
||||
data = await loadRulesetConfig.convertLegacyStorage();
|
||||
if ( data ) {
|
||||
rulesetConfig.version = data.version;
|
||||
rulesetConfig.enabledRulesets = data.enabledRulesets;
|
||||
rulesetConfig.autoReload = data.autoReload;
|
||||
return false;
|
||||
sessionWrite('rulesetConfig', rulesetConfig);
|
||||
return;
|
||||
}
|
||||
rulesetConfig.enabledRulesets = await defaultRulesetsFromLanguage();
|
||||
sessionWrite('rulesetConfig', rulesetConfig);
|
||||
localWrite('rulesetConfig', rulesetConfig);
|
||||
return true;
|
||||
firstRun = true;
|
||||
}
|
||||
|
||||
// TODO: To remove after next stable release is widespread (2023-06-04)
|
||||
loadRulesetConfig.convertLegacyStorage = async function() {
|
||||
const dynamicRuleMap = await getDynamicRules();
|
||||
const configRule = dynamicRuleMap.get(CURRENT_CONFIG_BASE_RULE_ID);
|
||||
if ( configRule === undefined ) { return; }
|
||||
let rawConfig;
|
||||
try {
|
||||
rawConfig = JSON.parse(self.atob(configRule.condition.urlFilter));
|
||||
} catch(ex) {
|
||||
return;
|
||||
}
|
||||
if ( rawConfig === undefined ) { return; }
|
||||
const config = {
|
||||
version: rawConfig[0],
|
||||
enabledRulesets: rawConfig[1],
|
||||
autoReload: rawConfig[2],
|
||||
};
|
||||
localWrite('rulesetConfig', config);
|
||||
sessionWrite('rulesetConfig', config);
|
||||
dnr.updateDynamicRules({
|
||||
removeRuleIds: [ CURRENT_CONFIG_BASE_RULE_ID ],
|
||||
});
|
||||
return config;
|
||||
};
|
||||
|
||||
async function saveRulesetConfig() {
|
||||
sessionWrite('rulesetConfig', rulesetConfig);
|
||||
return localWrite('rulesetConfig', rulesetConfig);
|
||||
|
@ -153,12 +122,13 @@ function hasOmnipotence() {
|
|||
async function onPermissionsRemoved() {
|
||||
const beforeMode = await getDefaultFilteringMode();
|
||||
const modified = await syncWithBrowserPermissions();
|
||||
if ( modified === false ) { return; }
|
||||
if ( modified === false ) { return false; }
|
||||
const afterMode = await getDefaultFilteringMode();
|
||||
if ( beforeMode > 1 && afterMode <= 1 ) {
|
||||
updateDynamicRules();
|
||||
}
|
||||
registerInjectables();
|
||||
return true;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -180,6 +150,7 @@ function onMessage(request, sender, callback) {
|
|||
}).catch(reason => {
|
||||
console.log(reason);
|
||||
});
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -273,8 +244,7 @@ function onMessage(request, sender, callback) {
|
|||
}
|
||||
|
||||
case 'setDefaultFilteringMode': {
|
||||
getDefaultFilteringMode(
|
||||
).then(beforeLevel =>
|
||||
getDefaultFilteringMode().then(beforeLevel =>
|
||||
setDefaultFilteringMode(request.level).then(afterLevel =>
|
||||
({ beforeLevel, afterLevel })
|
||||
)
|
||||
|
@ -298,37 +268,44 @@ function onMessage(request, sender, callback) {
|
|||
/******************************************************************************/
|
||||
|
||||
async function start() {
|
||||
firstRun = await loadRulesetConfig();
|
||||
await enableRulesets(rulesetConfig.enabledRulesets);
|
||||
await loadRulesetConfig();
|
||||
|
||||
if ( wakeupRun === false ) {
|
||||
await enableRulesets(rulesetConfig.enabledRulesets);
|
||||
}
|
||||
|
||||
// We need to update the regex rules only when ruleset version changes.
|
||||
const currentVersion = getCurrentVersion();
|
||||
if ( currentVersion !== rulesetConfig.version ) {
|
||||
ubolLog(`Version change: ${rulesetConfig.version} => ${currentVersion}`);
|
||||
updateDynamicRules().then(( ) => {
|
||||
rulesetConfig.version = currentVersion;
|
||||
saveRulesetConfig();
|
||||
});
|
||||
if ( wakeupRun === false ) {
|
||||
const currentVersion = getCurrentVersion();
|
||||
if ( currentVersion !== rulesetConfig.version ) {
|
||||
ubolLog(`Version change: ${rulesetConfig.version} => ${currentVersion}`);
|
||||
updateDynamicRules().then(( ) => {
|
||||
rulesetConfig.version = currentVersion;
|
||||
saveRulesetConfig();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Permissions may have been removed while the extension was disabled
|
||||
await onPermissionsRemoved();
|
||||
const permissionsChanged = await onPermissionsRemoved();
|
||||
|
||||
// Unsure whether the browser remembers correctly registered css/scripts
|
||||
// after we quit the browser. For now uBOL will check unconditionally at
|
||||
// launch time whether content css/scripts are properly registered.
|
||||
registerInjectables();
|
||||
if ( wakeupRun === false || permissionsChanged ) {
|
||||
registerInjectables();
|
||||
|
||||
const enabledRulesets = await dnr.getEnabledRulesets();
|
||||
ubolLog(`Enabled rulesets: ${enabledRulesets}`);
|
||||
const enabledRulesets = await dnr.getEnabledRulesets();
|
||||
ubolLog(`Enabled rulesets: ${enabledRulesets}`);
|
||||
|
||||
dnr.getAvailableStaticRuleCount().then(count => {
|
||||
ubolLog(`Available static rule count: ${count}`);
|
||||
});
|
||||
dnr.getAvailableStaticRuleCount().then(count => {
|
||||
ubolLog(`Available static rule count: ${count}`);
|
||||
});
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest
|
||||
// Firefox API does not support `dnr.setExtensionActionOptions`
|
||||
if ( dnr.setExtensionActionOptions ) {
|
||||
if ( wakeupRun === false && dnr.setExtensionActionOptions ) {
|
||||
dnr.setExtensionActionOptions({ displayActionCountAsBadgeText: true });
|
||||
}
|
||||
|
||||
|
|
|
@ -25,22 +25,22 @@
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const browser =
|
||||
export const browser =
|
||||
self.browser instanceof Object &&
|
||||
self.browser instanceof Element === false
|
||||
? self.browser
|
||||
: self.chrome;
|
||||
|
||||
const dnr = browser.declarativeNetRequest;
|
||||
const i18n = browser.i18n;
|
||||
const runtime = browser.runtime;
|
||||
export const dnr = browser.declarativeNetRequest;
|
||||
export const i18n = browser.i18n;
|
||||
export const runtime = browser.runtime;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// The extension's service worker can be evicted at any time, so when we
|
||||
// send a message, we try a few more times when the message fails to be sent.
|
||||
|
||||
function sendMessage(msg) {
|
||||
export function sendMessage(msg) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let i = 5;
|
||||
const send = ( ) => {
|
||||
|
@ -61,37 +61,43 @@ function sendMessage(msg) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
async function localRead(key) {
|
||||
export async function localRead(key) {
|
||||
if ( browser.storage instanceof Object === false ) { return; }
|
||||
if ( browser.storage.local instanceof Object === false ) { return; }
|
||||
try {
|
||||
const bin = await browser.storage.local.get(key);
|
||||
if ( bin instanceof Object === false ) { return; }
|
||||
return bin[key];
|
||||
return bin[key] ?? undefined;
|
||||
} catch(ex) {
|
||||
}
|
||||
}
|
||||
|
||||
async function localWrite(key, value) {
|
||||
export async function localWrite(key, value) {
|
||||
if ( browser.storage instanceof Object === false ) { return; }
|
||||
if ( browser.storage.local instanceof Object === false ) { return; }
|
||||
return browser.storage.local.set({ [key]: value });
|
||||
}
|
||||
|
||||
export async function localRemove(key) {
|
||||
if ( browser.storage instanceof Object === false ) { return; }
|
||||
if ( browser.storage.local instanceof Object === false ) { return; }
|
||||
return browser.storage.local.remove(key);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function sessionRead(key) {
|
||||
export async function sessionRead(key) {
|
||||
if ( browser.storage instanceof Object === false ) { return; }
|
||||
if ( browser.storage.session instanceof Object === false ) { return; }
|
||||
try {
|
||||
const bin = await browser.storage.session.get(key);
|
||||
if ( bin instanceof Object === false ) { return; }
|
||||
return bin[key];
|
||||
return bin[key] ?? undefined;
|
||||
} catch(ex) {
|
||||
}
|
||||
}
|
||||
|
||||
async function sessionWrite(key, value) {
|
||||
export async function sessionWrite(key, value) {
|
||||
if ( browser.storage instanceof Object === false ) { return; }
|
||||
if ( browser.storage.session instanceof Object === false ) { return; }
|
||||
return browser.storage.session.set({ [key]: value });
|
||||
|
@ -99,12 +105,15 @@ async function sessionWrite(key, value) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
export {
|
||||
browser,
|
||||
dnr,
|
||||
i18n,
|
||||
runtime,
|
||||
sendMessage,
|
||||
localRead, localWrite,
|
||||
sessionRead, sessionWrite,
|
||||
};
|
||||
export async function adminRead(key) {
|
||||
if ( browser.storage instanceof Object === false ) { return; }
|
||||
if ( browser.storage.local instanceof Object === false ) { return; }
|
||||
try {
|
||||
const bin = await browser.storage.managed.get(key);
|
||||
if ( bin instanceof Object === false ) { return; }
|
||||
return bin[key] ?? undefined;
|
||||
} catch(ex) {
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -28,8 +28,9 @@
|
|||
import {
|
||||
browser,
|
||||
dnr,
|
||||
localRead, localWrite,
|
||||
localRead, localWrite, localRemove,
|
||||
sessionRead, sessionWrite,
|
||||
adminRead,
|
||||
} from './ext.js';
|
||||
|
||||
import {
|
||||
|
@ -40,12 +41,23 @@ import {
|
|||
|
||||
import {
|
||||
TRUSTED_DIRECTIVE_BASE_RULE_ID,
|
||||
BLOCKING_MODES_RULE_ID,
|
||||
getDynamicRules
|
||||
} from './ruleset-manager.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// 0: no filtering
|
||||
// 1: basic filtering
|
||||
// 2: optimal filtering
|
||||
// 3: complete filtering
|
||||
|
||||
export const MODE_NONE = 0;
|
||||
export const MODE_BASIC = 1;
|
||||
export const MODE_OPTIMAL = 2;
|
||||
export const MODE_COMPLETE = 3;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const pruneDescendantHostnamesFromSet = (hostname, hnSet) => {
|
||||
for ( const hn of hnSet ) {
|
||||
if ( hn.endsWith(hostname) === false ) { continue; }
|
||||
|
@ -55,8 +67,6 @@ const pruneDescendantHostnamesFromSet = (hostname, hnSet) => {
|
|||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const pruneHostnameFromSet = (hostname, hnSet) => {
|
||||
let hn = hostname;
|
||||
for (;;) {
|
||||
|
@ -80,317 +90,297 @@ const eqSets = (setBefore, setAfter) => {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
// 0: no blocking
|
||||
// 1: network
|
||||
// 2: specific content
|
||||
// 3: generic content
|
||||
const serializeModeDetails = details => {
|
||||
return {
|
||||
none: Array.from(details.none),
|
||||
basic: Array.from(details.basic),
|
||||
optimal: Array.from(details.optimal),
|
||||
complete: Array.from(details.complete),
|
||||
};
|
||||
};
|
||||
|
||||
async function getActualFilteringModeDetails() {
|
||||
if ( getActualFilteringModeDetails.cache ) {
|
||||
return getActualFilteringModeDetails.cache;
|
||||
}
|
||||
let details = await sessionRead('filteringModeDetails');
|
||||
if ( details === undefined ) {
|
||||
details = await localRead('filteringModeDetails');
|
||||
if ( details === undefined ) {
|
||||
details = await getActualFilteringModeDetails.convertLegacyStorage();
|
||||
if ( details === undefined ) {
|
||||
details = {
|
||||
network: [ 'all-urls' ],
|
||||
};
|
||||
}
|
||||
}
|
||||
if ( details ) {
|
||||
sessionWrite('filteringModeDetails', details);
|
||||
}
|
||||
}
|
||||
const out = {
|
||||
const unserializeModeDetails = details => {
|
||||
return {
|
||||
none: new Set(details.none),
|
||||
network: new Set(details.network),
|
||||
extendedSpecific: new Set(details.extendedSpecific),
|
||||
extendedGeneric: new Set(details.extendedGeneric),
|
||||
basic: new Set(details.basic ?? details.network),
|
||||
optimal: new Set(details.optimal ?? details.extendedSpecific),
|
||||
complete: new Set(details.complete ?? details.extendedGeneric),
|
||||
};
|
||||
getActualFilteringModeDetails.cache = out;
|
||||
return out;
|
||||
}
|
||||
|
||||
// TODO: To remove after next stable release is widespread (2023-06-04)
|
||||
getActualFilteringModeDetails.convertLegacyStorage = async function() {
|
||||
const dynamicRuleMap = await getDynamicRules();
|
||||
const trustedSiteDirectives = (( ) => {
|
||||
const rule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID);
|
||||
return rule ? rule.condition.requestDomains : [];
|
||||
})();
|
||||
const rule = dynamicRuleMap.get(BLOCKING_MODES_RULE_ID);
|
||||
if ( rule === undefined ) { return; }
|
||||
dnr.updateDynamicRules({
|
||||
removeRuleIds: [
|
||||
BLOCKING_MODES_RULE_ID,
|
||||
],
|
||||
});
|
||||
const details = {
|
||||
none: trustedSiteDirectives || [],
|
||||
network: rule.condition.excludedInitiatorDomains || [],
|
||||
extendedSpecific: rule.condition.excludedRequestDomains || [],
|
||||
extendedGeneric: rule.condition.initiatorDomains || [],
|
||||
};
|
||||
sessionWrite('filteringModeDetails', details);
|
||||
localWrite('filteringModeDetails', details);
|
||||
return details;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function getFilteringModeDetails() {
|
||||
const actualDetails = await getActualFilteringModeDetails();
|
||||
return {
|
||||
none: new Set(actualDetails.none),
|
||||
network: new Set(actualDetails.network),
|
||||
extendedSpecific: new Set(actualDetails.extendedSpecific),
|
||||
extendedGeneric: new Set(actualDetails.extendedGeneric),
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function setFilteringModeDetails(afterDetails) {
|
||||
const actualDetails = await getActualFilteringModeDetails();
|
||||
if ( eqSets(actualDetails.none, afterDetails.none) === false ) {
|
||||
const dynamicRuleMap = await getDynamicRules();
|
||||
const removeRuleIds = [];
|
||||
if ( dynamicRuleMap.has(TRUSTED_DIRECTIVE_BASE_RULE_ID) ) {
|
||||
removeRuleIds.push(TRUSTED_DIRECTIVE_BASE_RULE_ID);
|
||||
dynamicRuleMap.delete(TRUSTED_DIRECTIVE_BASE_RULE_ID);
|
||||
}
|
||||
const addRules = [];
|
||||
if ( afterDetails.none.size !== 0 ) {
|
||||
const rule = {
|
||||
id: TRUSTED_DIRECTIVE_BASE_RULE_ID,
|
||||
action: { type: 'allowAllRequests' },
|
||||
condition: {
|
||||
resourceTypes: [ 'main_frame' ],
|
||||
},
|
||||
priority: 100,
|
||||
};
|
||||
if (
|
||||
afterDetails.none.size !== 1 ||
|
||||
afterDetails.none.has('all-urls') === false
|
||||
) {
|
||||
rule.condition.requestDomains = Array.from(afterDetails.none);
|
||||
}
|
||||
addRules.push(rule);
|
||||
dynamicRuleMap.set(TRUSTED_DIRECTIVE_BASE_RULE_ID, rule);
|
||||
}
|
||||
if ( addRules.length !== 0 || removeRuleIds.length !== 0 ) {
|
||||
const updateOptions = {};
|
||||
if ( addRules.length ) {
|
||||
updateOptions.addRules = addRules;
|
||||
}
|
||||
if ( removeRuleIds.length ) {
|
||||
updateOptions.removeRuleIds = removeRuleIds;
|
||||
}
|
||||
await dnr.updateDynamicRules(updateOptions);
|
||||
}
|
||||
}
|
||||
const data = {
|
||||
none: Array.from(afterDetails.none),
|
||||
network: Array.from(afterDetails.network),
|
||||
extendedSpecific: Array.from(afterDetails.extendedSpecific),
|
||||
extendedGeneric: Array.from(afterDetails.extendedGeneric),
|
||||
};
|
||||
sessionWrite('filteringModeDetails', data);
|
||||
localWrite('filteringModeDetails', data);
|
||||
getActualFilteringModeDetails.cache = undefined;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function getFilteringMode(hostname) {
|
||||
const filteringModes = await getFilteringModeDetails();
|
||||
const {
|
||||
none,
|
||||
network,
|
||||
extendedSpecific,
|
||||
extendedGeneric,
|
||||
} = filteringModes;
|
||||
if ( none.has(hostname) ) { return 0; }
|
||||
if ( none.has('all-urls') === false ) {
|
||||
if ( isDescendantHostnameOfIter(hostname, none) ) { return 0; }
|
||||
}
|
||||
if ( network.has(hostname) ) { return 1; }
|
||||
if ( network.has('all-urls') === false ) {
|
||||
if ( isDescendantHostnameOfIter(hostname, network) ) { return 1; }
|
||||
}
|
||||
if ( extendedSpecific.has(hostname) ) { return 2; }
|
||||
if ( extendedSpecific.has('all-urls') === false ) {
|
||||
if ( isDescendantHostnameOfIter(hostname, extendedSpecific) ) { return 2; }
|
||||
}
|
||||
if ( extendedGeneric.has(hostname) ) { return 3; }
|
||||
if ( extendedGeneric.has('all-urls') === false ) {
|
||||
if ( isDescendantHostnameOfIter(hostname, extendedGeneric) ) { return 3; }
|
||||
}
|
||||
return getDefaultFilteringMode();
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function setFilteringMode(hostname, afterLevel) {
|
||||
function lookupFilteringMode(filteringModes, hostname) {
|
||||
const { none, basic, optimal, complete } = filteringModes;
|
||||
if ( hostname === 'all-urls' ) {
|
||||
return setDefaultFilteringMode(afterLevel);
|
||||
if ( filteringModes.none.has('all-urls') ) { return MODE_NONE; }
|
||||
if ( filteringModes.basic.has('all-urls') ) { return MODE_BASIC; }
|
||||
if ( filteringModes.optimal.has('all-urls') ) { return MODE_OPTIMAL; }
|
||||
if ( filteringModes.complete.has('all-urls') ) { return MODE_COMPLETE; }
|
||||
return MODE_BASIC;
|
||||
}
|
||||
const [
|
||||
beforeLevel,
|
||||
defaultLevel,
|
||||
filteringModes
|
||||
] = await Promise.all([
|
||||
getFilteringMode(hostname),
|
||||
getDefaultFilteringMode(),
|
||||
getFilteringModeDetails(),
|
||||
]);
|
||||
if ( none.has(hostname) ) { return MODE_NONE; }
|
||||
if ( none.has('all-urls') === false ) {
|
||||
if ( isDescendantHostnameOfIter(hostname, none) ) { return MODE_NONE; }
|
||||
}
|
||||
if ( basic.has(hostname) ) { return MODE_BASIC; }
|
||||
if ( basic.has('all-urls') === false ) {
|
||||
if ( isDescendantHostnameOfIter(hostname, basic) ) { return MODE_BASIC; }
|
||||
}
|
||||
if ( optimal.has(hostname) ) { return MODE_OPTIMAL; }
|
||||
if ( optimal.has('all-urls') === false ) {
|
||||
if ( isDescendantHostnameOfIter(hostname, optimal) ) { return MODE_OPTIMAL; }
|
||||
}
|
||||
if ( complete.has(hostname) ) { return MODE_COMPLETE; }
|
||||
if ( complete.has('all-urls') === false ) {
|
||||
if ( isDescendantHostnameOfIter(hostname, complete) ) { return MODE_COMPLETE; }
|
||||
}
|
||||
return lookupFilteringMode(filteringModes, 'all-urls');
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function applyFilteringMode(filteringModes, hostname, afterLevel) {
|
||||
const defaultLevel = lookupFilteringMode(filteringModes, 'all-urls');
|
||||
if ( hostname === 'all-urls' ) {
|
||||
if ( afterLevel === defaultLevel ) { return afterLevel; }
|
||||
switch ( afterLevel ) {
|
||||
case MODE_NONE:
|
||||
filteringModes.none.clear();
|
||||
filteringModes.none.add('all-urls');
|
||||
break;
|
||||
case MODE_BASIC:
|
||||
filteringModes.basic.clear();
|
||||
filteringModes.basic.add('all-urls');
|
||||
break;
|
||||
case MODE_OPTIMAL:
|
||||
filteringModes.optimal.clear();
|
||||
filteringModes.optimal.add('all-urls');
|
||||
break;
|
||||
case MODE_COMPLETE:
|
||||
filteringModes.complete.clear();
|
||||
filteringModes.complete.add('all-urls');
|
||||
break;
|
||||
}
|
||||
switch ( defaultLevel ) {
|
||||
case MODE_NONE:
|
||||
filteringModes.none.delete('all-urls');
|
||||
break;
|
||||
case MODE_BASIC:
|
||||
filteringModes.basic.delete('all-urls');
|
||||
break;
|
||||
case MODE_OPTIMAL:
|
||||
filteringModes.optimal.delete('all-urls');
|
||||
break;
|
||||
case MODE_COMPLETE:
|
||||
filteringModes.complete.delete('all-urls');
|
||||
break;
|
||||
}
|
||||
return lookupFilteringMode(filteringModes, 'all-urls');
|
||||
}
|
||||
const beforeLevel = lookupFilteringMode(filteringModes, hostname);
|
||||
if ( afterLevel === beforeLevel ) { return afterLevel; }
|
||||
const {
|
||||
none,
|
||||
network,
|
||||
extendedSpecific,
|
||||
extendedGeneric,
|
||||
} = filteringModes;
|
||||
const { none, basic, optimal, complete } = filteringModes;
|
||||
switch ( beforeLevel ) {
|
||||
case 0:
|
||||
case MODE_NONE:
|
||||
pruneHostnameFromSet(hostname, none);
|
||||
break;
|
||||
case 1:
|
||||
pruneHostnameFromSet(hostname, network);
|
||||
case MODE_BASIC:
|
||||
pruneHostnameFromSet(hostname, basic);
|
||||
break;
|
||||
case 2:
|
||||
pruneHostnameFromSet(hostname, extendedSpecific);
|
||||
case MODE_OPTIMAL:
|
||||
pruneHostnameFromSet(hostname, optimal);
|
||||
break;
|
||||
case 3:
|
||||
pruneHostnameFromSet(hostname, extendedGeneric);
|
||||
case MODE_COMPLETE:
|
||||
pruneHostnameFromSet(hostname, complete);
|
||||
break;
|
||||
}
|
||||
if ( afterLevel !== defaultLevel ) {
|
||||
switch ( afterLevel ) {
|
||||
case 0:
|
||||
case MODE_NONE:
|
||||
if ( isDescendantHostnameOfIter(hostname, none) === false ) {
|
||||
filteringModes.none.add(hostname);
|
||||
pruneDescendantHostnamesFromSet(hostname, none);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if ( isDescendantHostnameOfIter(hostname, network) === false ) {
|
||||
filteringModes.network.add(hostname);
|
||||
pruneDescendantHostnamesFromSet(hostname, network);
|
||||
case MODE_BASIC:
|
||||
if ( isDescendantHostnameOfIter(hostname, basic) === false ) {
|
||||
filteringModes.basic.add(hostname);
|
||||
pruneDescendantHostnamesFromSet(hostname, basic);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if ( isDescendantHostnameOfIter(hostname, extendedSpecific) === false ) {
|
||||
filteringModes.extendedSpecific.add(hostname);
|
||||
pruneDescendantHostnamesFromSet(hostname, extendedSpecific);
|
||||
case MODE_OPTIMAL:
|
||||
if ( isDescendantHostnameOfIter(hostname, optimal) === false ) {
|
||||
filteringModes.optimal.add(hostname);
|
||||
pruneDescendantHostnamesFromSet(hostname, optimal);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if ( isDescendantHostnameOfIter(hostname, extendedGeneric) === false ) {
|
||||
filteringModes.extendedGeneric.add(hostname);
|
||||
pruneDescendantHostnamesFromSet(hostname, extendedGeneric);
|
||||
case MODE_COMPLETE:
|
||||
if ( isDescendantHostnameOfIter(hostname, complete) === false ) {
|
||||
filteringModes.complete.add(hostname);
|
||||
pruneDescendantHostnamesFromSet(hostname, complete);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
await setFilteringModeDetails(filteringModes);
|
||||
return getFilteringMode(hostname);
|
||||
return lookupFilteringMode(filteringModes, hostname);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function getDefaultFilteringMode() {
|
||||
const filteringModes = await getFilteringModeDetails();
|
||||
if ( filteringModes.none.has('all-urls') ) { return 0; }
|
||||
if ( filteringModes.network.has('all-urls') ) { return 1; }
|
||||
if ( filteringModes.extendedSpecific.has('all-urls') ) { return 2; }
|
||||
if ( filteringModes.extendedGeneric.has('all-urls') ) { return 3; }
|
||||
return 1;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function setDefaultFilteringMode(afterLevel) {
|
||||
const [ beforeLevel, filteringModes ] = await Promise.all([
|
||||
getDefaultFilteringMode(),
|
||||
getFilteringModeDetails(),
|
||||
async function readFilteringModeDetails() {
|
||||
if ( readFilteringModeDetails.cache ) {
|
||||
return readFilteringModeDetails.cache;
|
||||
}
|
||||
const sessionModes = await sessionRead('filteringModeDetails');
|
||||
if ( sessionModes instanceof Object ) {
|
||||
readFilteringModeDetails.cache = unserializeModeDetails(sessionModes);
|
||||
return readFilteringModeDetails.cache;
|
||||
}
|
||||
let [ userModes, adminNoFiltering ] = await Promise.all([
|
||||
localRead('filteringModeDetails'),
|
||||
localRead('adminNoFiltering'),
|
||||
]);
|
||||
if ( afterLevel === beforeLevel ) { return afterLevel; }
|
||||
switch ( afterLevel ) {
|
||||
case 0:
|
||||
filteringModes.none.clear();
|
||||
filteringModes.none.add('all-urls');
|
||||
break;
|
||||
case 1:
|
||||
filteringModes.network.clear();
|
||||
filteringModes.network.add('all-urls');
|
||||
break;
|
||||
case 2:
|
||||
filteringModes.extendedSpecific.clear();
|
||||
filteringModes.extendedSpecific.add('all-urls');
|
||||
break;
|
||||
case 3:
|
||||
filteringModes.extendedGeneric.clear();
|
||||
filteringModes.extendedGeneric.add('all-urls');
|
||||
break;
|
||||
if ( userModes === undefined ) {
|
||||
userModes = { basic: [ 'all-urls' ] };
|
||||
}
|
||||
switch ( beforeLevel ) {
|
||||
case 0:
|
||||
filteringModes.none.delete('all-urls');
|
||||
break;
|
||||
case 1:
|
||||
filteringModes.network.delete('all-urls');
|
||||
break;
|
||||
case 2:
|
||||
filteringModes.extendedSpecific.delete('all-urls');
|
||||
break;
|
||||
case 3:
|
||||
filteringModes.extendedGeneric.delete('all-urls');
|
||||
break;
|
||||
userModes = unserializeModeDetails(userModes);
|
||||
if ( Array.isArray(adminNoFiltering) ) {
|
||||
for ( const hn of adminNoFiltering ) {
|
||||
applyFilteringMode(userModes, hn, 0);
|
||||
}
|
||||
}
|
||||
await setFilteringModeDetails(filteringModes);
|
||||
return getDefaultFilteringMode();
|
||||
filteringModesToDNR(userModes);
|
||||
sessionWrite('filteringModeDetails', serializeModeDetails(userModes));
|
||||
readFilteringModeDetails.cache = userModes;
|
||||
adminRead('noFiltering').then(results => {
|
||||
if ( results ) {
|
||||
localWrite('adminNoFiltering', results);
|
||||
} else {
|
||||
localRemove('adminNoFiltering');
|
||||
}
|
||||
});
|
||||
return userModes;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function syncWithBrowserPermissions() {
|
||||
const permissions = await browser.permissions.getAll();
|
||||
async function writeFilteringModeDetails(afterDetails) {
|
||||
await filteringModesToDNR(afterDetails);
|
||||
const data = serializeModeDetails(afterDetails);
|
||||
localWrite('filteringModeDetails', data);
|
||||
sessionWrite('filteringModeDetails', data);
|
||||
readFilteringModeDetails.cache = unserializeModeDetails(data);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function filteringModesToDNR(modes) {
|
||||
const dynamicRuleMap = await getDynamicRules();
|
||||
const presentRule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID);
|
||||
const presentNone = new Set(
|
||||
presentRule && presentRule.condition.requestDomains
|
||||
);
|
||||
if ( eqSets(presentNone, modes.none) ) { return; }
|
||||
const removeRuleIds = [];
|
||||
if ( presentRule !== undefined ) {
|
||||
removeRuleIds.push(TRUSTED_DIRECTIVE_BASE_RULE_ID);
|
||||
dynamicRuleMap.delete(TRUSTED_DIRECTIVE_BASE_RULE_ID);
|
||||
}
|
||||
const addRules = [];
|
||||
if ( modes.none.size !== 0 ) {
|
||||
const rule = {
|
||||
id: TRUSTED_DIRECTIVE_BASE_RULE_ID,
|
||||
action: { type: 'allowAllRequests' },
|
||||
condition: {
|
||||
resourceTypes: [ 'main_frame' ],
|
||||
},
|
||||
priority: 100,
|
||||
};
|
||||
if (
|
||||
modes.none.size !== 1 ||
|
||||
modes.none.has('all-urls') === false
|
||||
) {
|
||||
rule.condition.requestDomains = Array.from(modes.none);
|
||||
}
|
||||
addRules.push(rule);
|
||||
dynamicRuleMap.set(TRUSTED_DIRECTIVE_BASE_RULE_ID, rule);
|
||||
}
|
||||
const updateOptions = {};
|
||||
if ( addRules.length ) {
|
||||
updateOptions.addRules = addRules;
|
||||
}
|
||||
if ( removeRuleIds.length ) {
|
||||
updateOptions.removeRuleIds = removeRuleIds;
|
||||
}
|
||||
await dnr.updateDynamicRules(updateOptions);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export async function getFilteringModeDetails() {
|
||||
const actualDetails = await readFilteringModeDetails();
|
||||
return {
|
||||
none: new Set(actualDetails.none),
|
||||
basic: new Set(actualDetails.basic),
|
||||
optimal: new Set(actualDetails.optimal),
|
||||
complete: new Set(actualDetails.complete),
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export async function getFilteringMode(hostname) {
|
||||
const filteringModes = await getFilteringModeDetails();
|
||||
return lookupFilteringMode(filteringModes, hostname);
|
||||
}
|
||||
|
||||
export async function setFilteringMode(hostname, afterLevel) {
|
||||
const filteringModes = await getFilteringModeDetails();
|
||||
const level = applyFilteringMode(filteringModes, hostname, afterLevel);
|
||||
await writeFilteringModeDetails(filteringModes);
|
||||
return level;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function getDefaultFilteringMode() {
|
||||
return getFilteringMode('all-urls');
|
||||
}
|
||||
|
||||
export function setDefaultFilteringMode(afterLevel) {
|
||||
return setFilteringMode('all-urls', afterLevel);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export async function syncWithBrowserPermissions() {
|
||||
const [ permissions, beforeMode ] = await Promise.all([
|
||||
browser.permissions.getAll(),
|
||||
getDefaultFilteringMode(),
|
||||
]);
|
||||
const allowedHostnames = new Set(hostnamesFromMatches(permissions.origins || []));
|
||||
const beforeMode = await getDefaultFilteringMode();
|
||||
let modified = false;
|
||||
if ( beforeMode > 1 && allowedHostnames.has('all-urls') === false ) {
|
||||
await setDefaultFilteringMode(1);
|
||||
if ( beforeMode > MODE_BASIC && allowedHostnames.has('all-urls') === false ) {
|
||||
await setDefaultFilteringMode(MODE_BASIC);
|
||||
modified = true;
|
||||
}
|
||||
const afterMode = await getDefaultFilteringMode();
|
||||
if ( afterMode > 1 ) { return false; }
|
||||
if ( afterMode > MODE_BASIC ) { return false; }
|
||||
const filteringModes = await getFilteringModeDetails();
|
||||
const { extendedSpecific, extendedGeneric } = filteringModes;
|
||||
for ( const hn of extendedSpecific ) {
|
||||
const { optimal, complete } = filteringModes;
|
||||
for ( const hn of optimal ) {
|
||||
if ( allowedHostnames.has(hn) ) { continue; }
|
||||
extendedSpecific.delete(hn);
|
||||
optimal.delete(hn);
|
||||
modified = true;
|
||||
}
|
||||
for ( const hn of extendedGeneric ) {
|
||||
for ( const hn of complete ) {
|
||||
if ( allowedHostnames.has(hn) ) { continue; }
|
||||
extendedGeneric.delete(hn);
|
||||
complete.delete(hn);
|
||||
modified = true;
|
||||
}
|
||||
await setFilteringModeDetails(filteringModes);
|
||||
await writeFilteringModeDetails(filteringModes);
|
||||
return modified;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export {
|
||||
getFilteringMode,
|
||||
setFilteringMode,
|
||||
getDefaultFilteringMode,
|
||||
setDefaultFilteringMode,
|
||||
getFilteringModeDetails,
|
||||
syncWithBrowserPermissions,
|
||||
};
|
||||
|
|
|
@ -41,8 +41,6 @@ const REDIRECT_REALM_END = REDIRECT_REALM_START + RULE_REALM_SIZE;
|
|||
const CSP_REALM_START = REDIRECT_REALM_END;
|
||||
const CSP_REALM_END = CSP_REALM_START + RULE_REALM_SIZE;
|
||||
const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000;
|
||||
const BLOCKING_MODES_RULE_ID = TRUSTED_DIRECTIVE_BASE_RULE_ID + 1;
|
||||
const CURRENT_CONFIG_BASE_RULE_ID = 9000000;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
@ -509,8 +507,6 @@ async function getEnabledRulesetsDetails() {
|
|||
/******************************************************************************/
|
||||
|
||||
export {
|
||||
BLOCKING_MODES_RULE_ID,
|
||||
CURRENT_CONFIG_BASE_RULE_ID,
|
||||
TRUSTED_DIRECTIVE_BASE_RULE_ID,
|
||||
getRulesetDetails,
|
||||
getDynamicRules,
|
||||
|
|
|
@ -110,20 +110,20 @@ function registerGeneric(context, genericDetails) {
|
|||
|
||||
js.push('/js/scripting/css-generic.js');
|
||||
|
||||
const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails;
|
||||
const { none, basic, optimal, complete } = filteringModeDetails;
|
||||
const matches = [];
|
||||
const excludeMatches = [];
|
||||
if ( extendedGeneric.has('all-urls') ) {
|
||||
if ( complete.has('all-urls') ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(none));
|
||||
excludeMatches.push(...ut.matchesFromHostnames(network));
|
||||
excludeMatches.push(...ut.matchesFromHostnames(extendedSpecific));
|
||||
excludeMatches.push(...ut.matchesFromHostnames(basic));
|
||||
excludeMatches.push(...ut.matchesFromHostnames(optimal));
|
||||
excludeMatches.push(...ut.matchesFromHostnames(excludeHostnames));
|
||||
matches.push('<all_urls>');
|
||||
} else {
|
||||
matches.push(
|
||||
...ut.matchesFromHostnames(
|
||||
ut.subtractHostnameIters(
|
||||
Array.from(extendedGeneric),
|
||||
Array.from(complete),
|
||||
excludeHostnames
|
||||
)
|
||||
)
|
||||
|
@ -173,10 +173,10 @@ function registerProcedural(context) {
|
|||
}
|
||||
if ( js.length === 0 ) { return; }
|
||||
|
||||
const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails;
|
||||
const { none, basic, optimal, complete } = filteringModeDetails;
|
||||
const matches = [
|
||||
...ut.matchesFromHostnames(extendedSpecific),
|
||||
...ut.matchesFromHostnames(extendedGeneric),
|
||||
...ut.matchesFromHostnames(optimal),
|
||||
...ut.matchesFromHostnames(complete),
|
||||
];
|
||||
if ( matches.length === 0 ) { return; }
|
||||
|
||||
|
@ -186,8 +186,8 @@ function registerProcedural(context) {
|
|||
if ( none.has('all-urls') === false ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(none));
|
||||
}
|
||||
if ( network.has('all-urls') === false ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(network));
|
||||
if ( basic.has('all-urls') === false ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(basic));
|
||||
}
|
||||
|
||||
const registered = before.get('css-procedural');
|
||||
|
@ -232,10 +232,10 @@ function registerDeclarative(context) {
|
|||
}
|
||||
if ( js.length === 0 ) { return; }
|
||||
|
||||
const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails;
|
||||
const { none, basic, optimal, complete } = filteringModeDetails;
|
||||
const matches = [
|
||||
...ut.matchesFromHostnames(extendedSpecific),
|
||||
...ut.matchesFromHostnames(extendedGeneric),
|
||||
...ut.matchesFromHostnames(optimal),
|
||||
...ut.matchesFromHostnames(complete),
|
||||
];
|
||||
if ( matches.length === 0 ) { return; }
|
||||
|
||||
|
@ -245,8 +245,8 @@ function registerDeclarative(context) {
|
|||
if ( none.has('all-urls') === false ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(none));
|
||||
}
|
||||
if ( network.has('all-urls') === false ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(network));
|
||||
if ( basic.has('all-urls') === false ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(basic));
|
||||
}
|
||||
|
||||
const registered = before.get('css-declarative');
|
||||
|
@ -291,10 +291,10 @@ function registerSpecific(context) {
|
|||
}
|
||||
if ( js.length === 0 ) { return; }
|
||||
|
||||
const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails;
|
||||
const { none, basic, optimal, complete } = filteringModeDetails;
|
||||
const matches = [
|
||||
...ut.matchesFromHostnames(extendedSpecific),
|
||||
...ut.matchesFromHostnames(extendedGeneric),
|
||||
...ut.matchesFromHostnames(optimal),
|
||||
...ut.matchesFromHostnames(complete),
|
||||
];
|
||||
if ( matches.length === 0 ) { return; }
|
||||
|
||||
|
@ -304,8 +304,8 @@ function registerSpecific(context) {
|
|||
if ( none.has('all-urls') === false ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(none));
|
||||
}
|
||||
if ( network.has('all-urls') === false ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(network));
|
||||
if ( basic.has('all-urls') === false ) {
|
||||
excludeMatches.push(...ut.matchesFromHostnames(basic));
|
||||
}
|
||||
|
||||
const registered = before.get('css-specific');
|
||||
|
@ -347,16 +347,16 @@ function registerScriptlet(context, scriptletDetails) {
|
|||
const { before, filteringModeDetails, rulesetsDetails } = context;
|
||||
|
||||
const hasBroadHostPermission =
|
||||
filteringModeDetails.extendedSpecific.has('all-urls') ||
|
||||
filteringModeDetails.extendedGeneric.has('all-urls');
|
||||
filteringModeDetails.optimal.has('all-urls') ||
|
||||
filteringModeDetails.complete.has('all-urls');
|
||||
|
||||
const permissionRevokedMatches = [
|
||||
...ut.matchesFromHostnames(filteringModeDetails.none),
|
||||
...ut.matchesFromHostnames(filteringModeDetails.network),
|
||||
...ut.matchesFromHostnames(filteringModeDetails.basic),
|
||||
];
|
||||
const permissionGrantedHostnames = [
|
||||
...filteringModeDetails.extendedSpecific,
|
||||
...filteringModeDetails.extendedGeneric,
|
||||
...filteringModeDetails.optimal,
|
||||
...filteringModeDetails.complete,
|
||||
];
|
||||
|
||||
for ( const rulesetId of rulesetsDetails.map(v => v.id) ) {
|
||||
|
|
|
@ -241,32 +241,32 @@ const renderWidgets = function() {
|
|||
async function onFilteringModeChange(ev) {
|
||||
const input = ev.target;
|
||||
const newLevel = parseInt(input.value, 10);
|
||||
let granted = false;
|
||||
|
||||
switch ( newLevel ) {
|
||||
case 1: { // Revoke broad permissions
|
||||
granted = await browser.permissions.remove({
|
||||
await browser.permissions.remove({
|
||||
origins: [ '<all_urls>' ]
|
||||
});
|
||||
cachedRulesetData.defaultFilteringMode = 1;
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
case 3: { // Request broad permissions
|
||||
granted = await browser.permissions.request({
|
||||
const granted = await browser.permissions.request({
|
||||
origins: [ '<all_urls>' ]
|
||||
});
|
||||
if ( granted ) {
|
||||
const actualLevel = await sendMessage({
|
||||
what: 'setDefaultFilteringMode',
|
||||
level: newLevel,
|
||||
});
|
||||
cachedRulesetData.defaultFilteringMode = actualLevel;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if ( granted ) {
|
||||
const actualLevel = await sendMessage({
|
||||
what: 'setDefaultFilteringMode',
|
||||
level: newLevel,
|
||||
});
|
||||
cachedRulesetData.defaultFilteringMode = actualLevel;
|
||||
}
|
||||
renderFilterLists(true);
|
||||
renderWidgets();
|
||||
}
|
||||
|
|
11
platform/mv3/extension/managed_storage.json
Normal file
11
platform/mv3/extension/managed_storage.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"noFiltering": {
|
||||
"title": "List of domains for which no filtering should occur",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -66,6 +66,7 @@ if [ "$PLATFORM" = "firefox" ]; then
|
|||
cp platform/mv3/firefox/background.html $DES/
|
||||
fi
|
||||
cp platform/mv3/extension/*.html $DES/
|
||||
cp platform/mv3/extension/*.json $DES/
|
||||
cp platform/mv3/extension/css/* $DES/css/
|
||||
cp -R platform/mv3/extension/js/* $DES/js/
|
||||
cp platform/mv3/extension/img/* $DES/img/
|
||||
|
|
Loading…
Reference in a new issue