| Index: lib/filterClasses.js | 
| =================================================================== | 
| --- a/lib/filterClasses.js | 
| +++ b/lib/filterClasses.js | 
| @@ -79,20 +79,20 @@ | 
| /** | 
| * Cache for known filters, maps string representation to filter objects. | 
| * @type {Map.<string,Filter>} | 
| */ | 
| Filter.knownFilters = new Map(); | 
| /** | 
| - * Regular expression that element hiding filters should match | 
| + * Regular expression that content filters should match | 
| * @type {RegExp} | 
| */ | 
| -Filter.elemhideRegExp = /^([^/*|@"!]*?)#([@?])?#(.+)$/; | 
| +Filter.contentRegExp = /^([^/*|@"!]*?)#([@?$])?#(.+)$/; | 
| /** | 
| * Regular expression that RegExp filters specified as RegExps should match | 
| * @type {RegExp} | 
| */ | 
| Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w-]+(?:=[^,\s]+)?(?:,~?[\w-]+(?:=[^,\s]+)?)*)?$/; | 
| /** | 
| * Regular expression that options on a RegExp filter should match | 
| * @type {RegExp} | 
| @@ -112,34 +112,32 @@ | 
| * @return {Filter} | 
| */ | 
| Filter.fromText = function(text) | 
| { | 
| let filter = Filter.knownFilters.get(text); | 
| if (filter) | 
| return filter; | 
| - let match = (text.includes("#") ? Filter.elemhideRegExp.exec(text) : null); | 
| + let match = text.includes("#") ? Filter.contentRegExp.exec(text) : null; | 
| if (match) | 
| { | 
| let propsMatch; | 
| if (!match[2] && | 
| (propsMatch = /\[-abp-properties=(["'])([^"']+)\1\]/.exec(match[3]))) | 
| { | 
| // This is legacy CSS properties syntax, convert to current syntax | 
| let prefix = match[3].substr(0, propsMatch.index); | 
| let expression = propsMatch[2]; | 
| let suffix = match[3].substr(propsMatch.index + propsMatch[0].length); | 
| return Filter.fromText(`${match[1]}#?#` + | 
| `${prefix}:-abp-properties(${expression})${suffix}`); | 
| } | 
| - filter = ElemHideBase.fromText( | 
| - text, match[1], match[2], match[3] | 
| - ); | 
| + filter = ContentFilter.fromText(text, match[1], match[2], match[3]); | 
| } | 
| else if (text[0] == "!") | 
| filter = new CommentFilter(text); | 
| else | 
| filter = RegExpFilter.fromText(text); | 
| Filter.knownFilters.set(filter.text, filter); | 
| return filter; | 
| @@ -179,22 +177,22 @@ | 
| // Remove line breaks, tabs etc | 
| text = text.replace(/[^\S ]+/g, ""); | 
| // Don't remove spaces inside comments | 
| if (/^ *!/.test(text)) | 
| return text.trim(); | 
| - // Special treatment for element hiding filters, right side is allowed to | 
| - // contain spaces | 
| - if (Filter.elemhideRegExp.test(text)) | 
| + // Special treatment for content filters, right side is allowed to contain | 
| + // spaces | 
| + if (Filter.contentRegExp.test(text)) | 
| { | 
| - let [, domains, separator, selector] = /^(.*?)(#[@?]?#?)(.*)$/.exec(text); | 
| - return domains.replace(/ +/g, "") + separator + selector.trim(); | 
| + let [, domains, separator, script] = /^(.*?)(#[@?$]?#?)(.*)$/.exec(text); | 
| + return domains.replace(/ +/g, "") + separator + script.trim(); | 
| } | 
| // For most regexp filters we strip all spaces, but $csp filter options | 
| // are allowed to contain single (non trailing) spaces. | 
| let strippedText = text.replace(/ +/g, ""); | 
| if (!strippedText.includes("$") || !/\bcsp=/i.test(strippedText)) | 
| return strippedText; | 
| @@ -990,83 +988,114 @@ | 
| } | 
| exports.WhitelistFilter = WhitelistFilter; | 
| WhitelistFilter.prototype = extend(RegExpFilter, { | 
| type: "whitelist" | 
| }); | 
| /** | 
| - * Base class for element hiding filters | 
| + * Base class for content filters | 
| * @param {string} text see {@link Filter Filter()} | 
| * @param {string} [domains] Host names or domains the filter should be | 
| * restricted to | 
| - * @param {string} selector CSS selector for the HTML elements that should be | 
| - * hidden | 
| + * @param {string} script Script that should be executed | 
| * @constructor | 
| * @augments ActiveFilter | 
| */ | 
| -function ElemHideBase(text, domains, selector) | 
| +function ContentFilter(text, domains, script) | 
| { | 
| ActiveFilter.call(this, text, domains || null); | 
| - // Braces are being escaped to prevent CSS rule injection. | 
| - this.selector = selector.replace("{", "\\7B ").replace("}", "\\7D "); | 
| + this.script = script; | 
| } | 
| -exports.ElemHideBase = ElemHideBase; | 
| +exports.ContentFilter = ContentFilter; | 
| -ElemHideBase.prototype = extend(ActiveFilter, { | 
| +ContentFilter.prototype = extend(ActiveFilter, { | 
| /** | 
| * @see ActiveFilter.domainSeparator | 
| */ | 
| domainSeparator: ",", | 
| /** | 
| - * CSS selector for the HTML elements that should be hidden | 
| + * Script that should be executed | 
| * @type {string} | 
| */ | 
| - selector: null | 
| + script: null | 
| }); | 
| /** | 
| - * Creates an element hiding filter from a pre-parsed text representation | 
| + * Creates a content filter from a pre-parsed text representation | 
| * | 
| * @param {string} text same as in Filter() | 
| * @param {string} [domains] | 
| * domains part of the text representation | 
| * @param {string} [type] | 
| -* rule type, either empty or @ (exception) or ? (emulation rule) | 
| - * @param {string} selector raw CSS selector | 
| + * rule type, either empty or @ (exception) or ? (emulation rule) or | 
| + * $ (snippet) | 
| + * @param {string} script | 
| + * script part of the text representation, either a CSS selector or a snippet | 
| + * script | 
| * @return {ElemHideFilter|ElemHideException| | 
| - * ElemHideEmulationFilter|InvalidFilter} | 
| + * ElemHideEmulationFilter|SnippetFilter|InvalidFilter} | 
| */ | 
| -ElemHideBase.fromText = function(text, domains, type, selector) | 
| +ContentFilter.fromText = function(text, domains, type, script) | 
| { | 
| - // We don't allow ElemHide filters which have any empty domains. | 
| - // Note: The ElemHide.prototype.domainSeparator is duplicated here, if that | 
| - // changes this must be changed too. | 
| + // We don't allow content filters which have any empty domains. | 
| + // Note: The ContentFilter.prototype.domainSeparator is duplicated here, if | 
| + // that changes this must be changed too. | 
| if (domains && /(^|,)~?(,|$)/.test(domains)) | 
| return new InvalidFilter(text, "filter_invalid_domain"); | 
| if (type == "@") | 
| - return new ElemHideException(text, domains, selector); | 
| + return new ElemHideException(text, domains, script); | 
| + | 
| + if (type == "$") | 
| + return new SnippetFilter(text, domains, script); | 
| if (type == "?") | 
| { | 
| // Element hiding emulation filters are inefficient so we need to make sure | 
| // that they're only applied if they specify active domains | 
| if (!/,[^~][^,.]*\.[^,]/.test("," + domains)) | 
| return new InvalidFilter(text, "filter_elemhideemulation_nodomain"); | 
| - return new ElemHideEmulationFilter(text, domains, selector); | 
| + return new ElemHideEmulationFilter(text, domains, script); | 
| } | 
| - return new ElemHideFilter(text, domains, selector); | 
| + return new ElemHideFilter(text, domains, script); | 
| }; | 
| +/* | 
| + * Base class for element hiding filters | 
| + * @param {string} text see {@link Filter Filter()} | 
| + * @param {string} [domains] see {@link ContentFilter ContentFilter()} | 
| + * @param {string} selector CSS selector for the HTML elements that should be | 
| + * hidden | 
| + * @constructor | 
| + * @augments ContentFilter | 
| + */ | 
| +function ElemHideBase(text, domains, selector) | 
| +{ | 
| + ContentFilter.call(this, text, domains, selector); | 
| +} | 
| +exports.ElemHideBase = ElemHideBase; | 
| + | 
| +ElemHideBase.prototype = extend(ContentFilter, { | 
| + /** | 
| + * CSS selector for the HTML elements that should be hidden | 
| + * @type {string} | 
| + */ | 
| + get selector() | 
| + { | 
| + // Braces are being escaped to prevent CSS rule injection. | 
| + return this.script.replace("{", "\\7B ").replace("}", "\\7D "); | 
| + } | 
| +}); | 
| + | 
| /** | 
| * Class for element hiding filters | 
| * @param {string} text see {@link Filter Filter()} | 
| * @param {string} [domains] see {@link ElemHideBase ElemHideBase()} | 
| * @param {string} selector see {@link ElemHideBase ElemHideBase()} | 
| * @constructor | 
| * @augments ElemHideBase | 
| */ | 
| @@ -1110,8 +1139,26 @@ | 
| { | 
| ElemHideBase.call(this, text, domains, selector); | 
| } | 
| exports.ElemHideEmulationFilter = ElemHideEmulationFilter; | 
| ElemHideEmulationFilter.prototype = extend(ElemHideBase, { | 
| type: "elemhideemulation" | 
| }); | 
| + | 
| +/** | 
| + * Class for snippet filters | 
| + * @param {string} text see Filter() | 
| + * @param {string} [domains] see ContentFilter() | 
| + * @param {string} script Script that should be executed | 
| + * @constructor | 
| + * @augments ContentFilter | 
| + */ | 
| +function SnippetFilter(text, domains, script) | 
| +{ | 
| + ContentFilter.call(this, text, domains, script); | 
| +} | 
| +exports.SnippetFilter = SnippetFilter; | 
| + | 
| +SnippetFilter.prototype = extend(ContentFilter, { | 
| + type: "snippet" | 
| +}); |