Improve detection of invalid CSS selectors

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

Additionally, fix case of using potentially uninitialized variable
in preview mode. Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/425
This commit is contained in:
Raymond Hill 2019-02-18 16:00:42 -05:00
parent 426a6ea9a7
commit 93842a3f9c
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
1 changed files with 56 additions and 68 deletions

View File

@ -707,17 +707,17 @@ var filtersFrom = function(x, y) {
const filterToDOMInterface = (function() { const filterToDOMInterface = (function() {
// Net filters: we need to lookup manually -- translating into a foolproof // Net filters: we need to lookup manually -- translating into a foolproof
// CSS selector is just not possible. // CSS selector is just not possible.
var fromNetworkFilter = function(filter) { const fromNetworkFilter = function(filter) {
var out = []; const out = [];
// https://github.com/chrisaljoudi/uBlock/issues/945 // https://github.com/chrisaljoudi/uBlock/issues/945
// Transform into a regular expression, this allows the user to edit and // Transform into a regular expression, this allows the user to edit and
// insert wildcard(s) into the proposed filter. // insert wildcard(s) into the proposed filter.
var reStr = ''; let reStr = '';
if ( filter.length > 1 && filter.charAt(0) === '/' && filter.slice(-1) === '/' ) { if ( filter.length > 1 && filter.charAt(0) === '/' && filter.slice(-1) === '/' ) {
reStr = filter.slice(1, -1); reStr = filter.slice(1, -1);
} }
else { else {
var rePrefix = '', reSuffix = ''; let rePrefix = '', reSuffix = '';
if ( filter.slice(0, 2) === '||' ) { if ( filter.slice(0, 2) === '||' ) {
filter = filter.replace('||', ''); filter = filter.replace('||', '');
} else { } else {
@ -734,7 +734,7 @@ const filterToDOMInterface = (function() {
filter.replace(/[.+?${}()|[\]\\]/g, '\\$&').replace(/[\*^]+/g, '.*') + filter.replace(/[.+?${}()|[\]\\]/g, '\\$&').replace(/[\*^]+/g, '.*') +
reSuffix; reSuffix;
} }
var reFilter = null; let reFilter = null;
try { try {
reFilter = new RegExp(reStr); reFilter = new RegExp(reStr);
} }
@ -743,18 +743,14 @@ const filterToDOMInterface = (function() {
} }
// Lookup by tag names. // Lookup by tag names.
var src1stProps = netFilter1stSources; let elems = document.querySelectorAll(
var src2ndProps = netFilter2ndSources; Object.keys(netFilter1stSources).join()
var srcProp, src; );
var elems = document.querySelectorAll(Object.keys(src1stProps).join()), for ( const elem of elems ) {
iElem = elems.length, let srcProp = netFilter1stSources[elem.localName];
elem; let src = elem[srcProp];
while ( iElem-- ) {
elem = elems[iElem];
srcProp = src1stProps[elem.localName];
src = elem[srcProp];
if ( typeof src !== 'string' || src.length === 0 ) { if ( typeof src !== 'string' || src.length === 0 ) {
srcProp = src2ndProps[elem.localName]; srcProp = netFilter2ndSources[elem.localName];
src = elem[srcProp]; src = elem[srcProp];
} }
if ( src && reFilter.test(src) ) { if ( src && reFilter.test(src) ) {
@ -768,10 +764,7 @@ const filterToDOMInterface = (function() {
} }
// Find matching background image in current set of candidate elements. // Find matching background image in current set of candidate elements.
elems = candidateElements; for ( const elem of candidateElements ) {
iElem = elems.length;
while ( iElem-- ) {
elem = elems[iElem];
if ( reFilter.test(backgroundImageURLFromElement(elem)) ) { if ( reFilter.test(backgroundImageURLFromElement(elem)) ) {
out.push({ out.push({
type: 'network', type: 'network',
@ -790,9 +783,14 @@ const filterToDOMInterface = (function() {
// ways to compose a valid href to the same effective URL. One idea is to // ways to compose a valid href to the same effective URL. One idea is to
// normalize all a[href] on the page, but for now I will wait and see, as I // normalize all a[href] on the page, but for now I will wait and see, as I
// prefer to refrain from tampering with the page content if I can avoid it. // prefer to refrain from tampering with the page content if I can avoid it.
var fromPlainCosmeticFilter = function(filter) { //
// https://github.com/uBlockOrigin/uBlock-issues/issues/389
// Test filter using comma-separated list to better detect invalid CSS
// selectors.
const fromPlainCosmeticFilter = function(filter) {
let elems; let elems;
try { try {
document.documentElement.matches(`${filter},\na`);
elems = document.querySelectorAll(filter); elems = document.querySelectorAll(filter);
} }
catch (e) { catch (e) {
@ -808,15 +806,15 @@ const filterToDOMInterface = (function() {
// https://github.com/gorhill/uBlock/issues/1772 // https://github.com/gorhill/uBlock/issues/1772
// Handle procedural cosmetic filters. // Handle procedural cosmetic filters.
var fromCompiledCosmeticFilter = function(raw) { const fromCompiledCosmeticFilter = function(raw) {
if ( typeof raw !== 'string' ) { return; } if ( typeof raw !== 'string' ) { return; }
var o; let o;
try { try {
o = JSON.parse(raw); o = JSON.parse(raw);
} catch(ex) { } catch(ex) {
return; return;
} }
var elems; let elems;
if ( o.style ) { if ( o.style ) {
elems = document.querySelectorAll(o.style[0]); elems = document.querySelectorAll(o.style[0]);
lastAction = o.style[0] + ' {' + o.style[1] + '}'; lastAction = o.style[0] + ' {' + o.style[1] + '}';
@ -824,21 +822,21 @@ const filterToDOMInterface = (function() {
elems = vAPI.domFilterer.createProceduralFilter(o).exec(); elems = vAPI.domFilterer.createProceduralFilter(o).exec();
} }
if ( !elems ) { return; } if ( !elems ) { return; }
var out = []; const out = [];
for ( var i = 0, n = elems.length; i < n; i++ ) { for ( const elem of elems ) {
out.push({ type: 'cosmetic', elem: elems[i] }); out.push({ type: 'cosmetic', elem });
} }
return out; return out;
}; };
var lastFilter, let lastFilter,
lastResultset, lastResultset,
lastAction, lastAction,
appliedStyleTag, appliedStyleTag,
applied = false, applied = false,
previewing = false; previewing = false;
var queryAll = function(filter, callback) { const queryAll = function(filter, callback) {
filter = filter.trim(); filter = filter.trim();
if ( filter === lastFilter ) { if ( filter === lastFilter ) {
callback(lastResultset); callback(lastResultset);
@ -859,7 +857,7 @@ const filterToDOMInterface = (function() {
callback(lastResultset); callback(lastResultset);
return; return;
} }
var selector = filter.slice(2); const selector = filter.slice(2);
lastResultset = fromPlainCosmeticFilter(selector); lastResultset = fromPlainCosmeticFilter(selector);
if ( lastResultset ) { if ( lastResultset ) {
if ( previewing ) { apply(); } if ( previewing ) { apply(); }
@ -878,18 +876,13 @@ const filterToDOMInterface = (function() {
); );
}; };
var applyHide = function() { const applyHide = function() {
var htmlElem = document.documentElement, const htmlElem = document.documentElement;
items = lastResultset, for ( const item of lastResultset ) {
item, elem, style; const elem = item.elem;
for ( var i = 0, n = items.length; i < n; i++ ) {
item = items[i];
elem = item.elem;
// https://github.com/gorhill/uBlock/issues/1629 // https://github.com/gorhill/uBlock/issues/1629
if ( elem === pickerRoot ) { if ( elem === pickerRoot ) { continue; }
continue; const style = elem.style;
}
style = elem.style;
if ( if (
(elem !== htmlElem) && (elem !== htmlElem) &&
(item.type === 'cosmetic' || item.type === 'network' && item.src !== undefined) (item.type === 'cosmetic' || item.type === 'network' && item.src !== undefined)
@ -906,10 +899,9 @@ const filterToDOMInterface = (function() {
} }
}; };
var unapplyHide = function() { const unapplyHide = function() {
var items = lastResultset, item; if ( lastResultset === undefined ) { return; }
for ( var i = 0, n = items.length; i < n; i++ ) { for ( const item of lastResultset ) {
item = items[i];
if ( item.hasOwnProperty('display') ) { if ( item.hasOwnProperty('display') ) {
item.elem.style.setProperty( item.elem.style.setProperty(
'display', 'display',
@ -929,14 +921,14 @@ const filterToDOMInterface = (function() {
} }
}; };
var unapplyStyle = function() { const unapplyStyle = function() {
if ( !appliedStyleTag || appliedStyleTag.parentNode === null ) { if ( !appliedStyleTag || appliedStyleTag.parentNode === null ) {
return; return;
} }
appliedStyleTag.parentNode.removeChild(appliedStyleTag); appliedStyleTag.parentNode.removeChild(appliedStyleTag);
}; };
var applyStyle = function() { const applyStyle = function() {
if ( !appliedStyleTag ) { if ( !appliedStyleTag ) {
appliedStyleTag = document.createElement('style'); appliedStyleTag = document.createElement('style');
appliedStyleTag.setAttribute('type', 'text/css'); appliedStyleTag.setAttribute('type', 'text/css');
@ -947,13 +939,11 @@ const filterToDOMInterface = (function() {
} }
}; };
var apply = function() { const apply = function() {
if ( applied ) { if ( applied ) {
unapply(); unapply();
} }
if ( lastResultset === undefined ) { if ( lastResultset === undefined ) { return; }
return;
}
if ( typeof lastAction === 'string' ) { if ( typeof lastAction === 'string' ) {
applyStyle(); applyStyle();
} else { } else {
@ -962,10 +952,8 @@ const filterToDOMInterface = (function() {
applied = true; applied = true;
}; };
var unapply = function() { const unapply = function() {
if ( !applied ) { if ( !applied ) { return; }
return;
}
if ( typeof lastAction === 'string' ) { if ( typeof lastAction === 'string' ) {
unapplyStyle(); unapplyStyle();
} else { } else {
@ -974,13 +962,12 @@ const filterToDOMInterface = (function() {
applied = false; applied = false;
}; };
var preview = function(filter) { const preview = function(filter) {
previewing = filter !== false; previewing = filter !== false;
if ( previewing ) { if ( previewing ) {
queryAll(filter, function(items) { queryAll(filter, items => {
if ( items !== undefined ) { if ( items === undefined ) { return; }
apply(); apply();
}
}); });
} else { } else {
unapply(); unapply();
@ -998,7 +985,7 @@ const filterToDOMInterface = (function() {
/******************************************************************************/ /******************************************************************************/
const userFilterFromCandidate = function(callback) { const userFilterFromCandidate = function(callback) {
var v = rawFilterFromTextarea(); let v = rawFilterFromTextarea();
filterToDOMInterface.set(v, function(items) { filterToDOMInterface.set(v, function(items) {
if ( !items || items.length === 0 ) { if ( !items || items.length === 0 ) {
callback(); callback();
@ -1007,7 +994,7 @@ const userFilterFromCandidate = function(callback) {
// https://github.com/gorhill/uBlock/issues/738 // https://github.com/gorhill/uBlock/issues/738
// Trim dots. // Trim dots.
var hostname = window.location.hostname; let hostname = window.location.hostname;
if ( hostname.slice(-1) === '.' ) { if ( hostname.slice(-1) === '.' ) {
hostname = hostname.slice(0, -1); hostname = hostname.slice(0, -1);
} }
@ -1019,14 +1006,14 @@ const userFilterFromCandidate = function(callback) {
} }
// Assume net filter // Assume net filter
var opts = []; const opts = [];
// If no domain included in filter, we need domain option // If no domain included in filter, we need domain option
if ( v.lastIndexOf('||', 0) === -1 ) { if ( v.lastIndexOf('||', 0) === -1 ) {
opts.push('domain=' + hostname); opts.push('domain=' + hostname);
} }
var item = items[0]; const item = items[0];
if ( item.opts ) { if ( item.opts ) {
opts.push(item.opts); opts.push(item.opts);
} }
@ -1042,11 +1029,12 @@ const userFilterFromCandidate = function(callback) {
/******************************************************************************/ /******************************************************************************/
const onCandidateChanged = (function() { const onCandidateChanged = (function() {
var process = function(items) { const process = function(items) {
var elems = [], valid = items !== undefined; const elems = [];
const valid = items !== undefined;
if ( valid ) { if ( valid ) {
for ( var i = 0; i < items.length; i++ ) { for ( const item of items ) {
elems.push(items[i].elem); elems.push(item.elem);
} }
} }
pickerBody.querySelector('#resultsetCount').textContent = valid ? pickerBody.querySelector('#resultsetCount').textContent = valid ?