Bumping manifests a=b2g-bump
[gecko.git] / b2g / components / AlertsService.js
blobae76de3e1ec083fac1b15a433fd50deaa36f7b6d
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const Ci = Components.interfaces;
6 const Cu = Components.utils;
7 const Cc = Components.classes;
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
10 Cu.import("resource://gre/modules/Services.jsm");
12 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
13                                    "@mozilla.org/system-message-internal;1",
14                                    "nsISystemMessagesInternal");
16 XPCOMUtils.defineLazyServiceGetter(this, "uuidGenerator",
17                                    "@mozilla.org/uuid-generator;1",
18                                    "nsIUUIDGenerator");
20 XPCOMUtils.defineLazyServiceGetter(this, "notificationStorage",
21                                    "@mozilla.org/notificationStorage;1",
22                                    "nsINotificationStorage");
25 XPCOMUtils.defineLazyGetter(this, "cpmm", function() {
26   return Cc["@mozilla.org/childprocessmessagemanager;1"]
27            .getService(Ci.nsIMessageSender);
28 });
30 function debug(str) {
31   dump("=*= AlertsService.js : " + str + "\n");
34 // -----------------------------------------------------------------------
35 // Alerts Service
36 // -----------------------------------------------------------------------
38 const kNotificationSystemMessageName = "notification";
40 const kMessageAppNotificationSend    = "app-notification-send";
41 const kMessageAppNotificationReturn  = "app-notification-return";
42 const kMessageAlertNotificationSend  = "alert-notification-send";
43 const kMessageAlertNotificationClose = "alert-notification-close";
45 const kTopicAlertShow          = "alertshow";
46 const kTopicAlertFinished      = "alertfinished";
47 const kTopicAlertClickCallback = "alertclickcallback";
49 const kAllowOverwritePrefName  =
50   "dom.notifications.mozchromenotifications.allow_resend_overwrite";
52 function AlertsService() {
53   this.trySetPrefValue();
54   Services.obs.addObserver(this, "xpcom-shutdown", false);
55   Services.prefs.addObserver(kAllowOverwritePrefName, this, false);
56   cpmm.addMessageListener(kMessageAppNotificationReturn, this);
59 AlertsService.prototype = {
60   classID: Components.ID("{fe33c107-82a4-41d6-8c64-5353267e04c9}"),
61   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAlertsService,
62                                          Ci.nsIAppNotificationService,
63                                          Ci.nsIObserver]),
65   trySetPrefValue: function() {
66     try {
67       this.allowResendOverwrite =
68         Services.prefs.getBoolPref(kAllowOverwritePrefName);
69     } catch (ex) {
70       this.allowResendOverwrite = false;
71     }
72   },
74   observe: function(aSubject, aTopic, aData) {
75     switch (aTopic) {
76       case "nsPref:changed":
77         if (aData === kAllowOverwritePrefName) {
78           this.trySetPrefValue();
79         }
80         break;
82       case "xpcom-shutdown":
83         Services.obs.removeObserver(this, "xpcom-shutdown");
84         Services.prefs.removeObserver(kAllowOverwritePrefName, this);
85         cpmm.removeMessageListener(kMessageAppNotificationReturn, this);
86         break;
87     }
88   },
90   // nsIAlertsService
91   showAlertNotification: function(aImageUrl, aTitle, aText, aTextClickable,
92                                   aCookie, aAlertListener, aName, aBidi,
93                                   aLang, aDataStr) {
94     cpmm.sendAsyncMessage(kMessageAlertNotificationSend, {
95       imageURL: aImageUrl,
96       title: aTitle,
97       text: aText,
98       clickable: aTextClickable,
99       cookie: aCookie,
100       listener: aAlertListener,
101       id: aName,
102       dir: aBidi,
103       lang: aLang,
104       dataStr: aDataStr
105     });
106   },
108   closeAlert: function(aName) {
109     cpmm.sendAsyncMessage(kMessageAlertNotificationClose, {
110       name: aName
111     });
112   },
114   // nsIAppNotificationService
115   showAppNotification: function(aImageURL, aTitle, aText, aAlertListener,
116                                 aDetails) {
117     let uid = (aDetails.id == "") ?
118           "app-notif-" + uuidGenerator.generateUUID() : aDetails.id;
120     let dataObj = this.deserializeStructuredClone(aDetails.data);
122     if (this._listeners[uid]) {
123       // Is the notification we are dealing with is a resent one?
124       let currentIsResend = (aDetails.resent === true);
126       // Was the previous notification a resent one?
127       let oldIsResend = (this._listeners[uid].resent === true);
129       // Does the user allows resent notification to overwrite non resent ?
130       let denyOverwrite = this.allowResendOverwrite !== true;
132       // Are we in a case where we are trying to overwrite non resent with
133       // resent ?
134       let tentativeOverwrite = !oldIsResend && currentIsResend;
136       /**
137        * If the current notification is a resent one and the previously
138        * recorded one was not a resent, let's check if we are allowed to
139        * overwrite. We do allow, however, to do multiple resends.
140        **/
141       if (tentativeOverwrite && denyOverwrite) {
142         Cu.reportError("Notification " + uid + ": overwriting tentative by resend.");
143         return false;
144       }
145     }
147     this._listeners[uid] = {
148       observer: aAlertListener,
149       title: aTitle,
150       text: aText,
151       manifestURL: aDetails.manifestURL,
152       imageURL: aImageURL,
153       lang: aDetails.lang || undefined,
154       id: aDetails.id || undefined,
155       dbId: aDetails.dbId || undefined,
156       dir: aDetails.dir || undefined,
157       tag: aDetails.tag || undefined,
158       timestamp: aDetails.timestamp || undefined,
159       dataObj: dataObj || undefined,
160       resent: aDetails.resent || undefined
161     };
163     cpmm.sendAsyncMessage(kMessageAppNotificationSend, {
164       imageURL: aImageURL,
165       title: aTitle,
166       text: aText,
167       uid: uid,
168       details: aDetails
169     });
171     return true;
172   },
174   // AlertsService.js custom implementation
175   _listeners: [],
177   receiveMessage: function(aMessage) {
178     let data = aMessage.data;
179     let listener = this._listeners[data.uid];
180     if (aMessage.name !== kMessageAppNotificationReturn || !listener) {
181       return;
182     }
184     let topic = data.topic;
186     try {
187       listener.observer.observe(null, topic, null);
188     } catch (e) {
189       // It seems like there is no callbacks anymore, forward the click on
190       // notification via a system message containing the title/text/icon of
191       // the notification so the app get a change to react.
192       if (data.target) {
193         if (topic !== kTopicAlertShow) {
194           // excluding the 'show' event: there is no reason a unlaunched app
195           // would want to be notified that a notification is shown. This
196           // happens when a notification is still displayed at reboot time.
197           gSystemMessenger.sendMessage(kNotificationSystemMessageName, {
198               clicked: (topic === kTopicAlertClickCallback),
199               title: listener.title,
200               body: listener.text,
201               imageURL: listener.imageURL,
202               lang: listener.lang,
203               dir: listener.dir,
204               id: listener.id,
205               tag: listener.tag,
206               dbId: listener.dbId,
207               timestamp: listener.timestamp,
208               data: listener.dataObj || undefined,
209             },
210             Services.io.newURI(data.target, null, null),
211             Services.io.newURI(listener.manifestURL, null, null)
212           );
213         }
214       }
215     }
217     // we're done with this notification
218     if (topic === kTopicAlertFinished) {
219       if (listener.dbId) {
220         notificationStorage.delete(listener.manifestURL, listener.dbId);
221       }
222       delete this._listeners[data.uid];
223     }
224   },
226   deserializeStructuredClone: function(dataString) {
227     if (!dataString) {
228       return null;
229     }
230     let scContainer = Cc["@mozilla.org/docshell/structured-clone-container;1"].
231       createInstance(Ci.nsIStructuredCloneContainer);
233     // The maximum supported structured-clone serialization format version
234     // as defined in "js/public/StructuredClone.h"
235     let JS_STRUCTURED_CLONE_VERSION = 4;
236     scContainer.initFromBase64(dataString, JS_STRUCTURED_CLONE_VERSION);
237     let dataObj = scContainer.deserializeToVariant();
239     // We have to check whether dataObj contains DOM objects (supported by
240     // nsIStructuredCloneContainer, but not by Cu.cloneInto), e.g. ImageData.
241     // After the structured clone callback systems will be unified, we'll not
242     // have to perform this check anymore.
243     try {
244       let data = Cu.cloneInto(dataObj, {});
245     } catch(e) { dataObj = null; }
247     return dataObj;
248   }
251 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AlertsService]);