Index: lib/elemHide.js |
=================================================================== |
--- a/lib/elemHide.js |
+++ b/lib/elemHide.js |
@@ -14,23 +14,19 @@ |
* You should have received a copy of the GNU General Public License |
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
*/ |
/** |
* @fileOverview Element hiding implementation. |
*/ |
-Cu.import("resource://gre/modules/Services.jsm"); |
- |
-var {Utils} = require("utils"); |
-var {IO} = require("io"); |
-var {Prefs} = require("prefs"); |
-var {ElemHideException} = require("filterClasses"); |
-var {FilterNotifier} = require("filterNotifier"); |
+let {Utils} = require("utils"); |
+let {ElemHideException} = require("filterClasses"); |
+let {FilterNotifier} = require("filterNotifier"); |
/** |
* Lookup table, filters by their associated key |
* @type Object |
*/ |
var filterByKey = []; |
/** |
@@ -81,70 +77,34 @@ var knownExceptions = Object.create(null |
/** |
* Lookup table, lists of element hiding exceptions by selector |
* @type Object |
*/ |
var exceptions = Object.create(null); |
/** |
- * Currently applied stylesheet URL |
- * @type nsIURI |
- */ |
-var styleURL = null; |
- |
-/** |
- * Element hiding component |
+ * Container for element hiding filters |
* @class |
*/ |
var ElemHide = exports.ElemHide = |
{ |
/** |
- * Indicates whether filters have been added or removed since the last apply() call. |
- * @type Boolean |
- */ |
- isDirty: false, |
- |
- /** |
- * Indicates whether the element hiding stylesheet is currently applied. |
- * @type Boolean |
- */ |
- applied: false, |
- |
- /** |
- * Called on module startup. |
- */ |
- init: function() |
- { |
- Prefs.addListener(function(name) |
- { |
- if (name == "enabled") |
- ElemHide.apply(); |
- }); |
- onShutdown.add(() => ElemHide.unapply()); |
- |
- let styleFile = IO.resolveFilePath(Prefs.data_directory); |
- styleFile.append("elemhide.css"); |
- styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL); |
- }, |
- |
- /** |
* Removes all known filters |
*/ |
clear: function() |
{ |
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(); |
+ FilterNotifier.emit("elemhideupdate"); |
}, |
_addToFiltersByDomain: function(filter) |
{ |
let key = keyByFilter[filter.text]; |
let domains = filter.domains || defaultDomains; |
for (let domain in domains) |
{ |
@@ -217,19 +177,19 @@ var ElemHide = exports.ElemHide = |
} |
} |
else |
{ |
// The new filter's selector only applies to some domains |
this._addToFiltersByDomain(filter); |
} |
} |
+ } |
- ElemHide.isDirty = true; |
- } |
+ FilterNotifier.emit("elemhideupdate"); |
}, |
/** |
* Removes an element hiding filter |
* @param {ElemHideFilter} filter |
*/ |
remove: function(filter) |
{ |
@@ -247,17 +207,16 @@ var ElemHide = exports.ElemHide = |
else |
{ |
if (!(filter.text in keyByFilter)) |
return; |
let key = keyByFilter[filter.text]; |
delete filterByKey[key]; |
delete keyByFilter[filter.text]; |
- ElemHide.isDirty = true; |
if (usingGetSelectorsForDomain) |
{ |
let filters = filtersBySelector[filter.selector]; |
if (filters) |
{ |
if (filters.length > 1) |
{ |
@@ -277,16 +236,18 @@ var ElemHide = exports.ElemHide = |
{ |
let filters = filtersByDomain[domain]; |
if (filters) |
delete filters[key]; |
} |
} |
} |
} |
+ |
+ FilterNotifier.emit("elemhideupdate"); |
}, |
/** |
* Checks whether an exception rule is registered for a filter on a particular |
* domain. |
*/ |
getException: function(/**Filter*/ filter, /**String*/ docDomain) /**ElemHideException*/ |
{ |
@@ -297,200 +258,48 @@ var ElemHide = exports.ElemHide = |
for (let i = list.length - 1; i >= 0; i--) |
if (list[i].isActiveOnDomain(docDomain)) |
return list[i]; |
return null; |
}, |
/** |
- * Will be set to true if apply() is running (reentrance protection). |
- * @type Boolean |
+ * Retrieves an element hiding filter by the corresponding protocol key |
*/ |
- _applying: false, |
+ getFilterByKey: function(/**String*/ key) /**Filter*/ |
+ { |
+ return (key in filterByKey ? filterByKey[key] : null); |
+ }, |
/** |
- * Will be set to true if an apply() call arrives while apply() is already |
- * running (delayed execution). |
- * @type Boolean |
+ * Returns a list of all selectors as a nested map. On first level, the keys |
+ * are all values of `ElemHideBase.selectorDomain` (domains on which these |
+ * selectors should apply, ignoring exceptions). The values are maps again, |
+ * with the keys being selectors and values the corresponding filter keys. |
+ * @returns {Map.<String,Map<String,String>>} |
*/ |
- _needsApply: false, |
- |
- /** |
- * Generates stylesheet URL and applies it globally |
- */ |
- apply: function() |
+ getSelectors: function() |
{ |
- if (this._applying) |
- { |
- this._needsApply = true; |
- return; |
- } |
- |
- if (!ElemHide.isDirty || !Prefs.enabled) |
- { |
- // Nothing changed, looks like we merely got enabled/disabled |
- if (Prefs.enabled && !ElemHide.applied) |
- { |
- try |
- { |
- Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET); |
- ElemHide.applied = true; |
- } |
- catch (e) |
- { |
- Cu.reportError(e); |
- } |
- } |
- else if (!Prefs.enabled && ElemHide.applied) |
- { |
- ElemHide.unapply(); |
- } |
- |
- return; |
- } |
- |
- IO.writeToFile(styleURL.file, this._generateCSSContent(), function(e) |
- { |
- this._applying = false; |
- |
- // _generateCSSContent is throwing NS_ERROR_NOT_AVAILABLE to indicate that |
- // there are no filters. If that exception is passed through XPCOM we will |
- // see a proper exception here, otherwise a number. |
- let noFilters = (e == Cr.NS_ERROR_NOT_AVAILABLE || (e && e.result == Cr.NS_ERROR_NOT_AVAILABLE)); |
- if (noFilters) |
- { |
- e = null; |
- IO.removeFile(styleURL.file, function(e) {}); |
- } |
- else if (e) |
- Cu.reportError(e); |
- |
- if (this._needsApply) |
- { |
- this._needsApply = false; |
- this.apply(); |
- } |
- else if (!e) |
- { |
- ElemHide.isDirty = false; |
- |
- ElemHide.unapply(); |
- |
- if (!noFilters) |
- { |
- try |
- { |
- Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET); |
- ElemHide.applied = true; |
- } |
- catch (e) |
- { |
- Cu.reportError(e); |
- } |
- } |
- |
- FilterNotifier.triggerListeners("elemhideupdate"); |
- } |
- }.bind(this)); |
- |
- this._applying = true; |
- }, |
- |
- _generateCSSContent: function*() |
- { |
- // Grouping selectors by domains |
- let domains = Object.create(null); |
- let hasFilters = false; |
+ let domains = new Map(); |
for (let key in filterByKey) |
{ |
let filter = filterByKey[key]; |
let selector = filter.selector; |
if (!selector) |
continue; |
let domain = filter.selectorDomain || ""; |
- let list; |
- if (domain in domains) |
- list = domains[domain]; |
- else |
- { |
- list = Object.create(null); |
- domains[domain] = list; |
- } |
- list[selector] = key; |
- hasFilters = true; |
+ if (!domains.has(domain)) |
+ domains.set(domain, new Map()); |
+ domains.get(domain).set(selector, key); |
} |
- if (!hasFilters) |
- throw Cr.NS_ERROR_NOT_AVAILABLE; |
- |
- function escapeChar(match) |
- { |
- return "\\" + match.charCodeAt(0).toString(16) + " "; |
- } |
- |
- // Return CSS data |
- let cssTemplate = "-moz-binding: url(about:abp-elemhidehit?%ID%#dummy) !important;"; |
- for (let domain in domains) |
- { |
- let rules = []; |
- let list = domains[domain]; |
- |
- if (domain) |
- yield ('@-moz-document domain("' + domain.split(",").join('"),domain("') + '"){').replace(/[^\x01-\x7F]/g, escapeChar); |
- else |
- { |
- // Only allow unqualified rules on a few protocols to prevent them from blocking chrome |
- yield '@-moz-document url-prefix("http://"),url-prefix("https://"),' |
- + 'url-prefix("mailbox://"),url-prefix("imap://"),' |
- + 'url-prefix("news://"),url-prefix("snews://"){'; |
- } |
- |
- for (let selector in list) |
- yield selector.replace(/[^\x01-\x7F]/g, escapeChar) + "{" + cssTemplate.replace("%ID%", list[selector]) + "}"; |
- yield '}'; |
- } |
- }, |
- |
- /** |
- * Unapplies current stylesheet URL |
- */ |
- unapply: function() |
- { |
- if (ElemHide.applied) |
- { |
- try |
- { |
- Utils.styleService.unregisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET); |
- } |
- catch (e) |
- { |
- Cu.reportError(e); |
- } |
- ElemHide.applied = false; |
- } |
- }, |
- |
- /** |
- * Retrieves the currently applied stylesheet URL |
- * @type String |
- */ |
- get styleURL() |
- { |
- return 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 domains; |
}, |
/** |
* Returns a list of all selectors active on a particular domain, must not be |
* used in Firefox (when usingGetSelectorsForDomain is false). |
*/ |
getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly) |
{ |