mirror of
https://github.com/gorhill/uBlock.git
synced 2026-03-11 09:04:36 +00:00
[mv3] Implement strict blocking
Related issue: https://github.com/uBlockOrigin/uBOL-home/issues/214 This implements basic functionality for strict blocking, i.e. the ability to block navigation to undesirable websites. This is a first implementation, which converts only filters that are plain hostnames. Unlike with uBO, it is not possible to know from which ruleset a blocking rule originates. Nonetheless, users will have to make a choice as to whether navigation should proceed or not. A setting has been added to the dashboard to wholly enable/disable strict blocking. It is enabled by default. Potential future improvements, pending investigation on feasability in an MV3 framework: - Extend coverage to explicit `document` filters - Leverage and use `urlskip=` filters in the blocking page in order to proceed while bypassing unwanted redirects.
This commit is contained in:
parent
d7df6cda4a
commit
aa05cb32c6
18 changed files with 886 additions and 372 deletions
|
|
@ -25,7 +25,7 @@
|
|||
"128": "img/icon_128.png"
|
||||
},
|
||||
"manifest_version": 3,
|
||||
"minimum_chrome_version": "119.0",
|
||||
"minimum_chrome_version": "122.0",
|
||||
"name": "__MSG_extName__",
|
||||
"options_page": "dashboard.html",
|
||||
"optional_host_permissions": [
|
||||
|
|
@ -41,6 +41,5 @@
|
|||
"storage": {
|
||||
"managed_schema": "managed_storage.json"
|
||||
},
|
||||
"version": "1.0",
|
||||
"web_accessible_resources": []
|
||||
"version": "1.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,8 +231,44 @@
|
|||
"message": "Show the number of blocked requests on the toolbar icon",
|
||||
"description": "Label for a checkbox in the options page"
|
||||
},
|
||||
"enableStrictBlockLabel": {
|
||||
"message": "Enable strict blocking",
|
||||
"description": "Label for a checkbox in the options page"
|
||||
},
|
||||
"enableStrictBlockLegend": {
|
||||
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
|
||||
"description": "Label for a checkbox in the options page"
|
||||
},
|
||||
"findListsPlaceholder": {
|
||||
"message": "Find lists",
|
||||
"description": "Placeholder for the input field used to find lists"
|
||||
},
|
||||
"strictblockTitle": {
|
||||
"message": "Page blocked",
|
||||
"description": "Webpage title for the strict-blocked page"
|
||||
},
|
||||
"strictblockSentence1": {
|
||||
"message": "uBO Lite has prevented the following page from loading:",
|
||||
"description": "Sentence used in the strict-blocked page"
|
||||
},
|
||||
"strictblockNoParamsPrompt": {
|
||||
"message": "without parameters",
|
||||
"description": "Label to be used for the parameter-less URL"
|
||||
},
|
||||
"strictblockBack": {
|
||||
"message": "Go back",
|
||||
"description": "A button to go back to the previous webpage"
|
||||
},
|
||||
"strictblockClose": {
|
||||
"message": "Close this window",
|
||||
"description": "A button to close the current tab"
|
||||
},
|
||||
"strictblockDontWarn": {
|
||||
"message": "Don't warn me again about this site",
|
||||
"description": "Label for checkbox in document-blocked page"
|
||||
},
|
||||
"strictblockProceed": {
|
||||
"message": "Proceed",
|
||||
"description": "A button to navigate to the blocked page"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ body.firstRun .firstRun {
|
|||
h3 {
|
||||
margin: 1em 0;
|
||||
}
|
||||
p {
|
||||
white-space: pre-line;
|
||||
|
||||
label + legend {
|
||||
color: color-mix(in srgb, currentColor 69%, transparent);
|
||||
font-size: small;
|
||||
margin-inline-start: var(--default-gap-large);
|
||||
}
|
||||
|
||||
body[data-forbid~="dashboard"] #dashboard-nav [data-pane="settings"],
|
||||
|
|
|
|||
147
platform/mv3/extension/css/strictblock.css
Normal file
147
platform/mv3/extension/css/strictblock.css
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2018-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
|
||||
*/
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
padding: var(--default-gap-xxlarge) var(--default-gap-small);
|
||||
justify-content: center;
|
||||
}
|
||||
:root.mobile body {
|
||||
padding: var(--default-gap-small);
|
||||
}
|
||||
|
||||
#rootContainer {
|
||||
width: min(100%, 640px);
|
||||
}
|
||||
#rootContainer > * {
|
||||
margin: 0 0 var(--default-gap-xxlarge) 0;
|
||||
}
|
||||
:root.mobile #rootContainer > * {
|
||||
margin-bottom: var(--default-gap-xlarge);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.code {
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
}
|
||||
#warningSign {
|
||||
color: var(--accent-surface-1);
|
||||
fill: var(--accent-surface-1);
|
||||
font-size: 96px;
|
||||
line-height: 1;
|
||||
width: 100%;
|
||||
}
|
||||
:root.mobile #warningSign {
|
||||
font-size: 64px;
|
||||
}
|
||||
#theURL {
|
||||
color: var(--ink-2);
|
||||
padding: 0;
|
||||
}
|
||||
#theURL > * {
|
||||
margin: 0;
|
||||
}
|
||||
#theURL > p {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
#theURL > p > span:first-of-type {
|
||||
display: block;
|
||||
max-height: 6lh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
:root.mobile #theURL > p > span:first-of-type {
|
||||
max-height: 3lh;
|
||||
}
|
||||
#theURL #toggleParse {
|
||||
background-color: transparent;
|
||||
top: 100%;
|
||||
box-sizing: border-box;
|
||||
color: var(--ink-3);
|
||||
fill: var(--ink-3);
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
padding: var(--default-gap-xxsmall);
|
||||
position: absolute;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
#theURL:not(.collapsed) #toggleParse > span:first-of-type {
|
||||
display: none;
|
||||
}
|
||||
#theURL.collapsed #toggleParse > span:last-of-type {
|
||||
display: none;
|
||||
}
|
||||
body[dir="ltr"] #toggleParse {
|
||||
right: 0;
|
||||
}
|
||||
body[dir="rtl"] #toggleParse {
|
||||
left: 0;
|
||||
}
|
||||
#theURL > p:hover #toggleParse {
|
||||
transform: translate(0, -50%) scale(1.15);
|
||||
}
|
||||
#parsed {
|
||||
background-color: var(--surface-1);
|
||||
border: 4px solid var(--surface-2);
|
||||
font-size: small;
|
||||
overflow-x: auto;
|
||||
padding: var(--default-gap-xxsmall);
|
||||
text-align: initial;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#theURL.collapsed > #parsed {
|
||||
display: none;
|
||||
}
|
||||
#parsed ul, #parsed li {
|
||||
list-style-type: none;
|
||||
}
|
||||
#parsed li {
|
||||
white-space: nowrap;
|
||||
}
|
||||
#parsed span {
|
||||
display: inline-block;
|
||||
}
|
||||
#parsed span:first-of-type {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#actionContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
:root.mobile #actionContainer {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#actionContainer > button {
|
||||
margin-bottom: 2rem
|
||||
}
|
||||
|
||||
/* Small-screen devices */
|
||||
:root.mobile button {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
<p><label id="autoReload" data-i18n="autoReloadLabel"><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span>_</label>
|
||||
</p>
|
||||
<p><label id="showBlockedCount" data-i18n="showBlockedCountLabel"><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span>_</label>
|
||||
<p><label id="strictBlockMode" data-i18n="enableStrictBlockLabel"><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span>_</label><legend data-i18n="enableStrictBlockLegend"></legend>
|
||||
<p id="developerMode" hidden><label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span>Developer mode</label>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -47,8 +47,10 @@ import {
|
|||
|
||||
import {
|
||||
enableRulesets,
|
||||
excludeFromStrictBlock,
|
||||
getEnabledRulesetsDetails,
|
||||
getRulesetDetails,
|
||||
setStrictBlockMode,
|
||||
updateDynamicRules,
|
||||
} from './ruleset-manager.js';
|
||||
|
||||
|
|
@ -213,6 +215,7 @@ function onMessage(request, sender, callback) {
|
|||
autoReload: rulesetConfig.autoReload,
|
||||
showBlockedCount: rulesetConfig.showBlockedCount,
|
||||
canShowBlockedCount,
|
||||
strictBlockMode: rulesetConfig.strictBlockMode,
|
||||
firstRun: process.firstRun,
|
||||
isSideloaded,
|
||||
developerMode: rulesetConfig.developerMode,
|
||||
|
|
@ -244,6 +247,13 @@ function onMessage(request, sender, callback) {
|
|||
});
|
||||
return true;
|
||||
|
||||
case 'setStrictBlockMode':
|
||||
setStrictBlockMode(request.state).then(( ) => {
|
||||
callback();
|
||||
broadcastMessage({ strictBlockMode: rulesetConfig.strictBlockMode });
|
||||
});
|
||||
return true;
|
||||
|
||||
case 'setDeveloperMode':
|
||||
rulesetConfig.developerMode = request.state;
|
||||
toggleDeveloperMode(rulesetConfig.developerMode);
|
||||
|
|
@ -335,6 +345,13 @@ function onMessage(request, sender, callback) {
|
|||
});
|
||||
return true;
|
||||
|
||||
case 'excludeFromStrictBlock': {
|
||||
excludeFromStrictBlock(request.hostname, request.permanent).then(( ) => {
|
||||
callback();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'getMatchedRules':
|
||||
getMatchedRules(request.tabId).then(entries => {
|
||||
callback(entries);
|
||||
|
|
@ -360,19 +377,19 @@ function onMessage(request, sender, callback) {
|
|||
async function start() {
|
||||
await loadRulesetConfig();
|
||||
|
||||
if ( process.wakeupRun === false ) {
|
||||
const rulesetsUpdated = process.wakeupRun === false &&
|
||||
await enableRulesets(rulesetConfig.enabledRulesets);
|
||||
}
|
||||
|
||||
// We need to update the regex rules only when ruleset version changes.
|
||||
if ( process.wakeupRun === false ) {
|
||||
const currentVersion = getCurrentVersion();
|
||||
if ( currentVersion !== rulesetConfig.version ) {
|
||||
ubolLog(`Version change: ${rulesetConfig.version} => ${currentVersion}`);
|
||||
updateDynamicRules().then(( ) => {
|
||||
rulesetConfig.version = currentVersion;
|
||||
saveRulesetConfig();
|
||||
});
|
||||
rulesetConfig.version = currentVersion;
|
||||
saveRulesetConfig();
|
||||
if ( rulesetsUpdated === false ) {
|
||||
updateDynamicRules();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export const rulesetConfig = {
|
|||
enabledRulesets: [ 'default' ],
|
||||
autoReload: true,
|
||||
showBlockedCount: true,
|
||||
strictBlockMode: true,
|
||||
developerMode: false,
|
||||
};
|
||||
|
||||
|
|
@ -50,6 +51,7 @@ export async function loadRulesetConfig() {
|
|||
rulesetConfig.enabledRulesets = sessionData.enabledRulesets;
|
||||
rulesetConfig.autoReload = sessionData.autoReload ?? true;
|
||||
rulesetConfig.showBlockedCount = sessionData.showBlockedCount ?? true;
|
||||
rulesetConfig.strictBlockMode = sessionData.strictBlockMode ?? true;
|
||||
rulesetConfig.developerMode = sessionData.developerMode ?? false;
|
||||
process.wakeupRun = true;
|
||||
return;
|
||||
|
|
@ -60,6 +62,7 @@ export async function loadRulesetConfig() {
|
|||
rulesetConfig.enabledRulesets = localData.enabledRulesets;
|
||||
rulesetConfig.autoReload = localData.autoReload ?? true;
|
||||
rulesetConfig.showBlockedCount = localData.showBlockedCount ?? true;
|
||||
rulesetConfig.strictBlockMode = localData.strictBlockMode ?? true;
|
||||
rulesetConfig.developerMode = localData.developerMode ?? false;
|
||||
sessionWrite('rulesetConfig', rulesetConfig);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -98,6 +98,12 @@ export async function sessionWrite(key, value) {
|
|||
return browser.storage.session.set({ [key]: value });
|
||||
}
|
||||
|
||||
export async function sessionRemove(key) {
|
||||
if ( browser.storage instanceof Object === false ) { return; }
|
||||
if ( browser.storage.session instanceof Object === false ) { return; }
|
||||
return browser.storage.session.remove(key);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export async function adminRead(key) {
|
||||
|
|
|
|||
|
|
@ -19,11 +19,6 @@
|
|||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
import {
|
||||
TRUSTED_DIRECTIVE_BASE_RULE_ID,
|
||||
getDynamicRules,
|
||||
} from './ruleset-manager.js';
|
||||
|
||||
import {
|
||||
broadcastMessage,
|
||||
hostnamesFromMatches,
|
||||
|
|
@ -33,12 +28,12 @@ import {
|
|||
|
||||
import {
|
||||
browser,
|
||||
dnr,
|
||||
localRead, localWrite,
|
||||
sessionRead, sessionWrite,
|
||||
} from './ext.js';
|
||||
|
||||
import { adminReadEx } from './admin.js';
|
||||
import { filteringModesToDNR } from './ruleset-manager.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
@ -74,19 +69,6 @@ const pruneHostnameFromSet = (hostname, hnSet) => {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const eqSets = (setBefore, setAfter) => {
|
||||
if ( setBefore.size !== setAfter.size ) { return false; }
|
||||
for ( const hn of setAfter ) {
|
||||
if ( setBefore.has(hn) === false ) { return false; }
|
||||
}
|
||||
for ( const hn of setBefore ) {
|
||||
if ( setAfter.has(hn) === false ) { return false; }
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const serializeModeDetails = details => {
|
||||
return {
|
||||
none: Array.from(details.none),
|
||||
|
|
@ -284,93 +266,6 @@ async function writeFilteringModeDetails(afterDetails) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
async function filteringModesToDNR(modes) {
|
||||
const dynamicRuleMap = await getDynamicRules();
|
||||
const trustedRule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID+0);
|
||||
const beforeRequestDomainSet = new Set(trustedRule?.condition.requestDomains);
|
||||
const beforeExcludedRrequestDomainSet = new Set(trustedRule?.condition.excludedRequestDomains);
|
||||
if ( trustedRule !== undefined && beforeRequestDomainSet.size === 0 ) {
|
||||
beforeRequestDomainSet.add('all-urls');
|
||||
} else {
|
||||
beforeExcludedRrequestDomainSet.add('all-urls');
|
||||
}
|
||||
|
||||
const noneHostnames = new Set([ ...modes.none ]);
|
||||
const notNoneHostnames = new Set([ ...modes.basic, ...modes.optimal, ...modes.complete ]);
|
||||
let afterRequestDomainSet = new Set();
|
||||
let afterExcludedRequestDomainSet = new Set();
|
||||
if ( noneHostnames.has('all-urls') ) {
|
||||
afterRequestDomainSet = new Set([ 'all-urls' ]);
|
||||
afterExcludedRequestDomainSet = notNoneHostnames;
|
||||
} else {
|
||||
afterRequestDomainSet = noneHostnames;
|
||||
afterExcludedRequestDomainSet = new Set([ 'all-urls' ]);
|
||||
}
|
||||
|
||||
if ( eqSets(beforeRequestDomainSet, afterRequestDomainSet) ) {
|
||||
if ( eqSets(beforeExcludedRrequestDomainSet, afterExcludedRequestDomainSet) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const removeRuleIds = [
|
||||
TRUSTED_DIRECTIVE_BASE_RULE_ID+0,
|
||||
TRUSTED_DIRECTIVE_BASE_RULE_ID+1,
|
||||
];
|
||||
dynamicRuleMap.delete(TRUSTED_DIRECTIVE_BASE_RULE_ID+0);
|
||||
dynamicRuleMap.delete(TRUSTED_DIRECTIVE_BASE_RULE_ID+1);
|
||||
|
||||
const allowEverywhere = afterRequestDomainSet.delete('all-urls');
|
||||
afterExcludedRequestDomainSet.delete('all-urls');
|
||||
|
||||
const addRules = [];
|
||||
if (
|
||||
allowEverywhere ||
|
||||
afterRequestDomainSet.size !== 0 ||
|
||||
afterExcludedRequestDomainSet.size !== 0
|
||||
) {
|
||||
const rule0 = {
|
||||
id: TRUSTED_DIRECTIVE_BASE_RULE_ID+0,
|
||||
action: { type: 'allowAllRequests' },
|
||||
condition: {
|
||||
resourceTypes: [ 'main_frame' ],
|
||||
},
|
||||
priority: 100,
|
||||
};
|
||||
if ( afterRequestDomainSet.size !== 0 ) {
|
||||
rule0.condition.requestDomains = Array.from(afterRequestDomainSet);
|
||||
} else if ( afterExcludedRequestDomainSet.size !== 0 ) {
|
||||
rule0.condition.excludedRequestDomains = Array.from(afterExcludedRequestDomainSet);
|
||||
}
|
||||
addRules.push(rule0);
|
||||
dynamicRuleMap.set(TRUSTED_DIRECTIVE_BASE_RULE_ID+0, rule0);
|
||||
// https://github.com/uBlockOrigin/uBOL-home/issues/114
|
||||
const rule1 = {
|
||||
id: TRUSTED_DIRECTIVE_BASE_RULE_ID+1,
|
||||
action: { type: 'allow' },
|
||||
condition: {
|
||||
resourceTypes: [ 'script' ],
|
||||
},
|
||||
priority: 100,
|
||||
};
|
||||
if ( rule0.condition.requestDomains ) {
|
||||
rule1.condition.initiatorDomains = rule0.condition.requestDomains.slice();
|
||||
} else if ( rule0.condition.excludedRequestDomains ) {
|
||||
rule1.condition.excludedInitiatorDomains = rule0.condition.excludedRequestDomains.slice();
|
||||
}
|
||||
addRules.push(rule1);
|
||||
dynamicRuleMap.set(TRUSTED_DIRECTIVE_BASE_RULE_ID+1, rule1);
|
||||
}
|
||||
|
||||
const updateOptions = { removeRuleIds };
|
||||
if ( addRules.length ) {
|
||||
updateOptions.addRules = addRules;
|
||||
}
|
||||
await dnr.updateDynamicRules(updateOptions);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export async function getFilteringModeDetails() {
|
||||
const actualDetails = await readFilteringModeDetails();
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import punycode from './punycode.js';
|
|||
|
||||
const popupPanelData = {};
|
||||
const currentTab = {};
|
||||
let tabHostname = '';
|
||||
const tabURL = new URL(runtime.getURL('/'));
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
@ -68,8 +68,8 @@ function setFilteringMode(level, commit = false) {
|
|||
}
|
||||
|
||||
async function commitFilteringMode() {
|
||||
if ( tabHostname === '' ) { return; }
|
||||
const targetHostname = normalizedHostname(tabHostname);
|
||||
if ( tabURL.hostname === '' ) { return; }
|
||||
const targetHostname = normalizedHostname(tabURL.hostname);
|
||||
const modeSlider = qs$('.filteringModeSlider');
|
||||
const afterLevel = parseInt(modeSlider.dataset.level, 10);
|
||||
const beforeLevel = parseInt(modeSlider.dataset.levelBefore, 10);
|
||||
|
|
@ -100,7 +100,9 @@ async function commitFilteringMode() {
|
|||
}
|
||||
if ( actualLevel !== beforeLevel && popupPanelData.autoReload ) {
|
||||
self.setTimeout(( ) => {
|
||||
browser.tabs.reload(currentTab.id);
|
||||
browser.tabs.update(currentTab.id, {
|
||||
url: tabURL.href,
|
||||
});
|
||||
}, 437);
|
||||
}
|
||||
}
|
||||
|
|
@ -317,8 +319,12 @@ async function init() {
|
|||
|
||||
let url;
|
||||
try {
|
||||
const strictBlockURL = runtime.getURL('/strictblock.');
|
||||
url = new URL(currentTab.url);
|
||||
tabHostname = url.hostname || '';
|
||||
if ( url.href.startsWith(strictBlockURL) ) {
|
||||
url = new URL(url.hash.slice(1));
|
||||
}
|
||||
tabURL.href = url.href || '';
|
||||
} catch(ex) {
|
||||
}
|
||||
|
||||
|
|
@ -326,7 +332,7 @@ async function init() {
|
|||
const response = await sendMessage({
|
||||
what: 'popupPanelData',
|
||||
origin: url.origin,
|
||||
hostname: normalizedHostname(tabHostname),
|
||||
hostname: normalizedHostname(tabURL.hostname),
|
||||
});
|
||||
if ( response instanceof Object ) {
|
||||
Object.assign(popupPanelData, response);
|
||||
|
|
@ -337,7 +343,7 @@ async function init() {
|
|||
|
||||
setFilteringMode(popupPanelData.level);
|
||||
|
||||
dom.text('#hostname', punycode.toUnicode(tabHostname));
|
||||
dom.text('#hostname', punycode.toUnicode(tabURL.hostname));
|
||||
|
||||
dom.cl.toggle('#showMatchedRules', 'enabled',
|
||||
popupPanelData.isSideloaded === true &&
|
||||
|
|
|
|||
|
|
@ -23,25 +23,44 @@ import {
|
|||
browser,
|
||||
dnr,
|
||||
i18n,
|
||||
runtime,
|
||||
} from './ext.js';
|
||||
|
||||
import {
|
||||
localRead, localRemove, localWrite,
|
||||
sessionRead, sessionRemove, sessionWrite,
|
||||
} from './ext.js';
|
||||
|
||||
import {
|
||||
rulesetConfig,
|
||||
saveRulesetConfig,
|
||||
} from './config.js';
|
||||
|
||||
|
||||
import { fetchJSON } from './fetch.js';
|
||||
import { getAdminRulesets } from './admin.js';
|
||||
import { ubolLog } from './debug.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const RULE_REALM_SIZE = 1000000;
|
||||
const REGEXES_REALM_START = 1000000;
|
||||
const REGEXES_REALM_END = REGEXES_REALM_START + RULE_REALM_SIZE;
|
||||
const REMOVEPARAMS_REALM_START = REGEXES_REALM_END;
|
||||
const REMOVEPARAMS_REALM_END = REMOVEPARAMS_REALM_START + RULE_REALM_SIZE;
|
||||
const REDIRECT_REALM_START = REMOVEPARAMS_REALM_END;
|
||||
const REDIRECT_REALM_END = REDIRECT_REALM_START + RULE_REALM_SIZE;
|
||||
const MODIFYHEADERS_REALM_START = REDIRECT_REALM_END;
|
||||
const MODIFYHEADERS_REALM_END = MODIFYHEADERS_REALM_START + RULE_REALM_SIZE;
|
||||
const STRICTBLOCK_BASE_RULE_ID = 7000000;
|
||||
const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000;
|
||||
|
||||
let dynamicRuleId = 1;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const eqSets = (setBefore, setAfter) => {
|
||||
if ( setBefore.size !== setAfter.size ) { return false; }
|
||||
for ( const hn of setAfter ) {
|
||||
if ( setBefore.has(hn) === false ) { return false; }
|
||||
}
|
||||
for ( const hn of setBefore ) {
|
||||
if ( setAfter.has(hn) === false ) { return false; }
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function getRulesetDetails() {
|
||||
|
|
@ -59,79 +78,50 @@ function getRulesetDetails() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
function getDynamicRules() {
|
||||
if ( getDynamicRules.dynamicRuleMapPromise !== undefined ) {
|
||||
return getDynamicRules.dynamicRuleMapPromise;
|
||||
}
|
||||
getDynamicRules.dynamicRuleMapPromise = dnr.getDynamicRules().then(rules => {
|
||||
const rulesMap = new Map(rules.map(rule => [ rule.id, rule ]));
|
||||
ubolLog(`Dynamic rule count: ${rulesMap.size}`);
|
||||
ubolLog(`Available dynamic rule count: ${dnr.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES - rulesMap.size}`);
|
||||
return rulesMap;
|
||||
});
|
||||
return getDynamicRules.dynamicRuleMapPromise;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function pruneInvalidRegexRules(realm, rulesIn) {
|
||||
// Avoid testing already tested regexes
|
||||
const dynamicRules = await dnr.getDynamicRules();
|
||||
const validRegexSet = new Set(
|
||||
dynamicRules.filter(rule =>
|
||||
rule.condition?.regexFilter && true || false
|
||||
).map(rule =>
|
||||
rule.condition.regexFilter
|
||||
)
|
||||
);
|
||||
const rejectedRegexRules = [];
|
||||
|
||||
const validateRegex = regex => {
|
||||
return dnr.isRegexSupported({ regex, isCaseSensitive: false }).then(result => {
|
||||
const isSupported = result?.isSupported || false;
|
||||
pruneInvalidRegexRules.validated.set(regex, isSupported);
|
||||
if ( isSupported ) { return true; }
|
||||
rejectedRegexRules.push(`\t${regex} ${result?.reason}`);
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
// Validate regex-based rules
|
||||
const toCheck = [];
|
||||
const rejectedRegexRules = [];
|
||||
for ( const rule of rulesIn ) {
|
||||
if ( rule.condition?.regexFilter === undefined ) {
|
||||
toCheck.push(true);
|
||||
continue;
|
||||
}
|
||||
const {
|
||||
regexFilter: regex,
|
||||
isUrlFilterCaseSensitive: isCaseSensitive
|
||||
} = rule.condition;
|
||||
if ( validRegexSet.has(regex) ) {
|
||||
toCheck.push(true);
|
||||
const { regexFilter } = rule.condition;
|
||||
if ( pruneInvalidRegexRules.validated.has(regexFilter) ) {
|
||||
toCheck.push(pruneInvalidRegexRules.validated.get(regexFilter));
|
||||
continue;
|
||||
}
|
||||
if ( pruneInvalidRegexRules.invalidRegexes.has(regex) ) {
|
||||
toCheck.push(false);
|
||||
continue;
|
||||
}
|
||||
toCheck.push(
|
||||
dnr.isRegexSupported({ regex, isCaseSensitive }).then(result => {
|
||||
if ( result.isSupported ) { return true; }
|
||||
pruneInvalidRegexRules.invalidRegexes.add(regex);
|
||||
rejectedRegexRules.push(`\t${regex} ${result.reason}`);
|
||||
return false;
|
||||
})
|
||||
);
|
||||
toCheck.push(validateRegex(regexFilter));
|
||||
}
|
||||
|
||||
// Collate results
|
||||
const isValid = await Promise.all(toCheck);
|
||||
|
||||
if ( rejectedRegexRules.length !== 0 ) {
|
||||
ubolLog(
|
||||
`${realm} realm: rejected regexes:\n`,
|
||||
ubolLog(`${realm} realm: rejected regexes:\n`,
|
||||
rejectedRegexRules.join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
return rulesIn.filter((v, i) => isValid[i]);
|
||||
}
|
||||
pruneInvalidRegexRules.invalidRegexes = new Set();
|
||||
pruneInvalidRegexRules.validated = new Map();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function updateRegexRules() {
|
||||
async function updateRegexRules(toAdd) {
|
||||
const rulesetDetails = await getEnabledRulesetsDetails();
|
||||
|
||||
// Fetch regexes for all enabled rulesets
|
||||
|
|
@ -144,69 +134,31 @@ async function updateRegexRules() {
|
|||
|
||||
// Collate all regexes rules
|
||||
const allRules = [];
|
||||
let regexRuleId = REGEXES_REALM_START;
|
||||
for ( const rules of regexRulesets ) {
|
||||
if ( Array.isArray(rules) === false ) { continue; }
|
||||
for ( const rule of rules ) {
|
||||
rule.id = regexRuleId++;
|
||||
rule.id = dynamicRuleId++;
|
||||
allRules.push(rule);
|
||||
}
|
||||
}
|
||||
if ( allRules.length === 0 ) { return; }
|
||||
|
||||
const validatedRules = await pruneInvalidRegexRules('regexes', allRules);
|
||||
const validRules = await pruneInvalidRegexRules('regexes', allRules);
|
||||
if ( validRules.length === 0 ) { return; }
|
||||
|
||||
// Add validated regex rules to dynamic ruleset without affecting rules
|
||||
// outside regex rules realm.
|
||||
const dynamicRuleMap = await getDynamicRules();
|
||||
const newRuleMap = new Map(validatedRules.map(rule => [ rule.id, rule ]));
|
||||
const addRules = [];
|
||||
const removeRuleIds = [];
|
||||
|
||||
for ( const oldRule of dynamicRuleMap.values() ) {
|
||||
if ( oldRule.id < REGEXES_REALM_START ) { continue; }
|
||||
if ( oldRule.id >= REGEXES_REALM_END ) { continue; }
|
||||
const newRule = newRuleMap.get(oldRule.id);
|
||||
if ( newRule === undefined ) {
|
||||
removeRuleIds.push(oldRule.id);
|
||||
dynamicRuleMap.delete(oldRule.id);
|
||||
} else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) {
|
||||
removeRuleIds.push(oldRule.id);
|
||||
addRules.push(newRule);
|
||||
dynamicRuleMap.set(oldRule.id, newRule);
|
||||
}
|
||||
}
|
||||
|
||||
for ( const newRule of newRuleMap.values() ) {
|
||||
if ( dynamicRuleMap.has(newRule.id) ) { continue; }
|
||||
addRules.push(newRule);
|
||||
dynamicRuleMap.set(newRule.id, newRule);
|
||||
}
|
||||
|
||||
if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
|
||||
|
||||
if ( removeRuleIds.length !== 0 ) {
|
||||
ubolLog(`Remove ${removeRuleIds.length} DNR regex rules`);
|
||||
}
|
||||
if ( addRules.length !== 0 ) {
|
||||
ubolLog(`Add ${addRules.length} DNR regex rules`);
|
||||
}
|
||||
|
||||
return dnr.updateDynamicRules({ addRules, removeRuleIds }).catch(reason => {
|
||||
console.error(`updateRegexRules() / ${reason}`);
|
||||
});
|
||||
ubolLog(`Add ${validRules.length} DNR regex rules`);
|
||||
toAdd.push(...validRules);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function updateRemoveparamRules() {
|
||||
async function updateRemoveparamRules(toAdd) {
|
||||
const [
|
||||
hasOmnipotence,
|
||||
rulesetDetails,
|
||||
dynamicRuleMap,
|
||||
] = await Promise.all([
|
||||
browser.permissions.contains({ origins: [ '<all_urls>' ] }),
|
||||
getEnabledRulesetsDetails(),
|
||||
getDynamicRules(),
|
||||
]);
|
||||
|
||||
// Fetch removeparam rules for all enabled rulesets
|
||||
|
|
@ -220,69 +172,32 @@ async function updateRemoveparamRules() {
|
|||
// Removeparam rules can only be enforced with omnipotence
|
||||
const allRules = [];
|
||||
if ( hasOmnipotence ) {
|
||||
let removeparamRuleId = REMOVEPARAMS_REALM_START;
|
||||
for ( const rules of removeparamRulesets ) {
|
||||
if ( Array.isArray(rules) === false ) { continue; }
|
||||
for ( const rule of rules ) {
|
||||
rule.id = removeparamRuleId++;
|
||||
rule.id = dynamicRuleId++;
|
||||
allRules.push(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( allRules.length === 0 ) { return; }
|
||||
|
||||
const validatedRules = await pruneInvalidRegexRules('removeparam', allRules);
|
||||
const validRules = await pruneInvalidRegexRules('removeparam', allRules);
|
||||
if ( validRules.length === 0 ) { return; }
|
||||
|
||||
// Add removeparam rules to dynamic ruleset without affecting rules
|
||||
// outside removeparam rules realm.
|
||||
const newRuleMap = new Map(validatedRules.map(rule => [ rule.id, rule ]));
|
||||
const addRules = [];
|
||||
const removeRuleIds = [];
|
||||
|
||||
for ( const oldRule of dynamicRuleMap.values() ) {
|
||||
if ( oldRule.id < REMOVEPARAMS_REALM_START ) { continue; }
|
||||
if ( oldRule.id >= REMOVEPARAMS_REALM_END ) { continue; }
|
||||
const newRule = newRuleMap.get(oldRule.id);
|
||||
if ( newRule === undefined ) {
|
||||
removeRuleIds.push(oldRule.id);
|
||||
dynamicRuleMap.delete(oldRule.id);
|
||||
} else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) {
|
||||
removeRuleIds.push(oldRule.id);
|
||||
addRules.push(newRule);
|
||||
dynamicRuleMap.set(oldRule.id, newRule);
|
||||
}
|
||||
}
|
||||
|
||||
for ( const newRule of newRuleMap.values() ) {
|
||||
if ( dynamicRuleMap.has(newRule.id) ) { continue; }
|
||||
addRules.push(newRule);
|
||||
dynamicRuleMap.set(newRule.id, newRule);
|
||||
}
|
||||
|
||||
if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
|
||||
|
||||
if ( removeRuleIds.length !== 0 ) {
|
||||
ubolLog(`Remove ${removeRuleIds.length} DNR removeparam rules`);
|
||||
}
|
||||
if ( addRules.length !== 0 ) {
|
||||
ubolLog(`Add ${addRules.length} DNR removeparam rules`);
|
||||
}
|
||||
|
||||
return dnr.updateDynamicRules({ addRules, removeRuleIds }).catch(reason => {
|
||||
console.error(`updateRemoveparamRules() / ${reason}`);
|
||||
});
|
||||
ubolLog(`Add ${validRules.length} DNR removeparam rules`);
|
||||
toAdd.push(...validRules);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function updateRedirectRules() {
|
||||
async function updateRedirectRules(toAdd) {
|
||||
const [
|
||||
hasOmnipotence,
|
||||
rulesetDetails,
|
||||
dynamicRuleMap,
|
||||
] = await Promise.all([
|
||||
browser.permissions.contains({ origins: [ '<all_urls>' ] }),
|
||||
getEnabledRulesetsDetails(),
|
||||
getDynamicRules(),
|
||||
]);
|
||||
|
||||
// Fetch redirect rules for all enabled rulesets
|
||||
|
|
@ -296,69 +211,32 @@ async function updateRedirectRules() {
|
|||
// Redirect rules can only be enforced with omnipotence
|
||||
const allRules = [];
|
||||
if ( hasOmnipotence ) {
|
||||
let redirectRuleId = REDIRECT_REALM_START;
|
||||
for ( const rules of redirectRulesets ) {
|
||||
if ( Array.isArray(rules) === false ) { continue; }
|
||||
for ( const rule of rules ) {
|
||||
rule.id = redirectRuleId++;
|
||||
rule.id = dynamicRuleId++;
|
||||
allRules.push(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( allRules.length === 0 ) { return; }
|
||||
|
||||
const validatedRules = await pruneInvalidRegexRules('redirect', allRules);
|
||||
const validRules = await pruneInvalidRegexRules('redirect', allRules);
|
||||
if ( validRules.length === 0 ) { return; }
|
||||
|
||||
// Add redirect rules to dynamic ruleset without affecting rules
|
||||
// outside redirect rules realm.
|
||||
const newRuleMap = new Map(validatedRules.map(rule => [ rule.id, rule ]));
|
||||
const addRules = [];
|
||||
const removeRuleIds = [];
|
||||
|
||||
for ( const oldRule of dynamicRuleMap.values() ) {
|
||||
if ( oldRule.id < REDIRECT_REALM_START ) { continue; }
|
||||
if ( oldRule.id >= REDIRECT_REALM_END ) { continue; }
|
||||
const newRule = newRuleMap.get(oldRule.id);
|
||||
if ( newRule === undefined ) {
|
||||
removeRuleIds.push(oldRule.id);
|
||||
dynamicRuleMap.delete(oldRule.id);
|
||||
} else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) {
|
||||
removeRuleIds.push(oldRule.id);
|
||||
addRules.push(newRule);
|
||||
dynamicRuleMap.set(oldRule.id, newRule);
|
||||
}
|
||||
}
|
||||
|
||||
for ( const newRule of newRuleMap.values() ) {
|
||||
if ( dynamicRuleMap.has(newRule.id) ) { continue; }
|
||||
addRules.push(newRule);
|
||||
dynamicRuleMap.set(newRule.id, newRule);
|
||||
}
|
||||
|
||||
if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
|
||||
|
||||
if ( removeRuleIds.length !== 0 ) {
|
||||
ubolLog(`Remove ${removeRuleIds.length} DNR redirect rules`);
|
||||
}
|
||||
if ( addRules.length !== 0 ) {
|
||||
ubolLog(`Add ${addRules.length} DNR redirect rules`);
|
||||
}
|
||||
|
||||
return dnr.updateDynamicRules({ addRules, removeRuleIds }).catch(reason => {
|
||||
console.error(`updateRedirectRules() / ${reason}`);
|
||||
});
|
||||
ubolLog(`Add ${validRules.length} DNR redirect rules`);
|
||||
toAdd.push(...validRules);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function updateModifyHeadersRules() {
|
||||
async function updateModifyHeadersRules(toAdd) {
|
||||
const [
|
||||
hasOmnipotence,
|
||||
rulesetDetails,
|
||||
dynamicRuleMap,
|
||||
] = await Promise.all([
|
||||
browser.permissions.contains({ origins: [ '<all_urls>' ] }),
|
||||
getEnabledRulesetsDetails(),
|
||||
getDynamicRules(),
|
||||
]);
|
||||
|
||||
// Fetch modifyHeaders rules for all enabled rulesets
|
||||
|
|
@ -372,69 +250,312 @@ async function updateModifyHeadersRules() {
|
|||
// Redirect rules can only be enforced with omnipotence
|
||||
const allRules = [];
|
||||
if ( hasOmnipotence ) {
|
||||
let ruleId = MODIFYHEADERS_REALM_START;
|
||||
for ( const rules of rulesets ) {
|
||||
if ( Array.isArray(rules) === false ) { continue; }
|
||||
for ( const rule of rules ) {
|
||||
rule.id = ruleId++;
|
||||
rule.id = dynamicRuleId++;
|
||||
allRules.push(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( allRules.length === 0 ) { return; }
|
||||
|
||||
const validatedRules = await pruneInvalidRegexRules('modify-headers', allRules);
|
||||
const validRules = await pruneInvalidRegexRules('modify-headers', allRules);
|
||||
if ( validRules.length === 0 ) { return; }
|
||||
|
||||
// Add modifyHeaders rules to dynamic ruleset without affecting rules
|
||||
// outside modifyHeaders realm.
|
||||
const newRuleMap = new Map(validatedRules.map(rule => [ rule.id, rule ]));
|
||||
const addRules = [];
|
||||
const removeRuleIds = [];
|
||||
|
||||
for ( const oldRule of dynamicRuleMap.values() ) {
|
||||
if ( oldRule.id < MODIFYHEADERS_REALM_START ) { continue; }
|
||||
if ( oldRule.id >= MODIFYHEADERS_REALM_END ) { continue; }
|
||||
const newRule = newRuleMap.get(oldRule.id);
|
||||
if ( newRule === undefined ) {
|
||||
removeRuleIds.push(oldRule.id);
|
||||
dynamicRuleMap.delete(oldRule.id);
|
||||
} else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) {
|
||||
removeRuleIds.push(oldRule.id);
|
||||
addRules.push(newRule);
|
||||
dynamicRuleMap.set(oldRule.id, newRule);
|
||||
}
|
||||
}
|
||||
|
||||
for ( const newRule of newRuleMap.values() ) {
|
||||
if ( dynamicRuleMap.has(newRule.id) ) { continue; }
|
||||
addRules.push(newRule);
|
||||
dynamicRuleMap.set(newRule.id, newRule);
|
||||
}
|
||||
|
||||
if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
|
||||
|
||||
if ( removeRuleIds.length !== 0 ) {
|
||||
ubolLog(`Remove ${removeRuleIds.length} DNR modifyHeaders rules`);
|
||||
}
|
||||
if ( addRules.length !== 0 ) {
|
||||
ubolLog(`Add ${addRules.length} DNR modifyHeaders rules`);
|
||||
}
|
||||
|
||||
return dnr.updateDynamicRules({ addRules, removeRuleIds }).catch(reason => {
|
||||
console.error(`updateModifyHeadersRules() / ${reason}`);
|
||||
});
|
||||
ubolLog(`Add ${validRules.length} DNR modify-headers rules`);
|
||||
toAdd.push(...validRules);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// TODO: group all omnipotence-related rules into one realm.
|
||||
async function updateStrictBlockRules(dynamicRules, sessionRules) {
|
||||
if ( rulesetConfig.strictBlockMode === false ) { return; }
|
||||
|
||||
const [
|
||||
hasOmnipotence,
|
||||
rulesetDetails,
|
||||
permanentlyExcluded = [],
|
||||
temporarilyExcluded = [],
|
||||
] = await Promise.all([
|
||||
browser.permissions.contains({ origins: [ '<all_urls>' ] }),
|
||||
getEnabledRulesetsDetails(),
|
||||
localRead('excludedStrictBlockHostnames'),
|
||||
sessionRead('excludedStrictBlockHostnames'),
|
||||
]);
|
||||
|
||||
// Fetch strick-block hostnames
|
||||
const toFetch = [];
|
||||
for ( const details of rulesetDetails ) {
|
||||
if ( details.rules.strictblock === 0 ) { continue; }
|
||||
toFetch.push(fetchJSON(`/rulesets/strictblock/${details.id}`));
|
||||
}
|
||||
const strictblockRulesets = await Promise.all(toFetch);
|
||||
|
||||
// Strict-block rules can only be enforced with omnipotence
|
||||
let toStrictBlock = new Set();
|
||||
if ( hasOmnipotence ) {
|
||||
for ( const hostnames of strictblockRulesets ) {
|
||||
if ( Array.isArray(hostnames) === false ) { continue; }
|
||||
toStrictBlock = toStrictBlock.union(new Set(hostnames));
|
||||
}
|
||||
} else {
|
||||
if ( permanentlyExcluded.length !== 0 ) {
|
||||
localRemove('excludedStrictBlockHostnames');
|
||||
permanentlyExcluded.length = 0;
|
||||
}
|
||||
if ( temporarilyExcluded.length !== 0 ) {
|
||||
sessionRemove('excludedStrictBlockHostnames');
|
||||
temporarilyExcluded.length = 0;
|
||||
}
|
||||
}
|
||||
for ( const hn of permanentlyExcluded ) {
|
||||
toStrictBlock.delete(hn);
|
||||
}
|
||||
if ( toStrictBlock.size === 0 ) { return; }
|
||||
const manifest = runtime.getManifest();
|
||||
let strictblockPath = '';
|
||||
for ( const war of manifest.web_accessible_resources ) {
|
||||
if ( war.resources.length !== 1 ) { continue; }
|
||||
if ( war.resources[0].startsWith('/strictblock.') === false ) { continue; }
|
||||
strictblockPath = runtime.getURL(war.resources[0]);
|
||||
break;
|
||||
}
|
||||
if ( strictblockPath === '' ) { return; }
|
||||
const dynamicRule = {
|
||||
id: STRICTBLOCK_BASE_RULE_ID,
|
||||
action: {
|
||||
type: 'redirect',
|
||||
redirect: {
|
||||
regexSubstitution: `${strictblockPath}#\\0`,
|
||||
},
|
||||
},
|
||||
condition: {
|
||||
regexFilter: '^https?://.+',
|
||||
requestDomains: Array.from(toStrictBlock),
|
||||
resourceTypes: [ 'main_frame' ],
|
||||
},
|
||||
priority: 29,
|
||||
};
|
||||
if ( permanentlyExcluded.length !== 0 ) {
|
||||
dynamicRule.condition.excludedRequestDomains = permanentlyExcluded;
|
||||
}
|
||||
dynamicRules.push(dynamicRule);
|
||||
ubolLog(`Add 1 DNR dynamic rule with ${toStrictBlock.size} strictblock domains`);
|
||||
|
||||
if ( temporarilyExcluded.length === 0 ) { return; }
|
||||
sessionRules.push({
|
||||
id: STRICTBLOCK_BASE_RULE_ID,
|
||||
action: {
|
||||
type: 'allow',
|
||||
},
|
||||
condition: {
|
||||
requestDomains: temporarilyExcluded,
|
||||
resourceTypes: [ 'main_frame' ],
|
||||
},
|
||||
priority: 29,
|
||||
});
|
||||
ubolLog(`Add 1 DNR session rule with ${temporarilyExcluded.length} excluded strictblock domains`);
|
||||
}
|
||||
|
||||
async function commitStrictBlockRules() {
|
||||
const [
|
||||
beforePermanentRules,
|
||||
beforeTemporaryRules,
|
||||
] = await Promise.all([
|
||||
dnr.getDynamicRules({ ruleIds: [ STRICTBLOCK_BASE_RULE_ID ] }),
|
||||
dnr.getSessionRules({ ruleIds: [ STRICTBLOCK_BASE_RULE_ID ] }),
|
||||
]);
|
||||
if ( beforePermanentRules?.length ) {
|
||||
ubolLog(`Remove 1 DNR dynamic strictblock rule`);
|
||||
}
|
||||
if ( beforeTemporaryRules?.length ) {
|
||||
ubolLog(`Remove 1 DNR session strictblock rule`);
|
||||
}
|
||||
const afterPermanentRules = [];
|
||||
const afterTemporaryRules = [];
|
||||
await updateStrictBlockRules(afterPermanentRules, afterTemporaryRules)
|
||||
return Promise.all([
|
||||
dnr.updateDynamicRules({
|
||||
addRules: afterPermanentRules,
|
||||
removeRuleIds: beforePermanentRules.map(rule => rule.id),
|
||||
}),
|
||||
dnr.updateSessionRules({
|
||||
addRules: afterTemporaryRules,
|
||||
removeRuleIds: beforeTemporaryRules.map(rule => rule.id),
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
async function excludeFromStrictBlock(hostname, permanent) {
|
||||
if ( typeof hostname !== 'string' || hostname === '' ) { return; }
|
||||
const readFn = permanent ? localRead : sessionRead;
|
||||
const hostnames = new Set(await readFn('excludedStrictBlockHostnames'));
|
||||
hostnames.add(hostname);
|
||||
const writeFn = permanent ? localWrite : sessionWrite;
|
||||
await writeFn('excludedStrictBlockHostnames', Array.from(hostnames));
|
||||
return commitStrictBlockRules();
|
||||
}
|
||||
|
||||
async function setStrictBlockMode(state) {
|
||||
const newState = Boolean(state);
|
||||
if ( newState === rulesetConfig.strictBlockMode ) { return; }
|
||||
rulesetConfig.strictBlockMode = newState;
|
||||
const promises = [ saveRulesetConfig() ];
|
||||
if ( newState === false ) {
|
||||
promises.push(
|
||||
localRemove('excludedStrictBlockHostnames'),
|
||||
sessionRemove('excludedStrictBlockHostnames')
|
||||
);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
return commitStrictBlockRules();
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function updateDynamicRules() {
|
||||
return Promise.all([
|
||||
updateRegexRules(),
|
||||
updateRemoveparamRules(),
|
||||
updateRedirectRules(),
|
||||
updateModifyHeadersRules(),
|
||||
dynamicRuleId = 1;
|
||||
const dynamicRules = [];
|
||||
const sessionRules = [];
|
||||
const [
|
||||
dynamicRuleIds,
|
||||
sessionRuleIds,
|
||||
] = await Promise.all([
|
||||
dnr.getDynamicRules().then(rules =>
|
||||
rules.map(rule => rule.id)
|
||||
.filter(id => id < TRUSTED_DIRECTIVE_BASE_RULE_ID)
|
||||
),
|
||||
dnr.getSessionRules().then(rules => rules.map(rule => rule.id)),
|
||||
updateRegexRules(dynamicRules),
|
||||
updateRemoveparamRules(dynamicRules),
|
||||
updateRedirectRules(dynamicRules),
|
||||
updateModifyHeadersRules(dynamicRules),
|
||||
updateStrictBlockRules(dynamicRules, sessionRules),
|
||||
]);
|
||||
if ( dynamicRules.length === 0 && dynamicRuleIds.length === 0 ) { return; }
|
||||
const promises = [];
|
||||
if ( dynamicRules.length !== 0 || dynamicRuleIds.length !== 0 ) {
|
||||
promises.push(
|
||||
dnr.updateDynamicRules({
|
||||
addRules: dynamicRules,
|
||||
removeRuleIds: dynamicRuleIds,
|
||||
}).then(( ) => {
|
||||
if ( dynamicRuleIds.length !== 0 ) {
|
||||
ubolLog(`Remove ${dynamicRuleIds.length} dynamic DNR rules`);
|
||||
}
|
||||
if ( dynamicRules.length !== 0 ) {
|
||||
ubolLog(`Add ${dynamicRules.length} dynamic DNR rules`);
|
||||
}
|
||||
}).catch(reason => {
|
||||
console.error(`updateDynamicRules() / ${reason}`);
|
||||
})
|
||||
);
|
||||
}
|
||||
if ( sessionRules.length !== 0 || sessionRuleIds.length !== 0 ) {
|
||||
promises.push(
|
||||
dnr.updateSessionRules({
|
||||
addRules: sessionRules,
|
||||
removeRuleIds: sessionRuleIds,
|
||||
}).then(( ) => {
|
||||
if ( sessionRuleIds.length !== 0 ) {
|
||||
ubolLog(`Remove ${sessionRuleIds.length} session DNR rules`);
|
||||
}
|
||||
if ( sessionRules.length !== 0 ) {
|
||||
ubolLog(`Add ${sessionRules.length} session DNR rules`);
|
||||
}
|
||||
}).catch(reason => {
|
||||
console.error(`updateSessionRules() / ${reason}`);
|
||||
})
|
||||
);
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function filteringModesToDNR(modes) {
|
||||
const trustedRules = await dnr.getDynamicRules({
|
||||
ruleIds: [ TRUSTED_DIRECTIVE_BASE_RULE_ID+0 ],
|
||||
});
|
||||
const trustedRule = trustedRules.length !== 0 && trustedRules[0] || undefined;
|
||||
const beforeRequestDomainSet = new Set(trustedRule?.condition.requestDomains);
|
||||
const beforeExcludedRrequestDomainSet = new Set(trustedRule?.condition.excludedRequestDomains);
|
||||
if ( trustedRule !== undefined && beforeRequestDomainSet.size === 0 ) {
|
||||
beforeRequestDomainSet.add('all-urls');
|
||||
} else {
|
||||
beforeExcludedRrequestDomainSet.add('all-urls');
|
||||
}
|
||||
|
||||
const noneHostnames = new Set([ ...modes.none ]);
|
||||
const notNoneHostnames = new Set([ ...modes.basic, ...modes.optimal, ...modes.complete ]);
|
||||
let afterRequestDomainSet = new Set();
|
||||
let afterExcludedRequestDomainSet = new Set();
|
||||
if ( noneHostnames.has('all-urls') ) {
|
||||
afterRequestDomainSet = new Set([ 'all-urls' ]);
|
||||
afterExcludedRequestDomainSet = notNoneHostnames;
|
||||
} else {
|
||||
afterRequestDomainSet = noneHostnames;
|
||||
afterExcludedRequestDomainSet = new Set([ 'all-urls' ]);
|
||||
}
|
||||
|
||||
if ( eqSets(beforeRequestDomainSet, afterRequestDomainSet) ) {
|
||||
if ( eqSets(beforeExcludedRrequestDomainSet, afterExcludedRequestDomainSet) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const removeRuleIds = [];
|
||||
if ( trustedRule ) {
|
||||
removeRuleIds.push(
|
||||
TRUSTED_DIRECTIVE_BASE_RULE_ID+0,
|
||||
TRUSTED_DIRECTIVE_BASE_RULE_ID+1
|
||||
);
|
||||
}
|
||||
|
||||
const allowEverywhere = afterRequestDomainSet.delete('all-urls');
|
||||
afterExcludedRequestDomainSet.delete('all-urls');
|
||||
|
||||
const addRules = [];
|
||||
if (
|
||||
allowEverywhere ||
|
||||
afterRequestDomainSet.size !== 0 ||
|
||||
afterExcludedRequestDomainSet.size !== 0
|
||||
) {
|
||||
const rule0 = {
|
||||
id: TRUSTED_DIRECTIVE_BASE_RULE_ID+0,
|
||||
action: { type: 'allowAllRequests' },
|
||||
condition: {
|
||||
resourceTypes: [ 'main_frame' ],
|
||||
},
|
||||
priority: 100,
|
||||
};
|
||||
if ( afterRequestDomainSet.size !== 0 ) {
|
||||
rule0.condition.requestDomains = Array.from(afterRequestDomainSet);
|
||||
} else if ( afterExcludedRequestDomainSet.size !== 0 ) {
|
||||
rule0.condition.excludedRequestDomains = Array.from(afterExcludedRequestDomainSet);
|
||||
}
|
||||
addRules.push(rule0);
|
||||
// https://github.com/uBlockOrigin/uBOL-home/issues/114
|
||||
const rule1 = {
|
||||
id: TRUSTED_DIRECTIVE_BASE_RULE_ID+1,
|
||||
action: { type: 'allow' },
|
||||
condition: {
|
||||
resourceTypes: [ 'script' ],
|
||||
},
|
||||
priority: 100,
|
||||
};
|
||||
if ( rule0.condition.requestDomains ) {
|
||||
rule1.condition.initiatorDomains = rule0.condition.requestDomains.slice();
|
||||
} else if ( rule0.condition.excludedRequestDomains ) {
|
||||
rule1.condition.excludedInitiatorDomains = rule0.condition.excludedRequestDomains.slice();
|
||||
}
|
||||
addRules.push(rule1);
|
||||
}
|
||||
|
||||
if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
|
||||
|
||||
return dnr.updateDynamicRules({ addRules, removeRuleIds });
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
@ -511,7 +632,7 @@ async function enableRulesets(ids) {
|
|||
}
|
||||
|
||||
if ( enableRulesetSet.size === 0 && disableRulesetSet.size === 0 ) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const enableRulesetIds = Array.from(enableRulesetSet);
|
||||
|
|
@ -525,7 +646,9 @@ async function enableRulesets(ids) {
|
|||
}
|
||||
await dnr.updateEnabledRulesets({ enableRulesetIds, disableRulesetIds });
|
||||
|
||||
return updateDynamicRules();
|
||||
await updateDynamicRules();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
@ -550,11 +673,12 @@ async function getEnabledRulesetsDetails() {
|
|||
/******************************************************************************/
|
||||
|
||||
export {
|
||||
TRUSTED_DIRECTIVE_BASE_RULE_ID,
|
||||
getRulesetDetails,
|
||||
getDynamicRules,
|
||||
enableRulesets,
|
||||
defaultRulesetsFromLanguage,
|
||||
enableRulesets,
|
||||
excludeFromStrictBlock,
|
||||
filteringModesToDNR,
|
||||
getRulesetDetails,
|
||||
getEnabledRulesetsDetails,
|
||||
setStrictBlockMode,
|
||||
updateDynamicRules,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ function renderWidgets() {
|
|||
}
|
||||
}
|
||||
|
||||
qs$('#strictBlockMode input[type="checkbox"]').checked = cachedRulesetData.strictBlockMode;
|
||||
|
||||
{
|
||||
dom.prop('#developerMode input[type="checkbox"]', 'checked',
|
||||
Boolean(cachedRulesetData.developerMode)
|
||||
|
|
@ -146,6 +148,13 @@ dom.on('#showBlockedCount input[type="checkbox"]', 'change', ev => {
|
|||
});
|
||||
});
|
||||
|
||||
dom.on('#strictBlockMode input[type="checkbox"]', 'change', ev => {
|
||||
sendMessage({
|
||||
what: 'setStrictBlockMode',
|
||||
state: ev.target.checked,
|
||||
});
|
||||
});
|
||||
|
||||
dom.on('#developerMode input[type="checkbox"]', 'change', ev => {
|
||||
sendMessage({
|
||||
what: 'setDeveloperMode',
|
||||
|
|
@ -240,6 +249,13 @@ listen.onmessage = ev => {
|
|||
}
|
||||
}
|
||||
|
||||
if ( message.strictBlockMode !== undefined ) {
|
||||
if ( message.strictBlockMode !== local.strictBlockMode ) {
|
||||
local.strictBlockMode = message.strictBlockMode;
|
||||
render = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( message.adminRulesets !== undefined ) {
|
||||
if ( hashFromIterable(message.adminRulesets) !== hashFromIterable(local.adminRulesets) ) {
|
||||
local.adminRulesets = message.adminRulesets;
|
||||
|
|
|
|||
178
platform/mv3/extension/js/strictblock.js
Normal file
178
platform/mv3/extension/js/strictblock.js
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
Copyright (C) 2024-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 { dom, qs$ } from './dom.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
import { sendMessage } from './ext.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const toURL = new URL('about:blank');
|
||||
|
||||
function setURL(url) {
|
||||
try {
|
||||
toURL.href = url;
|
||||
} catch(_) {
|
||||
}
|
||||
}
|
||||
|
||||
setURL(self.location.hash.slice(1));
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function urlToFragment(raw) {
|
||||
try {
|
||||
const fragment = new DocumentFragment();
|
||||
const url = new URL(raw);
|
||||
const hn = url.hostname;
|
||||
const i = raw.indexOf(hn);
|
||||
const b = document.createElement('b');
|
||||
b.append(hn);
|
||||
fragment.append(raw.slice(0,i), b, raw.slice(i+hn.length));
|
||||
return fragment;
|
||||
} catch(_) {
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function proceed() {
|
||||
await sendMessage({
|
||||
what: 'excludeFromStrictBlock',
|
||||
hostname: toURL.hostname,
|
||||
permanent: qs$('#disableWarning').checked,
|
||||
});
|
||||
window.location.replace(toURL.href);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
dom.clear('#theURL > p > span:first-of-type');
|
||||
qs$('#theURL > p > span:first-of-type').append(urlToFragment(toURL.href));
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/691
|
||||
// Parse URL to extract as much useful information as possible. This is
|
||||
// useful to assist the user in deciding whether to navigate to the web page.
|
||||
(( ) => {
|
||||
const reURL = /^https?:\/\//;
|
||||
|
||||
const liFromParam = function(name, value) {
|
||||
if ( value === '' ) {
|
||||
value = name;
|
||||
name = '';
|
||||
}
|
||||
const li = dom.create('li');
|
||||
let span = dom.create('span');
|
||||
dom.text(span, name);
|
||||
li.appendChild(span);
|
||||
if ( name !== '' && value !== '' ) {
|
||||
li.appendChild(document.createTextNode(' = '));
|
||||
}
|
||||
span = dom.create('span');
|
||||
if ( reURL.test(value) ) {
|
||||
const a = dom.create('a');
|
||||
dom.attr(a, 'href', value);
|
||||
dom.text(a, value);
|
||||
span.appendChild(a);
|
||||
} else {
|
||||
dom.text(span, value);
|
||||
}
|
||||
li.appendChild(span);
|
||||
return li;
|
||||
};
|
||||
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/1649
|
||||
// Limit recursion.
|
||||
const renderParams = function(parentNode, rawURL, depth = 0) {
|
||||
let url;
|
||||
try {
|
||||
url = new URL(rawURL);
|
||||
} catch(ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const search = url.search.slice(1);
|
||||
if ( search === '' ) { return false; }
|
||||
|
||||
url.search = '';
|
||||
const li = liFromParam(i18n$('strictblockNoParamsPrompt'), url.href);
|
||||
parentNode.appendChild(li);
|
||||
|
||||
const params = new self.URLSearchParams(search);
|
||||
for ( const [ name, value ] of params ) {
|
||||
const li = liFromParam(name, value);
|
||||
if ( depth < 2 && reURL.test(value) ) {
|
||||
const ul = dom.create('ul');
|
||||
renderParams(ul, value, depth + 1);
|
||||
li.appendChild(ul);
|
||||
}
|
||||
parentNode.appendChild(li);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if ( renderParams(qs$('#parsed'), toURL.href) === false ) { return; }
|
||||
|
||||
dom.cl.remove('#toggleParse', 'hidden');
|
||||
|
||||
dom.on('#toggleParse', 'click', ( ) => {
|
||||
dom.cl.toggle('#theURL', 'collapsed');
|
||||
//vAPI.localStorage.setItem(
|
||||
// 'document-blocked-expand-url',
|
||||
// (dom.cl.has('#theURL', 'collapsed') === false).toString()
|
||||
//);
|
||||
});
|
||||
|
||||
//vAPI.localStorage.getItemAsync('document-blocked-expand-url').then(value => {
|
||||
// dom.cl.toggle('#theURL', 'collapsed', value !== 'true' && value !== true);
|
||||
//});
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// https://www.reddit.com/r/uBlockOrigin/comments/breeux/
|
||||
if ( window.history.length > 1 ) {
|
||||
dom.on('#back', 'click', ( ) => {
|
||||
window.history.back();
|
||||
});
|
||||
qs$('#bye').style.display = 'none';
|
||||
} else {
|
||||
dom.on('#bye', 'click', ( ) => {
|
||||
window.close();
|
||||
});
|
||||
qs$('#back').style.display = 'none';
|
||||
}
|
||||
|
||||
dom.on('#disableWarning', 'change', ev => {
|
||||
const checked = ev.target.checked;
|
||||
dom.cl.toggle('[data-i18n="strictblockBack"]', 'disabled', checked);
|
||||
dom.cl.toggle('[data-i18n="strictblockClose"]', 'disabled', checked);
|
||||
});
|
||||
|
||||
dom.on('#proceed', 'click', ( ) => {
|
||||
proceed();
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
@ -16,10 +16,10 @@
|
|||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "uBOLite@raymondhill.net",
|
||||
"strict_min_version": "114.0"
|
||||
"strict_min_version": "127.0"
|
||||
},
|
||||
"gecko_android": {
|
||||
"strict_min_version": "114.0"
|
||||
"strict_min_version": "127.0"
|
||||
}
|
||||
},
|
||||
"declarative_net_request": {
|
||||
|
|
@ -50,6 +50,5 @@
|
|||
"storage"
|
||||
],
|
||||
"short_name": "uBO Lite",
|
||||
"version": "1.0",
|
||||
"web_accessible_resources": []
|
||||
"version": "1.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -433,6 +433,33 @@ async function processNetworkFilters(assetDetails, network) {
|
|||
);
|
||||
}
|
||||
|
||||
const strictBlocked = new Set();
|
||||
for ( const rule of plainGood ) {
|
||||
if ( rule.action.type !== 'block' ) { continue; }
|
||||
if ( rule.condition.domainType ) { continue; }
|
||||
if ( rule.condition.regexFilter ) { continue; }
|
||||
if ( rule.condition.urlFilter ) { continue; }
|
||||
if ( rule.condition.requestMethods ) { continue; }
|
||||
if ( rule.condition.excludedRequestMethods ) { continue; }
|
||||
if ( rule.condition.resourceTypes ) { continue; }
|
||||
if ( rule.condition.excludedResourceTypes ) { continue; }
|
||||
if ( rule.condition.responseHeaders ) { continue; }
|
||||
if ( rule.condition.excludedResponseHeaders ) { continue; }
|
||||
if ( rule.condition.initiatorDomains ) { continue; }
|
||||
if ( rule.condition.excludedInitiatorDomains ) { continue; }
|
||||
if ( rule.condition.requestDomains === undefined ) { continue; }
|
||||
if ( rule.condition.excludedRequestDomains ) { continue; }
|
||||
for ( const hn of rule.condition.requestDomains ) {
|
||||
strictBlocked.add(hn);
|
||||
}
|
||||
}
|
||||
if ( strictBlocked.size !== 0 ) {
|
||||
writeFile(
|
||||
`${rulesetDir}/strictblock/${assetDetails.id}.json`,
|
||||
toJSONRuleset(Array.from(strictBlocked))
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
total: rules.length,
|
||||
plain: plainGood.length,
|
||||
|
|
@ -442,6 +469,7 @@ async function processNetworkFilters(assetDetails, network) {
|
|||
removeparam: removeparamsGood.length,
|
||||
redirect: redirects.length,
|
||||
modifyHeaders: modifyHeaders.length,
|
||||
strictblock: strictBlocked.size,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1085,6 +1113,7 @@ async function rulesetFromURLs(assetDetails) {
|
|||
removeparam: netStats.removeparam,
|
||||
redirect: netStats.redirect,
|
||||
modifyHeaders: netStats.modifyHeaders,
|
||||
strictblock: netStats.strictblock,
|
||||
discarded: netStats.discarded,
|
||||
rejected: netStats.rejected,
|
||||
},
|
||||
|
|
@ -1332,6 +1361,15 @@ async function main() {
|
|||
// Patch declarative_net_request key
|
||||
manifest.declarative_net_request = { rule_resources: ruleResources };
|
||||
// Patch web_accessible_resources key
|
||||
manifest.web_accessible_resources = manifest.web_accessible_resources || [];
|
||||
// Strict-block-related resource
|
||||
const strictblockDocument = `strictblock.${secret}.html`;
|
||||
copyFile('./strictblock.html', `${outputDir}/${strictblockDocument}`);
|
||||
manifest.web_accessible_resources.push({
|
||||
resources: [ `/${strictblockDocument}` ],
|
||||
matches: [ '<all_urls>' ],
|
||||
});
|
||||
// Secondary resources
|
||||
const web_accessible_resources = {
|
||||
resources: Array.from(requiredRedirectResources).map(path => `/${path}`),
|
||||
matches: [ '<all_urls>' ],
|
||||
|
|
@ -1339,7 +1377,7 @@ async function main() {
|
|||
if ( platform === 'chromium' ) {
|
||||
web_accessible_resources.use_dynamic_url = true;
|
||||
}
|
||||
manifest.web_accessible_resources = [ web_accessible_resources ];
|
||||
manifest.web_accessible_resources.push(web_accessible_resources);
|
||||
|
||||
// Patch manifest version property
|
||||
manifest.version = version;
|
||||
|
|
|
|||
43
platform/mv3/strictblock.html
Normal file
43
platform/mv3/strictblock.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=yes">
|
||||
<title data-i18n="strictblockTitle"></title>
|
||||
<link rel="stylesheet" href="css/default.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/fa-icons.css">
|
||||
<link rel="stylesheet" href="css/strictblock.css">
|
||||
<link rel="shortcut icon" type="image/png" href="img/icon_64.png"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="rootContainer">
|
||||
<div id="warningSign">
|
||||
<a class="fa-icon" href="https://github.com/gorhill/uBlock/wiki/Strict-blocking" target="_blank" rel="noopener noreferrer">exclamation-triangle</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p data-i18n="strictblockSentence1">_</p>
|
||||
<div id="theURL" class="collapsed">
|
||||
<p class="code"><span> </span><span id="toggleParse" class="hidden"><span class="fa-icon">zoom-in</span><span class="fa-icon">zoom-out</span></span></p>
|
||||
<ul id="parsed"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="li">
|
||||
<label><span class="input checkbox"><input type="checkbox" id="disableWarning"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="strictblockDontWarn">_</span></label>
|
||||
</div>
|
||||
|
||||
<div id="actionContainer">
|
||||
<button id="back" data-i18n="strictblockBack" type="button">_<span class="hover"></span></button>
|
||||
<button id="bye" data-i18n="strictblockClose" type="button">_<span class="hover"></span></button>
|
||||
<button id="proceed" class="preferred" data-i18n="strictblockProceed" type="button"><span class="hover"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/theme.js" type="module"></script>
|
||||
<script src="js/fa-icons.js" type="module"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/strictblock.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -47,10 +47,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="templates" style="display: none;">
|
||||
<ul>
|
||||
<li class="filterList">
|
||||
<a class="filterListSource" href="asset-viewer.html?url=" target="_blank"></a> <!--
|
||||
--><a class="fa-icon filterListSupport hidden" href="#" target="_blank" rel="noopener noreferrer">home</a>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<script src="lib/hsluv/hsluv-0.1.0.min.js"></script>
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ if [ "$QUICK" != "yes" ]; then
|
|||
cp platform/mv3/package.json "$TMPDIR"/
|
||||
cp platform/mv3/*.js "$TMPDIR"/
|
||||
cp platform/mv3/*.mjs "$TMPDIR"/
|
||||
cp platform/mv3/*.html "$TMPDIR"/
|
||||
cp platform/mv3/extension/js/utils.js "$TMPDIR"/js/
|
||||
cp -R "$UBO_DIR"/src/js/resources "$TMPDIR"/js/
|
||||
cp "$UBO_DIR"/assets/assets.dev.json "$TMPDIR"/
|
||||
|
|
|
|||
Loading…
Reference in a new issue