diff --git a/platform/mv3/extension/_locales/en/messages.json b/platform/mv3/extension/_locales/en/messages.json index 9ad38953a..17a5b24cd 100644 --- a/platform/mv3/extension/_locales/en/messages.json +++ b/platform/mv3/extension/_locales/en/messages.json @@ -231,6 +231,14 @@ "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" diff --git a/platform/mv3/extension/css/settings.css b/platform/mv3/extension/css/settings.css index feefcadd8..a8b07d2a1 100644 --- a/platform/mv3/extension/css/settings.css +++ b/platform/mv3/extension/css/settings.css @@ -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"], diff --git a/platform/mv3/extension/dashboard.html b/platform/mv3/extension/dashboard.html index bcd1726bd..5ac63f3d0 100644 --- a/platform/mv3/extension/dashboard.html +++ b/platform/mv3/extension/dashboard.html @@ -31,6 +31,7 @@
+
diff --git a/platform/mv3/extension/js/background.js b/platform/mv3/extension/js/background.js index 087df6101..a58dd7063 100644 --- a/platform/mv3/extension/js/background.js +++ b/platform/mv3/extension/js/background.js @@ -50,6 +50,7 @@ import { excludeFromStrictBlock, getEnabledRulesetsDetails, getRulesetDetails, + setStrictBlockMode, updateDynamicRules, } from './ruleset-manager.js'; @@ -214,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, @@ -245,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); diff --git a/platform/mv3/extension/js/config.js b/platform/mv3/extension/js/config.js index 71524e560..e3859e63f 100644 --- a/platform/mv3/extension/js/config.js +++ b/platform/mv3/extension/js/config.js @@ -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; diff --git a/platform/mv3/extension/js/ruleset-manager.js b/platform/mv3/extension/js/ruleset-manager.js index 77b517a9c..c9490e683 100644 --- a/platform/mv3/extension/js/ruleset-manager.js +++ b/platform/mv3/extension/js/ruleset-manager.js @@ -31,16 +31,22 @@ import { 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 STRICTBLOCK_BASE_RULE_ID = 7000000; const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000; let dynamicRuleId = 1; -let sessionRuleId = 1; /******************************************************************************/ @@ -264,6 +270,8 @@ async function updateModifyHeadersRules(toAdd) { /******************************************************************************/ async function updateStrictBlockRules(dynamicRules, sessionRules) { + if ( rulesetConfig.strictBlockMode === false ) { return; } + const [ hasOmnipotence, rulesetDetails, @@ -315,7 +323,7 @@ async function updateStrictBlockRules(dynamicRules, sessionRules) { } if ( strictblockPath === '' ) { return; } const dynamicRule = { - id: dynamicRuleId++, + id: STRICTBLOCK_BASE_RULE_ID, action: { type: 'redirect', redirect: { @@ -337,7 +345,7 @@ async function updateStrictBlockRules(dynamicRules, sessionRules) { if ( temporarilyExcluded.length === 0 ) { return; } sessionRules.push({ - id: sessionRuleId++, + id: STRICTBLOCK_BASE_RULE_ID, action: { type: 'allow', }, @@ -350,10 +358,64 @@ async function updateStrictBlockRules(dynamicRules, sessionRules) { 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() { - dynamicRuleId = sessionRuleId = 1; + dynamicRuleId = 1; const dynamicRules = []; const sessionRules = []; const [ @@ -474,7 +536,6 @@ async function filteringModesToDNR(modes) { rule0.condition.excludedRequestDomains = Array.from(afterExcludedRequestDomainSet); } addRules.push(rule0); - unexcludeFromStrictBlock(afterRequestDomainSet); // https://github.com/uBlockOrigin/uBOL-home/issues/114 const rule1 = { id: TRUSTED_DIRECTIVE_BASE_RULE_ID+1, @@ -499,60 +560,6 @@ async function filteringModesToDNR(modes) { /******************************************************************************/ -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 updateDynamicRules(); -} - -async function unexcludeFromStrictBlock(hostnames) { - const [ - permanentlyExcluded, - temporarilyExcluded, - ] = await Promise.all([ - localRead('excludedStrictBlockHostnames').then(r => r = new Set(r)), - sessionRead('excludedStrictBlockHostnames').then(r => r = new Set(r)), - ]); - const permanentCountBefore = permanentlyExcluded.size; - const temporaryCountBefore = temporarilyExcluded.size; - for ( const hn of hostnames ) { - permanentlyExcluded.delete(hn); - temporarilyExcluded.delete(hn); - } - const promises = []; - if ( permanentlyExcluded.size !== permanentCountBefore ) { - if ( permanentlyExcluded.size === 0 ) { - promises.push( - localRemove('excludedStrictBlockHostnames') - ); - } else { - promises.push( - localWrite('excludedStrictBlockHostnames', Array.from(permanentlyExcluded)) - ); - } - } - if ( temporarilyExcluded.size !== temporaryCountBefore ) { - if ( temporarilyExcluded.size === 0 ) { - promises.push( - sessionRemove('excludedStrictBlockHostnames') - ); - } else { - promises.push( - sessionWrite('excludedStrictBlockHostnames', Array.from(temporarilyExcluded)) - ); - } - } - if ( promises.length === 0 ) { return; } - await Promise.all(promises); - return updateDynamicRules(); -} - -/******************************************************************************/ - async function defaultRulesetsFromLanguage() { const out = await dnr.getEnabledRulesets(); @@ -672,5 +679,6 @@ export { filteringModesToDNR, getRulesetDetails, getEnabledRulesetsDetails, + setStrictBlockMode, updateDynamicRules, }; diff --git a/platform/mv3/extension/js/settings.js b/platform/mv3/extension/js/settings.js index 6cbd0e23d..5b73144cb 100644 --- a/platform/mv3/extension/js/settings.js +++ b/platform/mv3/extension/js/settings.js @@ -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;