diff options
author | Ruben Rodriguez <ruben@gnu.org> | 2014-10-20 02:24:51 +0200 |
---|---|---|
committer | Ruben Rodriguez <ruben@gnu.org> | 2014-10-20 02:24:51 +0200 |
commit | 6e7918b6ccb69876d339a320091fdee811445395 (patch) | |
tree | 31cb88ee438d652fddefca1193f70289a8b3dcc8 /data/extensions/spyblock@gnu.org/lib/elemHide.js | |
parent | 60e5b13c35d4d3ba21bb03b026750a0a414f6c77 (diff) |
Generalize data directory
Diffstat (limited to 'data/extensions/spyblock@gnu.org/lib/elemHide.js')
-rw-r--r-- | data/extensions/spyblock@gnu.org/lib/elemHide.js | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/data/extensions/spyblock@gnu.org/lib/elemHide.js b/data/extensions/spyblock@gnu.org/lib/elemHide.js new file mode 100644 index 0000000..df17a0f --- /dev/null +++ b/data/extensions/spyblock@gnu.org/lib/elemHide.js @@ -0,0 +1,419 @@ +/* + * This file is part of Adblock Plus <http://adblockplus.org/>, + * Copyright (C) 2006-2014 Eyeo GmbH + * + * Adblock Plus is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * Adblock Plus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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"); + +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 + * @type Object + */ +let filterByKey = {__proto__: null}; + +/** + * Lookup table, keys of the filters by filter text + * @type Object + */ +let keyByFilter = {__proto__: null}; + +/** + * Lookup table, keys are known element hiding exceptions + * @type Object + */ +let knownExceptions = {__proto__: null}; + +/** + * Lookup table, lists of element hiding exceptions by selector + * @type Object + */ +let exceptions = {__proto__: null}; + +/** + * Currently applied stylesheet URL + * @type nsIURI + */ +let styleURL = null; + +/** + * Element hiding component + * @class + */ +let ElemHide = exports.ElemHide = +{ + /** + * Indicates whether filters have been added or removed since the last apply() call. + * @type Boolean + */ + isDirty: false, + + /** + * Inidicates whether the element hiding stylesheet is currently applied. + * @type Boolean + */ + applied: false, + + /** + * Called on module startup. + */ + init: function() + { + TimeLine.enter("Entered ElemHide.init()"); + Prefs.addListener(function(name) + { + if (name == "enabled") + ElemHide.apply(); + }); + onShutdown.add(function() + { + ElemHide.unapply(); + }); + + TimeLine.log("done adding prefs listener"); + + let styleFile = IO.resolveFilePath(Prefs.data_directory); + styleFile.append("elemhide.css"); + styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL); + TimeLine.log("done determining stylesheet URL"); + + TimeLine.leave("ElemHide.init() done"); + }, + + /** + * Removes all known filters + */ + clear: function() + { + filterByKey = {__proto__: null}; + keyByFilter = {__proto__: null}; + knownExceptions = {__proto__: null}; + exceptions = {__proto__: null}; + ElemHide.isDirty = false; + ElemHide.unapply(); + }, + + /** + * Add a new element hiding filter + * @param {ElemHideFilter} filter + */ + add: function(filter) + { + if (filter instanceof ElemHideException) + { + if (filter.text in knownExceptions) + return; + + let selector = filter.selector; + if (!(selector in exceptions)) + exceptions[selector] = []; + exceptions[selector].push(filter); + knownExceptions[filter.text] = true; + } + else + { + if (filter.text in keyByFilter) + return; + + let key; + do { + key = Math.random().toFixed(15).substr(5); + } while (key in filterByKey); + + filterByKey[key] = filter; + keyByFilter[filter.text] = key; + ElemHide.isDirty = true; + } + }, + + /** + * Removes an element hiding filter + * @param {ElemHideFilter} filter + */ + remove: function(filter) + { + if (filter instanceof ElemHideException) + { + if (!(filter.text in knownExceptions)) + return; + + 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)) + return; + + let key = keyByFilter[filter.text]; + delete filterByKey[key]; + delete keyByFilter[filter.text]; + ElemHide.isDirty = true; + } + }, + + /** + * 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; + + let list = exceptions[filter.selector]; + 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 + */ + _applying: false, + + /** + * Will be set to true if an apply() call arrives while apply() is already + * running (delayed execution). + * @type Boolean + */ + _needsApply: false, + + /** + * Generates stylesheet URL and applies it globally + */ + apply: function() + { + if (this._applying) + { + this._needsApply = true; + return; + } + + TimeLine.enter("Entered ElemHide.apply()"); + + 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); + } + TimeLine.log("Applying existing stylesheet finished"); + } + else if (!Prefs.enabled && ElemHide.applied) + { + ElemHide.unapply(); + TimeLine.log("ElemHide.unapply() finished"); + } + + TimeLine.leave("ElemHide.apply() done (no file changes)"); + return; + } + + IO.writeToFile(styleURL.file, this._generateCSSContent(), function(e) + { + TimeLine.enter("ElemHide.apply() write callback"); + 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(); + TimeLine.log("ElemHide.unapply() finished"); + + if (!noFilters) + { + try + { + Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET); + ElemHide.applied = true; + } + catch (e) + { + Cu.reportError(e); + } + TimeLine.log("Applying stylesheet finished"); + } + + FilterNotifier.triggerListeners("elemhideupdate"); + } + TimeLine.leave("ElemHide.apply() write callback done"); + }.bind(this), "ElemHideWrite"); + + this._applying = true; + + TimeLine.leave("ElemHide.apply() done", "ElemHideWrite"); + }, + + _generateCSSContent: function() + { + // Grouping selectors by domains + TimeLine.log("start grouping selectors"); + let domains = {__proto__: null}; + let hasFilters = false; + for (let key in filterByKey) + { + let filter = filterByKey[key]; + let domain = filter.selectorDomain || ""; + + let list; + if (domain in domains) + list = domains[domain]; + else + { + list = {__proto__: null}; + domains[domain] = list; + } + list[filter.selector] = key; + hasFilters = true; + } + TimeLine.log("done grouping selectors"); + + 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:" + AboutHandler.aboutPrefix + "?%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); + }, + + /** + * Returns a list of all selectors active on a particular domain (currently + * used only in Chrome, Opera and Safari). + */ + getSelectorsForDomain: function(/**String*/ domain, /**Boolean*/ specificOnly) + { + let result = []; + for (let key in filterByKey) + { + let filter = filterByKey[key]; + + // it is important to always access filter.domains + // here, even if it isn't used, in order to + // workaround WebKit bug 132872, also see #419 + let domains = filter.domains; + + if (specificOnly && (!domains || domains[""])) + continue; + + if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain)) + result.push(filter.selector); + } + return result; + } +}; |