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 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
7 import { DOMRequestIpcHelper } from "resource://gre/modules/DOMRequestHelper.sys.mjs";
11 XPCOMUtils.defineLazyGetter(lazy, "console", () => {
12 let { ConsoleAPI } = ChromeUtils.importESModule(
13 "resource://gre/modules/Console.sys.mjs"
15 return new ConsoleAPI({
16 maxLogLevelPref: "dom.push.loglevel",
21 XPCOMUtils.defineLazyServiceGetter(
24 "@mozilla.org/push/Service;1",
28 const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");
31 * The Push component runs in the child process and exposes the Push API
32 * to the web application. The PushService running in the parent process is the
33 * one actually performing all operations.
35 export function Push() {
36 lazy.console.debug("Push()");
40 __proto__: DOMRequestIpcHelper.prototype,
42 contractID: "@mozilla.org/push/PushManager;1",
46 QueryInterface: ChromeUtils.generateQI([
47 "nsIDOMGlobalPropertyInitializer",
48 "nsISupportsWeakReference",
53 lazy.console.debug("init()");
57 this.initDOMRequestHelper(win);
59 // Get the client principal from the window. This won't be null because the
60 // service worker should be available when accessing the push manager.
61 this._principal = win.clientPrincipal;
63 if (!this._principal) {
64 throw new Error(" The client principal of the window is not available");
68 this._topLevelPrincipal = win.top.document.nodePrincipal;
70 // Accessing the top-level document might fails if cross-origin
71 this._topLevelPrincipal = undefined;
80 lazy.console.debug("askPermission()");
82 let hasValidTransientUserGestureActivation =
83 this._window.document.hasValidTransientUserGestureActivation;
85 return this.createPromise((resolve, reject) => {
86 let permissionDenied = () => {
88 new this._window.DOMException(
89 "User denied permission to use the Push API.",
96 Services.prefs.getBoolPref("dom.push.testing.ignorePermission", false)
102 this._requestPermission(
103 hasValidTransientUserGestureActivation,
111 lazy.console.debug("subscribe()", this._scope);
113 return this.askPermission().then(() =>
114 this.createPromise((resolve, reject) => {
115 let callback = new PushSubscriptionCallback(this, resolve, reject);
117 if (!options || options.applicationServerKey === null) {
118 lazy.PushService.subscribe(this._scope, this._principal, callback);
122 let keyView = this._normalizeAppServerKey(options.applicationServerKey);
123 if (keyView.byteLength === 0) {
124 callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
127 lazy.PushService.subscribeWithKey(
137 _normalizeAppServerKey(appServerKey) {
139 if (typeof appServerKey == "string") {
142 ChromeUtils.base64URLDecode(appServerKey, {
148 throw new this._window.DOMException(
149 "String contains an invalid character",
150 "InvalidCharacterError"
153 } else if (this._window.ArrayBuffer.isView(appServerKey)) {
154 key = appServerKey.buffer;
156 // `appServerKey` is an array buffer.
159 return new this._window.Uint8Array(key);
163 lazy.console.debug("getSubscription()", this._scope);
165 return this.createPromise((resolve, reject) => {
166 let callback = new PushSubscriptionCallback(this, resolve, reject);
167 lazy.PushService.getSubscription(this._scope, this._principal, callback);
172 lazy.console.debug("permissionState()", this._scope);
174 return this.createPromise((resolve, reject) => {
175 let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
178 permission = this._testPermission();
184 let pushPermissionStatus = "prompt";
185 if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
186 pushPermissionStatus = "granted";
187 } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
188 pushPermissionStatus = "denied";
190 resolve(pushPermissionStatus);
195 let permission = Services.perms.testExactPermissionFromPrincipal(
197 "desktop-notification"
199 if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
203 if (Services.prefs.getBoolPref("dom.push.testing.ignorePermission")) {
204 permission = Ci.nsIPermissionManager.ALLOW_ACTION;
211 hasValidTransientUserGestureActivation,
215 // Create an array with a single nsIContentPermissionType element.
217 type: "desktop-notification",
219 QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionType"]),
221 let typeArray = Cc["@mozilla.org/array;1"].createInstance(
224 typeArray.appendElement(type);
226 // create a nsIContentPermissionRequest
228 QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionRequest"]),
230 principal: this._principal,
231 hasValidTransientUserGestureActivation,
232 topLevelPrincipal: this._topLevelPrincipal,
233 allow: allowCallback,
234 cancel: cancelCallback,
235 window: this._window,
238 // Using askPermission from nsIDOMWindowUtils that takes care of the
239 // remoting if needed.
240 let windowUtils = this._window.windowUtils;
241 windowUtils.askPermission(request);
245 function PushSubscriptionCallback(pushManager, resolve, reject) {
246 this.pushManager = pushManager;
247 this.resolve = resolve;
248 this.reject = reject;
251 PushSubscriptionCallback.prototype = {
252 QueryInterface: ChromeUtils.generateQI(["nsIPushSubscriptionCallback"]),
254 onPushSubscription(ok, subscription) {
255 let { pushManager } = this;
256 if (!Components.isSuccessCode(ok)) {
257 this._rejectWithError(ok);
266 let p256dhKey = this._getKey(subscription, "p256dh");
267 let authSecret = this._getKey(subscription, "auth");
269 endpoint: subscription.endpoint,
270 scope: pushManager._scope,
274 let appServerKey = this._getKey(subscription, "appServer");
276 // Avoid passing null keys to work around bug 1256449.
277 options.appServerKey = appServerKey;
279 let sub = new pushManager._window.PushSubscription(options);
283 _getKey(subscription, name) {
284 let rawKey = Cu.cloneInto(
285 subscription.getKey(name),
286 this.pushManager._window
288 if (!rawKey.length) {
292 let key = new this.pushManager._window.ArrayBuffer(rawKey.length);
293 let keyView = new this.pushManager._window.Uint8Array(key);
298 _rejectWithError(result) {
301 case Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR:
302 error = new this.pushManager._window.DOMException(
303 "Invalid raw ECDSA P-256 public key.",
308 case Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR:
309 error = new this.pushManager._window.DOMException(
310 "A subscription with a different application server key already exists.",
316 error = new this.pushManager._window.DOMException(
317 "Error retrieving push subscription.",