/* * This file is part of Adblock Plus , * 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 . */ /** * @fileOverview Various application-specific functions. */ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/AddonManager.jsm"); /** * Checks whether an application window is known and should get Adblock Plus * user interface elements. * @result Boolean */ exports.isKnownWindow = (/**Window*/ window) => false; /** * HACK: In some applications the window finishes initialization during load * event processing which makes an additional delay necessary. This flag * indicates that. * @type Boolean */ exports.delayInitialization = false; /** * Retrieves the browser element for an application window. * @type function(window) */ exports.getBrowser = null; /** * Adds a new browser tab in the given application window. * @type function(window, url, event) */ exports.addTab = null; /** * Retrieves the current browser location for an application window. */ exports.getCurrentLocation = function getCurrentLocation(/**Window*/ window) /**nsIURI|String*/ { let browser = (exports.getBrowser ? exports.getBrowser(window) : null); return (browser ? browser.currentURI : null); } /** * The ID (or a list of possible IDs) of the content area context menu. * @type String|String[] */ exports.contentContextMenu = null; /** * Determines the default placement of the toolbar icon via object properties * parent, before and after. * @type Object */ exports.defaultToolbarPosition = null; /** * The properties parent, before, after determine the placement of the status * bar icon. * @type Object */ exports.statusbarPosition = null; /** * The properties parent, before, after determine the placement of the Tools * submenu. * @type Object */ exports.toolsMenu = null; /** * Maps windows to their bottom bar info. */ let bottomBars = new WeakMap(); /** * Adds a bottom bar to the application window. * @type function(window, element) */ exports.addBottomBar = null; function _addBottomBar(window, parent, element) { if (bottomBars.has(window) || !parent) return null; let bar = {elements: []}; for (let child = element.firstElementChild; child; child = child.nextElementSibling) { let clone = child.cloneNode(true); parent.appendChild(clone); bar.elements.push(clone); } bottomBars.set(window, bar); return bar; }; /** * Removes the bottom bar from the application window. * @type function(window) */ exports.removeBottomBar = null; function _removeBottomBar(window) { if (!bottomBars.has(window)) return null; let bar = bottomBars.get(window); for (let i = 0; i < bar.elements.length; i++) if (bar.elements[i].parentNode) bar.elements[i].parentNode.removeChild(bar.elements[i]); bottomBars.delete(window); return bar; }; /** * Maps windows to a list of progress listeners. */ let progressListeners = new WeakMap(); /** * Makes sure that a function is called whenever the displayed browser location changes. */ exports.addBrowserLocationListener = function addBrowserLocationListener(/**Window*/ window, /**Function*/ callback, /**Boolean*/ ignoreSameDoc) { let browser = (exports.getBrowser ? exports.getBrowser(window) : null); if (browser) { let dummy = function() {}; let progressListener = { callback: callback, onLocationChange: function(progress, request, uri, flags) { if (!ignoreSameDoc || !flags || !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) this.callback(); }, onProgressChange: dummy, onSecurityChange: dummy, onStateChange: dummy, onStatusChange: dummy, QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]) }; browser.addProgressListener(progressListener); if (progressListeners.has(window)) progressListeners.get(window).push(progressListener); else progressListeners.set(window, [progressListener]); } }; /** * Removes a location listener registered for a window. */ exports.removeBrowserLocationListener = function removeBrowserLocationListener(/**Window*/ window, /**Function*/ callback) { if (!progressListeners.has(window)) return; let browser = (exports.getBrowser ? exports.getBrowser(window) : null); let listeners = progressListeners.get(window); for (let i = 0; i < listeners.length; i++) { if (listeners[i].callback == callback) { if (browser) browser.removeProgressListener(listeners[i]); listeners.splice(i--, 1); } } }; /** * Removes all location listeners registered for a window, to be called on * cleanup. */ exports.removeBrowserLocationListeners = function removeBrowserLocationListeners(/**Window*/ window) { if (!progressListeners.has(window)) return; let browser = (exports.getBrowser ? exports.getBrowser(window) : null); if (browser) { let listeners = progressListeners.get(window); for (let i = 0; i < listeners.length; i++) browser.removeProgressListener(listeners[i]); } progressListeners.delete(window); }; let {application} = require("info"); switch (application) { case "firefox": { exports.isKnownWindow = function ff_isKnownWindow(window) { return (window.document.documentElement.getAttribute("windowtype") == "navigator:browser"); }; exports.getBrowser = (window) => window.gBrowser; exports.addTab = function ff_addTab(window, url, event) { if (event) window.openNewTabWith(url, null, null, event, false); else window.gBrowser.loadOneTab(url, {inBackground: false}); }; exports.contentContextMenu = "contentAreaContextMenu"; exports.defaultToolbarPosition = { parent: "nav-bar" }; exports.toolsMenu = { parent: "menu_ToolsPopup" }; exports.addBottomBar = function fx_addBottomBar(window, element) { let bar = _addBottomBar(window, window.document.getElementById("appcontent"), element); if (bar) { let display = window.document.getElementById("statusbar-display"); bar.changedFixed = display && !display.hasAttribute("fixed"); if (bar.changedFixed) display.setAttribute("fixed", "true"); } }; exports.removeBottomBar = function fx_removeBottomBar(window) { let bar = _removeBottomBar(window); if (bar && bar.changedFixed) window.document.getElementById("statusbar-display").removeAttribute("fixed"); }; break; } case "seamonkey": { exports.isKnownWindow = function sm_isKnownWindow(window) { let type = window.document.documentElement.getAttribute("windowtype"); return (type == "navigator:browser" || type == "mail:3pane" || type == "mail:messageWindow"); }; exports.addTab = function sm_addTab(window, url, event) { if (event || !("gBrowser" in window)) window.openNewTabWith(url, null, null, event, false); else window.gBrowser.loadOneTab(url, {inBackground: false}); }; exports.getBrowser = function sm_getBrowser(window) { if ("gBrowser" in window) return window.gBrowser; else if ("getMessageBrowser" in window) return window.getMessageBrowser(); else return null; }; exports.getCurrentLocation = function sm_getCurrentLocation(window) { if ("currentHeaderData" in window && "content-base" in window.currentHeaderData) { // This is a blog entry return window.currentHeaderData["content-base"].headerValue; } else if ("currentHeaderData" in window && "from" in window.currentHeaderData) { // This is a mail/newsgroup entry try { let headerParser = Cc["@mozilla.org/messenger/headerparser;1"].getService(Ci.nsIMsgHeaderParser); let emailAddress = headerParser.extractHeaderAddressMailboxes(window.currentHeaderData.from.headerValue); return "mailto:" + emailAddress.replace(/^[\s"]+/, "").replace(/[\s"]+$/, "").replace(/\s/g, "%20"); } catch(e) { return null; } } else { let browser = exports.getBrowser(window); return (browser ? browser.currentURI : null); } }; // for Seamonkey we have to ignore same document flag because of // bug #1035171 (https://bugzilla.mozilla.org/show_bug.cgi?id=1035171) let origAddBrowserLocationListener = exports.addBrowserLocationListener; exports.addBrowserLocationListener = function sm_addBrowserLocationListener(window, callback, ignoreSameDoc) { origAddBrowserLocationListener(window, callback, false); }; exports.contentContextMenu = ["contentAreaContextMenu", "mailContext"]; exports.defaultToolbarPosition = { parent: ["PersonalToolbar", "msgToolbar"], before: ["bookmarks-button", "button-junk"] }; exports.statusbarPosition = { parent: "status-bar" }; exports.toolsMenu = { parent: "taskPopup", after: "downloadmgr" }; exports.addBottomBar = function sm_addBottomBar(window, element) { _addBottomBar(window, window.document.getElementById("appcontent") || window.document.getElementById("messagepanebox"), element); }; exports.removeBottomBar = _removeBottomBar; break; } case "thunderbird": { exports.isKnownWindow = function tb_isKnownWindow(window) { let type = window.document.documentElement.getAttribute("windowtype"); return (type == "mail:3pane" || type == "mail:messageWindow"); }; exports.delayInitialization = true; exports.getBrowser = (window) => window.getBrowser(); exports.addTab = function tb_addTab(window, url, event) { let tabmail = window.document.getElementById("tabmail"); if (!tabmail) { let wnd = Services.wm.getMostRecentWindow("mail:3pane"); if (window) tabmail = wnd.document.getElementById("tabmail"); } if (tabmail) tabmail.openTab("contentTab", {contentPage: url}); else { window.openDialog("chrome://messenger/content/", "_blank", "chrome,dialog=no,all", null, { tabType: "contentTab", tabParams: {contentPage: url} }); } }; exports.contentContextMenu = ["mailContext", "pageContextMenu"]; exports.defaultToolbarPosition = { parent: "header-view-toolbar", before: "hdrReplyButton", addClass: "msgHeaderView-button" }; exports.statusbarPosition = { parent: "status-bar" }; exports.toolsMenu = { parent: "taskPopup", after: "javaScriptConsole" }; exports.getCurrentLocation = function getCurrentLocation(window) { let browser = exports.getBrowser(window); if (!browser) return null; if (browser.id == "messagepane" && "currentHeaderData" in window && "content-base" in window.currentHeaderData) { // This is a blog entry return window.currentHeaderData["content-base"].headerValue; } else if (browser.id == "messagepane" && "currentHeaderData" in window && "from" in window.currentHeaderData) { // This is a mail/newsgroup entry try { let headerParser = Cc["@mozilla.org/messenger/headerparser;1"].getService(Ci.nsIMsgHeaderParser); let emailAddress = headerParser.extractHeaderAddressMailboxes(window.currentHeaderData.from.headerValue); return "mailto:" + emailAddress.replace(/^[\s"]+/, "").replace(/[\s"]+$/, "").replace(/\s/g, "%20"); } catch(e) { return null; } } else return browser.currentURI; } exports.addBottomBar = function tb_addBottomBar(window, element) { let browser = exports.getBrowser(window); if (!browser) return; let parent = window.document.getElementById("messagepanebox"); if (!parent || !(parent.compareDocumentPosition(browser) & Ci.nsIDOMNode.DOCUMENT_POSITION_CONTAINED_BY)) parent = browser.parentNode; _addBottomBar(window, parent, element); }; exports.removeBottomBar = _removeBottomBar; let BrowserChangeListener = function(window, callback) { this.window = window; this.callback = callback; this.onSelect = this.onSelect.bind(this); this.attach(); }; BrowserChangeListener.prototype = { window: null, callback: null, currentBrowser: null, setBrowser: function(browser) { if (browser != this.currentBrowser) { let oldBrowser = this.currentBrowser; this.currentBrowser = browser; this.callback(oldBrowser, browser); } }, onSelect: function() { this.setBrowser(exports.getBrowser(this.window)); }, attach: function() { this.onSelect(); let tabmail = this.window.document.getElementById("tabmail"); if (tabmail) tabmail.tabContainer.addEventListener("select", this.onSelect, false); }, detach: function() { let tabmail = this.window.document.getElementById("tabmail"); if (tabmail) tabmail.tabContainer.removeEventListener("select", this.onSelect, false); this.setBrowser(null); } }; exports.addBrowserLocationListener = function(/**Window*/ window, /**Function*/ callback, /**Boolean*/ ignoreSameDoc) { if (progressListeners.has(window)) { progressListeners.get(window).locationCallbacks.push(callback); return; } let callbacks = [callback]; let dummy = function() {}; let progressListener = { onLocationChange: function(progress, request, uri, flags) { if (!ignoreSameDoc || !flags || !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) for (let i = 0; i < callbacks.length; i++) callbacks[i](); }, onProgressChange: dummy, onSecurityChange: dummy, onStateChange: dummy, onStatusChange: dummy, QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]) }; let messageListener = { onStartHeaders: dummy, onEndHeaders: function() { let browser = exports.getBrowser(window); if (browser.id == "messagepane") for (let i = 0; i < callbacks.length; i++) callbacks[i](); }, onEndAttachments: dummy, onBeforeShowHeaderPane: dummy }; let listener = new BrowserChangeListener(window, function(oldBrowser, newBrowser) { if (oldBrowser) oldBrowser.removeProgressListener(progressListener); if (newBrowser) newBrowser.addProgressListener(progressListener); progressListener.onLocationChange(); }); listener.locationCallbacks = callbacks; if ("gMessageListeners" in window) window.gMessageListeners.push(messageListener); listener.messageListener = messageListener; progressListeners.set(window, listener); }; exports.removeBrowserLocationListener = function(/**Window*/ window, /**Function*/ callback) { if (!progressListeners.has(window)) return; let callbacks = progressListeners.get(window).locationCallbacks; for (let i = 0; i < callbacks.length; i++) if (callbacks[i] == callback) callbacks.splice(i--, 1); }; exports.removeBrowserLocationListeners = function(/**Window*/ window) { if (!progressListeners.has(window)) return; let listener = progressListeners.get(window); let messageListener = listener.messageListener; let index = ("gMessageListeners" in window ? window.gMessageListeners.indexOf(messageListener) : -1); if (index >= 0) window.gMessageListeners.splice(index, 1); listener.detach(); progressListeners.delete(window); }; // Make sure to close/reopen list of blockable items when the user changes tabs let {WindowObserver} = require("windowObserver"); new WindowObserver({ listeners: new WeakMap(), applyToWindow: function(window) { if (!exports.isKnownWindow(window) || this.listeners.has(window)) return; let {Utils} = require("utils"); Utils.runAsync(function() { let listener = new BrowserChangeListener(window, function(oldBrowser, newBrowser) { if (bottomBars.has(window)) { let {UI} = require("ui") UI.toggleBottombar(window); UI.toggleBottombar(window); } }); this.listeners.set(window, listener); }.bind(this)); }, removeFromWindow: function(window) { if (!this.listeners.has(window)) return; let listener = this.listeners.get(window); listener.detach(); this.listeners.delete(window); } }); break; } case "fennec2": case "adblockbrowser": { exports.isKnownWindow = (window) => window.document.documentElement.id == "main-window"; exports.getBrowser = (window) => window.BrowserApp.selectedBrowser; exports.addTab = (window, url, event) => window.BrowserApp.addTab(url, {selected: true}); let BrowserChangeListener = function(window, callback) { this.window = window; this.callback = callback; this.onSelect = this.onSelect.bind(this); this.attach = this.attach.bind(this); if (window.BrowserApp.deck) this.attach(); else window.addEventListener("UIReady", this.attach, false); }; BrowserChangeListener.prototype = { window: null, callback: null, currentBrowser: null, setBrowser: function(browser) { if (browser != this.currentBrowser) { let oldBrowser = this.currentBrowser; this.currentBrowser = browser; this.callback(oldBrowser, browser); } }, onSelect: function() { let {Utils} = require("utils"); Utils.runAsync(function() { this.setBrowser(exports.getBrowser(this.window)); }.bind(this)); }, attach: function() { this.window.removeEventListener("UIReady", this.attach, false); this.onSelect(); this.window.BrowserApp.deck.addEventListener("TabSelect", this.onSelect, false); }, detach: function() { this.window.BrowserApp.deck.removeEventListener("TabSelect", this.onSelect, false); this.setBrowser(null); } }; exports.addBrowserLocationListener = function ffn_addBrowserLocationListener(/**Window*/ window, /**Function*/ callback, /**Boolean*/ ignoreSameDoc) { if (progressListeners.has(window)) { progressListeners.get(window).locationCallbacks.push(callback); return; } let callbacks = [callback]; let dummy = function() {}; let progressListener = { onLocationChange: function(progress, request, uri, flags) { if (!ignoreSameDoc || !flags || !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) for (let i = 0; i < callbacks.length; i++) callbacks[i](); }, onProgressChange: dummy, onSecurityChange: dummy, onStateChange: dummy, onStatusChange: dummy, QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]) }; let listener = new BrowserChangeListener(window, function(oldBrowser, newBrowser) { if (oldBrowser && typeof oldBrowser.removeProgressListener == "function") oldBrowser.removeProgressListener(progressListener); if (newBrowser && typeof newBrowser.removeProgressListener == "function") newBrowser.addProgressListener(progressListener); progressListener.onLocationChange(); }); listener.locationCallbacks = callbacks; progressListeners.set(window, listener); }; exports.removeBrowserLocationListener = function ffn_removeBrowserLocationListener(/**Window*/ window, /**Function*/ callback) { if (!progressListeners.has(window)) return; let callbacks = progressListeners.get(window).locationCallbacks; for (let i = 0; i < callbacks.length; i++) if (callbacks[i] == callback) callbacks.splice(i--, 1); }; exports.removeBrowserLocationListeners = function ffn_removeBrowserLocationListeners(/**Window*/ window) { if (!progressListeners.has(window)) return; let listener = progressListeners.get(window); listener.detach(); progressListeners.delete(window); }; let {Filter} = require("filterClasses"); let {Prefs} = require("prefs"); let {Policy} = require("contentPolicy"); let {UI} = require("ui"); let {Utils} = require("utils"); let toggleWhitelist = function(window) { if (!Prefs.enabled) { Prefs.enabled = true; return; } let location = exports.getCurrentLocation(window); let host = null; if (location instanceof Ci.nsIURL && Policy.isBlockableScheme(location)) { try { host = location.host.replace(/^www\./, ""); } catch (e) {} } if (!host) return; if (Policy.isWhitelisted(location.spec)) UI.removeWhitelist(window); else UI.toggleFilter(Filter.fromText("@@||" + host + "^$document")); }; let menuItem = null; onShutdown.add(function() { let window = null; for (window of UI.applicationWindows) break; if (window && menuItem) window.NativeWindow.menu.remove(menuItem); }); UI.updateIconState = function fmn_updateIconState(window, icon) { if (menuItem !== null) { window.NativeWindow.menu.remove(menuItem); menuItem = null; } let action; let host = null; if (Prefs.enabled) { let location = exports.getCurrentLocation(window); if (location instanceof Ci.nsIURL && Policy.isBlockableScheme(location)) { try { host = location.host.replace(/^www\./, ""); } catch (e) {} } if (!host) return; if (host && Policy.isWhitelisted(location.spec)) action = "enable_site"; else if (host) action = "disable_site"; } else action = "enable"; let actionText = Utils.getString("mobile_menu_" + action); if (host) actionText = actionText.replace(/\?1\?/g, host); let iconUrl = require("info").addonRoot + "icon64.png"; menuItem = window.NativeWindow.menu.add(actionText, iconUrl, toggleWhitelist.bind(null, window)); }; UI.openSubscriptionDialog = function(window, url, title, mainURL, mainTitle) { let dialogTitle = this.overlay.attributes.subscriptionDialogTitle; let dialogMessage = this.overlay.attributes.subscriptionDialogMessage.replace(/\?1\?/, title).replace(/\?2\?/, url); if (Utils.confirm(window, dialogMessage, dialogTitle)) this.setSubscription(url, title); }; UI.openFiltersDialog = function() { let window = UI.currentWindow; if (!window) return let browser = exports.addTab(window, "about:addons").browser; browser.addEventListener("load", function openAddonPrefs(event) { browser.removeEventListener("load", openAddonPrefs, true); Utils.runAsync(function() { // The page won't be ready until the add-on manager data is loaded so we call this method // to know when the data will be ready. AddonManager.getAddonsByTypes(["extension", "theme", "locale"], function() { let event = new Event("Event"); event.initEvent("popstate", true, false); event.state = {id: require("info").addonID}; browser._contentWindow.dispatchEvent(event); }); }); }, true); }; break; } }