1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-10-04 16:47:15 +02:00

Add support to chain :style() to procedural operators

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/382

Additionally, remnant code for pseudo-user stylesheets
has been removed. Related commit:
- 5c68867b92
This commit is contained in:
Raymond Hill 2020-09-07 08:28:01 -04:00
parent 3a51ca0002
commit 35aefed926
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
11 changed files with 283 additions and 474 deletions

View File

@ -56,6 +56,16 @@ window.addEventListener('webextFlavor', function() {
/******************************************************************************/ /******************************************************************************/
vAPI.randomToken = function() {
const n = Math.random();
return String.fromCharCode(n * 26 + 97) +
Math.floor(
(0.25 + n * 0.75) * Number.MAX_SAFE_INTEGER
).toString(36).slice(-8);
};
/******************************************************************************/
vAPI.app = { vAPI.app = {
name: manifest.name.replace(/ dev\w+ build/, ''), name: manifest.name.replace(/ dev\w+ build/, ''),
version: (( ) => { version: (( ) => {
@ -339,7 +349,10 @@ vAPI.Tabs = class {
return tabs.length !== 0 ? tabs[0] : null; return tabs.length !== 0 ? tabs[0] : null;
} }
async insertCSS() { async insertCSS(tabId, details) {
if ( vAPI.supportsUserStylesheets ) {
details.cssOrigin = 'user';
}
try { try {
await webext.tabs.insertCSS(...arguments); await webext.tabs.insertCSS(...arguments);
} }
@ -357,7 +370,10 @@ vAPI.Tabs = class {
return Array.isArray(tabs) ? tabs : []; return Array.isArray(tabs) ? tabs : [];
} }
async removeCSS() { async removeCSS(tabId, details) {
if ( vAPI.supportsUserStylesheets ) {
details.cssOrigin = 'user';
}
try { try {
await webext.tabs.removeCSS(...arguments); await webext.tabs.removeCSS(...arguments);
} }
@ -1003,9 +1019,6 @@ vAPI.messaging = {
frameId: sender.frameId, frameId: sender.frameId,
matchAboutBlank: true matchAboutBlank: true
}; };
if ( vAPI.supportsUserStylesheets ) {
details.cssOrigin = 'user';
}
if ( msg.add ) { if ( msg.add ) {
details.runAt = 'document_start'; details.runAt = 'document_start';
} }

View File

@ -39,9 +39,11 @@ if (
/******************************************************************************/ /******************************************************************************/
vAPI.randomToken = function() { vAPI.randomToken = function() {
const now = Date.now(); const n = Math.random();
return String.fromCharCode(now % 26 + 97) + return String.fromCharCode(n * 26 + 97) +
Math.floor((1 + Math.random()) * now).toString(36); Math.floor(
(0.25 + n * 0.75) * Number.MAX_SAFE_INTEGER
).toString(36).slice(-8);
}; };
vAPI.sessionId = vAPI.randomToken(); vAPI.sessionId = vAPI.randomToken();

View File

@ -138,8 +138,8 @@ const µBlock = (( ) => { // jshint ignore:line
// Read-only // Read-only
systemSettings: { systemSettings: {
compiledMagic: 28, // Increase when compiled format changes compiledMagic: 29, // Increase when compiled format changes
selfieMagic: 28, // Increase when selfie format changes selfieMagic: 29, // Increase when selfie format changes
}, },
// https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501 // https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501

View File

@ -88,16 +88,8 @@
The domFilterer makes use of platform-dependent user stylesheets[1]. The domFilterer makes use of platform-dependent user stylesheets[1].
At time of writing, only modern Firefox provides a custom implementation,
which makes for solid, reliable and low overhead cosmetic filtering on
Firefox.
The generic implementation[2] performs as best as can be, but won't ever be
as reliable and accurate as real user stylesheets.
[1] "user stylesheets" refer to local CSS rules which have priority over, [1] "user stylesheets" refer to local CSS rules which have priority over,
and can't be overriden by a web page's own CSS rules. and can't be overriden by a web page's own CSS rules.
[2] below, see platformUserCSS / platformHideNode / platformUnhideNode
*/ */
@ -492,6 +484,11 @@ vAPI.injectScriptlet = function(doc, text) {
*/ */
{ {
vAPI.hideStyle = 'display:none!important;';
// TODO: Experiment/evaluate loading procedural operator code using an
// on demand approach.
// 'P' stands for 'Procedural' // 'P' stands for 'Procedural'
const PSelectorHasTextTask = class { const PSelectorHasTextTask = class {
@ -562,14 +559,6 @@ vAPI.injectScriptlet = function(doc, text) {
} }
}; };
const PSelectorPassthru = class {
constructor() {
}
transpose(node, output) {
output.push(node);
}
};
const PSelectorSpathTask = class { const PSelectorSpathTask = class {
constructor(task) { constructor(task) {
this.spath = task[1]; this.spath = task[1];
@ -701,17 +690,13 @@ vAPI.injectScriptlet = function(doc, text) {
[ ':min-text-length', PSelectorMinTextLengthTask ], [ ':min-text-length', PSelectorMinTextLengthTask ],
[ ':not', PSelectorIfNotTask ], [ ':not', PSelectorIfNotTask ],
[ ':nth-ancestor', PSelectorUpwardTask ], [ ':nth-ancestor', PSelectorUpwardTask ],
[ ':remove', PSelectorPassthru ],
[ ':spath', PSelectorSpathTask ], [ ':spath', PSelectorSpathTask ],
[ ':upward', PSelectorUpwardTask ], [ ':upward', PSelectorUpwardTask ],
[ ':watch-attr', PSelectorWatchAttrs ], [ ':watch-attr', PSelectorWatchAttrs ],
[ ':xpath', PSelectorXpathTask ], [ ':xpath', PSelectorXpathTask ],
]); ]);
} }
this.budget = 200; // I arbitrary picked a 1/5 second
this.raw = o.raw; this.raw = o.raw;
this.cost = 0;
this.lastAllowanceTime = 0;
this.selector = o.selector; this.selector = o.selector;
this.tasks = []; this.tasks = [];
const tasks = o.tasks; const tasks = o.tasks;
@ -722,9 +707,6 @@ vAPI.injectScriptlet = function(doc, text) {
); );
} }
} }
if ( o.action !== undefined ) {
this.action = o.action;
}
} }
prime(input) { prime(input) {
const root = input || document; const root = input || document;
@ -760,10 +742,20 @@ vAPI.injectScriptlet = function(doc, text) {
return false; return false;
} }
}; };
PSelector.prototype.action = undefined;
PSelector.prototype.hit = false;
PSelector.prototype.operatorToTaskMap = undefined; PSelector.prototype.operatorToTaskMap = undefined;
const PSelectorRoot = class extends PSelector {
constructor(o, styleToken) {
super(o);
this.budget = 200; // I arbitrary picked a 1/5 second
this.raw = o.raw;
this.cost = 0;
this.lastAllowanceTime = 0;
this.styleToken = styleToken;
}
};
PSelectorRoot.prototype.hit = false;
const DOMProceduralFilterer = class { const DOMProceduralFilterer = class {
constructor(domFilterer) { constructor(domFilterer) {
this.domFilterer = domFilterer; this.domFilterer = domFilterer;
@ -771,41 +763,49 @@ vAPI.injectScriptlet = function(doc, text) {
this.domIsWatched = false; this.domIsWatched = false;
this.mustApplySelectors = false; this.mustApplySelectors = false;
this.selectors = new Map(); this.selectors = new Map();
this.hiddenNodes = new Set(); this.masterToken = vAPI.randomToken();
this.styleTokenMap = new Map();
this.styledNodes = new Set();
if ( vAPI.domWatcher instanceof Object ) { if ( vAPI.domWatcher instanceof Object ) {
vAPI.domWatcher.addListener(this); vAPI.domWatcher.addListener(this);
} }
} }
addProceduralSelectors(aa) { addProceduralSelectors(selectors) {
const addedSelectors = []; const addedSelectors = [];
let mustCommit = this.domIsWatched; let mustCommit = this.domIsWatched;
for ( let i = 0, n = aa.length; i < n; i++ ) { for ( const raw of selectors ) {
const raw = aa[i]; if ( this.selectors.has(raw) ) { continue; }
const o = JSON.parse(raw); const o = JSON.parse(raw);
if ( o.action === 'style' ) {
this.domFilterer.addCSSRule(o.selector, o.tasks[0][1]);
mustCommit = true;
continue;
}
if ( o.pseudo !== undefined ) { if ( o.pseudo !== undefined ) {
this.domFilterer.addCSSRule( this.domFilterer.addCSSRule(o.selector, vAPI.hideStyle);
o.selector,
'display:none!important;'
);
mustCommit = true; mustCommit = true;
continue; continue;
} }
if ( o.tasks !== undefined ) { // CSS selector-based styles.
if ( this.selectors.has(raw) === false ) { if (
const pselector = new PSelector(o); o.action !== undefined &&
o.action[0] === ':style' &&
o.tasks === undefined
) {
this.domFilterer.addCSSRule(o.selector, o.action[1]);
mustCommit = true;
continue;
}
let style, styleToken;
if ( o.action === undefined ) {
style = vAPI.hideStyle;
} else if ( o.action[0] === ':style' ) {
style = o.action[1];
}
if ( style !== undefined ) {
styleToken = this.styleTokenFromStyle(style);
}
const pselector = new PSelectorRoot(o, styleToken);
this.selectors.set(raw, pselector); this.selectors.set(raw, pselector);
addedSelectors.push(pselector); addedSelectors.push(pselector);
mustCommit = true; mustCommit = true;
} }
continue;
}
}
if ( mustCommit === false ) { return; } if ( mustCommit === false ) { return; }
this.mustApplySelectors = this.selectors.size !== 0; this.mustApplySelectors = this.selectors.size !== 0;
this.domFilterer.commit(); this.domFilterer.commit();
@ -828,8 +828,8 @@ vAPI.injectScriptlet = function(doc, text) {
// https://github.com/uBlockOrigin/uBlock-issues/issues/341 // https://github.com/uBlockOrigin/uBlock-issues/issues/341
// Be ready to unhide nodes which no longer matches any of // Be ready to unhide nodes which no longer matches any of
// the procedural selectors. // the procedural selectors.
const toRemove = this.hiddenNodes; const toUnstyle = this.styledNodes;
this.hiddenNodes = new Set(); this.styledNodes = new Set();
let t0 = Date.now(); let t0 = Date.now();
@ -851,37 +851,54 @@ vAPI.injectScriptlet = function(doc, text) {
t0 = t1; t0 = t1;
if ( nodes.length === 0 ) { continue; } if ( nodes.length === 0 ) { continue; }
pselector.hit = true; pselector.hit = true;
if ( pselector.action === 'remove' ) { this.styleNodes(nodes, pselector.styleToken);
this.removeNodes(nodes);
} else {
this.hideNodes(nodes);
}
} }
for ( const node of toRemove ) { this.unstyleNodes(toUnstyle);
if ( this.hiddenNodes.has(node) ) { continue; }
this.domFilterer.unhideNode(node);
}
//console.timeEnd('procedural selectors/dom layout changed'); //console.timeEnd('procedural selectors/dom layout changed');
} }
hideNodes(nodes) { styleTokenFromStyle(style) {
for ( const node of nodes ) { if ( style === undefined ) { return; }
if ( node.parentElement === null ) { continue; } let styleToken = this.styleTokenMap.get(style);
this.domFilterer.hideNode(node); if ( styleToken !== undefined ) { return styleToken; }
this.hiddenNodes.add(node); styleToken = vAPI.randomToken();
} this.styleTokenMap.set(style, styleToken);
this.domFilterer.addCSSRule(
`[${this.masterToken}][${styleToken}]`,
style,
{ silent: true }
);
return styleToken;
} }
removeNodes(nodes) { styleNodes(nodes, styleToken) {
if ( styleToken === undefined ) {
for ( const node of nodes ) { for ( const node of nodes ) {
node.textContent = ''; node.textContent = '';
node.remove(); node.remove();
} }
return;
}
for ( const node of nodes ) {
if ( node.parentElement === null ) { continue; }
node.setAttribute(this.masterToken, '');
node.setAttribute(styleToken, '');
}
}
// TODO: Current assumption is one style per hit element. Could be an
// issue if an element has multiple styling and one styling is
// brough back. Possibly too rare to care about this for now.
unstyleNodes(nodes) {
for ( const node of nodes ) {
if ( this.styledNodes.has(node) ) { continue; }
node.removeAttribute(this.masterToken);
}
} }
createProceduralFilter(o) { createProceduralFilter(o) {
return new PSelector(o); return new PSelectorRoot(o);
} }
onDOMCreated() { onDOMCreated() {
@ -908,14 +925,10 @@ vAPI.injectScriptlet = function(doc, text) {
this.disabled = false; this.disabled = false;
this.listeners = []; this.listeners = [];
this.filterset = new Set(); this.filterset = new Set();
this.excludedNodeSet = new WeakSet();
this.addedCSSRules = new Set(); this.addedCSSRules = new Set();
this.exceptedCSSRules = []; this.exceptedCSSRules = [];
this.reOnlySelectors = /\n\{[^\n]+/g;
this.exceptions = []; this.exceptions = [];
this.proceduralFilterer = null; this.proceduralFilterer = null;
this.hideNodeAttr = undefined;
this.hideNodeStyleSheetInjected = false;
// https://github.com/uBlockOrigin/uBlock-issues/issues/167 // https://github.com/uBlockOrigin/uBlock-issues/issues/167
// By the time the DOMContentLoaded is fired, the content script might // By the time the DOMContentLoaded is fired, the content script might
// have been disconnected from the background page. Unclear why this // have been disconnected from the background page. Unclear why this
@ -988,33 +1001,6 @@ vAPI.injectScriptlet = function(doc, text) {
} }
} }
excludeNode(node) {
this.excludedNodeSet.add(node);
this.unhideNode(node);
}
unexcludeNode(node) {
this.excludedNodeSet.delete(node);
}
hideNode(node) {
if ( this.excludedNodeSet.has(node) ) { return; }
if ( this.hideNodeAttr === undefined ) { return; }
node.setAttribute(this.hideNodeAttr, '');
if ( this.hideNodeStyleSheetInjected ) { return; }
this.hideNodeStyleSheetInjected = true;
this.addCSSRule(
`[${this.hideNodeAttr}]`,
'display:none!important;',
{ silent: true }
);
}
unhideNode(node) {
if ( this.hideNodeAttr === undefined ) { return; }
node.removeAttribute(this.hideNodeAttr);
}
toggle(state, callback) { toggle(state, callback) {
if ( state === undefined ) { state = this.disabled; } if ( state === undefined ) { state = this.disabled; }
if ( state !== this.disabled ) { return; } if ( state !== this.disabled ) { return; }
@ -1031,24 +1017,6 @@ vAPI.injectScriptlet = function(doc, text) {
userStylesheet.apply(callback); userStylesheet.apply(callback);
} }
getAllSelectors_(all) {
const out = {
declarative: [],
exceptions: this.exceptedCSSRules,
};
for ( const entry of this.filterset ) {
let selectors = entry.selectors;
if ( all !== true && this.hideNodeAttr !== undefined ) {
selectors = selectors
.replace(`[${this.hideNodeAttr}]`, '')
.replace(/^,\n|,\n$/gm, '');
if ( selectors === '' ) { continue; }
}
out.declarative.push([ selectors, entry.declarations ]);
}
return out;
}
// Here we will deal with: // Here we will deal with:
// - Injecting low priority user styles; // - Injecting low priority user styles;
// - Notifying listeners about changed filterset. // - Notifying listeners about changed filterset.
@ -1097,7 +1065,7 @@ vAPI.injectScriptlet = function(doc, text) {
} }
addProceduralSelectors(aa) { addProceduralSelectors(aa) {
if ( aa.length === 0 ) { return; } if ( Array.isArray(aa) === false || aa.length === 0 ) { return; }
this.proceduralFiltererInstance().addProceduralSelectors(aa); this.proceduralFiltererInstance().addProceduralSelectors(aa);
} }
@ -1105,25 +1073,39 @@ vAPI.injectScriptlet = function(doc, text) {
return this.proceduralFiltererInstance().createProceduralFilter(o); return this.proceduralFiltererInstance().createProceduralFilter(o);
} }
getAllSelectors() { getAllSelectors(bits = 0) {
const out = this.getAllSelectors_(false); const out = {
out.procedural = this.proceduralFilterer instanceof Object declarative: [],
exceptions: this.exceptedCSSRules,
};
const hasProcedural = this.proceduralFilterer instanceof Object;
const includePrivateSelectors = (bits & 0b01) !== 0;
const masterToken = hasProcedural
? `[${this.proceduralFilterer.masterToken}]`
: undefined;
for ( const entry of this.filterset ) {
const selectors = entry.selectors;
if (
includePrivateSelectors === false &&
masterToken !== undefined &&
selectors.startsWith(masterToken)
) {
continue;
}
out.declarative.push([ selectors, entry.declarations ]);
}
const excludeProcedurals = (bits & 0b10) !== 0;
if ( excludeProcedurals !== true ) {
out.procedural = hasProcedural
? Array.from(this.proceduralFilterer.selectors.values()) ? Array.from(this.proceduralFilterer.selectors.values())
: []; : [];
}
return out; return out;
} }
getAllExceptionSelectors() { getAllExceptionSelectors() {
return this.exceptions.join(',\n'); return this.exceptions.join(',\n');
} }
getFilteredElementCount() {
const details = this.getAllSelectors_(true);
if ( Array.isArray(details.declarative) === false ) { return 0; }
const selectors = details.declarative.map(entry => entry[0]);
if ( selectors.length === 0 ) { return 0; }
return document.querySelectorAll(selectors.join(',\n')).length;
}
}; };
} }
@ -1548,29 +1530,11 @@ vAPI.injectScriptlet = function(doc, text) {
let mustCommit = false; let mustCommit = false;
if ( result ) { if ( result ) {
let selectors = result.simple; let selectors = result.injected;
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
domFilterer.addCSSRule(
selectors,
'display:none!important;',
{ type: 'simple' }
);
mustCommit = true;
}
selectors = result.complex;
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
domFilterer.addCSSRule(
selectors,
'display:none!important;',
{ type: 'complex' }
);
mustCommit = true;
}
selectors = result.injected;
if ( typeof selectors === 'string' && selectors.length !== 0 ) { if ( typeof selectors === 'string' && selectors.length !== 0 ) {
domFilterer.addCSSRule( domFilterer.addCSSRule(
selectors, selectors,
'display:none!important;', vAPI.hideStyle,
{ injected: true } { injected: true }
); );
mustCommit = true; mustCommit = true;
@ -1740,37 +1704,15 @@ vAPI.injectScriptlet = function(doc, text) {
vAPI.domSurveyor = null; vAPI.domSurveyor = null;
} }
domFilterer.exceptions = cfeDetails.exceptionFilters; domFilterer.exceptions = cfeDetails.exceptionFilters;
domFilterer.hideNodeAttr = cfeDetails.hideNodeAttr;
domFilterer.hideNodeStyleSheetInjected =
cfeDetails.hideNodeStyleSheetInjected === true;
domFilterer.addCSSRule(
cfeDetails.declarativeFilters,
'display:none!important;'
);
domFilterer.addCSSRule(
cfeDetails.highGenericHideSimple,
'display:none!important;',
{ type: 'simple', lazy: true }
);
domFilterer.addCSSRule(
cfeDetails.highGenericHideComplex,
'display:none!important;',
{ type: 'complex', lazy: true }
);
domFilterer.addCSSRule( domFilterer.addCSSRule(
cfeDetails.injectedHideFilters, cfeDetails.injectedHideFilters,
'display:none!important;', vAPI.hideStyle,
{ injected: true } { injected: true }
); );
domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters); domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters);
domFilterer.exceptCSSRules(cfeDetails.exceptedFilters); domFilterer.exceptCSSRules(cfeDetails.exceptedFilters);
} }
if ( cfeDetails.networkFilters.length !== 0 ) {
vAPI.userStylesheet.add(
cfeDetails.networkFilters + '\n{display:none!important;}');
}
vAPI.userStylesheet.apply(); vAPI.userStylesheet.apply();
// Library of resources is located at: // Library of resources is located at:

View File

@ -32,12 +32,6 @@ const cosmeticSurveyingMissCountMax =
parseInt(vAPI.localStorage.getItem('cosmeticSurveyingMissCountMax'), 10) || parseInt(vAPI.localStorage.getItem('cosmeticSurveyingMissCountMax'), 10) ||
15; 15;
let supportsUserStylesheets = vAPI.webextFlavor.soup.has('user_stylesheet');
// https://www.reddit.com/r/uBlockOrigin/comments/8dkvqn/116_broken_loading_custom_filters_from_my_filters/
window.addEventListener('webextFlavor', function() {
supportsUserStylesheets = vAPI.webextFlavor.soup.has('user_stylesheet');
}, { once: true });
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
@ -754,9 +748,9 @@ FilterContainer.prototype.triggerSelectorCachePruner = function() {
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.addToSelectorCache = function(details) { FilterContainer.prototype.addToSelectorCache = function(details) {
let hostname = details.hostname; const hostname = details.hostname;
if ( typeof hostname !== 'string' || hostname === '' ) { return; } if ( typeof hostname !== 'string' || hostname === '' ) { return; }
let selectors = details.selectors; const selectors = details.selectors;
if ( Array.isArray(selectors) === false ) { return; } if ( Array.isArray(selectors) === false ) { return; }
let entry = this.selectorCache.get(hostname); let entry = this.selectorCache.get(hostname);
if ( entry === undefined ) { if ( entry === undefined ) {
@ -838,14 +832,6 @@ FilterContainer.prototype.pruneSelectorCacheAsync = function() {
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.randomAlphaToken = function() {
const now = Date.now();
return String.fromCharCode(now % 26 + 97) +
Math.floor((1 + Math.random()) * now).toString(36);
};
/******************************************************************************/
FilterContainer.prototype.getSession = function() { FilterContainer.prototype.getSession = function() {
return this.sessionFilterDB; return this.sessionFilterDB;
}; };
@ -912,57 +898,38 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
return; return;
} }
const out = { const out = { injected: '', excepted, };
simple: Array.from(simpleSelectors),
complex: Array.from(complexSelectors),
injected: '',
excepted,
};
// Important: always clear used registers before leaving. const injected = [];
if ( simpleSelectors.size !== 0 ) {
injected.push(Array.from(simpleSelectors).join(',\n'));
simpleSelectors.clear(); simpleSelectors.clear();
}
if ( complexSelectors.size !== 0 ) {
injected.push(Array.from(complexSelectors).join(',\n'));
complexSelectors.clear(); complexSelectors.clear();
}
// Cache and inject (if user stylesheets supported) looked-up low generic // Cache and inject looked-up low generic cosmetic filters.
// cosmetic filters. if ( injected.length === 0 ) { return out; }
if (
(typeof request.hostname === 'string' && request.hostname !== '') && if ( typeof request.hostname === 'string' && request.hostname !== '' ) {
(out.simple.length !== 0 || out.complex.length !== 0)
) {
this.addToSelectorCache({ this.addToSelectorCache({
cost: request.surveyCost || 0, cost: request.surveyCost || 0,
hostname: request.hostname, hostname: request.hostname,
injectedHideFilters: '', injectedHideFilters: '',
selectors: out.simple.concat(out.complex), selectors: injected,
type: 'cosmetic' type: 'cosmetic',
}); });
} }
// If user stylesheets are supported in the current process, inject the
// cosmetic filters now.
if (
supportsUserStylesheets &&
request.tabId !== undefined &&
request.frameId !== undefined
) {
const injected = [];
if ( out.simple.length !== 0 ) {
injected.push(out.simple.join(',\n'));
out.simple = [];
}
if ( out.complex.length !== 0 ) {
injected.push(out.complex.join(',\n'));
out.complex = [];
}
out.injected = injected.join(',\n'); out.injected = injected.join(',\n');
vAPI.tabs.insertCSS(request.tabId, { vAPI.tabs.insertCSS(request.tabId, {
code: out.injected + '\n{display:none!important;}', code: out.injected + '\n{display:none!important;}',
cssOrigin: 'user',
frameId: request.frameId, frameId: request.frameId,
matchAboutBlank: true, matchAboutBlank: true,
runAt: 'document_start', runAt: 'document_start',
}); });
}
//console.timeEnd('cosmeticFilteringEngine.retrieveGenericSelectors'); //console.timeEnd('cosmeticFilteringEngine.retrieveGenericSelectors');
@ -989,18 +956,11 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
ready: this.frozen, ready: this.frozen,
hostname: hostname, hostname: hostname,
domain: request.domain, domain: request.domain,
declarativeFilters: [],
exceptionFilters: [], exceptionFilters: [],
exceptedFilters: [], exceptedFilters: [],
hideNodeAttr: this.randomAlphaToken(),
hideNodeStyleSheetInjected: false,
highGenericHideSimple: '',
highGenericHideComplex: '',
injectedHideFilters: '',
networkFilters: '',
noDOMSurveying: this.needDOMSurveyor === false, noDOMSurveying: this.needDOMSurveyor === false,
proceduralFilters: []
}; };
const injectedHideFilters = [];
if ( options.noCosmeticFiltering !== true ) { if ( options.noCosmeticFiltering !== true ) {
const specificSet = this.$specificSet; const specificSet = this.$specificSet;
@ -1063,7 +1023,7 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
} }
if ( specificSet.size !== 0 ) { if ( specificSet.size !== 0 ) {
out.declarativeFilters = Array.from(specificSet); injectedHideFilters.push(Array.from(specificSet).join(',\n'));
} }
if ( proceduralSet.size !== 0 ) { if ( proceduralSet.size !== 0 ) {
out.proceduralFilters = Array.from(proceduralSet); out.proceduralFilters = Array.from(proceduralSet);
@ -1078,10 +1038,10 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
// string in memory, which I have observed occurs when the string is // string in memory, which I have observed occurs when the string is
// stored directly as a value in a Map. // stored directly as a value in a Map.
if ( options.noGenericCosmeticFiltering !== true ) { if ( options.noGenericCosmeticFiltering !== true ) {
const exceptionHash = out.exceptionFilters.join(); const exceptionSetHash = out.exceptionFilters.join();
for ( const type in this.highlyGeneric ) { for ( const key in this.highlyGeneric ) {
const entry = this.highlyGeneric[type]; const entry = this.highlyGeneric[key];
let str = entry.mru.lookup(exceptionHash); let str = entry.mru.lookup(exceptionSetHash);
if ( str === undefined ) { if ( str === undefined ) {
str = { s: entry.str, excepted: [] }; str = { s: entry.str, excepted: [] };
let genericSet = entry.dict; let genericSet = entry.dict;
@ -1098,13 +1058,14 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
} }
str.s = Array.from(genericSet).join(',\n'); str.s = Array.from(genericSet).join(',\n');
} }
entry.mru.add(exceptionHash, str); entry.mru.add(exceptionSetHash, str);
} }
out[entry.canonical] = str.s;
if ( str.excepted.length !== 0 ) { if ( str.excepted.length !== 0 ) {
out.exceptedFilters.push(...str.excepted); out.exceptedFilters.push(...str.excepted);
} }
if ( str.s.length !== 0 ) {
injectedHideFilters.push(str.s);
}
} }
} }
@ -1115,54 +1076,26 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
dummySet.clear(); dummySet.clear();
} }
// CSS selectors for collapsible blocked elements
if ( cacheEntry ) {
const networkFilters = [];
cacheEntry.retrieve('net', networkFilters);
out.networkFilters = networkFilters.join(',\n');
}
// https://github.com/gorhill/uBlock/issues/3160
// If user stylesheets are supported in the current process, inject the
// cosmetic filters now.
if (
supportsUserStylesheets &&
request.tabId !== undefined &&
request.frameId !== undefined
) {
const injectedHideFilters = [];
if ( out.declarativeFilters.length !== 0 ) {
injectedHideFilters.push(out.declarativeFilters.join(',\n'));
out.declarativeFilters = [];
}
if ( out.proceduralFilters.length !== 0 ) {
injectedHideFilters.push('[' + out.hideNodeAttr + ']');
out.hideNodeStyleSheetInjected = true;
}
if ( out.highGenericHideSimple.length !== 0 ) {
injectedHideFilters.push(out.highGenericHideSimple);
out.highGenericHideSimple = '';
}
if ( out.highGenericHideComplex.length !== 0 ) {
injectedHideFilters.push(out.highGenericHideComplex);
out.highGenericHideComplex = '';
}
out.injectedHideFilters = injectedHideFilters.join(',\n');
const details = { const details = {
code: '', code: '',
cssOrigin: 'user',
frameId: request.frameId, frameId: request.frameId,
matchAboutBlank: true, matchAboutBlank: true,
runAt: 'document_start', runAt: 'document_start',
}; };
if ( out.injectedHideFilters.length !== 0 ) {
if ( injectedHideFilters.length !== 0 ) {
out.injectedHideFilters = injectedHideFilters.join(',\n');
details.code = out.injectedHideFilters + '\n{display:none!important;}'; details.code = out.injectedHideFilters + '\n{display:none!important;}';
vAPI.tabs.insertCSS(request.tabId, details); vAPI.tabs.insertCSS(request.tabId, details);
} }
if ( out.networkFilters.length !== 0 ) {
details.code = out.networkFilters + '\n{display:none!important;}'; // CSS selectors for collapsible blocked elements
if ( cacheEntry ) {
const networkFilters = [];
cacheEntry.retrieve('net', networkFilters);
if ( networkFilters.length !== 0 ) {
details.code = networkFilters.join('\n') + '\n{display:none!important;}';
vAPI.tabs.insertCSS(request.tabId, details); vAPI.tabs.insertCSS(request.tabId, details);
out.networkFilters = '';
} }
} }

