summaryrefslogtreecommitdiff
path: root/data/extensions/spyblock@gnu.org/lib/child/requestNotifier.js
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/spyblock@gnu.org/lib/child/requestNotifier.js')
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/requestNotifier.js444
1 files changed, 444 insertions, 0 deletions
diff --git a/data/extensions/spyblock@gnu.org/lib/child/requestNotifier.js b/data/extensions/spyblock@gnu.org/lib/child/requestNotifier.js
new file mode 100644
index 0000000..fc6d314
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/requestNotifier.js
@@ -0,0 +1,444 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-2017 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.
+ */
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let {port} = require("messaging");
+let {Utils} = require("utils");
+let {Flasher} = require("child/flasher");
+
+let nodeData = new WeakMap();
+let windowStats = new WeakMap();
+let windowData = new WeakMap();
+let requestEntryMaxId = 0;
+
+/**
+ * Active RequestNotifier instances by their ID
+ * @type Map.<number,RequestNotifier>
+ */
+let notifiers = new Map();
+
+port.on("startWindowScan", onStartScan);
+port.on("shutdownNotifier", onNotifierShutdown);
+port.on("flashNodes", onFlashNodes);
+port.on("retrieveNodeSize", onRetrieveNodeSize);
+port.on("storeNodesForEntries", onStoreNodes);
+port.on("retrieveWindowStats", onRetrieveWindowStats);
+port.on("storeWindowData", onStoreWindowData);
+port.on("retrieveWindowData", onRetrieveWindowData);
+
+function onStartScan({notifierID, outerWindowID})
+{
+ let window = Services.wm.getOuterWindowWithId(outerWindowID);
+ if (window)
+ new RequestNotifier(window, notifierID);
+}
+
+function onNotifierShutdown(notifierID)
+{
+ let notifier = notifiers.get(notifierID);
+ if (notifier)
+ notifier.shutdown();
+}
+
+function onFlashNodes({notifierID, requests, scrollToItem})
+{
+ let notifier = notifiers.get(notifierID);
+ if (notifier)
+ notifier.flashNodes(requests, scrollToItem);
+}
+
+function onRetrieveNodeSize({notifierID, requests})
+{
+ let notifier = notifiers.get(notifierID);
+ if (notifier)
+ return notifier.retrieveNodeSize(requests);
+}
+
+function onStoreNodes({notifierID, requests})
+{
+ let notifier = notifiers.get(notifierID);
+ if (notifier)
+ return notifier.storeNodesForEntries(requests);
+}
+
+function onRetrieveWindowStats(outerWindowID)
+{
+ let window = Services.wm.getOuterWindowWithId(outerWindowID);
+ if (window)
+ return RequestNotifier.getWindowStatistics(window);
+}
+
+function onStoreWindowData({outerWindowID, data})
+{
+ let window = Services.wm.getOuterWindowWithId(outerWindowID);
+ if (window)
+ windowData.set(window.document, data);
+};
+
+function onRetrieveWindowData(outerWindowID)
+{
+ let window = Services.wm.getOuterWindowWithId(outerWindowID);
+ if (window)
+ return windowData.get(window.document) || null;
+};
+
+/**
+ * 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} window window to attach the notifier to
+ * @param {Integer} notifierID Parent notifier ID to be messaged
+ */
+function RequestNotifier(window, notifierID)
+{
+ this.window = window;
+ this.id = notifierID;
+ notifiers.set(this.id, this);
+ this.nodes = new Map();
+ this.startScan(window);
+}
+exports.RequestNotifier = RequestNotifier;
+
+RequestNotifier.prototype =
+{
+ /**
+ * Parent notifier ID to be messaged
+ * @type Integer
+ */
+ id: null,
+
+ /**
+ * The window this notifier is associated with.
+ * @type Window
+ */
+ window: null,
+
+ /**
+ * Nodes associated with a particular request ID.
+ * @type Map.<number,Node>
+ */
+ nodes: null,
+
+ /**
+ * 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.nodes;
+ this.stopFlashing();
+ notifiers.delete(this.id);
+ },
+
+ /**
+ * Notifies the parent about a new request.
+ * @param {Node} node DOM node that the request is associated with
+ * @param {Object} entry
+ */
+ notifyListener: function(node, entry)
+ {
+ if (this.nodes)
+ this.nodes.set(entry.id, node);
+ port.emit("foundNodeData", {
+ notifierID: this.id,
+ data: entry
+ });
+ },
+
+ onComplete: function()
+ {
+ port.emit("scanComplete", this.id);
+ },
+
+ /**
+ * 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()
+ {
+ // Don't do anything if the notifier was shut down already.
+ if (!this.window)
+ return;
+
+ let node = walker.currentNode;
+ let data = nodeData.get(node);
+ if (typeof data != "undefined")
+ for (let k in data)
+ this.notifyListener(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.onComplete();
+ }
+ }
+ }.bind(this);
+
+ // Process each node in a separate event to allow other events to process
+ this.eventsPosted++;
+ Utils.runAsync(process);
+ },
+
+ /**
+ * Makes the nodes associated with the given requests blink.
+ * @param {number[]} requests list of request IDs that were previously
+ * reported by this notifier.
+ * @param {boolean} scrollToItem if true, scroll to first node
+ */
+ flashNodes: function(requests, scrollToItem)
+ {
+ this.stopFlashing();
+
+ let nodes = [];
+ for (let id of requests)
+ {
+ if (!this.nodes.has(id))
+ continue;
+
+ let node = this.nodes.get(id);
+ if (Cu.isDeadWrapper(node))
+ this.nodes.delete(node);
+ else if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE)
+ nodes.push(node);
+ }
+ if (nodes.length)
+ this.flasher = new Flasher(nodes, scrollToItem);
+ },
+
+ /**
+ * Stops flashing nodes after a previous flashNodes() call.
+ */
+ stopFlashing: function()
+ {
+ if (this.flasher)
+ this.flasher.stop();
+ this.flasher = null;
+ },
+
+ /**
+ * Attempts to calculate the size of the nodes associated with the requests.
+ * @param {number[]} requests list of request IDs that were previously
+ * reported by this notifier.
+ * @return {number[]|null} either an array containing width and height or
+ * null if the size could not be calculated.
+ */
+ retrieveNodeSize: function(requests)
+ {
+ function getNodeSize(node)
+ {
+ if (node instanceof Ci.nsIDOMHTMLImageElement && (node.naturalWidth || node.naturalHeight))
+ return [node.naturalWidth, node.naturalHeight];
+ else if (node instanceof Ci.nsIDOMHTMLElement && (node.offsetWidth || node.offsetHeight))
+ return [node.offsetWidth, node.offsetHeight];
+ else
+ return null;
+ }
+
+ let size = null;
+ for (let id of requests)
+ {
+ if (!this.nodes.has(id))
+ continue;
+
+ let node = this.nodes.get(id);
+ if (Cu.isDeadWrapper(node))
+ this.nodes.delete(node);
+ else
+ {
+ size = getNodeSize(node);
+ if (size)
+ break;
+ }
+ }
+ return size;
+ },
+
+ /**
+ * Stores the nodes associated with the requests and generates a unique ID
+ * for them that can be used with Policy.refilterNodes().
+ * @param {number[]} requests list of request IDs that were previously
+ * reported by this notifier.
+ * @return {string} unique identifiers associated with the nodes.
+ */
+ storeNodesForEntries: function(requests)
+ {
+ let nodes = [];
+ for (let id of requests)
+ {
+ if (!this.nodes.has(id))
+ continue;
+
+ let node = this.nodes.get(id);
+ if (Cu.isDeadWrapper(node))
+ this.nodes.delete(node);
+ else
+ nodes.push(node);
+ }
+
+ let {storeNodes} = require("child/contentPolicy");
+ return storeNodes(nodes);
+ }
+};
+
+/**
+ * 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 {Object} hitData
+ * @param {String} hitData.contentType request type, e.g. "IMAGE"
+ * @param {String} hitData.docDomain domain of the document that initiated the request
+ * @param {Boolean} hitData.thirdParty will be true if a third-party server has been requested
+ * @param {String} hitData.location the address that has been requested
+ * @param {String} hitData.filter filter applied to the request or null if none
+ * @param {String} hitData.filterType type of filter applied to the request
+ */
+RequestNotifier.addNodeData = function(node, topWnd, {contentType, docDomain, thirdParty, location, filter, filterType})
+{
+ let entry = {
+ id: ++requestEntryMaxId,
+ type: contentType,
+ docDomain, thirdParty, location, filter
+ };
+
+ let existingData = nodeData.get(node);
+ if (typeof existingData == "undefined")
+ {
+ existingData = {};
+ nodeData.set(node, existingData);
+ }
+
+ // Add this request to the node data
+ existingData[contentType + " " + location] = entry;
+
+ // Update window statistics
+ if (!windowStats.has(topWnd.document))
+ {
+ windowStats.set(topWnd.document, {
+ items: 0,
+ hidden: 0,
+ blocked: 0,
+ whitelisted: 0,
+ filters: {}
+ });
+ }
+
+ let stats = windowStats.get(topWnd.document);
+ if (filterType != "elemhide" && filterType != "elemhideexception" && filterType != "elemhideemulation")
+ stats.items++;
+ if (filter)
+ {
+ if (filterType == "blocking")
+ stats.blocked++;
+ else if (filterType == "whitelist" || filterType == "elemhideexception")
+ stats.whitelisted++;
+ else if (filterType == "elemhide" || filterType == "elemhideemulation")
+ stats.hidden++;
+
+ if (filter in stats.filters)
+ stats.filters[filter]++;
+ else
+ stats.filters[filter] = 1;
+ }
+
+ // Notify listeners
+ for (let notifier of notifiers.values())
+ if (!notifier.window || notifier.window == topWnd)
+ notifier.notifyListener(node, entry);
+}
+
+/**
+ * Retrieves the statistics for a window.
+ * @return {Object} Object with the properties items, blocked, whitelisted, hidden, filters containing statistics for the window (might be null)
+ */
+RequestNotifier.getWindowStatistics = function(/**Window*/ wnd)
+{
+ if (windowStats.has(wnd.document))
+ return windowStats.get(wnd.document);
+ else
+ return null;
+}
+
+/**
+ * Retrieves the request data 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, Object]}
+ * @static
+ */
+RequestNotifier.getDataForNode = function(node, noParent, type, location)
+{
+ while (node)
+ {
+ let data = nodeData.get(node);
+ if (typeof data != "undefined")
+ {
+ let entry = null;
+ // Look for matching entry
+ for (let k in data)
+ {
+ if ((!entry || entry.id < data[k].id) &&
+ (typeof type == "undefined" || data[k].type == type) &&
+ (typeof location == "undefined" || data[k].location == location))
+ {
+ entry = data[k];
+ }
+ }
+ if (entry)
+ 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;
+};