| Index: lib/elemHide.js | 
| =================================================================== | 
| --- a/lib/elemHide.js | 
| +++ b/lib/elemHide.js | 
| @@ -16,46 +16,48 @@ | 
| */ | 
|  | 
| "use strict"; | 
|  | 
| /** | 
| * @fileOverview Element hiding implementation. | 
| */ | 
|  | 
| +const {Filter} = require("./filterClasses"); | 
| const {ElemHideExceptions} = require("./elemHideExceptions"); | 
| const {filterNotifier} = require("./filterNotifier"); | 
| const {normalizeHostname, domainSuffixes} = require("./url"); | 
| const {Cache} = require("./caching"); | 
|  | 
| /** | 
| * The maximum number of selectors in a CSS rule. This is used by | 
| * <code>{@link createStyleSheet}</code> to split up a long list of selectors | 
| * into multiple rules. | 
| * @const {number} | 
| * @default | 
| */ | 
| const selectorGroupSize = 1024; | 
|  | 
| /** | 
| - * Lookup table, active flag, by filter by domain. | 
| - * (Only contains filters that aren't unconditionally matched for all domains.) | 
| - * @type {Map.<string,Map.<Filter,boolean>>} | 
| + * Lookup table, selector by filter text by domain. | 
| + * (Only contains filter text for filters that aren't unconditionally matched | 
| + * for all domains.) | 
| + * @type {Map.<string,Map.<string,string>>} | 
| */ | 
| -let filtersByDomain = new Map(); | 
| +let filterTextByDomain = new Map(); | 
|  | 
| /** | 
| - * Lookup table, filter by selector. (Only used for selectors that are | 
| + * Lookup table, filter text by selector. (Only used for selectors that are | 
| * unconditionally matched for all domains.) | 
| - * @type {Map.<string,Filter>} | 
| + * @type {Map.<string,string>} | 
| */ | 
| -let filterBySelector = new Map(); | 
| +let filterTextBySelector = new Map(); | 
|  | 
| /** | 
| - * This array caches the keys of filterBySelector table (selectors | 
| + * This array caches the keys of filterTextBySelector 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 | 
| @@ -84,36 +86,36 @@ | 
| /** | 
| * 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>} | 
| + * Set containing text of known element hiding filters | 
| + * @type {Set.<string>} | 
| */ | 
| -let knownFilters = new Set(); | 
| +let knownFilterText = 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) && | 
| +  while (domain && !filterTextByDomain.has(domain) && | 
| !knownExceptionDomains.has(domain)) | 
| { | 
| let index = domain.indexOf("."); | 
| domain = index == -1 ? "" : domain.substring(index + 1); | 
| } | 
|  | 
| return domain; | 
| } | 
| @@ -126,31 +128,31 @@ | 
| function addToFiltersByDomain(filter, domains = filter.domains) | 
| { | 
| for (let [domain, isIncluded] of domains || defaultDomains) | 
| { | 
| // There's no need to note that a filter is generically disabled. | 
| if (!isIncluded && domain == "") | 
| continue; | 
|  | 
| -    let filters = filtersByDomain.get(domain); | 
| +    let filters = filterTextByDomain.get(domain); | 
| if (!filters) | 
| -      filtersByDomain.set(domain, filters = new Map()); | 
| -    filters.set(filter, isIncluded); | 
| +      filterTextByDomain.set(domain, filters = new Map()); | 
| +    filters.set(filter.text, isIncluded ? filter.selector : null); | 
| } | 
| } | 
|  | 
| /** | 
| * Returns a list of selectors that apply on each website unconditionally. | 
| * @returns {string[]} | 
| */ | 
| function getUnconditionalSelectors() | 
| { | 
| if (!unconditionalSelectors) | 
| -    unconditionalSelectors = [...filterBySelector.keys()]; | 
| +    unconditionalSelectors = [...filterTextBySelector.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. | 
| * | 
| @@ -163,33 +165,29 @@ | 
| function getConditionalSelectors(domain, specificOnly) | 
| { | 
| let selectors = []; | 
|  | 
| let excluded = new Set(); | 
|  | 
| for (let currentDomain of domainSuffixes(domain, !specificOnly)) | 
| { | 
| -    let filters = filtersByDomain.get(currentDomain); | 
| +    let filters = filterTextByDomain.get(currentDomain); | 
| if (filters) | 
| { | 
| -      for (let [filter, isIncluded] of filters) | 
| +      for (let [text, selector] of filters) | 
| { | 
| -        if (!isIncluded) | 
| +        if (!selector) | 
| { | 
| -          excluded.add(filter); | 
| +          excluded.add(text); | 
| } | 
| -        else | 
| +        else if ((excluded.size == 0 || !excluded.has(text)) && | 
| +                 !ElemHideExceptions.getException(selector, domain)) | 
| { | 
| -          let {selector} = filter; | 
| -          if ((excluded.size == 0 || !excluded.has(filter)) && | 
| -              !ElemHideExceptions.getException(selector, domain)) | 
| -          { | 
| -            selectors.push(selector); | 
| -          } | 
| +          selectors.push(selector); | 
| } | 
| } | 
| } | 
| } | 
|  | 
| return selectors; | 
| } | 
|  | 
| @@ -209,28 +207,27 @@ | 
| { | 
| let selectors = []; | 
| let exceptions = []; | 
|  | 
| let excluded = new Set(); | 
|  | 
| for (let currentDomain of domainSuffixes(domain, !specificOnly)) | 
| { | 
| -    let filters = filtersByDomain.get(currentDomain); | 
| +    let filters = filterTextByDomain.get(currentDomain); | 
| if (filters) | 
| { | 
| -      for (let [filter, isIncluded] of filters) | 
| +      for (let [text, selector] of filters) | 
| { | 
| -        if (!isIncluded) | 
| +        if (!selector) | 
| { | 
| -          excluded.add(filter); | 
| +          excluded.add(text); | 
| } | 
| -        else if (excluded.size == 0 || !excluded.has(filter)) | 
| +        else if (excluded.size == 0 || !excluded.has(text)) | 
| { | 
| -          let {selector} = filter; | 
| let exception = ElemHideExceptions.getException(selector, domain); | 
|  | 
| if (exception) | 
| exceptions.push(exception); | 
| else | 
| selectors.push(selector); | 
| } | 
| } | 
| @@ -299,21 +296,22 @@ | 
| // 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); | 
| +  let unconditionalFilterForSelector = filterTextBySelector.get(selector); | 
| if (unconditionalFilterForSelector) | 
| { | 
| -    addToFiltersByDomain(unconditionalFilterForSelector); | 
| -    filterBySelector.delete(selector); | 
| +    addToFiltersByDomain(Filter.fromText(unconditionalFilterForSelector, | 
| +                                         false)); | 
| +    filterTextBySelector.delete(selector); | 
| unconditionalSelectors = null; | 
| defaultStyleSheet = null; | 
| } | 
| }); | 
|  | 
| /** | 
| * Container for element hiding filters | 
| * @class | 
| @@ -321,98 +319,103 @@ | 
| exports.ElemHide = { | 
| /** | 
| * Removes all known filters | 
| */ | 
| clear() | 
| { | 
| commonStyleSheet = null; | 
|  | 
| -    for (let collection of [styleSheetCache, filtersByDomain, filterBySelector, | 
| -                            knownFilters, knownExceptionDomains]) | 
| +    for (let collection of [styleSheetCache, filterTextByDomain, | 
| +                            filterTextBySelector, knownFilterText, | 
| +                            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)) | 
| +    let {text} = filter; | 
| + | 
| +    if (knownFilterText.has(text)) | 
| return; | 
|  | 
| styleSheetCache.clear(); | 
| commonStyleSheet = null; | 
|  | 
| let {domains, selector} = filter; | 
|  | 
| if (!(domains || ElemHideExceptions.hasExceptions(selector))) | 
| { | 
| // The new filter's selector is unconditionally applied to all domains | 
| -      filterBySelector.set(selector, filter); | 
| +      filterTextBySelector.set(selector, text); | 
| unconditionalSelectors = null; | 
| defaultStyleSheet = null; | 
| } | 
| else | 
| { | 
| // The new filter's selector only applies to some domains | 
| addToFiltersByDomain(filter, domains); | 
| } | 
|  | 
| -    knownFilters.add(filter); | 
| +    knownFilterText.add(text); | 
| filterNotifier.emit("elemhideupdate"); | 
| }, | 
|  | 
| /** | 
| * Removes an element hiding filter | 
| * @param {ElemHideFilter} filter | 
| */ | 
| remove(filter) | 
| { | 
| -    if (!knownFilters.has(filter)) | 
| +    let {text} = filter; | 
| + | 
| +    if (!knownFilterText.has(text)) | 
| return; | 
|  | 
| styleSheetCache.clear(); | 
| commonStyleSheet = null; | 
|  | 
| let {selector} = filter; | 
|  | 
| // Unconditially applied element hiding filters | 
| -    if (filterBySelector.get(selector) == filter) | 
| +    if (filterTextBySelector.get(selector) == text) | 
| { | 
| -      filterBySelector.delete(selector); | 
| +      filterTextBySelector.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); | 
| +        let filters = filterTextByDomain.get(domain); | 
| if (filters) | 
| { | 
| -          filters.delete(filter); | 
| +          filters.delete(text); | 
|  | 
| if (filters.size == 0) | 
| -            filtersByDomain.delete(domain); | 
| +            filterTextByDomain.delete(domain); | 
| } | 
| } | 
| } | 
|  | 
| -    knownFilters.delete(filter); | 
| +    knownFilterText.delete(text); | 
| filterNotifier.emit("elemhideupdate"); | 
| }, | 
|  | 
| /** | 
| * @typedef {object} ElemHideStyleSheet | 
| * @property {string} code CSS code. | 
| * @property {Array.<string>} selectors List of selectors. | 
| */ | 
|  |