Bug 1449132 [wpt PR 10194] - [css-grid] Fix resolution of percentage paddings and...
[gecko.git] / dom / push / Push.js
blobc985273e8ebafeb46af942f1e3bf3ed68b7f75b4
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 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",
15     prefix: "Push",
16   });
17 });
19 XPCOMUtils.defineLazyServiceGetter(this, "PushService",
20   "@mozilla.org/push/Service;1", "nsIPushService");
22 const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");
24 /**
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.
28  */
29 function Push() {
30   console.debug("Push()");
33 Push.prototype = {
34   __proto__: DOMRequestIpcHelper.prototype,
36   contractID: "@mozilla.org/push/PushManager;1",
38   classID : PUSH_CID,
40   QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
41                                           Ci.nsISupportsWeakReference,
42                                           Ci.nsIObserver]),
44   init: function(win) {
45     console.debug("init()");
47     this._window = win;
49     this.initDOMRequestHelper(win);
51     this._principal = win.document.nodePrincipal;
52   },
54   __init: function(scope) {
55     this._scope = scope;
56   },
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.",
65           "NotAllowedError"
66         ));
67       };
69       let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
70       try {
71         permission = this._testPermission();
72       } catch (e) {
73         permissionDenied();
74         return;
75       }
77       if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
78         resolve();
79       } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
80         permissionDenied();
81       } else {
82         this._requestPermission(resolve, permissionDenied);
83       }
84     });
85   },
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);
96           return;
97         }
99         let keyView = this._normalizeAppServerKey(options.applicationServerKey);
100         if (keyView.byteLength === 0) {
101           callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
102           return;
103         }
104         PushService.subscribeWithKey(this._scope, this._principal,
105                                      keyView.byteLength, keyView,
106                                      callback);
107       })
108     );
109   },
111   _normalizeAppServerKey: function(appServerKey) {
112     let key;
113     if (typeof appServerKey == "string") {
114       try {
115         key = Cu.cloneInto(ChromeUtils.base64URLDecode(appServerKey, {
116           padding: "reject",
117         }), this._window);
118       } catch (e) {
119         throw new this._window.DOMException(
120           "String contains an invalid character",
121           "InvalidCharacterError"
122         );
123       }
124     } else if (this._window.ArrayBuffer.isView(appServerKey)) {
125       key = appServerKey.buffer;
126     } else {
127       // `appServerKey` is an array buffer.
128       key = appServerKey;
129     }
130     return new this._window.Uint8Array(key);
131   },
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);
139     });
140   },
142   permissionState: function() {
143     console.debug("permissionState()", this._scope);
145     return this.createPromise((resolve, reject) => {
146       let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
148       try {
149         permission = this._testPermission();
150       } catch(e) {
151         reject();
152         return;
153       }
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";
160       }
161       resolve(pushPermissionStatus);
162     });
163   },
165   _testPermission: function() {
166     let permission = Services.perms.testExactPermissionFromPrincipal(
167       this._principal, "desktop-notification");
168     if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
169       return permission;
170     }
171     try {
172       if (Services.prefs.getBoolPref("dom.push.testing.ignorePermission")) {
173         permission = Ci.nsIPermissionManager.ALLOW_ACTION;
174       }
175     } catch (e) {}
176     return permission;
177   },
179   _requestPermission: function(allowCallback, cancelCallback) {
180     // Create an array with a single nsIContentPermissionType element.
181     let type = {
182       type: "desktop-notification",
183       access: null,
184       options: [],
185       QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionType]),
186     };
187     let typeArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
188     typeArray.appendElement(type);
190     // create a nsIContentPermissionRequest
191     let request = {
192       types: typeArray,
193       principal: this._principal,
194       QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionRequest]),
195       allow: allowCallback,
196       cancel: cancelCallback,
197       window: this._window,
198     };
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);
205   },
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);
221       return;
222     }
224     if (!subscription) {
225       this.resolve(null);
226       return;
227     }
229     let p256dhKey = this._getKey(subscription, "p256dh");
230     let authSecret = this._getKey(subscription, "auth");
231     let options = {
232       endpoint: subscription.endpoint,
233       scope: pushManager._scope,
234       p256dhKey: p256dhKey,
235       authSecret: authSecret,
236     };
237     let appServerKey = this._getKey(subscription, "appServer");
238     if (appServerKey) {
239       // Avoid passing null keys to work around bug 1256449.
240       options.appServerKey = appServerKey;
241     }
242     let sub = new pushManager._window.PushSubscription(options);
243     this.resolve(sub);
244   },
246   _getKey: function(subscription, name) {
247     let outKeyLen = {};
248     let rawKey = Cu.cloneInto(subscription.getKey(name, outKeyLen),
249                               this.pushManager._window);
250     if (!outKeyLen.value) {
251       return null;
252     }
254     let key = new this.pushManager._window.ArrayBuffer(outKeyLen.value);
255     let keyView = new this.pushManager._window.Uint8Array(key);
256     keyView.set(rawKey);
257     return key;
258   },
260   _rejectWithError: function(result) {
261     let error;
262     switch (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.",
266           "InvalidAccessError"
267         );
268         break;
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.",
273           "InvalidStateError"
274         );
275         break;
277       default:
278         error = new this.pushManager._window.DOMException(
279           "Error retrieving push subscription.",
280           "AbortError"
281         );
282     }
283     this.reject(error);
284   },
287 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Push]);