View File

@ -56,7 +56,7 @@ if ( epickerId === null ) { return; }
let epickerConnectionId; let epickerConnectionId;
let filterHostname = ''; let filterHostname = '';
let filterOrigin = ''; let filterOrigin = '';
let filterResultset = []; let resultsetOpt;
/******************************************************************************/ /******************************************************************************/
@ -88,11 +88,8 @@ const userFilterFromCandidate = function(filter) {
opts.push(`domain=${filterHostname}`); opts.push(`domain=${filterHostname}`);
} }
if ( filterResultset.length !== 0 ) { if ( resultsetOpt !== undefined ) {
const item = filterResultset[0]; opts.push(resultsetOpt);
if ( item.opts ) {
opts.push(item.opts);
}
} }
if ( opts.length ) { if ( opts.length ) {
@ -632,10 +629,10 @@ const onPickerMessage = function(msg) {
case 'showDialog': case 'showDialog':
showDialog(msg); showDialog(msg);
break; break;
case 'filterResultset': { case 'resultsetDetails': {
filterResultset = msg.resultset; resultsetOpt = msg.opt;
$id('resultsetCount').textContent = filterResultset.length; $id('resultsetCount').textContent = msg.count;
if ( filterResultset.length !== 0 ) { if ( msg.count !== 0 ) {
$id('create').removeAttribute('disabled'); $id('create').removeAttribute('disabled');
} else { } else {
$id('create').setAttribute('disabled', ''); $id('create').setAttribute('disabled', '');

View File

@ -278,7 +278,11 @@ const handlers = {
continue; continue;
} }
const details = JSON.parse(selector); const details = JSON.parse(selector);
if ( details.action === 'style' ) { if (
details.action !== undefined &&
details.tasks === undefined &&
details.action[0] === ':style'
) {
exceptionDict.set(details.selector, details.raw); exceptionDict.set(details.selector, details.raw);
continue; continue;
} }

View File

@ -501,16 +501,15 @@ const cosmeticFilterMapper = (function() {
} }
const nodesFromStyleTag = function(rootNode) { const nodesFromStyleTag = function(rootNode) {
var filterMap = roRedNodes, const filterMap = roRedNodes;
entry, selector, canonical, nodes, node; const details = vAPI.domFilterer.getAllSelectors();
var details = vAPI.domFilterer.getAllSelectors();
// Declarative selectors. // Declarative selectors.
for ( entry of (details.declarative || []) ) { for ( const entry of (details.declarative || []) ) {
for ( selector of entry[0].split(',\n') ) { for ( const selector of entry[0].split(',\n') ) {
canonical = selector; let canonical = selector;
if ( entry[1] !== 'display:none!important;' ) { let nodes;
if ( entry[1] !== vAPI.hideStyle ) {
canonical += ':style(' + entry[1] + ')'; canonical += ':style(' + entry[1] + ')';
} }
if ( reHasCSSCombinators.test(selector) ) { if ( reHasCSSCombinators.test(selector) ) {
@ -524,7 +523,7 @@ const cosmeticFilterMapper = (function() {
} }
nodes = rootNode.querySelectorAll(selector); nodes = rootNode.querySelectorAll(selector);
} }
for ( node of nodes ) { for ( const node of nodes ) {
if ( filterMap.has(node) === false ) { if ( filterMap.has(node) === false ) {
filterMap.set(node, canonical); filterMap.set(node, canonical);
} }
@ -533,9 +532,9 @@ const cosmeticFilterMapper = (function() {
} }
// Procedural selectors. // Procedural selectors.
for ( entry of (details.procedural || []) ) { for ( const entry of (details.procedural || []) ) {
nodes = entry.exec(); const nodes = entry.exec();
for ( node of nodes ) { for ( const node of nodes ) {
// Upgrade declarative selector to procedural one // Upgrade declarative selector to procedural one
filterMap.set(node, entry.raw); filterMap.set(node, entry.raw);
} }

View File

@ -51,7 +51,7 @@
if ( isNaN(surveyResults.hiddenElementCount) ) { if ( isNaN(surveyResults.hiddenElementCount) ) {
surveyResults.hiddenElementCount = (( ) => { surveyResults.hiddenElementCount = (( ) => {
if ( vAPI.domFilterer instanceof Object === false ) { return 0; } if ( vAPI.domFilterer instanceof Object === false ) { return 0; }
const details = vAPI.domFilterer.getAllSelectors_(true); const details = vAPI.domFilterer.getAllSelectors(0b11);
if ( if (
Array.isArray(details.declarative) === false || Array.isArray(details.declarative) === false ||
details.declarative.length === 0 details.declarative.length === 0

View File

@ -49,6 +49,8 @@ const lastNetFilterSession = window.location.host + window.location.pathname;
let lastNetFilterHostname = ''; let lastNetFilterHostname = '';
let lastNetFilterUnion = ''; let lastNetFilterUnion = '';
const hideBackgroundStyle = 'background-image:none!important;';
/******************************************************************************/ /******************************************************************************/
const safeQuerySelectorAll = function(node, selector) { const safeQuerySelectorAll = function(node, selector) {
@ -645,10 +647,10 @@ const filterToDOMInterface = (( ) => {
reFilter.test(elem.currentSrc) reFilter.test(elem.currentSrc)
) { ) {
out.push({ out.push({
type: 'network', elem,
elem: elem,
src: srcProp, src: srcProp,
opts: filterTypes[elem.localName], opt: filterTypes[elem.localName],
style: vAPI.hideStyle,
}); });
} }
} }
@ -657,10 +659,10 @@ const filterToDOMInterface = (( ) => {
for ( const elem of candidateElements ) { for ( const elem of candidateElements ) {
if ( reFilter.test(backgroundImageURLFromElement(elem)) ) { if ( reFilter.test(backgroundImageURLFromElement(elem)) ) {
out.push({ out.push({
type: 'network', elem,
elem: elem, bg: true,
style: 'background-image', opt: 'image',
opts: 'image', style: hideBackgroundStyle,
}); });
} }
} }
@ -690,7 +692,7 @@ const filterToDOMInterface = (( ) => {
const out = []; const out = [];
for ( const elem of elems ) { for ( const elem of elems ) {
if ( elem === pickerRoot ) { continue; } if ( elem === pickerRoot ) { continue; }
out.push({ type: 'cosmetic', elem, raw }); out.push({ elem, raw, style: vAPI.hideStyle });
} }
return out; return out;
}; };
@ -702,33 +704,28 @@ const filterToDOMInterface = (( ) => {
// Remove trailing pseudo-element when querying. // Remove trailing pseudo-element when querying.
const fromCompiledCosmeticFilter = function(raw) { const fromCompiledCosmeticFilter = function(raw) {
if ( typeof raw !== 'string' ) { return; } if ( typeof raw !== 'string' ) { return; }
let elems; let elems, style;
try { try {
const o = JSON.parse(raw); const o = JSON.parse(raw);
if ( o.action === 'style' ) {
elems = document.querySelectorAll(
o.selector.replace(rePseudoElements, '')
);
lastAction = o.selector + ' {' + o.tasks[0][1] + '}';
} else if ( o.tasks ) {
elems = vAPI.domFilterer.createProceduralFilter(o).exec(); elems = vAPI.domFilterer.createProceduralFilter(o).exec();
} style = o.action === undefined || o.action[0] !== ':style'
? vAPI.hideStyle
: o.action[1];
} catch(ex) { } catch(ex) {
return; return;
} }
if ( !elems ) { return; } if ( !elems ) { return; }
const out = []; const out = [];
for ( const elem of elems ) { for ( const elem of elems ) {
out.push({ type: 'cosmetic', elem, raw }); out.push({ elem, raw, style });
} }
return out; return out;
}; };
vAPI.epickerStyleProxies = vAPI.epickerStyleProxies || new Map();
let lastFilter; let lastFilter;
let lastResultset; let lastResultset;
let lastAction;
let appliedStyleTag;
let applied = false;
let previewing = false; let previewing = false;
const queryAll = function(details) { const queryAll = function(details) {
@ -738,11 +735,10 @@ const filterToDOMInterface = (( ) => {
unapply(); unapply();
if ( filter === '' || filter === '!' ) { if ( filter === '' || filter === '!' ) {
lastFilter = ''; lastFilter = '';
lastResultset = []; lastResultset = undefined;
return lastResultset; return;
} }
lastFilter = filter; lastFilter = filter;
lastAction = undefined;
if ( filter.startsWith('##') === false ) { if ( filter.startsWith('##') === false ) {
lastResultset = fromNetworkFilter(filter); lastResultset = fromNetworkFilter(filter);
if ( previewing ) { apply(); } if ( previewing ) { apply(); }
@ -759,86 +755,29 @@ const filterToDOMInterface = (( ) => {
return lastResultset; return lastResultset;
}; };
// https://github.com/gorhill/uBlock/issues/1629
// Avoid hiding the element picker's related elements.
const applyHide = function() {
const htmlElem = document.documentElement;
for ( const item of lastResultset ) {
const elem = item.elem;
if ( elem === pickerRoot ) { continue; }
if (
(elem !== htmlElem) &&
(item.type === 'cosmetic' || item.type === 'network' && item.src !== undefined)
) {
vAPI.domFilterer.hideNode(elem);
item.hidden = true;
}
if ( item.type === 'network' && item.style === 'background-image' ) {
const style = elem.style;
item.backgroundImage = style.getPropertyValue('background-image');
item.backgroundImagePriority = style.getPropertyPriority('background-image');
style.setProperty('background-image', 'none', 'important');
}
}
};
const unapplyHide = function() {
if ( lastResultset === undefined ) { return; }
for ( const item of lastResultset ) {
if ( item.hidden === true ) {
vAPI.domFilterer.unhideNode(item.elem);
item.hidden = false;
}
if ( item.hasOwnProperty('backgroundImage') ) {
item.elem.style.setProperty(
'background-image',
item.backgroundImage,
item.backgroundImagePriority
);
delete item.backgroundImage;
}
}
};
const unapplyStyle = function() {
if ( !appliedStyleTag || appliedStyleTag.parentNode === null ) {
return;
}
appliedStyleTag.parentNode.removeChild(appliedStyleTag);
};
const applyStyle = function() {
if ( !appliedStyleTag ) {
appliedStyleTag = document.createElement('style');
appliedStyleTag.setAttribute('type', 'text/css');
}
appliedStyleTag.textContent = lastAction;
if ( appliedStyleTag.parentNode === null ) {
document.head.appendChild(appliedStyleTag);
}
};
const apply = function() { const apply = function() {
if ( applied ) {
unapply(); unapply();
if ( Array.isArray(lastResultset) === false ) { return; }
const rootElem = document.documentElement;
for ( const { elem, style } of lastResultset ) {
if ( elem === pickerRoot ) { continue; }
if ( elem === rootElem && style === vAPI.hideStyle ) { continue; }
let styleToken = vAPI.epickerStyleProxies.get(style);
if ( styleToken === undefined ) {
styleToken = vAPI.randomToken();
vAPI.epickerStyleProxies.set(style, styleToken);
vAPI.userStylesheet.add(`[${styleToken}]\n{${style}}`, true);
} }
if ( lastResultset === undefined ) { return; } elem.setAttribute(styleToken, '');
if ( typeof lastAction === 'string' ) {
applyStyle();
} else {
applyHide();
} }
applied = true;
}; };
const unapply = function() { const unapply = function() {
if ( !applied ) { return; } for ( const styleToken of vAPI.epickerStyleProxies.values() ) {
if ( typeof lastAction === 'string' ) { for ( const elem of document.querySelectorAll(`[${styleToken}]`) ) {
unapplyStyle(); elem.removeAttribute(styleToken);
} else { }
unapplyHide();
} }
applied = false;
}; };
// https://www.reddit.com/r/uBlockOrigin/comments/c62irc/ // https://www.reddit.com/r/uBlockOrigin/comments/c62irc/
@ -849,24 +788,24 @@ const filterToDOMInterface = (( ) => {
if ( previewing === false ) { if ( previewing === false ) {
return unapply(); return unapply();
} }
if ( lastResultset === undefined ) { return; } if ( Array.isArray(lastResultset) === false ) { return; }
apply(); if ( permanent === false || lastFilter.startsWith('##') === false ) {
if ( permanent === false ) { return; } return apply();
}
if ( vAPI.domFilterer instanceof Object === false ) { return; } if ( vAPI.domFilterer instanceof Object === false ) { return; }
const cssSelectors = new Set(); const cssSelectors = new Set();
const proceduralSelectors = new Set(); const proceduralSelectors = new Set();
for ( const item of lastResultset ) { for ( const { raw } of lastResultset ) {
if ( item.type !== 'cosmetic' ) { continue; } if ( raw.startsWith('{') ) {
if ( item.raw.startsWith('{') ) { proceduralSelectors.add(raw);
proceduralSelectors.add(item.raw);
} else { } else {
cssSelectors.add(item.raw); cssSelectors.add(raw);
} }
} }
if ( cssSelectors.size !== 0 ) { if ( cssSelectors.size !== 0 ) {
vAPI.domFilterer.addCSSRule( vAPI.domFilterer.addCSSRule(
Array.from(cssSelectors), Array.from(cssSelectors),
'display:none!important;' vAPI.hideStyle
); );
} }
if ( proceduralSelectors.size !== 0 ) { if ( proceduralSelectors.size !== 0 ) {
@ -876,11 +815,7 @@ const filterToDOMInterface = (( ) => {
} }
}; };
return { return { preview, queryAll };
get previewing() { return previewing; },
preview,
queryAll,
};
})(); })();
/******************************************************************************/ /******************************************************************************/
@ -1091,11 +1026,6 @@ const quitPicker = function() {
if ( pickerRoot === null ) { return; } if ( pickerRoot === null ) { return; }
// https://github.com/gorhill/uBlock/issues/2060
if ( vAPI.domFilterer instanceof Object ) {
vAPI.domFilterer.unexcludeNode(pickerRoot);
}
pickerRoot.remove(); pickerRoot.remove();
pickerRoot = null; pickerRoot = null;
@ -1118,16 +1048,13 @@ const onDialogMessage = function(msg) {
quitPicker(); quitPicker();
break; break;
case 'dialogSetFilter': { case 'dialogSetFilter': {
const resultset = filterToDOMInterface.queryAll(msg); const resultset = filterToDOMInterface.queryAll(msg) || [];
highlightElements(resultset.map(a => a.elem), true); highlightElements(resultset.map(a => a.elem), true);
if ( msg.filter === '!' ) { break; } if ( msg.filter === '!' ) { break; }
vAPI.MessagingConnection.sendTo(epickerConnectionId, { vAPI.MessagingConnection.sendTo(epickerConnectionId, {
what: 'filterResultset', what: 'resultsetDetails',
resultset: resultset.map(a => { count: resultset.length,
const o = Object.assign({}, a); opt: resultset.length !== 0 ? resultset[0].opt : undefined,
o.elem = undefined;
return o;
}),
}); });
break; break;
} }
@ -1250,7 +1177,7 @@ const pickerCSSStyle = [
].join(' !important;'); ].join(' !important;');
const pickerCSS = ` const pickerCSS = `
:root [${vAPI.sessionId}] { :root > [${vAPI.sessionId}] {
${pickerCSSStyle} ${pickerCSSStyle}
} }
:root [${vAPI.sessionId}-clickblind] { :root [${vAPI.sessionId}-clickblind] {
@ -1265,11 +1192,6 @@ pickerRoot = document.createElement('iframe');
pickerRoot.setAttribute(vAPI.sessionId, ''); pickerRoot.setAttribute(vAPI.sessionId, '');
document.documentElement.append(pickerRoot); document.documentElement.append(pickerRoot);
// https://github.com/gorhill/uBlock/issues/2060
if ( vAPI.domFilterer instanceof Object ) {
vAPI.domFilterer.excludeNode(pickerRoot);
}
vAPI.shutdown.add(quitPicker); vAPI.shutdown.add(quitPicker);
vAPI.MessagingConnection.addListener(onConnectionMessage); vAPI.MessagingConnection.addListener(onConnectionMessage);

View File

@ -1177,7 +1177,7 @@ Parser.prototype.SelectorCompiler = class {
]); ]);
this.reSimpleSelector = /^[#.][A-Za-z_][\w-]*$/; this.reSimpleSelector = /^[#.][A-Za-z_][\w-]*$/;
this.div = document.createElement('div'); this.div = document.createElement('div');
this.rePseudoClass = /:(?::?after|:?before|:-?[a-z][a-z-]*[a-z])$/; this.rePseudoElement = /:(?::?after|:?before|:-?[a-z][a-z-]*[a-z])$/;
this.reProceduralOperator = new RegExp([ this.reProceduralOperator = new RegExp([
'^(?:', '^(?:',
Array.from(parser.proceduralOperatorTokens.keys()).join('|'), Array.from(parser.proceduralOperatorTokens.keys()).join('|'),
@ -1296,7 +1296,7 @@ Parser.prototype.SelectorCompiler = class {
// is fixed. // is fixed.
cssSelectorType(s) { cssSelectorType(s) {
if ( this.reSimpleSelector.test(s) ) { return 1; } if ( this.reSimpleSelector.test(s) ) { return 1; }
const pos = this.cssPseudoSelector(s); const pos = this.cssPseudoElement(s);
if ( pos !== -1 ) { if ( pos !== -1 ) {
return this.cssSelectorType(s.slice(0, pos)) === 1 ? 3 : 0; return this.cssSelectorType(s.slice(0, pos)) === 1 ? 3 : 0;
} }
@ -1308,9 +1308,9 @@ Parser.prototype.SelectorCompiler = class {
return 1; return 1;
} }
cssPseudoSelector(s) { cssPseudoElement(s) {
if ( s.lastIndexOf(':') === -1 ) { return -1; } if ( s.lastIndexOf(':') === -1 ) { return -1; }
const match = this.rePseudoClass.exec(s); const match = this.rePseudoElement.exec(s);
return match !== null ? match.index : -1; return match !== null ? match.index : -1;
} }
@ -1450,13 +1450,10 @@ Parser.prototype.SelectorCompiler = class {
// The normalized string version is what is reported in the logger, // The normalized string version is what is reported in the logger,
// by design. // by design.
decompileProcedural(compiled) { decompileProcedural(compiled) {
const tasks = compiled.tasks; const tasks = compiled.tasks || [];
if ( Array.isArray(tasks) === false ) {
return compiled.selector;
}
const raw = [ compiled.selector ]; const raw = [ compiled.selector ];
let value;
for ( const task of tasks ) { for ( const task of tasks ) {
let value;
switch ( task[0] ) { switch ( task[0] ) {
case ':has': case ':has':
case ':if': case ':if':
@ -1494,8 +1491,6 @@ Parser.prototype.SelectorCompiler = class {
raw.push(task[1]); raw.push(task[1]);
break; break;
case ':min-text-length': case ':min-text-length':
case ':remove':
case ':style':
case ':upward': case ':upward':
case ':watch-attr': case ':watch-attr':
case ':xpath': case ':xpath':
@ -1503,6 +1498,10 @@ Parser.prototype.SelectorCompiler = class {
break; break;
} }
} }
if ( Array.isArray(compiled.action) ) {
const [ op, arg ] = compiled.action;
raw.push(`${op}(${arg})`);
}
return raw.join(''); return raw.join('');
} }
@ -1578,10 +1577,12 @@ Parser.prototype.SelectorCompiler = class {
tasks.push([ ':spath', spath ]); tasks.push([ ':spath', spath ]);
} }
if ( action !== undefined ) { return; } if ( action !== undefined ) { return; }
tasks.push([ operator, args ]); const task = [ operator, args ];
if ( this.actionOperators.has(operator) ) { if ( this.actionOperators.has(operator) ) {
if ( root === false ) { return; } if ( root === false ) { return; }
action = operator.slice(1); action = task;
} else {
tasks.push(task);
} }
opPrefixBeg = i; opPrefixBeg = i;
if ( i === n ) { break; } if ( i === n ) { break; }
@ -1589,7 +1590,7 @@ Parser.prototype.SelectorCompiler = class {
// No task found: then we have a CSS selector. // No task found: then we have a CSS selector.
// At least one task found: nothing should be left to parse. // At least one task found: nothing should be left to parse.
if ( tasks.length === 0 ) { if ( tasks.length === 0 && action === undefined ) {
prefix = raw; prefix = raw;
} else if ( opPrefixBeg < n ) { } else if ( opPrefixBeg < n ) {
if ( action !== undefined ) { return; } if ( action !== undefined ) { return; }
@ -1626,21 +1627,17 @@ Parser.prototype.SelectorCompiler = class {
} }
// Expose action to take in root descriptor. // Expose action to take in root descriptor.
//
// https://github.com/uBlockOrigin/uBlock-issues/issues/961
// https://github.com/uBlockOrigin/uBlock-issues/issues/382
// For the time being, `style` action can't be used in a
// procedural selector.
if ( action !== undefined ) { if ( action !== undefined ) {
if ( tasks.length > 1 && action === 'style' ) { return; }
out.action = action; out.action = action;
} }
// Pseudo-selectors are valid only when used in a root task list. // Pseudo elements are valid only when used in a root task list AND
// only when there are no procedural operators: pseudo elements can't
// be querySelectorAll-ed.
if ( prefix !== '' ) { if ( prefix !== '' ) {
const pos = this.cssPseudoSelector(prefix); const pos = this.cssPseudoElement(prefix);
if ( pos !== -1 ) { if ( pos !== -1 ) {
if ( root === false ) { return; } if ( root === false || tasks.length !== 0 ) { return; }
out.pseudo = pos; out.pseudo = pos;
} }
} }