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/. */
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",
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);
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";
61 kMessageAppNotificationSend,
62 kMessageAlertNotificationSend,
63 kMessageAlertNotificationClose
71 Services.obs.addObserver(this, "xpcom-shutdown", false);
72 for (let message of kMessages) {
73 ppmm.addMessageListener(message, this);
75 SystemAppProxy.addEventListener(kMozContentNotificationEvent, this);
78 observe: function(aSubject, aTopic, aData) {
80 case "xpcom-shutdown":
81 Services.obs.removeObserver(this, "xpcom-shutdown");
82 for (let message of kMessages) {
83 ppmm.removeMessageListener(message, this);
85 SystemAppProxy.removeEventListener(kMozContentNotificationEvent, this);
90 handleEvent: function(evt) {
91 let detail = evt.detail;
94 case kDesktopNotificationShow:
95 case kDesktopNotificationClick:
96 case kDesktopNotificationClose:
97 this.handleNotificationEvent(detail);
100 debug("FIXME: Unhandled notification event: " + detail.type);
105 handleNotificationEvent: function(detail) {
106 if (!detail || !detail.id) {
111 let listener = this._listeners[uid];
117 if (detail.type === kDesktopNotificationClick) {
118 topic = kTopicAlertClickCallback;
119 } else if (detail.type === kDesktopNotificationShow) {
120 topic = kTopicAlertShow;
122 /* kDesktopNotificationClose */
123 topic = kTopicAlertFinished;
126 if (listener.cookie) {
128 listener.observer.observe(null, topic, listener.cookie);
132 listener.mm.sendAsyncMessage(kMessageAppNotificationReturn, {
135 target: listener.target
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,
147 imageURL: listener.imageURL,
152 timestamp: listener.timestamp,
153 data: listener.dataObj
155 Services.io.newURI(listener.target, null, null),
156 Services.io.newURI(listener.manifestURL, null, null)
162 // we"re done with this notification
163 if (topic === kTopicAlertFinished) {
164 delete this._listeners[uid];
168 registerListener: function(alertId, cookie, alertListener) {
169 this._listeners[alertId] = { observer: alertListener, cookie: cookie };
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) {
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]);
193 // No message found...
197 listener.target = getNotificationURLFor(manifest.messages);
199 // Bug 816944 - Support notification messages for entry_points.
203 deserializeStructuredClone: function(dataString) {
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.
221 let data = Cu.cloneInto(dataObj, {});
222 } catch(e) { dataObj = null; }
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,
240 manifestURL: manifestURL,
241 timestamp: timestamp,
246 if (!manifestURL || !manifestURL.length) {
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));
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);
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);
274 showAppNotification: function(aMessage) {
275 let data = aMessage.data;
276 let details = data.details;
277 let dataObject = this.deserializeStructuredClone(details.data);
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
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,
298 closeAlert: function(name) {
299 SystemAppProxy._sendCustomEvent(kMozChromeNotificationEvent, {
300 type: kDesktopNotificationClose,
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 +
313 switch(aMessage.name) {
314 case kMessageAppNotificationSend:
315 this.showAppNotification(aMessage);
318 case kMessageAlertNotificationSend:
319 this.showAlertNotification(aMessage);
322 case kMessageAlertNotificationClose:
323 this.closeAlert(aMessage.data.name);