summaryrefslogtreecommitdiffstats
path: root/toolkit/components/webextensions/ext-notifications.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/webextensions/ext-notifications.js')
-rw-r--r--toolkit/components/webextensions/ext-notifications.js161
1 files changed, 161 insertions, 0 deletions
diff --git a/toolkit/components/webextensions/ext-notifications.js b/toolkit/components/webextensions/ext-notifications.js
new file mode 100644
index 000000000..1df96a2ac
--- /dev/null
+++ b/toolkit/components/webextensions/ext-notifications.js
@@ -0,0 +1,161 @@
+"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"),
+ },
+ };
+});