From e983f9a76e1ace241410e384bba1968fd1f93aae Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 12 Jul 2021 11:55:58 -0400 Subject: [PATCH] Disable creation of cosmetic filters in picker when unenforceable Related issue: - https://github.com/gorhill/uBlock/issues/3212 The element picker will now properly work on sites where cosmetic filtering is disabled, but will not allow the creation of cosmetic filters when specific cosmetic filters are not meant to be enforced in the current page. When specific cosmetic filters are not meant to be enforced, the element picker will still allow the creation of network filters, that is unless the current page is trusted, in which case using the element picker is pointless. --- src/js/contentscript.js | 19 +++-- src/js/contextmenu.js | 40 ++++++++--- src/js/cosmetic-filtering.js | 7 +- src/js/logger-ui.js | 2 +- src/js/messaging.js | 83 +++++----------------- src/js/pagestore.js | 130 +++++++++++++++++++++++++++-------- src/js/scriptlets/epicker.js | 34 ++++++--- 7 files changed, 195 insertions(+), 120 deletions(-) diff --git a/src/js/contentscript.js b/src/js/contentscript.js index aaf996249..38f184c82 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -1275,12 +1275,21 @@ vAPI.DOMFilterer = class { vAPI.domCollapser.start(); - if ( response.noCosmeticFiltering ) { + const { + noSpecificCosmeticFiltering, + noGenericCosmeticFiltering, + scriptlets, + } = response; + + vAPI.noSpecificCosmeticFiltering = noSpecificCosmeticFiltering; + vAPI.noGenericCosmeticFiltering = noGenericCosmeticFiltering; + + if ( noSpecificCosmeticFiltering && noGenericCosmeticFiltering ) { vAPI.domFilterer = null; vAPI.domSurveyor = null; } else { const domFilterer = vAPI.domFilterer = new vAPI.DOMFilterer(); - if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) { + if ( noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) { vAPI.domSurveyor = null; } domFilterer.exceptions = cfeDetails.exceptionFilters; @@ -1293,9 +1302,9 @@ vAPI.DOMFilterer = class { // Library of resources is located at: // https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt - if ( response.scriptlets ) { - vAPI.injectScriptlet(document, response.scriptlets); - vAPI.injectedScripts = response.scriptlets; + if ( scriptlets ) { + vAPI.injectScriptlet(document, scriptlets); + vAPI.injectedScripts = scriptlets; } if ( vAPI.domSurveyor instanceof Object ) { diff --git a/src/js/contextmenu.js b/src/js/contextmenu.js index 75ec7d36c..12190a430 100644 --- a/src/js/contextmenu.js +++ b/src/js/contextmenu.js @@ -114,6 +114,9 @@ const onEntryClicked = function(details, tab) { if ( details.menuItemId === 'uBlock0-blockElementInFrame' ) { return onBlockElementInFrame(details, tab); } + if ( details.menuItemId === 'uBlock0-blockResource' ) { + return onBlockElement(details, tab); + } if ( details.menuItemId === 'uBlock0-subscribeToList' ) { return onSubscribeToList(details); } @@ -128,23 +131,28 @@ const menuEntries = { blockElement: { id: 'uBlock0-blockElement', title: vAPI.i18n('pickerContextMenuEntry'), - contexts: ['all'], + contexts: [ 'all' ], }, blockElementInFrame: { id: 'uBlock0-blockElementInFrame', title: vAPI.i18n('contextMenuBlockElementInFrame'), - contexts: ['frame'], + contexts: [ 'frame' ], + }, + blockResource: { + id: 'uBlock0-blockResource', + title: vAPI.i18n('pickerContextMenuEntry'), + contexts: [ 'audio', 'frame', 'image', 'video' ], }, subscribeToList: { id: 'uBlock0-subscribeToList', title: vAPI.i18n('contextMenuSubscribeToList'), - contexts: ['link'], + contexts: [ 'link' ], targetUrlPatterns: [ 'abp:*' ], }, temporarilyAllowLargeMediaElements: { id: 'uBlock0-temporarilyAllowLargeMediaElements', title: vAPI.i18n('contextMenuTemporarilyAllowLargeMediaElements'), - contexts: ['all'], + contexts: [ 'all' ], } }; @@ -155,25 +163,35 @@ let currentBits = 0; const update = function(tabId = undefined) { let newBits = 0; if ( µBlock.userSettings.contextMenuEnabled && tabId !== undefined ) { - let pageStore = µBlock.pageStoreFromTabId(tabId); + const pageStore = µBlock.pageStoreFromTabId(tabId); if ( pageStore && pageStore.getNetFilteringSwitch() ) { - newBits |= 0x01; + if ( pageStore.shouldApplySpecificCosmeticFilters(0) ) { + newBits |= 0b0001; + } else { + newBits |= 0b0010; + } if ( pageStore.largeMediaCount !== 0 ) { - newBits |= 0x02; + newBits |= 0b0100; } } + newBits |= 0b1000; } if ( newBits === currentBits ) { return; } currentBits = newBits; - let usedEntries = []; - if ( newBits & 0x01 ) { + const usedEntries = []; + if ( newBits & 0b0001 ) { usedEntries.push(menuEntries.blockElement); usedEntries.push(menuEntries.blockElementInFrame); - usedEntries.push(menuEntries.subscribeToList); } - if ( newBits & 0x02 ) { + if ( newBits & 0b0010 ) { + usedEntries.push(menuEntries.blockResource); + } + if ( newBits & 0b0100 ) { usedEntries.push(menuEntries.temporarilyAllowLargeMediaElements); } + if ( newBits & 0b1000 ) { + usedEntries.push(menuEntries.subscribeToList); + } vAPI.contextMenu.setEntries(usedEntries, onEntryClicked); }; diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index bb4340ec1..ce77a89b7 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -966,7 +966,10 @@ FilterContainer.prototype.retrieveSpecificSelectors = function( }; const injectedCSS = []; - if ( options.noCosmeticFiltering !== true ) { + if ( + options.noSpecificCosmeticFiltering !== true || + options.noGenericCosmeticFiltering !== true + ) { const injectedHideFilters = []; const specificSet = this.$specificSet; const proceduralSet = this.$proceduralSet; @@ -1161,7 +1164,7 @@ FilterContainer.prototype.benchmark = async function() { entity: '', }; const options = { - noCosmeticFiltering: false, + noSpecificCosmeticFiltering: false, noGenericCosmeticFiltering: false, }; let count = 0; diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 5e5ca7539..2dd738d2f 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -696,7 +696,7 @@ const viewPort = (( ) => { if ( filteringType === 'static' ) { divcl.add('canLookup'); } else if ( details.realm === 'extended' ) { - divcl.add('canLookup'); + divcl.toggle('canLookup', /^#@?#/.test(filter.raw)); divcl.toggle('isException', filter.raw.startsWith('#@#')); } if ( filter.modifier === true ) { diff --git a/src/js/messaging.js b/src/js/messaging.js index 1dce1fe31..bd311a15d 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -555,73 +555,23 @@ const retrieveContentScriptParameters = async function(sender, request) { } const loggerEnabled = µb.logger.enabled; - const noCosmeticFiltering = pageStore.noCosmeticFiltering === true; + const noSpecificCosmeticFiltering = + pageStore.shouldApplySpecificCosmeticFilters(frameId) === false; + const noGenericCosmeticFiltering = + pageStore.shouldApplyGenericCosmeticFilters(frameId) === false; const response = { collapseBlocked: µb.userSettings.collapseBlocked, - noCosmeticFiltering, - noGenericCosmeticFiltering: noCosmeticFiltering, - noSpecificCosmeticFiltering: noCosmeticFiltering, + noGenericCosmeticFiltering, + noSpecificCosmeticFiltering, }; - // https://github.com/uBlockOrigin/uAssets/issues/5704 - // `generichide` must be evaluated in the frame context. - if ( noCosmeticFiltering === false ) { - const genericHide = - µb.staticNetFilteringEngine.matchStringReverse( - 'generichide', - request.url - ); - response.noGenericCosmeticFiltering = genericHide === 2; - if ( loggerEnabled && genericHide !== 0 ) { - µBlock.filteringContext - .duplicate() - .fromTabId(tabId) - .setURL(request.url) - .setRealm('network') - .setType('generichide') - .setFilter(µb.staticNetFilteringEngine.toLogData()) - .toLogger(); - } - } - request.tabId = tabId; request.frameId = frameId; request.hostname = µb.URI.hostnameFromURI(request.url); request.domain = µb.URI.domainFromHostname(request.hostname); request.entity = µb.URI.entityFromDomain(request.domain); - // https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/ - // Add support for `specifichide`. - if ( noCosmeticFiltering === false ) { - const specificHide = - µb.staticNetFilteringEngine.matchStringReverse( - 'specifichide', - request.url - ); - response.noSpecificCosmeticFiltering = specificHide === 2; - if ( loggerEnabled && specificHide !== 0 ) { - µBlock.filteringContext - .duplicate() - .fromTabId(tabId) - .setURL(request.url) - .setRealm('network') - .setType('specifichide') - .setFilter(µb.staticNetFilteringEngine.toLogData()) - .toLogger(); - } - } - - // Cosmetic filtering can be effectively disabled when both specific and - // generic cosmetic filtering are disabled. - if ( - noCosmeticFiltering === false && - response.noGenericCosmeticFiltering && - response.noSpecificCosmeticFiltering - ) { - response.noCosmeticFiltering = true; - } - response.specificCosmeticFilters = µb.cosmeticFilteringEngine.retrieveSpecificSelectors(request, response); @@ -656,14 +606,19 @@ const retrieveContentScriptParameters = async function(sender, request) { // Inject as early as possible to make the cosmetic logger code less // sensitive to the removal of DOM nodes which may match injected // cosmetic filters. - if ( loggerEnabled && response.noCosmeticFiltering !== true ) { - vAPI.tabs.executeScript(tabId, { - allFrames: false, - file: '/js/scriptlets/cosmetic-logger.js', - frameId, - matchAboutBlank: true, - runAt: 'document_start', - }); + if ( loggerEnabled ) { + if ( + noSpecificCosmeticFiltering === false || + noGenericCosmeticFiltering === false + ) { + vAPI.tabs.executeScript(tabId, { + allFrames: false, + file: '/js/scriptlets/cosmetic-logger.js', + frameId, + matchAboutBlank: true, + runAt: 'document_start', + }); + } } return response; diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 57b899bec..f30e88d67 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -192,6 +192,10 @@ const FrameStore = class { this.domain = vAPI.domainFromHostname(this.hostname) || this.hostname; } + // Evaluated on-demand + // - 0b01: specific cosmetic filtering + // - 0b10: generic cosmetic filtering + this._cosmeticFilteringBits = undefined; return this; } @@ -203,6 +207,60 @@ const FrameStore = class { return null; } + getCosmeticFilteringBits(tabId) { + if ( this._cosmeticFilteringBits !== undefined ) { + return this._cosmeticFilteringBits; + } + this._cosmeticFilteringBits = 0b11; + { + const result = µb.staticNetFilteringEngine.matchStringReverse( + 'specifichide', + this.rawURL + ); + if ( result !== 0 && µb.logger.enabled ) { + µBlock.filteringContext + .duplicate() + .fromTabId(tabId) + .setURL(this.rawURL) + .setRealm('network') + .setType('specifichide') + .setFilter(µb.staticNetFilteringEngine.toLogData()) + .toLogger(); + } + if ( result === 2 ) { + this._cosmeticFilteringBits &= ~0b01; + } + } + { + const result = µb.staticNetFilteringEngine.matchStringReverse( + 'generichide', + this.rawURL + ); + if ( result !== 0 && µb.logger.enabled ) { + µBlock.filteringContext + .duplicate() + .fromTabId(tabId) + .setURL(this.rawURL) + .setRealm('network') + .setType('generichide') + .setFilter(µb.staticNetFilteringEngine.toLogData()) + .toLogger(); + } + if ( result === 2 ) { + this._cosmeticFilteringBits &= ~0b10; + } + } + return this._cosmeticFilteringBits; + } + + shouldApplySpecificCosmeticFilters(tabId) { + return (this.getCosmeticFilteringBits(tabId) & 0b01) !== 0; + } + + shouldApplyGenericCosmeticFilters(tabId) { + return (this.getCosmeticFilteringBits(tabId) & 0b10) !== 0; + } + static factory(frameURL, parentId = -1) { const entry = FrameStore.junkyard.pop(); if ( entry === undefined ) { @@ -296,7 +354,7 @@ const PageStore = class { // The context is used to determine whether we report behavior change // to the logger. - init(tabId, context) { + init(tabId) { const tabContext = µb.tabContextManager.mustLookup(tabId); this.tabId = tabId; @@ -327,28 +385,8 @@ const PageStore = class { this.frames = new Map(); this.setFrameURL({ url: tabContext.rawURL }); - // https://github.com/uBlockOrigin/uBlock-issues/issues/314 - const masterSwitch = tabContext.getNetFilteringSwitch(); - - this.noCosmeticFiltering = µb.sessionSwitches.evaluateZ( - 'no-cosmetic-filtering', - tabContext.rootHostname - ) === true; - if ( - masterSwitch && - this.noCosmeticFiltering && - µb.logger.enabled && - context === 'tabCommitted' - ) { - µb.filteringContext - .duplicate() - .fromTabId(tabId) - .setURL(tabContext.rawURL) - .setRealm('cosmetic') - .setType('dom') - .setFilter(µb.sessionSwitches.toLogData()) - .toLogger(); - } + // Evaluated on-demand + this._noCosmeticFiltering = undefined; return this; } @@ -493,15 +531,53 @@ const PageStore = class { .getNetFilteringSwitch(); } - getSpecificCosmeticFilteringSwitch() { - return this.noCosmeticFiltering !== true; - } - toggleNetFilteringSwitch(url, scope, state) { µb.toggleNetFilteringSwitch(url, scope, state); this.netFilteringCache.empty(); } + shouldApplyCosmeticFilters(frameId = 0) { + if ( this._noCosmeticFiltering === undefined ) { + this._noCosmeticFiltering = this.getNetFilteringSwitch() === false; + if ( this._noCosmeticFiltering === false ) { + this._noCosmeticFiltering = µb.sessionSwitches.evaluateZ( + 'no-cosmetic-filtering', + this.tabHostname + ) === true; + if ( this._noCosmeticFiltering && µb.logger.enabled ) { + µb.filteringContext + .duplicate() + .fromTabId(this.tabId) + .setURL(this.rawURL) + .setRealm('cosmetic') + .setType('dom') + .setFilter(µb.sessionSwitches.toLogData()) + .toLogger(); + } + } + } + if ( this._noCosmeticFiltering ) { return false; } + if ( frameId === -1 ) { return true; } + // Cosmetic filtering can be effectively disabled when both specific + // and generic cosmetic filters are disabled. + return this.shouldApplySpecificCosmeticFilters(frameId) || + this.shouldApplyGenericCosmeticFilters(frameId); + } + + shouldApplySpecificCosmeticFilters(frameId) { + if ( this.shouldApplyCosmeticFilters(-1) === false ) { return false; } + const frameStore = this.getFrameStore(frameId); + if ( frameStore === null ) { return false; } + return frameStore.shouldApplySpecificCosmeticFilters(this.tabId); + } + + shouldApplyGenericCosmeticFilters(frameId) { + if ( this.shouldApplyCosmeticFilters(-1) === false ) { return false; } + const frameStore = this.getFrameStore(frameId); + if ( frameStore === null ) { return false; } + return frameStore.shouldApplyGenericCosmeticFilters(this.tabId); + } + // https://github.com/gorhill/uBlock/issues/2105 // Be sure to always include the current page's hostname -- it might not // be present when the page itself is pulled from the browser's diff --git a/src/js/scriptlets/epicker.js b/src/js/scriptlets/epicker.js index 898882050..9ae3971a4 100644 --- a/src/js/scriptlets/epicker.js +++ b/src/js/scriptlets/epicker.js @@ -378,6 +378,7 @@ const filterTypes = { const cosmeticFilterFromElement = function(elem) { if ( elem === null ) { return 0; } if ( elem.nodeType !== 1 ) { return 0; } + if ( noCosmeticFiltering ) { return 0; } if ( candidateElements.indexOf(elem) === -1 ) { candidateElements.push(elem); @@ -709,6 +710,7 @@ const filterToDOMInterface = (( ) => { // https://github.com/gorhill/uBlock/issues/2515 // Remove trailing pseudo-element when querying. const fromCompiledCosmeticFilter = function(raw) { + if ( noCosmeticFiltering ) { return; } if ( typeof raw !== 'string' ) { return; } let elems, style; try { @@ -798,7 +800,7 @@ const filterToDOMInterface = (( ) => { if ( permanent === false || reCosmeticAnchor.test(lastFilter) === false ) { return apply(); } - if ( vAPI.domFilterer instanceof Object === false ) { return; } + if ( noCosmeticFiltering ) { return; } const cssSelectors = new Set(); const proceduralSelectors = new Set(); for ( const { raw } of lastResultset ) { @@ -886,7 +888,15 @@ const elementFromPoint = (( ) => { const magicAttr = `${vAPI.sessionId}-clickblind`; pickerRoot.setAttribute(magicAttr, ''); let elem = document.elementFromPoint(x, y); - if ( elem === document.body || elem === document.documentElement ) { + if ( + elem === null || /* to skip following tests */ + elem === document.body || + elem === document.documentElement || ( + pickerBootArgs.zap !== true && + noCosmeticFiltering && + resourceURLsFromElement(elem).length === 0 + ) + ) { elem = null; } // https://github.com/uBlockOrigin/uBlock-issues/issues/380 @@ -1013,6 +1023,7 @@ const startPicker = function() { // Try using mouse position if ( pickerBootArgs.mouse && + vAPI.mouseClick instanceof Object && typeof vAPI.mouseClick.x === 'number' && vAPI.mouseClick.x > 0 ) { @@ -1046,9 +1057,15 @@ const startPicker = function() { if ( (src !== url) && (src !== '' || url !== 'about:blank') ) { continue; } - elem.scrollIntoView({ behavior: 'smooth', block: 'start' }); filtersFrom(elem); - return showDialog({ broad: true }); + if ( + netFilterCandidates.length !== 0 || + cosmeticFilterCandidates.length !== 0 + ) { + elem.scrollIntoView({ behavior: 'smooth', block: 'center' }); + showDialog({ broad: true }); + } + return; } // A target was specified, but it wasn't found: abort. @@ -1193,12 +1210,9 @@ const onConnectionMessage = function(msg) { } // The DOM filterer will not be present when cosmetic filtering is disabled. -if ( - pickerBootArgs.zap !== true && - vAPI.domFilterer instanceof Object === false -) { - return; -} +const noCosmeticFiltering = + vAPI.domFilterer instanceof Object === false || + vAPI.noSpecificCosmeticFiltering === true; // https://github.com/gorhill/uBlock/issues/1529 // In addition to inline styles, harden the element picker styles by using