diff options
Diffstat (limited to 'src/js/cosmetic-filtering.js')
-rw-r--r-- | src/js/cosmetic-filtering.js | 120 |
1 files changed, 106 insertions, 14 deletions
diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 9a5d23f..3ef45d4 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -230,20 +230,19 @@ FilterEntity.fromSelfie = function(s) { /******************************************************************************/ var FilterParser = function() { - this.prefix = ''; - this.suffix = ''; + this.prefix = this.suffix = this.style = ''; this.unhide = 0; this.hostnames = []; this.invalid = false; this.cosmetic = true; - this.reParser = /^\s*([^#]*)(##|#@#)(.+)\s*$/; + this.reParser = /^([^#]*?)(##|#@#)(.+)$/; + this.reScriptContains = /^script:contains\(.+?\)$/; }; /******************************************************************************/ FilterParser.prototype.reset = function() { - this.prefix = ''; - this.suffix = ''; + this.prefix = this.suffix = this.style = ''; this.unhide = 0; this.hostnames.length = 0; this.invalid = false; @@ -262,10 +261,20 @@ FilterParser.prototype.parse = function(s) { this.cosmetic = false; return this; } - - // Remember original string - this.prefix = matches[1]; - this.suffix = matches[3]; + this.prefix = matches[1].trim(); + this.unhide = matches[2].charAt(1) === '@' ? 1 : 0; + this.suffix = matches[3].trim(); + + // Cosmetic filters with explicit style properties can apply only: + // - to specific cosmetic filters (those which apply to a specific site) + // - to block cosmetic filters (not exception cosmetic filters) + if ( this.suffix.slice(-1) === '}' ) { + // Not supported for now: this code will ensure some backward + // compatibility for when cosmetic filters with explicit style + // properties start to be in use. + this.invalid = true; + return this; + } // 2014-05-23: // https://github.com/gorhill/httpswitchboard/issues/260 @@ -283,10 +292,31 @@ FilterParser.prototype.parse = function(s) { this.suffix = this.suffix.slice(1); } - this.unhide = matches[2].charAt(1) === '@' ? 1 : 0; if ( this.prefix !== '' ) { this.hostnames = this.prefix.split(/\s*,\s*/); } + + // Script tag filters: pre-process them so that can be used with minimal + // overhead in the content script. + // Examples: + // focus.de##script:contains(/uabInject/) + // focus.de##script:contains(uabInject) + if ( this.suffix.charAt(0) === 's' && this.reScriptContains.test(this.suffix) ) { + // Currently supported only as non-generic selector. Also, exception + // script tag filter makes no sense, ignore. + if ( this.hostnames.length === 0 || this.unhide === 1 ) { + this.invalid = true; + return this; + } + var suffix = this.suffix; + this.suffix = 'script//:'; + if ( suffix.charAt(16) !== '/' || suffix.slice(-2) !== '/)' ) { + this.suffix += suffix.slice(16, -1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } else { + this.suffix += suffix.slice(17, -2).replace(/\\/g, '\\'); + } + } + return this; }; @@ -554,6 +584,8 @@ FilterContainer.prototype.reset = function() { // hostname, entity-based filters this.hostnameFilters = {}; this.entityFilters = {}; + this.scriptTagFilters = {}; + this.scriptTagFilterCount = 0; }; /******************************************************************************/ @@ -576,11 +608,14 @@ FilterContainer.prototype.isValidSelector = (function() { try { // https://github.com/gorhill/uBlock/issues/693 div.matches(s + ',\n#foo'); + return true; } catch (e) { - console.error('uBlock> invalid cosmetic filter:', s); - return false; } - return true; + if ( s.lastIndexOf('script//:', 0) === 0 ) { + return true; + } + console.error('uBlock> invalid cosmetic filter:', s); + return false; }; })(); @@ -790,6 +825,11 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) { // h ir twitter.com .promoted-tweet if ( fields[0] === 'h' ) { + // Special filter: script tags. Not a real CSS selector. + if ( fields[3].lastIndexOf('script//:', 0) === 0 ) { + this.createScriptTagFilter(fields[2], fields[3].slice(9)); + continue; + } filter = new FilterHostname(fields[3], fields[2]); bucket = this.hostnameFilters[fields[1]]; if ( bucket === undefined ) { @@ -821,6 +861,11 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) { // entity selector if ( fields[0] === 'e' ) { + // Special filter: script tags. Not a real CSS selector. + if ( fields[2].lastIndexOf('script//:', 0) === 0 ) { + this.createScriptTagFilter(fields[1], fields[2].slice(9)); + continue; + } bucket = this.entityFilters[fields[1]]; if ( bucket === undefined ) { this.entityFilters[fields[1]] = [fields[2]]; @@ -886,6 +931,49 @@ FilterContainer.prototype.skipCompiledContent = function(text, lineBeg) { /******************************************************************************/ +FilterContainer.prototype.createScriptTagFilter = function(hostname, s) { + if ( this.scriptTagFilters.hasOwnProperty(hostname) ) { + this.scriptTagFilters[hostname] += '|' + s; + } else { + this.scriptTagFilters[hostname] = s; + } + this.scriptTagFilterCount += 1; +}; + +/******************************************************************************/ + +FilterContainer.prototype.retrieveScriptTagRegex = function(domain, hostname) { + if ( this.scriptTagFilterCount === 0 ) { + return; + } + var out = [], hn = hostname, pos; + for (;;) { + if ( this.scriptTagFilters.hasOwnProperty(hn) ) { + out.push(this.scriptTagFilters[hn]); + } + if ( hn === domain ) { + break; + } + pos = hn.indexOf('.'); + if ( pos === -1 ) { + break; + } + hn = hn.slice(pos + 1); + } + pos = domain.indexOf('.'); + if ( pos !== -1 ) { + hn = domain.slice(0, pos); + if ( this.scriptTagFilters.hasOwnProperty(hn) ) { + out.push(this.scriptTagFilters[hn]); + } + } + if ( out.length !== 0 ) { + return out.join('|'); + } +}; + +/******************************************************************************/ + FilterContainer.prototype.freeze = function() { this.duplicateBuster = {}; @@ -939,7 +1027,9 @@ FilterContainer.prototype.toSelfie = function() { highMediumGenericHideCount: this.highMediumGenericHideCount, highHighGenericHide: this.highHighGenericHide, highHighGenericHideCount: this.highHighGenericHideCount, - genericDonthide: this.genericDonthide + genericDonthide: this.genericDonthide, + scriptTagFilters: this.scriptTagFilters, + scriptTagFilterCount: this.scriptTagFilterCount }; }; @@ -1000,6 +1090,8 @@ FilterContainer.prototype.fromSelfie = function(selfie) { this.highHighGenericHide = selfie.highHighGenericHide; this.highHighGenericHideCount = selfie.highHighGenericHideCount; this.genericDonthide = selfie.genericDonthide; + this.scriptTagFilters = selfie.scriptTagFilters; + this.scriptTagFilterCount = selfie.scriptTagFilterCount; this.frozen = true; }; |