summaryrefslogtreecommitdiff
path: root/data/extensions/spyblock@gnu.org/lib/child
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/spyblock@gnu.org/lib/child')
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/bootstrap.js97
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/contentPolicy.js518
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/contextMenu.js137
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/dataCollector.js108
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/elemHide.js403
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/elemHideEmulation.js118
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/flasher.js99
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/main.js31
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/objectTabs.js405
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/requestNotifier.js444
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/subscribeLinks.js118
-rw-r--r--data/extensions/spyblock@gnu.org/lib/child/utils.js141
12 files changed, 2619 insertions, 0 deletions
diff --git a/data/extensions/spyblock@gnu.org/lib/child/bootstrap.js b/data/extensions/spyblock@gnu.org/lib/child/bootstrap.js
new file mode 100644
index 0000000..477ca44
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/bootstrap.js
@@ -0,0 +1,97 @@
+/*
+ * 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/>.
+ */
+
+(function()
+{
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ const Cr = Components.results;
+ const Cu = Components.utils;
+
+ let {Loader, main, unload} = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
+ let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+ Cu.importGlobalProperties(["atob", "btoa", "File", "URL", "URLSearchParams",
+ "TextDecoder", "TextEncoder"]);
+
+ let shutdownHandlers = [];
+ let onShutdown =
+ {
+ done: false,
+ add: function(handler)
+ {
+ if (shutdownHandlers.indexOf(handler) < 0)
+ shutdownHandlers.push(handler);
+ },
+ remove: function(handler)
+ {
+ let index = shutdownHandlers.indexOf(handler);
+ if (index >= 0)
+ shutdownHandlers.splice(index, 1);
+ }
+ };
+
+ function init()
+ {
+ let url = new URL(Components.stack.filename);
+ let params = new URLSearchParams(url.search.substr(1));
+ let info = JSON.parse(params.get("info"));
+
+ let loader = Loader({
+ paths: {
+ "": info.addonRoot + "lib/"
+ },
+ globals: {
+ Components, Cc, Ci, Cu, Cr, atob, btoa, File, URL, URLSearchParams,
+ TextDecoder, TextEncoder, onShutdown
+ },
+ modules: {"info": info, "messageManager": this},
+ id: info.addonID
+ });
+ onShutdown.add(() => unload(loader, "disable"))
+
+ main(loader, "child/main");
+ }
+
+ function shutdown(message)
+ {
+ if (message.data == Components.stack.filename)
+ {
+ onShutdown.done = true;
+ for (let i = shutdownHandlers.length - 1; i >= 0; i --)
+ {
+ try
+ {
+ shutdownHandlers[i]();
+ }
+ catch (e)
+ {
+ Cu.reportError(e);
+ }
+ }
+ shutdownHandlers = null;
+ }
+ }
+
+ addMessageListener("AdblockPlus:Shutdown", shutdown);
+ onShutdown.add(() =>
+ {
+ removeMessageListener("AdblockPlus:Shutdown", shutdown);
+ });
+
+ init();
+})();
diff --git a/data/extensions/spyblock@gnu.org/lib/child/contentPolicy.js b/data/extensions/spyblock@gnu.org/lib/child/contentPolicy.js
new file mode 100644
index 0000000..97ea7b1
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/contentPolicy.js
@@ -0,0 +1,518 @@
+/*
+ * 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 Content policy implementation, responsible for blocking things.
+ */
+
+"use strict";
+
+try
+{
+ // Hack: SDK loader masks our Components object with a getter.
+ let proto = Object.getPrototypeOf(this);
+ let property = Object.getOwnPropertyDescriptor(proto, "Components");
+ if (property && property.get)
+ delete proto.Components;
+}
+catch (e)
+{
+ Cu.reportError(e);
+}
+
+let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let {port} = require("messaging");
+let {Utils} = require("utils");
+let {getFrames, isPrivate, getRequestWindow} = require("child/utils");
+let {objectMouseEventHander} = require("child/objectTabs");
+let {RequestNotifier} = require("child/requestNotifier");
+
+/**
+ * Randomly generated class name, to be applied to collapsed nodes.
+ * @type Promise.<string>
+ */
+let collapsedClass = port.emitWithResponse("getCollapsedClass");
+
+/**
+ * Maps numerical content type IDs to strings.
+ * @type Map.<number,string>
+ */
+let types = new Map();
+
+/**
+ * Contains nodes stored by storeNodes() mapped by their IDs.
+ * @type Map.<string,DOMNode[]>
+ */
+let storedNodes = new Map();
+
+/**
+ * Process-dependent prefix to be used for unique nodes identifiers returned
+ * by storeNodes().
+ * @type string
+ */
+let nodesIDPrefix = Services.appinfo.processID + " ";
+
+/**
+ * Counter used to generate unique nodes identifiers in storeNodes().
+ * @type number
+ */
+let maxNodesID = 0;
+
+port.on("deleteNodes", onDeleteNodes);
+port.on("refilterNodes", onRefilterNodes);
+
+/**
+ * Processes parent's response to the ShouldAllow message.
+ * @param {nsIDOMWindow} window window that the request is associated with
+ * @param {nsIDOMElement} node DOM element that the request is associated with
+ * @param {Object|undefined} response object received as response
+ * @return {Boolean} false if the request should be blocked
+ */
+function processPolicyResponse(window, node, response)
+{
+ if (typeof response == "undefined")
+ return true;
+
+ let {allow, collapse, hits} = response;
+ let isObject = false;
+ for (let hit of hits)
+ {
+ if (hit.contentType == "OBJECT")
+ isObject = true;
+
+ let context = node;
+ if (typeof hit.frameIndex == "number")
+ {
+ context = window;
+ for (let i = 0; i < hit.frameIndex; i++)
+ context = context.parent;
+ context = context.document;
+ }
+ RequestNotifier.addNodeData(context, window.top, hit);
+ }
+
+ if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE)
+ {
+ // Track mouse events for objects
+ if (allow && isObject)
+ {
+ node.addEventListener("mouseover", objectMouseEventHander, true);
+ node.addEventListener("mouseout", objectMouseEventHander, true);
+ }
+
+ if (collapse)
+ schedulePostProcess(node);
+ }
+ return allow;
+}
+
+/**
+ * Checks whether a request should be allowed, hides it if necessary
+ * @param {nsIDOMWindow} window
+ * @param {nsIDOMElement} node
+ * @param {String} contentType
+ * @param {String} location location of the request, filter key if contentType is ELEMHIDE
+ * @return {Boolean} false if the request should be blocked
+ */
+let shouldAllow = exports.shouldAllow = function(window, node, contentType, location)
+{
+ return processPolicyResponse(window, node, port.emitSync("shouldAllow", {
+ contentType,
+ location,
+ frames: getFrames(window),
+ isPrivate: isPrivate(window)
+ }));
+};
+
+/**
+ * Asynchronously checks whether a request should be allowed.
+ * @param {nsIDOMWindow} window
+ * @param {nsIDOMElement} node
+ * @param {String} contentType
+ * @param {String} location location of the request, filter key if contentType is ELEMHIDE
+ * @param {Function} callback callback to be called with a boolean value, if
+ * false the request should be blocked
+ */
+let shouldAllowAsync = exports.shouldAllowAsync = function(window, node, contentType, location, callback)
+{
+ port.emitWithResponse("shouldAllow", {
+ contentType,
+ location,
+ frames: getFrames(window),
+ isPrivate: isPrivate(window)
+ }).then(response =>
+ {
+ callback(processPolicyResponse(window, node, response));
+ });
+};
+
+/**
+ * Stores nodes and generates a unique ID for them that can be used for
+ * Policy.refilterNodes() later. It's important that Policy.deleteNodes() is
+ * called later, otherwise the nodes will be leaked.
+ * @param {DOMNode[]} nodes list of nodes to be stored
+ * @return {string} unique ID for the nodes
+ */
+let storeNodes = exports.storeNodes = function(nodes)
+{
+ let id = nodesIDPrefix + (++maxNodesID);
+ storedNodes.set(id, nodes);
+ return id;
+};
+
+/**
+ * Called via message whenever Policy.deleteNodes() is called in the parent.
+ */
+function onDeleteNodes(id, sender)
+{
+ storedNodes.delete(id);
+}
+
+/**
+ * Called via message whenever Policy.refilterNodes() is called in the parent.
+ */
+function onRefilterNodes({nodesID, entry}, sender)
+{
+ let nodes = storedNodes.get(nodesID);
+ if (nodes)
+ for (let node of nodes)
+ if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE)
+ Utils.runAsync(refilterNode.bind(this, node, entry));
+}
+
+/**
+ * Re-checks filters on an element.
+ */
+function refilterNode(/**Node*/ node, /**Object*/ entry)
+{
+ let wnd = Utils.getWindow(node);
+ if (!wnd || wnd.closed)
+ return;
+
+ if (entry.type == "OBJECT")
+ {
+ node.removeEventListener("mouseover", objectMouseEventHander, true);
+ node.removeEventListener("mouseout", objectMouseEventHander, true);
+ }
+
+ shouldAllow(wnd, node, entry.type, entry.location, (allow) => {
+ // Force node to be collapsed
+ if (!allow)
+ schedulePostProcess(node)
+ });
+}
+
+/**
+ * Actual nsIContentPolicy and nsIChannelEventSink implementation
+ * @class
+ */
+var PolicyImplementation =
+{
+ classDescription: "Adblock Plus content policy",
+ classID: Components.ID("cfeaabe6-1dd1-11b2-a0c6-cb5c268894c9"),
+ contractID: "@adblockplus.org/abp/policy;1",
+ xpcom_categories: ["content-policy", "net-channel-event-sinks"],
+
+ /**
+ * Registers the content policy on startup.
+ */
+ init: function()
+ {
+ // Populate types map
+ let iface = Ci.nsIContentPolicy;
+ for (let name in iface)
+ if (name.indexOf("TYPE_") == 0 && name != "TYPE_DATAREQUEST")
+ types.set(iface[name], name.substr(5));
+
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(this.classID, this.classDescription, this.contractID, this);
+
+ let catMan = Utils.categoryManager;
+ for (let category of this.xpcom_categories)
+ catMan.addCategoryEntry(category, this.contractID, this.contractID, false, true);
+
+ Services.obs.addObserver(this, "document-element-inserted", true);
+
+ onShutdown.add(() =>
+ {
+ Services.obs.removeObserver(this, "document-element-inserted");
+
+ for (let category of this.xpcom_categories)
+ catMan.deleteCategoryEntry(category, this.contractID, false);
+
+ registrar.unregisterFactory(this.classID, this);
+ });
+ },
+
+ //
+ // nsISupports interface implementation
+ //
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
+ Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]),
+
+ //
+ // nsIContentPolicy interface implementation
+ //
+
+ shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTypeGuess, extra)
+ {
+ // Ignore requests without context and top-level documents
+ if (!node || contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT)
+ return Ci.nsIContentPolicy.ACCEPT;
+
+ // Bail out early for chrome: an resource: URLs, this is a work-around for
+ // https://bugzil.la/1127744 and https://bugzil.la/1247640
+ let location = Utils.unwrapURL(contentLocation);
+ if (location.schemeIs("chrome") || location.schemeIs("resource"))
+ return Ci.nsIContentPolicy.ACCEPT;
+
+ // Ignore standalone objects
+ if (contentType == Ci.nsIContentPolicy.TYPE_OBJECT && node.ownerDocument && !/^text\/|[+\/]xml$/.test(node.ownerDocument.contentType))
+ return Ci.nsIContentPolicy.ACCEPT;
+
+ let wnd = Utils.getWindow(node);
+ if (!wnd)
+ return Ci.nsIContentPolicy.ACCEPT;
+
+ // Data loaded by plugins should be associated with the document
+ if (contentType == Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST && node instanceof Ci.nsIDOMElement)
+ node = node.ownerDocument;
+
+ // Fix type for objects misrepresented as frames or images
+ if (contentType != Ci.nsIContentPolicy.TYPE_OBJECT && (node instanceof Ci.nsIDOMHTMLObjectElement || node instanceof Ci.nsIDOMHTMLEmbedElement))
+ contentType = Ci.nsIContentPolicy.TYPE_OBJECT;
+
+ let result = shouldAllow(wnd, node, types.get(contentType), location.spec);
+ return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQUEST);
+ },
+
+ shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra)
+ {
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+
+ //
+ // nsIObserver interface implementation
+ //
+ _openers: new WeakMap(),
+ _alreadyLoaded: Symbol(),
+
+ observe: function(subject, topic, data, uri)
+ {
+ switch (topic)
+ {
+ case "document-element-inserted":
+ {
+ let window = subject.defaultView;
+ if (!window)
+ return;
+
+ let type = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .itemType;
+ if (type != Ci.nsIDocShellTreeItem.typeContent)
+ return;
+
+ let opener = this._openers.get(window);
+ if (opener == this._alreadyLoaded)
+ {
+ // This window has loaded already, ignore it regardless of whether
+ // window.opener is still set.
+ return;
+ }
+
+ if (opener && Cu.isDeadWrapper(opener))
+ opener = null;
+
+ if (!opener)
+ {
+ // We don't know the opener for this window yet, try to find it
+ opener = window.opener;
+ if (!opener)
+ return;
+
+ // The opener might be an intermediate window, get the real one
+ while (opener.location == "about:blank" && opener.opener)
+ opener = opener.opener;
+
+ this._openers.set(window, opener);
+
+ let forgetPopup = event =>
+ {
+ subject.removeEventListener("DOMContentLoaded", forgetPopup);
+ this._openers.set(window, this._alreadyLoaded);
+ };
+ subject.addEventListener("DOMContentLoaded", forgetPopup);
+ }
+
+ if (!uri)
+ uri = window.location.href;
+ if (!shouldAllow(opener, opener.document, "POPUP", uri))
+ {
+ window.stop();
+ Utils.runAsync(() => window.close());
+ }
+ else if (uri == "about:blank")
+ {
+ // An about:blank pop-up most likely means that a load will be
+ // initiated asynchronously. Wait for that.
+ Utils.runAsync(() =>
+ {
+ let channel = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIDocumentLoader)
+ .documentChannel;
+ if (channel)
+ this.observe(subject, topic, data, channel.URI.spec);
+ });
+ }
+ break;
+ }
+ }
+ },
+
+ //
+ // nsIChannelEventSink interface implementation
+ //
+
+ asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback)
+ {
+ let async = false;
+ try
+ {
+ // nsILoadInfo.contentPolicyType was introduced in Gecko 35, then
+ // renamed to nsILoadInfo.externalContentPolicyType in Gecko 44.
+ let loadInfo = oldChannel.loadInfo;
+ let contentType = ("externalContentPolicyType" in loadInfo ?
+ loadInfo.externalContentPolicyType : loadInfo.contentPolicyType);
+ if (!contentType)
+ return;
+
+ let wnd = getRequestWindow(newChannel);
+ if (!wnd)
+ return;
+
+ if (contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT)
+ {
+ if (wnd.history.length <= 1 && wnd.opener)
+ {
+ // Special treatment for pop-up windows - this will close the window
+ // rather than preventing the redirect. Note that we might not have
+ // seen the original channel yet because the redirect happened before
+ // the async code in observe() had a chance to run.
+ this.observe(wnd.document, "document-element-inserted", null, oldChannel.URI.spec);
+ this.observe(wnd.document, "document-element-inserted", null, newChannel.URI.spec);
+ }
+ return;
+ }
+
+ shouldAllowAsync(wnd, wnd.document, types.get(contentType), newChannel.URI.spec, function(allow)
+ {
+ callback.onRedirectVerifyCallback(allow ? Cr.NS_OK : Cr.NS_BINDING_ABORTED);
+ });
+ async = true;
+ }
+ catch (e)
+ {
+ // We shouldn't throw exceptions here - this will prevent the redirect.
+ Cu.reportError(e);
+ }
+ finally
+ {
+ if (!async)
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ }
+ },
+
+ //
+ // nsIFactory interface implementation
+ //
+
+ createInstance: function(outer, iid)
+ {
+ if (outer)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ }
+};
+PolicyImplementation.init();
+
+/**
+ * Nodes scheduled for post-processing (might be null).
+ * @type Node[]
+ */
+let scheduledNodes = null;
+
+/**
+ * Schedules a node for post-processing.
+ */
+function schedulePostProcess(/**Element*/ node)
+{
+ if (scheduledNodes)
+ scheduledNodes.push(node);
+ else
+ {
+ scheduledNodes = [node];
+ Utils.runAsync(postProcessNodes);
+ }
+}
+
+/**
+ * Processes nodes scheduled for post-processing (typically hides them).
+ */
+function postProcessNodes()
+{
+ collapsedClass.then(cls =>
+ {
+ let nodes = scheduledNodes;
+ scheduledNodes = null;
+
+ // Resolving class is async initially so the nodes might have already been
+ // processed in the meantime.
+ if (!nodes)
+ return;
+
+ for (let node of nodes)
+ {
+ // adjust frameset's cols/rows for frames
+ let parentNode = node.parentNode;
+ if (parentNode && parentNode instanceof Ci.nsIDOMHTMLFrameSetElement)
+ {
+ let hasCols = (parentNode.cols && parentNode.cols.indexOf(",") > 0);
+ let hasRows = (parentNode.rows && parentNode.rows.indexOf(",") > 0);
+ if ((hasCols || hasRows) && !(hasCols && hasRows))
+ {
+ let index = -1;
+ for (let frame = node; frame; frame = frame.previousSibling)
+ if (frame instanceof Ci.nsIDOMHTMLFrameElement || frame instanceof Ci.nsIDOMHTMLFrameSetElement)
+ index++;
+
+ let property = (hasCols ? "cols" : "rows");
+ let weights = parentNode[property].split(",");
+ weights[index] = "0";
+ parentNode[property] = weights.join(",");
+ }
+ }
+ else
+ node.classList.add(cls);
+ }
+ });
+}
diff --git a/data/extensions/spyblock@gnu.org/lib/child/contextMenu.js b/data/extensions/spyblock@gnu.org/lib/child/contextMenu.js
new file mode 100644
index 0000000..297ef3e
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/contextMenu.js
@@ -0,0 +1,137 @@
+/*
+ * 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/>.
+ */
+
+"use strict";
+
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+let {Utils} = require("utils");
+let {RequestNotifier} = require("child/requestNotifier");
+let {storeNodes} = require("child/contentPolicy");
+
+/**
+ * Determines the context menu entries to be shown for a contextmenu event.
+ * @param {Event} event
+ * @return {Array}
+ */
+function getContextInfo(event)
+{
+ let items = [];
+ let target = event.target;
+ if (target.localName == "menupopup" && target.triggerNode)
+ {
+ // SeaMonkey gives us the context menu's popupshowing event
+ target = target.triggerNode;
+ }
+ if (target instanceof Ci.nsIDOMHTMLMapElement || target instanceof Ci.nsIDOMHTMLAreaElement)
+ {
+ // HTML image maps will usually receive events when the mouse pointer is
+ // over a different element, get the real event target.
+ let rect = target.getClientRects()[0];
+ target = target.ownerDocument.elementFromPoint(Math.max(rect.left, 0), Math.max(rect.top, 0));
+ }
+
+ if (!target)
+ return items;
+
+ let addMenuItem = function([node, nodeData])
+ {
+ let nodeID = null;
+ if (node && node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE)
+ nodeID = storeNodes([node]);
+ items.push([nodeID, nodeData]);
+ }.bind(this);
+
+ // Look up data that we have for the node
+ let data = RequestNotifier.getDataForNode(target);
+ let hadImage = false;
+ if (data && !data[1].filter)
+ {
+ addMenuItem(data);
+ hadImage = (data[1].type == "IMAGE");
+ }
+
+ // Look for frame data
+ let wnd = Utils.getWindow(target);
+ if (wnd.frameElement)
+ {
+ let data = RequestNotifier.getDataForNode(wnd.frameElement, true);
+ if (data && !data[1].filter)
+ addMenuItem(data);
+ }
+
+ // Look for a background image
+ if (!hadImage)
+ {
+ let extractImageURL = function(computedStyle, property)
+ {
+ let value = computedStyle.getPropertyCSSValue(property);
+ // CSSValueList
+ if ("length" in value && value.length >= 1)
+ value = value[0];
+ // CSSValuePrimitiveType
+ if ("primitiveType" in value && value.primitiveType == value.CSS_URI)
+ return Utils.unwrapURL(value.getStringValue()).spec;
+
+ return null;
+ };
+
+ let node = target;
+ while (node)
+ {
+ if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE)
+ {
+ let style = wnd.getComputedStyle(node, "");
+ let bgImage = extractImageURL(style, "background-image") || extractImageURL(style, "list-style-image");
+ if (bgImage)
+ {
+ let data = RequestNotifier.getDataForNode(wnd.document, true, "IMAGE", bgImage);
+ if (data && !data[1].filter)
+ {
+ addMenuItem(data);
+ break;
+ }
+ }
+ }
+
+ node = node.parentNode;
+ }
+ }
+
+ return items;
+};
+
+let ContextMenuObserver =
+{
+ observe: function(subject, topic, data)
+ {
+ if (subject.wrappedJSObject)
+ subject = subject.wrappedJSObject;
+
+ if (subject.addonInfo)
+ subject.addonInfo.adblockplus = getContextInfo(subject.event);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver])
+};
+
+Services.obs.addObserver(ContextMenuObserver, "content-contextmenu", true);
+Services.obs.addObserver(ContextMenuObserver, "AdblockPlus:content-contextmenu", true);
+onShutdown.add(() => {
+ Services.obs.removeObserver(ContextMenuObserver, "content-contextmenu");
+ Services.obs.removeObserver(ContextMenuObserver, "AdblockPlus:content-contextmenu");
+});
diff --git a/data/extensions/spyblock@gnu.org/lib/child/dataCollector.js b/data/extensions/spyblock@gnu.org/lib/child/dataCollector.js
new file mode 100644
index 0000000..09c334a
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/dataCollector.js
@@ -0,0 +1,108 @@
+/*
+ * 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 Collects some data for a content window, to be attached to
+ * issue reports.
+ */
+
+"use strict";
+
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+let {PrivateBrowsingUtils} = Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", {});
+
+let {port} = require("messaging");
+let {Utils} = require("utils");
+
+port.on("collectData", onCollectData);
+
+function onCollectData({outerWindowID, screenshotWidth}, sender)
+{
+ let window = Services.wm.getOuterWindowWithId(outerWindowID);
+ if (window)
+ {
+ return Task.spawn(function*()
+ {
+ let data = {};
+ data.isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window);
+ data.opener = window.opener ? window.opener.location.href : null;
+ data.referrer = window.document.referrer;
+ data.frames = yield scanFrames(window);
+ data.screenshot = yield createScreenshot(window, screenshotWidth);
+ return data;
+ });
+ }
+}
+
+function scanFrames(window)
+{
+ let frames = [];
+ for (let i = 0; i < window.frames.length; i++)
+ {
+ let frame = window.frames[i];
+ frames.push({
+ url: frame.location.href,
+ frames: scanFrames(frame)
+ });
+ }
+ return frames;
+}
+
+function* createScreenshot(window, screenshotWidth)
+{
+ let canvas = window.document.createElement("canvas");
+ canvas.width = screenshotWidth;
+
+ let context = canvas.getContext("2d");
+ let wndWidth = window.document.documentElement.scrollWidth;
+ let wndHeight = window.document.documentElement.scrollHeight;
+
+ // Copy scaled screenshot of the webpage, according to the specified width.
+
+ // Gecko doesn't like sizes more than 64k, restrict to 30k to be on the safe side.
+ // Also, make sure height is at most five times the width to keep image size down.
+ let copyWidth = Math.min(wndWidth, 30000);
+ let copyHeight = Math.min(wndHeight, 30000, copyWidth * 5);
+ let copyX = Math.max(Math.min(window.scrollX - copyWidth / 2, wndWidth - copyWidth), 0);
+ let copyY = Math.max(Math.min(window.scrollY - copyHeight / 2, wndHeight - copyHeight), 0);
+
+ let scalingFactor = screenshotWidth / copyWidth;
+ canvas.height = copyHeight * scalingFactor;
+
+ context.save();
+ context.scale(scalingFactor, scalingFactor);
+ context.drawWindow(window, copyX, copyY, copyWidth, copyHeight, "rgb(255,255,255)");
+ context.restore();
+
+ // Reduce colors
+ let pixelData = context.getImageData(0, 0, canvas.width, canvas.height);
+ let data = pixelData.data;
+ let mapping = [0x00, 0x55, 0xAA, 0xFF];
+ for (let i = 0; i < data.length; i++)
+ {
+ data[i] = mapping[data[i] >> 6];
+
+ if (i % 5000 == 0)
+ {
+ // Take a break every 5000 bytes to prevent browser hangs
+ yield new Promise((resolve, reject) => Utils.runAsync(resolve));
+ }
+ }
+
+ return pixelData;
+}
diff --git a/data/extensions/spyblock@gnu.org/lib/child/elemHide.js b/data/extensions/spyblock@gnu.org/lib/child/elemHide.js
new file mode 100644
index 0000000..988adee
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/elemHide.js
@@ -0,0 +1,403 @@
+/*
+ * 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 Serves CSS for element hiding and processes hits.
+ */
+
+try
+{
+ // Hack: SDK loader masks our Components object with a getter.
+ let proto = Object.getPrototypeOf(this);
+ let property = Object.getOwnPropertyDescriptor(proto, "Components");
+ if (property && property.get)
+ delete proto.Components;
+}
+catch (e)
+{
+ Cu.reportError(e);
+}
+
+let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let {shouldAllowAsync} = require("child/contentPolicy");
+let {getFrames, isPrivate, getRequestWindow} = require("child/utils");
+let {RequestNotifier} = require("child/requestNotifier");
+let {port} = require("messaging");
+let {Utils} = require("utils");
+
+const notImplemented = () => Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+/**
+ * about: URL module used to count hits.
+ * @class
+ */
+let AboutHandler =
+{
+ classID: Components.ID("{55fb7be0-1dd2-11b2-98e6-9e97caf8ba67}"),
+ classDescription: "Element hiding hit registration protocol handler",
+ aboutPrefix: "abp-elemhide",
+
+ /**
+ * Registers handler on startup.
+ */
+ init: function()
+ {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(this.classID, this.classDescription,
+ "@mozilla.org/network/protocol/about;1?what=" + this.aboutPrefix, this);
+ onShutdown.add(function()
+ {
+ registrar.unregisterFactory(this.classID, this);
+ }.bind(this));
+ },
+
+ //
+ // Factory implementation
+ //
+
+ createInstance: function(outer, iid)
+ {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+
+ return this.QueryInterface(iid);
+ },
+
+ //
+ // About module implementation
+ //
+
+ getURIFlags: function(uri)
+ {
+ return Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT;
+ },
+
+ newChannel: function(uri, loadInfo)
+ {
+ let match = /\?hit(\d+)$/.exec(uri.path);
+ if (match)
+ return new HitRegistrationChannel(uri, loadInfo, match[1]);
+
+ match = /\?css(?:=(.*?))?(&specificonly)?$/.exec(uri.path);
+ if (match)
+ {
+ return new StyleDataChannel(uri, loadInfo,
+ match[1] ? decodeURIComponent(match[1]) : null, !!match[2]);
+ }
+
+ throw Cr.NS_ERROR_FAILURE;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory, Ci.nsIAboutModule])
+};
+AboutHandler.init();
+
+/**
+ * Base class for channel implementations, subclasses usually only need to
+ * override BaseChannel._getResponse() method.
+ * @constructor
+ */
+function BaseChannel(uri, loadInfo)
+{
+ this.URI = this.originalURI = uri;
+ this.loadInfo = loadInfo;
+}
+BaseChannel.prototype = {
+ URI: null,
+ originalURI: null,
+ contentCharset: "utf-8",
+ contentLength: 0,
+ contentType: null,
+ owner: Utils.systemPrincipal,
+ securityInfo: null,
+ notificationCallbacks: null,
+ loadFlags: 0,
+ loadGroup: null,
+ name: null,
+ status: Cr.NS_OK,
+
+ _getResponse: notImplemented,
+
+ _checkSecurity: function()
+ {
+ if (!this.loadInfo.triggeringPrincipal.equals(Utils.systemPrincipal))
+ throw Cr.NS_ERROR_FAILURE;
+ },
+
+ asyncOpen: function(listener, context)
+ {
+ Promise.resolve(this._getResponse()).then(data =>
+ {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(data, data.length);
+
+ try
+ {
+ listener.onStartRequest(this, context);
+ }
+ catch(e)
+ {
+ // Listener failing isn't our problem
+ }
+
+ try
+ {
+ listener.onDataAvailable(this, context, stream, 0, stream.available());
+ }
+ catch(e)
+ {
+ // Listener failing isn't our problem
+ }
+
+ try
+ {
+ listener.onStopRequest(this, context, Cr.NS_OK);
+ }
+ catch(e)
+ {
+ // Listener failing isn't our problem
+ }
+ });
+ },
+
+ asyncOpen2: function(listener)
+ {
+ this._checkSecurity();
+ this.asyncOpen(listener, null);
+ },
+
+ open: function()
+ {
+ let data = this._getResponse();
+ if (typeof data.then == "function")
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(data, data.length);
+ return stream;
+ },
+
+ open2: function()
+ {
+ this._checkSecurity();
+ return this.open();
+ },
+
+ isPending: () => false,
+ cancel: notImplemented,
+ suspend: notImplemented,
+ resume: notImplemented,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
+};
+
+/**
+ * Channel returning CSS data for the global as well as site-specific stylesheet.
+ * @constructor
+ */
+function StyleDataChannel(uri, loadInfo, domain, specificOnly)
+{
+ BaseChannel.call(this, uri, loadInfo);
+ this._domain = domain;
+ this._specificOnly = specificOnly;
+}
+StyleDataChannel.prototype = {
+ __proto__: BaseChannel.prototype,
+ contentType: "text/css",
+ _domain: null,
+
+ _getResponse: function()
+ {
+ function escapeChar(match)
+ {
+ return "\\" + match.charCodeAt(0).toString(16) + " ";
+ }
+
+ // Would be great to avoid sync messaging here but nsIStyleSheetService
+ // insists on opening channels synchronously.
+ let [selectors, keys] = (this._domain ?
+ port.emitSync("getSelectorsForDomain", [this._domain, this._specificOnly]) :
+ port.emitSync("getUnconditionalSelectors"));
+
+ let cssPrefix = "{-moz-binding: url(about:abp-elemhide?hit";
+ let cssSuffix = "#dummy) !important;}\n";
+ let result = [];
+
+ for (let i = 0; i < selectors.length; i++)
+ {
+ let selector = selectors[i];
+ let key = keys[i];
+ result.push(selector.replace(/[^\x01-\x7F]/g, escapeChar),
+ cssPrefix, key, cssSuffix);
+ }
+
+ return result.join("");
+ }
+};
+
+/**
+ * Channel returning data for element hiding hits.
+ * @constructor
+ */
+function HitRegistrationChannel(uri, loadInfo, key)
+{
+ BaseChannel.call(this, uri, loadInfo);
+ this.key = key;
+}
+HitRegistrationChannel.prototype = {
+ __proto__: BaseChannel.prototype,
+ key: null,
+ contentType: "text/xml",
+
+ _getResponse: function()
+ {
+ let window = getRequestWindow(this);
+ port.emitWithResponse("registerElemHideHit", {
+ key: this.key,
+ frames: getFrames(window),
+ isPrivate: isPrivate(window)
+ }).then(hit =>
+ {
+ if (hit)
+ RequestNotifier.addNodeData(window.document, window.top, hit);
+ });
+ return "<bindings xmlns='http://www.mozilla.org/xbl'/>";
+ }
+};
+
+let observer = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver, Ci.nsISupportsWeakReference
+ ]),
+
+ topic: "document-element-inserted",
+ styleURL: Utils.makeURI("about:abp-elemhide?css"),
+ sheet: null,
+
+ init: function()
+ {
+ Services.obs.addObserver(this, this.topic, true);
+ onShutdown.add(() =>
+ {
+ Services.obs.removeObserver(this, this.topic);
+ });
+
+ port.on("elemhideupdate", () =>
+ {
+ this.sheet = null;
+ });
+ },
+
+ observe: function(subject, topic, data)
+ {
+ if (topic != this.topic)
+ return;
+
+ let window = subject.defaultView;
+ if (!window)
+ {
+ // This is typically XBL bindings and SVG images, but also real
+ // documents occasionally - probably due to speculative loading?
+ return;
+ }
+ let type = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .itemType;
+ if (type != Ci.nsIDocShellTreeItem.typeContent)
+ return;
+
+ port.emitWithResponse("elemhideEnabled", {
+ frames: getFrames(window),
+ isPrivate: isPrivate(window)
+ }).then(({
+ enabled, contentType, docDomain, thirdParty, location, filter,
+ filterType
+ }) =>
+ {
+ if (Cu.isDeadWrapper(window))
+ {
+ // We are too late, the window is gone already.
+ return;
+ }
+
+ if (enabled)
+ {
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ // If we have a filter hit at this point then it must be a $generichide
+ // filter - apply only specific element hiding filters.
+ let specificOnly = !!filter;
+ if (!specificOnly)
+ {
+ if (!this.sheet)
+ {
+ this.sheet = Utils.styleService.preloadSheet(this.styleURL,
+ Ci.nsIStyleSheetService.USER_SHEET);
+ }
+
+ try
+ {
+ utils.addSheet(this.sheet, Ci.nsIStyleSheetService.USER_SHEET);
+ }
+ catch (e)
+ {
+ // Ignore NS_ERROR_ILLEGAL_VALUE - it will be thrown if we try to add
+ // the stylesheet multiple times to the same document (the observer
+ // will be notified twice for some documents).
+ if (e.result != Cr.NS_ERROR_ILLEGAL_VALUE)
+ throw e;
+ }
+ }
+
+ let host = window.location.hostname;
+ if (host)
+ {
+ try
+ {
+ let suffix = "=" + encodeURIComponent(host);
+ if (specificOnly)
+ suffix += "&specificonly";
+ utils.loadSheetUsingURIString(this.styleURL.spec + suffix,
+ Ci.nsIStyleSheetService.USER_SHEET);
+ }
+ catch (e)
+ {
+ // Ignore NS_ERROR_ILLEGAL_VALUE - it will be thrown if we try to add
+ // the stylesheet multiple times to the same document (the observer
+ // will be notified twice for some documents).
+ if (e.result != Cr.NS_ERROR_ILLEGAL_VALUE)
+ throw e;
+ }
+ }
+ }
+
+ if (filter)
+ {
+ RequestNotifier.addNodeData(window.document, window.top, {
+ contentType, docDomain, thirdParty, location, filter, filterType
+ });
+ }
+ });
+ }
+};
+observer.init();
diff --git a/data/extensions/spyblock@gnu.org/lib/child/elemHideEmulation.js b/data/extensions/spyblock@gnu.org/lib/child/elemHideEmulation.js
new file mode 100644
index 0000000..7c4ee17
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/elemHideEmulation.js
@@ -0,0 +1,118 @@
+/*
+ * 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/>.
+ */
+
+"use strict";
+
+(function()
+{
+ let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+ let {port} = require("messaging");
+ let {getFrames, isPrivate} = require("child/utils");
+ let {RequestNotifier} = require("child/requestNotifier");
+
+ function getFilters(window, callback)
+ {
+ let message = {
+ frames: getFrames(window),
+ payload: {
+ type: "filters.get",
+ what: "elemhideemulation"
+ }
+ };
+ port.emitWithResponse("ext_message", message).then(callback);
+ }
+
+ function addUserCSS(window, cssCode)
+ {
+ let uri = Services.io.newURI("data:text/css," + encodeURIComponent(cssCode),
+ null, null);
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ utils.loadSheet(uri, Ci.nsIDOMWindowUtils.USER_SHEET);
+ }
+
+ function initElemHideEmulation()
+ {
+ let scope = Object.assign({}, require("common"));
+ Services.scriptloader.loadSubScript(
+ "chrome://adblockplus/content/elemHideEmulation.js", scope);
+
+ let onContentWindow = (subject, topic, data) =>
+ {
+ if (!(subject instanceof Ci.nsIDOMWindow))
+ return;
+
+ let onReady = event =>
+ {
+ subject.removeEventListener("load", onReady);
+ let handler = new scope.ElemHideEmulation(
+ subject, getFilters.bind(null, subject), (selectors, filters) =>
+ {
+ if (selectors.length == 0)
+ return;
+
+ addUserCSS(subject, selectors.map(
+ selector => selector + "{display: none !important;}"
+ ).join("\n"));
+
+ if (!isPrivate(subject))
+ port.emit("addHits", filters);
+
+ let docDomain = null;
+ try
+ {
+ // We are calling getFrames() here because it will consider
+ // "inheritance" for about:blank and data: frames.
+ docDomain = new URL(getFrames(subject)[0].location).hostname;
+ }
+ catch (e)
+ {
+ // Invalid URL?
+ }
+
+ for (let i = 0; i < filters.length; i++)
+ {
+ RequestNotifier.addNodeData(subject.document, subject.top, {
+ contentType: "ELEMHIDE",
+ docDomain: docDomain,
+ thirdParty: false,
+ location: "##" + selectors[i],
+ filter: filters[i],
+ filterType: "elemhideemulation"
+ });
+ }
+ }
+ );
+
+ handler.apply();
+ };
+
+ subject.addEventListener("load", onReady);
+ };
+
+ Services.obs.addObserver(onContentWindow, "content-document-global-created",
+ false);
+ onShutdown.add(() =>
+ {
+ Services.obs.removeObserver(onContentWindow,
+ "content-document-global-created");
+ });
+ }
+
+ initElemHideEmulation();
+})();
diff --git a/data/extensions/spyblock@gnu.org/lib/child/flasher.js b/data/extensions/spyblock@gnu.org/lib/child/flasher.js
new file mode 100644
index 0000000..492f4e0
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/flasher.js
@@ -0,0 +1,99 @@
+/*
+ * 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 Draws a blinking border for a list of matching elements.
+ */
+
+function Flasher(elements, scrollToItem)
+{
+ if (scrollToItem && elements[0].ownerDocument)
+ {
+ // Ensure that at least one element is visible when flashing
+ elements[0].scrollIntoView();
+ }
+
+ this.elements = elements;
+ this.count = 0;
+
+ this.doFlash();
+
+}
+Flasher.prototype =
+{
+ elements: null,
+ count: 0,
+ timer: null,
+
+ doFlash: function()
+ {
+ if (this.count >= 12)
+ {
+ this.stop();
+ return;
+ }
+
+ if (this.count % 2)
+ this.switchOff();
+ else
+ this.switchOn();
+
+ this.count++;
+
+ this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.timer.initWithCallback(() => this.doFlash(), 300, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ stop: function()
+ {
+ if (this.timer)
+ {
+ this.timer.cancel();
+ this.timer = null;
+ }
+
+ if (this.elements)
+ {
+ this.switchOff();
+ this.elements = null;
+ }
+ },
+
+ setOutline: function(outline, offset)
+ {
+ for (let element of this.elements)
+ {
+ if (!Cu.isDeadWrapper(element) && "style" in element)
+ {
+ element.style.outline = outline;
+ element.style.outlineOffset = offset;
+ }
+ }
+ },
+
+ switchOn: function()
+ {
+ this.setOutline("#CC0000 dotted 2px", "-2px");
+ },
+
+ switchOff: function()
+ {
+ this.setOutline("", "");
+ }
+};
+
+exports.Flasher = Flasher;
diff --git a/data/extensions/spyblock@gnu.org/lib/child/main.js b/data/extensions/spyblock@gnu.org/lib/child/main.js
new file mode 100644
index 0000000..bc21e9a
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/main.js
@@ -0,0 +1,31 @@
+/*
+ * 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/>.
+ */
+
+let {port} = require("messaging");
+
+// Only initialize after receiving a "response" to a dummy message - this makes
+// sure that on update the old version has enough time to receive and process
+// the shutdown message.
+port.emitWithResponse("ping").then(() =>
+{
+ require("child/elemHide");
+ require("child/contentPolicy");
+ require("child/contextMenu");
+ require("child/dataCollector");
+ require("child/elemHideEmulation");
+ require("child/subscribeLinks");
+}).catch(e => Cu.reportError(e));
diff --git a/data/extensions/spyblock@gnu.org/lib/child/objectTabs.js b/data/extensions/spyblock@gnu.org/lib/child/objectTabs.js
new file mode 100644
index 0000000..74e7387
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/objectTabs.js
@@ -0,0 +1,405 @@
+/*
+ * 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 Code responsible for showing and hiding object tabs.
+ */
+
+let {port} = require("messaging");
+
+/**
+ * Class responsible for showing and hiding object tabs.
+ * @class
+ */
+var objTabs =
+{
+ /**
+ * Number of milliseconds to wait until hiding tab after the mouse moves away.
+ * @type Integer
+ */
+ HIDE_DELAY: 1000,
+
+ /**
+ * Document element the object tab is currently being displayed for.
+ * @type Element
+ */
+ currentElement: null,
+
+ /**
+ * Windows that the window event handler is currently registered for.
+ * @type Window[]
+ */
+ windowListeners: null,
+
+ /**
+ * Panel element currently used as object tab.
+ * @type Element
+ */
+ objtabElement: null,
+
+ /**
+ * Time of previous position update.
+ * @type Integer
+ */
+ prevPositionUpdate: 0,
+
+ /**
+ * Timer used to update position of the object tab.
+ * @type nsITimer
+ */
+ positionTimer: null,
+
+ /**
+ * Timer used to delay hiding of the object tab.
+ * @type nsITimer
+ */
+ hideTimer: null,
+
+ /**
+ * Used when hideTimer is running, time when the tab should be hidden.
+ * @type Integer
+ */
+ hideTargetTime: 0,
+
+ /**
+ * Localized texts and class names to be used for the tab. This will be set
+ * when showTabFor is called for the first time.
+ * @type Object
+ */
+ texts: null,
+
+ /**
+ * Called to show object tab for an element.
+ */
+ showTabFor: function(/**Element*/ element)
+ {
+ // Object tabs aren't usable in Fennec
+ let {application} = require("info");
+ if (application == "fennec" || application == "fennec2" ||
+ application == "adblockbrowser")
+ return;
+
+ if (!this.texts)
+ this.texts = port.emitWithResponse("getObjectTabsTexts");
+ Promise.all([port.emitWithResponse("getObjectTabsStatus"), this.texts])
+ .then(([status, texts]) =>
+ {
+ this.texts = texts;
+ if (!status)
+ return;
+
+ if (this.hideTimer)
+ {
+ this.hideTimer.cancel();
+ this.hideTimer = null;
+ }
+
+ if (this.objtabElement)
+ this.objtabElement.style.setProperty("opacity", "1", "important");
+
+ if (this.currentElement != element)
+ {
+ this._hideTab();
+
+ let {RequestNotifier} = require("child/requestNotifier");
+ let data = RequestNotifier.getDataForNode(element, true, "OBJECT");
+ if (data)
+ this._showTab(element, data[1]);
+ }
+ });
+ },
+
+ /**
+ * Called to hide object tab for an element (actual hiding happens delayed).
+ */
+ hideTabFor: function(/**Element*/ element)
+ {
+ if (element != this.currentElement || this.hideTimer)
+ return;
+
+ this.hideTargetTime = Date.now() + this.HIDE_DELAY;
+ this.hideTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.hideTimer.init(this, 40, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ },
+
+ /**
+ * Makes the tab element visible.
+ * @param {Element} element
+ * @param {RequestEntry} data
+ */
+ _showTab: function(element, data)
+ {
+ let doc = element.ownerDocument.defaultView.top.document;
+
+ this.objtabElement = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
+ this.objtabElement.textContent = this.texts.label;
+ this.objtabElement.setAttribute("title", this.texts.tooltip);
+ this.objtabElement.setAttribute("href", data.location);
+ this.objtabElement.setAttribute("class", this.texts.classHidden);
+ this.objtabElement.style.setProperty("opacity", "1", "important");
+ this.objtabElement.nodeData = data;
+
+ this.currentElement = element;
+
+ // Register paint listeners for the relevant windows
+ this.windowListeners = [];
+ let wnd = element.ownerDocument.defaultView;
+ while (wnd)
+ {
+ wnd.addEventListener("MozAfterPaint", objectWindowEventHandler, false);
+ this.windowListeners.push(wnd);
+ wnd = (wnd.parent != wnd ? wnd.parent : null);
+ }
+
+ // Register mouse listeners on the object tab
+ this.objtabElement.addEventListener("mouseover", objectTabEventHander, false);
+ this.objtabElement.addEventListener("mouseout", objectTabEventHander, false);
+ this.objtabElement.addEventListener("click", objectTabEventHander, true);
+
+ // Insert the tab into the document and adjust its position
+ doc.documentElement.appendChild(this.objtabElement);
+ if (!this.positionTimer)
+ {
+ this.positionTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.positionTimer.init(this, 200, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ }
+ this._positionTab();
+ },
+
+ /**
+ * Hides the tab element.
+ */
+ _hideTab: function()
+ {
+ if (this.objtabElement)
+ {
+ // Prevent recursive calls via popuphidden handler
+ let objtab = this.objtabElement;
+ this.objtabElement = null;
+ this.currentElement = null;
+
+ if (this.hideTimer)
+ {
+ this.hideTimer.cancel();
+ this.hideTimer = null;
+ }
+
+ if (this.positionTimer)
+ {
+ this.positionTimer.cancel();
+ this.positionTimer = null;
+ }
+
+ try {
+ objtab.parentNode.removeChild(objtab);
+ } catch (e) {}
+ objtab.removeEventListener("mouseover", objectTabEventHander, false);
+ objtab.removeEventListener("mouseout", objectTabEventHander, false);
+ objtab.nodeData = null;
+
+ for (let wnd of this.windowListeners)
+ wnd.removeEventListener("MozAfterPaint", objectWindowEventHandler, false);
+ this.windowListeners = null;
+ }
+ },
+
+ /**
+ * Updates position of the tab element.
+ */
+ _positionTab: function()
+ {
+ // Test whether element is still in document
+ let elementDoc = null;
+ try
+ {
+ elementDoc = this.currentElement.ownerDocument;
+ } catch (e) {} // Ignore "can't access dead object" error
+ if (!elementDoc || !this.currentElement.offsetWidth || !this.currentElement.offsetHeight ||
+ !elementDoc.defaultView || !elementDoc.documentElement)
+ {
+ this._hideTab();
+ return;
+ }
+
+ let objRect = this._getElementPosition(this.currentElement);
+
+ let className = this.texts.classVisibleTop;
+ let left = objRect.right - this.objtabElement.offsetWidth;
+ let top = objRect.top - this.objtabElement.offsetHeight;
+ if (top < 0)
+ {
+ top = objRect.bottom;
+ className = this.texts.classVisibleBottom;
+ }
+
+ if (this.objtabElement.style.left != left + "px")
+ this.objtabElement.style.setProperty("left", left + "px", "important");
+ if (this.objtabElement.style.top != top + "px")
+ this.objtabElement.style.setProperty("top", top + "px", "important");
+
+ if (this.objtabElement.getAttribute("class") != className)
+ this.objtabElement.setAttribute("class", className);
+
+ this.prevPositionUpdate = Date.now();
+ },
+
+ /**
+ * Calculates element's position relative to the top frame and considering
+ * clipping due to scrolling.
+ * @return {{left: Number, top: Number, right: Number, bottom: Number}}
+ */
+ _getElementPosition: function(/**Element*/ element)
+ {
+ // Restrict rectangle coordinates by the boundaries of a window's client area
+ function intersectRect(rect, wnd)
+ {
+ // Cannot use wnd.innerWidth/Height because they won't account for scrollbars
+ let doc = wnd.document;
+ let wndWidth = doc.documentElement.clientWidth;
+ let wndHeight = doc.documentElement.clientHeight;
+ if (doc.compatMode == "BackCompat") // clientHeight will be bogus in quirks mode
+ wndHeight = Math.max(doc.documentElement.offsetHeight, doc.body.offsetHeight) - wnd.scrollMaxY - 1;
+
+ rect.left = Math.max(rect.left, 0);
+ rect.top = Math.max(rect.top, 0);
+ rect.right = Math.min(rect.right, wndWidth);
+ rect.bottom = Math.min(rect.bottom, wndHeight);
+ }
+
+ let rect = element.getBoundingClientRect();
+ let wnd = element.ownerDocument.defaultView;
+
+ let style = wnd.getComputedStyle(element, null);
+ let offsets = [
+ parseFloat(style.borderLeftWidth) + parseFloat(style.paddingLeft),
+ parseFloat(style.borderTopWidth) + parseFloat(style.paddingTop),
+ parseFloat(style.borderRightWidth) + parseFloat(style.paddingRight),
+ parseFloat(style.borderBottomWidth) + parseFloat(style.paddingBottom)
+ ];
+
+ rect = {left: rect.left + offsets[0], top: rect.top + offsets[1],
+ right: rect.right - offsets[2], bottom: rect.bottom - offsets[3]};
+ while (true)
+ {
+ intersectRect(rect, wnd);
+
+ if (!wnd.frameElement)
+ break;
+
+ // Recalculate coordinates to be relative to frame's parent window
+ let frameElement = wnd.frameElement;
+ wnd = frameElement.ownerDocument.defaultView;
+
+ let frameRect = frameElement.getBoundingClientRect();
+ let frameStyle = wnd.getComputedStyle(frameElement, null);
+ let relLeft = frameRect.left + parseFloat(frameStyle.borderLeftWidth) + parseFloat(frameStyle.paddingLeft);
+ let relTop = frameRect.top + parseFloat(frameStyle.borderTopWidth) + parseFloat(frameStyle.paddingTop);
+
+ rect.left += relLeft;
+ rect.right += relLeft;
+ rect.top += relTop;
+ rect.bottom += relTop;
+ }
+
+ return rect;
+ },
+
+ doBlock: function()
+ {
+ let {storeNodes} = require("child/contentPolicy");
+ let nodesID = storeNodes([this.currentElement]);
+ port.emit("blockItem", {
+ request: this.objtabElement.nodeData,
+ nodesID
+ });
+ },
+
+ /**
+ * Called whenever a timer fires.
+ * @param {nsISupport} subject
+ * @param {string} topic
+ * @param {string} data
+ */
+ observe: function(subject, topic, data)
+ {
+ if (subject == this.positionTimer)
+ {
+ // Don't update position if it was already updated recently (via MozAfterPaint)
+ if (Date.now() - this.prevPositionUpdate > 100)
+ this._positionTab();
+ }
+ else if (subject == this.hideTimer)
+ {
+ let now = Date.now();
+ if (now >= this.hideTargetTime)
+ this._hideTab();
+ else if (this.hideTargetTime - now < this.HIDE_DELAY / 2)
+ this.objtabElement.style.setProperty("opacity", (this.hideTargetTime - now) * 2 / this.HIDE_DELAY, "important");
+ }
+ }
+};
+
+onShutdown.add(objTabs._hideTab.bind(objTabs));
+
+/**
+ * Function called whenever the mouse enters or leaves an object.
+ */
+function objectMouseEventHander(/**Event*/ event)
+{
+ if (!event.isTrusted)
+ return;
+
+ if (event.type == "mouseover")
+ objTabs.showTabFor(event.target);
+ else if (event.type == "mouseout")
+ objTabs.hideTabFor(event.target);
+}
+
+/**
+ * Function called for paint events of the object tab window.
+ */
+function objectWindowEventHandler(/**Event*/ event)
+{
+ if (!event.isTrusted)
+ return;
+
+ // Don't trigger update too often, avoid overusing CPU on frequent page updates
+ if (event.type == "MozAfterPaint" && Date.now() - objTabs.prevPositionUpdate > 20)
+ objTabs._positionTab();
+}
+
+/**
+ * Function called whenever the mouse enters or leaves an object tab.
+ */
+function objectTabEventHander(/**Event*/ event)
+{
+ if (onShutdown.done || !event.isTrusted)
+ return;
+
+ if (event.type == "click" && event.button == 0)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ objTabs.doBlock();
+ }
+ else if (event.type == "mouseover")
+ objTabs.showTabFor(objTabs.currentElement);
+ else if (event.type == "mouseout")
+ objTabs.hideTabFor(objTabs.currentElement);
+}
+exports.objectMouseEventHander = objectMouseEventHander;
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;
+};
diff --git a/data/extensions/spyblock@gnu.org/lib/child/subscribeLinks.js b/data/extensions/spyblock@gnu.org/lib/child/subscribeLinks.js
new file mode 100644
index 0000000..a2e729d
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/subscribeLinks.js
@@ -0,0 +1,118 @@
+/*
+ * 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/>.
+ */
+
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let {port} = require("messaging");
+
+Services.obs.addObserver(onContentWindow, "content-document-global-created",
+ false);
+onShutdown.add(() =>
+{
+ Services.obs.removeObserver(onContentWindow,
+ "content-document-global-created");
+});
+
+function onContentWindow(subject, topic, data)
+{
+ if (subject instanceof Ci.nsIDOMWindow && subject.top == subject)
+ {
+ let eventTarget = subject.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ if (eventTarget)
+ eventTarget.addEventListener("click", onClick, true);
+ }
+}
+
+function onClick(event)
+{
+ if (onShutdown.done)
+ return;
+
+ // Ignore right-clicks
+ if (event.button == 2)
+ return;
+
+ // Search the link associated with the click
+ let link = event.target;
+ while (!(link instanceof Ci.nsIDOMHTMLAnchorElement))
+ {
+ link = link.parentNode;
+
+ if (!link)
+ return;
+ }
+
+ let queryString = null;
+ if (link.protocol == "http:" || link.protocol == "https:")
+ {
+ if (link.host == "subscribe.adblockplus.org" && link.pathname == "/")
+ queryString = link.search.substr(1);
+ }
+ else
+ {
+ // Firefox doesn't populate the "search" property for links with
+ // non-standard URL schemes so we need to extract the query string
+ // manually
+ let match = /^abp:\/*subscribe\/*\?(.*)/i.exec(link.href);
+ if (match)
+ queryString = match[1];
+ }
+
+ if (!queryString)
+ return;
+
+ // This is our link - make sure the browser doesn't handle it
+ event.preventDefault();
+ event.stopPropagation();
+
+ // Decode URL parameters
+ let title = null;
+ let url = null;
+ let mainSubscriptionTitle = null;
+ let mainSubscriptionURL = null;
+ for (let param of queryString.split("&"))
+ {
+ let parts = param.split("=", 2);
+ if (parts.length != 2 || !/\S/.test(parts[1]))
+ continue;
+ switch (parts[0])
+ {
+ case "title":
+ title = decodeURIComponent(parts[1]);
+ break;
+ case "location":
+ url = decodeURIComponent(parts[1]);
+ break;
+ case "requiresTitle":
+ mainSubscriptionTitle = decodeURIComponent(parts[1]);
+ break;
+ case "requiresLocation":
+ mainSubscriptionURL = decodeURIComponent(parts[1]);
+ break;
+ }
+ }
+
+ port.emit("subscribeLinkClick", {
+ title: title,
+ url: url,
+ mainSubscriptionTitle: mainSubscriptionTitle,
+ mainSubscriptionURL: mainSubscriptionURL
+ });
+}
diff --git a/data/extensions/spyblock@gnu.org/lib/child/utils.js b/data/extensions/spyblock@gnu.org/lib/child/utils.js
new file mode 100644
index 0000000..fde649f
--- /dev/null
+++ b/data/extensions/spyblock@gnu.org/lib/child/utils.js
@@ -0,0 +1,141 @@
+/*
+ * 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/>.
+ */
+
+"use strict";
+
+let {PrivateBrowsingUtils} = Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", {});
+
+let {Utils} = require("utils");
+
+/**
+ * Retrieves the effective location of a window.
+ */
+let getWindowLocation = exports.getWindowLocation = function(/**Window*/ window) /**String*/
+{
+ let result = null;
+
+ // Crazy Thunderbird stuff
+ if ("name" in window && window.name == "messagepane")
+ {
+ try
+ {
+ let mailWnd = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+
+ // Typically we get a wrapped mail window here, need to unwrap
+ try
+ {
+ mailWnd = mailWnd.wrappedJSObject;
+ } catch(e) {}
+
+ if ("currentHeaderData" in mailWnd && "content-base" in mailWnd.currentHeaderData)
+ {
+ result = mailWnd.currentHeaderData["content-base"].headerValue;
+ }
+ else if ("currentHeaderData" in mailWnd && "from" in mailWnd.currentHeaderData)
+ {
+ let emailAddress = Utils.headerParser.extractHeaderAddressMailboxes(mailWnd.currentHeaderData.from.headerValue);
+ if (emailAddress)
+ result = 'mailto:' + emailAddress.replace(/^[\s"]+/, "").replace(/[\s"]+$/, "").replace(/\s/g, '%20');
+ }
+ } catch(e) {}
+ }
+
+ // Sane branch
+ if (!result)
+ result = window.location.href;
+
+ // Remove the anchor if any
+ let index = result.indexOf("#");
+ if (index >= 0)
+ result = result.substring(0, index);
+
+ return result;
+}
+
+/**
+ * Retrieves the frame hierarchy for a window. Returns an array containing
+ * the information for all frames, starting with the window itself up to its
+ * top-level window. Each entry has a location and a sitekey entry.
+ * @return {Array}
+ */
+let getFrames = exports.getFrames = function(/**Window*/ window)
+{
+ let frames = [];
+ while (window)
+ {
+ let frame = {
+ location: getWindowLocation(window),
+ sitekey: null
+ };
+
+ let documentElement = window.document && window.document.documentElement;
+ if (documentElement)
+ frame.sitekey = documentElement.getAttribute("data-adblockkey")
+
+ frames.push(frame);
+ window = (window != window.parent ? window.parent : null);
+ }
+
+ // URLs like about:blank inherit their security context from upper-level
+ // frames, resolve their URLs accordingly.
+ for (let i = frames.length - 2; i >= 0; i--)
+ {
+ let frame = frames[i];
+ if (frame.location == "about:blank" || frame.location == "moz-safe-about:blank" ||
+ frame.location == "about:srcdoc" ||
+ Utils.netUtils.URIChainHasFlags(Utils.makeURI(frame.location), Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT))
+ {
+ frame.location = frames[i + 1].location;
+ }
+ }
+
+ return frames;
+};
+
+/**
+ * Checks whether Private Browsing mode is enabled for a content window.
+ * @return {Boolean}
+ */
+let isPrivate = exports.isPrivate = function(/**Window*/ window)
+{
+ return PrivateBrowsingUtils.isContentWindowPrivate(window);
+};
+
+/**
+ * Gets the DOM window associated with a particular request (if any).
+ */
+let getRequestWindow = exports.getRequestWindow = function(/**nsIChannel*/ channel) /**nsIDOMWindow*/
+{
+ try
+ {
+ if (channel.notificationCallbacks)
+ return channel.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
+ } catch(e) {}
+
+ try
+ {
+ if (channel.loadGroup && channel.loadGroup.notificationCallbacks)
+ return channel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
+ } catch(e) {}
+
+ return null;
+};