| Index: lib/contentFilterModule.js |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/lib/contentFilterModule.js |
| @@ -0,0 +1,201 @@ |
| +/* |
| + * This file is part of Adblock Plus <https://adblockplus.org/>, |
| + * Copyright (C) 2006-present eyeo GmbH |
| + * |
| + * Adblock Plus is free software: you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 3 as |
| + * published by the Free Software Foundation. |
| + * |
| + * Adblock Plus is distributed in the hope that it will be useful, |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| + * GNU General Public License for more details. |
| + * |
| + * You should have received a copy of the GNU General Public License |
| + * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| + */ |
| + |
| +"use strict"; |
| + |
| +/** |
| + * @fileOverview Base implementation for content filter modules. |
| + */ |
| + |
| +const {ElemHideExceptions} = require("./elemHideExceptions"); |
| + |
| +/** |
| + * Map to be used instead when a filter has a blank domains property. |
| + * @type {Map.<string,boolean>} |
| + * @const |
| + */ |
| +let defaultDomains = new Map([["", true]]); |
| + |
| +/** |
| + * Base class for content filter modules |
| + * @class |
| + */ |
| +function ContentFilterModule() |
| +{ |
| + this._filtersByDomain = new Map(); |
| + this._filterBySelector = new Map(); |
| + this._unconditionalSelectors = null; |
| + this._knownFilters = new Set(); |
| + |
| + ElemHideExceptions.on("added", this._onExceptionAdded.bind(this)); |
| +} |
| + |
| +ContentFilterModule.prototype = { |
| + /** |
| + * Lookup table, active flag, by filter by domain. |
| + * (Only contains filters that aren't unconditionally matched for all |
| + * domains.) |
| + * @type {Map.<string,Map.<Filter,boolean>>} |
| + */ |
| + _filtersByDomain: null, |
| + |
| + /** |
| + * Lookup table, filter by selector. (Only used for selectors that are |
| + * unconditionally matched for all domains.) |
| + * @type {Map.<string,Filter>} |
| + */ |
| + _filterBySelector: null, |
| + |
| + /** |
| + * This array caches the keys of filterBySelector table (selectors |
| + * which unconditionally apply on all domains). It will be null if the |
| + * cache needs to be rebuilt. |
| + * @type {?string[]} |
| + */ |
| + _unconditionalSelectors: null, |
| + |
| + /** |
| + * Set containing known content filters |
| + * @type {Set.<ContentFilter>} |
| + */ |
| + _knownFilters: null, |
| + |
| + /** |
| + * Adds a filter to the lookup table of filters by domain. |
| + * @param {Filter} filter |
| + */ |
| + _addToFiltersByDomain(filter) |
| + { |
| + let domains = filter.domains || defaultDomains; |
| + for (let [domain, isIncluded] of domains) |
| + { |
| + // There's no need to note that a filter is generically disabled. |
| + if (!isIncluded && domain == "") |
| + continue; |
| + |
| + let filters = this._filtersByDomain.get(domain); |
| + if (!filters) |
| + this._filtersByDomain.set(domain, filters = new Map()); |
| + filters.set(filter, isIncluded); |
| + } |
| + }, |
| + |
| + /** |
| + * Returns a list of selectors that apply on each website unconditionally. |
| + * @returns {string[]} |
| + */ |
| + _getUnconditionalSelectors() |
| + { |
| + if (!this._unconditionalSelectors) |
| + this._unconditionalSelectors = [...this._filterBySelector.keys()]; |
| + |
| + return this._unconditionalSelectors; |
| + }, |
| + |
| + /** |
| + * Handles the event when a new element hiding exception has been added |
| + * @param {ElemHideException} exception |
| + */ |
| + _onExceptionAdded(exception) |
| + { |
| + let {selector} = exception; |
| + |
| + // If this is the first exception for a previously unconditionally applied |
| + // element hiding selector we need to take care to update the lookups. |
| + let unconditionalFilterForSelector = this._filterBySelector.get(selector); |
| + if (unconditionalFilterForSelector) |
| + { |
| + this._addToFiltersByDomain(unconditionalFilterForSelector); |
| + this._filterBySelector.delete(selector); |
| + this._unconditionalSelectors = null; |
| + } |
| + }, |
| + |
| + /** |
| + * Removes all known filters |
| + */ |
| + clear() |
| + { |
| + for (let collection of [this._filtersByDomain, this._filterBySelector, |
| + this._knownFilters]) |
|
hub
2018/08/23 16:05:38
I'd be for using forEach() here.
|
| + { |
| + collection.clear(); |
| + } |
| + |
| + this._unconditionalSelectors = null; |
| + }, |
| + |
| + /** |
| + * Add a new content filter |
| + * @param {ContentFilter} filter |
| + */ |
| + add(filter) |
| + { |
| + if (this._knownFilters.has(filter)) |
| + return; |
| + |
| + let {selector} = filter; |
| + |
| + if (!(filter.domains || ElemHideExceptions.hasExceptions(selector))) |
| + { |
| + // The new filter's selector is unconditionally applied to all domains |
| + this._filterBySelector.set(selector, filter); |
| + this._unconditionalSelectors = null; |
| + } |
| + else |
| + { |
| + // The new filter's selector only applies to some domains |
| + this._addToFiltersByDomain(filter); |
| + } |
| + |
| + this._knownFilters.add(filter); |
| + }, |
| + |
| + /** |
| + * Removes a content filter |
| + * @param {ContentFilter} filter |
| + */ |
| + remove(filter) |
| + { |
| + if (!this._knownFilters.has(filter)) |
| + return; |
| + |
| + let {selector} = filter; |
| + |
| + // Unconditially applied content filters |
| + if (this._filterBySelector.get(selector) == filter) |
| + { |
| + this._filterBySelector.delete(selector); |
| + this._unconditionalSelectors = null; |
| + } |
| + // Conditionally applied content filters |
| + else |
| + { |
| + let domains = filter.domains || defaultDomains; |
| + for (let domain of domains.keys()) |
| + { |
| + let filters = this._filtersByDomain.get(domain); |
| + if (filters) |
| + filters.delete(filter); |
| + } |
| + } |
| + |
| + this._knownFilters.delete(filter); |
| + } |
| +}; |
| + |
| +exports.ContentFilterModule = ContentFilterModule; |