diff --git a/platform/mv3/chromium/manifest.json b/platform/mv3/chromium/manifest.json index c633619b6..f42001a9d 100644 --- a/platform/mv3/chromium/manifest.json +++ b/platform/mv3/chromium/manifest.json @@ -40,9 +40,7 @@ "activeTab", "declarativeNetRequest", "scripting", - "storage", - "tabs", - "webNavigation" + "storage" ], "short_name": "uBO Lite", "storage": { diff --git a/platform/mv3/extension/js/action.js b/platform/mv3/extension/js/action.js index a76210cef..811fe101c 100644 --- a/platform/mv3/extension/js/action.js +++ b/platform/mv3/extension/js/action.js @@ -19,8 +19,8 @@ Home: https://github.com/gorhill/uBlock */ +import { matchesFromHostnames, strArrayEq } from './utils.js'; import { browser } from './ext.js'; -import { getFilteringModeDetails } from './mode-manager.js'; /******************************************************************************/ @@ -28,14 +28,6 @@ let reverseMode = false; /******************************************************************************/ -function toggleToolbarIcon(tabId) { - if ( reverseMode ) { - enableToolbarIcon(tabId); - } else { - disableToolbarIcon(tabId); - } -} - function disableToolbarIcon(tabId) { const details = { path: { @@ -68,9 +60,12 @@ function enableToolbarIcon(tabId) { /******************************************************************************/ -function toolbarIconListener(details) { - if ( details.frameId !== 0 ) { return; } - toggleToolbarIcon(details.tabId); +export function toggleToolbarIcon(tabId) { + if ( reverseMode ) { + enableToolbarIcon(tabId); + } else { + disableToolbarIcon(tabId); + } } /******************************************************************************/ @@ -78,13 +73,8 @@ function toolbarIconListener(details) { // https://github.com/uBlockOrigin/uBOL-home/issues/198 // Ensure the toolbar icon reflects the no-filtering mode of "trusted sites" -export async function syncToolbarIcon(wakeup) { - const { webNavigation } = browser; - if ( typeof webNavigation !== 'object' ) { return; } - - webNavigation.onCommitted.removeListener(toolbarIconListener); - - const { none, basic, optimal, complete } = await getFilteringModeDetails(); +export async function registerToolbarIconToggler(context) { + const { none, basic, optimal, complete } = context.filteringModeDetails; const reverseModeAfter = none.delete('all-urls'); const toToggle = reverseModeAfter ? new Set([ ...basic, ...optimal, ...complete ]) @@ -101,20 +91,20 @@ export async function syncToolbarIcon(wakeup) { if ( toToggle.size === 0 ) { return; } - webNavigation.onCommitted.addListener(toolbarIconListener, { - url: Array.from(toToggle).map(a => ({ - originAndPathMatches: `^https?://([^.].*\\.)?${a.replaceAll('.', '\\.')}/` - })), - }); + const registered = context.before.get('toolbar-icon'); + context.before.delete('toolbar-icon'); // Important! - if ( wakeup === undefined ) { return; } + const directive = { + id: 'toolbar-icon', + js: [ '/js/scripting/toolbar-icon.js' ], + matches: matchesFromHostnames(toToggle), + runAt: 'document_start', + }; - // If waking up, update icon for existing tabs - const tabs = await browser.tabs.query({ - url: Array.from(toToggle).map(a => `*://*.${a}/*`) - }).catch(( ) => ([])); - - for ( const tab of tabs ) { - toggleToolbarIcon(tab.id); + if ( registered === undefined ) { + context.toAdd.push(directive); + } else if ( strArrayEq(registered.matches, directive.matches) === false ) { + context.toRemove.push('toolbar-icon'); + context.toAdd.push(directive); } } diff --git a/platform/mv3/extension/js/background.js b/platform/mv3/extension/js/background.js index f97771dcf..7d7661a37 100644 --- a/platform/mv3/extension/js/background.js +++ b/platform/mv3/extension/js/background.js @@ -21,7 +21,6 @@ import { MODE_BASIC, - MODE_NONE, MODE_OPTIMAL, getDefaultFilteringMode, getFilteringMode, @@ -78,7 +77,7 @@ import { import { dnr } from './ext-compat.js'; import { registerInjectables } from './scripting-manager.js'; -import { syncToolbarIcon } from './action.js'; +import { toggleToolbarIcon } from './action.js'; /******************************************************************************/ @@ -170,6 +169,14 @@ function onMessage(request, sender, callback) { return false; } + case 'toggleToolbarIcon': { + const tabId = sender?.tab?.id ?? false; + if ( tabId ) { + toggleToolbarIcon(tabId); + } + return false; + } + default: break; } @@ -313,17 +320,11 @@ function onMessage(request, sender, callback) { break; case 'setFilteringMode': { - let trustedSitesChanged = false; getFilteringMode(request.hostname).then(beforeLevel => { if ( request.level === beforeLevel ) { return beforeLevel; } - trustedSitesChanged = beforeLevel === MODE_NONE; return setFilteringMode(request.hostname, request.level); }).then(afterLevel => { registerInjectables(); - trustedSitesChanged ||= afterLevel === MODE_NONE; - if ( trustedSitesChanged ) { - syncToolbarIcon(); - } callback(afterLevel); }); return true; @@ -357,7 +358,6 @@ function onMessage(request, sender, callback) { case 'setTrustedSites': setTrustedSites(request.hostnames).then(( ) => { registerInjectables(); - syncToolbarIcon(true); return Promise.all([ getDefaultFilteringMode(), getTrustedSites(), @@ -485,7 +485,6 @@ async function start() { await startSession(); } - syncToolbarIcon(process.wakeupRun); toggleDeveloperMode(rulesetConfig.developerMode); } diff --git a/platform/mv3/extension/js/scripting-manager.js b/platform/mv3/extension/js/scripting-manager.js index 8ff9a5c36..6730e031d 100644 --- a/platform/mv3/extension/js/scripting-manager.js +++ b/platform/mv3/extension/js/scripting-manager.js @@ -25,6 +25,7 @@ import { browser } from './ext.js'; import { fetchJSON } from './fetch.js'; import { getEnabledRulesetsDetails } from './ruleset-manager.js'; import { getFilteringModeDetails } from './mode-manager.js'; +import { registerToolbarIconToggler } from './action.js'; import { ubolLog } from './debug.js'; /******************************************************************************/ @@ -53,19 +54,6 @@ function getGenericDetails() { /******************************************************************************/ -// Important: We need to sort the arrays for fast comparison -const arrayEq = (a = [], b = [], sort = true) => { - const alen = a.length; - if ( alen !== b.length ) { return false; } - if ( sort ) { a.sort(); b.sort(); } - for ( let i = 0; i < alen; i++ ) { - if ( a[i] !== b[i] ) { return false; } - } - return true; -}; - -/******************************************************************************/ - const normalizeMatches = matches => { if ( matches.length <= 1 ) { return; } if ( matches.includes('') === false ) { @@ -168,9 +156,9 @@ function registerHighGeneric(context, genericDetails) { // update if ( - arrayEq(registered.css, css, false) === false || - arrayEq(registered.matches, matches) === false || - arrayEq(registered.excludeMatches, excludeMatches) === false + ut.strArrayEq(registered.css, css, false) === false || + ut.strArrayEq(registered.matches, matches) === false || + ut.strArrayEq(registered.excludeMatches, excludeMatches) === false ) { context.toRemove.push('css-generichigh'); context.toAdd.push(directive); @@ -231,8 +219,8 @@ function registerGeneric(context, genericDetails) { if ( registered === undefined ) { // register context.toAdd.push(directive); } else if ( // update - arrayEq(registered.js, js, false) === false || - arrayEq(registered.matches, directive.matches) === false + ut.strArrayEq(registered.js, js, false) === false || + ut.strArrayEq(registered.matches, directive.matches) === false ) { context.toRemove.push('css-generic-some'); context.toAdd.push(directive); @@ -257,8 +245,8 @@ function registerGeneric(context, genericDetails) { if ( registeredAll === undefined ) { // register context.toAdd.push(directiveAll); } else if ( // update - arrayEq(registeredAll.js, js, false) === false || - arrayEq(registeredAll.excludeMatches, directiveAll.excludeMatches) === false + ut.strArrayEq(registeredAll.js, js, false) === false || + ut.strArrayEq(registeredAll.excludeMatches, directiveAll.excludeMatches) === false ) { context.toRemove.push('css-generic-all'); context.toAdd.push(directiveAll); @@ -281,8 +269,8 @@ function registerGeneric(context, genericDetails) { if ( registeredSome === undefined ) { // register context.toAdd.push(directiveSome); } else if ( // update - arrayEq(registeredSome.js, js, false) === false || - arrayEq(registeredSome.matches, directiveSome.matches) === false + ut.strArrayEq(registeredSome.js, js, false) === false || + ut.strArrayEq(registeredSome.matches, directiveSome.matches) === false ) { context.toRemove.push('css-generic-some'); context.toAdd.push(directiveSome); @@ -342,9 +330,9 @@ function registerProcedural(context) { // update if ( - arrayEq(registered.js, js, false) === false || - arrayEq(registered.matches, matches) === false || - arrayEq(registered.excludeMatches, excludeMatches) === false + ut.strArrayEq(registered.js, js, false) === false || + ut.strArrayEq(registered.matches, matches) === false || + ut.strArrayEq(registered.excludeMatches, excludeMatches) === false ) { context.toRemove.push('css-procedural'); context.toAdd.push(directive); @@ -404,9 +392,9 @@ function registerDeclarative(context) { // update if ( - arrayEq(registered.js, js, false) === false || - arrayEq(registered.matches, matches) === false || - arrayEq(registered.excludeMatches, excludeMatches) === false + ut.strArrayEq(registered.js, js, false) === false || + ut.strArrayEq(registered.matches, matches) === false || + ut.strArrayEq(registered.excludeMatches, excludeMatches) === false ) { context.toRemove.push('css-declarative'); context.toAdd.push(directive); @@ -466,9 +454,9 @@ function registerSpecific(context) { // update if ( - arrayEq(registered.js, js, false) === false || - arrayEq(registered.matches, matches) === false || - arrayEq(registered.excludeMatches, excludeMatches) === false + ut.strArrayEq(registered.js, js, false) === false || + ut.strArrayEq(registered.matches, matches) === false || + ut.strArrayEq(registered.excludeMatches, excludeMatches) === false ) { context.toRemove.push('css-specific'); context.toAdd.push(directive); @@ -545,8 +533,8 @@ function registerScriptlet(context, scriptletDetails) { // update if ( - arrayEq(registered.matches, matches) === false || - arrayEq(registered.excludeMatches, excludeMatches) === false + ut.strArrayEq(registered.matches, matches) === false || + ut.strArrayEq(registered.excludeMatches, excludeMatches) === false ) { context.toRemove.push(id); context.toAdd.push(directive); @@ -599,6 +587,7 @@ async function registerInjectables() { registerSpecific(context); registerGeneric(context, genericDetails); registerHighGeneric(context, genericDetails); + registerToolbarIconToggler(context); toRemove.push(...Array.from(before.keys())); diff --git a/platform/mv3/extension/js/scripting/toolbar-icon.js b/platform/mv3/extension/js/scripting/toolbar-icon.js new file mode 100644 index 000000000..c5843a05c --- /dev/null +++ b/platform/mv3/extension/js/scripting/toolbar-icon.js @@ -0,0 +1,27 @@ +/******************************************************************************* + + uBlock Origin Lite - a comprehensive, MV3-compliant content blocker + Copyright (C) 2025-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 +*/ + +(function uBOL_toggleToolbarIcon() { + chrome.runtime.sendMessage({ + what: 'toggleToolbarIcon', + }).catch(( ) => { + }); +})(); diff --git a/platform/mv3/extension/js/utils.js b/platform/mv3/extension/js/utils.js index 94ed121b2..935d61cfc 100644 --- a/platform/mv3/extension/js/utils.js +++ b/platform/mv3/extension/js/utils.js @@ -182,6 +182,19 @@ async function gotoURL(url, type) { /******************************************************************************/ +// Important: We need to sort the arrays for fast comparison +const strArrayEq = (a = [], b = [], sort = true) => { + const alen = a.length; + if ( alen !== b.length ) { return false; } + if ( sort ) { a.sort(); b.sort(); } + for ( let i = 0; i < alen; i++ ) { + if ( a[i] !== b[i] ) { return false; } + } + return true; +}; + +/******************************************************************************/ + export { broadcastMessage, parsedURLromOrigin, @@ -195,4 +208,5 @@ export { hostnamesFromMatches, hasBroadHostPermissions, gotoURL, + strArrayEq, }; diff --git a/platform/mv3/firefox/manifest.json b/platform/mv3/firefox/manifest.json index 73d2fa1dd..c2dd5a3aa 100644 --- a/platform/mv3/firefox/manifest.json +++ b/platform/mv3/firefox/manifest.json @@ -52,9 +52,7 @@ "activeTab", "declarativeNetRequest", "scripting", - "storage", - "tabs", - "webNavigation" + "storage" ], "short_name": "uBO Lite", "version": "1.0", diff --git a/platform/mv3/safari/manifest.json b/platform/mv3/safari/manifest.json index d4c8725f8..c724367cb 100644 --- a/platform/mv3/safari/manifest.json +++ b/platform/mv3/safari/manifest.json @@ -40,9 +40,7 @@ "declarativeNetRequest", "declarativeNetRequestWithHostAccess", "scripting", - "storage", - "tabs", - "webNavigation" + "storage" ], "short_name": "uBO Lite", "version": "1.0",