mirror of https://github.com/roytam1/boc-uxp.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1667 lines
51 KiB
1667 lines
51 KiB
/* |
|
* This Source Code is subject to the terms of the Mozilla Public License |
|
* version 2.0 (the "License"). You can obtain a copy of the License at |
|
* http://mozilla.org/MPL/2.0/. |
|
*/ |
|
|
|
#filter substitution |
|
|
|
/** |
|
* @fileOverview Application integration module, will keep track of application |
|
* windows and handle the necessary events. |
|
*/ |
|
|
|
var EXPORTED_SYMBOLS = ["AppIntegration"]; |
|
|
|
const Cc = Components.classes; |
|
const Ci = Components.interfaces; |
|
const Cr = Components.results; |
|
const Cu = Components.utils; |
|
|
|
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/"; |
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
Cu.import("resource://gre/modules/Services.jsm"); |
|
Cu.import(baseURL + "TimeLine.jsm"); |
|
Cu.import(baseURL + "Utils.jsm"); |
|
Cu.import(baseURL + "Prefs.jsm"); |
|
Cu.import(baseURL + "ContentPolicy.jsm"); |
|
Cu.import(baseURL + "FilterListener.jsm"); |
|
Cu.import(baseURL + "FilterStorage.jsm"); |
|
Cu.import(baseURL + "FilterNotifier.jsm"); |
|
Cu.import(baseURL + "FilterClasses.jsm"); |
|
Cu.import(baseURL + "SubscriptionClasses.jsm"); |
|
Cu.import(baseURL + "RequestNotifier.jsm"); |
|
Cu.import(baseURL + "Synchronizer.jsm"); |
|
/* Cu.import(baseURL + "Sync.jsm"); */ |
|
|
|
/** |
|
* Wrappers for tracked application windows. |
|
* @type Array of WindowWrapper |
|
*/ |
|
let wrappers = []; |
|
|
|
/** |
|
* Stores the selected hotkeys, initialized when the first browser window opens. |
|
*/ |
|
let hotkeys = null; |
|
|
|
/** |
|
* Object observing add-on manager notifications about add-on options being initialized. |
|
* @type nsIObserver |
|
*/ |
|
let optionsObserver = |
|
{ |
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), |
|
observe: function(subject, topic, data) |
|
{ |
|
if (data != Utils.addonID) |
|
return; |
|
|
|
initOptionsDoc(subject.QueryInterface(Ci.nsIDOMDocument)); |
|
} |
|
}; |
|
|
|
/** |
|
* Initializes app integration module |
|
*/ |
|
function init() |
|
{ |
|
// Process preferences |
|
reloadPrefs(); |
|
|
|
// Listen for pref and filters changes |
|
Prefs.addListener(function(name) |
|
{ |
|
if (name == "enabled" || name == "showinstatusbar" || name == "defaulttoolbaraction" || name == "defaultstatusbaraction") |
|
reloadPrefs(); |
|
}); |
|
FilterNotifier.addListener(function(action) |
|
{ |
|
if (/^(filter|subscription)\.(added|removed|disabled|updated)$/.test(action) || action == "load") |
|
reloadPrefs(); |
|
}); |
|
Services.obs.addObserver(optionsObserver, "addon-options-displayed", true); |
|
} |
|
|
|
/** |
|
* Exported app integration functions. |
|
* @class |
|
*/ |
|
var AppIntegration = |
|
{ |
|
/** |
|
* Adds an application window to the tracked list. |
|
*/ |
|
addWindow: function(/**Window*/ window) |
|
{ |
|
let hooks = window.document.getElementById("abp-hooks"); |
|
if (!hooks) |
|
return; |
|
|
|
TimeLine.enter("Entered AppIntegration.addWindow()") |
|
// Execute first-run actions |
|
if (!("lastVersion" in Prefs)) |
|
{ |
|
Prefs.lastVersion = Prefs.currentVersion; |
|
|
|
// Show subscriptions dialog if the user doesn't have any subscriptions yet |
|
if (Prefs.currentVersion != Utils.addonVersion) |
|
{ |
|
Prefs.currentVersion = Utils.addonVersion; |
|
|
|
if ("nsISessionStore" in Ci) |
|
{ |
|
// Have to wait for session to be restored |
|
let observer = |
|
{ |
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), |
|
observe: function(subject, topic, data) |
|
{ |
|
Services.obs.removeObserver(observer, "sessionstore-windows-restored"); |
|
timer.cancel(); |
|
timer = null; |
|
addSubscription(); |
|
} |
|
}; |
|
|
|
Services.obs.addObserver(observer, "sessionstore-windows-restored", false); |
|
|
|
// Just in case, don't wait more than a second |
|
let timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer); |
|
timer.init(observer, 1000, Ci.nsITimer.TYPE_ONE_SHOT); |
|
} |
|
else |
|
addSubscription(); |
|
} |
|
} |
|
TimeLine.log("App-wide first-run actions done") |
|
|
|
let wrapper = new WindowWrapper(window, hooks); |
|
wrappers.push(wrapper); |
|
TimeLine.leave("AppIntegration.addWindow() done") |
|
}, |
|
|
|
/** |
|
* Retrieves the wrapper object corresponding to a particular application window. |
|
*/ |
|
getWrapperForWindow: function(/**Window*/ wnd) /**WindowWrapper*/ |
|
{ |
|
for each (let wrapper in wrappers) |
|
if (wrapper.window == wnd) |
|
return wrapper; |
|
|
|
return null; |
|
}, |
|
|
|
/** |
|
* Toggles the value of a boolean preference. |
|
*/ |
|
togglePref: function(/**String*/ pref) |
|
{ |
|
Prefs[pref] = !Prefs[pref]; |
|
}, |
|
|
|
/** |
|
* Toggles the pref for the Adblock Plus sync engine. |
|
* @return {Boolean} new state of the sync engine |
|
*/ |
|
/** toggleSync: function() |
|
{ |
|
let syncEngine = Sync.getEngine(); |
|
syncEngine.enabled = !syncEngine.enabled; |
|
return syncEngine.enabled; |
|
}, |
|
*/ |
|
|
|
/** |
|
* Adds or removes the Adblock Plus toolbar icon. |
|
* @return {Boolean} new state of the toolbar button |
|
*/ |
|
toggleToolbarIcon: function() |
|
{ |
|
if (!wrappers.length) |
|
return false; |
|
|
|
let newVal = !wrappers[0].isToolbarIconVisible(); |
|
for (let i = 0; i < wrappers.length; i++) |
|
{ |
|
if (newVal) |
|
wrappers[i].installToolbarIcon(); |
|
else |
|
wrappers[i].hideToolbarIcon(); |
|
} |
|
return wrappers[0].isToolbarIconVisible(); |
|
}, |
|
|
|
/** |
|
* If the given filter is already in user's list, removes it from the list. Otherwise adds it. |
|
*/ |
|
toggleFilter: function(/**Filter*/ filter) |
|
{ |
|
if (filter.subscriptions.length) |
|
{ |
|
if (filter.disabled || filter.subscriptions.some(function(subscription) !(subscription instanceof SpecialSubscription))) |
|
filter.disabled = !filter.disabled; |
|
else |
|
FilterStorage.removeFilter(filter); |
|
} |
|
else |
|
FilterStorage.addFilter(filter); |
|
}, |
|
|
|
/** |
|
* Opens ABP menu. |
|
*/ |
|
openMenu: function(window) |
|
{ |
|
let wrapper = AppIntegration.getWrapperForWindow(window.top); |
|
if (!wrapper) |
|
{ |
|
// Maybe we got a content window |
|
window = window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
.getInterface(Ci.nsIWebNavigation) |
|
.QueryInterface(Ci.nsIDocShellTreeItem) |
|
.rootTreeItem |
|
.QueryInterface(Ci.nsIInterfaceRequestor) |
|
.getInterface(Ci.nsIDOMWindow); |
|
if (window.wrappedJSObject) |
|
window = window.wrappedJSObject; |
|
wrapper = AppIntegration.getWrapperForWindow(window); |
|
} |
|
if (!wrapper) |
|
{ |
|
// Try to find any known window |
|
let enumerator = Utils.windowMediator.getZOrderDOMWindowEnumerator(null, true); |
|
if (!enumerator.hasMoreElements()) |
|
{ |
|
// On Linux the list returned will be empty, see bug 156333. Fall back to random order. |
|
enumerator = Utils.windowMediator.getEnumerator(null); |
|
} |
|
while (enumerator.hasMoreElements()) |
|
{ |
|
window = enumerator.getNext().QueryInterface(Ci.nsIDOMWindow); |
|
wrapper = AppIntegration.getWrapperForWindow(window); |
|
if (wrapper) |
|
break; |
|
} |
|
} |
|
|
|
if (wrapper) |
|
Utils.runAsync(wrapper.openMenu, wrapper); |
|
} |
|
}; |
|
|
|
/** |
|
* Removes an application window from the tracked list. |
|
*/ |
|
function removeWindow() |
|
{ |
|
let wnd = this; |
|
|
|
for (let i = 0; i < wrappers.length; i++) |
|
if (wrappers[i].window == wnd) |
|
wrappers.splice(i--, 1); |
|
} |
|
|
|
/** |
|
* Class providing various functions related to application windows. |
|
* @constructor |
|
*/ |
|
function WindowWrapper(window, hooks) |
|
{ |
|
TimeLine.enter("Entered WindowWrapper constructor") |
|
this.window = window; |
|
|
|
this.initializeHooks(hooks); |
|
TimeLine.log("Hooks element initialized") |
|
|
|
this.fixupMenus(); |
|
TimeLine.log("Context menu copying done") |
|
|
|
this.configureKeys(); |
|
TimeLine.log("Shortcut keys configured") |
|
|
|
this.initContextMenu(); |
|
TimeLine.log("Context menu initialized") |
|
|
|
let browser = this.getBrowser(); |
|
if (browser && browser.currentURI) |
|
{ |
|
this.updateState(); |
|
} |
|
else |
|
{ |
|
// Update state asynchronously, the Thunderbird window won't be initialized yet for non-default window layouts |
|
Utils.runAsync(this.updateState, this); |
|
} |
|
TimeLine.log("Icon state updated") |
|
|
|
// Some people actually switch off browser.frames.enabled and are surprised |
|
// that things stop working... |
|
window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
.getInterface(Ci.nsIWebNavigation) |
|
.QueryInterface(Ci.nsIDocShell) |
|
.allowSubframes = true; |
|
|
|
this.registerEventListeners(); |
|
TimeLine.log("Added event listeners") |
|
|
|
this.executeFirstRunActions(); |
|
TimeLine.log("Window-specific first-run actions done") |
|
|
|
TimeLine.leave("WindowWrapper constructor done") |
|
} |
|
WindowWrapper.prototype = |
|
{ |
|
/** |
|
* Application window this object belongs to. |
|
* @type Window |
|
*/ |
|
window: null, |
|
|
|
/** |
|
* Current state as displayed for this window. |
|
* @type String |
|
*/ |
|
state: null, |
|
|
|
/** |
|
* Methods that can be defined at attributes of the hooks element. |
|
* @type Array of String |
|
*/ |
|
customMethods: ["getBrowser", "addTab", "getContextMenu", "getToolbox", "getDefaultToolbar", "toolbarInsertBefore"], |
|
|
|
/** |
|
* Progress listener used to watch for location changes, if any. |
|
* @type nsIProgressListener |
|
*/ |
|
progressListener: null, |
|
|
|
/** |
|
* Filter corresponding with "disable on site" menu item (set in fillPopup()). |
|
* @type Filter |
|
*/ |
|
siteWhitelist: null, |
|
/** |
|
* Filter corresponding with "disable on site" menu item (set in fillPopup()). |
|
* @type Filter |
|
*/ |
|
pageWhitelist: null, |
|
|
|
/** |
|
* Data associated with the node currently under mouse pointer (set in updateContextMenu()). |
|
* @type RequestEntry |
|
*/ |
|
nodeData: null, |
|
/** |
|
* The document node that nodeData belongs to. |
|
*/ |
|
currentNode: null, |
|
/** |
|
* Data associated with the background image currently under mouse pointer (set in updateContextMenu()). |
|
* @type RequestEntry |
|
*/ |
|
backgroundData: null, |
|
/** |
|
* Data associated with the frame currently under mouse pointer (set in updateContextMenu()). |
|
* @type RequestEntry |
|
*/ |
|
frameData: null, |
|
/** |
|
* The frame that frameData belongs to. |
|
*/ |
|
currentFrame: null, |
|
|
|
/** |
|
* Window of the detached list of blockable items (might be null or closed). |
|
* @type Window |
|
*/ |
|
detachedSidebar: null, |
|
|
|
/** |
|
* Binds a function to the object, ensuring that "this" pointer is always set |
|
* correctly. |
|
*/ |
|
_bindMethod: function(/**Function*/ method) /**Function*/ |
|
{ |
|
let me = this; |
|
return function() method.apply(me, arguments); |
|
}, |
|
|
|
/** |
|
* Retrieves an element by its ID. |
|
*/ |
|
E: function(/**String*/ id) |
|
{ |
|
let doc = this.window.document; |
|
this.E = function(id) doc.getElementById(id); |
|
return this.E(id); |
|
}, |
|
|
|
/** |
|
* Initializes abp-hooks element, converts any function attributes to actual |
|
* functions. |
|
*/ |
|
initializeHooks: function(hooks) |
|
{ |
|
for each (let hook in this.customMethods) |
|
{ |
|
let handler = hooks.getAttribute(hook); |
|
this[hook] = hooks[hook] = (handler ? this._bindMethod(new Function(handler)) : null); |
|
} |
|
}, |
|
|
|
/** |
|
* Makes a copy of the ABP icon's context menu for the toolbar button. |
|
*/ |
|
fixupMenus: function() |
|
{ |
|
function fixId(node, newId) |
|
{ |
|
if (node.nodeType == node.ELEMENT_NODE) |
|
{ |
|
if (node.hasAttribute("id")) |
|
node.setAttribute("id", node.getAttribute("id").replace(/abp-status/, newId)); |
|
|
|
for (let i = 0, len = node.childNodes.length; i < len; i++) |
|
fixId(node.childNodes[i], newId); |
|
} |
|
return node; |
|
} |
|
|
|
let menuSource = this.E("abp-status-popup"); |
|
let paletteButton = this.getPaletteButton(); |
|
let toolbarButton = this.E("abp-toolbarbutton"); |
|
let menuItem = this.E("abp-menuitem"); |
|
if (toolbarButton) |
|
toolbarButton.appendChild(fixId(menuSource.cloneNode(true), "abp-toolbar")); |
|
if (paletteButton && paletteButton != toolbarButton) |
|
paletteButton.appendChild(fixId(menuSource.cloneNode(true), "abp-toolbar")); |
|
if (menuItem) |
|
menuItem.appendChild(fixId(menuSource.cloneNode(true), "abp-menuitem")); |
|
}, |
|
|
|
/** |
|
* Attaches event listeners to a window represented by hooks element |
|
*/ |
|
registerEventListeners: function() |
|
{ |
|
// Palette button elements aren't reachable by ID, create a lookup table |
|
let paletteButtonIDs = {}; |
|
let paletteButton = this.getPaletteButton(); |
|
if (paletteButton) |
|
{ |
|
function getElementIds(element) |
|
{ |
|
if (element.hasAttribute("id")) |
|
paletteButtonIDs[element.getAttribute("id")] = element; |
|
|
|
for (let child = element.firstChild; child; child = child.nextSibling) |
|
if (child.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) |
|
getElementIds(child); |
|
} |
|
getElementIds(paletteButton); |
|
} |
|
|
|
// Go on and register listeners |
|
this.window.addEventListener("unload", removeWindow, false); |
|
for each (let [id, event, handler] in this.eventHandlers) |
|
{ |
|
handler = this._bindMethod(handler); |
|
|
|
let element = this.E(id); |
|
if (element) |
|
element.addEventListener(event, handler, false); |
|
|
|
if (id in paletteButtonIDs) |
|
paletteButtonIDs[id].addEventListener(event, handler, false); |
|
} |
|
|
|
let browser = this.getBrowser(); |
|
browser.addEventListener("click", this._bindMethod(this.handleLinkClick), true); |
|
|
|
// Register progress listener as well if requested |
|
if (!("isDummy" in this.updateState)) |
|
{ |
|
let dummy = function() {}; |
|
this.progressListener = |
|
{ |
|
onLocationChange: this._bindMethod(this.updateState), |
|
onProgressChange: dummy, |
|
onSecurityChange: dummy, |
|
onStateChange: dummy, |
|
onStatusChange: dummy, |
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]) |
|
}; |
|
browser.addProgressListener(this.progressListener); |
|
} |
|
}, |
|
|
|
/** |
|
* Retrieves the current location of the browser (might return null on failure). |
|
*/ |
|
getCurrentLocation: function() /**nsIURI*/ |
|
{ |
|
if ("currentHeaderData" in this.window && "content-base" in this.window.currentHeaderData) |
|
{ |
|
// Thunderbird blog entry |
|
return Utils.unwrapURL(this.window.currentHeaderData["content-base"].headerValue); |
|
} |
|
else if ("currentHeaderData" in this.window && "from" in this.window.currentHeaderData) |
|
{ |
|
// Thunderbird mail/newsgroup entry |
|
try |
|
{ |
|
let headerParser = Cc["@mozilla.org/messenger/headerparser;1"].getService(Ci.nsIMsgHeaderParser); |
|
let emailAddress = headerParser.extractHeaderAddressMailboxes(this.window.currentHeaderData.from.headerValue); |
|
return Utils.makeURI("mailto:" + emailAddress.replace(/^[\s"]+/, "").replace(/[\s"]+$/, "").replace(/\s/g, "%20")); |
|
} |
|
catch(e) |
|
{ |
|
return null; |
|
} |
|
} |
|
else |
|
{ |
|
// Regular browser |
|
return Utils.unwrapURL(this.getBrowser().currentURI.clone()); |
|
} |
|
}, |
|
|
|
/** |
|
* Executes window-specific first-run actions if necessary. |
|
*/ |
|
executeFirstRunActions: function() |
|
{ |
|
// Only execute first-run actions for this window once |
|
if ("doneFirstRunActions " + this.window.location.href in Prefs) |
|
return; |
|
Prefs["doneFirstRunActions " + this.window.location.href] = true; |
|
|
|
// Check version we previously executed first-run actions for; |
|
let hooks = this.E("abp-hooks"); |
|
let lastVersion = hooks.getAttribute("currentVersion") || "0.0"; |
|
if (lastVersion != Prefs.currentVersion) |
|
{ |
|
hooks.setAttribute("currentVersion", Prefs.currentVersion); |
|
this.window.document.persist("abp-hooks", "currentVersion"); |
|
|
|
let needInstall = (Utils.versionComparator.compare(lastVersion, "0.0") <= 0); |
|
if (!needInstall) |
|
{ |
|
// Before version 1.1 we didn't add toolbar icon in SeaMonkey, do it now |
|
needInstall = Utils.appID == "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}" && |
|
Utils.versionComparator.compare(lastVersion, "1.1") < 0; |
|
} |
|
|
|
// Add ABP icon to toolbar if necessary |
|
if (needInstall) |
|
Utils.runAsync(this.installToolbarIcon, this); |
|
} |
|
}, |
|
|
|
/** |
|
* Finds the toolbar button in the toolbar palette. |
|
*/ |
|
getPaletteButton: function() |
|
{ |
|
let toolbox = (this.getToolbox ? this.getToolbox() : null); |
|
if (!toolbox || !("palette" in toolbox) || !toolbox.palette) |
|
return null; |
|
|
|
for (var child = toolbox.palette.firstChild; child; child = child.nextSibling) |
|
if (child.id == "abp-toolbarbutton") |
|
return child; |
|
|
|
return null; |
|
}, |
|
|
|
/** |
|
* Updates displayed state for an application window. |
|
*/ |
|
updateState: function() |
|
{ |
|
let state = (Prefs.enabled ? "active" : "disabled"); |
|
|
|
if (state == "active") |
|
{ |
|
let location = this.getCurrentLocation(); |
|
if (location && Policy.isWhitelisted(location.spec)) |
|
state = "whitelisted"; |
|
} |
|
this.state = state; |
|
|
|
function updateElement(element) |
|
{ |
|
if (!element) |
|
return; |
|
|
|
if (element.tagName == "statusbarpanel") |
|
element.hidden = !Prefs.showinstatusbar; |
|
else |
|
{ |
|
if (element.hasAttribute("context") && Prefs.defaulttoolbaraction == 0) |
|
element.setAttribute("type", "menu"); |
|
else |
|
element.setAttribute("type", "menu-button"); |
|
} |
|
|
|
element.setAttribute("abpstate", state); |
|
}; |
|
|
|
let status = this.E("abp-status"); |
|
if (status) |
|
{ |
|
updateElement.call(this, status); |
|
if (Prefs.defaultstatusbaraction == 0) |
|
status.setAttribute("popup", status.getAttribute("context")); |
|
else |
|
status.removeAttribute("popup"); |
|
} |
|
|
|
let button = this.E("abp-toolbarbutton"); |
|
if (button) |
|
updateElement.call(this, button); |
|
|
|
updateElement.call(this, this.getPaletteButton()); |
|
}, |
|
|
|
/** |
|
* Sets up hotkeys for the window. |
|
*/ |
|
configureKeys: function() |
|
{ |
|
if (!hotkeys) |
|
{ |
|
hotkeys = {__proto__: null}; |
|
|
|
let validModifiers = |
|
{ |
|
accel: 1, |
|
shift: 2, |
|
ctrl: 4, |
|
control: 4, |
|
alt: 8, |
|
meta: 16, |
|
__proto__: null |
|
}; |
|
|
|
try |
|
{ |
|
let accelKey = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch).getIntPref("ui.key.accelKey"); |
|
if (accelKey == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL) |
|
validModifiers.ctrl = validModifiers.control = validModifiers.accel; |
|
else if (accelKey == Ci.nsIDOMKeyEvent.DOM_VK_ALT) |
|
validModifiers.alt = validModifiers.accel; |
|
else if (accelKey == Ci.nsIDOMKeyEvent.DOM_VK_META) |
|
validModifiers.meta = validModifiers.accel; |
|
} |
|
catch(e) |
|
{ |
|
Cu.reportError(e); |
|
} |
|
|
|
// Find which hotkeys are already taken, convert them to canonical form |
|
let existing = {}; |
|
let keys = this.window.document.getElementsByTagName("key"); |
|
for (let i = 0; i < keys.length; i++) |
|
{ |
|
let key = keys[i]; |
|
let keyChar = key.getAttribute("key"); |
|
let keyCode = key.getAttribute("keycode"); |
|
if (!keyChar && !keyCode) |
|
continue; |
|
|
|
let modifiers = 0; |
|
let keyModifiers = key.getAttribute("modifiers"); |
|
if (keyModifiers) |
|
{ |
|
for each (let modifier in keyModifiers.match(/\w+/g)) |
|
{ |
|
modifier = modifier.toLowerCase(); |
|
if (modifier in validModifiers) |
|
modifiers |= validModifiers[modifier] |
|
} |
|
|
|
let canonical = modifiers + " " + (keyChar || keyCode).toUpperCase(); |
|
existing[canonical] = true; |
|
} |
|
} |
|
|
|
// Find available keys for our prefs |
|
for (let pref in Prefs) |
|
{ |
|
let match = /_key$/.exec(pref); |
|
if (match && typeof Prefs[pref] == "string") |
|
{ |
|
try |
|
{ |
|
let id = match.input.substr(0, match.index); |
|
let result = this.findAvailableKey(id, Prefs[pref], validModifiers, existing); |
|
if (result) |
|
hotkeys[id] = result; |
|
} |
|
catch (e) |
|
{ |
|
Cu.reportError(e); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Add elements for all configured hotkeys |
|
for (let id in hotkeys) |
|
{ |
|
let [keychar, keycode, modifierString] = hotkeys[id]; |
|
|
|
let element = this.window.document.createElement("key"); |
|
element.setAttribute("id", "abp-key-" + id); |
|
element.setAttribute("command", "abp-command-" + id); |
|
if (keychar) |
|
element.setAttribute("key", keychar); |
|
else |
|
element.setAttribute("keycode", keycode); |
|
element.setAttribute("modifiers", modifierString); |
|
|
|
this.E("abp-keyset").appendChild(element); |
|
} |
|
}, |
|
|
|
/** |
|
* Finds an available hotkey for a value defined in preferences. |
|
*/ |
|
findAvailableKey: function(/**String*/ id, /**String*/ value, /**Object*/ validModifiers, /**Object*/ existing) /**Array*/ |
|
{ |
|
let command = this.E("abp-command-" + id); |
|
if (!command) |
|
return; |
|
|
|
for each (let variant in value.split(/\s*,\s*/)) |
|
{ |
|
if (!variant) |
|
continue; |
|
|
|
let modifiers = 0; |
|
let keychar = null; |
|
let keycode = null; |
|
for each (let part in variant.split(/\s+/)) |
|
{ |
|
if (part.toLowerCase() in validModifiers) |
|
modifiers |= validModifiers[part.toLowerCase()]; |
|
else if (part.length == 1) |
|
keychar = part.toUpperCase(); |
|
else if ("DOM_VK_" + part.toUpperCase() in Ci.nsIDOMKeyEvent) |
|
keycode = "VK_" + part.toUpperCase(); |
|
} |
|
|
|
if (!keychar && !keycode) |
|
continue; |
|
|
|
let canonical = modifiers + " " + (keychar || keycode); |
|
if (canonical in existing) |
|
continue; |
|
|
|
let modifierString = ""; |
|
for each (let modifier in ["accel", "shift", "control", "alt", "meta"]) |
|
{ |
|
if (modifiers & validModifiers[modifier]) |
|
{ |
|
modifierString += modifier + " "; |
|
modifiers &= ~validModifiers[modifier]; |
|
} |
|
} |
|
return [keychar, keycode, modifierString]; |
|
} |
|
return null; |
|
}, |
|
|
|
/** |
|
* Initializes window's context menu. |
|
*/ |
|
initContextMenu: function() |
|
{ |
|
let contextMenu = this.getContextMenu(); |
|
if (contextMenu) |
|
{ |
|
contextMenu.addEventListener("popupshowing", this._bindMethod(this.updateContextMenu), false); |
|
contextMenu.addEventListener("popuphidden", this._bindMethod(this.clearContextMenu), false); |
|
|
|
// Make sure our context menu items are at the bottom |
|
contextMenu.appendChild(this.E("abp-removeWhitelist-menuitem")); |
|
contextMenu.appendChild(this.E("abp-frame-menuitem")); |
|
contextMenu.appendChild(this.E("abp-object-menuitem")); |
|
contextMenu.appendChild(this.E("abp-media-menuitem")); |
|
contextMenu.appendChild(this.E("abp-image-menuitem")); |
|
} |
|
}, |
|
|
|
/** |
|
* Checks whether the toolbar icon is currently displayed. |
|
*/ |
|
isToolbarIconVisible: function() |
|
{ |
|
let tb = this.E("abp-toolbarbutton"); |
|
if (!tb || tb.parentNode.localName == "toolbarpalette") |
|
return false; |
|
|
|
if (tb.parentNode.collapsed) |
|
return false; |
|
|
|
return true; |
|
}, |
|
|
|
/** |
|
* Makes sure the toolbar button is displayed. |
|
*/ |
|
installToolbarIcon: function() |
|
{ |
|
let tb = this.E("abp-toolbarbutton"); |
|
if (!tb || tb.parentNode.localName == "toolbarpalette") |
|
{ |
|
let toolbar = (this.getDefaultToolbar ? this.getDefaultToolbar() : null); |
|
if (!toolbar || typeof toolbar.insertItem != "function") |
|
return; |
|
|
|
let insertBefore = (this.toolbarInsertBefore ? this.toolbarInsertBefore() : null); |
|
if (insertBefore && insertBefore.parentNode != toolbar) |
|
insertBefore = null; |
|
|
|
toolbar.insertItem("abp-toolbarbutton", insertBefore, null, false); |
|
|
|
toolbar.setAttribute("currentset", toolbar.currentSet); |
|
this.window.document.persist(toolbar.id, "currentset"); |
|
} |
|
|
|
tb = this.E("abp-toolbarbutton"); |
|
if (tb && tb.parentNode.collapsed) |
|
{ |
|
tb.parentNode.setAttribute("collapsed", "false"); |
|
this.window.document.persist(tb.parentNode.id, "collapsed"); |
|
} |
|
}, |
|
|
|
/** |
|
* Removes toolbar button from the toolbar. |
|
*/ |
|
hideToolbarIcon: function() |
|
{ |
|
let tb = this.E("abp-toolbarbutton"); |
|
if (!tb || tb.parentNode.localName != "toolbar") |
|
return; |
|
|
|
let toolbar = tb.parentNode; |
|
toolbar.currentSet = toolbar.currentSet.split(",").filter(function(id) id != "abp-toolbarbutton").join(","); |
|
|
|
toolbar.setAttribute("currentset", toolbar.currentSet); |
|
this.window.document.persist(toolbar.id, "currentset"); |
|
}, |
|
|
|
/** |
|
* Opens Adblock Plus menu. |
|
*/ |
|
openMenu: function() |
|
{ |
|
this.installToolbarIcon(); |
|
|
|
let button = this.E("abp-toolbarbutton"); |
|
if (!button) |
|
return; |
|
|
|
let toolbar = button.parentNode; |
|
if (toolbar.collapsed) |
|
{ |
|
toolbar.setAttribute("collapsed", "false"); |
|
this.window.document.persist(toolbar.id, "collapsed"); |
|
} |
|
|
|
Utils.runAsync(function() |
|
{ |
|
button.open = true; |
|
}); |
|
}, |
|
|
|
/** |
|
* Handles browser clicks to intercept clicks on abp: links. This can be |
|
* called either with an event object or with the link target (if it is the |
|
* former then link target will be retrieved from event target). |
|
*/ |
|
handleLinkClick: function (/**Event*/ event, /**String*/ linkTarget) |
|
{ |
|
if (event) |
|
{ |
|
// Ignore right-clicks |
|
if (event.button == 2) |
|
return; |
|
|
|
// Search the link associated with the click |
|
let link = event.target; |
|
while (link && !(link instanceof Ci.nsIDOMHTMLAnchorElement)) |
|
link = link.parentNode; |
|
|
|
if (!link || link.protocol != "abp:") |
|
return; |
|
|
|
// This is our link - make sure the browser doesn't handle it |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
|
|
linkTarget = link.href; |
|
} |
|
|
|
let match = /^abp:\/*subscribe\/*\?(.*)/i.exec(linkTarget); |
|
if (!match) |
|
return; |
|
|
|
// Decode URL parameters |
|
let title = null; |
|
let url = null; |
|
let mainSubscriptionTitle = null; |
|
let mainSubscriptionURL = null; |
|
for each (let param in match[1].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; |
|
} |
|
} |
|
if (!url) |
|
return; |
|
|
|
// Default title to the URL |
|
if (!title) |
|
title = url; |
|
|
|
// Main subscription needs both title and URL |
|
if (mainSubscriptionTitle && !mainSubscriptionURL) |
|
mainSubscriptionTitle = null; |
|
if (mainSubscriptionURL && !mainSubscriptionTitle) |
|
mainSubscriptionURL = null; |
|
|
|
// Trim spaces in title and URL |
|
title = title.replace(/^\s+/, "").replace(/\s+$/, ""); |
|
url = url.replace(/^\s+/, "").replace(/\s+$/, ""); |
|
if (mainSubscriptionURL) |
|
{ |
|
mainSubscriptionTitle = mainSubscriptionTitle.replace(/^\s+/, "").replace(/\s+$/, ""); |
|
mainSubscriptionURL = mainSubscriptionURL.replace(/^\s+/, "").replace(/\s+$/, ""); |
|
} |
|
|
|
// Verify that the URL is valid |
|
url = Utils.makeURI(url); |
|
if (!url || (url.scheme != "http" && url.scheme != "https" && url.scheme != "ftp")) |
|
return; |
|
url = url.spec; |
|
|
|
if (mainSubscriptionURL) |
|
{ |
|
mainSubscriptionURL = Utils.makeURI(mainSubscriptionURL); |
|
if (!mainSubscriptionURL || (mainSubscriptionURL.scheme != "http" && mainSubscriptionURL.scheme != "https" && mainSubscriptionURL.scheme != "ftp")) |
|
mainSubscriptionURL = mainSubscriptionTitle = null; |
|
else |
|
mainSubscriptionURL = mainSubscriptionURL.spec; |
|
} |
|
|
|
// Open dialog |
|
let subscription = {url: url, title: title, disabled: false, external: false, |
|
mainSubscriptionTitle: mainSubscriptionTitle, mainSubscriptionURL: mainSubscriptionURL}; |
|
this.window.openDialog("chrome://@ADDON_CHROME_NAME@/content/subscriptionSelection.xul", "_blank", |
|
"chrome,centerscreen,resizable,dialog=no", subscription, null); |
|
}, |
|
|
|
/** |
|
* Updates state of the icon tooltip. |
|
*/ |
|
fillTooltip: function(/**Event*/ event) |
|
{ |
|
let node = this.window.document.tooltipNode; |
|
if (!node || !node.hasAttribute("tooltip")) |
|
{ |
|
event.preventDefault(); |
|
return; |
|
} |
|
|
|
// Prevent tooltip from overlapping menu |
|
for each (let id in ["abp-toolbar-popup", "abp-status-popup"]) |
|
{ |
|
let element = this.E(id); |
|
if (element && element.state == "open") |
|
{ |
|
event.preventDefault(); |
|
return; |
|
} |
|
} |
|
|
|
let type = (node.id == "abp-toolbarbutton" ? "toolbar" : "statusbar"); |
|
let action = parseInt(Prefs["default" + type + "action"]); |
|
if (isNaN(action)) |
|
action = -1; |
|
|
|
let actionDescr = this.E("abp-tooltip-action"); |
|
actionDescr.hidden = (action < 0 || action > 3); |
|
if (!actionDescr.hidden) |
|
actionDescr.setAttribute("value", Utils.getString("action" + action + "_tooltip")); |
|
|
|
let statusDescr = this.E("abp-tooltip-status"); |
|
let statusStr = Utils.getString(this.state + "_tooltip"); |
|
if (this.state == "active") |
|
{ |
|
let [activeSubscriptions, activeFilters] = FilterStorage.subscriptions.reduce(function([subscriptions, filters], current) |
|
{ |
|
if (current instanceof SpecialSubscription) |
|
return [subscriptions, filters + current.filters.filter(function(filter) !filter.disabled).length]; |
|
else if (!current.disabled && !(Prefs.subscriptions_exceptionscheckbox && current.url == Prefs.subscriptions_exceptionsurl)) |
|
return [subscriptions + 1, filters]; |
|
else |
|
return [subscriptions, filters] |
|
}, [0, 0]); |
|
|
|
statusStr = statusStr.replace(/\?1\?/, activeSubscriptions).replace(/\?2\?/, activeFilters); |
|
} |
|
statusDescr.setAttribute("value", statusStr); |
|
|
|
let activeFilters = []; |
|
this.E("abp-tooltip-blocked-label").hidden = (this.state != "active"); |
|
this.E("abp-tooltip-blocked").hidden = (this.state != "active"); |
|
if (this.state == "active") |
|
{ |
|
let stats = RequestNotifier.getWindowStatistics(this.getBrowser().contentWindow); |
|
|
|
let blockedStr = Utils.getString("blocked_count_tooltip"); |
|
blockedStr = blockedStr.replace(/\?1\?/, stats ? stats.blocked : 0).replace(/\?2\?/, stats ? stats.items : 0); |
|
|
|
if (stats && stats.whitelisted + stats.hidden) |
|
{ |
|
blockedStr += " " + Utils.getString("blocked_count_addendum"); |
|
blockedStr = blockedStr.replace(/\?1\?/, stats.whitelisted).replace(/\?2\?/, stats.hidden); |
|
} |
|
|
|
this.E("abp-tooltip-blocked").setAttribute("value", blockedStr); |
|
|
|
if (stats) |
|
{ |
|
let filterSort = function(a, b) |
|
{ |
|
return stats.filters[b] - stats.filters[a]; |
|
}; |
|
for (let filter in stats.filters) |
|
activeFilters.push(filter); |
|
activeFilters = activeFilters.sort(filterSort); |
|
} |
|
|
|
if (activeFilters.length > 0) |
|
{ |
|
let filtersContainer = this.E("abp-tooltip-filters"); |
|
while (filtersContainer.firstChild) |
|
filtersContainer.removeChild(filtersContainer.firstChild); |
|
|
|
for (let i = 0; i < activeFilters.length && i < 3; i++) |
|
{ |
|
let descr = filtersContainer.ownerDocument.createElement("description"); |
|
descr.setAttribute("value", activeFilters[i] + " (" + stats.filters[activeFilters[i]] + ")"); |
|
filtersContainer.appendChild(descr); |
|
} |
|
} |
|
} |
|
|
|
this.E("abp-tooltip-filters-label").hidden = (activeFilters.length == 0); |
|
this.E("abp-tooltip-filters").hidden = (activeFilters.length == 0); |
|
this.E("abp-tooltip-more-filters").hidden = (activeFilters.length <= 3); |
|
}, |
|
|
|
/** |
|
* Updates state of the icon context menu. |
|
*/ |
|
fillPopup: function(/**Event*/ event) |
|
{ |
|
let popup = event.target; |
|
|
|
// Submenu being opened - ignore |
|
let match = /^(abp-(?:toolbar|status|menuitem)-)popup$/.exec(popup.getAttribute("id")); |
|
if (!match) |
|
return; |
|
let prefix = match[1]; |
|
|
|
let sidebarOpen = this.isSidebarOpen(); |
|
this.E(prefix + "opensidebar").hidden = sidebarOpen; |
|
this.E(prefix + "closesidebar").hidden = !sidebarOpen; |
|
|
|
let whitelistItemSite = this.E(prefix + "whitelistsite"); |
|
let whitelistItemPage = this.E(prefix + "whitelistpage"); |
|
whitelistItemSite.hidden = whitelistItemPage.hidden = true; |
|
|
|
let location = this.getCurrentLocation(); |
|
if (location && Policy.isBlockableScheme(location)) |
|
{ |
|
let host = null; |
|
try |
|
{ |
|
host = location.host.replace(/^www\./, ""); |
|
} catch (e) {} |
|
|
|
if (host) |
|
{ |
|
let ending = "|"; |
|
if (location instanceof Ci.nsIURL && location.ref) |
|
location.ref = ""; |
|
if (location instanceof Ci.nsIURL && location.query) |
|
{ |
|
location.query = ""; |
|
ending = "?"; |
|
} |
|
|
|
this.siteWhitelist = Filter.fromText("@@||" + host + "^$document"); |
|
whitelistItemSite.setAttribute("checked", this.siteWhitelist.subscriptions.length && !this.siteWhitelist.disabled); |
|
whitelistItemSite.setAttribute("label", whitelistItemSite.getAttribute("labeltempl").replace(/\?1\?/, host)); |
|
whitelistItemSite.hidden = false; |
|
|
|
this.pageWhitelist = Filter.fromText("@@|" + location.spec + ending + "$document"); |
|
whitelistItemPage.setAttribute("checked", this.pageWhitelist.subscriptions.length && !this.pageWhitelist.disabled); |
|
whitelistItemPage.hidden = false; |
|
} |
|
else |
|
{ |
|
this.siteWhitelist = Filter.fromText("@@|" + location.spec + "|"); |
|
whitelistItemSite.setAttribute("checked", this.siteWhitelist.subscriptions.length && !this.siteWhitelist.disabled); |
|
whitelistItemSite.setAttribute("label", whitelistItemSite.getAttribute("labeltempl").replace(/\?1\?/, location.spec.replace(/^mailto:/, ""))); |
|
whitelistItemSite.hidden = false; |
|
} |
|
} |
|
|
|
this.E(prefix + "disabled").setAttribute("checked", !Prefs.enabled); |
|
this.E(prefix + "frameobjects").setAttribute("checked", Prefs.frameobjects); |
|
this.E(prefix + "slowcollapse").setAttribute("checked", !Prefs.fastcollapse); |
|
this.E(prefix + "savestats").setAttribute("checked", Prefs.savestats); |
|
|
|
let hasToolbar = this.getDefaultToolbar && this.getDefaultToolbar(); |
|
let hasStatusBar = this.E("abp-status"); |
|
this.E(prefix + "showintoolbar").hidden = !hasToolbar || prefix == "abp-toolbar-"; |
|
this.E(prefix + "showinstatusbar").hidden = !hasStatusBar; |
|
this.E(prefix + "iconSettingsSeparator").hidden = this.E(prefix + "showintoolbar").hidden && this.E(prefix + "showinstatusbar").hidden; |
|
|
|
this.E(prefix + "showintoolbar").setAttribute("checked", this.isToolbarIconVisible()); |
|
this.E(prefix + "showinstatusbar").setAttribute("checked", Prefs.showinstatusbar); |
|
|
|
/** let syncEngine = Sync.getEngine(); |
|
this.E(prefix + "sync").hidden = !syncEngine; |
|
this.E(prefix + "sync").setAttribute("checked", syncEngine && syncEngine.enabled); */ |
|
|
|
let defAction = (!this.window.document.popupNode || this.window.document.popupNode.id == "abp-toolbarbutton" ? |
|
Prefs.defaulttoolbaraction : |
|
Prefs.defaultstatusbaraction); |
|
this.E(prefix + "opensidebar").setAttribute("default", defAction == 1); |
|
this.E(prefix + "closesidebar").setAttribute("default", defAction == 1); |
|
this.E(prefix + "filters").setAttribute("default", defAction == 2); |
|
this.E(prefix + "disabled").setAttribute("default", defAction == 3); |
|
}, |
|
|
|
/** |
|
* Opens report wizard for the current page. |
|
*/ |
|
openReportDialog: function() |
|
{ |
|
let wnd = Utils.windowMediator.getMostRecentWindow("abp:sendReport"); |
|
if (wnd) |
|
wnd.focus(); |
|
else |
|
this.window.openDialog("chrome://@ADDON_CHROME_NAME@/content/sendReport.xul", "_blank", "chrome,centerscreen,resizable=no", this.window.content, this.getCurrentLocation()); |
|
}, |
|
|
|
/** |
|
* Opens our contribution page. |
|
*/ |
|
openContributePage: function() |
|
{ |
|
Utils.loadDocLink("contribute"); |
|
}, |
|
|
|
/** |
|
* Hide contribute button and persist this choice. |
|
*/ |
|
hideContributeButton: function(event) |
|
{ |
|
for each (let button in [this.E("abp-status-contributebutton"), this.E("abp-toolbar-contributebutton"), this.E("abp-menuitem-contributebutton")]) |
|
{ |
|
if (button) |
|
{ |
|
button.setAttribute("hidden", "true"); |
|
this.window.document.persist(button.id, "hidden"); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Tests whether blockable items list is currently open. |
|
*/ |
|
isSidebarOpen: function() /**Boolean*/ |
|
{ |
|
if (this.detachedSidebar && !this.detachedSidebar.closed) |
|
return true; |
|
|
|
let sidebar = this.E("abp-sidebar"); |
|
return (sidebar ? !sidebar.hidden : false); |
|
}, |
|
|
|
/** |
|
* Toggles open/closed state of the blockable items list. |
|
*/ |
|
toggleSidebar: function() |
|
{ |
|
if (this.detachedSidebar && !this.detachedSidebar.closed) |
|
{ |
|
this.detachedSidebar.close(); |
|
this.detachedSidebar = null; |
|
} |
|
else |
|
{ |
|
let sidebar = this.E("abp-sidebar"); |
|
if (sidebar && (!Prefs.detachsidebar || !sidebar.hidden)) |
|
{ |
|
this.E("abp-sidebar-splitter").hidden = !sidebar.hidden; |
|
this.E("abp-sidebar-browser").setAttribute("src", sidebar.hidden ? "chrome://@ADDON_CHROME_NAME@/content/sidebar.xul" : "about:blank"); |
|
sidebar.hidden = !sidebar.hidden; |
|
if (sidebar.hidden) |
|
this.getBrowser().contentWindow.focus(); |
|
} |
|
else |
|
this.detachedSidebar = this.window.openDialog("chrome://@ADDON_CHROME_NAME@/content/sidebarDetached.xul", "_blank", "chrome,resizable,dependent,dialog=no"); |
|
} |
|
}, |
|
|
|
/** |
|
* Removes/disables the exception rule applying for the current page. |
|
*/ |
|
removeWhitelist: function() |
|
{ |
|
let location = this.getCurrentLocation(); |
|
let filter = null; |
|
if (location) |
|
filter = Policy.isWhitelisted(location.spec); |
|
if (filter && filter.subscriptions.length && !filter.disabled) |
|
{ |
|
AppIntegration.toggleFilter(filter); |
|
return true; |
|
} |
|
return false; |
|
}, |
|
|
|
/** |
|
* Toggles "Count filter hits" option. |
|
*/ |
|
toggleSaveStats: function() |
|
{ |
|
if (Prefs.savestats) |
|
{ |
|
if (!Utils.confirm(this.window, Utils.getString("clearStats_warning"))) |
|
return; |
|
|
|
FilterStorage.resetHitCounts(); |
|
FilterListener.setDirty(0); // Force saving to disk |
|
Prefs.savestats = false; |
|
} |
|
else |
|
Prefs.savestats = true; |
|
}, |
|
|
|
/** |
|
* Handles command events on toolbar icon. |
|
*/ |
|
handleToolbarCommand: function(event) |
|
{ |
|
if (event.eventPhase != event.AT_TARGET) |
|
return; |
|
|
|
if (Prefs.defaulttoolbaraction == 0) |
|
event.target.open = true; |
|
else |
|
this.executeAction(Prefs.defaulttoolbaraction); |
|
}, |
|
|
|
/** |
|
* Handles click events on toolbar icon. |
|
*/ |
|
handleToolbarClick: function(/**Event*/ event) |
|
{ |
|
if (event.eventPhase != event.AT_TARGET) |
|
return; |
|
|
|
if (event.button == 1) |
|
this.executeAction(3); |
|
}, |
|
|
|
/** |
|
* Handles click events on status bar icon. |
|
*/ |
|
handleStatusClick: function(/**Event*/ event) |
|
{ |
|
if (event.eventPhase != event.AT_TARGET) |
|
return; |
|
|
|
if (event.button == 0) |
|
this.executeAction(Prefs.defaultstatusbaraction); |
|
else if (event.button == 1) |
|
this.executeAction(3); |
|
}, |
|
|
|
// Executes default action for statusbar/toolbar by its number |
|
executeAction: function (action) |
|
{ |
|
if (action == 1) |
|
this.toggleSidebar(); |
|
else if (action == 2) |
|
Utils.openFiltersDialog(); |
|
else if (action == 3) |
|
{ |
|
// If there is a whitelisting rule for current page - remove it (reenable). |
|
// Otherwise flip "enabled" pref. |
|
if (!this.removeWhitelist()) |
|
AppIntegration.togglePref("enabled"); |
|
} |
|
}, |
|
|
|
/** |
|
* Updates context menu, in particularly controls the visibility of context |
|
* menu items like "Block image". |
|
*/ |
|
updateContextMenu: function(event) |
|
{ |
|
if (event.eventPhase != event.AT_TARGET) |
|
return; |
|
|
|
let contextMenu = this.getContextMenu(); |
|
let target = this.window.document.popupNode; |
|
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)); |
|
} |
|
|
|
let nodeType = null; |
|
this.nodeData = null; |
|
this.currentNode = null; |
|
this.backgroundData = null; |
|
this.frameData = null; |
|
this.currentFrame = null; |
|
if (target) |
|
{ |
|
// Lookup the node in our stored data |
|
let data = RequestNotifier.getDataForNode(target); |
|
if (data && !data[1].filter) |
|
{ |
|
[this.currentNode, this.nodeData] = data; |
|
nodeType = this.nodeData.typeDescr; |
|
} |
|
|
|
let wnd = Utils.getWindow(target); |
|
|
|
if (wnd.frameElement) |
|
{ |
|
let data = RequestNotifier.getDataForNode(wnd.frameElement, true); |
|
if (data && !data[1].filter) |
|
[this.currentFrame, this.frameData] = data; |
|
} |
|
|
|
if (nodeType != "IMAGE") |
|
{ |
|
// Look for a background image |
|
let imageNode = target; |
|
while (imageNode) |
|
{ |
|
if (imageNode.nodeType == imageNode.ELEMENT_NODE) |
|
{ |
|
let style = wnd.getComputedStyle(imageNode, ""); |
|
let bgImage = extractImageURL(style, "background-image") || extractImageURL(style, "list-style-image"); |
|
if (bgImage) |
|
{ |
|
let data = RequestNotifier.getDataForNode(wnd.document, true, Policy.type.IMAGE, bgImage); |
|
if (data && !data[1].filter) |
|
{ |
|
this.backgroundData = data[1]; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
imageNode = imageNode.parentNode; |
|
} |
|
} |
|
|
|
// Hide "Block Images from ..." if hideimagemanager pref is true and the image manager isn't already blocking something |
|
let imgManagerContext = this.E("context-blockimage"); |
|
if (imgManagerContext && shouldHideImageManager()) |
|
{ |
|
// Don't use "hidden" attribute - it might be overridden by the default popupshowing handler |
|
imgManagerContext.collapsed = true; |
|
} |
|
} |
|
|
|
this.E("abp-image-menuitem").hidden = (nodeType != "IMAGE" && this.backgroundData == null); |
|
this.E("abp-object-menuitem").hidden = (nodeType != "OBJECT"); |
|
this.E("abp-media-menuitem").hidden = (nodeType != "MEDIA"); |
|
this.E("abp-frame-menuitem").hidden = (this.frameData == null); |
|
|
|
let location = this.getCurrentLocation(); |
|
this.E("abp-removeWhitelist-menuitem").hidden = (!location || !Policy.isWhitelisted(location.spec)); |
|
}, |
|
|
|
/** |
|
* Clears context menu data once the menu is closed. |
|
*/ |
|
clearContextMenu: function(event) |
|
{ |
|
if (event.eventPhase != event.AT_TARGET) |
|
return; |
|
|
|
this.nodeData = null; |
|
this.currentNode = null; |
|
this.backgroundData = null; |
|
this.frameData = null; |
|
this.currentFrame = null; |
|
}, |
|
|
|
/** |
|
* Brings up the filter composer dialog to block an item. |
|
*/ |
|
blockItem: function(/**Node*/ node, /**RequestEntry*/ item) |
|
{ |
|
if (!item) |
|
return; |
|
|
|
this.window.openDialog("chrome://@ADDON_CHROME_NAME@/content/composer.xul", "_blank", "chrome,centerscreen,resizable,dialog=no,dependent", [node], item); |
|
} |
|
}; |
|
|
|
/** |
|
* List of event handers to be registered. For each event handler the element ID, |
|
* event and the actual event handler are listed. |
|
* @type Array |
|
*/ |
|
WindowWrapper.prototype.eventHandlers = [ |
|
["abp-tooltip", "popupshowing", WindowWrapper.prototype.fillTooltip], |
|
["abp-status-popup", "popupshowing", WindowWrapper.prototype.fillPopup], |
|
["abp-toolbar-popup", "popupshowing", WindowWrapper.prototype.fillPopup], |
|
["abp-menuitem-popup", "popupshowing", WindowWrapper.prototype.fillPopup], |
|
["abp-command-sendReport", "command", WindowWrapper.prototype.openReportDialog], |
|
["abp-command-filters", "command", function() {Utils.openFiltersDialog();}], |
|
["abp-command-sidebar", "command", WindowWrapper.prototype.toggleSidebar], |
|
["abp-command-togglesitewhitelist", "command", function() { AppIntegration.toggleFilter(this.siteWhitelist); }], |
|
["abp-command-togglepagewhitelist", "command", function() { AppIntegration.toggleFilter(this.pageWhitelist); }], |
|
["abp-command-toggleobjtabs", "command", function() { AppIntegration.togglePref("frameobjects"); }], |
|
["abp-command-togglecollapse", "command", function() { AppIntegration.togglePref("fastcollapse"); }], |
|
["abp-command-togglesavestats", "command", WindowWrapper.prototype.toggleSaveStats], |
|
["abp-command-togglesync", "command", AppIntegration.toggleSync], |
|
["abp-command-toggleshowintoolbar", "command", AppIntegration.toggleToolbarIcon], |
|
["abp-command-toggleshowinstatusbar", "command", function() { AppIntegration.togglePref("showinstatusbar"); }], |
|
["abp-command-enable", "command", function() { AppIntegration.togglePref("enabled"); }], |
|
["abp-command-contribute", "command", WindowWrapper.prototype.openContributePage], |
|
["abp-command-contribute-hide", "command", WindowWrapper.prototype.hideContributeButton], |
|
["abp-toolbarbutton", "command", WindowWrapper.prototype.handleToolbarCommand], |
|
["abp-toolbarbutton", "click", WindowWrapper.prototype.handleToolbarClick], |
|
["abp-status", "click", WindowWrapper.prototype.handleStatusClick], |
|
["abp-image-menuitem", "command", function() { this.backgroundData ? this.blockItem(null, this.backgroundData) : this.blockItem(this.currentNode, this.nodeData); }], |
|
["abp-object-menuitem", "command", function() { this.blockItem(this.currentNode, this.nodeData); }], |
|
["abp-media-menuitem", "command", function() { this.blockItem(this.currentNode, this.nodeData); }], |
|
["abp-frame-menuitem", "command", function() { this.blockItem(this.currentFrame, this.frameData); }], |
|
["abp-removeWhitelist-menuitem", "command", WindowWrapper.prototype.removeWhitelist] |
|
]; |
|
|
|
/** |
|
* Updates displayed status for all application windows (on prefs or filters |
|
* change). |
|
*/ |
|
function reloadPrefs() |
|
{ |
|
for each (let wrapper in wrappers) |
|
wrapper.updateState(); |
|
} |
|
|
|
/** |
|
* Initializes options in add-on manager when they show up. |
|
*/ |
|
function initOptionsDoc(/**Document*/ doc) |
|
{ |
|
function E(id) doc.getElementById(id); |
|
|
|
E("@ADDON_CHROME_NAME@-filters").addEventListener("command", Utils.openFiltersDialog, false); |
|
|
|
let wrapper = wrappers.length ? wrappers[0] : null; |
|
let hasToolbar = wrapper && wrapper.getDefaultToolbar && wrapper.getDefaultToolbar(); |
|
let hasStatusBar = wrapper && wrapper.E("abp-status"); |
|
|
|
let syncEngine = Sync.getEngine(); |
|
E("@ADDON_CHROME_NAME@-sync").collapsed = !syncEngine; |
|
|
|
E("@ADDON_CHROME_NAME@-showintoolbar").collapsed = !hasToolbar; |
|
E("@ADDON_CHROME_NAME@-showinstatusbar").collapsed = !hasStatusBar; |
|
|
|
function initCheckboxes() |
|
{ |
|
|
|
E("@ADDON_CHROME_NAME@-savestats").value = Prefs.savestats; |
|
E("@ADDON_CHROME_NAME@-savestats").addEventListener("command", function() |
|
{ |
|
wrapper.toggleSaveStats.call({window: doc.defaultView}); |
|
E("@ADDON_CHROME_NAME@-savestats").value = Prefs.savestats; |
|
}, false); |
|
|
|
E("@ADDON_CHROME_NAME@-sync").value = syncEngine && syncEngine.enabled; |
|
E("@ADDON_CHROME_NAME@-sync").addEventListener("command", function() |
|
{ |
|
E("@ADDON_CHROME_NAME@-sync").value = AppIntegration.toggleSync(); |
|
}, false); |
|
|
|
if (wrapper) |
|
{ |
|
E("@ADDON_CHROME_NAME@-showintoolbar").value = |
|
wrapper.isToolbarIconVisible(); |
|
let handler = function() |
|
{ |
|
E("@ADDON_CHROME_NAME@-showintoolbar").value = |
|
AppIntegration.toggleToolbarIcon(); |
|
}; |
|
E("@ADDON_CHROME_NAME@-showintoolbar").addEventListener("command", handler, false); |
|
} |
|
} |
|
initCheckboxes(); |
|
} |
|
|
|
/** |
|
* Tests whether image manager context menu entry should be hidden with user's current preferences. |
|
* @return Boolean |
|
*/ |
|
function shouldHideImageManager() |
|
{ |
|
let result = false; |
|
if (Prefs.hideimagemanager && "@mozilla.org/permissionmanager;1" in Cc) |
|
{ |
|
try |
|
{ |
|
result = true; |
|
let enumerator = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager).enumerator; |
|
while (enumerator.hasMoreElements()) |
|
{ |
|
let item = enumerator.getNext().QueryInterface(Ci.nsIPermission); |
|
if (item.type == "image" && item.capability == Ci.nsIPermissionManager.DENY_ACTION) |
|
{ |
|
result = false; |
|
break; |
|
} |
|
} |
|
} |
|
catch(e) |
|
{ |
|
result = false; |
|
} |
|
} |
|
|
|
shouldHideImageManager = function() result; |
|
return result; |
|
} |
|
|
|
/** |
|
* Executed on first run, adds a filter subscription and notifies that user |
|
* about that. |
|
*/ |
|
function addSubscription() |
|
{ |
|
// Add "acceptable ads" subscription for new users and user updating from old ABP versions. |
|
// Don't add it for users of privacy subscriptions (use a hardcoded list for now). |
|
let addAcceptable = false; |
|
let privacySubscriptions = { |
|
"https://easylist-downloads.adblockplus.org/easyprivacy+easylist.txt": true, |
|
"https://easylist-downloads.adblockplus.org/easyprivacy.txt": true, |
|
"https://secure.fanboy.co.nz/fanboy-tracking.txt": true, |
|
"https://fanboy-adblock-list.googlecode.com/hg/fanboy-adblocklist-stats.txt": true, |
|
"https://bitbucket.org/fanboy/fanboyadblock/raw/tip/fanboy-adblocklist-stats.txt": true, |
|
"https://hg01.codeplex.com/fanboyadblock/raw-file/tip/fanboy-adblocklist-stats.txt": true, |
|
"https://adversity.googlecode.com/hg/Adversity-Tracking.txt": true |
|
}; |
|
|
|
// Don't add subscription if the user has a subscription already |
|
let addSubscription = true; |
|
if (FilterStorage.subscriptions.some(function(subscription) subscription instanceof DownloadableSubscription && subscription.url != Prefs.subscriptions_exceptionsurl)) |
|
addSubscription = false; |
|
|
|
// Only add subscription if this is the first run or the user has no filters |
|
if (addSubscription) |
|
{ |
|
let hasFilters = FilterStorage.subscriptions.some(function(subscription) subscription.filters.length); |
|
if (hasFilters && Utils.versionComparator.compare(Prefs.lastVersion, "0.0") > 0) |
|
addSubscription = false; |
|
} |
|
|
|
if (!addSubscription && !addAcceptable) |
|
return; |
|
|
|
function notifyUser() |
|
{ |
|
let wrapper = (wrappers.length ? wrappers[0] : null); |
|
if (wrapper && wrapper.addTab) |
|
{ |
|
wrapper.addTab("chrome://@ADDON_CHROME_NAME@/content/firstRun.xul"); |
|
} |
|
else |
|
{ |
|
Utils.windowWatcher.openWindow(wrapper ? wrapper.window : null, |
|
"chrome://@ADDON_CHROME_NAME@/content/firstRun.xul", |
|
"_blank", "chrome,centerscreen,resizable,dialog=no", null); |
|
} |
|
} |
|
|
|
if (addSubscription) |
|
{ |
|
// Load subscriptions data |
|
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); |
|
request.open("GET", "chrome://@ADDON_CHROME_NAME@/content/subscriptions.xml"); |
|
request.addEventListener("load", function() |
|
{ |
|
let node = Utils.chooseFilterSubscription(request.responseXML.getElementsByTagName("subscription")); |
|
let subscription = (node ? Subscription.fromURL(node.getAttribute("url")) : null); |
|
if (subscription) |
|
{ |
|
FilterStorage.addSubscription(subscription); |
|
subscription.disabled = false; |
|
subscription.title = node.getAttribute("title"); |
|
subscription.homepage = node.getAttribute("homepage"); |
|
if (subscription instanceof DownloadableSubscription && !subscription.lastDownload) |
|
Synchronizer.execute(subscription); |
|
|
|
notifyUser(); |
|
} |
|
}, false); |
|
request.send(); |
|
} |
|
else |
|
notifyUser(); |
|
} |
|
|
|
/** |
|
* Extracts the URL of the image from a CSS property. |
|
*/ |
|
function extractImageURL(/**CSSStyleDeclaration*/ computedStyle, /**String*/ property) |
|
{ |
|
let value = computedStyle.getPropertyCSSValue(property); |
|
if (value instanceof Ci.nsIDOMCSSValueList && value.length >= 1) |
|
value = value[0]; |
|
if (value instanceof Ci.nsIDOMCSSPrimitiveValue && value.primitiveType == Ci.nsIDOMCSSPrimitiveValue.CSS_URI) |
|
return Utils.unwrapURL(value.getStringValue()).spec; |
|
|
|
return null; |
|
} |
|
|
|
init();
|
|
|