summaryrefslogtreecommitdiffstats
path: root/modules/PopupNotifications.jsm
diff options
context:
space:
mode:
authorThomas Groman <tgroman@nuegia.net>2020-04-20 20:49:37 -0700
committerThomas Groman <tgroman@nuegia.net>2020-04-20 20:49:37 -0700
commitf9cab004186edb425a9b88ad649726605080a17c (patch)
treee2dae51d3144e83d097a12e7a1499e3ea93f90be /modules/PopupNotifications.jsm
parentf428692de8b59ab89a66502c079e1823dfda8aeb (diff)
downloadwebbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar
webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.gz
webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.lz
webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.xz
webbrowser-f9cab004186edb425a9b88ad649726605080a17c.zip
move browser to webbrowser/
Diffstat (limited to 'modules/PopupNotifications.jsm')
-rw-r--r--modules/PopupNotifications.jsm994
1 files changed, 0 insertions, 994 deletions
diff --git a/modules/PopupNotifications.jsm b/modules/PopupNotifications.jsm
deleted file mode 100644
index 0cb9702..0000000
--- a/modules/PopupNotifications.jsm
+++ /dev/null
@@ -1,994 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-this.EXPORTED_SYMBOLS = ["PopupNotifications"];
-
-var Cc = Components.classes, Ci = Components.interfaces, Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-const NOTIFICATION_EVENT_DISMISSED = "dismissed";
-const NOTIFICATION_EVENT_REMOVED = "removed";
-const NOTIFICATION_EVENT_SHOWING = "showing";
-const NOTIFICATION_EVENT_SHOWN = "shown";
-const NOTIFICATION_EVENT_SWAPPING = "swapping";
-
-const ICON_SELECTOR = ".notification-anchor-icon";
-const ICON_ATTRIBUTE_SHOWING = "showing";
-
-const PREF_SECURITY_DELAY = "security.notification_enable_delay";
-
-var popupNotificationsMap = new WeakMap();
-var gNotificationParents = new WeakMap;
-
-function getAnchorFromBrowser(aBrowser) {
- let anchor = aBrowser.getAttribute("popupnotificationanchor") ||
- aBrowser.popupnotificationanchor;
- if (anchor) {
- if (anchor instanceof Ci.nsIDOMXULElement) {
- return anchor;
- }
- return aBrowser.ownerDocument.getElementById(anchor);
- }
- return null;
-}
-
-function getNotificationFromElement(aElement) {
- // Need to find the associated notification object, which is a bit tricky
- // since it isn't associated with the element directly - this is kind of
- // gross and very dependent on the structure of the popupnotification
- // binding's content.
- let notificationEl;
- let parent = aElement;
- while (parent && (parent = aElement.ownerDocument.getBindingParent(parent)))
- notificationEl = parent;
- return notificationEl;
-}
-
-/**
- * Notification object describes a single popup notification.
- *
- * @see PopupNotifications.show()
- */
-function Notification(id, message, anchorID, mainAction, secondaryActions,
- browser, owner, options) {
- this.id = id;
- this.message = message;
- this.anchorID = anchorID;
- this.mainAction = mainAction;
- this.secondaryActions = secondaryActions || [];
- this.browser = browser;
- this.owner = owner;
- this.options = options || {};
-}
-
-Notification.prototype = {
-
- id: null,
- message: null,
- anchorID: null,
- mainAction: null,
- secondaryActions: null,
- browser: null,
- owner: null,
- options: null,
- timeShown: null,
-
- /**
- * Removes the notification and updates the popup accordingly if needed.
- */
- remove: function Notification_remove() {
- this.owner.remove(this);
- },
-
- get anchorElement() {
- let iconBox = this.owner.iconBox;
-
- let anchorElement = getAnchorFromBrowser(this.browser);
-
- if (!iconBox)
- return anchorElement;
-
- if (!anchorElement && this.anchorID)
- anchorElement = iconBox.querySelector("#"+this.anchorID);
-
- // Use a default anchor icon if it's available
- if (!anchorElement)
- anchorElement = iconBox.querySelector("#default-notification-icon") ||
- iconBox;
-
- return anchorElement;
- },
-
- reshow: function() {
- this.owner._reshowNotifications(this.anchorElement, this.browser);
- }
-};
-
-/**
- * The PopupNotifications object manages popup notifications for a given browser
- * window.
- * @param tabbrowser
- * window's <xul:tabbrowser/>. Used to observe tab switching events and
- * for determining the active browser element.
- * @param panel
- * The <xul:panel/> element to use for notifications. The panel is
- * populated with <popupnotification> children and displayed it as
- * needed.
- * @param iconBox
- * Reference to a container element that should be hidden or
- * unhidden when notifications are hidden or shown. It should be the
- * parent of anchor elements whose IDs are passed to show().
- * It is used as a fallback popup anchor if notifications specify
- * invalid or non-existent anchor IDs.
- */
-this.PopupNotifications = function PopupNotifications(tabbrowser, panel, iconBox) {
- if (!(tabbrowser instanceof Ci.nsIDOMXULElement))
- throw "Invalid tabbrowser";
- if (iconBox && !(iconBox instanceof Ci.nsIDOMXULElement))
- throw "Invalid iconBox";
- if (!(panel instanceof Ci.nsIDOMXULElement))
- throw "Invalid panel";
-
- this.window = tabbrowser.ownerDocument.defaultView;
- this.panel = panel;
- this.tabbrowser = tabbrowser;
- this.iconBox = iconBox;
- this.buttonDelay = Services.prefs.getIntPref(PREF_SECURITY_DELAY);
-
- this.panel.addEventListener("popuphidden", this, true);
-
- this.window.addEventListener("activate", this, true);
- if (this.tabbrowser.tabContainer)
- this.tabbrowser.tabContainer.addEventListener("TabSelect", this, true);
-}
-
-PopupNotifications.prototype = {
-
- window: null,
- panel: null,
- tabbrowser: null,
-
- _iconBox: null,
- set iconBox(iconBox) {
- // Remove the listeners on the old iconBox, if needed
- if (this._iconBox) {
- this._iconBox.removeEventListener("click", this, false);
- this._iconBox.removeEventListener("keypress", this, false);
- }
- this._iconBox = iconBox;
- if (iconBox) {
- iconBox.addEventListener("click", this, false);
- iconBox.addEventListener("keypress", this, false);
- }
- },
- get iconBox() {
- return this._iconBox;
- },
-
- /**
- * Retrieve a Notification object associated with the browser/ID pair.
- * @param id
- * The Notification ID to search for.
- * @param browser
- * The browser whose notifications should be searched. If null, the
- * currently selected browser's notifications will be searched.
- *
- * @returns the corresponding Notification object, or null if no such
- * notification exists.
- */
- getNotification: function PopupNotifications_getNotification(id, browser) {
- let n = null;
- let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
- notifications.some(function(x) x.id == id && (n = x));
- return n;
- },
-
- /**
- * Adds a new popup notification.
- * @param browser
- * The <xul:browser> element associated with the notification. Must not
- * be null.
- * @param id
- * A unique ID that identifies the type of notification (e.g.
- * "geolocation"). Only one notification with a given ID can be visible
- * at a time. If a notification already exists with the given ID, it
- * will be replaced.
- * @param message
- * The text to be displayed in the notification.
- * @param anchorID
- * The ID of the element that should be used as this notification
- * popup's anchor. May be null, in which case the notification will be
- * anchored to the iconBox.
- * @param mainAction
- * A JavaScript object literal describing the notification button's
- * action. If present, it must have the following properties:
- * - label (string): the button's label.
- * - accessKey (string): the button's accessKey.
- * - callback (function): a callback to be invoked when the button is
- * pressed, is passed an object that contains the following fields:
- * - checkboxChecked: (boolean) If the optional checkbox is checked.
- * If null, the notification will not have a button, and
- * secondaryActions will be ignored.
- * @param secondaryActions
- * An optional JavaScript array describing the notification's alternate
- * actions. The array should contain objects with the same properties
- * as mainAction. These are used to populate the notification button's
- * dropdown menu.
- * @param options
- * An options JavaScript object holding additional properties for the
- * notification. The following properties are currently supported:
- * persistence: An integer. The notification will not automatically
- * dismiss for this many page loads.
- * timeout: A time in milliseconds. The notification will not
- * automatically dismiss before this time.
- * persistWhileVisible:
- * A boolean. If true, a visible notification will always
- * persist across location changes.
- * dismissed: Whether the notification should be added as a dismissed
- * notification. Dismissed notifications can be activated
- * by clicking on their anchorElement.
- * eventCallback:
- * Callback to be invoked when the notification changes
- * state. The callback's first argument is a string
- * identifying the state change:
- * "dismissed": notification has been dismissed by the
- * user (e.g. by clicking away or switching
- * tabs)
- * "removed": notification has been removed (due to
- * location change or user action)
- * "showing": notification is about to be shown
- * (this can be fired multiple times as
- * notifications are dismissed and re-shown)
- * "shown": notification has been shown (this can be fired
- * multiple times as notifications are dismissed
- * and re-shown)
- * "swapping": the docshell of the browser that created
- * the notification is about to be swapped to
- * another browser. A second parameter contains
- * the browser that is receiving the docshell,
- * so that the event callback can transfer stuff
- * specific to this notification.
- * If the callback returns true, the notification
- * will be moved to the new browser.
- * If the callback isn't implemented, returns false,
- * or doesn't return any value, the notification
- * will be removed.
- * neverShow: Indicate that no popup should be shown for this
- * notification. Useful for just showing the anchor icon.
- * removeOnDismissal:
- * Notifications with this parameter set to true will be
- * removed when they would have otherwise been dismissed
- * (i.e. any time the popup is closed due to user
- * interaction).
- * checkbox: An object that allows you to add a checkbox and
- * control its behavior with these fields:
- * label:
- * (required) Label to be shown next to the checkbox.
- * checked:
- * (optional) Whether the checkbox should be checked
- * by default. Defaults to false.
- * checkedState:
- * (optional) An object that allows you to customize
- * the notification state when the checkbox is checked.
- * disableMainAction:
- * (optional) Whether the mainAction is disabled.
- * Defaults to false.
- * warningLabel:
- * (optional) A (warning) text that is shown below the
- * checkbox. Pass null to hide.
- * uncheckedState:
- * (optional) An object that allows you to customize
- * the notification state when the checkbox is not checked.
- * Has the same attributes as checkedState.
- * popupIconURL:
- * A string. URL of the image to be displayed in the popup.
- * Normally specified in CSS using list-style-image and the
- * .popup-notification-icon[popupid=...] selector.
- * learnMoreURL:
- * A string URL. Setting this property will make the
- * prompt display a "Learn More" link that, when clicked,
- * opens the URL in a new tab.
- * @returns the Notification object corresponding to the added notification.
- */
- show: function PopupNotifications_show(browser, id, message, anchorID,
- mainAction, secondaryActions, options) {
- function isInvalidAction(a) {
- return !a || !(typeof(a.callback) == "function") || !a.label || !a.accessKey;
- }
-
- if (!browser)
- throw "PopupNotifications_show: invalid browser";
- if (!id)
- throw "PopupNotifications_show: invalid ID";
- if (mainAction && isInvalidAction(mainAction))
- throw "PopupNotifications_show: invalid mainAction";
- if (secondaryActions && secondaryActions.some(isInvalidAction))
- throw "PopupNotifications_show: invalid secondaryActions";
-
- let notification = new Notification(id, message, anchorID, mainAction,
- secondaryActions, browser, this, options);
-
- if (options && options.dismissed)
- notification.dismissed = true;
-
- let existingNotification = this.getNotification(id, browser);
- if (existingNotification)
- this._remove(existingNotification);
-
- let notifications = this._getNotificationsForBrowser(browser);
- notifications.push(notification);
-
- let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
- if (browser.docShell.isActive && fm.activeWindow == this.window) {
- // show panel now
- this._update(notifications, notification.anchorElement, true);
- } else {
- // Otherwise, update() will display the notification the next time the
- // relevant tab/window is selected.
-
- // If the tab is selected but the window is in the background, let the OS
- // tell the user that there's a notification waiting in that window.
- // At some point we might want to do something about background tabs here
- // too. When the user switches to this window, we'll show the panel if
- // this browser is a tab (thus showing the anchor icon). For
- // non-tabbrowser browsers, we need to make the icon visible now or the
- // user will not be able to open the panel.
- if (!notification.dismissed && browser.docShell.isActive) {
- this.window.getAttention();
- if (notification.anchorElement.parentNode != this.iconBox) {
- notification.anchorElement.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
- }
- }
-
- // Notify observers that we're not showing the popup (useful for testing)
- this._notify("backgroundShow");
- }
-
- return notification;
- },
-
- /**
- * Returns true if the notification popup is currently being displayed.
- */
- get isPanelOpen() {
- let panelState = this.panel.state;
-
- return panelState == "showing" || panelState == "open";
- },
-
- /**
- * Called by the consumer to indicate that a browser's location has changed,
- * so that we can update the active notifications accordingly.
- */
- locationChange: function PopupNotifications_locationChange(aBrowser) {
- if (!aBrowser)
- throw "PopupNotifications_locationChange: invalid browser";
-
- let notifications = this._getNotificationsForBrowser(aBrowser);
-
- notifications = notifications.filter(function (notification) {
- // The persistWhileVisible option allows an open notification to persist
- // across location changes
- if (notification.options.persistWhileVisible &&
- this.isPanelOpen) {
- if ("persistence" in notification.options &&
- notification.options.persistence)
- notification.options.persistence--;
- return true;
- }
-
- // The persistence option allows a notification to persist across multiple
- // page loads
- if ("persistence" in notification.options &&
- notification.options.persistence) {
- notification.options.persistence--;
- return true;
- }
-
- // The timeout option allows a notification to persist until a certain time
- if ("timeout" in notification.options &&
- Date.now() <= notification.options.timeout) {
- return true;
- }
-
- this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
- return false;
- }, this);
-
- this._setNotificationsForBrowser(aBrowser, notifications);
-
- if (aBrowser.docShell.isActive) {
- // get the anchor element if the browser has defined one so it will
- // _update will handle both the tabs iconBox and non-tab permission
- // anchors.
- let anchorElement = notifications.length > 0 ? notifications[0].anchorElement : null;
- if (!anchorElement)
- anchorElement = getAnchorFromBrowser(aBrowser);
- this._update(notifications, anchorElement);
- }
- },
-
- /**
- * Removes a Notification.
- * @param notification
- * The Notification object to remove.
- */
- remove: function PopupNotifications_remove(notification) {
- this._remove(notification);
-
- if (notification.browser.docShell.isActive) {
- let notifications = this._getNotificationsForBrowser(notification.browser);
- this._update(notifications, notification.anchorElement);
- }
- },
-
- handleEvent: function (aEvent) {
- switch (aEvent.type) {
- case "popuphidden":
- this._onPopupHidden(aEvent);
- break;
- case "activate":
- case "TabSelect":
- let self = this;
- // setTimeout(..., 0) needed, otherwise openPopup from "activate" event
- // handler results in the popup being hidden again for some reason...
- this.window.setTimeout(function () {
- self._update();
- }, 0);
- break;
- case "click":
- case "keypress":
- this._onIconBoxCommand(aEvent);
- break;
- }
- },
-
-////////////////////////////////////////////////////////////////////////////////
-// Utility methods
-////////////////////////////////////////////////////////////////////////////////
-
- _ignoreDismissal: null,
- _currentAnchorElement: null,
-
- /**
- * Gets notifications for the currently selected browser.
- */
- get _currentNotifications() {
- return this.tabbrowser.selectedBrowser ? this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser) : [];
- },
-
- _remove: function PopupNotifications_removeHelper(notification) {
- // This notification may already be removed, in which case let's just fail
- // silently.
- let notifications = this._getNotificationsForBrowser(notification.browser);
- if (!notifications)
- return;
-
- var index = notifications.indexOf(notification);
- if (index == -1)
- return;
-
- if (notification.browser.docShell.isActive)
- notification.anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
-
- // remove the notification
- notifications.splice(index, 1);
- this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
- },
-
- /**
- * Dismisses the notification without removing it.
- */
- _dismiss: function PopupNotifications_dismiss() {
- let browser = this.panel.firstChild &&
- this.panel.firstChild.notification.browser;
- if (typeof this.panel.hidePopup === "function") {
- this.panel.hidePopup();
- }
- if (browser)
- browser.focus();
- },
-
- /**
- * Hides the notification popup.
- */
- _hidePanel: function PopupNotifications_hide() {
- this._ignoreDismissal = true;
- if (typeof this.panel.hidePopup === "function") {
- this.panel.hidePopup();
- }
- this._ignoreDismissal = false;
- },
-
- /**
- * Removes all notifications from the notification popup.
- */
- _clearPanel: function () {
- let popupnotification;
- while ((popupnotification = this.panel.lastChild)) {
- this.panel.removeChild(popupnotification);
-
- // If this notification was provided by the chrome document rather than
- // created ad hoc, move it back to where we got it from.
- let originalParent = gNotificationParents.get(popupnotification);
- if (originalParent) {
- popupnotification.notification = null;
-
- // Remove nodes dynamically added to the notification's menu button
- // in _refreshPanel. Keep popupnotificationcontent nodes; they are
- // provided by the chrome document.
- let contentNode = popupnotification.lastChild;
- while (contentNode) {
- let previousSibling = contentNode.previousSibling;
- if (contentNode.nodeName != "popupnotificationcontent")
- popupnotification.removeChild(contentNode);
- contentNode = previousSibling;
- }
-
- // Re-hide the notification such that it isn't rendered in the chrome
- // document. _refreshPanel will unhide it again when needed.
- popupnotification.hidden = true;
-
- originalParent.appendChild(popupnotification);
- }
- }
- },
-
- _refreshPanel: function PopupNotifications_refreshPanel(notificationsToShow) {
- this._clearPanel();
-
- const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
- notificationsToShow.forEach(function (n) {
- let doc = this.window.document;
-
- // Append "-notification" to the ID to try to avoid ID conflicts with other stuff
- // in the document.
- let popupnotificationID = n.id + "-notification";
-
- // If the chrome document provides a popupnotification with this id, use
- // that. Otherwise create it ad-hoc.
- let popupnotification = doc.getElementById(popupnotificationID);
- if (popupnotification)
- gNotificationParents.set(popupnotification, popupnotification.parentNode);
- else
- popupnotification = doc.createElementNS(XUL_NS, "popupnotification");
-
- popupnotification.setAttribute("label", n.message);
- popupnotification.setAttribute("id", popupnotificationID);
- popupnotification.setAttribute("popupid", n.id);
- popupnotification.setAttribute("closebuttoncommand", "PopupNotifications._dismiss();");
- if (n.mainAction) {
- popupnotification.setAttribute("buttonlabel", n.mainAction.label);
- popupnotification.setAttribute("buttonaccesskey", n.mainAction.accessKey);
- popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonCommand(event);");
- popupnotification.setAttribute("menucommand", "PopupNotifications._onMenuCommand(event);");
- popupnotification.setAttribute("closeitemcommand", "PopupNotifications._dismiss();event.stopPropagation();");
- } else {
- popupnotification.removeAttribute("buttonlabel");
- popupnotification.removeAttribute("buttonaccesskey");
- popupnotification.removeAttribute("buttoncommand");
- popupnotification.removeAttribute("menucommand");
- popupnotification.removeAttribute("closeitemcommand");
- }
-
- if (n.options.popupIconURL)
- popupnotification.setAttribute("icon", n.options.popupIconURL);
- if (n.options.learnMoreURL)
- popupnotification.setAttribute("learnmoreurl", n.options.learnMoreURL);
- else
- popupnotification.removeAttribute("learnmoreurl");
-
- popupnotification.notification = n;
-
- if (n.secondaryActions) {
- n.secondaryActions.forEach(function (a) {
- let item = doc.createElementNS(XUL_NS, "menuitem");
- item.setAttribute("label", a.label);
- item.setAttribute("accesskey", a.accessKey);
- item.notification = n;
- item.action = a;
-
- popupnotification.appendChild(item);
- }, this);
-
- if (n.secondaryActions.length) {
- let closeItemSeparator = doc.createElementNS(XUL_NS, "menuseparator");
- popupnotification.appendChild(closeItemSeparator);
- }
- }
-
- let checkbox = n.options.checkbox;
- if (checkbox && checkbox.label) {
- let checked = n._checkboxChecked != null ? n._checkboxChecked : !!checkbox.checked;
-
- popupnotification.setAttribute("checkboxhidden", "false");
- popupnotification.setAttribute("checkboxchecked", checked);
- popupnotification.setAttribute("checkboxlabel", checkbox.label);
-
- popupnotification.setAttribute("checkboxcommand", "PopupNotifications._onCheckboxCommand(event);");
-
- if (checked) {
- this._setNotificationUIState(popupnotification, checkbox.checkedState);
- } else {
- this._setNotificationUIState(popupnotification, checkbox.uncheckedState);
- }
- } else {
- popupnotification.setAttribute("checkboxhidden", "true");
- }
-
- this.panel.appendChild(popupnotification);
-
- // The popupnotification may be hidden if we got it from the chrome
- // document rather than creating it ad hoc.
- popupnotification.hidden = false;
- }, this);
- },
-
- _setNotificationUIState(notification, state={}) {
- notification.setAttribute("mainactiondisabled", state.disableMainAction || "false");
-
- if (state.warningLabel) {
- notification.setAttribute("warninglabel", state.warningLabel);
- notification.setAttribute("warninghidden", "false");
- } else {
- notification.setAttribute("warninghidden", "true");
- }
- },
-
- _onCheckboxCommand(event) {
- let notificationEl = getNotificationFromElement(event.originalTarget);
- let checked = notificationEl.checkbox.checked;
- let notification = notificationEl.notification;
-
- // Save checkbox state to be able to persist it when re-opening the doorhanger.
- notification._checkboxChecked = checked;
-
- if (checked) {
- this._setNotificationUIState(notificationEl, notification.options.checkbox.checkedState);
- } else {
- this._setNotificationUIState(notificationEl, notification.options.checkbox.uncheckedState);
- }
- },
-
- _showPanel: function PopupNotifications_showPanel(notificationsToShow, anchorElement) {
- this.panel.hidden = false;
-
- notificationsToShow.forEach(function (n) {
- this._fireCallback(n, NOTIFICATION_EVENT_SHOWING);
- }, this);
- this._refreshPanel(notificationsToShow);
-
- if (this.isPanelOpen && this._currentAnchorElement == anchorElement)
- return;
-
- // If the panel is already open but we're changing anchors, we need to hide
- // it first. Otherwise it can appear in the wrong spot. (_hidePanel is
- // safe to call even if the panel is already hidden.)
- this._hidePanel();
-
- // If the anchor element is hidden or null, use the tab as the anchor. We
- // only ever show notifications for the current browser, so we can just use
- // the current tab.
- let selectedTab = this.tabbrowser.selectedTab;
- if (anchorElement) {
- let bo = anchorElement.boxObject;
- if (bo.height == 0 && bo.width == 0)
- anchorElement = selectedTab; // hidden
- } else {
- anchorElement = selectedTab; // null
- }
-
- this._currentAnchorElement = anchorElement;
-
- // On OS X and Linux we need a different panel arrow color for
- // click-to-play plugins, so copy the popupid and use css.
- this.panel.setAttribute("popupid", this.panel.firstChild.getAttribute("popupid"));
- notificationsToShow.forEach(function (n) {
- // Remember the time the notification was shown for the security delay.
- n.timeShown = this.window.performance.now();
- }, this);
- this.panel.openPopup(anchorElement, "bottomcenter topleft");
- notificationsToShow.forEach(function (n) {
- this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
- }, this);
- },
-
- /**
- * Updates the notification state in response to window activation or tab
- * selection changes.
- *
- * @param notifications an array of Notification instances. if null,
- * notifications will be retrieved off the current
- * browser tab
- * @param anchor is a XUL element that the notifications panel will be
- * anchored to
- * @param dismissShowing if true, dismiss any currently visible notifications
- * if there are no notifications to show. Otherwise,
- * currently displayed notifications will be left alone.
- */
- _update: function PopupNotifications_update(notifications, anchor, dismissShowing = false) {
- let useIconBox = this.iconBox && (!anchor || anchor.parentNode == this.iconBox);
- if (useIconBox) {
- // hide icons of the previous tab.
- this._hideIcons();
- }
-
- let anchorElement = anchor, notificationsToShow = [];
- if (!notifications)
- notifications = this._currentNotifications;
- let haveNotifications = notifications.length > 0;
- if (haveNotifications) {
- // Only show the notifications that have the passed-in anchor (or the
- // first notification's anchor, if none was passed in). Other
- // notifications will be shown once these are dismissed.
- anchorElement = anchor || notifications[0].anchorElement;
-
- if (useIconBox) {
- this._showIcons(notifications);
- this.iconBox.hidden = false;
- } else if (anchorElement) {
- anchorElement.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
- // use the anchorID as a class along with the default icon class as a
- // fallback if anchorID is not defined in CSS. We always use the first
- // notifications icon, so in the case of multiple notifications we'll
- // only use the default icon
- if (anchorElement.classList.contains("notification-anchor-icon")) {
- // remove previous icon classes
- let className = anchorElement.className.replace(/([-\w]+-notification-icon\s?)/g,"")
- className = "default-notification-icon " + className;
- if (notifications.length == 1) {
- className = notifications[0].anchorID + " " + className;
- }
- anchorElement.className = className;
- }
- }
-
- // Also filter out notifications that have been dismissed.
- notificationsToShow = notifications.filter(function (n) {
- return !n.dismissed && n.anchorElement == anchorElement &&
- !n.options.neverShow;
- });
- }
-
- if (notificationsToShow.length > 0) {
- this._showPanel(notificationsToShow, anchorElement);
- } else {
- // Notify observers that we're not showing the popup (useful for testing)
- this._notify("updateNotShowing");
-
- // Close the panel if there are no notifications to show.
- // When called from PopupNotifications.show() we should never close the
- // panel, however. It may just be adding a dismissed notification, in
- // which case we want to continue showing any existing notifications.
- if (!dismissShowing)
- this._dismiss();
-
- // Only hide the iconBox if we actually have no notifications (as opposed
- // to not having any showable notifications)
- if (!haveNotifications) {
- if (useIconBox)
- this.iconBox.hidden = true;
- else if (anchorElement)
- anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
- }
- }
- },
-
- _showIcons: function PopupNotifications_showIcons(aCurrentNotifications) {
- for (let notification of aCurrentNotifications) {
- let anchorElm = notification.anchorElement;
- if (anchorElm) {
- anchorElm.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
- }
- }
- },
-
- _hideIcons: function PopupNotifications_hideIcons() {
- let icons = this.iconBox.querySelectorAll(ICON_SELECTOR);
- for (let icon of icons) {
- icon.removeAttribute(ICON_ATTRIBUTE_SHOWING);
- }
- },
-
- /**
- * Gets and sets notifications for the browser.
- */
- _getNotificationsForBrowser: function PopupNotifications_getNotifications(browser) {
- let notifications = popupNotificationsMap.get(browser);
- if (!notifications) {
- // Initialize the WeakMap for the browser so callers can reference/manipulate the array.
- notifications = [];
- popupNotificationsMap.set(browser, notifications);
- }
- return notifications;
- },
- _setNotificationsForBrowser: function PopupNotifications_setNotifications(browser, notifications) {
- popupNotificationsMap.set(browser, notifications);
- return notifications;
- },
-
- _onIconBoxCommand: function PopupNotifications_onIconBoxCommand(event) {
- // Left click, space or enter only
- let type = event.type;
- if (type == "click" && event.button != 0)
- return;
-
- if (type == "keypress" &&
- !(event.charCode == Ci.nsIDOMKeyEvent.DOM_VK_SPACE ||
- event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN))
- return;
-
- if (this._currentNotifications.length == 0)
- return;
-
- // Get the anchor that is the immediate child of the icon box
- let anchor = event.target;
- while (anchor && anchor.parentNode != this.iconBox)
- anchor = anchor.parentNode;
-
- this._reshowNotifications(anchor);
- },
-
- _reshowNotifications: function PopupNotifications_reshowNotifications(anchor, browser) {
- // Mark notifications anchored to this anchor as un-dismissed
- let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
- notifications.forEach(function (n) {
- if (n.anchorElement == anchor)
- n.dismissed = false;
- });
-
- // ...and then show them.
- this._update(notifications, anchor);
- },
-
- _swapBrowserNotifications: function PopupNotifications_swapBrowserNoficications(ourBrowser, otherBrowser) {
- // When swaping browser docshells (e.g. dragging tab to new window) we need
- // to update our notification map.
-
- let ourNotifications = this._getNotificationsForBrowser(ourBrowser);
- let other = otherBrowser.ownerDocument.defaultView.PopupNotifications;
- if (!other) {
- if (ourNotifications.length > 0)
- Cu.reportError("unable to swap notifications: otherBrowser doesn't support notifications");
- return;
- }
- let otherNotifications = other._getNotificationsForBrowser(otherBrowser);
- if (ourNotifications.length < 1 && otherNotifications.length < 1) {
- // No notification to swap.
- return;
- }
-
- otherNotifications = otherNotifications.filter(n => {
- if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, ourBrowser)) {
- n.browser = ourBrowser;
- n.owner = this;
- return true;
- }
- other._fireCallback(n, NOTIFICATION_EVENT_REMOVED);
- return false;
- });
-
- ourNotifications = ourNotifications.filter(n => {
- if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, otherBrowser)) {
- n.browser = otherBrowser;
- n.owner = other;
- return true;
- }
- this._fireCallback(n, NOTIFICATION_EVENT_REMOVED);
- return false;
- });
-
- this._setNotificationsForBrowser(otherBrowser, ourNotifications);
- other._setNotificationsForBrowser(ourBrowser, otherNotifications);
-
- if (otherNotifications.length > 0)
- this._update(otherNotifications, otherNotifications[0].anchorElement);
- if (ourNotifications.length > 0)
- other._update(ourNotifications, ourNotifications[0].anchorElement);
- },
-
- _fireCallback: function PopupNotifications_fireCallback(n, event, ...args) {
- try {
- if (n.options.eventCallback)
- return n.options.eventCallback.call(n, event, ...args);
- } catch (error) {
- Cu.reportError(error);
- }
- return undefined;
- },
-
- _onPopupHidden: function PopupNotifications_onPopupHidden(event) {
- if (event.target != this.panel || this._ignoreDismissal)
- return;
-
- let browser = this.panel.firstChild &&
- this.panel.firstChild.notification.browser;
- if (!browser)
- return;
-
- let notifications = this._getNotificationsForBrowser(browser);
- // Mark notifications as dismissed and call dismissal callbacks
- Array.forEach(this.panel.childNodes, function (nEl) {
- let notificationObj = nEl.notification;
- // Never call a dismissal handler on a notification that's been removed.
- if (notifications.indexOf(notificationObj) == -1)
- return;
-
- // Do not mark the notification as dismissed or fire NOTIFICATION_EVENT_DISMISSED
- // if the notification is removed.
- if (notificationObj.options.removeOnDismissal)
- this._remove(notificationObj);
- else {
- notificationObj.dismissed = true;
- this._fireCallback(notificationObj, NOTIFICATION_EVENT_DISMISSED);
- }
- }, this);
-
- this._clearPanel();
-
- this._update();
- },
-
- _onButtonCommand: function PopupNotifications_onButtonCommand(event) {
- let notificationEl = getNotificationFromElement(event.originalTarget);
-
- if (!notificationEl)
- throw "PopupNotifications_onButtonCommand: couldn't find notification element";
-
- if (!notificationEl.notification)
- throw "PopupNotifications_onButtonCommand: couldn't find notification";
-
- let notification = notificationEl.notification;
- let timeSinceShown = this.window.performance.now() - notification.timeShown;
-
- // Only report the first time mainAction is triggered and remember that this occurred.
- if (!notification.timeMainActionFirstTriggered) {
- notification.timeMainActionFirstTriggered = timeSinceShown;
- }
-
- if (timeSinceShown < this.buttonDelay) {
- Services.console.logStringMessage("PopupNotifications_onButtonCommand: " +
- "Button click happened before the security delay: " +
- timeSinceShown + "ms");
- return;
- }
-
- try {
- notification.mainAction.callback.call(undefined, {
- checkboxChecked: notificationEl.checkbox.checked
- });
- } catch (error) {
- Cu.reportError(error);
- }
-
- this._remove(notification);
- this._update();
- },
-
- _onMenuCommand: function PopupNotifications_onMenuCommand(event) {
- let target = event.originalTarget;
- if (!target.action || !target.notification)
- throw "menucommand target has no associated action/notification";
-
- let notificationEl = target.parentElement;
- event.stopPropagation();
-
- try {
- target.action.callback.call(undefined, {
- checkboxChecked: notificationEl.checkbox.checked
- });
- } catch (error) {
- Cu.reportError(error);
- }
-
- this._remove(target.notification);
- this._update();
- },
-
- _notify: function PopupNotifications_notify(topic) {
- Services.obs.notifyObservers(null, "PopupNotifications-" + topic, "");
- },
-};