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 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
8 ChromeUtils.import("resource://gre/modules/Services.jsm");
9 ChromeUtils.import("resource://gre/modules/DOMRequestHelper.jsm");
11 XPCOMUtils.defineLazyGetter(this, "console", () => {
12 let {ConsoleAPI} = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
13 return new ConsoleAPI({
14 maxLogLevelPref: "dom.push.loglevel",
19 XPCOMUtils.defineLazyServiceGetter(this, "PushService",
20 "@mozilla.org/push/Service;1", "nsIPushService");
22 const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");
25 * The Push component runs in the child process and exposes the Push API
26 * to the web application. The PushService running in the parent process is the
27 * one actually performing all operations.
30 console.debug("Push()");
34 __proto__: DOMRequestIpcHelper.prototype,
36 contractID: "@mozilla.org/push/PushManager;1",
40 QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
41 Ci.nsISupportsWeakReference,
45 console.debug("init()");
49 this.initDOMRequestHelper(win);
51 this._principal = win.document.nodePrincipal;
54 __init: function(scope) {
58 askPermission: function () {
59 console.debug("askPermission()");
61 return this.createPromise((resolve, reject) => {
62 let permissionDenied = () => {
63 reject(new this._window.DOMException(
64 "User denied permission to use the Push API.",
69 let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
71 permission = this._testPermission();
77 if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
79 } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
82 this._requestPermission(resolve, permissionDenied);
87 subscribe: function(options) {
88 console.debug("subscribe()", this._scope);
90 return this.askPermission().then(() =>
91 this.createPromise((resolve, reject) => {
92 let callback = new PushSubscriptionCallback(this, resolve, reject);
94 if (!options || options.applicationServerKey === null) {
95 PushService.subscribe(this._scope, this._principal, callback);
99 let keyView = this._normalizeAppServerKey(options.applicationServerKey);
100 if (keyView.byteLength === 0) {
101 callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
104 PushService.subscribeWithKey(this._scope, this._principal,
105 keyView.byteLength, keyView,
111 _normalizeAppServerKey: function(appServerKey) {
113 if (typeof appServerKey == "string") {
115 key = Cu.cloneInto(ChromeUtils.base64URLDecode(appServerKey, {
119 throw new this._window.DOMException(
120 "String contains an invalid character",
121 "InvalidCharacterError"
124 } else if (this._window.ArrayBuffer.isView(appServerKey)) {
125 key = appServerKey.buffer;
127 // `appServerKey` is an array buffer.
130 return new this._window.Uint8Array(key);
133 getSubscription: function() {
134 console.debug("getSubscription()", this._scope);
136 return this.createPromise((resolve, reject) => {
137 let callback = new PushSubscriptionCallback(this, resolve, reject);
138 PushService.getSubscription(this._scope, this._principal, callback);
142 permissionState: function() {
143 console.debug("permissionState()", this._scope);
145 return this.createPromise((resolve, reject) => {
146 let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
149 permission = this._testPermission();
155 let pushPermissionStatus = "prompt";
156 if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
157 pushPermissionStatus = "granted";
158 } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
159 pushPermissionStatus = "denied";
161 resolve(pushPermissionStatus);
165 _testPermission: function() {
166 let permission = Services.perms.testExactPermissionFromPrincipal(
167 this._principal, "desktop-notification");
168 if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
172 if (Services.prefs.getBoolPref("dom.push.testing.ignorePermission")) {
173 permission = Ci.nsIPermissionManager.ALLOW_ACTION;
179 _requestPermission: function(allowCallback, cancelCallback) {
180 // Create an array with a single nsIContentPermissionType element.
182 type: "desktop-notification",
185 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionType]),
187 let typeArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
188 typeArray.appendElement(type);
190 // create a nsIContentPermissionRequest
193 principal: this._principal,
194 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionRequest]),
195 allow: allowCallback,
196 cancel: cancelCallback,
197 window: this._window,
200 // Using askPermission from nsIDOMWindowUtils that takes care of the
201 // remoting if needed.
202 let windowUtils = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
203 .getInterface(Ci.nsIDOMWindowUtils);
204 windowUtils.askPermission(request);
208 function PushSubscriptionCallback(pushManager, resolve, reject) {
209 this.pushManager = pushManager;
210 this.resolve = resolve;
211 this.reject = reject;
214 PushSubscriptionCallback.prototype = {
215 QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushSubscriptionCallback]),
217 onPushSubscription: function(ok, subscription) {
218 let {pushManager} = this;
219 if (!Components.isSuccessCode(ok)) {
220 this._rejectWithError(ok);
229 let p256dhKey = this._getKey(subscription, "p256dh");
230 let authSecret = this._getKey(subscription, "auth");
232 endpoint: subscription.endpoint,
233 scope: pushManager._scope,
234 p256dhKey: p256dhKey,
235 authSecret: authSecret,
237 let appServerKey = this._getKey(subscription, "appServer");
239 // Avoid passing null keys to work around bug 1256449.
240 options.appServerKey = appServerKey;
242 let sub = new pushManager._window.PushSubscription(options);
246 _getKey: function(subscription, name) {
248 let rawKey = Cu.cloneInto(subscription.getKey(name, outKeyLen),
249 this.pushManager._window);
250 if (!outKeyLen.value) {
254 let key = new this.pushManager._window.ArrayBuffer(outKeyLen.value);
255 let keyView = new this.pushManager._window.Uint8Array(key);
260 _rejectWithError: function(result) {
263 case Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR:
264 error = new this.pushManager._window.DOMException(
265 "Invalid raw ECDSA P-256 public key.",
270 case Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR:
271 error = new this.pushManager._window.DOMException(
272 "A subscription with a different application server key already exists.",
278 error = new this.pushManager._window.DOMException(
279 "Error retrieving push subscription.",
287 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Push]);