/*
* This file is part of Adblock Plus ,
* Copyright (C) 2006-2014 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 .
*/
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let {Utils} = require("utils");
let {Prefs} = require("prefs");
let {Policy} = require("contentPolicy");
let {FilterStorage} = require("filterStorage");
let {FilterNotifier} = require("filterNotifier");
let {RequestNotifier} = require("requestNotifier");
let {Filter} = require("filterClasses");
let {Subscription, SpecialSubscription, DownloadableSubscription} = require("subscriptionClasses");
let {Synchronizer} = require("synchronizer");
let {KeySelector} = require("keySelector");
let {Notification} = require("notification");
let {initAntiAdblockNotification} = require("antiadblockInit");
let CustomizableUI = null;
/**
* Filter corresponding with "disable on site" menu item (set in fillIconMent()).
* @type Filter
*/
let siteWhitelist = null;
/**
* Filter corresponding with "disable on site" menu item (set in fillIconMenu()).
* @type Filter
*/
let pageWhitelist = null;
/**
* Window containing the detached list of blockable items.
* @type Window
*/
let detachedBottombar = null;
/**
* Object initializing add-on options, observes add-on manager notifications
* about add-on options being opened.
* @type nsIObserver
*/
let optionsObserver =
{
init: function()
{
Services.obs.addObserver(this, "addon-options-displayed", true);
onShutdown.add(function()
{
Services.obs.removeObserver(this, "addon-options-displayed");
}.bind(this));
},
/**
* Initializes options in add-on manager when they show up.
*/
initOptionsDoc: function(/**Document*/ doc)
{
function hideElement(id, hide)
{
let element = doc.getElementById(id);
if (element)
element.collapsed = hide;
}
function setChecked(id, checked)
{
let element = doc.getElementById(id);
if (element)
element.value = checked;
}
function addCommandHandler(id, handler)
{
let element = doc.getElementById(id);
if (element)
element.addEventListener("command", handler, false);
}
Utils.splitAllLabels(doc);
addCommandHandler("adblockplus-filters", UI.openFiltersDialog.bind(UI));
let {Sync} = require("sync");
let syncEngine = Sync.getEngine();
hideElement("adblockplus-sync", !syncEngine);
let {defaultToolbarPosition, statusbarPosition} = require("appSupport");
let hasToolbar = defaultToolbarPosition;
let hasStatusBar = statusbarPosition;
hideElement("adblockplus-showintoolbar", !hasToolbar);
hideElement("adblockplus-showinstatusbar", !hasStatusBar);
let checkbox = doc.querySelector("setting[type=bool]");
if (checkbox)
initCheckboxes();
function initCheckboxes()
{
if (!("value" in checkbox))
{
// XBL bindings didn't apply yet (bug 708397), try later
Utils.runAsync(initCheckboxes);
return;
}
setChecked("adblockplus-savestats", Prefs.savestats);
addCommandHandler("adblockplus-savestats", function()
{
UI.toggleSaveStats(doc.defaultView);
this.value = Prefs.savestats;
});
let hasAcceptableAds = FilterStorage.subscriptions.some((subscription) => subscription instanceof DownloadableSubscription &&
subscription.url == Prefs.subscriptions_exceptionsurl);
setChecked("adblockplus-acceptableAds", hasAcceptableAds);
addCommandHandler("adblockplus-acceptableAds", function()
{
this.value = UI.toggleAcceptableAds();
});
setChecked("adblockplus-sync", syncEngine && syncEngine.enabled);
addCommandHandler("adblockplus-sync", function()
{
this.value = UI.toggleSync();
});
setChecked("adblockplus-showintoolbar", UI.isToolbarIconVisible());
addCommandHandler("adblockplus-showintoolbar", function()
{
UI.toggleToolbarIcon();
this.value = UI.isToolbarIconVisible();
});
let list = doc.getElementById("adblockplus-subscription-list");
if (list)
{
// Load subscriptions data
let request = new XMLHttpRequest();
request.mozBackgroundRequest = true;
request.open("GET", "chrome://adblockplus/content/ui/subscriptions.xml");
request.addEventListener("load", function()
{
if (onShutdown.done)
return;
let currentSubscription = FilterStorage.subscriptions.filter((subscription) => subscription instanceof DownloadableSubscription &&
subscription.url != Prefs.subscriptions_exceptionsurl);
currentSubscription = (currentSubscription.length ? currentSubscription[0] : null);
let subscriptions =request.responseXML.getElementsByTagName("subscription");
for (let i = 0; i < subscriptions.length; i++)
{
let item = subscriptions[i];
let url = item.getAttribute("url");
if (!url)
continue;
list.appendItem(item.getAttribute("title"), url, null);
if (currentSubscription && url == currentSubscription.url)
list.selectedIndex = list.itemCount - 1;
if (currentSubscription && list.selectedIndex < 0)
{
list.appendItem(currentSubscription.title, currentSubscription.url, null);
list.selectedIndex = list.itemCount - 1;
}
}
var listener = function()
{
if (list.value)
UI.setSubscription(list.value, list.label);
}
list.addEventListener("command", listener, false);
// xul:menulist in Fennec is broken and doesn't trigger any events
// on selection. Have to detect selectIndex changes instead.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=891736
list.watch("selectedIndex", function(prop, oldval, newval)
{
Utils.runAsync(listener);
return newval;
});
}, false);
request.send();
}
}
},
observe: function(subject, topic, data)
{
let {addonID} = require("info")
if (data != addonID)
return;
this.initOptionsDoc(subject.QueryInterface(Ci.nsIDOMDocument));
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference])
};
optionsObserver.init();
/**
* Session restore observer instance, stored to prevent it from being garbage
* collected.
* @type SessionRestoreObserver
*/
let sessionRestoreObserver = null;
/**
* Observer waiting for the browsing session to be restored on startup.
*/
function SessionRestoreObserver(/**function*/ callback)
{
sessionRestoreObserver = this;
this.callback = callback;
Services.obs.addObserver(this, "sessionstore-windows-restored", true);
// Just in case, don't wait longer than 5 seconds
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.timer.init(this, 5000, Ci.nsITimer.TYPE_ONE_SHOT);
}
SessionRestoreObserver.prototype =
{
callback: null,
timer: null,
observe: function(subject, topic, data)
{
Services.obs.removeObserver(this, "sessionstore-windows-restored");
sessionRestoreObserver = null;
this.timer.cancel();
this.timer = null;
if (!onShutdown.done)
this.callback();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference])
}
/**
* Timer used to delay notification handling.
* @type nsITimer
*/
let notificationTimer = null;
let UI = exports.UI =
{
/**
* Gets called on startup, initializes UI integration.
*/
init: function()
{
// We should call initDone once both overlay and filters are loaded
let overlayLoaded = false;
let filtersLoaded = false;
let sessionRestored = false;
// Start loading overlay
let request = new XMLHttpRequest();
request.mozBackgroundRequest = true;
request.open("GET", "chrome://adblockplus/content/ui/overlay.xul");
request.addEventListener("load", function(event)
{
if (onShutdown.done)
return;
this.processOverlay(request.responseXML.documentElement);
// Don't wait for the rest of the startup sequence, add icon already
this.addToolbarButton();
overlayLoaded = true;
if (overlayLoaded && filtersLoaded && sessionRestored)
this.initDone();
}.bind(this), false);
request.send(null);
// Wait for filters to load
if (FilterStorage._loading)
{
let listener = function(action)
{
if (action != "load")
return;
FilterNotifier.removeListener(listener);
filtersLoaded = true;
if (overlayLoaded && filtersLoaded && sessionRestored)
this.initDone();
}.bind(this);
FilterNotifier.addListener(listener);
}
else
filtersLoaded = true;
// Initialize UI after the session is restored
let window = this.currentWindow;
if (!window && "nsISessionStore" in Ci)
{
// No application windows yet, the application must be starting up. Wait
// for session to be restored before initializing our UI.
new SessionRestoreObserver(function()
{
sessionRestored = true;
if (overlayLoaded && filtersLoaded && sessionRestored)
this.initDone();
}.bind(this));
}
else
sessionRestored = true;
},
/**
* Provesses overlay document data and initializes overlay property.
*/
processOverlay: function(/**Element*/ root)
{
Utils.splitAllLabels(root);
let specialElements = {"abp-status-popup": true, "abp-status": true, "abp-toolbarbutton": true, "abp-menuitem": true, "abp-bottombar-container": true};
this.overlay = {all: []};
// Remove whitespace text nodes
let walker = root.ownerDocument.createTreeWalker(
root, Ci.nsIDOMNodeFilter.SHOW_TEXT,
(node) => !/\S/.test(node.nodeValue), false
);
let whitespaceNodes = [];
while (walker.nextNode())
whitespaceNodes.push(walker.currentNode);
for (let i = 0; i < whitespaceNodes.length; i++)
whitespaceNodes[i].parentNode.removeChild(whitespaceNodes[i]);
// Put overlay elements into appropriate fields
while (root.firstElementChild)
{
let child = root.firstElementChild;
if (child.getAttribute("id") in specialElements)
this.overlay[child.getAttribute("id")] = child;
else
this.overlay.all.push(child);
root.removeChild(child);
}
// Read overlay attributes
this.overlay.attributes = {};
for (let i = 0; i < root.attributes.length; i++)
this.overlay.attributes[root.attributes[i].name] = root.attributes[i].value;
// Copy context menu into the toolbar icon and Tools menu item
function fixId(element, newId)
{
if (element.hasAttribute("id"))
element.setAttribute("id", element.getAttribute("id").replace("abp-status", newId));
for (let i = 0, len = element.children.length; i < len; i++)
fixId(element.children[i], newId);
return element;
}
if ("abp-status-popup" in this.overlay)
{
let menuSource = this.overlay["abp-status-popup"];
delete this.overlay["abp-status-popup"];
if (this.overlay.all.length)
this.overlay.all[0].appendChild(menuSource);
if ("abp-toolbarbutton" in this.overlay)
this.overlay["abp-toolbarbutton"].appendChild(fixId(menuSource.cloneNode(true), "abp-toolbar"));
if ("abp-menuitem" in this.overlay)
this.overlay["abp-menuitem"].appendChild(fixId(menuSource.cloneNode(true), "abp-menuitem"));
}
},
/**
* Gets called once the initialization is finished and Adblock Plus elements
* can be added to the UI.
*/
initDone: function()
{
// The icon might be added already, make sure its state is correct
this.updateState();
// Listen for pref and filters changes
Prefs.addListener(function(name)
{
if (name == "enabled" || name == "defaulttoolbaraction" || name == "defaultstatusbaraction")
this.updateState();
else if (name == "showinstatusbar")
{
for (let window in this.applicationWindows)
this.updateStatusbarIcon(window);
}
}.bind(this));
FilterNotifier.addListener(function(action)
{
if (/^(filter|subscription)\.(added|removed|disabled|updated)$/.test(action) || action == "load")
this.updateState();
}.bind(this));
notificationTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
notificationTimer.initWithCallback(this.showNextNotification.bind(this),
3 * 60 * 1000, Ci.nsITimer.TYPE_ONE_SHOT);
onShutdown.add(() => notificationTimer.cancel());
// Add "anti-adblock messages" notification
initAntiAdblockNotification();
let documentCreationObserver = {
observe: function(subject, topic, data)
{
if (!(subject instanceof Ci.nsIDOMWindow))
return;
this.showNextNotification(subject.location.href);
}.bind(UI)
};
Services.obs.addObserver(documentCreationObserver, "content-document-global-created", false);
onShutdown.add(function()
{
Services.obs.removeObserver(documentCreationObserver, "content-document-global-created", false);
});
// Execute first-run actions if a window is open already, otherwise it
// will happen in applyToWindow() when a window is opened.
this.firstRunActions(this.currentWindow);
},
addToolbarButton: function()
{
let {WindowObserver} = require("windowObserver");
new WindowObserver(this);
let {defaultToolbarPosition} = require("appSupport");
if ("abp-toolbarbutton" in this.overlay && defaultToolbarPosition)
{
try
{
({CustomizableUI}) = Cu.import("resource:///modules/CustomizableUI.jsm", null);
}
catch (e)
{
// No built-in CustomizableUI API, use our own implementation.
({CustomizableUI}) = require("customizableUI");
}
CustomizableUI.createWidget({
id: "abp-toolbarbutton",
type: "custom",
positionAttribute: "abp-iconposition", // For emulation only
defaultArea: defaultToolbarPosition.parent,
defaultBefore: defaultToolbarPosition.before, // For emulation only
defaultAfter: defaultToolbarPosition.after, // For emulation only
removable: true,
onBuild: function(document)
{
let node = document.importNode(this.overlay["abp-toolbarbutton"], true);
node.addEventListener("click", this.onIconClick, false);
node.addEventListener("command", this.onIconClick, false);
this.updateIconState(document.defaultView, node);
return node;
}.bind(this),
onAdded: function(node)
{
// For emulation only, this callback isn't part of the official
// CustomizableUI API.
this.updateIconState(node.ownerDocument.defaultView, node);
}.bind(this),
});
onShutdown.add(CustomizableUI.destroyWidget.bind(CustomizableUI, "abp-toolbarbutton"));
}
},
firstRunActions: function(window)
{
if (this.firstRunDone || !window || FilterStorage._loading)
return;
this.firstRunDone = true;
let {addonVersion} = require("info");
let prevVersion = Prefs.currentVersion;
if (prevVersion != addonVersion)
{
Prefs.currentVersion = addonVersion;
this.addSubscription(window, prevVersion);
}
},
/**
* Will be set to true after the check whether first-run actions should run
* has been performed.
* @type Boolean
*/
firstRunDone: false,
/**
* Initializes Adblock Plus UI in a window.
*/
applyToWindow: function(/**Window*/ window, /**Boolean*/ noDelay)
{
let {delayInitialization, isKnownWindow, getBrowser, addBrowserLocationListener, addBrowserClickListener} = require("appSupport");
if (window.document.documentElement.id == "CustomizeToolbarWindow" || isKnownWindow(window))
{
// Add style processing instruction
let style = window.document.createProcessingInstruction("xml-stylesheet", 'class="adblockplus-node" href="chrome://adblockplus/skin/overlay.css" type="text/css"');
window.document.insertBefore(style, window.document.firstChild);
}
if (!isKnownWindow(window))
return;
// Thunderbird windows will not be initialized at this point, execute
// delayed
if (!noDelay && delayInitialization)
{
Utils.runAsync(this.applyToWindow.bind(this, window, true));
return;
}
// Add general items to the document
for (let i = 0; i < this.overlay.all.length; i++)
window.document.documentElement.appendChild(this.overlay.all[i].cloneNode(true));
// Add status bar icon
this.updateStatusbarIcon(window);
// Add tools menu item
if ("abp-menuitem" in this.overlay)
{
let {toolsMenu} = require("appSupport");
let [parent, before] = this.resolveInsertionPoint(window, toolsMenu);
if (parent)
parent.insertBefore(this.overlay["abp-menuitem"].cloneNode(true), before);
}
// Attach event handlers
for (let i = 0; i < eventHandlers.length; i++)
{
let [id, event, handler] = eventHandlers[i];
let element = window.document.getElementById(id);
if (element)
element.addEventListener(event, handler.bind(null, window), false);
}
window.addEventListener("popupshowing", this.onPopupShowing, false);
window.addEventListener("keypress", this.onKeyPress, false);
addBrowserLocationListener(window, function()
{
this.updateIconState(window, window.document.getElementById("abp-status"));
this.updateIconState(window, window.document.getElementById("abp-toolbarbutton"));
}.bind(this));
addBrowserClickListener(window, this.onBrowserClick.bind(this, window));
window.document.getElementById("abp-notification-close").addEventListener("command", function(event)
{
window.document.getElementById("abp-notification").hidePopup();
}, false);
// First-run actions?
this.firstRunActions(window);
// 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;
},
/**
* Removes Adblock Plus UI from a window.
*/
removeFromWindow: function(/**Window*/ window)
{
let {isKnownWindow, removeBrowserLocationListeners, removeBrowserClickListeners} = require("appSupport");
if (window.document.documentElement.id == "CustomizeToolbarWindow" || isKnownWindow(window))
{
// Remove style processing instruction
for (let child = window.document.firstChild; child; child = child.nextSibling)
if (child.nodeType == child.PROCESSING_INSTRUCTION_NODE && child.data.indexOf("adblockplus-node") >= 0)
child.parentNode.removeChild(child);
}
if (!isKnownWindow(window))
return;
for (let id in this.overlay)
{
if (id == "all")
{
let list = this.overlay[id];
for (let i = 0; i < list.length; i++)
{
let clone = window.document.getElementById(list[i].getAttribute("id"));
if (clone)
clone.parentNode.removeChild(clone);
}
}
else
{
let clone = window.document.getElementById(id);
if (clone)
clone.parentNode.removeChild(clone);
}
}
window.removeEventListener("popupshowing", this.onPopupShowing, false);
window.removeEventListener("keypress", this.onKeyPress, false);
removeBrowserLocationListeners(window);
removeBrowserClickListeners(window);
},
/**
* The overlay information to be used when adding elements to the UI.
* @type Object
*/
overlay: null,
/**
* Iterator for application windows that Adblock Plus should apply to.
* @type Iterator
*/
get applicationWindows()
{
let {isKnownWindow} = require("appSupport");
let enumerator = Services.wm.getZOrderDOMWindowEnumerator(null, true);
if (!enumerator.hasMoreElements())
{
// On Linux the list returned will be empty, see bug 156333. Fall back to random order.
enumerator = Services.wm.getEnumerator(null);
}
while (enumerator.hasMoreElements())
{
let window = enumerator.getNext().QueryInterface(Ci.nsIDOMWindow);
if (isKnownWindow(window))
yield window;
}
},
/**
* Returns the top-most application window or null if none exists.
* @type Window
*/
get currentWindow()
{
for (let window of this.applicationWindows)
return window;
return null;
},
/**
* Opens a URL in the browser window. If browser window isn't passed as parameter,
* this function attempts to find a browser window. If an event is passed in
* it should be passed in to the browser if possible (will e.g. open a tab in
* background depending on modifiers keys).
*/
loadInBrowser: function(/**String*/ url, /**Window*/ currentWindow, /**Event*/ event)
{
if (!currentWindow)
currentWindow = this.currentWindow;
let {addTab} = require("appSupport");
if (currentWindow && addTab)
addTab(currentWindow, url, event);
else
{
let protocolService = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService);
protocolService.loadURI(Services.io.newURI(url, null, null), null);
}
},
/**
* Opens a pre-defined documentation link in the browser window. This will
* send the UI language to adblockplus.org so that the correct language
* version of the page can be selected.
*/
loadDocLink: function(/**String*/ linkID, /**Window*/ window)
{
let link = Utils.getDocLink(linkID);
this.loadInBrowser(link, window);
},
/**
* Brings up the filter composer dialog to block an item.
*/
blockItem: function(/**Window*/ window, /**Node*/ node, /**RequestEntry*/ item)
{
if (!item)
return;
window.openDialog("chrome://adblockplus/content/ui/composer.xul", "_blank", "chrome,centerscreen,resizable,dialog=no,dependent", [node], item);
},
/**
* Opens filter preferences dialog or focuses an already open dialog.
* @param {Filter} [filter] filter to be selected
*/
openFiltersDialog: function(filter)
{
let existing = Services.wm.getMostRecentWindow("abp:filters");
if (existing)
{
try
{
existing.focus();
} catch (e) {}
if (filter)
existing.SubscriptionActions.selectFilter(filter);
}
else
{
Services.ww.openWindow(null, "chrome://adblockplus/content/ui/filters.xul", "_blank", "chrome,centerscreen,resizable,dialog=no", {wrappedJSObject: filter});
}
},
/**
* Opens report wizard for the current page.
*/
openReportDialog: function(/**Window*/ window)
{
let wnd = Services.wm.getMostRecentWindow("abp:sendReport");
if (wnd)
wnd.focus();
else
{
let uri = this.getCurrentLocation(window);
if (uri)
{
let {getBrowser} = require("appSupport");
window.openDialog("chrome://adblockplus/content/ui/sendReport.xul", "_blank", "chrome,centerscreen,resizable=no", getBrowser(window).contentWindow, uri);
}
}
},
/**
* Opens our contribution page.
*/
openContributePage: function(/**Window*/ window)
{
this.loadDocLink("contribute", window);
},
/**
* Executed on first run, adds a filter subscription and notifies that user
* about that.
*/
addSubscription: function(/**Window*/ window, /**String*/ prevVersion)
{
// 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 = (Services.vc.compare(prevVersion, "2.0") < 0);
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
};
if (FilterStorage.subscriptions.some((subscription) => subscription.url == Prefs.subscriptions_exceptionsurl || subscription.url in privacySubscriptions))
addAcceptable = false;
// Don't add subscription if the user has a subscription already
let addSubscription = true;
//if (FilterStorage.subscriptions.some((subscription) => subscription instanceof DownloadableSubscription && subscription.url != Prefs.subscriptions_exceptionsurl))
addSubscription = false;
// If this isn't the first run, only add subscription if the user has no custom filters
if (addSubscription && Services.vc.compare(prevVersion, "0.0") > 0)
{
if (FilterStorage.subscriptions.some((subscription) => subscription.url != Prefs.subscriptions_exceptionsurl && subscription.filters.length))
addSubscription = false;
}
// Add "acceptable ads" subscription
if (false)
{
let subscription = Subscription.fromURL(Prefs.subscriptions_exceptionsurl);
if (subscription)
{
subscription.title = "Allow non-intrusive advertising";
FilterStorage.addSubscription(subscription);
if (subscription instanceof DownloadableSubscription && !subscription.lastDownload)
Synchronizer.execute(subscription);
}
else
addAcceptable = false;
}
/* Add "anti-adblock messages" subscription for new users and users updating from old ABP versions
if (Services.vc.compare(prevVersion, "2.5") < 0)
{
let subscription = Subscription.fromURL(Prefs.subscriptions_antiadblockurl);
if (subscription && !(subscription.url in FilterStorage.knownSubscriptions))
{
subscription.disabled = true;
FilterStorage.addSubscription(subscription);
if (subscription instanceof DownloadableSubscription && !subscription.lastDownload)
Synchronizer.execute(subscription);
}
}*/
// Extra subsriptions
let subscription = Subscription.fromURL("http://gnuzilla.gnu.org/filters/blacklist.txt");
subscription.disabled = false;
FilterStorage.addSubscription(subscription);
Synchronizer.execute(subscription);
let subscription = Subscription.fromURL("http://gnuzilla.gnu.org/filters/third-party.txt");
subscription.disabled = false;
FilterStorage.addSubscription(subscription);
Synchronizer.execute(subscription);
let subscription = Subscription.fromURL("http://gnuzilla.gnu.org/filters/javascript.txt");
subscription.disabled = true;
FilterStorage.addSubscription(subscription);
Synchronizer.execute(subscription);
if (!addSubscription && !addAcceptable)
return;
function notifyUser()
{return;
let {addTab} = require("appSupport");
if (addTab)
{
addTab(window, "chrome://adblockplus/content/ui/firstRun.html");
}
else
{
let dialogSource = '\
\
';
Services.ww.openWindow(window,
"data:application/vnd.mozilla.xul+xml," + encodeURIComponent(dialogSource),
"_blank", "chrome,centerscreen,resizable,dialog=no", null);
}
}
if (addSubscription)
{
// Load subscriptions data
let request = new XMLHttpRequest();
request.mozBackgroundRequest = true;
request.open("GET", "chrome://adblockplus/content/ui/subscriptions.xml");
request.addEventListener("load", function()
{
if (onShutdown.done)
return;
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();
},
/**
* Handles clicks inside the browser's content area, will 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).
*/
onBrowserClick: function (/**Window*/ window, /**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 (let param of 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;
}
this.openSubscriptionDialog(window, url, title, mainSubscriptionURL, mainSubscriptionTitle);
},
/**
* Opens a dialog letting the user confirm/adjust a filter subscription to
* be added.
*/
openSubscriptionDialog: function(/**Window*/ window, /**String*/ url, /**String*/ title, /**String*/ mainURL, /**String*/ mainTitle)
{
let subscription = {url: url, title: title, disabled: false, external: false,
mainSubscriptionTitle: mainTitle, mainSubscriptionURL: mainURL};
window.openDialog("chrome://adblockplus/content/ui/subscriptionSelection.xul", "_blank",
"chrome,centerscreen,resizable,dialog=no", subscription, null);
},
/**
* Retrieves the current location of the browser.
*/
getCurrentLocation: function(/**Window*/ window) /**nsIURI*/
{
let {getCurrentLocation} = require("appSupport");
let result = getCurrentLocation(window);
return (result ? Utils.unwrapURL(result) : null);
},
/**
* Looks up an element with given ID in the window. If a list of IDs is given
* will try all of them until an element exists.
*/
findElement: function(/**Window*/ window, /**String|String[]*/ id) /**Element*/
{
if (id instanceof Array)
{
for (let candidate of id)
{
let result = window.document.getElementById(candidate);
if (result)
return result;
}
return null;
}
else
return window.document.getElementById(id);
},
/**
* Resolves an insertion point as specified in appSupport module. Returns
* two elements: the parent element and the element to insert before.
*/
resolveInsertionPoint: function(/**Window*/ window, /**Object*/ insertionPoint) /**Element[]*/
{
let parent = null;
let before = null;
if (insertionPoint)
{
if ("parent" in insertionPoint)
parent = this.findElement(window, insertionPoint.parent);
if (parent && "before" in insertionPoint)
before = this.findElement(window, insertionPoint.before);
if (parent && !before && "after" in insertionPoint)
{
let after = this.findElement(window, insertionPoint.after);
if (after)
before = after.nextElementSibling;
}
if (before && before.parentNode != parent)
before = null;
}
return [parent, before];
},
/**
* Toggles visibility state of the toolbar icon.
*/
toggleToolbarIcon: function()
{
if (!CustomizableUI)
return;
if (this.isToolbarIconVisible())
CustomizableUI.removeWidgetFromArea("abp-toolbarbutton");
else
{
let {defaultToolbarPosition} = require("appSupport");
CustomizableUI.addWidgetToArea("abp-toolbarbutton", defaultToolbarPosition.parent);
}
},
/**
* Updates Adblock Plus icon state for all windows.
*/
updateState: function()
{
for (let window in this.applicationWindows)
{
this.updateIconState(window, window.document.getElementById("abp-status"));
this.updateIconState(window, window.document.getElementById("abp-toolbarbutton"));
}
},
/**
* Updates Adblock Plus icon state for a single application window.
*/
updateIconState: function(/**Window*/ window, /**Element*/ icon)
{
if (!icon)
return;
let state = (Prefs.enabled ? "active" : "disabled");
if (state == "active")
{
let location = this.getCurrentLocation(window);
if (location && Policy.isWhitelisted(location.spec))
state = "whitelisted";
}
let popupId = "abp-status-popup";
if (icon.localName == "statusbarpanel")
{
if (Prefs.defaultstatusbaraction == 0)
{
icon.setAttribute("popup", popupId);
icon.removeAttribute("context");
}
else
{
icon.removeAttribute("popup");
icon.setAttribute("context", popupId);
}
}
else
{
if (Prefs.defaulttoolbaraction == 0)
{
icon.setAttribute("type", "menu");
icon.removeAttribute("context");
}
else
{
icon.setAttribute("type", "menu-button");
icon.setAttribute("context", popupId);
}
}
icon.setAttribute("abpstate", state);
},
/**
* Shows or hides status bar icons in all windows, according to pref.
*/
updateStatusbarIcon: function(/**Window*/ window)
{
if (!("abp-status" in this.overlay))
return;
let {statusbarPosition} = require("appSupport");
if (!statusbarPosition)
return;
let icon = window.document.getElementById("abp-status");
if (Prefs.showinstatusbar && !icon)
{
let [parent, before] = this.resolveInsertionPoint(window, statusbarPosition);
if (!parent)
return;
parent.insertBefore(this.overlay["abp-status"].cloneNode(true), before);
icon = window.document.getElementById("abp-status");
this.updateIconState(window, icon);
icon.addEventListener("click", this.onIconClick, false);
}
else if (!Prefs.showinstatusbar && icon)
icon.parentNode.removeChild(icon);
},
/**
* Toggles the value of a boolean preference.
*/
togglePref: function(/**String*/ pref)
{
Prefs[pref] = !Prefs[pref];
},
/**
* 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((subscription) => !(subscription instanceof SpecialSubscription)))
filter.disabled = !filter.disabled;
else
FilterStorage.removeFilter(filter);
}
else
FilterStorage.addFilter(filter);
},
/**
* Toggles "Count filter hits" option.
*/
toggleSaveStats: function(window)
{
if (Prefs.savestats)
{
if (!Utils.confirm(window, Utils.getString("clearStats_warning")))
return;
FilterStorage.resetHitCounts();
Prefs.savestats = false;
}
else
Prefs.savestats = true;
},
/**
* Sets the current filter subscription in a single-subscription scenario,
* all other subscriptions will be removed.
*/
setSubscription: function(url, title)
{
let subscription = Subscription.fromURL(url);
let currentSubscriptions = FilterStorage.subscriptions.filter(
((subscription) => subscription instanceof DownloadableSubscription && subscription.url != Prefs.subscriptions_exceptionsurl)
);
if (!subscription || currentSubscriptions.indexOf(subscription) >= 0)
return;
for (let i = 0; i < currentSubscriptions.length; i++)
FilterStorage.removeSubscription(currentSubscriptions[i]);
subscription.title = title;
FilterStorage.addSubscription(subscription);
if (subscription instanceof DownloadableSubscription && !subscription.lastDownload)
Synchronizer.execute(subscription);
},
/**
* Adds or removes "non-intrisive ads" filter list.
* @return {Boolean} true if the filter list has been added
**/
toggleAcceptableAds: function()
{
let subscription = Subscription.fromURL(Prefs.subscriptions_exceptionsurl);
if (!subscription)
return false;
subscription.disabled = false;
subscription.title = "Allow non-intrusive advertising";
if (subscription.url in FilterStorage.knownSubscriptions)
FilterStorage.removeSubscription(subscription);
else
{
FilterStorage.addSubscription(subscription);
if (subscription instanceof DownloadableSubscription && !subscription.lastDownload)
Synchronizer.execute(subscription);
}
return (subscription.url in FilterStorage.knownSubscriptions);
},
/**
* Toggles the pref for the Adblock Plus sync engine.
* @return {Boolean} new state of the sync engine
*/
toggleSync: function()
{
let {Sync} = require("sync");
let syncEngine = Sync.getEngine();
if (syncEngine)
{
syncEngine.enabled = !syncEngine.enabled;
return syncEngine.enabled;
}
else
return false;
},
/**
* Tests whether blockable items list is currently open.
*/
isBottombarOpen: function(/**Window*/ window) /**Boolean*/
{
if (detachedBottombar && !detachedBottombar.closed)
return true;
return !!window.document.getElementById("abp-bottombar");
},
/**
* Called when some pop-up in the application window shows up, initializes
* pop-ups related to Adblock Plus.
*/
onPopupShowing: function(/**Event*/ event)
{
if (event.defaultPrevented)
return;
let popup = event.originalTarget;
let {contentContextMenu} = require("appSupport");
if ((typeof contentContextMenu == "string" && popup.id == contentContextMenu) ||
(contentContextMenu instanceof Array && contentContextMenu.indexOf(popup.id) >= 0))
{
this.fillContentContextMenu(popup);
}
else if (popup.id == "abp-tooltip")
this.fillIconTooltip(event, popup.ownerDocument.defaultView);
else
{
let match = /^(abp-(?:toolbar|status|menuitem)-)popup$/.exec(popup.id);
if (match)
this.fillIconMenu(event, popup.ownerDocument.defaultView, match[1]);
}
},
/**
* Handles click on toolbar and status bar icons.
*/
onIconClick: function(/**Event*/ event)
{
if (event.eventPhase != event.AT_TARGET)
return;
let isToolbar = (event.target.localName != "statusbarpanel");
let action = 0;
if ((isToolbar && event.type == "command") || (!isToolbar && event.button == 0))
action = (isToolbar ? Prefs.defaulttoolbaraction : Prefs.defaultstatusbaraction);
else if (event.button == 1)
action = 3;
let window = event.target.ownerDocument.defaultView;
if (action == 1)
this.toggleBottombar(window);
else if (action == 2)
this.openFiltersDialog();
else if (action == 3)
{
// If there is a whitelisting rule for current page - remove it (reenable).
// Otherwise flip "enabled" pref.
if (!this.removeWhitelist(window))
this.togglePref("enabled");
}
},
/**
* Removes/disables the exception rule applying for the current page.
*/
removeWhitelist: function(/**Window*/ window)
{
let location = this.getCurrentLocation(window);
let filter = null;
if (location)
filter = Policy.isWhitelisted(location.spec);
if (filter && filter.subscriptions.length && !filter.disabled)
{
UI.toggleFilter(filter);
return true;
}
return false;
},
/**
* Updates state of the icon tooltip.
*/
fillIconTooltip: function(/**Event*/ event, /**Window*/ window)
{
let E = (id) => window.document.getElementById(id);
let node = window.document.tooltipNode;
if (!node || !node.hasAttribute("tooltip"))
{
event.preventDefault();
return;
}
// Prevent tooltip from overlapping menu
for (let id of ["abp-toolbar-popup", "abp-status-popup"])
{
let element = 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 = E("abp-tooltip-action");
actionDescr.hidden = (action < 0 || action > 3);
if (!actionDescr.hidden)
actionDescr.setAttribute("value", Utils.getString("action" + action + "_tooltip"));
let statusDescr = E("abp-tooltip-status");
let state = node.getAttribute("abpstate");
let statusStr = Utils.getString(state + "_tooltip");
if (state == "active")
{
let [activeSubscriptions, activeFilters] = FilterStorage.subscriptions.reduce(function([subscriptions, filters], current)
{
if (current instanceof SpecialSubscription)
return [subscriptions, filters + current.filters.filter((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 = [];
E("abp-tooltip-blocked-label").hidden = (state != "active");
E("abp-tooltip-blocked").hidden = (state != "active");
if (state == "active")
{
let {getBrowser} = require("appSupport");
let stats = RequestNotifier.getWindowStatistics(getBrowser(window).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);
}
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 = 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);
}
}
}
E("abp-tooltip-filters-label").hidden = (activeFilters.length == 0);
E("abp-tooltip-filters").hidden = (activeFilters.length == 0);
E("abp-tooltip-more-filters").hidden = (activeFilters.length <= 3);
},
/**
* Updates state of the icon context menu.
*/
fillIconMenu: function(/**Event*/ event, /**Window*/ window, /**String*/ prefix)
{
function hideElement(id, hide)
{
let element = window.document.getElementById(id);
if (element)
element.hidden = hide;
}
function setChecked(id, checked)
{
let element = window.document.getElementById(id);
if (element)
element.setAttribute("checked", checked);
}
function setDisabled(id, disabled)
{
let element = window.document.getElementById(id);
if (element)
element.setAttribute("disabled", disabled);
}
function setDefault(id, isDefault)
{
let element = window.document.getElementById(id);
if (element)
element.setAttribute("default", isDefault);
}
function generateLabel(id, param)
{
let element = window.document.getElementById(id);
if (element)
element.setAttribute("label", element.getAttribute("labeltempl").replace(/\?1\?/, param));
}
let bottombarOpen = this.isBottombarOpen(window);
hideElement(prefix + "openbottombar", bottombarOpen);
hideElement(prefix + "closebottombar", !bottombarOpen);
hideElement(prefix + "whitelistsite", true);
hideElement(prefix + "whitelistpage", true);
let location = this.getCurrentLocation(window);
if (location && Policy.isBlockableScheme(location))
{
let host = null;
try
{
host = location.host.replace(/^www\./, "");
} catch (e) {}
if (host)
{
let ending = "|";
location = location.clone();
if (location instanceof Ci.nsIURL)
location.ref = "";
if (location instanceof Ci.nsIURL && location.query)
{
location.query = "";
ending = "?";
}
siteWhitelist = Filter.fromText("@@||" + host + "^$document");
setChecked(prefix + "whitelistsite", siteWhitelist.subscriptions.length && !siteWhitelist.disabled);
generateLabel(prefix + "whitelistsite", host);
hideElement(prefix + "whitelistsite", false);
pageWhitelist = Filter.fromText("@@|" + location.spec + ending + "$document");
setChecked(prefix + "whitelistpage", pageWhitelist.subscriptions.length && !pageWhitelist.disabled);
hideElement(prefix + "whitelistpage", false);
}
else
{
siteWhitelist = Filter.fromText("@@|" + location.spec + "|");
setChecked(prefix + "whitelistsite", siteWhitelist.subscriptions.length && !siteWhitelist.disabled);
generateLabel(prefix + "whitelistsite", location.spec.replace(/^mailto:/, ""));
hideElement(prefix + "whitelistsite", false);
}
}
setDisabled("abp-command-sendReport", !location || !Policy.isBlockableScheme(location) || location.scheme == "mailto");
setChecked(prefix + "disabled", !Prefs.enabled);
setChecked(prefix + "frameobjects", Prefs.frameobjects);
setChecked(prefix + "slowcollapse", !Prefs.fastcollapse);
setChecked(prefix + "savestats", Prefs.savestats);
let {defaultToolbarPosition, statusbarPosition} = require("appSupport");
let hasToolbar = defaultToolbarPosition;
let hasStatusBar = statusbarPosition;
hideElement(prefix + "showintoolbar", !hasToolbar || prefix == "abp-toolbar-");
hideElement(prefix + "showinstatusbar", !hasStatusBar);
hideElement(prefix + "iconSettingsSeparator", (prefix == "abp-toolbar-" || !hasToolbar) && !hasStatusBar);
setChecked(prefix + "showintoolbar", this.isToolbarIconVisible());
setChecked(prefix + "showinstatusbar", Prefs.showinstatusbar);
let {Sync} = require("sync");
let syncEngine = Sync.getEngine();
hideElement(prefix + "sync", !syncEngine);
setChecked(prefix + "sync", syncEngine && syncEngine.enabled);
let defAction = (!window.document.popupNode || window.document.popupNode.id == "abp-toolbarbutton" ?
Prefs.defaulttoolbaraction :
Prefs.defaultstatusbaraction);
setDefault(prefix + "openbottombar", defAction == 1);
setDefault(prefix + "closebottombar", defAction == 1);
setDefault(prefix + "filters", defAction == 2);
setDefault(prefix + "disabled", defAction == 3);
let popup = window.document.getElementById(prefix + "popup");
let items = (popup ? popup.querySelectorAll('menuitem[key]') : []);
for (let i = 0; i < items.length; i++)
{
let item = items[i];
let match = /^abp-key-/.exec(item.getAttribute("key"));
if (!match)
continue;
let name = match.input.substr(match.index + match[0].length);
if (!this.hotkeys)
this.configureKeys(window);
if (name in this.hotkeys)
{
let text = KeySelector.getTextForKey(this.hotkeys[name]);
if (text)
item.setAttribute("acceltext", text);
else
item.removeAttribute("acceltext");
}
}
hideElement(prefix + "contributebutton", Prefs.hideContributeButton);
},
/**
* Adds Adblock Plus menu items to the content area context menu when it shows
* up.
*/
fillContentContextMenu: function(/**Element*/ popup)
{
let target = popup.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;
let window = popup.ownerDocument.defaultView;
let menuItems = [];
let addMenuItem = function([node, nodeData])
{
let type = nodeData.typeDescr.toLowerCase();
if (type == "background")
{
type = "image";
node = null;
}
let label = this.overlay.attributes[type + "contextlabel"];
if (!label)
return;
let item = popup.ownerDocument.createElement("menuitem");
item.setAttribute("label", label);
item.setAttribute("class", "abp-contextmenuitem");
item.addEventListener("command", this.blockItem.bind(this, window, node, nodeData), false);
popup.appendChild(item);
menuItems.push(item);
}.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].typeDescr == "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, Policy.type.IMAGE, bgImage);
if (data && !data[1].filter)
{
addMenuItem(data);
break;
}
}
}
node = node.parentNode;
}
}
// Add "Remove exception" menu item if necessary
let location = this.getCurrentLocation(window);
let filter = (location ? Policy.isWhitelisted(location.spec) : null);
if (filter && filter.subscriptions.length && !filter.disabled)
{
let label = this.overlay.attributes.whitelistcontextlabel;
if (!label)
return;
let item = popup.ownerDocument.createElement("menuitem");
item.setAttribute("label", label);
item.setAttribute("class", "abp-contextmenuitem");
item.addEventListener("command", this.toggleFilter.bind(this, filter), false);
popup.appendChild(item);
menuItems.push(item);
}
// Make sure to clean up everything once the context menu is closed
if (menuItems.length)
{
let cleanUp = function(event)
{
if (event.eventPhase != event.AT_TARGET)
return;
popup.removeEventListener("popuphidden", cleanUp, false);
for (let i = 0; i < menuItems.length; i++)
if (menuItems[i].parentNode)
menuItems[i].parentNode.removeChild(menuItems[i]);
}.bind(this);
popup.addEventListener("popuphidden", cleanUp, false);
}
},
/**
* Called when the user presses a key in the application window, reacts to our
* shortcut keys.
*/
onKeyPress: function(/**Event*/ event)
{
if (!this.hotkeys)
this.configureKeys(event.currentTarget);
for (let key in this.hotkeys)
{
if (KeySelector.matchesKey(event, this.hotkeys[key]))
{
event.preventDefault();
let command = event.currentTarget.document.getElementById("abp-command-" + key);
if (command)
command.doCommand();
}
}
},
/**
* Checks whether the toolbar icon is currently displayed.
*/
isToolbarIconVisible: function() /**Boolean*/
{
if (!CustomizableUI)
return false;
let placement = CustomizableUI.getPlacementOfWidget("abp-toolbarbutton");
return !!placement;
},
/**
* Stores the selected hotkeys, initialized when the user presses a key.
*/
hotkeys: null,
/**
* Chooses shortcut keys that are available in the window according to
* preferences.
*/
configureKeys: function(/**Window*/ window)
{
let selector = new KeySelector(window);
this.hotkeys = {};
for (let name in Prefs)
{
let match = /_key$/.exec(name);
if (match && typeof Prefs[name] == "string")
{
let keyName = match.input.substr(0, match.index);
this.hotkeys[keyName] = selector.selectKey(Prefs[name]);
}
}
},
/**
* Toggles open/closed state of the blockable items list.
*/
toggleBottombar: function(/**Window*/ window)
{
if (detachedBottombar && !detachedBottombar.closed)
{
detachedBottombar.close();
detachedBottombar = null;
}
else
{
let {addBottomBar, removeBottomBar, getBrowser} = require("appSupport");
let mustDetach = !addBottomBar || !removeBottomBar || !("abp-bottombar-container" in this.overlay);
let detach = mustDetach || Prefs.detachsidebar;
if (!detach && window.document.getElementById("abp-bottombar"))
{
removeBottomBar(window);
let browser = (getBrowser ? getBrowser(window) : null);
if (browser)
browser.contentWindow.focus();
}
else if (!detach)
{
addBottomBar(window, this.overlay["abp-bottombar-container"]);
let element = window.document.getElementById("abp-bottombar");
if (element)
{
element.setAttribute("width", Prefs.blockableItemsSize.width);
element.setAttribute("height", Prefs.blockableItemsSize.height);
let splitter = window.document.getElementById("abp-bottombar-splitter");
if (splitter)
{
splitter.addEventListener("command", function()
{
Prefs.blockableItemsSize = {width: element.width, height: element.height};
}, false);
}
}
}
else
detachedBottombar = window.openDialog("chrome://adblockplus/content/ui/sidebarDetached.xul", "_blank", "chrome,resizable,dependent,dialog=no", mustDetach);
}
},
/**
* Hide contribute button and persist this choice.
*/
hideContributeButton: function(/**Window*/ window)
{
Prefs.hideContributeButton = true;
for (let id of ["abp-status-contributebutton", "abp-toolbar-contributebutton", "abp-menuitem-contributebutton"])
{
let button = window.document.getElementById(id);
if (button)
button.hidden = true;
}
},
showNextNotification: function(url)
{
let window = this.currentWindow;
if (!window)
return;
let button = window.document.getElementById("abp-toolbarbutton")
|| window.document.getElementById("abp-status");
if (!button)
return;
let notification = Notification.getNextToShow(url);
if (!notification)
return;
this._showNotification(window, button, notification);
},
_showNotification: function(window, button, notification)
{
let panel = window.document.getElementById("abp-notification");
if (panel.state !== "closed")
return;
function insertMessage(element, text, links)
{
let match = /^(.*?)<(a|strong)>(.*?)<\/\2>(.*)$/.exec(text);
if (!match)
{
element.appendChild(window.document.createTextNode(text));
return;
}
let [_, before, tagName, value, after] = match;
insertMessage(element, before, links);
let newElement = window.document.createElementNS("http://www.w3.org/1999/xhtml", tagName);
if (tagName === "a" && links && links.length)
newElement.setAttribute("href", links.shift());
insertMessage(newElement, value, links);
element.appendChild(newElement);
insertMessage(element, after, links);
}
let texts = Notification.getLocalizedTexts(notification);
let titleElement = window.document.getElementById("abp-notification-title");
titleElement.textContent = texts.title;
let messageElement = window.document.getElementById("abp-notification-message");
messageElement.innerHTML = "";
let docLinks = [];
for (let link of notification.links)
docLinks.push(Utils.getDocLink(link));
insertMessage(messageElement, texts.message, docLinks);
messageElement.addEventListener("click", function(event)
{
let link = event.target;
while (link && link !== messageElement && link.localName !== "a")
link = link.parentNode;
if (!link || link.localName !== "a")
return;
event.preventDefault();
event.stopPropagation();
this.loadInBrowser(link.href, window);
}.bind(this));
if (notification.type === "question")
{
function buttonHandler(approved, event)
{
event.preventDefault();
event.stopPropagation();
panel.hidePopup();
Notification.triggerQuestionListeners(notification.id, approved)
Notification.markAsShown(notification.id);
}
window.document.getElementById("abp-notification-yes").onclick = buttonHandler.bind(null, true);
window.document.getElementById("abp-notification-no").onclick = buttonHandler.bind(null, false);
}
panel.setAttribute("class", "abp-" + notification.type);
panel.setAttribute("noautohide", notification.type === "question");
panel.openPopup(button, "bottomcenter topcenter", 0, 0, false, false, null);
}
};
UI.onPopupShowing = UI.onPopupShowing.bind(UI);
UI.onKeyPress = UI.onKeyPress.bind(UI);
UI.onIconClick = UI.onIconClick.bind(UI);
UI.init();
/**
* List of event handers to be registered for each window. For each event
* handler the element ID, event and the actual event handler are listed.
* @type Array
*/
let eventHandlers = [
["abp-command-sendReport", "command", UI.openReportDialog.bind(UI)],
["abp-command-filters", "command", UI.openFiltersDialog.bind(UI)],
["abp-command-sidebar", "command", UI.toggleBottombar.bind(UI)],
["abp-command-togglesitewhitelist", "command", function() { UI.toggleFilter(siteWhitelist); }],
["abp-command-togglepagewhitelist", "command", function() { UI.toggleFilter(pageWhitelist); }],
["abp-command-toggleobjtabs", "command", UI.togglePref.bind(UI, "frameobjects")],
["abp-command-togglecollapse", "command", UI.togglePref.bind(UI, "fastcollapse")],
["abp-command-togglesavestats", "command", UI.toggleSaveStats.bind(UI)],
["abp-command-togglesync", "command", UI.toggleSync.bind(UI)],
["abp-command-toggleshowintoolbar", "command", UI.toggleToolbarIcon.bind(UI)],
["abp-command-toggleshowinstatusbar", "command", UI.togglePref.bind(UI, "showinstatusbar")],
["abp-command-enable", "command", UI.togglePref.bind(UI, "enabled")],
["abp-command-contribute", "command", UI.openContributePage.bind(UI)],
["abp-command-contribute-hide", "command", UI.hideContributeButton.bind(UI)]
];
onShutdown.add(function()
{
for (let window in UI.applicationWindows)
if (UI.isBottombarOpen(window))
UI.toggleBottombar(window);
});