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; |