From 7ff750eaf6007bdea4e843d3314fc7275b1ce945 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 10 Aug 2019 10:57:24 -0400 Subject: [PATCH] Reflect blocking mode in badge color of toolbar icon Related feedback: - https://www.reddit.com/r/uBlockOrigin/comments/cmh910/ Additionally, the `3p` rule has been made distinct from `3p-script`/`3p-frame` for the purpose of "Relax blocking mode" command. The badge color will hint at the current blocking mode. There are four colors for the four following blocking modes: - JavaScript wholly disabled - All 3rd parties blocked - 3rd-party scripts and frames blocked - None of the above The default badge color will be used when JavaScript is not wholly disabled and when there are no rules for `3p`, `3p-script` or `3p-frame`. A new advanced setting has been added to let the user choose the badge colors for the various blocking modes, `blockingProfileColors`. The value *must* be a sequence of 4 valid CSS color values that match 6 hexadecimal digits prefixed with`#` -- anything else will be ignored. --- platform/chromium/vapi-background.js | 57 +++++++++++++++------------- src/js/background.js | 3 +- src/js/commands.js | 29 +------------- src/js/messaging.js | 3 +- src/js/storage.js | 1 + src/js/tab.js | 46 ++++++++++++++++++---- src/js/ublock.js | 30 +++++++++++++++ src/js/utils.js | 12 ++++++ 8 files changed, 118 insertions(+), 63 deletions(-) diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 5b964aa4f..0036ed518 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -661,28 +661,25 @@ vAPI.Tabs = class { // https://github.com/uBlockOrigin/uBlock-issues/issues/32 // Ensure ImageData for toolbar icon is valid before use. -vAPI.setIcon = (function() { - const browserAction = chrome.browserAction, - titleTemplate = - chrome.runtime.getManifest().browser_action.default_title + - ' ({badge})'; +vAPI.setIcon = (( ) => { + const browserAction = chrome.browserAction; + const titleTemplate = + browser.runtime.getManifest().browser_action.default_title + + ' ({badge})'; const icons = [ - { - path: { '16': 'img/icon_16-off.png', '32': 'img/icon_32-off.png' } - }, - { - path: { '16': 'img/icon_16.png', '32': 'img/icon_32.png' } - } + { path: { '16': 'img/icon_16-off.png', '32': 'img/icon_32-off.png' } }, + { path: { '16': 'img/icon_16.png', '32': 'img/icon_32.png' } }, ]; - (function() { + (( ) => { if ( browserAction.setIcon === undefined ) { return; } - // The global badge background color. + // The global badge text and background color. if ( browserAction.setBadgeBackgroundColor !== undefined ) { - browserAction.setBadgeBackgroundColor({ - color: [ 0x66, 0x66, 0x66, 0xFF ] - }); + browserAction.setBadgeBackgroundColor({ color: '#666666' }); + } + if ( browserAction.setBadgeTextColor !== undefined ) { + browserAction.setBadgeTextColor({ color: '#FFFFFF' }); } // As of 2018-05, benchmarks show that only Chromium benefits for sure @@ -698,7 +695,7 @@ vAPI.setIcon = (function() { const imgs = []; for ( let i = 0; i < icons.length; i++ ) { - let path = icons[i].path; + const path = icons[i].path; for ( const key in path ) { if ( path.hasOwnProperty(key) === false ) { continue; } imgs.push({ i: i, p: key }); @@ -719,10 +716,10 @@ vAPI.setIcon = (function() { for ( const img of imgs ) { if ( img.r.complete === false ) { return; } } - let ctx = document.createElement('canvas').getContext('2d'); - let iconData = [ null, null ]; + const ctx = document.createElement('canvas').getContext('2d'); + const iconData = [ null, null ]; for ( const img of imgs ) { - let w = img.r.naturalWidth, h = img.r.naturalHeight; + const w = img.r.naturalWidth, h = img.r.naturalHeight; ctx.width = w; ctx.height = h; ctx.clearRect(0, 0, w, h); ctx.drawImage(img.r, 0, 0); @@ -753,16 +750,23 @@ vAPI.setIcon = (function() { } })(); - var onTabReady = function(tab, state, badge, parts) { + const onTabReady = function(tab, details) { if ( vAPI.lastError() || !tab ) { return; } + const { parts, state, badge, color } = details; + if ( browserAction.setIcon !== undefined ) { - if ( parts === undefined || (parts & 0x01) !== 0 ) { + if ( parts === undefined || (parts & 0b001) !== 0 ) { browserAction.setIcon( Object.assign({ tabId: tab.id }, icons[state]) ); } - browserAction.setBadgeText({ tabId: tab.id, text: badge }); + if ( (parts & 0b010) !== 0 ) { + browserAction.setBadgeText({ tabId: tab.id, text: badge }); + } + if ( (parts & 0b100) !== 0 ) { + browserAction.setBadgeBackgroundColor({ tabId: tab.id, color }); + } } if ( browserAction.setTitle !== undefined ) { @@ -778,14 +782,13 @@ vAPI.setIcon = (function() { // parts: bit 0 = icon // bit 1 = badge + // bit 2 = badge color - return function(tabId, state, badge, parts) { + return function(tabId, details) { tabId = toChromiumTabId(tabId); if ( tabId === 0 ) { return; } - chrome.tabs.get(tabId, function(tab) { - onTabReady(tab, state, badge, parts); - }); + browser.tabs.get(tabId, tab => onTabReady(tab, details)); if ( vAPI.contextMenu instanceof Object ) { vAPI.contextMenu.onMustUpdate(tabId); diff --git a/src/js/background.js b/src/js/background.js index 68a7fed24..008566121 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -42,7 +42,8 @@ const µBlock = (function() { // jshint ignore:line autoUpdateAssetFetchPeriod: 120, autoUpdateDelayAfterLaunch: 180, autoUpdatePeriod: 7, - blockingProfiles: '11101 00001', + blockingProfiles: '11101 11001 00001', + blockingProfileColors: '#666666 #E7552C #F69454 #008DCB', cacheStorageAPI: 'unset', cacheStorageCompression: true, cacheControlForFirefox1376932: 'no-cache, no-store, must-revalidate', diff --git a/src/js/commands.js b/src/js/commands.js index 0707c8765..e94a3fdf0 100644 --- a/src/js/commands.js +++ b/src/js/commands.js @@ -39,12 +39,7 @@ if ( µBlock.canUseShortcuts === false ) { return; } const relaxBlockingMode = function(tab) { - if ( - tab instanceof Object === false || - tab.id <= 0 - ) { - return; - } + if ( tab instanceof Object === false || tab.id <= 0 ) { return; } const µb = µBlock; const normalURL = µb.normalizePageURL(tab.id, tab.url); @@ -52,27 +47,7 @@ const relaxBlockingMode = function(tab) { if ( µb.getNetFilteringSwitch(normalURL) === false ) { return; } const hn = µb.URI.hostnameFromURI(normalURL); - - // Construct current blocking profile - const ssw = µb.sessionSwitches; - const sfw = µb.sessionFirewall; - let currentProfile = 0; - - if ( ssw.evaluateZ('no-scripting', hn) ) { - currentProfile |= 0b00000010; - } - if ( µb.userSettings.advancedUserEnabled ) { - if ( sfw.evaluateCellZY(hn, '*', '3p') === 1 ) { - currentProfile |= 0b00000100; - } - if ( sfw.evaluateCellZY(hn, '*', '3p-script') === 1 ) { - currentProfile |= 0b00001000; - } - if ( sfw.evaluateCellZY(hn, '*', '3p-frame') === 1 ) { - currentProfile |= 0b00010000; - } - } - + const currentProfile = µb.blockingModeFromHostname(hn); const profiles = []; for ( const s of µb.hiddenSettings.blockingProfiles.split(/\s+/) ) { const v = parseInt(s, 2); diff --git a/src/js/messaging.js b/src/js/messaging.js index a08a23db3..bc79b36dd 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -425,6 +425,7 @@ var onMessage = function(request, sender, callback) { request.srcHostname, 'net' ); + µb.updateToolbarIcon(request.tabId, 0b100); response = popupDataFromTabId(request.tabId); break; @@ -462,7 +463,7 @@ var onMessage = function(request, sender, callback) { pageStore = µb.pageStoreFromTabId(request.tabId); if ( pageStore ) { pageStore.toggleNetFilteringSwitch(request.url, request.scope, request.state); - µb.updateToolbarIcon(request.tabId, 0x03); + µb.updateToolbarIcon(request.tabId, 0b111); } break; diff --git a/src/js/storage.js b/src/js/storage.js index 51861aec9..9c6eb49f9 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -122,6 +122,7 @@ } self.log.verbosity = this.hiddenSettings.consoleLogLevel; resolve(); + this.fireDOMEvent('hiddenSettingsChanged'); }); // <<<< end of executor diff --git a/src/js/tab.js b/src/js/tab.js index a397d5c38..9e4a4304e 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -342,7 +342,7 @@ // Blocked if ( µb.userSettings.showIconBadge ) { - µb.updateToolbarIcon(openerTabId, 0x02); + µb.updateToolbarIcon(openerTabId, 0b010); } // It is a popup, block and remove the tab. @@ -859,7 +859,7 @@ vAPI.tabs = new vAPI.Tabs(); // Create an entry for the tab if it doesn't exist. µBlock.bindTabToPageStats = function(tabId, context) { - this.updateToolbarIcon(tabId, 0x03); + this.updateToolbarIcon(tabId, 0b111); // Do not create a page store for URLs which are of no interests if ( this.tabContextManager.exists(tabId) === false ) { @@ -954,6 +954,24 @@ vAPI.tabs = new vAPI.Tabs(); µBlock.updateToolbarIcon = (( ) => { const tabIdToDetails = new Map(); + const blockingProfileColors = [ + '#666666', + '#E7552C', + '#F69454', + '#008DCB', + ]; + + self.addEventListener( + 'hiddenSettingsChanged', + ( ) => { + const colors = µBlock.hiddenSettings.blockingProfileColors; + if ( /^#[0-9a-f]{6}(\s#[0-9a-f]{6}){3}$/i.test(colors) === false ) { + return; + } + blockingProfileColors.length = 0; + blockingProfileColors.push(...colors.split(/\s+/)); + } + ); const updateBadge = function(tabId) { const µb = µBlock; @@ -962,26 +980,40 @@ vAPI.tabs = new vAPI.Tabs(); let state = 0; let badge = ''; + let color = blockingProfileColors[0]; let pageStore = µb.pageStoreFromTabId(tabId); if ( pageStore !== null ) { state = pageStore.getNetFilteringSwitch() ? 1 : 0; if ( state === 1 && - µb.userSettings.showIconBadge && - pageStore.perLoadBlockedRequestCount + µb.userSettings.showIconBadge ) { - badge = µb.formatCount(pageStore.perLoadBlockedRequestCount); + if ( (parts & 0b010) !== 0 && pageStore.perLoadBlockedRequestCount ) { + badge = µb.formatCount(pageStore.perLoadBlockedRequestCount); + } + if ( (parts & 0b100) !== 0 ) { + let profile = µb.blockingModeFromHostname(pageStore.tabHostname); + if ( (profile & 0b00000010) !== 0 ) { + color = blockingProfileColors[3]; + } else if ( (profile & 0b00000100) !== 0 ) { + color = blockingProfileColors[2]; + } else if ( (profile & 0b00011000) !== 0 ) { + color = blockingProfileColors[1]; + } + } } } - vAPI.setIcon(tabId, state, badge, parts); + vAPI.setIcon(tabId, { parts, state, badge, color }); }; // parts: bit 0 = icon // bit 1 = badge + // bit 2 = badge color - return function(tabId, newParts = 0b11) { + return function(tabId, newParts = 0b111) { + if ( typeof tabId !== 'number' ) { return; } if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } let currentParts = tabIdToDetails.get(tabId); if ( currentParts === newParts ) { return; } diff --git a/src/js/ublock.js b/src/js/ublock.js index d151f12ef..034898c56 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -409,6 +409,7 @@ const matchBucket = function(url, hostname, bucket, start) { this.redirectEngine.invalidateResourcesSelfie(); this.loadRedirectResources(); } + this.fireDOMEvent('hiddenSettingsChanged'); }; /******************************************************************************/ @@ -506,6 +507,10 @@ const matchBucket = function(url, hostname, bucket, start) { // https://github.com/chrisaljoudi/uBlock/issues/420 this.cosmeticFilteringEngine.removeFromSelectorCache(srcHostname, 'net'); + + if ( requestType.startsWith('3p') ) { + this.updateToolbarIcon(details.tabId, 0b100); + } }; /******************************************************************************/ @@ -548,6 +553,9 @@ const matchBucket = function(url, hostname, bucket, start) { // Take action if needed switch ( details.name ) { + case 'no-scripting': + this.updateToolbarIcon(details.tabId, 0b100); + break; case 'no-cosmetic-filtering': this.scriptlets.injectDeep( details.tabId, @@ -577,6 +585,28 @@ const matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ +µBlock.blockingModeFromHostname = function(hn) { + let bits = 0; + if ( this.sessionSwitches.evaluateZ('no-scripting', hn) ) { + bits |= 0b00000010; + } + if ( this.userSettings.advancedUserEnabled ) { + const fw = this.sessionFirewall; + if ( fw.evaluateCellZY(hn, '*', '3p') === 1 ) { + bits |= 0b00000100; + } + if ( fw.evaluateCellZY(hn, '*', '3p-script') === 1 ) { + bits |= 0b00001000; + } + if ( fw.evaluateCellZY(hn, '*', '3p-frame') === 1 ) { + bits |= 0b00010000; + } + } + return bits; +}; + +/******************************************************************************/ + // https://github.com/NanoMeow/QuickReports/issues/6#issuecomment-414516623 // Inject as early as possible to make the cosmetic logger code less // sensitive to the removal of DOM nodes which may match injected diff --git a/src/js/utils.js b/src/js/utils.js index f5c942250..728b3ee74 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -700,3 +700,15 @@ return datasetPromise; }; })(); + +/******************************************************************************/ + +µBlock.fireDOMEvent = function(name) { + if ( + window instanceof Object && + window.dispatchEvent instanceof Function && + window.CustomEvent instanceof Function + ) { + window.dispatchEvent(new CustomEvent(name)); + } +};