From 3752ac48807fd51eb567eea535a1923c1e75fa23 Mon Sep 17 00:00:00 2001 From: gorhill Date: Wed, 29 Jun 2016 17:07:33 -0400 Subject: [PATCH] fix #781: support for explicit style properties --- src/js/contentscript.js | 71 ++++++++++++++++++++------------- src/js/cosmetic-filtering.js | 77 +++++++++++++++++++++++++----------- 2 files changed, 96 insertions(+), 52 deletions(-) diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 97cedddd5..211ed881a 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -66,6 +66,7 @@ vAPI.domFilterer = { hiddenId: String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 982451653 + 982451653).toString(36), hiddenNodeCount: 0, matchesProp: vAPI.matchesProp, + newCSSRules: [], newDeclarativeSelectors: [], shadowId: String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 982451653 + 982451653).toString(36), styleTags: [], @@ -94,32 +95,13 @@ vAPI.domFilterer = { } }, - addHasSelector: function(s1, s2) { - var entry = { a: s1, b: s2.slice(5, -1) }; - if ( s1.indexOf(' ') === -1 ) { - this.simpleHasSelectors.push(entry); - } else { - this.complexHasSelectors.push(entry); - this.complexHasSelectorsCost = 0; - } - }, - addSelector: function(s) { if ( this.allSelectors[s] || this.allExceptions[s] ) { return; } this.allSelectors[s] = true; - var pos = s.indexOf(':'); - if ( pos !== -1 ) { - pos = s.indexOf(':has('); - if ( pos !== -1 ) { - this.addHasSelector(s.slice(0, pos), s.slice(pos)); - return; - } - if ( s.lastIndexOf(':xpath(', 0) === 0 ) { - this.addXpathSelector('', s); - return; - } + if ( s.indexOf(':') !== -1 && this.addSelectorEx(s) ) { + return; } if ( s.indexOf(' ') === -1 ) { this.simpleSelectors.push(s); @@ -132,18 +114,41 @@ vAPI.domFilterer = { this.newDeclarativeSelectors.push(s); }, + addSelectorEx: function(s) { + var pos = s.indexOf(':has('); + if ( pos !== -1 ) { + var entry = { + a: s.slice(0, pos), + b: s.slice(pos + 5, -1) + }; + if ( entry.a.indexOf(' ') === -1 ) { + this.simpleHasSelectors.push(entry); + } else { + this.complexHasSelectors.push(entry); + this.complexHasSelectorsCost = 0; + } + return true; + } + pos = s.indexOf(':style('); + if ( pos !== -1 ) { + this.newCSSRules.push(s.slice(0, pos) + ' {' + s.slice(pos + 7, -1) + '}'); + return true; + } + if ( s.lastIndexOf(':xpath(', 0) === 0 ) { + this.xpathExpression = null; + this.xpathSelectorsCost = 0; + this.addXpathSelector('', s.slice(7, -1)); + return true; + } + return false; + }, + addSelectors: function(aa) { for ( var i = 0, n = aa.length; i < n; i++ ) { this.addSelector(aa[i]); } }, - addXpathSelector: function(s1, s2) { - this.xpathSelectors.push(s2.slice(7, -1)); - this.xpathExpression = null; - this.xpathSelectorsCost = 0; - }, - checkStyleTags: function(commitIfNeeded) { var doc = document, html = doc.documentElement, @@ -188,8 +193,9 @@ vAPI.domFilterer = { } // Inject new declarative selectors. + var styleTag; if ( this.newDeclarativeSelectors.length ) { - var styleTag = document.createElement('style'); + styleTag = document.createElement('style'); styleTag.setAttribute('type', 'text/css'); styleTag.textContent = ':root ' + @@ -199,6 +205,15 @@ vAPI.domFilterer = { this.styleTags.push(styleTag); this.newDeclarativeSelectors.length = 0; } + // Inject new CSS rules. + if ( this.newCSSRules.length ) { + styleTag = document.createElement('style'); + styleTag.setAttribute('type', 'text/css'); + styleTag.textContent = ':root ' + this.newCSSRules.join(',\n:root '); + document.head.appendChild(styleTag); + this.styleTags.push(styleTag); + this.newCSSRules.length = 0; + } // Simple `:has()` selectors. if ( this.simpleHasSelectors.length ) { diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 0cd905d62..2b974249e 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -297,17 +297,29 @@ FilterParser.prototype.parse = function(raw) { // https://github.com/gorhill/uBlock/issues/952 // Find out whether we are dealing with an Adguard-specific cosmetic - // filter, and if so, discard the filter. + // filter, and if so, translate it if supported, or discard it if not + // supported. var cCode = raw.charCodeAt(rpos - 1); if ( cCode !== 0x23 /* '#' */ && cCode !== 0x40 /* '@' */ ) { // We have an Adguard cosmetic filter if and only if the character is // `$` or `%`, otherwise it's not a cosmetic filter. - if ( cCode === 0x24 /* '$' */ || cCode === 0x25 /* '%' */ ) { - this.invalid = true; - } else { + // Not a cosmetic filter. + if ( cCode !== 0x24 /* '$' */ && cCode !== 0x25 /* '%' */ ) { this.cosmetic = false; + return this; } - return this; + // Not supported. + if ( cCode !== 0x24 /* '$' */ ) { + this.invalid = true; + return this; + } + // CSS injection rule: supported, but translate into uBO's own format. + raw = this.translateAdguardCSSInjectionFilter(raw); + if ( raw === '' ) { + this.invalid = true; + return this; + } + rpos = raw.indexOf('#', lpos + 1); } // Extract the hostname(s). @@ -322,17 +334,6 @@ FilterParser.prototype.parse = function(raw) { return this; } - // 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.endsWith('}') ) { - // 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 // Any sequence of `#` longer than one means the line is not a valid @@ -380,6 +381,21 @@ FilterParser.prototype.parse = function(raw) { /******************************************************************************/ +// Reference: https://adguard.com/en/filterrules.html#cssInjection + +FilterParser.prototype.translateAdguardCSSInjectionFilter = function(raw) { + var matches = /^([^#]*)#(@?)\$#([^{]+)\{([^}]+)\}$/.exec(raw); + if ( matches === null ) { + return ''; + } + return matches[1] + + '#' + matches[2] + '#' + + matches[3].trim() + + ':style(' + matches[4].trim() + ')'; +}; + +/******************************************************************************/ + FilterParser.prototype.parseScriptTagFilter = function(matches) { // Currently supported only as non-generic selector. Also, exception // script tag filter makes no sense, ignore. @@ -699,6 +715,10 @@ FilterContainer.prototype.reset = function() { // https://github.com/chrisaljoudi/uBlock/issues/1004 // Detect and report invalid CSS selectors. +// Discard new ABP's `-abp-properties` directive until it is +// implemented (if ever). Unlikely, see: +// https://github.com/gorhill/uBlock/issues/1752 + FilterContainer.prototype.isValidSelector = (function() { var div = document.createElement('div'); var matchesProp = (function() { @@ -722,18 +742,22 @@ FilterContainer.prototype.isValidSelector = (function() { } var reHasSelector = /^(.+?):has\((.+?)\)$/; + var reStyleSelector = /^(.+?):style\((.+?)\)$/; var reXpathSelector = /^:xpath\((.+?)\)$/; - return function(s) { + // Keep in mind: https://github.com/gorhill/uBlock/issues/693 + var isValidCSSSelector = function(s) { try { - // https://github.com/gorhill/uBlock/issues/693 div[matchesProp](s + ',\n#foo'); - // Discard new ABP's `-abp-properties` directive until it is - // implemented (if ever). - if ( s.indexOf('[-abp-properties=') === -1 ) { - return true; - } - } catch (e) { + } catch (ex) { + return false; + } + return true; + }; + + return function(s) { + if ( isValidCSSSelector(s) && s.indexOf('[-abp-properties=') === -1 ) { + return true; } // We reach this point very rarely. var matches; @@ -753,6 +777,11 @@ FilterContainer.prototype.isValidSelector = (function() { } return false; } + // `:style` selector? + matches = reStyleSelector.exec(s); + if ( matches !== null ) { + return isValidCSSSelector(matches[1]); + } // Special `script:` filter? if ( s.startsWith('script') ) { if ( s.startsWith('?', 6) || s.startsWith('+', 6) ) {