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.
This commit is contained in:
Raymond Hill 2021-07-12 11:55:58 -04:00
parent f1a453d349
commit e983f9a76e
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
7 changed files with 195 additions and 120 deletions

View file

@ -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 ) {

View file

@ -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);
};

View file

@ -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;

View file

@ -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 ) {

View file

@ -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;

View file

@ -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

View file

@ -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