Bumping manifests a=b2g-bump
[gecko.git] / b2g / components / AlertsHelper.jsm
blob1065fa2f533a0c9797e9ef671d559a6b389c707a
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 "use strict";
7 this.EXPORTED_SYMBOLS = [];
9 const Ci = Components.interfaces;
10 const Cu = Components.utils;
11 const Cc = Components.classes;
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
14 Cu.import("resource://gre/modules/Services.jsm");
15 Cu.import("resource://gre/modules/AppsUtils.jsm");
17 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
18                                    "@mozilla.org/system-message-internal;1",
19                                    "nsISystemMessagesInternal");
21 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
22                                    "@mozilla.org/AppsService;1",
23                                    "nsIAppsService");
25 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
26                                   "resource://gre/modules/SystemAppProxy.jsm");
28 XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
29   return Cc["@mozilla.org/parentprocessmessagemanager;1"]
30          .getService(Ci.nsIMessageListenerManager);
31 });
33 function debug(str) {
34   //dump("=*= AlertsHelper.jsm : " + str + "\n");
37 const kNotificationIconSize = 128;
39 const kDesktopNotificationPerm = "desktop-notification";
41 const kNotificationSystemMessageName = "notification";
43 const kDesktopNotification      = "desktop-notification";
44 const kDesktopNotificationShow  = "desktop-notification-show";
45 const kDesktopNotificationClick = "desktop-notification-click";
46 const kDesktopNotificationClose = "desktop-notification-close";
48 const kTopicAlertClickCallback = "alertclickcallback";
49 const kTopicAlertShow          = "alertshow";
50 const kTopicAlertFinished      = "alertfinished";
52 const kMozChromeNotificationEvent  = "mozChromeNotificationEvent";
53 const kMozContentNotificationEvent = "mozContentNotificationEvent";
55 const kMessageAppNotificationSend    = "app-notification-send";
56 const kMessageAppNotificationReturn  = "app-notification-return";
57 const kMessageAlertNotificationSend  = "alert-notification-send";
58 const kMessageAlertNotificationClose = "alert-notification-close";
60 const kMessages = [
61   kMessageAppNotificationSend,
62   kMessageAlertNotificationSend,
63   kMessageAlertNotificationClose
66 let AlertsHelper = {
68   _listeners: {},
70   init: function() {
71     Services.obs.addObserver(this, "xpcom-shutdown", false);
72     for (let message of kMessages) {
73       ppmm.addMessageListener(message, this);
74     }
75     SystemAppProxy.addEventListener(kMozContentNotificationEvent, this);
76   },
78   observe: function(aSubject, aTopic, aData) {
79     switch (aTopic) {
80       case "xpcom-shutdown":
81         Services.obs.removeObserver(this, "xpcom-shutdown");
82         for (let message of kMessages) {
83           ppmm.removeMessageListener(message, this);
84         }
85         SystemAppProxy.removeEventListener(kMozContentNotificationEvent, this);
86         break;
87     }
88   },
90   handleEvent: function(evt) {
91     let detail = evt.detail;
93     switch(detail.type) {
94       case kDesktopNotificationShow:
95       case kDesktopNotificationClick:
96       case kDesktopNotificationClose:
97         this.handleNotificationEvent(detail);
98         break;
99       default:
100         debug("FIXME: Unhandled notification event: " + detail.type);
101         break;
102     }
103   },
105   handleNotificationEvent: function(detail) {
106     if (!detail || !detail.id) {
107       return;
108     }
110     let uid = detail.id;
111     let listener = this._listeners[uid];
112     if (!listener) {
113       return;
114     }
116     let topic;
117     if (detail.type === kDesktopNotificationClick) {
118       topic = kTopicAlertClickCallback;
119     } else if (detail.type === kDesktopNotificationShow) {
120       topic = kTopicAlertShow;
121     } else {
122       /* kDesktopNotificationClose */
123       topic = kTopicAlertFinished;
124     }
126     if (listener.cookie) {
127       try {
128         listener.observer.observe(null, topic, listener.cookie);
129       } catch (e) { }
130     } else {
131       try {
132         listener.mm.sendAsyncMessage(kMessageAppNotificationReturn, {
133           uid: uid,
134           topic: topic,
135           target: listener.target
136         });
137       } catch (e) {
138         // we get an exception if the app is not launched yet
139         if (detail.type !== kDesktopNotificationShow) {
140           // excluding the 'show' event: there is no reason a unlaunched app
141           // would want to be notified that a notification is shown. This
142           // happens when a notification is still displayed at reboot time.
143           gSystemMessenger.sendMessage(kNotificationSystemMessageName, {
144               clicked: (detail.type === kDesktopNotificationClick),
145               title: listener.title,
146               body: listener.text,
147               imageURL: listener.imageURL,
148               lang: listener.lang,
149               dir: listener.dir,
150               id: listener.id,
151               tag: listener.tag,
152               timestamp: listener.timestamp,
153               data: listener.dataObj
154             },
155             Services.io.newURI(listener.target, null, null),
156             Services.io.newURI(listener.manifestURL, null, null)
157           );
158         }
159       }
160     }
162     // we"re done with this notification
163     if (topic === kTopicAlertFinished) {
164       delete this._listeners[uid];
165     }
166   },
168   registerListener: function(alertId, cookie, alertListener) {
169     this._listeners[alertId] = { observer: alertListener, cookie: cookie };
170   },
172   registerAppListener: function(uid, listener) {
173     this._listeners[uid] = listener;
175     appsService.getManifestFor(listener.manifestURL).then((manifest) => {
176       let app = appsService.getAppByManifestURL(listener.manifestURL);
177       let helper = new ManifestHelper(manifest, app.origin, app.manifestURL);
178       let getNotificationURLFor = function(messages) {
179         if (!messages) {
180           return null;
181         }
183         for (let i = 0; i < messages.length; i++) {
184           let message = messages[i];
185           if (message === kNotificationSystemMessageName) {
186             return helper.fullLaunchPath();
187           } else if (typeof message === "object" &&
188                      kNotificationSystemMessageName in message) {
189             return helper.resolveURL(message[kNotificationSystemMessageName]);
190           }
191         }
193         // No message found...
194         return null;
195       }
197       listener.target = getNotificationURLFor(manifest.messages);
199       // Bug 816944 - Support notification messages for entry_points.
200     });
201   },
203   deserializeStructuredClone: function(dataString) {
204     if (!dataString) {
205       return null;
206     }
207     let scContainer = Cc["@mozilla.org/docshell/structured-clone-container;1"].
208       createInstance(Ci.nsIStructuredCloneContainer);
210     // The maximum supported structured-clone serialization format version
211     // as defined in "js/public/StructuredClone.h"
212     let JS_STRUCTURED_CLONE_VERSION = 4;
213     scContainer.initFromBase64(dataString, JS_STRUCTURED_CLONE_VERSION);
214     let dataObj = scContainer.deserializeToVariant();
216     // We have to check whether dataObj contains DOM objects (supported by
217     // nsIStructuredCloneContainer, but not by Cu.cloneInto), e.g. ImageData.
218     // After the structured clone callback systems will be unified, we'll not
219     // have to perform this check anymore.
220     try {
221       let data = Cu.cloneInto(dataObj, {});
222     } catch(e) { dataObj = null; }
224     return dataObj;
225   },
227   showNotification: function(imageURL, title, text, textClickable, cookie,
228                              uid, bidi, lang, dataObj, manifestURL, timestamp) {
229     function send(appName, appIcon) {
230       SystemAppProxy._sendCustomEvent(kMozChromeNotificationEvent, {
231         type: kDesktopNotification,
232         id: uid,
233         icon: imageURL,
234         title: title,
235         text: text,
236         bidi: bidi,
237         lang: lang,
238         appName: appName,
239         appIcon: appIcon,
240         manifestURL: manifestURL,
241         timestamp: timestamp,
242         data: dataObj
243       });
244     }
246     if (!manifestURL || !manifestURL.length) {
247       send(null, null);
248       return;
249     }
251     // If we have a manifest URL, get the icon and title from the manifest
252     // to prevent spoofing.
253     appsService.getManifestFor(manifestURL).then((manifest) => {
254       let app = appsService.getAppByManifestURL(manifestURL);
255       let helper = new ManifestHelper(manifest, app.origin, manifestURL);
256       send(helper.name, helper.iconURLForSize(kNotificationIconSize));
257     });
258   },
260   showAlertNotification: function(aMessage) {
261     let data = aMessage.data;
262     let currentListener = this._listeners[data.name];
263     if (currentListener && currentListener.observer) {
264       currentListener.observer.observe(null, kTopicAlertFinished, currentListener.cookie);
265     }
267     let dataObj = this.deserializeStructuredClone(data.dataStr);
268     this.registerListener(data.name, data.cookie, data.alertListener);
269     this.showNotification(data.imageURL, data.title, data.text,
270                           data.textClickable, data.cookie, data.name, data.bidi,
271                           data.lang, dataObj, null);
272   },
274   showAppNotification: function(aMessage) {
275     let data = aMessage.data;
276     let details = data.details;
277     let dataObject = this.deserializeStructuredClone(details.data);
278     let listener = {
279       mm: aMessage.target,
280       title: data.title,
281       text: data.text,
282       manifestURL: details.manifestURL,
283       imageURL: data.imageURL,
284       lang: details.lang || undefined,
285       id: details.id || undefined,
286       dir: details.dir || undefined,
287       tag: details.tag || undefined,
288       timestamp: details.timestamp || undefined,
289       dataObj: dataObject || undefined
290     };
291     this.registerAppListener(data.uid, listener);
292     this.showNotification(data.imageURL, data.title, data.text,
293                           details.textClickable, null, data.uid, details.dir,
294                           details.lang, dataObject, details.manifestURL,
295                           details.timestamp);
296   },
298   closeAlert: function(name) {
299     SystemAppProxy._sendCustomEvent(kMozChromeNotificationEvent, {
300       type: kDesktopNotificationClose,
301       id: name
302     });
303   },
305   receiveMessage: function(aMessage) {
306     if (!aMessage.target.assertAppHasPermission(kDesktopNotificationPerm)) {
307       Cu.reportError("Desktop-notification message " + aMessage.name +
308                      " from a content process with no " + kDesktopNotificationPerm +
309                      " privileges.");
310       return;
311     }
313     switch(aMessage.name) {
314       case kMessageAppNotificationSend:
315         this.showAppNotification(aMessage);
316         break;
318       case kMessageAlertNotificationSend:
319         this.showAlertNotification(aMessage);
320         break;
322       case kMessageAlertNotificationClose:
323         this.closeAlert(aMessage.data.name);
324         break;
325     }
327   },
330 AlertsHelper.init();