forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			188 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			188 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/. */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| var EXPORTED_SYMBOLS = ["AppMenuNotifications"];
 | |
| 
 | |
| function AppMenuNotification(id, mainAction, secondaryAction, options = {}) {
 | |
|   this.id = id;
 | |
|   this.mainAction = mainAction;
 | |
|   this.secondaryAction = secondaryAction;
 | |
|   this.options = options;
 | |
|   this.dismissed = this.options.dismissed || false;
 | |
| }
 | |
| 
 | |
| var AppMenuNotifications = {
 | |
|   _notifications: [],
 | |
|   _hasInitialized: false,
 | |
| 
 | |
|   get notifications() {
 | |
|     return Array.from(this._notifications);
 | |
|   },
 | |
| 
 | |
|   _lazyInit() {
 | |
|     if (!this._hasInitialized) {
 | |
|       Services.obs.addObserver(this, "xpcom-shutdown");
 | |
|       Services.obs.addObserver(this, "appMenu-notifications-request");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     Services.obs.removeObserver(this, "xpcom-shutdown");
 | |
|     Services.obs.removeObserver(this, "appMenu-notifications-request");
 | |
|   },
 | |
| 
 | |
|   observe(subject, topic, status) {
 | |
|     switch (topic) {
 | |
|       case "xpcom-shutdown":
 | |
|         this.uninit();
 | |
|         break;
 | |
|       case "appMenu-notifications-request":
 | |
|         if (this._notifications.length) {
 | |
|           Services.obs.notifyObservers(null, "appMenu-notifications", "init");
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   get activeNotification() {
 | |
|     if (this._notifications.length) {
 | |
|       const doorhanger = this._notifications.find(
 | |
|         n => !n.dismissed && !n.options.badgeOnly
 | |
|       );
 | |
|       return doorhanger || this._notifications[0];
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   showNotification(id, mainAction, secondaryAction, options = {}) {
 | |
|     let notification = new AppMenuNotification(
 | |
|       id,
 | |
|       mainAction,
 | |
|       secondaryAction,
 | |
|       options
 | |
|     );
 | |
|     let existingIndex = this._notifications.findIndex(n => n.id == id);
 | |
|     if (existingIndex != -1) {
 | |
|       this._notifications.splice(existingIndex, 1);
 | |
|     }
 | |
| 
 | |
|     // We don't want to clobber doorhanger notifications just to show a badge,
 | |
|     // so don't dismiss any of them and the badge will show once the doorhanger
 | |
|     // gets resolved.
 | |
|     if (!options.badgeOnly && !options.dismissed) {
 | |
|       this._notifications.forEach(n => {
 | |
|         n.dismissed = true;
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     // Since notifications are generally somewhat pressing, the ideal case is that
 | |
|     // we never have two notifications at once. However, in the event that we do,
 | |
|     // it's more likely that the older notification has been sitting around for a
 | |
|     // bit, and so we don't want to hide the new notification behind it. Thus,
 | |
|     // we want our notifications to behave like a stack instead of a queue.
 | |
|     this._notifications.unshift(notification);
 | |
| 
 | |
|     this._lazyInit();
 | |
|     this._updateNotifications();
 | |
|     return notification;
 | |
|   },
 | |
| 
 | |
|   showBadgeOnlyNotification(id) {
 | |
|     return this.showNotification(id, null, null, { badgeOnly: true });
 | |
|   },
 | |
| 
 | |
|   removeNotification(id) {
 | |
|     let notifications;
 | |
|     if (typeof id == "string") {
 | |
|       notifications = this._notifications.filter(n => n.id == id);
 | |
|     } else {
 | |
|       // If it's not a string, assume RegExp
 | |
|       notifications = this._notifications.filter(n => id.test(n.id));
 | |
|     }
 | |
|     // _updateNotifications can be expensive if it forces attachment of XBL
 | |
|     // bindings that haven't been used yet, so return early if we haven't found
 | |
|     // any notification to remove, as callers may expect this removeNotification
 | |
|     // method to be a no-op for non-existent notifications.
 | |
|     if (!notifications.length) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     notifications.forEach(n => {
 | |
|       this._removeNotification(n);
 | |
|     });
 | |
|     this._updateNotifications();
 | |
|   },
 | |
| 
 | |
|   dismissNotification(id) {
 | |
|     let notifications;
 | |
|     if (typeof id == "string") {
 | |
|       notifications = this._notifications.filter(n => n.id == id);
 | |
|     } else {
 | |
|       // If it's not a string, assume RegExp
 | |
|       notifications = this._notifications.filter(n => id.test(n.id));
 | |
|     }
 | |
| 
 | |
|     notifications.forEach(n => {
 | |
|       n.dismissed = true;
 | |
|       if (n.options.onDismissed) {
 | |
|         n.options.onDismissed();
 | |
|       }
 | |
|     });
 | |
|     this._updateNotifications();
 | |
|   },
 | |
| 
 | |
|   callMainAction(win, notification, fromDoorhanger) {
 | |
|     let action = notification.mainAction;
 | |
|     this._callAction(win, notification, action, fromDoorhanger);
 | |
|   },
 | |
| 
 | |
|   callSecondaryAction(win, notification) {
 | |
|     let action = notification.secondaryAction;
 | |
|     this._callAction(win, notification, action, true);
 | |
|   },
 | |
| 
 | |
|   _callAction(win, notification, action, fromDoorhanger) {
 | |
|     let dismiss = true;
 | |
|     if (action) {
 | |
|       try {
 | |
|         action.callback(win, fromDoorhanger);
 | |
|       } catch (error) {
 | |
|         Cu.reportError(error);
 | |
|       }
 | |
| 
 | |
|       dismiss = action.dismiss;
 | |
|     }
 | |
| 
 | |
|     if (dismiss) {
 | |
|       notification.dismissed = true;
 | |
|     } else {
 | |
|       this._removeNotification(notification);
 | |
|     }
 | |
| 
 | |
|     this._updateNotifications();
 | |
|   },
 | |
| 
 | |
|   _removeNotification(notification) {
 | |
|     // This notification may already be removed, in which case let's just ignore.
 | |
|     let notifications = this._notifications;
 | |
|     if (!notifications) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     var index = notifications.indexOf(notification);
 | |
|     if (index == -1) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Remove the notification
 | |
|     notifications.splice(index, 1);
 | |
|   },
 | |
| 
 | |
|   _updateNotifications() {
 | |
|     Services.obs.notifyObservers(null, "appMenu-notifications", "update");
 | |
|   },
 | |
| };
 | 
