| Index: lib/elemHide.js |
| =================================================================== |
| --- a/lib/elemHide.js |
| +++ b/lib/elemHide.js |
| @@ -58,40 +58,93 @@ |
| /** |
| * 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 all known domains |
| + * @type {Set.<string>} |
| + */ |
| +let knownDomains = new Set(); |
| + |
| +/** |
| * Set containing known element hiding filters |
| * @type {Set.<ElemHideFilter>} |
| */ |
| let knownFilters = 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) |
| +{ |
| + if (!domain) |
| + return ""; |
| + |
| + if (domain[domain.length - 1] == ".") |
| + domain = domain.replace(/\.+$/, ""); |
| + |
| + domain = domain.toLowerCase(); |
| + |
| + while (domain) |
| + { |
| + if (knownDomains.has(domain) || ElemHideExceptions.isKnownDomain(domain)) |
| + break; |
| + |
| + 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) |
| { |
| // There's no need to note that a filter is generically disabled. |
| - if (!isIncluded && domain == "") |
| - continue; |
| + if (domain == "") |
| + { |
| + if (!isIncluded) |
| + continue; |
| + } |
| + else |
| + { |
| + // Note: Once a domain is known it never becomes unknown, even when all |
| + // the filters containing that domain are removed. This is a best-case |
| + // optimization. |
| + knownDomains.add(domain); |
| + } |
| let filters = filtersByDomain.get(domain); |
| if (!filters) |
| filtersByDomain.set(domain, filters = new Map()); |
| filters.set(filter, isIncluded); |
| } |
| } |
| @@ -117,17 +170,17 @@ |
| * |
| * @returns {Array.<string>} The list of selectors. |
| */ |
| function getConditionalSelectorsForDomain(domain, specificOnly = false) |
| { |
| 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 +222,33 @@ |
| function getDefaultStyleSheet() |
| { |
| if (!defaultStyleSheet) |
| defaultStyleSheet = createStyleSheet(getUnconditionalSelectors()); |
| return defaultStyleSheet; |
| } |
| +/** |
| + * Returns the default style sheet that applies on all unknown domains. |
| + * @returns {string} |
| + */ |
| +function getCommonStyleSheet() |
| +{ |
| + if (!commonStyleSheet) |
| + commonStyleSheet = getDefaultStyleSheet() + |
| + createStyleSheet(getConditionalSelectorsForDomain("")); |
| + |
| + return commonStyleSheet; |
| +} |
| + |
| ElemHideExceptions.on("added", ({selector}) => |
| { |
| + commonStyleSheet = null; |
| + |
| // 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,17 +261,20 @@ |
| * @class |
| */ |
| exports.ElemHide = { |
| /** |
| * Removes all known filters |
| */ |
| clear() |
| { |
| - for (let collection of [filtersByDomain, filterBySelector, knownFilters]) |
| + commonStyleSheet = null; |
| + |
| + for (let collection of [filtersByDomain, filterBySelector, knownDomains, |
| + knownFilters]) |
| collection.clear(); |
| unconditionalSelectors = null; |
| defaultStyleSheet = null; |
| filterNotifier.emit("elemhideupdate"); |
| }, |
| @@ -211,16 +282,18 @@ |
| * 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 +312,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,19 +363,22 @@ |
| * @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 knownSuffix = getKnownSuffix(domain); |
| + |
| + let selectors = getConditionalSelectorsForDomain(knownSuffix, specificOnly); |
| let code = specificOnly ? createStyleSheet(selectors) : |
| - (getDefaultStyleSheet() + createStyleSheet(selectors)); |
| + knownSuffix == "" ? getCommonStyleSheet() : |
| + (getDefaultStyleSheet() + createStyleSheet(selectors)); |
| if (!specificOnly) |
| selectors = getUnconditionalSelectors().concat(selectors); |
| return {code, selectors}; |
| } |
| }; |