diff --git a/platform/mv3/chromium/manifest.json b/platform/mv3/chromium/manifest.json index f42001a9d..c633619b6 100644 --- a/platform/mv3/chromium/manifest.json +++ b/platform/mv3/chromium/manifest.json @@ -40,7 +40,9 @@ "activeTab", "declarativeNetRequest", "scripting", - "storage" + "storage", + "tabs", + "webNavigation" ], "short_name": "uBO Lite", "storage": { diff --git a/platform/mv3/extension/img/icon_128_off.png b/platform/mv3/extension/img/icon_128_off.png new file mode 100644 index 000000000..f87878597 Binary files /dev/null and b/platform/mv3/extension/img/icon_128_off.png differ diff --git a/platform/mv3/extension/img/icon_16_off.png b/platform/mv3/extension/img/icon_16_off.png new file mode 100644 index 000000000..5373e8229 Binary files /dev/null and b/platform/mv3/extension/img/icon_16_off.png differ diff --git a/platform/mv3/extension/img/icon_32_off.png b/platform/mv3/extension/img/icon_32_off.png new file mode 100644 index 000000000..04f1a67a7 Binary files /dev/null and b/platform/mv3/extension/img/icon_32_off.png differ diff --git a/platform/mv3/extension/img/icon_64_off.png b/platform/mv3/extension/img/icon_64_off.png new file mode 100644 index 000000000..d9a22955a Binary files /dev/null and b/platform/mv3/extension/img/icon_64_off.png differ diff --git a/platform/mv3/extension/js/action.js b/platform/mv3/extension/js/action.js new file mode 100644 index 000000000..f196dee5e --- /dev/null +++ b/platform/mv3/extension/js/action.js @@ -0,0 +1,120 @@ +/******************************************************************************* + + 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 { browser } from './ext.js'; +import { getFilteringModeDetails } from './mode-manager.js'; + +/******************************************************************************/ + +let reverseMode = false; + +/******************************************************************************/ + +function toggleToolbarIcon(tabId) { + if ( reverseMode ) { + enableToolbarIcon(tabId); + } else { + disableToolbarIcon(tabId); + } +} + +function disableToolbarIcon(tabId) { + const details = { + path: { + '16': '/img/icon_16_off.png', + '32': '/img/icon_32_off.png', + '64': '/img/icon_64_off.png', + '128': '/img/icon_128_off.png', + } + }; + if ( tabId !== undefined ) { + details.tabId = tabId; + } + browser.action.setIcon(details); +} + +function enableToolbarIcon(tabId) { + const details = { + path: { + '16': '/img/icon_16.png', + '32': '/img/icon_32.png', + '64': '/img/icon_64.png', + '128': '/img/icon_128.png', + } + }; + if ( tabId !== undefined ) { + details.tabId = tabId; + } + browser.action.setIcon(details); +} + +/******************************************************************************/ + +function toolbarIconListener(details) { + if ( details.frameId !== 0 ) { return; } + toggleToolbarIcon(details.tabId); +} + +/******************************************************************************/ + +// 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(); + const reverseModeAfter = none.delete('all-urls'); + const toToggle = reverseModeAfter ? + new Set([ ...basic, ...optimal, ...complete ]) + : none; + + if ( reverseModeAfter !== reverseMode ) { + if ( reverseModeAfter ) { + disableToolbarIcon(); + } else { + enableToolbarIcon(); + } + reverseMode = reverseModeAfter; + } + + if ( toToggle.size === 0 ) { return; } + + webNavigation.onCommitted.addListener(toolbarIconListener, { + url: Array.from(toToggle).map(a => ({ + originAndPathMatches: `^https?://(.+\\.)?${a}/` + })), + }); + + if ( wakeup === undefined ) { return; } + + // 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); + } +} diff --git a/platform/mv3/extension/js/background.js b/platform/mv3/extension/js/background.js index 6af564822..4668b467f 100644 --- a/platform/mv3/extension/js/background.js +++ b/platform/mv3/extension/js/background.js @@ -21,6 +21,7 @@ import { MODE_BASIC, + MODE_NONE, MODE_OPTIMAL, getDefaultFilteringMode, getFilteringMode, @@ -38,6 +39,7 @@ import { import { broadcastMessage, + gotoURL, hasBroadHostPermissions, hostnamesFromMatches, } from './utils.js'; @@ -46,7 +48,6 @@ import { browser, localRead, localRemove, localWrite, runtime, - windows, } from './ext.js'; import { @@ -76,6 +77,7 @@ import { import { dnr } from './ext-compat.js'; import { registerInjectables } from './scripting-manager.js'; +import { syncToolbarIcon } from './action.js'; /******************************************************************************/ @@ -133,36 +135,6 @@ async function onPermissionsAdded(permissions) { /******************************************************************************/ -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. @@ -340,12 +312,18 @@ function onMessage(request, sender, callback) { break; case 'setFilteringMode': { - getFilteringMode(request.hostname).then(actualLevel => { - if ( request.level === actualLevel ) { return actualLevel; } + 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(actualLevel => { + }).then(afterLevel => { registerInjectables(); - callback(actualLevel); + trustedSitesChanged ||= afterLevel === MODE_NONE; + if ( trustedSitesChanged ) { + syncToolbarIcon(); + } + callback(afterLevel); }); return true; } @@ -378,6 +356,7 @@ function onMessage(request, sender, callback) { case 'setTrustedSites': setTrustedSites(request.hostnames).then(( ) => { registerInjectables(); + syncToolbarIcon(true); return Promise.all([ getDefaultFilteringMode(), getTrustedSites(), @@ -404,7 +383,7 @@ function onMessage(request, sender, callback) { return true; case 'showMatchedRules': - windows.create({ + browser.windows.create({ type: 'popup', url: `/matched-rules.html?tab=${request.tabId}`, }); @@ -436,7 +415,7 @@ function onCommand(command, tab) { /******************************************************************************/ -async function launch() { +async function startSession() { const currentVersion = getCurrentVersion(); const isNewVersion = currentVersion !== rulesetConfig.version; @@ -499,9 +478,10 @@ async function start() { await loadRulesetConfig(); if ( process.wakeupRun === false ) { - await launch(); + await startSession(); } + syncToolbarIcon(process.wakeupRun); toggleDeveloperMode(rulesetConfig.developerMode); } diff --git a/platform/mv3/extension/js/ext.js b/platform/mv3/extension/js/ext.js index ea1ab61ce..a2e6f18f5 100644 --- a/platform/mv3/extension/js/ext.js +++ b/platform/mv3/extension/js/ext.js @@ -26,7 +26,6 @@ import { webext } from './ext-compat.js'; export const browser = webext; export const i18n = browser.i18n; export const runtime = browser.runtime; -export const windows = browser.windows; /******************************************************************************/ diff --git a/platform/mv3/extension/js/mode-manager.js b/platform/mv3/extension/js/mode-manager.js index 41abc057d..6affead57 100644 --- a/platform/mv3/extension/js/mode-manager.js +++ b/platform/mv3/extension/js/mode-manager.js @@ -334,13 +334,18 @@ export async function getTrustedSites() { } export async function setTrustedSites(hostnames) { - const filteringModes = await getFilteringModeDetails(); + const [ + filteringModes, + hasOmnipotence, + ] = await Promise.all([ + getFilteringModeDetails(), + hasBroadHostPermissions(), + ]); const { none } = filteringModes; const hnSet = new Set(hostnames); let modified = false; - // Set default mode to Basic when removing No-filtering as default mode if ( none.has('all-urls') && hnSet.has('all-urls') === false ) { - applyFilteringMode(filteringModes, 'all-urls', MODE_BASIC); + applyFilteringMode(filteringModes, 'all-urls', hasOmnipotence ? MODE_OPTIMAL : MODE_BASIC); modified = true; } for ( const hn of none ) { @@ -389,16 +394,18 @@ export async function syncWithBrowserPermissions() { const afterMode = await getDefaultFilteringMode(); if ( afterMode > MODE_BASIC ) { return afterMode !== beforeMode; } const filteringModes = await getFilteringModeDetails(); - const { optimal, complete } = filteringModes; - for ( const hn of optimal ) { - if ( allowedHostnames.has(hn) ) { continue; } - optimal.delete(hn); - modified = true; - } - for ( const hn of complete ) { - if ( allowedHostnames.has(hn) ) { continue; } - complete.delete(hn); - modified = true; + if ( allowedHostnames.has('all-urls') === false ) { + const { optimal, complete } = filteringModes; + for ( const hn of optimal ) { + if ( allowedHostnames.has(hn) ) { continue; } + optimal.delete(hn); + modified = true; + } + for ( const hn of complete ) { + if ( allowedHostnames.has(hn) ) { continue; } + complete.delete(hn); + modified = true; + } } await writeFilteringModeDetails(filteringModes); return modified; diff --git a/platform/mv3/extension/js/utils.js b/platform/mv3/extension/js/utils.js index 00451f15f..94ed121b2 100644 --- a/platform/mv3/extension/js/utils.js +++ b/platform/mv3/extension/js/utils.js @@ -19,7 +19,10 @@ Home: https://github.com/gorhill/uBlock */ -import { browser } from './ext.js'; +import { + browser, + runtime, +} from './ext.js'; /******************************************************************************/ @@ -149,6 +152,36 @@ async function hasBroadHostPermissions() { /******************************************************************************/ +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 browser.windows.create({ + type: 'popup', + url: pageURL.href, + }); + } + + return browser.tabs.create({ + active: true, + url: pageURL.href, + }); +} + +/******************************************************************************/ + export { broadcastMessage, parsedURLromOrigin, @@ -161,4 +194,5 @@ export { matchesFromHostnames, hostnamesFromMatches, hasBroadHostPermissions, + gotoURL, }; diff --git a/platform/mv3/firefox/manifest.json b/platform/mv3/firefox/manifest.json index c2dd5a3aa..73d2fa1dd 100644 --- a/platform/mv3/firefox/manifest.json +++ b/platform/mv3/firefox/manifest.json @@ -52,7 +52,9 @@ "activeTab", "declarativeNetRequest", "scripting", - "storage" + "storage", + "tabs", + "webNavigation" ], "short_name": "uBO Lite", "version": "1.0", diff --git a/platform/mv3/safari/manifest.json b/platform/mv3/safari/manifest.json index ba7cc7715..a24dfb885 100644 --- a/platform/mv3/safari/manifest.json +++ b/platform/mv3/safari/manifest.json @@ -39,7 +39,9 @@ "declarativeNetRequest", "declarativeNetRequestWithHostAccess", "scripting", - "storage" + "storage", + "tabs", + "webNavigation" ], "short_name": "uBO Lite", "version": "1.0",