Index: lib/elemHide.js |
=================================================================== |
--- a/lib/elemHide.js |
+++ b/lib/elemHide.js |
@@ -58,29 +58,62 @@ |
/** |
* The default style sheet that applies on all domains. This is based on the |
* value of <code>{@link unconditionalSelectors}</code>. |
* @type {?string} |
*/ |
let defaultStyleSheet = null; |
/** |
+ * The common style sheet that applies on all unknown domains. This is a |
+ * concatenation of the default style sheet and an additional style sheet based |
+ * on selectors from all generic filters that are not in the |
+ * <code>{@link unconditionalSelectors}</code> list. |
+ * @type {?string} |
+ */ |
+let commonStyleSheet = null; |
+ |
+/** |
* Map to be used instead when a filter has a blank domains property. |
* @type {Map.<string,boolean>} |
* @const |
*/ |
let defaultDomains = new Map([["", true]]); |
/** |
* Set containing known element hiding filters |
* @type {Set.<ElemHideFilter>} |
*/ |
let knownFilters = new Set(); |
/** |
+ * All domains known to occur in exceptions |
+ * @type {Set.<string>} |
+ */ |
+let knownExceptionDomains = new Set(); |
+ |
+/** |
+ * Returns the suffix of the given domain that is known. If no suffix is known, |
+ * an empty string is returned. |
+ * @param {?string} domain |
+ * @returns {string} |
+ */ |
+function getKnownSuffix(domain) |
+{ |
+ while (domain && !filtersByDomain.has(domain) && |
+ !knownExceptionDomains.has(domain)) |
+ { |
+ let index = domain.indexOf("."); |
+ domain = index == -1 ? "" : domain.substring(index + 1); |
+ } |
+ |
+ return domain; |
+} |
+ |
+/** |
* 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) |
{ |
@@ -107,27 +140,27 @@ |
return unconditionalSelectors; |
} |
/** |
* Returns the list of selectors that apply on a given domain from the subset |
* of filters that do not apply unconditionally on all domains. |
* |
* @param {string} domain The domain. |
- * @param {boolean} [specificOnly=false] Whether selectors from generic filters |
- * should be included. |
+ * @param {boolean} specificOnly Whether selectors from generic filters should |
+ * be included. |
* |
* @returns {Array.<string>} The list of selectors. |
*/ |
-function getConditionalSelectorsForDomain(domain, specificOnly = false) |
+function getConditionalSelectors(domain, specificOnly) |
{ |
let selectors = []; |
let excluded = new Set(); |
- let currentDomain = domain ? domain.replace(/\.+$/, "").toLowerCase() : ""; |
+ let currentDomain = domain; |
// 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; |
@@ -169,18 +202,47 @@ |
function getDefaultStyleSheet() |
{ |
if (!defaultStyleSheet) |
defaultStyleSheet = createStyleSheet(getUnconditionalSelectors()); |
return defaultStyleSheet; |
} |
-ElemHideExceptions.on("added", ({selector}) => |
+/** |
+ * Returns the common style sheet that applies on all unknown domains. |
+ * @returns {string} |
+ */ |
+function getCommonStyleSheet() |
{ |
+ if (!commonStyleSheet) |
+ { |
+ commonStyleSheet = getDefaultStyleSheet() + |
+ createStyleSheet(getConditionalSelectors("", false)); |
+ } |
+ |
+ return commonStyleSheet; |
+} |
+ |
+ElemHideExceptions.on("added", ({domains, selector}) => |
+{ |
+ commonStyleSheet = null; |
+ |
+ if (domains) |
+ { |
+ for (let domain of domains.keys()) |
+ { |
+ // Note: Once an exception domain is known it never becomes unknown, even |
+ // when all the exceptions containing that domain are removed. This is a |
+ // best-case optimization. |
+ if (domain != "") |
+ knownExceptionDomains.add(domain); |
+ } |
+ } |
+ |
// 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; |
@@ -193,34 +255,41 @@ |
* @class |
*/ |
exports.ElemHide = { |
/** |
* Removes all known filters |
*/ |
clear() |
{ |
- for (let collection of [filtersByDomain, filterBySelector, knownFilters]) |
+ commonStyleSheet = null; |
+ |
+ for (let collection of [filtersByDomain, filterBySelector, knownFilters, |
+ knownExceptionDomains]) |
+ { |
collection.clear(); |
+ } |
unconditionalSelectors = null; |
defaultStyleSheet = null; |
filterNotifier.emit("elemhideupdate"); |
}, |
/** |
* Add a new element hiding filter |
* @param {ElemHideFilter} filter |
*/ |
add(filter) |
{ |
if (knownFilters.has(filter)) |
return; |
+ commonStyleSheet = null; |
+ |
let {selector} = filter; |
if (!(filter.domains || ElemHideExceptions.hasExceptions(selector))) |
{ |
// The new filter's selector is unconditionally applied to all domains |
filterBySelector.set(selector, filter); |
unconditionalSelectors = null; |
defaultStyleSheet = null; |
@@ -239,16 +308,18 @@ |
* Removes an element hiding filter |
* @param {ElemHideFilter} filter |
*/ |
remove(filter) |
{ |
if (!knownFilters.has(filter)) |
return; |
+ commonStyleSheet = null; |
+ |
let {selector} = filter; |
// Unconditially applied element hiding filters |
if (filterBySelector.get(selector) == filter) |
{ |
filterBySelector.delete(selector); |
unconditionalSelectors = null; |
defaultStyleSheet = null; |
@@ -288,22 +359,39 @@ |
* @param {boolean} [specificOnly=false] Whether selectors from generic |
* filters should be included. |
* |
* @returns {ElemHideStyleSheet} An object containing the CSS code and the |
* list of selectors. |
*/ |
generateStyleSheetForDomain(domain, specificOnly = false) |
{ |
- let selectors = getConditionalSelectorsForDomain(domain, specificOnly); |
- let code = specificOnly ? createStyleSheet(selectors) : |
- (getDefaultStyleSheet() + createStyleSheet(selectors)); |
+ let code = null; |
+ let selectors = null; |
+ |
+ if (domain[domain.length - 1] == ".") |
+ domain = domain.replace(/\.+$/, ""); |
+ |
+ domain = domain.toLowerCase(); |
- if (!specificOnly) |
+ if (specificOnly) |
+ { |
+ selectors = getConditionalSelectors(domain, true); |
+ code = createStyleSheet(selectors); |
+ } |
+ else |
+ { |
+ let knownSuffix = getKnownSuffix(domain); |
+ |
+ selectors = getConditionalSelectors(knownSuffix, false); |
+ code = knownSuffix == "" ? getCommonStyleSheet() : |
+ (getDefaultStyleSheet() + createStyleSheet(selectors)); |
+ |
selectors = getUnconditionalSelectors().concat(selectors); |
+ } |
return {code, selectors}; |
} |
}; |
/** |
* Splits a list of selectors into groups determined by the value of |
* <code>{@link selectorGroupSize}</code>. |