mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-31 11:37:30 +09:00
Add Pale Moon
This commit is contained in:
parent
dcd9973243
commit
fe8028fa2e
1173 changed files with 143053 additions and 934 deletions
843
application/palemoon/modules/PopupNotifications.jsm
Normal file
843
application/palemoon/modules/PopupNotifications.jsm
Normal file
|
|
@ -0,0 +1,843 @@
|
|||
/* 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;
|
||||
|
||||
Components.utils.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 ICON_SELECTOR = ".notification-anchor-icon";
|
||||
const ICON_ATTRIBUTE_SHOWING = "showing";
|
||||
|
||||
const PREF_SECURITY_DELAY = "security.notification_enable_delay";
|
||||
|
||||
let popupNotificationsMap = new WeakMap();
|
||||
let 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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)
|
||||
* "shown": notification has been shown (this can be fired
|
||||
* multiple times as notifications are dismissed
|
||||
* and re-shown)
|
||||
* 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).
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
_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);
|
||||
},
|
||||
|
||||
_fireCallback: function PopupNotifications_fireCallback(n, event) {
|
||||
if (n.options.eventCallback)
|
||||
n.options.eventCallback.call(n, event);
|
||||
},
|
||||
|
||||
_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) {
|
||||
// Need to find the associated notification object, which is a bit tricky
|
||||
// since it isn't associated with the button directly - this is kind of
|
||||
// gross and very dependent on the structure of the popupnotification
|
||||
// binding's content.
|
||||
let target = event.originalTarget;
|
||||
let notificationEl;
|
||||
let parent = target;
|
||||
while (parent && (parent = target.ownerDocument.getBindingParent(parent)))
|
||||
notificationEl = parent;
|
||||
|
||||
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;
|
||||
}
|
||||
notification.mainAction.callback.call();
|
||||
|
||||
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";
|
||||
|
||||
event.stopPropagation();
|
||||
target.action.callback.call();
|
||||
|
||||
this._remove(target.notification);
|
||||
this._update();
|
||||
},
|
||||
|
||||
_notify: function PopupNotifications_notify(topic) {
|
||||
Services.obs.notifyObservers(null, "PopupNotifications-" + topic, "");
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue