| 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 omitted, 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) |
| { |