diff options
Diffstat (limited to 'application/palemoon/modules/PopupNotifications.jsm')
-rw-r--r-- | application/palemoon/modules/PopupNotifications.jsm | 189 |
1 files changed, 170 insertions, 19 deletions
diff --git a/application/palemoon/modules/PopupNotifications.jsm b/application/palemoon/modules/PopupNotifications.jsm index 9b2e8e5d1..0cb970230 100644 --- a/application/palemoon/modules/PopupNotifications.jsm +++ b/application/palemoon/modules/PopupNotifications.jsm @@ -4,22 +4,23 @@ this.EXPORTED_SYMBOLS = ["PopupNotifications"]; -var Cc = Components.classes, Ci = Components.interfaces; +var Cc = Components.classes, Ci = Components.interfaces, Cu = Components.utils; -Components.utils.import("resource://gre/modules/Services.jsm"); +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"; -let popupNotificationsMap = new WeakMap(); -let gNotificationParents = new WeakMap; +var popupNotificationsMap = new WeakMap(); +var gNotificationParents = new WeakMap; function getAnchorFromBrowser(aBrowser) { let anchor = aBrowser.getAttribute("popupnotificationanchor") || @@ -33,6 +34,18 @@ function getAnchorFromBrowser(aBrowser) { 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. * @@ -194,7 +207,8 @@ PopupNotifications.prototype = { * - label (string): the button's label. * - accessKey (string): the button's accessKey. * - callback (function): a callback to be invoked when the button is - * pressed. + * 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 @@ -224,9 +238,23 @@ PopupNotifications.prototype = { * 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: @@ -234,6 +262,26 @@ PopupNotifications.prototype = { * 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 @@ -552,6 +600,25 @@ PopupNotifications.prototype = { } } + 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 @@ -560,6 +627,32 @@ PopupNotifications.prototype = { }, 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; @@ -751,9 +844,60 @@ PopupNotifications.prototype = { this._update(notifications, anchor); }, - _fireCallback: function PopupNotifications_fireCallback(n, event) { - if (n.options.eventCallback) - n.options.eventCallback.call(n, event); + _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) { @@ -789,15 +933,7 @@ PopupNotifications.prototype = { }, _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; + let notificationEl = getNotificationFromElement(event.originalTarget); if (!notificationEl) throw "PopupNotifications_onButtonCommand: couldn't find notification element"; @@ -819,7 +955,14 @@ PopupNotifications.prototype = { timeSinceShown + "ms"); return; } - notification.mainAction.callback.call(); + + try { + notification.mainAction.callback.call(undefined, { + checkboxChecked: notificationEl.checkbox.checked + }); + } catch (error) { + Cu.reportError(error); + } this._remove(notification); this._update(); @@ -830,8 +973,16 @@ PopupNotifications.prototype = { if (!target.action || !target.notification) throw "menucommand target has no associated action/notification"; + let notificationEl = target.parentElement; event.stopPropagation(); - target.action.callback.call(); + + try { + target.action.callback.call(undefined, { + checkboxChecked: notificationEl.checkbox.checked + }); + } catch (error) { + Cu.reportError(error); + } this._remove(target.notification); this._update(); |