Index: lib/elemHide.js |
=================================================================== |
--- a/lib/elemHide.js |
+++ b/lib/elemHide.js |
@@ -51,16 +51,23 @@ |
* 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; |
/** |
+ * 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; |
+ |
+/** |
* 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 |
@@ -95,26 +102,94 @@ |
function getUnconditionalSelectors() |
{ |
if (!unconditionalSelectors) |
unconditionalSelectors = [...filterBySelector.keys()]; |
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. |
+ * |
+ * @returns {Array.<string>} The list of selectors. |
+ */ |
+function getConditionalSelectorsForDomain(domain, specificOnly = false) |
+{ |
+ let selectors = []; |
+ |
+ let excluded = new Set(); |
+ let currentDomain = domain ? domain.replace(/\.+$/, "").toLowerCase() : ""; |
+ |
+ // 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); |
+ if (filters) |
+ { |
+ for (let [filter, isIncluded] of filters) |
+ { |
+ if (!isIncluded) |
+ { |
+ excluded.add(filter); |
+ } |
+ else |
+ { |
+ let {selector} = filter; |
+ if ((excluded.size == 0 || !excluded.has(filter)) && |
+ !ElemHideExceptions.getException(selector, domain)) |
+ { |
+ selectors.push(selector); |
+ } |
+ } |
+ } |
+ } |
+ |
+ if (currentDomain == "") |
+ break; |
+ |
+ let nextDot = currentDomain.indexOf("."); |
+ currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); |
+ } |
+ |
+ return selectors; |
+} |
+ |
+/** |
+ * Returns the default style sheet that applies on all domains. |
+ * @returns {string} |
+ */ |
+function getDefaultStyleSheet() |
+{ |
+ if (!defaultStyleSheet) |
+ defaultStyleSheet = createStyleSheet(getUnconditionalSelectors()); |
+ |
+ return defaultStyleSheet; |
+} |
+ |
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; |
+ defaultStyleSheet = null; |
} |
}); |
/** |
* Container for element hiding filters |
* @class |
*/ |
exports.ElemHide = { |
@@ -122,16 +197,18 @@ |
* Removes all known filters |
*/ |
clear() |
{ |
for (let collection of [filtersByDomain, filterBySelector, knownFilters]) |
collection.clear(); |
unconditionalSelectors = null; |
+ defaultStyleSheet = null; |
+ |
filterNotifier.emit("elemhideupdate"); |
}, |
/** |
* Add a new element hiding filter |
* @param {ElemHideFilter} filter |
*/ |
add(filter) |
@@ -141,16 +218,17 @@ |
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; |
} |
else |
{ |
// The new filter's selector only applies to some domains |
addToFiltersByDomain(filter); |
} |
knownFilters.add(filter); |
@@ -168,16 +246,17 @@ |
let {selector} = filter; |
// Unconditially applied element hiding filters |
if (filterBySelector.get(selector) == filter) |
{ |
filterBySelector.delete(selector); |
unconditionalSelectors = null; |
+ defaultStyleSheet = null; |
} |
// Conditionally applied element hiding filters |
else |
{ |
let domains = filter.domains || defaultDomains; |
for (let domain of domains.keys()) |
{ |
let filters = filtersByDomain.get(domain); |
@@ -191,68 +270,42 @@ |
} |
} |
knownFilters.delete(filter); |
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. |
+ * @typedef {object} ElemHideStyleSheet |
+ * @property {string} code CSS code. |
+ * @property {Array.<string>} selectors List of selectors. |
*/ |
- getSelectorsForDomain(domain, specificOnly = false) |
- { |
- let selectors = []; |
- |
- let excluded = new Set(); |
- let currentDomain = domain ? domain.replace(/\.+$/, "").toLowerCase() : ""; |
- |
- // 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); |
- if (filters) |
- { |
- for (let [filter, isIncluded] of filters) |
- { |
- if (!isIncluded) |
- { |
- excluded.add(filter); |
- } |
- else |
- { |
- let {selector} = filter; |
- if ((excluded.size == 0 || !excluded.has(filter)) && |
- !ElemHideExceptions.getException(selector, domain)) |
- { |
- selectors.push(selector); |
- } |
- } |
- } |
- } |
- |
- if (currentDomain == "") |
- break; |
- |
- let nextDot = currentDomain.indexOf("."); |
- currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); |
- } |
+ /** |
+ * Generates a style sheet for a given domain based on the current set of |
+ * filters. |
+ * |
+ * @param {string} domain The domain. |
+ * @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)); |
if (!specificOnly) |
selectors = getUnconditionalSelectors().concat(selectors); |
- return selectors; |
+ return {code, selectors}; |
} |
}; |
/** |
* Splits a list of selectors into groups determined by the value of |
* <code>{@link selectorGroupSize}</code>. |
* |
* @param {Array.<string>} selectors |