"use strict"; var {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/ExtensionUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter", "resource://devtools/shared/event-emitter.js"); var { EventManager, ignoreEvent, } = ExtensionUtils; // WeakMap[Extension -> Map[id -> Notification]] var notificationsMap = new WeakMap(); // Manages a notification popup (notifications API) created by the extension. function Notification(extension, id, options) { this.extension = extension; this.id = id; this.options = options; let imageURL; if (options.iconUrl) { imageURL = this.extension.baseURI.resolve(options.iconUrl); } try { let svc = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); svc.showAlertNotification(imageURL, options.title, options.message, true, // textClickable this.id, this, this.id); } catch (e) { // This will fail if alerts aren't available on the system. } } Notification.prototype = { clear() { try { let svc = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); svc.closeAlert(this.id); } catch (e) { // This will fail if the OS doesn't support this function. } notificationsMap.get(this.extension).delete(this.id); }, observe(subject, topic, data) { let notifications = notificationsMap.get(this.extension); let emitAndDelete = event => { notifications.emit(event, data); notifications.delete(this.id); }; // Don't try to emit events if the extension has been unloaded if (!notifications) { return; } if (topic === "alertclickcallback") { emitAndDelete("clicked"); } if (topic === "alertfinished") { emitAndDelete("closed"); } }, }; /* eslint-disable mozilla/balanced-listeners */ extensions.on("startup", (type, extension) => { let map = new Map(); EventEmitter.decorate(map); notificationsMap.set(extension, map); }); extensions.on("shutdown", (type, extension) => { if (notificationsMap.has(extension)) { for (let notification of notificationsMap.get(extension).values()) { notification.clear(); } notificationsMap.delete(extension); } }); /* eslint-enable mozilla/balanced-listeners */ var nextId = 0; extensions.registerSchemaAPI("notifications", "addon_parent", context => { let {extension} = context; return { notifications: { create: function(notificationId, options) { if (!notificationId) { notificationId = String(nextId++); } let notifications = notificationsMap.get(extension); if (notifications.has(notificationId)) { notifications.get(notificationId).clear(); } // FIXME: Lots of options still aren't supported, especially // buttons. let notification = new Notification(extension, notificationId, options); notificationsMap.get(extension).set(notificationId, notification); return Promise.resolve(notificationId); }, clear: function(notificationId) { let notifications = notificationsMap.get(extension); if (notifications.has(notificationId)) { notifications.get(notificationId).clear(); return Promise.resolve(true); } return Promise.resolve(false); }, getAll: function() { let result = {}; notificationsMap.get(extension).forEach((value, key) => { result[key] = value.options; }); return Promise.resolve(result); }, onClosed: new EventManager(context, "notifications.onClosed", fire => { let listener = (event, notificationId) => { // FIXME: Support the byUser argument. fire(notificationId, true); }; notificationsMap.get(extension).on("closed", listener); return () => { notificationsMap.get(extension).off("closed", listener); }; }).api(), onClicked: new EventManager(context, "notifications.onClicked", fire => { let listener = (event, notificationId) => { fire(notificationId, true); }; notificationsMap.get(extension).on("clicked", listener); return () => { notificationsMap.get(extension).off("clicked", listener); }; }).api(), // Intend to implement this later: https://bugzilla.mozilla.org/show_bug.cgi?id=1190681 onButtonClicked: ignoreEvent(context, "notifications.onButtonClicked"), }, }; });