 Issue 29349187:
  Issue 4167 - getSelectorsForDomain criteria + keys  (Closed)
    
  
    Issue 29349187:
  Issue 4167 - getSelectorsForDomain criteria + keys  (Closed) 
  | Index: lib/elemHide.js | 
| diff --git a/lib/elemHide.js b/lib/elemHide.js | 
| index 2c81a2324f579247d53eca5eeb6ac459210a937a..5c43b774fa270726ccffd7fbac760134b1fa8574 100644 | 
| --- a/lib/elemHide.js | 
| +++ b/lib/elemHide.js | 
| @@ -36,14 +36,6 @@ var filterByKey = []; | 
| var keyByFilter = Object.create(null); | 
| /** | 
| - * Indicates whether we are using the getSelectorsForDomain function and | 
| - * therefore mainting the required filtersByDomain, filtersBySelector and | 
| - * unconditionalSelectors lookups. (Will be false for Firefox) | 
| - * @type Boolean | 
| - */ | 
| -var usingGetSelectorsForDomain = !("nsIStyleSheetService" in Ci); | 
| - | 
| -/** | 
| * Nested lookup table, filter (or false if inactive) by filter key by domain. | 
| * (Only contains filters that aren't unconditionally matched for all domains.) | 
| * @type Object | 
| @@ -51,19 +43,27 @@ var usingGetSelectorsForDomain = !("nsIStyleSheetService" in Ci); | 
| var filtersByDomain = Object.create(null); | 
| /** | 
| - * Lookup table, filters by selector. (Only contains filters that have a | 
| + * Lookup table, filter keys by selector. (Only contains filters that have a | 
| * selector that is unconditionally matched for all domains.) | 
| */ | 
| -var filtersBySelector = Object.create(null); | 
| +var filterKeysBySelector = Object.create(null); | 
| /** | 
| - * This array caches the keys of filtersBySelector table (selectors which | 
| + * This array caches the keys of filterKeysBySelector table (selectors which | 
| * unconditionally apply on all domains). It will be null if the cache needs to | 
| * be rebuilt. | 
| */ | 
| var unconditionalSelectors = null; | 
| /** | 
| + * This array caches the values of filterKeysBySelector table (filterIds for | 
| + * selectors which unconditionally apply on all domains). It will be null if the | 
| + * cache needs to be rebuilt. Note: Only the first filter key for each selector | 
| + * is cached. | 
| + */ | 
| +var unconditionalFilterKeys = null; | 
| + | 
| +/** | 
| * Object to be used instead when a filter has a blank domains property. | 
| */ | 
| var defaultDomains = Object.create(null); | 
| @@ -95,16 +95,15 @@ var ElemHide = exports.ElemHide = | 
| filterByKey = []; | 
| keyByFilter = Object.create(null); | 
| filtersByDomain = Object.create(null); | 
| - filtersBySelector = Object.create(null); | 
| - unconditionalSelectors = null; | 
| + filterKeysBySelector = Object.create(null); | 
| + unconditionalSelectors = unconditionalFilterKeys = null; | 
| knownExceptions = Object.create(null); | 
| exceptions = Object.create(null); | 
| FilterNotifier.emit("elemhideupdate"); | 
| }, | 
| - _addToFiltersByDomain: function(filter) | 
| + _addToFiltersByDomain: function(key, filter) | 
| { | 
| - let key = keyByFilter[filter.text]; | 
| let domains = filter.domains || defaultDomains; | 
| for (let domain in domains) | 
| { | 
| @@ -135,19 +134,16 @@ var ElemHide = exports.ElemHide = | 
| exceptions[selector] = []; | 
| exceptions[selector].push(filter); | 
| - if (usingGetSelectorsForDomain) | 
| + // If this is the first exception for a previously unconditionally | 
| + // applied element hiding selector we need to take care to update the | 
| + // lookups. | 
| + let filterKeys = filterKeysBySelector[selector]; | 
| + if (filterKeys) | 
| { | 
| - // If this is the first exception for a previously unconditionally | 
| - // applied element hiding selector we need to take care to update the | 
| - // lookups. | 
| - let unconditionalFilters = filtersBySelector[selector]; | 
| - if (unconditionalFilters) | 
| - { | 
| - for (let f of unconditionalFilters) | 
| - this._addToFiltersByDomain(f); | 
| - delete filtersBySelector[selector]; | 
| - unconditionalSelectors = null; | 
| - } | 
| + for (let filterKey of filterKeys) | 
| + this._addToFiltersByDomain(filterKey, filterByKey[filterKey]); | 
| + delete filterKeysBySelector[selector]; | 
| + unconditionalSelectors = unconditionalFilterKeys = null; | 
| } | 
| knownExceptions[filter.text] = true; | 
| @@ -160,33 +156,64 @@ var ElemHide = exports.ElemHide = | 
| let key = filterByKey.push(filter) - 1; | 
| keyByFilter[filter.text] = key; | 
| - if (usingGetSelectorsForDomain) | 
| + if (!(filter.domains || filter.selector in exceptions)) | 
| { | 
| - if (!(filter.domains || filter.selector in exceptions)) | 
| + // The new filter's selector is unconditionally applied to all domains | 
| + let filterKeys = filterKeysBySelector[filter.selector]; | 
| + if (filterKeys) | 
| { | 
| - // The new filter's selector is unconditionally applied to all domains | 
| - let filters = filtersBySelector[filter.selector]; | 
| - if (filters) | 
| - { | 
| - filters.push(filter); | 
| - } | 
| - else | 
| - { | 
| - filtersBySelector[filter.selector] = [filter]; | 
| - unconditionalSelectors = null; | 
| - } | 
| + filterKeys.push(key); | 
| } | 
| else | 
| { | 
| - // The new filter's selector only applies to some domains | 
| - this._addToFiltersByDomain(filter); | 
| + filterKeysBySelector[filter.selector] = [key]; | 
| + unconditionalSelectors = unconditionalFilterKeys = null; | 
| } | 
| } | 
| + else | 
| + { | 
| + // The new filter's selector only applies to some domains | 
| + this._addToFiltersByDomain(key, filter); | 
| + } | 
| } | 
| FilterNotifier.emit("elemhideupdate"); | 
| }, | 
| + _removeFilterKey: function(key, filter) | 
| + { | 
| + let filterKeys = filterKeysBySelector[filter.selector]; | 
| + if (filterKeys) | 
| + { | 
| + let index = filterKeys.indexOf(key); | 
| + if (index >= 0) | 
| 
kzar
2016/09/20 18:37:35
It kind of bugs me how we check index twice, also
 | 
| + { | 
| + if (filterKeys.length > 1) | 
| + { | 
| + filterKeys.splice(index, 1); | 
| + if (index == 0) | 
| + unconditionalFilterKeys = null; | 
| + } | 
| + else | 
| + { | 
| + delete filterKeysBySelector[filter.selector]; | 
| + unconditionalSelectors = unconditionalFilterKeys = null; | 
| + } | 
| + return; | 
| + } | 
| + } | 
| + | 
| + // We haven't found this filter in unconditional filters, look in | 
| + // filtersByDomain. | 
| + let domains = filter.domains || defaultDomains; | 
| + for (let domain in domains) | 
| + { | 
| + let filters = filtersByDomain[domain]; | 
| + if (filters) | 
| + delete filters[key]; | 
| + } | 
| + }, | 
| + | 
| /** | 
| * Removes an element hiding filter | 
| * @param {ElemHideFilter} filter | 
| @@ -212,34 +239,7 @@ var ElemHide = exports.ElemHide = | 
| let key = keyByFilter[filter.text]; | 
| delete filterByKey[key]; | 
| delete keyByFilter[filter.text]; | 
| - | 
| - if (usingGetSelectorsForDomain) | 
| - { | 
| - let filters = filtersBySelector[filter.selector]; | 
| - if (filters) | 
| - { | 
| - if (filters.length > 1) | 
| - { | 
| - let index = filters.indexOf(filter); | 
| - filters.splice(index, 1); | 
| - } | 
| - else | 
| - { | 
| - delete filtersBySelector[filter.selector]; | 
| - unconditionalSelectors = null; | 
| - } | 
| - } | 
| - else | 
| - { | 
| - let domains = filter.domains || defaultDomains; | 
| - for (let domain in domains) | 
| - { | 
| - let filters = filtersByDomain[domain]; | 
| - if (filters) | 
| - delete filters[key]; | 
| - } | 
| - } | 
| - } | 
| + this._removeFilterKey(key, filter); | 
| } | 
| FilterNotifier.emit("elemhideupdate"); | 
| @@ -265,7 +265,7 @@ var ElemHide = exports.ElemHide = | 
| /** | 
| * Retrieves an element hiding filter by the corresponding protocol key | 
| */ | 
| - getFilterByKey: function(/**String*/ key) /**Filter*/ | 
| + getFilterByKey: function(/**Number*/ key) /**Filter*/ | 
| { | 
| return (key in filterByKey ? filterByKey[key] : null); | 
| }, | 
| @@ -298,18 +298,75 @@ var ElemHide = exports.ElemHide = | 
| }, | 
| /** | 
| - * Returns a list of all selectors active on a particular domain, must not be | 
| - * used in Firefox (when usingGetSelectorsForDomain is false). | 
| + * Returns a list of selectors that apply on each website unconditionally. | 
| + * @returns {String[]} | 
| */ | 
| - getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly) | 
| + getUnconditionalSelectors: function() | 
| { | 
| - if (!usingGetSelectorsForDomain) | 
| - throw new Error("getSelectorsForDomain can not be used in Firefox!"); | 
| - | 
| if (!unconditionalSelectors) | 
| - unconditionalSelectors = Object.keys(filtersBySelector); | 
| - let selectors = specificOnly ? [] : unconditionalSelectors.slice(); | 
| + unconditionalSelectors = Object.keys(filterKeysBySelector); | 
| + return unconditionalSelectors.slice(); | 
| + }, | 
| + | 
| + /** | 
| + * Returns a list of all selectors active on a particular domain. | 
| + * Returns a list of filterKeys for selectors that apply on each website | 
| + * unconditionally. | 
| + * @returns {Number[]} | 
| + */ | 
| + getUnconditionalFilterKeys: function() | 
| + { | 
| + if (!unconditionalFilterKeys) | 
| + { | 
| + let selectors = this.getUnconditionalSelectors(); | 
| + unconditionalFilterKeys = []; | 
| + for (let selector of selectors) | 
| + unconditionalFilterKeys.push(filterKeysBySelector[selector][0]); | 
| + } | 
| + return unconditionalFilterKeys.slice(); | 
| + }, | 
| + | 
| + /** | 
| + * Constant used by getSelectorsForDomain's when matching all selectors. | 
| + */ | 
| + ALL_MATCHING: 0, | 
| + | 
| + /** | 
| + * Constant used by getSelectorsForDomain's when not matching unconditional | 
| + * selectors. | 
| + */ | 
| + NO_UNCONDITIONAL: 1, | 
| + | 
| + /** | 
| + * Constant used by getSelectorsForDomain's when only matching specific | 
| + * selectors. | 
| + */ | 
| + SPECIFIC_ONLY: 2, | 
| + | 
| + /** | 
| + * Returns a list of all selectors active on a particular domain. Optionally | 
| + * a list of corresponding filter keys for the selectors can be returned too. | 
| + * The optional criteria parameter restricts the results, and must have the | 
| + * value of ALL_MATCHING, NO_UNCONDITIONAL or SPECIFIC_ONLY if provided. | 
| 
Wladimir Palant
2016/09/26 14:56:54
Nit: You are documenting return value and paramete
 
kzar
2016/09/27 12:40:22
Done.
 | 
| + */ | 
| + getSelectorsForDomain: function(/**String*/ domain, | 
| + /**Number*/ criteria, | 
| + /**Boolean*/ provideFilterKeys) | 
| + { | 
| + let filterKeys = []; | 
| + let selectors = []; | 
| + | 
| + if (typeof criteria == "undefined") | 
| + criteria = ElemHide.ALL_MATCHING; | 
| + if (criteria < ElemHide.NO_UNCONDITIONAL) | 
| + { | 
| + selectors = this.getUnconditionalSelectors(); | 
| + if (provideFilterKeys) | 
| + filterKeys = this.getUnconditionalFilterKeys(); | 
| + } | 
| + | 
| + let specificOnly = (criteria >= ElemHide.SPECIFIC_ONLY); | 
| let seenFilters = Object.create(null); | 
| let currentDomain = domain ? domain.toUpperCase() : ""; | 
| while (true) | 
| @@ -328,7 +385,11 @@ var ElemHide = exports.ElemHide = | 
| let filter = filters[filterKey]; | 
| if (filter && !this.getException(filter, domain)) | 
| + { | 
| selectors.push(filter.selector); | 
| + // It is faster to always push the key, even if not required. | 
| + filterKeys.push(filterKey); | 
| + } | 
| } | 
| } | 
| @@ -339,6 +400,9 @@ var ElemHide = exports.ElemHide = | 
| currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); | 
| } | 
| - return selectors; | 
| + if (provideFilterKeys) | 
| + return [selectors, filterKeys]; | 
| + else | 
| + return selectors; | 
| } | 
| }; |