mirror of
https://github.com/roytam1/boc-uxp.git
synced 2021-11-05 16:13:16 +00:00
ebc19cacf5
This reverts commit 3e94e634ae54925da650a57b9982fe087658c580.
814 lines
22 KiB
JavaScript
814 lines
22 KiB
JavaScript
/*
|
|
* 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);
|
|
}
|