mirror of https://github.com/gorhill/uBlock.git
455 lines
14 KiB
JavaScript
455 lines
14 KiB
JavaScript
/*******************************************************************************
|
|
|
|
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
|
Copyright (C) 2022-present Raymond Hill
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
Home: https://github.com/gorhill/uBlock
|
|
*/
|
|
|
|
import {
|
|
adminRead,
|
|
browser,
|
|
dnr,
|
|
localRead, localWrite,
|
|
runtime,
|
|
sessionRead, sessionWrite,
|
|
windows,
|
|
} from './ext.js';
|
|
|
|
import {
|
|
defaultRulesetsFromLanguage,
|
|
enableRulesets,
|
|
getEnabledRulesetsDetails,
|
|
getRulesetDetails,
|
|
updateDynamicRules,
|
|
} from './ruleset-manager.js';
|
|
|
|
import {
|
|
MODE_BASIC,
|
|
MODE_OPTIMAL,
|
|
getDefaultFilteringMode,
|
|
getFilteringMode,
|
|
getTrustedSites,
|
|
setDefaultFilteringMode,
|
|
setFilteringMode,
|
|
setTrustedSites,
|
|
syncWithBrowserPermissions,
|
|
} from './mode-manager.js';
|
|
|
|
import {
|
|
getMatchedRules,
|
|
isSideloaded,
|
|
ubolLog,
|
|
} from './debug.js';
|
|
|
|
import { broadcastMessage } from './utils.js';
|
|
import { registerInjectables } from './scripting-manager.js';
|
|
|
|
/******************************************************************************/
|
|
|
|
const rulesetConfig = {
|
|
version: '',
|
|
enabledRulesets: [ 'default' ],
|
|
autoReload: true,
|
|
showBlockedCount: true,
|
|
};
|
|
|
|
const UBOL_ORIGIN = runtime.getURL('').replace(/\/$/, '');
|
|
|
|
const canShowBlockedCount = typeof dnr.setExtensionActionOptions === 'function';
|
|
|
|
let firstRun = false;
|
|
let wakeupRun = false;
|
|
|
|
/******************************************************************************/
|
|
|
|
function getCurrentVersion() {
|
|
return runtime.getManifest().version;
|
|
}
|
|
|
|
async function loadRulesetConfig() {
|
|
let data = await sessionRead('rulesetConfig');
|
|
if ( data ) {
|
|
rulesetConfig.version = data.version;
|
|
rulesetConfig.enabledRulesets = data.enabledRulesets;
|
|
rulesetConfig.autoReload = typeof data.autoReload === 'boolean'
|
|
? data.autoReload
|
|
: true;
|
|
rulesetConfig.showBlockedCount = typeof data.showBlockedCount === 'boolean'
|
|
? data.showBlockedCount
|
|
: true;
|
|
wakeupRun = true;
|
|
return;
|
|
}
|
|
data = await localRead('rulesetConfig');
|
|
if ( data ) {
|
|
rulesetConfig.version = data.version;
|
|
rulesetConfig.enabledRulesets = data.enabledRulesets;
|
|
rulesetConfig.autoReload = typeof data.autoReload === 'boolean'
|
|
? data.autoReload
|
|
: true;
|
|
rulesetConfig.showBlockedCount = typeof data.showBlockedCount === 'boolean'
|
|
? data.showBlockedCount
|
|
: true;
|
|
sessionWrite('rulesetConfig', rulesetConfig);
|
|
return;
|
|
}
|
|
rulesetConfig.enabledRulesets = await defaultRulesetsFromLanguage();
|
|
sessionWrite('rulesetConfig', rulesetConfig);
|
|
localWrite('rulesetConfig', rulesetConfig);
|
|
firstRun = true;
|
|
}
|
|
|
|
async function saveRulesetConfig() {
|
|
sessionWrite('rulesetConfig', rulesetConfig);
|
|
return localWrite('rulesetConfig', rulesetConfig);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
async function hasGreatPowers(origin) {
|
|
if ( /^https?:\/\//.test(origin) === false ) { return false; }
|
|
return browser.permissions.contains({
|
|
origins: [ `${origin}/*` ],
|
|
});
|
|
}
|
|
|
|
function hasOmnipotence() {
|
|
return browser.permissions.contains({
|
|
origins: [ '<all_urls>' ],
|
|
});
|
|
}
|
|
|
|
async function onPermissionsRemoved() {
|
|
const beforeMode = await getDefaultFilteringMode();
|
|
const modified = await syncWithBrowserPermissions();
|
|
if ( modified === false ) { return false; }
|
|
const afterMode = await getDefaultFilteringMode();
|
|
if ( beforeMode > MODE_BASIC && afterMode <= MODE_BASIC ) {
|
|
updateDynamicRules();
|
|
}
|
|
registerInjectables();
|
|
return true;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
async function gotoURL(url, type) {
|
|
const pageURL = new URL(url, runtime.getURL('/'));
|
|
const tabs = await browser.tabs.query({
|
|
url: pageURL.href,
|
|
windowType: type !== 'popup' ? 'normal' : 'popup'
|
|
});
|
|
|
|
if ( Array.isArray(tabs) && tabs.length !== 0 ) {
|
|
const { windowId, id } = tabs[0];
|
|
return Promise.all([
|
|
browser.windows.update(windowId, { focused: true }),
|
|
browser.tabs.update(id, { active: true }),
|
|
]);
|
|
}
|
|
|
|
if ( type === 'popup' ) {
|
|
return windows.create({
|
|
type: 'popup',
|
|
url: pageURL.href,
|
|
});
|
|
}
|
|
|
|
return browser.tabs.create({
|
|
active: true,
|
|
url: pageURL.href,
|
|
});
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
function onMessage(request, sender, callback) {
|
|
|
|
// Does not require trusted origin.
|
|
|
|
switch ( request.what ) {
|
|
|
|
case 'insertCSS': {
|
|
const tabId = sender?.tab?.id ?? false;
|
|
const frameId = sender?.frameId ?? false;
|
|
if ( tabId === false || frameId === false ) { return; }
|
|
browser.scripting.insertCSS({
|
|
css: request.css,
|
|
origin: 'USER',
|
|
target: { tabId, frameIds: [ frameId ] },
|
|
}).catch(reason => {
|
|
console.log(reason);
|
|
});
|
|
return false;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Does require trusted origin.
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/MessageSender
|
|
// Firefox API does not set `sender.origin`
|
|
if ( sender.origin !== undefined && sender.origin !== UBOL_ORIGIN ) { return; }
|
|
|
|
switch ( request.what ) {
|
|
|
|
case 'applyRulesets': {
|
|
enableRulesets(request.enabledRulesets).then(( ) => {
|
|
rulesetConfig.enabledRulesets = request.enabledRulesets;
|
|
return saveRulesetConfig();
|
|
}).then(( ) => {
|
|
registerInjectables();
|
|
callback();
|
|
broadcastMessage({ enabledRulesets: rulesetConfig.enabledRulesets });
|
|
});
|
|
return true;
|
|
}
|
|
|
|
case 'getOptionsPageData': {
|
|
Promise.all([
|
|
getDefaultFilteringMode(),
|
|
getTrustedSites(),
|
|
getRulesetDetails(),
|
|
dnr.getEnabledRulesets(),
|
|
]).then(results => {
|
|
const [
|
|
defaultFilteringMode,
|
|
trustedSites,
|
|
rulesetDetails,
|
|
enabledRulesets,
|
|
] = results;
|
|
callback({
|
|
defaultFilteringMode,
|
|
trustedSites: Array.from(trustedSites),
|
|
enabledRulesets,
|
|
maxNumberOfEnabledRulesets: dnr.MAX_NUMBER_OF_ENABLED_STATIC_RULESETS,
|
|
rulesetDetails: Array.from(rulesetDetails.values()),
|
|
autoReload: rulesetConfig.autoReload,
|
|
showBlockedCount: rulesetConfig.showBlockedCount,
|
|
canShowBlockedCount,
|
|
firstRun,
|
|
});
|
|
firstRun = false;
|
|
});
|
|
return true;
|
|
}
|
|
|
|
case 'setAutoReload':
|
|
rulesetConfig.autoReload = request.state && true || false;
|
|
saveRulesetConfig().then(( ) => {
|
|
callback();
|
|
broadcastMessage({ autoReload: rulesetConfig.autoReload });
|
|
});
|
|
return true;
|
|
|
|
case 'setShowBlockedCount':
|
|
rulesetConfig.showBlockedCount = request.state && true || false;
|
|
if ( canShowBlockedCount ) {
|
|
dnr.setExtensionActionOptions({
|
|
displayActionCountAsBadgeText: rulesetConfig.showBlockedCount,
|
|
});
|
|
}
|
|
saveRulesetConfig().then(( ) => {
|
|
callback();
|
|
broadcastMessage({ showBlockedCount: rulesetConfig.showBlockedCount });
|
|
});
|
|
return true;
|
|
|
|
case 'popupPanelData': {
|
|
Promise.all([
|
|
getFilteringMode(request.hostname),
|
|
hasOmnipotence(),
|
|
hasGreatPowers(request.origin),
|
|
getEnabledRulesetsDetails(),
|
|
]).then(results => {
|
|
callback({
|
|
level: results[0],
|
|
autoReload: rulesetConfig.autoReload,
|
|
hasOmnipotence: results[1],
|
|
hasGreatPowers: results[2],
|
|
rulesetDetails: results[3],
|
|
isSideloaded,
|
|
});
|
|
});
|
|
return true;
|
|
}
|
|
|
|
case 'getFilteringMode': {
|
|
getFilteringMode(request.hostname).then(actualLevel => {
|
|
callback(actualLevel);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
case 'gotoURL':
|
|
gotoURL(request.url, request.type);
|
|
break;
|
|
|
|
case 'setFilteringMode': {
|
|
getFilteringMode(request.hostname).then(actualLevel => {
|
|
if ( request.level === actualLevel ) { return actualLevel; }
|
|
return setFilteringMode(request.hostname, request.level);
|
|
}).then(actualLevel => {
|
|
registerInjectables();
|
|
callback(actualLevel);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
case 'getDefaultFilteringMode': {
|
|
getDefaultFilteringMode().then(level => {
|
|
callback(level);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
case 'setDefaultFilteringMode': {
|
|
getDefaultFilteringMode().then(beforeLevel =>
|
|
setDefaultFilteringMode(request.level).then(afterLevel =>
|
|
({ beforeLevel, afterLevel })
|
|
)
|
|
).then(({ beforeLevel, afterLevel }) => {
|
|
if ( beforeLevel === 1 || afterLevel === 1 ) {
|
|
updateDynamicRules();
|
|
}
|
|
if ( afterLevel !== beforeLevel ) {
|
|
registerInjectables();
|
|
}
|
|
callback(afterLevel);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
case 'setTrustedSites':
|
|
setTrustedSites(request.hostnames).then(( ) => {
|
|
registerInjectables();
|
|
return Promise.all([
|
|
getDefaultFilteringMode(),
|
|
getTrustedSites(),
|
|
]);
|
|
}).then(results => {
|
|
callback({
|
|
defaultFilteringMode: results[0],
|
|
trustedSites: Array.from(results[1]),
|
|
});
|
|
});
|
|
return true;
|
|
|
|
case 'getMatchedRules':
|
|
getMatchedRules(request.tabId).then(entries => {
|
|
callback(entries);
|
|
});
|
|
return true;
|
|
|
|
case 'showMatchedRules':
|
|
windows.create({
|
|
type: 'popup',
|
|
url: `/matched-rules.html?tab=${request.tabId}`,
|
|
});
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
async function start() {
|
|
await loadRulesetConfig();
|
|
|
|
if ( wakeupRun === false ) {
|
|
await enableRulesets(rulesetConfig.enabledRulesets);
|
|
}
|
|
|
|
// We need to update the regex rules only when ruleset version changes.
|
|
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
|
|
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.
|
|
if ( wakeupRun === false || permissionsChanged ) {
|
|
registerInjectables();
|
|
|
|
const enabledRulesets = await dnr.getEnabledRulesets();
|
|
ubolLog(`Enabled rulesets: ${enabledRulesets}`);
|
|
|
|
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 ( wakeupRun === false && canShowBlockedCount ) {
|
|
dnr.setExtensionActionOptions({
|
|
displayActionCountAsBadgeText: rulesetConfig.showBlockedCount,
|
|
});
|
|
}
|
|
|
|
runtime.onMessage.addListener(onMessage);
|
|
|
|
browser.permissions.onRemoved.addListener(
|
|
( ) => { onPermissionsRemoved(); }
|
|
);
|
|
|
|
if ( firstRun ) {
|
|
const enableOptimal = await hasOmnipotence();
|
|
if ( enableOptimal ) {
|
|
const afterLevel = await setDefaultFilteringMode(MODE_OPTIMAL);
|
|
if ( afterLevel === MODE_OPTIMAL ) {
|
|
updateDynamicRules();
|
|
registerInjectables();
|
|
}
|
|
}
|
|
const disableFirstRunPage = await adminRead('disableFirstRunPage');
|
|
if ( disableFirstRunPage !== true ) {
|
|
runtime.openOptionsPage();
|
|
} else {
|
|
firstRun = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://github.com/uBlockOrigin/uBOL-home/issues/199
|
|
// Force a restart of the extension once when an "internal error" occurs
|
|
start().then(( ) => {
|
|
localWrite({ goodStart: true });
|
|
}).catch(reason => {
|
|
console.trace(reason);
|
|
localRead('goodStart').then((bin = {}) => {
|
|
if ( bin.goodStart === false ) { return; }
|
|
localWrite({ goodStart: false }).then(( ) => {
|
|
runtime.reload();
|
|
});
|
|
});
|
|
});
|