mirror of https://github.com/roytam1/boc-uxp.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
813 lines
22 KiB
813 lines
22 KiB
/* |
|
* This Source Code is subject to the terms of the Mozilla Public License |
|
* version 2.0 (the "License"). You can obtain a copy of the License at |
|
* http://mozilla.org/MPL/2.0/. |
|
*/ |
|
|
|
#filter substitution |
|
|
|
/** |
|
* @fileOverview Definition of Filter class and its subclasses. |
|
*/ |
|
|
|
var EXPORTED_SYMBOLS = ["Filter", "InvalidFilter", "CommentFilter", "ActiveFilter", "RegExpFilter", "BlockingFilter", "WhitelistFilter", "ElemHideFilter"]; |
|
|
|
const Cc = Components.classes; |
|
const Ci = Components.interfaces; |
|
const Cr = Components.results; |
|
const Cu = Components.utils; |
|
|
|
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/"; |
|
Cu.import(baseURL + "Utils.jsm"); |
|
Cu.import(baseURL + "FilterNotifier.jsm"); |
|
|
|
/** |
|
* Abstract base class for filters |
|
* |
|
* @param {String} text string representation of the filter |
|
* @constructor |
|
*/ |
|
function Filter(text) |
|
{ |
|
this.text = text; |
|
this.subscriptions = []; |
|
} |
|
Filter.prototype = |
|
{ |
|
/** |
|
* String representation of the filter |
|
* @type String |
|
*/ |
|
text: null, |
|
|
|
/** |
|
* Filter subscriptions the filter belongs to |
|
* @type Array of Subscription |
|
*/ |
|
subscriptions: null, |
|
|
|
/** |
|
* Serializes the filter to an array of strings for writing out on the disk. |
|
* @param {Array of String} buffer buffer to push the serialization results into |
|
*/ |
|
serialize: function(buffer) |
|
{ |
|
buffer.push("[Filter]"); |
|
buffer.push("text=" + this.text); |
|
}, |
|
|
|
toString: function() |
|
{ |
|
return this.text; |
|
} |
|
}; |
|
|
|
/** |
|
* Cache for known filters, maps string representation to filter objects. |
|
* @type Object |
|
*/ |
|
Filter.knownFilters = {__proto__: null}; |
|
|
|
/** |
|
* Regular expression that element hiding filters should match |
|
* @type RegExp |
|
*/ |
|
Filter.elemhideRegExp = /^([^\/\*\|\@"!]*?)#(?:([\w\-]+|\*)((?:\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/; |
|
/** |
|
* 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 |
|
*/ |
|
Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/; |
|
|
|
/** |
|
* Creates a filter of correct type from its text representation - does the basic parsing and |
|
* calls the right constructor then. |
|
* |
|
* @param {String} text as in Filter() |
|
* @return {Filter} filter or null if the filter couldn't be created |
|
*/ |
|
Filter.fromText = function(text) |
|
{ |
|
if (text in Filter.knownFilters) |
|
return Filter.knownFilters[text]; |
|
|
|
if (!/\S/.test(text)) |
|
return null; |
|
|
|
let ret; |
|
let match = Filter.elemhideRegExp.exec(text); |
|
if (match) |
|
ret = ElemHideFilter.fromText(text, match[1], match[2], match[3], match[4]); |
|
else if (text[0] == "!") |
|
ret = new CommentFilter(text); |
|
else |
|
ret = RegExpFilter.fromText(text); |
|
|
|
Filter.knownFilters[ret.text] = ret; |
|
return ret; |
|
} |
|
|
|
/** |
|
* Deserializes a filter |
|
* |
|
* @param {Object} obj map of serialized properties and their values |
|
* @return {Filter} filter or null if the filter couldn't be created |
|
*/ |
|
Filter.fromObject = function(obj) |
|
{ |
|
let ret = Filter.fromText(obj.text); |
|
if (ret instanceof ActiveFilter) |
|
{ |
|
if ("disabled" in obj) |
|
ret._disabled = (obj.disabled == "true"); |
|
if ("hitCount" in obj) |
|
ret._hitCount = parseInt(obj.hitCount) || 0; |
|
if ("lastHit" in obj) |
|
ret._lastHit = parseInt(obj.lastHit) || 0; |
|
} |
|
return ret; |
|
} |
|
|
|
/** |
|
* Removes unnecessary whitespaces from filter text, will only return null if |
|
* the input parameter is null. |
|
*/ |
|
Filter.normalize = function(/**String*/ text) /**String*/ |
|
{ |
|
if (!text) |
|
return text; |
|
|
|
// Remove line breaks and such |
|
text = text.replace(/[^\S ]/g, ""); |
|
|
|
if (/^\s*!/.test(text)) |
|
{ |
|
// Don't remove spaces inside comments |
|
return text.replace(/^\s+/, "").replace(/\s+$/, ""); |
|
} |
|
else if (Filter.elemhideRegExp.test(text)) |
|
{ |
|
// Special treatment for element hiding filters, right side is allowed to contain spaces |
|
let [, domain, separator, selector] = /^(.*?)(#+)(.*)$/.exec(text); // .split(..., 2) will cut off the end of the string |
|
return domain.replace(/\s/g, "") + separator + selector.replace(/^\s+/, "").replace(/\s+$/, ""); |
|
} |
|
else |
|
return text.replace(/\s/g, ""); |
|
} |
|
|
|
/** |
|
* Class for invalid filters |
|
* @param {String} text see Filter() |
|
* @param {String} reason Reason why this filter is invalid |
|
* @constructor |
|
* @augments Filter |
|
*/ |
|
function InvalidFilter(text, reason) |
|
{ |
|
Filter.call(this, text); |
|
|
|
this.reason = reason; |
|
} |
|
InvalidFilter.prototype = |
|
{ |
|
__proto__: Filter.prototype, |
|
|
|
/** |
|
* Reason why this filter is invalid |
|
* @type String |
|
*/ |
|
reason: null, |
|
|
|
/** |
|
* See Filter.serialize() |
|
*/ |
|
serialize: function(buffer) {} |
|
}; |
|
|
|
/** |
|
* Class for comments |
|
* @param {String} text see Filter() |
|
* @constructor |
|
* @augments Filter |
|
*/ |
|
function CommentFilter(text) |
|
{ |
|
Filter.call(this, text); |
|
} |
|
CommentFilter.prototype = |
|
{ |
|
__proto__: Filter.prototype, |
|
|
|
/** |
|
* See Filter.serialize() |
|
*/ |
|
serialize: function(buffer) {} |
|
}; |
|
|
|
/** |
|
* Abstract base class for filters that can get hits |
|
* @param {String} text see Filter() |
|
* @param {String} domains (optional) Domains that the filter is restricted to separated by domainSeparator e.g. "foo.com|bar.com|~baz.com" |
|
* @constructor |
|
* @augments Filter |
|
*/ |
|
function ActiveFilter(text, domains) |
|
{ |
|
Filter.call(this, text); |
|
|
|
if (domains) |
|
{ |
|
this.domainSource = domains; |
|
this.__defineGetter__("domains", this._getDomains); |
|
} |
|
} |
|
ActiveFilter.prototype = |
|
{ |
|
__proto__: Filter.prototype, |
|
|
|
_disabled: false, |
|
_hitCount: 0, |
|
_lastHit: 0, |
|
|
|
/** |
|
* Defines whether the filter is disabled |
|
* @type Boolean |
|
*/ |
|
get disabled() this._disabled, |
|
set disabled(value) |
|
{ |
|
if (value != this._disabled) |
|
{ |
|
let oldValue = this._disabled; |
|
this._disabled = value; |
|
FilterNotifier.triggerListeners("filter.disabled", this, value, oldValue); |
|
} |
|
return this._disabled; |
|
}, |
|
|
|
/** |
|
* Number of hits on the filter since the last reset |
|
* @type Number |
|
*/ |
|
get hitCount() this._hitCount, |
|
set hitCount(value) |
|
{ |
|
if (value != this._hitCount) |
|
{ |
|
let oldValue = this._hitCount; |
|
this._hitCount = value; |
|
FilterNotifier.triggerListeners("filter.hitCount", this, value, oldValue); |
|
} |
|
return this._hitCount; |
|
}, |
|
|
|
/** |
|
* Last time the filter had a hit (in milliseconds since the beginning of the epoch) |
|
* @type Number |
|
*/ |
|
get lastHit() this._lastHit, |
|
set lastHit(value) |
|
{ |
|
if (value != this._lastHit) |
|
{ |
|
let oldValue = this._lastHit; |
|
this._lastHit = value; |
|
FilterNotifier.triggerListeners("filter.lastHit", this, value, oldValue); |
|
} |
|
return this._lastHit; |
|
}, |
|
|
|
/** |
|
* String that the domains property should be generated from |
|
* @type String |
|
*/ |
|
domainSource: null, |
|
|
|
/** |
|
* Separator character used in domainSource property, must be overridden by subclasses |
|
* @type String |
|
*/ |
|
domainSeparator: null, |
|
|
|
/** |
|
* Map containing domains that this filter should match on/not match on or null if the filter should match on all domains |
|
* @type Object |
|
*/ |
|
domains: null, |
|
|
|
/** |
|
* Called first time domains property is requested, triggers _generateDomains method. |
|
*/ |
|
_getDomains: function() |
|
{ |
|
this._generateDomains(); |
|
return this.domains; |
|
}, |
|
|
|
/** |
|
* Generates domains property when it is requested for the first time. |
|
*/ |
|
_generateDomains: function() |
|
{ |
|
let domains = this.domainSource.split(this.domainSeparator); |
|
|
|
delete this.domainSource; |
|
delete this.domains; |
|
|
|
if (domains.length == 1 && domains[0][0] != "~") |
|
{ |
|
// Fast track for the common one-domain scenario |
|
this.domains = {__proto__: null, "": false}; |
|
this.domains[domains[0]] = true; |
|
} |
|
else |
|
{ |
|
let hasIncludes = false; |
|
for (let i = 0; i < domains.length; i++) |
|
{ |
|
let domain = domains[i]; |
|
if (domain == "") |
|
continue; |
|
|
|
let include; |
|
if (domain[0] == "~") |
|
{ |
|
include = false; |
|
domain = domain.substr(1); |
|
} |
|
else |
|
{ |
|
include = true; |
|
hasIncludes = true; |
|
} |
|
|
|
if (!this.domains) |
|
this.domains = {__proto__: null}; |
|
|
|
this.domains[domain] = include; |
|
} |
|
this.domains[""] = !hasIncludes; |
|
} |
|
}, |
|
|
|
/** |
|
* Checks whether this filter is active on a domain. |
|
*/ |
|
isActiveOnDomain: function(/**String*/ docDomain) /**Boolean*/ |
|
{ |
|
// If no domains are set the rule matches everywhere |
|
if (!this.domains) |
|
return true; |
|
|
|
// If the document has no host name, match only if the filter isn't restricted to specific domains |
|
if (!docDomain) |
|
return this.domains[""]; |
|
|
|
docDomain = docDomain.replace(/\.+$/, "").toUpperCase(); |
|
|
|
while (true) |
|
{ |
|
if (docDomain in this.domains) |
|
return this.domains[docDomain]; |
|
|
|
let nextDot = docDomain.indexOf("."); |
|
if (nextDot < 0) |
|
break; |
|
docDomain = docDomain.substr(nextDot + 1); |
|
} |
|
return this.domains[""]; |
|
}, |
|
|
|
/** |
|
* Checks whether this filter is active only on a domain and its subdomains. |
|
*/ |
|
isActiveOnlyOnDomain: function(/**String*/ docDomain) /**Boolean*/ |
|
{ |
|
if (!docDomain || !this.domains || this.domains[""]) |
|
return false; |
|
|
|
docDomain = docDomain.replace(/\.+$/, "").toUpperCase(); |
|
|
|
for (let domain in this.domains) |
|
if (this.domains[domain] && domain != docDomain && (domain.length <= docDomain.length || domain.indexOf("." + docDomain) != domain.length - docDomain.length - 1)) |
|
return false; |
|
|
|
return true; |
|
}, |
|
|
|
/** |
|
* See Filter.serialize() |
|
*/ |
|
serialize: function(buffer) |
|
{ |
|
if (this._disabled || this._hitCount || this._lastHit) |
|
{ |
|
Filter.prototype.serialize.call(this, buffer); |
|
if (this._disabled) |
|
buffer.push("disabled=true"); |
|
if (this._hitCount) |
|
buffer.push("hitCount=" + this._hitCount); |
|
if (this._lastHit) |
|
buffer.push("lastHit=" + this._lastHit); |
|
} |
|
} |
|
}; |
|
|
|
/** |
|
* Abstract base class for RegExp-based filters |
|
* @param {String} text see Filter() |
|
* @param {String} regexpSource filter part that the regular expression should be build from |
|
* @param {Number} contentType (optional) Content types the filter applies to, combination of values from RegExpFilter.typeMap |
|
* @param {Boolean} matchCase (optional) Defines whether the filter should distinguish between lower and upper case letters |
|
* @param {String} domains (optional) Domains that the filter is restricted to, e.g. "foo.com|bar.com|~baz.com" |
|
* @param {Boolean} thirdParty (optional) Defines whether the filter should apply to third-party or first-party content only |
|
* @constructor |
|
* @augments ActiveFilter |
|
*/ |
|
function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty) |
|
{ |
|
ActiveFilter.call(this, text, domains); |
|
|
|
if (contentType != null) |
|
this.contentType = contentType; |
|
if (matchCase) |
|
this.matchCase = matchCase; |
|
if (thirdParty != null) |
|
this.thirdParty = thirdParty; |
|
|
|
if (regexpSource.length >= 2 && regexpSource[0] == "/" && regexpSource[regexpSource.length - 1] == "/") |
|
{ |
|
// The filter is a regular expression - convert it immediately to catch syntax errors |
|
this.regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i"); |
|
} |
|
else |
|
{ |
|
// No need to convert this filter to regular expression yet, do it on demand |
|
this.regexpSource = regexpSource; |
|
this.__defineGetter__("regexp", this._generateRegExp); |
|
} |
|
} |
|
RegExpFilter.prototype = |
|
{ |
|
__proto__: ActiveFilter.prototype, |
|
|
|
/** |
|
* Number of filters contained, will always be 1 (required to optimize Matcher). |
|
* @type Integer |
|
*/ |
|
length: 1, |
|
|
|
/** |
|
* @see ActiveFilter.domainSeparator |
|
*/ |
|
domainSeparator: "|", |
|
|
|
/** |
|
* Expression from which a regular expression should be generated - for delayed creation of the regexp property |
|
* @type String |
|
*/ |
|
regexpSource: null, |
|
/** |
|
* Regular expression to be used when testing against this filter |
|
* @type RegExp |
|
*/ |
|
regexp: null, |
|
/** |
|
* Content types the filter applies to, combination of values from RegExpFilter.typeMap |
|
* @type Number |
|
*/ |
|
contentType: 0x7FFFFFFF, |
|
/** |
|
* Defines whether the filter should distinguish between lower and upper case letters |
|
* @type Boolean |
|
*/ |
|
matchCase: false, |
|
/** |
|
* Defines whether the filter should apply to third-party or first-party content only. Can be null (apply to all content). |
|
* @type Boolean |
|
*/ |
|
thirdParty: null, |
|
|
|
/** |
|
* Generates regexp property when it is requested for the first time. |
|
* @return {RegExp} |
|
*/ |
|
_generateRegExp: function() |
|
{ |
|
// Remove multiple wildcards |
|
let source = this.regexpSource.replace(/\*+/g, "*"); |
|
|
|
// Remove leading wildcards |
|
if (source[0] == "*") |
|
source = source.substr(1); |
|
|
|
// Remove trailing wildcards |
|
let pos = source.length - 1; |
|
if (pos >= 0 && source[pos] == "*") |
|
source = source.substr(0, pos); |
|
|
|
source = source.replace(/\^\|$/, "^") // remove anchors following separator placeholder |
|
.replace(/\W/g, "\\$&") // escape special symbols |
|
.replace(/\\\*/g, ".*") // replace wildcards by .* |
|
// process separator placeholders (all ANSI charaters but alphanumeric characters and _%.-) |
|
.replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x80]|$)") |
|
.replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^.\\/]+\\.)*?") // process extended anchor at expression start |
|
.replace(/^\\\|/, "^") // process anchor at expression start |
|
.replace(/\\\|$/, "$"); // process anchor at expression end |
|
|
|
let regexp = new RegExp(source, this.matchCase ? "" : "i"); |
|
|
|
delete this.regexp; |
|
delete this.regexpSource; |
|
return (this.regexp = regexp); |
|
}, |
|
|
|
/** |
|
* Tests whether the URL matches this filter |
|
* @param {String} location URL to be tested |
|
* @param {String} contentType content type identifier of the URL |
|
* @param {String} docDomain domain name of the document that loads the URL |
|
* @param {Boolean} thirdParty should be true if the URL is a third-party request |
|
* @return {Boolean} true in case of a match |
|
*/ |
|
matches: function(location, contentType, docDomain, thirdParty) |
|
{ |
|
if (this.regexp.test(location) && |
|
(RegExpFilter.typeMap[contentType] & this.contentType) != 0 && |
|
(this.thirdParty == null || this.thirdParty == thirdParty) && |
|
this.isActiveOnDomain(docDomain)) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
}; |
|
|
|
RegExpFilter.prototype.__defineGetter__("0", function() |
|
{ |
|
return this; |
|
}); |
|
|
|
/** |
|
* Creates a RegExp filter from its text representation |
|
* @param {String} text same as in Filter() |
|
*/ |
|
RegExpFilter.fromText = function(text) |
|
{ |
|
let blocking = true; |
|
let origText = text; |
|
if (text.indexOf("@@") == 0) |
|
{ |
|
blocking = false; |
|
text = text.substr(2); |
|
} |
|
|
|
let contentType = null; |
|
let matchCase = null; |
|
let domains = null; |
|
let thirdParty = null; |
|
let collapse = null; |
|
let options; |
|
let match = Filter.optionsRegExp.exec(text); |
|
if (match) |
|
{ |
|
options = match[1].toUpperCase().split(","); |
|
text = match.input.substr(0, match.index); |
|
for each (let option in options) |
|
{ |
|
let value = null; |
|
let separatorIndex = option.indexOf("="); |
|
if (separatorIndex >= 0) |
|
{ |
|
value = option.substr(separatorIndex + 1); |
|
option = option.substr(0, separatorIndex); |
|
} |
|
option = option.replace(/-/, "_"); |
|
if (option in RegExpFilter.typeMap) |
|
{ |
|
if (contentType == null) |
|
contentType = 0; |
|
contentType |= RegExpFilter.typeMap[option]; |
|
} |
|
else if (option[0] == "~" && option.substr(1) in RegExpFilter.typeMap) |
|
{ |
|
if (contentType == null) |
|
contentType = RegExpFilter.prototype.contentType; |
|
contentType &= ~RegExpFilter.typeMap[option.substr(1)]; |
|
} |
|
else if (option == "MATCH_CASE") |
|
matchCase = true; |
|
else if (option == "DOMAIN" && typeof value != "undefined") |
|
domains = value; |
|
else if (option == "THIRD_PARTY") |
|
thirdParty = true; |
|
else if (option == "~THIRD_PARTY") |
|
thirdParty = false; |
|
else if (option == "COLLAPSE") |
|
collapse = true; |
|
else if (option == "~COLLAPSE") |
|
collapse = false; |
|
} |
|
} |
|
|
|
if (!blocking && (contentType == null || (contentType & RegExpFilter.typeMap.DOCUMENT)) && |
|
(!options || options.indexOf("DOCUMENT") < 0) && !/^\|?[\w\-]+:/.test(text)) |
|
{ |
|
// Exception filters shouldn't apply to pages by default unless they start with a protocol name |
|
if (contentType == null) |
|
contentType = RegExpFilter.prototype.contentType; |
|
contentType &= ~RegExpFilter.typeMap.DOCUMENT; |
|
} |
|
|
|
try |
|
{ |
|
if (blocking) |
|
return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, collapse); |
|
else |
|
return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty); |
|
} |
|
catch (e) |
|
{ |
|
return new InvalidFilter(text, e); |
|
} |
|
} |
|
|
|
/** |
|
* Maps type strings like "SCRIPT" or "OBJECT" to bit masks |
|
*/ |
|
RegExpFilter.typeMap = { |
|
OTHER: 1, |
|
SCRIPT: 2, |
|
IMAGE: 4, |
|
STYLESHEET: 8, |
|
OBJECT: 16, |
|
SUBDOCUMENT: 32, |
|
DOCUMENT: 64, |
|
XBL: 1, |
|
PING: 1, |
|
XMLHTTPREQUEST: 2048, |
|
OBJECT_SUBREQUEST: 4096, |
|
DTD: 1, |
|
MEDIA: 16384, |
|
FONT: 32768, |
|
WEBSOCKET: 1, |
|
WEBRTC: 1, |
|
CSP: 1, |
|
|
|
BACKGROUND: 4, // Backwards compat, same as IMAGE |
|
|
|
POPUP: 0x10000000, |
|
DONOTTRACK: 0x20000000, |
|
ELEMHIDE: 0x40000000 |
|
}; |
|
|
|
// ELEMHIDE, DONOTTRACK, POPUP option shouldn't be there by default |
|
RegExpFilter.prototype.contentType &= ~( |
|
RegExpFilter.typeMap.ELEMHIDE | |
|
RegExpFilter.typeMap.DONOTTRACK | |
|
RegExpFilter.typeMap.POPUP | |
|
RegExpFilter.typeMap.WEBSOCKET | |
|
RegExpFilter.typeMap.WEBRTC | |
|
RegExpFilter.typeMap.CSP |
|
); |
|
|
|
/** |
|
* Class for blocking filters |
|
* @param {String} text see Filter() |
|
* @param {String} regexpSource see RegExpFilter() |
|
* @param {Number} contentType see RegExpFilter() |
|
* @param {Boolean} matchCase see RegExpFilter() |
|
* @param {String} domains see RegExpFilter() |
|
* @param {Boolean} thirdParty see RegExpFilter() |
|
* @param {Boolean} collapse defines whether the filter should collapse blocked content, can be null |
|
* @constructor |
|
* @augments RegExpFilter |
|
*/ |
|
function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, collapse) |
|
{ |
|
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty); |
|
|
|
this.collapse = collapse; |
|
} |
|
BlockingFilter.prototype = |
|
{ |
|
__proto__: RegExpFilter.prototype, |
|
|
|
/** |
|
* Defines whether the filter should collapse blocked content. Can be null (use the global preference). |
|
* @type Boolean |
|
*/ |
|
collapse: null |
|
}; |
|
|
|
/** |
|
* Class for whitelist filters |
|
* @param {String} text see Filter() |
|
* @param {String} regexpSource see RegExpFilter() |
|
* @param {Number} contentType see RegExpFilter() |
|
* @param {Boolean} matchCase see RegExpFilter() |
|
* @param {String} domains see RegExpFilter() |
|
* @param {Boolean} thirdParty see RegExpFilter() |
|
* @constructor |
|
* @augments RegExpFilter |
|
*/ |
|
function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty) |
|
{ |
|
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty); |
|
} |
|
|
|
WhitelistFilter.prototype = |
|
{ |
|
__proto__: RegExpFilter.prototype, |
|
|
|
} |
|
|
|
/** |
|
* Class for element hiding filters |
|
* @param {String} text see Filter() |
|
* @param {String} domains (optional) Host names or domains the filter should be restricted to |
|
* @param {String} selector CSS selector for the HTML elements that should be hidden |
|
* @constructor |
|
* @augments ActiveFilter |
|
*/ |
|
function ElemHideFilter(text, domains, selector) |
|
{ |
|
ActiveFilter.call(this, text, domains ? domains.toUpperCase() : null); |
|
|
|
if (domains) |
|
this.selectorDomain = domains.replace(/,~[^,]+/g, "").replace(/^~[^,]+,?/, "").toLowerCase(); |
|
this.selector = selector; |
|
} |
|
ElemHideFilter.prototype = |
|
{ |
|
__proto__: ActiveFilter.prototype, |
|
|
|
/** |
|
* @see ActiveFilter.domainSeparator |
|
*/ |
|
domainSeparator: ",", |
|
|
|
/** |
|
* Host name or domain the filter should be restricted to (can be null for no restriction) |
|
* @type String |
|
*/ |
|
selectorDomain: null, |
|
/** |
|
* CSS selector for the HTML elements that should be hidden |
|
* @type String |
|
*/ |
|
selector: null |
|
}; |
|
|
|
/** |
|
* Creates an element hiding filter from a pre-parsed text representation |
|
* |
|
* @param {String} text same as in Filter() |
|
* @param {String} domain domain part of the text representation (can be empty) |
|
* @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 or InvalidFilter} |
|
*/ |
|
ElemHideFilter.fromText = function(text, domain, tagName, attrRules, selector) |
|
{ |
|
if (!selector) |
|
{ |
|
if (tagName == "*") |
|
tagName = ""; |
|
|
|
let id = null; |
|
let additional = ""; |
|
if (attrRules) { |
|
attrRules = attrRules.match(/\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\)/g); |
|
for each (let rule in attrRules) { |
|
rule = rule.substr(1, rule.length - 2); |
|
let separatorPos = rule.indexOf("="); |
|
if (separatorPos > 0) { |
|
rule = rule.replace(/=/, '="') + '"'; |
|
additional += "[" + rule + "]"; |
|
} |
|
else { |
|
if (id) |
|
return new InvalidFilter(text, Utils.getString("filter_elemhide_duplicate_id")); |
|
else |
|
id = rule; |
|
} |
|
} |
|
} |
|
|
|
if (id) |
|
selector = tagName + "." + id + additional + "," + tagName + "#" + id + additional; |
|
else if (tagName || additional) |
|
selector = tagName + additional; |
|
else |
|
return new InvalidFilter(text, Utils.getString("filter_elemhide_nocriteria")); |
|
} |
|
return new ElemHideFilter(text, domain, selector); |
|
}
|
|
|