diff options
Diffstat (limited to 'data/extensions/spyblock@gnu.org/lib/requestNotifier.js')
-rw-r--r-- | data/extensions/spyblock@gnu.org/lib/requestNotifier.js | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/data/extensions/spyblock@gnu.org/lib/requestNotifier.js b/data/extensions/spyblock@gnu.org/lib/requestNotifier.js new file mode 100644 index 0000000..2ee9ec3 --- /dev/null +++ b/data/extensions/spyblock@gnu.org/lib/requestNotifier.js @@ -0,0 +1,378 @@ +/* + * 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 Stores Adblock Plus data to be attached to a window. + */ + +Cu.import("resource://gre/modules/Services.jsm"); + +let {Utils} = require("utils"); +let {BlockingFilter, WhitelistFilter, ElemHideBase, ElemHideFilter, ElemHideException} = require("filterClasses"); + +let nodeData = new WeakMap(); +let windowStats = new WeakMap(); +let windowSelection = new WeakMap(); + +let setEntry, hasEntry, getEntry; +// Last issue(Bug 982561) preventing us from using WeakMap fixed for FF version 32 +if (Services.vc.compare(Utils.platformVersion, "32.0a1") >= 0) +{ + setEntry = (map, key, value) => map.set(key, value); + hasEntry = (map, key) => map.has(key); + getEntry = (map, key) => map.get(key); +} +else +{ + // Fall back to user data + let dataSeed = Math.random(); + let nodeDataProp = "abpNodeData" + dataSeed; + let windowStatsProp = "abpWindowStats" + dataSeed; + let windowSelectionProp = "abpWindowSelection" + dataSeed; + let getProp = function(map) + { + switch (map) + { + case nodeData: + return nodeDataProp; + case windowStats: + return windowStatsProp; + case windowSelection: + return windowSelectionProp; + default: + return null; + } + }; + + setEntry = (map, key, value) => key.setUserData(getProp(map), value, null); + hasEntry = (map, key) => key.getUserData(getProp(map)); + getEntry = (map, key) => key.getUserData(getProp(map)) || undefined; +} + +/** + * List of notifiers in use - these notifiers need to receive notifications on + * new requests. + * @type RequestNotifier[] + */ +let activeNotifiers = []; + +/** + * Creates a notifier object for a particular window. After creation the window + * will first be scanned for previously saved requests. Once that scan is + * complete only new requests for this window will be reported. + * @param {Window} wnd window to attach the notifier to + * @param {Function} listener listener to be called whenever a new request is found + * @param {Object} [listenerObj] "this" pointer to be used when calling the listener + */ +function RequestNotifier(wnd, listener, listenerObj) +{ + this.window = wnd; + this.listener = listener; + this.listenerObj = listenerObj || null; + activeNotifiers.push(this); + if (wnd) + this.startScan(wnd); + else + this.scanComplete = true; +} +exports.RequestNotifier = RequestNotifier; + +RequestNotifier.prototype = +{ + /** + * The window this notifier is associated with. + * @type Window + */ + window: null, + + /** + * The listener to be called when a new request is found. + * @type Function + */ + listener: null, + + /** + * "this" pointer to be used when calling the listener. + * @type Object + */ + listenerObj: null, + + /** + * Will be set to true once the initial window scan is complete. + * @type Boolean + */ + scanComplete: false, + + /** + * Shuts down the notifier once it is no longer used. The listener + * will no longer be called after that. + */ + shutdown: function() + { + delete this.window; + delete this.listener; + delete this.listenerObj; + + for (let i = activeNotifiers.length - 1; i >= 0; i--) + if (activeNotifiers[i] == this) + activeNotifiers.splice(i, 1); + }, + + /** + * Notifies listener about a new request. + */ + notifyListener: function(/**Window*/ wnd, /**Node*/ node, /**RequestEntry*/ entry) + { + this.listener.call(this.listenerObj, wnd, node, entry, this.scanComplete); + }, + + /** + * Number of currently posted scan events (will be 0 when the scan finishes + * running). + */ + eventsPosted: 0, + + /** + * Starts the initial scan of the window (will recurse into frames). + * @param {Window} wnd the window to be scanned + */ + startScan: function(wnd) + { + let doc = wnd.document; + let walker = doc.createTreeWalker(doc, Ci.nsIDOMNodeFilter.SHOW_ELEMENT, null, false); + + let process = function() + { + if (!this.listener) + return; + + let node = walker.currentNode; + let data = getEntry(nodeData, node); + if (typeof data != "undefined") + for (let k in data) + this.notifyListener(wnd, node, data[k]); + + if (walker.nextNode()) + Utils.runAsync(process); + else + { + // Done with the current window, start the scan for its frames + for (let i = 0; i < wnd.frames.length; i++) + this.startScan(wnd.frames[i]); + + this.eventsPosted--; + if (!this.eventsPosted) + { + this.scanComplete = true; + this.notifyListener(wnd, null, null); + } + } + }.bind(this); + + // Process each node in a separate event to allow other events to process + this.eventsPosted++; + Utils.runAsync(process); + } +}; + +RequestNotifier.storeSelection = function(/**Window*/ wnd, /**String*/ selection) +{ + setEntry(windowSelection, wnd.document, selection); +}; +RequestNotifier.getSelection = function(/**Window*/ wnd) /**String*/ +{ + if (hasEntry(windowSelection, wnd.document)) + return getEntry(windowSelection, wnd.document); + else + return null; +}; + +/** + * Attaches request data to a DOM node. + * @param {Node} node node to attach data to + * @param {Window} topWnd top-level window the node belongs to + * @param {Integer} contentType request type, one of the Policy.type.* constants + * @param {String} docDomain domain of the document that initiated the request + * @param {Boolean} thirdParty will be true if a third-party server has been requested + * @param {String} location the address that has been requested + * @param {Filter} filter filter applied to the request or null if none + */ +RequestNotifier.addNodeData = function(/**Node*/ node, /**Window*/ topWnd, /**Integer*/ contentType, /**String*/ docDomain, /**Boolean*/ thirdParty, /**String*/ location, /**Filter*/ filter) +{ + return new RequestEntry(node, topWnd, contentType, docDomain, thirdParty, location, filter); +} + +/** + * Retrieves the statistics for a window. + * @result {Object} Object with the properties items, blocked, whitelisted, hidden, filters containing statistics for the window (might be null) + */ +RequestNotifier.getWindowStatistics = function(/**Window*/ wnd) +{ + if (hasEntry(windowStats, wnd.document)) + return getEntry(windowStats, wnd.document); + else + return null; +} + +/** + * Retrieves the request entry associated with a DOM node. + * @param {Node} node + * @param {Boolean} noParent if missing or false, the search will extend to the parent nodes until one is found that has data associated with it + * @param {Integer} [type] request type to be looking for + * @param {String} [location] request location to be looking for + * @result {[Node, RequestEntry]} + * @static + */ +RequestNotifier.getDataForNode = function(node, noParent, type, location) +{ + while (node) + { + let data = getEntry(nodeData, node); + if (typeof data != "undefined") + { + // Look for matching entry + for (let k in data) + { + let entry = data[k]; + if ((typeof type == "undefined" || entry.type == type) && + (typeof location == "undefined" || entry.location == location)) + { + return [node, entry]; + } + } + } + + // If we don't have any match on this node then maybe its parent will do + if ((typeof noParent != "boolean" || !noParent) && + node.parentNode instanceof Ci.nsIDOMElement) + { + node = node.parentNode; + } + else + { + node = null; + } + } + + return null; +}; + +function RequestEntry(node, topWnd, contentType, docDomain, thirdParty, location, filter) +{ + this.type = contentType; + this.docDomain = docDomain; + this.thirdParty = thirdParty; + this.location = location; + this.filter = filter; + + this.attachToNode(node); + + // Update window statistics + if (!hasEntry(windowStats, topWnd.document)) + { + setEntry(windowStats, topWnd.document, { + items: 0, + hidden: 0, + blocked: 0, + whitelisted: 0, + filters: {} + }); + } + + let stats = getEntry(windowStats, topWnd.document); + if (!filter || !(filter instanceof ElemHideBase)) + stats.items++; + if (filter) + { + if (filter instanceof BlockingFilter) + stats.blocked++; + else if (filter instanceof WhitelistFilter || filter instanceof ElemHideException) + stats.whitelisted++; + else if (filter instanceof ElemHideFilter) + stats.hidden++; + + if (filter.text in stats.filters) + stats.filters[filter.text]++; + else + stats.filters[filter.text] = 1; + } + + // Notify listeners + for (let notifier of activeNotifiers) + if (!notifier.window || notifier.window == topWnd) + notifier.notifyListener(topWnd, node, this); +} +RequestEntry.prototype = +{ + /** + * Content type of the request (one of the nsIContentPolicy constants) + * @type Integer + */ + type: null, + /** + * Domain name of the requesting document + * @type String + */ + docDomain: null, + /** + * True if the request goes to a different domain than the domain of the containing document + * @type Boolean + */ + thirdParty: false, + /** + * Address being requested + * @type String + */ + location: null, + /** + * Filter that was applied to this request (if any) + * @type Filter + */ + filter: null, + /** + * String representation of the content type, e.g. "subdocument" + * @type String + */ + get typeDescr() + { + return require("contentPolicy").Policy.typeDescr[this.type]; + }, + /** + * User-visible localized representation of the content type, e.g. "frame" + * @type String + */ + get localizedDescr() + { + return require("contentPolicy").Policy.localizedDescr[this.type]; + }, + + /** + * Attaches this request object to a DOM node. + */ + attachToNode: function(/**Node*/ node) + { + let existingData = getEntry(nodeData, node); + if (typeof existingData == "undefined") + { + existingData = {}; + setEntry(nodeData, node, existingData); + } + + // Add this request to the node data + existingData[this.type + " " + this.location] = this; + } +}; |