| Index: lib/elemHide.js |
| diff --git a/lib/elemHide.js b/lib/elemHide.js |
| index 574ef91dff6340a1e9408571fef1b31726cec78c..ee309d1d4a823b798804f41693dc97c22236d598 100644 |
| --- a/lib/elemHide.js |
| +++ b/lib/elemHide.js |
| @@ -40,17 +40,32 @@ var filterByKey = []; |
| var keyByFilter = Object.create(null); |
| /** |
| - * Nested lookup table, filter (or false if inactive) by filter text by domain |
| + * 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 |
| */ |
| var filtersByDomain = Object.create(null); |
| /** |
| - * Indicates whether we are using (and maintaining) the filtersByDomain lookup. |
| - * (Will be false for Firefox) |
| - * @type Boolean |
| + * Lookup table, filters by selector. (Only contains filters that have a |
| + * selector that is unconditionally matched for all domains.) |
| */ |
| -var usingFiltersByDomain = !("nsIStyleSheetService" in Ci); |
| +var filtersBySelector = Object.create(null); |
| + |
| +/** |
| + * This array caches the keys of filtersBySelector table (selectors which |
| + * unconditionally apply on all domains). It will be null if the cache needs to |
| + * be rebuilt. |
| + */ |
| +var unconditionalSelectors = null; |
| /** |
| * Object to be used instead when a filter has a blank domains property. |
| @@ -119,12 +134,31 @@ var ElemHide = exports.ElemHide = |
| filterByKey = []; |
| keyByFilter = Object.create(null); |
| filtersByDomain = Object.create(null); |
| + filtersBySelector = Object.create(null); |
| + unconditionalSelectors = null; |
| knownExceptions = Object.create(null); |
| exceptions = Object.create(null); |
| ElemHide.isDirty = false; |
| ElemHide.unapply(); |
| }, |
| + _addToFiltersByDomain: function(filter) |
| + { |
| + let key = keyByFilter[filter.text]; |
| + let domains = filter.domains || defaultDomains; |
| + for (let domain in domains) |
| + { |
| + let filters = filtersByDomain[domain]; |
| + if (!filters) |
| + filters = filtersByDomain[domain] = Object.create(null); |
| + |
| + if (domains[domain]) |
| + filters[key] = filter; |
| + else |
| + filters[key] = false; |
| + } |
| + }, |
| + |
| /** |
| * Add a new element hiding filter |
| * @param {ElemHideFilter} filter |
| @@ -140,6 +174,22 @@ var ElemHide = exports.ElemHide = |
| if (!(selector in exceptions)) |
| 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 unconditionalFilters = filtersBySelector[selector]; |
| + if (unconditionalFilters) |
| + { |
| + for (let f of unconditionalFilters) |
| + this._addToFiltersByDomain(f); |
| + delete filtersBySelector[selector]; |
| + unconditionalSelectors = null; |
| + } |
| + } |
| + |
| knownExceptions[filter.text] = true; |
| } |
| else |
| @@ -150,19 +200,26 @@ var ElemHide = exports.ElemHide = |
| let key = filterByKey.push(filter) - 1; |
| keyByFilter[filter.text] = key; |
| - if (usingFiltersByDomain) |
| + if (usingGetSelectorsForDomain) |
| { |
| - let domains = filter.domains || defaultDomains; |
| - for (let domain in domains) |
| + if (!(filter.domains || filter.selector in exceptions)) |
| { |
| - let filters = filtersByDomain[domain]; |
| - if (!filters) |
| - filters = filtersByDomain[domain] = Object.create(null); |
| - |
| - if (domains[domain]) |
| - filters[filter.text] = filter; |
| + // The new filter's selector is unconditionally applied to all domains |
| + let filters = filtersBySelector[filter.selector]; |
| + if (filters) |
| + { |
| + filters.push(filter); |
| + } |
| else |
| - filters[filter.text] = false; |
| + { |
| + filtersBySelector[filter.selector] = [filter]; |
| + unconditionalSelectors = null; |
| + } |
| + } |
| + else |
| + { |
| + // The new filter's selector only applies to some domains |
| + this._addToFiltersByDomain(filter); |
| } |
| } |
| @@ -197,14 +254,31 @@ var ElemHide = exports.ElemHide = |
| delete keyByFilter[filter.text]; |
| ElemHide.isDirty = true; |
| - if (usingFiltersByDomain) |
| + if (usingGetSelectorsForDomain) |
| { |
| - let domains = filter.domains || defaultDomains; |
| - for (let domain in domains) |
| + let filters = filtersBySelector[filter.selector]; |
| + if (filters) |
| { |
| - let filters = filtersByDomain[domain]; |
| - if (filters) |
| - delete filters[filter.text]; |
| + 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]; |
| + } |
| } |
| } |
| } |
| @@ -416,14 +490,16 @@ var ElemHide = exports.ElemHide = |
| /** |
| * Returns a list of all selectors active on a particular domain, must not be |
| - * used in Firefox (when usingFiltersByDomain is false). |
| + * used in Firefox (when usingGetSelectorsForDomain is false). |
| */ |
| getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly) |
| { |
| - if (!usingFiltersByDomain) |
| + if (!usingGetSelectorsForDomain) |
| throw new Error("getSelectorsForDomain can not be used in Firefox!"); |
| - let selectors = []; |
| + if (!unconditionalSelectors) |
| + unconditionalSelectors = Object.keys(filtersBySelector); |
| + let selectors = specificOnly ? [] : unconditionalSelectors.slice(); |
| let seenFilters = Object.create(null); |
| let currentDomain = domain ? domain.toUpperCase() : ""; |
| @@ -435,13 +511,13 @@ var ElemHide = exports.ElemHide = |
| let filters = filtersByDomain[currentDomain]; |
| if (filters) |
| { |
| - for (let filterText in filters) |
| + for (let filterKey in filters) |
| { |
| - if (filterText in seenFilters) |
| + if (filterKey in seenFilters) |
| continue; |
| - seenFilters[filterText] = true; |
| + seenFilters[filterKey] = true; |
| - let filter = filters[filterText]; |
| + let filter = filters[filterKey]; |
| if (filter && !this.getException(filter, domain)) |
| selectors.push(filter.selector); |
| } |