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 ,
+ * 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 .
+ */
+
+"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.}
+ * @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.>}
+ */
+ _filtersByDomain: null,
+
+ /**
+ * Lookup table, filter by selector. (Only used for selectors that are
+ * unconditionally matched for all domains.)
+ * @type {Map.}
+ */
+ _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.}
+ */
+ _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])
+ {
+ 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;
Index: lib/elemHide.js
===================================================================
--- a/lib/elemHide.js
+++ b/lib/elemHide.js
@@ -16,174 +16,50 @@
*/
"use strict";
/**
* @fileOverview Element hiding implementation.
*/
+const {ContentFilterModule} = require("./contentFilterModule");
const {ElemHideExceptions} = require("./elemHideExceptions");
const {FilterNotifier} = require("./filterNotifier");
/**
- * Lookup table, active flag, by filter by domain.
- * (Only contains filters that aren't unconditionally matched for all domains.)
- * @type {Map.>}
- */
-let filtersByDomain = new Map();
-
-/**
- * Lookup table, filter by selector. (Only used for selectors that are
- * unconditionally matched for all domains.)
- * @type {Map.}
- */
-let filterBySelector = new Map();
-
-/**
- * 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[]}
- */
-let unconditionalSelectors = null;
-
-/**
- * Map to be used instead when a filter has a blank domains property.
- * @type {Map.}
- * @const
- */
-let defaultDomains = new Map([["", true]]);
-
-/**
- * Set containing known element hiding filters
- * @type {Set.}
- */
-let knownFilters = new Set();
-
-/**
- * Adds a filter to the lookup table of filters by domain.
- * @param {Filter} filter
- */
-function 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 = filtersByDomain.get(domain);
- if (!filters)
- filtersByDomain.set(domain, filters = new Map());
- filters.set(filter, isIncluded);
- }
-}
-
-/**
- * Returns a list of selectors that apply on each website unconditionally.
- * @returns {string[]}
- */
-function getUnconditionalSelectors()
-{
- if (!unconditionalSelectors)
- unconditionalSelectors = [...filterBySelector.keys()];
-
- return unconditionalSelectors;
-}
-
-ElemHideExceptions.on("added", ({selector}) =>
-{
- // 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 = filterBySelector.get(selector);
- if (unconditionalFilterForSelector)
- {
- addToFiltersByDomain(unconditionalFilterForSelector);
- filterBySelector.delete(selector);
- unconditionalSelectors = null;
- }
-});
-
-/**
* Container for element hiding filters
* @class
*/
-exports.ElemHide = {
- /**
- * Removes all known filters
- */
+exports.ElemHide = Object.assign(new ContentFilterModule(), {
clear()
{
- for (let collection of [filtersByDomain, filterBySelector, knownFilters])
- collection.clear();
+ ContentFilterModule.prototype.clear.call(this);
- unconditionalSelectors = null;
FilterNotifier.emit("elemhideupdate");
},
- /**
- * Add a new element hiding filter
- * @param {ElemHideFilter} filter
- */
add(filter)
{
- if (knownFilters.has(filter))
- return;
-
- let {selector} = filter;
+ let {size} = this._knownFilters;
- if (!(filter.domains || ElemHideExceptions.hasExceptions(selector)))
- {
- // The new filter's selector is unconditionally applied to all domains
- filterBySelector.set(selector, filter);
- unconditionalSelectors = null;
- }
- else
- {
- // The new filter's selector only applies to some domains
- addToFiltersByDomain(filter);
- }
+ ContentFilterModule.prototype.add.call(this, filter);
- knownFilters.add(filter);
- FilterNotifier.emit("elemhideupdate");
+ if (size != this._knownFilters.size)
+ FilterNotifier.emit("elemhideupdate");
},
- /**
- * Removes an element hiding filter
- * @param {ElemHideFilter} filter
- */
remove(filter)
{
- if (!knownFilters.has(filter))
- return;
-
- let {selector} = filter;
+ let {size} = this._knownFilters;
- // Unconditially applied element hiding filters
- if (filterBySelector.get(selector) == filter)
- {
- filterBySelector.delete(selector);
- unconditionalSelectors = null;
- }
- // Conditionally applied element hiding filters
- else
- {
- let domains = filter.domains || defaultDomains;
- for (let domain of domains.keys())
- {
- let filters = filtersByDomain.get(domain);
- if (filters)
- filters.delete(filter);
- }
- }
+ ContentFilterModule.prototype.remove.call(this, filter);
- knownFilters.delete(filter);
- FilterNotifier.emit("elemhideupdate");
+ if (size != this._knownFilters.size)
+ FilterNotifier.emit("elemhideupdate");
},
/**
* Determines from the current filter list which selectors should be applied
* on a particular host name.
* @param {string} domain
* @param {boolean} [specificOnly] true if generic filters should not apply.
* @returns {string[]} List of selectors.
@@ -197,17 +73,17 @@
// This code is a performance hot-spot, which is why we've made certain
// micro-optimisations. Please be careful before making changes.
while (true)
{
if (specificOnly && currentDomain == "")
break;
- let filters = filtersByDomain.get(currentDomain);
+ let filters = this._filtersByDomain.get(currentDomain);
if (filters)
{
for (let [filter, isIncluded] of filters)
{
if (!isIncluded)
{
excluded.add(filter);
}
@@ -226,13 +102,13 @@
if (currentDomain == "")
break;
let nextDot = currentDomain.indexOf(".");
currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1);
}
if (!specificOnly)
- selectors = getUnconditionalSelectors().concat(selectors);
+ selectors = this._getUnconditionalSelectors().concat(selectors);
return selectors;
}
-};
+});
Index: lib/elemHideEmulation.js
===================================================================
--- a/lib/elemHideEmulation.js
+++ b/lib/elemHideEmulation.js
@@ -16,65 +16,35 @@
*/
"use strict";
/**
* @fileOverview Element hiding emulation implementation.
*/
+const {ContentFilterModule} = require("./contentFilterModule");
const {ElemHideExceptions} = require("./elemHideExceptions");
-const {Filter} = require("./filterClasses");
-
-let filters = new Set();
/**
* Container for element hiding emulation filters
* @class
*/
-let ElemHideEmulation = {
- /**
- * Removes all known filters
- */
- clear()
- {
- filters.clear();
- },
-
- /**
- * Add a new element hiding emulation filter
- * @param {ElemHideEmulationFilter} filter
- */
- add(filter)
- {
- filters.add(filter.text);
- },
-
- /**
- * Removes an element hiding emulation filter
- * @param {ElemHideEmulationFilter} filter
- */
- remove(filter)
- {
- filters.delete(filter.text);
- },
-
+exports.ElemHideEmulation = Object.assign(new ContentFilterModule(), {
/**
* Returns a list of all rules active on a particular domain
* @param {string} domain
* @return {ElemHideEmulationFilter[]}
*/
getRulesForDomain(domain)
{
let result = [];
- for (let text of filters.values())
+ for (let filter of this._knownFilters)
{
- let filter = Filter.fromText(text);
if (filter.isActiveOnDomain(domain) &&
!ElemHideExceptions.getException(filter.selector, domain))
{
result.push(filter);
}
}
return result;
}
-};
-exports.ElemHideEmulation = ElemHideEmulation;
+});
Index: test/filterListener.js
===================================================================
--- a/test/filterListener.js
+++ b/test/filterListener.js
@@ -25,18 +25,16 @@
let Filter = null;
let defaultMatcher = null;
let SpecialSubscription = null;
exports.setUp = function(callback)
{
sandboxedRequire = createSandbox({
extraExports: {
- elemHide: ["knownFilters"],
- elemHideEmulation: ["filters"],
elemHideExceptions: ["knownExceptions"],
snippets: ["filters"]
}
});
// We need to require the filterListener module so that filter changes will be
// noticed, even though we don't directly use the module here.
sandboxedRequire("../lib/filterListener");
@@ -76,28 +74,28 @@
filters.push(filter.text);
}
}
result[type] = filters;
}
let elemHide = sandboxedRequire("../lib/elemHide");
result.elemhide = [];
- for (let filter of elemHide.knownFilters)
+ for (let filter of elemHide.ElemHide._knownFilters)
result.elemhide.push(filter.text);
let elemHideExceptions = sandboxedRequire("../lib/elemHideExceptions");
result.elemhideexception = [];
for (let exception of elemHideExceptions.knownExceptions)
result.elemhideexception.push(exception.text);
let elemHideEmulation = sandboxedRequire("../lib/elemHideEmulation");
result.elemhideemulation = [];
- for (let filterText of elemHideEmulation.filters)
- result.elemhideemulation.push(filterText);
+ for (let filter of elemHideEmulation.ElemHideEmulation._knownFilters)
+ result.elemhideemulation.push(filter.text);
let snippets = sandboxedRequire("../lib/snippets");
result.snippets = [];
for (let filterText of snippets.filters)
result.snippets.push(filterText);
let types = ["blacklist", "whitelist", "elemhide", "elemhideexception",
"elemhideemulation", "snippets"];