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