Index: lib/matcher.js |
=================================================================== |
--- a/lib/matcher.js |
+++ b/lib/matcher.js |
@@ -295,36 +295,45 @@ |
* Checks whether the entries for a particular keyword match a URL |
* @param {string} keyword |
* @param {string} location |
* @param {number} typeMask |
* @param {string} [docDomain] |
* @param {boolean} [thirdParty] |
* @param {string} [sitekey] |
* @param {boolean} [specificOnly] |
+ * @param {?Array.<Filter>} [collection] An optional list of filters to which |
+ * to append any results. If specified, the function adds <em>all</em> |
+ * matching filters to the list; if ommitted, the function directly returns |
+ * the first matching filter. |
* @returns {?Filter} |
* @protected |
*/ |
checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey, |
- specificOnly) |
+ specificOnly, collection) |
{ |
// We need to skip the simple (location-only) filters if the type mask does |
// not contain any default content types. |
if ((typeMask & DEFAULT_TYPES) != 0) |
{ |
let simpleSet = this._simpleFiltersByKeyword.get(keyword); |
if (simpleSet) |
{ |
for (let filter of simpleSet) |
{ |
if (specificOnly && !(filter instanceof WhitelistFilter)) |
continue; |
if (filter.matchesLocation(location)) |
- return filter; |
+ { |
+ if (!collection) |
+ return filter; |
+ |
+ collection.push(filter); |
+ } |
} |
} |
} |
let complexSet = null; |
// If the type mask contains a non-default type (first condition) and it is |
// the only type in the mask (second condition), we can use the |
@@ -346,17 +355,22 @@ |
{ |
for (let filter of complexSet) |
{ |
if (specificOnly && filter.isGeneric() && |
!(filter instanceof WhitelistFilter)) |
continue; |
if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey)) |
- return filter; |
+ { |
+ if (!collection) |
+ return filter; |
+ |
+ collection.push(filter); |
+ } |
} |
} |
return null; |
} |
/** |
* Tests whether the URL matches any of the known filters |
@@ -525,16 +539,60 @@ |
typeMask, docDomain, |
thirdParty, sitekey); |
} |
} |
return whitelistHit || blacklistHit; |
} |
+ _searchInternal(location, typeMask, docDomain, thirdParty, sitekey, |
+ specificOnly, filterType) |
+ { |
+ let hits = {}; |
+ |
+ let searchBlocking = filterType == "blocking" || filterType == "all"; |
+ let searchWhitelist = filterType == "whitelist" || filterType == "all"; |
+ |
+ if (searchBlocking) |
+ hits.blocking = []; |
+ |
+ if (searchWhitelist) |
+ hits.whitelist = []; |
+ |
+ // If the type mask includes no types other than whitelist-only types, we |
+ // can skip the blacklist. |
+ if ((typeMask & ~WHITELIST_ONLY_TYPES) == 0) |
+ searchBlocking = false; |
+ |
+ let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g); |
+ if (candidates === null) |
+ candidates = []; |
+ candidates.push(""); |
+ |
+ for (let i = 0, l = candidates.length; i < l; i++) |
+ { |
+ if (searchBlocking) |
+ { |
+ this._blacklist.checkEntryMatch(candidates[i], location, typeMask, |
+ docDomain, thirdParty, sitekey, |
+ specificOnly, hits.blocking); |
+ } |
+ |
+ if (searchWhitelist) |
+ { |
+ this._whitelist.checkEntryMatch(candidates[i], location, typeMask, |
+ docDomain, thirdParty, sitekey, |
+ false, hits.whitelist); |
+ } |
+ } |
+ |
+ return hits; |
+ } |
+ |
/** |
* @see Matcher#matchesAny |
* @inheritdoc |
*/ |
matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly) |
{ |
let key = location + " " + typeMask + " " + docDomain + " " + thirdParty + |
" " + sitekey + " " + specificOnly; |
@@ -550,16 +608,62 @@ |
this._resultCache.clear(); |
this._resultCache.set(key, result); |
return result; |
} |
/** |
+ * @typedef {object} MatcherSearchResults |
+ * @property {Array.<BlockingFilter>} [blocking] List of blocking filters |
+ * found. |
+ * @property {Array.<WhitelistFilter>} [whitelist] List of whitelist filters |
+ * found. |
+ */ |
+ |
+ /** |
+ * Searches all blocking and whitelist filters and returns results matching |
+ * the given parameters. |
+ * |
+ * @param {string} location |
+ * @param {number} typeMask |
+ * @param {string} [docDomain] |
+ * @param {boolean} [thirdParty] |
+ * @param {string} [sitekey] |
+ * @param {boolean} [specificOnly] |
+ * @param {string} [filterType] The types of filters to look for. This can be |
+ * <code>"blocking"</code>, <code>"whitelist"</code>, or |
+ * <code>"all"</code> (default). |
+ * |
+ * @returns {MatcherSearchResults} |
+ */ |
+ search(location, typeMask, docDomain, thirdParty, sitekey, specificOnly, |
+ filterType = "all") |
+ { |
+ let key = "* " + location + " " + typeMask + " " + docDomain + " " + |
+ thirdParty + " " + sitekey + " " + specificOnly + " " + |
+ filterType; |
+ |
+ let result = this._resultCache.get(key); |
+ if (typeof result != "undefined") |
+ return result; |
+ |
+ result = this._searchInternal(location, typeMask, docDomain, thirdParty, |
+ sitekey, specificOnly, filterType); |
+ |
+ if (this._resultCache.size >= this.maxCacheEntries) |
+ this._resultCache.clear(); |
+ |
+ this._resultCache.set(key, result); |
+ |
+ return result; |
+ } |
+ |
+ /** |
* Tests whether the URL is whitelisted |
* @see Matcher#matchesAny |
* @inheritdoc |
* @returns {boolean} |
*/ |
isWhitelisted(location, typeMask, docDomain, thirdParty, sitekey, |
specificOnly) |
{ |