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; |
@@ -852,7 +867,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) |
{ |
@@ -863,23 +878,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; |
} |
} |
} |
@@ -889,15 +905,26 @@ |
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); |
+ |
+ let match = Filter.csspropertyRegExp.exec(selector); |
+ if (match) |
+ { |
+ // 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, match[2], |
+ selector.substr(0, match.index), |
+ selector.substr(match.index + match[0].length)); |
+ } |
+ |
+ return new ElemHideFilter(text, domain, selector); |
}; |
/** |
@@ -937,3 +964,67 @@ |
{ |
__proto__: ElemHideBase.prototype |
}; |
+ |
+/** |
+ * Class for CSS property filters |
+ * @param {String} text see Filter() |
+ * @param {String} domains see ElemHideBase() |
+ * @param {String} selector see ElemHideBase() |
+ * @param {String} regexpSource see CSSPropertyFilter.regexpSource |
+ * @param {String} selectorPrefix see CSSPropertyFilter.selectorPrefix |
+ * @param {String} selectorSuffix see CSSPropertyFilter.selectorSuffix |
+ * @constructor |
+ * @augments ElemHideBase |
+ */ |
+function CSSPropertyFilter(text, domains, selector, regexpSource, |
+ selectorPrefix, selectorSuffix) |
+{ |
+ ElemHideBase.call(this, text, domains, selector); |
+ |
+ this.regexpSource = regexpSource; |
+ this.selectorPrefix = selectorPrefix; |
+ this.selectorSuffix = selectorSuffix; |
+} |
+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; |
+ } |
+}; |