Index: lib/elemHide.js |
=================================================================== |
--- a/lib/elemHide.js |
+++ b/lib/elemHide.js |
@@ -25,26 +25,26 @@ let {Utils} = require("utils"); |
let {IO} = require("io"); |
let {Prefs} = require("prefs"); |
let {ElemHideException} = require("filterClasses"); |
let {FilterNotifier} = require("filterNotifier"); |
let {AboutHandler} = require("elemHideHitRegistration"); |
let {TimeLine} = require("timeline"); |
/** |
- * Lookup table, filters by their associated key |
+ * Array of filters, index as "key". |
+ * @type Array |
+ */ |
+let filters = []; |
+/** |
+ * Lookup table, keys of the filters by filter text. |
+ * Can be null. |
* @type Object |
*/ |
-let filterByKey = {__proto__: null}; |
- |
-/** |
- * Lookup table, keys of the filters by filter text |
- * @type Object |
- */ |
-let keyByFilter = {__proto__: null}; |
+let keyByFilter = new Map(); |
/** |
* Lookup table, keys are known element hiding exceptions |
* @type Object |
*/ |
let knownExceptions = {__proto__: null}; |
/** |
@@ -55,16 +55,22 @@ let exceptions = {__proto__: null}; |
/** |
* Currently applied stylesheet URL |
* @type nsIURI |
*/ |
let styleURL = null; |
/** |
+ * Filters removed since last compact operation |
+ * @type number |
+ */ |
+let filtersRemoved = 0; |
+ |
+/** |
* Element hiding component |
* @class |
*/ |
let ElemHide = exports.ElemHide = |
{ |
/** |
* Indicates whether filters have been added or removed since the last apply() call. |
* @type Boolean |
@@ -103,18 +109,19 @@ let ElemHide = exports.ElemHide = |
TimeLine.leave("ElemHide.init() done"); |
}, |
/** |
* Removes all known filters |
*/ |
clear: function() |
{ |
- filterByKey = {__proto__: null}; |
- keyByFilter = {__proto__: null}; |
+ dump("clear\n"); |
+ keyByFilter = null; |
+ filters = []; |
knownExceptions = {__proto__: null}; |
exceptions = {__proto__: null}; |
ElemHide.isDirty = false; |
ElemHide.unapply(); |
}, |
/** |
* Add a new element hiding filter |
@@ -130,26 +137,24 @@ let ElemHide = exports.ElemHide = |
let selector = filter.selector; |
if (!(selector in exceptions)) |
exceptions[selector] = []; |
exceptions[selector].push(filter); |
knownExceptions[filter.text] = true; |
} |
else |
{ |
- if (filter.text in keyByFilter) |
+ this._ensureFilterMap(); |
+ |
+ if (keyByFilter.has(filter.text)) |
return; |
- let key; |
- do { |
- key = Math.random().toFixed(15).substr(5); |
- } while (key in filterByKey); |
+ filters.push(filter); |
+ keyByFilter.set(filter.text, filters.length - 1); |
- filterByKey[key] = filter; |
- keyByFilter[filter.text] = key; |
ElemHide.isDirty = true; |
} |
}, |
/** |
* Removes an element hiding filter |
* @param {ElemHideFilter} filter |
*/ |
@@ -163,27 +168,71 @@ let ElemHide = exports.ElemHide = |
let list = exceptions[filter.selector]; |
let index = list.indexOf(filter); |
if (index >= 0) |
list.splice(index, 1); |
delete knownExceptions[filter.text]; |
} |
else |
{ |
- if (!(filter.text in keyByFilter)) |
+ this._ensureFilterMap(); |
+ |
+ let key = keyByFilter.get(filter.text); |
+ if (key === undefined) |
return; |
- let key = keyByFilter[filter.text]; |
- delete filterByKey[key]; |
- delete keyByFilter[filter.text]; |
+ keyByFilter.delete(filter.text); |
+ filters[key] = null; |
ElemHide.isDirty = true; |
+ filtersRemoved += 1; |
} |
}, |
/** |
+ * Clean up and compact memory after filters |
+ * are removed. |
+ */ |
+ compact: function() |
+ { |
+ if (filtersRemoved == 0) { |
+ return; |
+ } |
+ |
+ if (filtersRemoved == 1) { |
+ for (let i = 0; i < filters; i++) { |
+ if (filters[i] === null) |
+ filters.splice(i, 1); |
+ } |
+ } else { |
+ let newList = []; |
+ for (let filter in filters) { |
+ if (filter) |
+ newList.push(filter); |
+ } |
+ filters = newList; |
+ } |
+ |
+ filtersRemoved = 0; |
+ }, |
+ |
+ /** |
+ * Ensure that we have a map of filter text to key. |
+ * Might have to recreate it from the array of filters. |
+ */ |
+ _ensureFilterMap: function() |
+ { |
+ if (keyByFilter) |
+ return; |
+ |
+ keyByFilter = new Map(); |
+ for (let i = 0; i < filters.length; i++) |
+ keyByFilter.set(filters[i].text, i); |
+ }, |
+ |
+ /** |
* Checks whether an exception rule is registered for a filter on a particular |
* domain. |
*/ |
getException: function(/**Filter*/ filter, /**String*/ docDomain) /**ElemHideException*/ |
{ |
let selector = filter.selector; |
if (!(filter.selector in exceptions)) |
return null; |
@@ -298,23 +347,27 @@ let ElemHide = exports.ElemHide = |
this._applying = true; |
TimeLine.leave("ElemHide.apply() done", "ElemHideWrite"); |
}, |
_generateCSSContent: function() |
{ |
+ // We don't need keyByFilter anymore now, if we need it again, |
+ // e.g. because of a subscription change, we will regenerate it. |
+ keyByFilter = null; |
+ |
// Grouping selectors by domains |
TimeLine.log("start grouping selectors"); |
let domains = {__proto__: null}; |
let hasFilters = false; |
- for (let key in filterByKey) |
+ for (let key = 0; key < filters.length; key++) |
{ |
- let filter = filterByKey[key]; |
+ let filter = filters[key]; |
let domain = filter.selectorDomain || ""; |
let list; |
if (domain in domains) |
list = domains[domain]; |
else |
{ |
list = {__proto__: null}; |
@@ -381,29 +434,31 @@ let ElemHide = exports.ElemHide = |
*/ |
get styleURL() ElemHide.applied ? styleURL.spec : null, |
/** |
* Retrieves an element hiding filter by the corresponding protocol key |
*/ |
getFilterByKey: function(/**String*/ key) /**Filter*/ |
{ |
- return (key in filterByKey ? filterByKey[key] : null); |
+ return (key < filters.length ? filters[key] : null); |
}, |
/** |
* Returns a list of all selectors active on a particular domain (currently |
* used only in Chrome). |
*/ |
getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly) |
{ |
let result = []; |
- for (let key in filterByKey) |
+ for (let filter of filters) |
{ |
- let filter = filterByKey[key]; |
+ if (!filter) |
+ continue |
+ |
if (specificOnly && (!filter.domains || filter.domains[""])) |
continue; |
if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain)) |
result.push(filter.selector); |
} |
return result; |
} |