diff options
Diffstat (limited to 'data/extensions/spyblock@gnu.org/lib/child')
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; +}; |