| Index: lib/filterClasses.js |
| =================================================================== |
| --- a/lib/filterClasses.js |
| +++ b/lib/filterClasses.js |
| @@ -20,6 +20,7 @@ |
| */ |
| let {FilterNotifier} = require("filterNotifier"); |
| +let {Utils} = require("utils"); |
| /** |
| * Abstract base class for filters |
| @@ -85,6 +86,12 @@ |
| * @type RegExp |
| */ |
| Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/; |
| +/** |
| + * Regular expression that CSS property filters should match |
| + * Properties must not contain " or ' |
| + * @type RegExp |
| + */ |
| +Filter.csspropertyRegExp = /\[\-abp\-properties=(["'])([^"']+)\1\]/; |
| /** |
| * Creates a filter of correct type from its text representation - does the basic parsing and |
| @@ -160,6 +167,27 @@ |
| }; |
| /** |
| + * Converts filter text into regular expression string |
| + * @param {String} text as in Filter() |
| + * @return {String} regular expression representation of filter text |
| + */ |
| +Filter.toRegExp = function(text) |
| +{ |
| + return text |
| + .replace(/\*+/g, "*") // remove multiple wildcards |
| + .replace(/\^\|$/, "^") // remove anchors following separator placeholder |
| + .replace(/\W/g, "\\$&") // escape special symbols |
| + .replace(/\\\*/g, ".*") // replace wildcards by .* |
| + // process separator placeholders (all ANSI characters but alphanumeric characters and _%.-) |
| + .replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x7F]|$)") |
| + .replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?") // process extended anchor at expression start |
| + .replace(/^\\\|/, "^") // process anchor at expression start |
| + .replace(/\\\|$/, "$") // process anchor at expression end |
| + .replace(/^(\.\*)/, "") // remove leading wildcards |
| + .replace(/(\.\*)$/, ""); // remove trailing wildcards |
| +} |
| + |
| +/** |
| * Class for invalid filters |
| * @param {String} text see Filter() |
| * @param {String} reason Reason why this filter is invalid |
| @@ -543,20 +571,7 @@ |
| if (prop) |
| return prop.value; |
| - // Remove multiple wildcards |
| - let source = this.regexpSource |
| - .replace(/\*+/g, "*") // remove multiple wildcards |
| - .replace(/\^\|$/, "^") // remove anchors following separator placeholder |
| - .replace(/\W/g, "\\$&") // escape special symbols |
| - .replace(/\\\*/g, ".*") // replace wildcards by .* |
| - // process separator placeholders (all ANSI characters but alphanumeric characters and _%.-) |
| - .replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x7F]|$)") |
| - .replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?") // process extended anchor at expression start |
| - .replace(/^\\\|/, "^") // process anchor at expression start |
| - .replace(/\\\|$/, "$") // process anchor at expression end |
| - .replace(/^(\.\*)/, "") // remove leading wildcards |
| - .replace(/(\.\*)$/, ""); // remove trailing wildcards |
| - |
| + let source = Filter.toRegExp(this.regexpSource); |
| let regexp = new RegExp(source, this.matchCase ? "" : "i"); |
| Object.defineProperty(this, "regexp", {value: regexp}); |
| return regexp; |
| @@ -861,7 +876,7 @@ |
| * @param {String} tagName tag name part (can be empty) |
| * @param {String} attrRules attribute matching rules (can be empty) |
| * @param {String} selector raw CSS selector (can be empty) |
| - * @return {ElemHideFilter|ElemHideException|InvalidFilter} |
| + * @return {ElemHideFilter|ElemHideException|CSSPropertyFilter|InvalidFilter} |
| */ |
| ElemHideBase.fromText = function(text, domain, isException, tagName, attrRules, selector) |
| { |
| @@ -872,23 +887,24 @@ |
| let id = null; |
| let additional = ""; |
| - if (attrRules) { |
| + if (attrRules) |
| + { |
| attrRules = attrRules.match(/\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\)/g); |
| - for (let rule of attrRules) { |
| + for (let rule of attrRules) |
| + { |
| rule = rule.substr(1, rule.length - 2); |
| let separatorPos = rule.indexOf("="); |
| - if (separatorPos > 0) { |
| + if (separatorPos > 0) |
| + { |
| rule = rule.replace(/=/, '="') + '"'; |
| additional += "[" + rule + "]"; |
| } |
| - else { |
| + else |
| + { |
| if (id) |
| - { |
| - let {Utils} = require("utils"); |
| return new InvalidFilter(text, Utils.getString("filter_elemhide_duplicate_id")); |
| - } |
| - else |
| - id = rule; |
| + |
| + id = rule; |
| } |
| } |
| } |
| @@ -898,15 +914,23 @@ |
| else if (tagName || additional) |
| selector = tagName + additional; |
| else |
| - { |
| - let {Utils} = require("utils"); |
| return new InvalidFilter(text, Utils.getString("filter_elemhide_nocriteria")); |
| - } |
| } |
| + |
| if (isException) |
| return new ElemHideException(text, domain, selector); |
| - else |
| - return new ElemHideFilter(text, domain, selector); |
| + |
| + if (Filter.csspropertyRegExp.test(selector)) |
|
Wladimir Palant
2015/06/05 22:08:59
How about actually storing the result of this sele
Thomas Greiner
2015/06/08 14:33:43
Done. I don't really mind either approach.
Note t
|
| + { |
| + // CSS property filters are inefficient so we need to make sure that |
| + // they're only applied if they specify active domains |
| + if (!/,[^~][^,.]*\.[^,]/.test("," + domain)) |
| + return new InvalidFilter(text, Utils.getString("filter_cssproperty_nodomain")); |
| + |
| + return new CSSPropertyFilter(text, domain, selector); |
| + } |
| + |
| + return new ElemHideFilter(text, domain, selector); |
| }; |
| /** |
| @@ -946,3 +970,63 @@ |
| { |
| __proto__: ElemHideBase.prototype |
| }; |
| + |
| +/** |
| + * Class for CSS property filters |
| + * @param {String} text see Filter() |
| + * @param {String} domains see ElemHideBase() |
| + * @param {String} selector see ElemHideBase() |
| + * @constructor |
| + * @augments ElemHideBase |
| + */ |
| +function CSSPropertyFilter(text, domains, selector) |
| +{ |
| + ElemHideBase.call(this, text, domains, selector); |
| + |
| + let properties; |
| + [properties, , this.regexpSource] = selector.match(Filter.csspropertyRegExp); |
| + [this.selectorPrefix, this.selectorSuffix] = selector.split(properties); |
| +} |
| +exports.CSSPropertyFilter = CSSPropertyFilter; |
| + |
| +CSSPropertyFilter.prototype = |
| +{ |
| + __proto__: ElemHideBase.prototype, |
| + |
| + /** |
| + * Expression from which a regular expression should be generated for matching |
| + * CSS properties - for delayed creation of the regexpString property |
| + * @type String |
| + */ |
| + regexpSource: null, |
| + /** |
| + * Substring of CSS selector before properties for the HTML elements that |
| + * should be hidden |
| + * @type String |
| + */ |
| + selectorPrefix: null, |
| + /** |
| + * Substring of CSS selector after properties for the HTML elements that |
| + * should be hidden |
| + * @type String |
| + */ |
| + selectorSuffix: null, |
| + |
| + /** |
| + * Raw regular expression string to be used when testing CSS properties |
| + * against this filter |
| + * @type String |
| + */ |
| + get regexpString() |
| + { |
| + // Despite this property being cached, the getter is called |
| + // several times on Safari, due to WebKit bug 132872 |
| + let prop = Object.getOwnPropertyDescriptor(this, "regexpString"); |
| + if (prop) |
| + return prop.value; |
| + |
| + let regexp = Filter.toRegExp(this.regexpSource); |
| + Object.defineProperty(this, "regexpString", {value: regexp}); |
| + return regexp; |
| + } |
| +}; |