summaryrefslogtreecommitdiffstats
path: root/dom/push/Push.sys.mjs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/push/Push.sys.mjs
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/push/Push.sys.mjs')
-rw-r--r--dom/push/Push.sys.mjs323
1 files changed, 323 insertions, 0 deletions
diff --git a/dom/push/Push.sys.mjs b/dom/push/Push.sys.mjs
new file mode 100644
index 0000000000..ccf9f6128f
--- /dev/null
+++ b/dom/push/Push.sys.mjs
@@ -0,0 +1,323 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+import { DOMRequestIpcHelper } from "resource://gre/modules/DOMRequestHelper.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyGetter(lazy, "console", () => {
+ let { ConsoleAPI } = ChromeUtils.importESModule(
+ "resource://gre/modules/Console.sys.mjs"
+ );
+ return new ConsoleAPI({
+ maxLogLevelPref: "dom.push.loglevel",
+ prefix: "Push",
+ });
+});
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "PushService",
+ "@mozilla.org/push/Service;1",
+ "nsIPushService"
+);
+
+const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");
+
+/**
+ * The Push component runs in the child process and exposes the Push API
+ * to the web application. The PushService running in the parent process is the
+ * one actually performing all operations.
+ */
+export function Push() {
+ lazy.console.debug("Push()");
+}
+
+Push.prototype = {
+ __proto__: DOMRequestIpcHelper.prototype,
+
+ contractID: "@mozilla.org/push/PushManager;1",
+
+ classID: PUSH_CID,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIDOMGlobalPropertyInitializer",
+ "nsISupportsWeakReference",
+ "nsIObserver",
+ ]),
+
+ init(win) {
+ lazy.console.debug("init()");
+
+ this._window = win;
+
+ this.initDOMRequestHelper(win);
+
+ // Get the client principal from the window. This won't be null because the
+ // service worker should be available when accessing the push manager.
+ this._principal = win.clientPrincipal;
+
+ if (!this._principal) {
+ throw new Error(" The client principal of the window is not available");
+ }
+
+ try {
+ this._topLevelPrincipal = win.top.document.nodePrincipal;
+ } catch (error) {
+ // Accessing the top-level document might fails if cross-origin
+ this._topLevelPrincipal = undefined;
+ }
+ },
+
+ __init(scope) {
+ this._scope = scope;
+ },
+
+ askPermission() {
+ lazy.console.debug("askPermission()");
+
+ let hasValidTransientUserGestureActivation =
+ this._window.document.hasValidTransientUserGestureActivation;
+
+ return this.createPromise((resolve, reject) => {
+ let permissionDenied = () => {
+ reject(
+ new this._window.DOMException(
+ "User denied permission to use the Push API.",
+ "NotAllowedError"
+ )
+ );
+ };
+
+ if (
+ Services.prefs.getBoolPref("dom.push.testing.ignorePermission", false)
+ ) {
+ resolve();
+ return;
+ }
+
+ this._requestPermission(
+ hasValidTransientUserGestureActivation,
+ resolve,
+ permissionDenied
+ );
+ });
+ },
+
+ subscribe(options) {
+ lazy.console.debug("subscribe()", this._scope);
+
+ return this.askPermission().then(() =>
+ this.createPromise((resolve, reject) => {
+ let callback = new PushSubscriptionCallback(this, resolve, reject);
+
+ if (!options || options.applicationServerKey === null) {
+ lazy.PushService.subscribe(this._scope, this._principal, callback);
+ return;
+ }
+
+ let keyView = this._normalizeAppServerKey(options.applicationServerKey);
+ if (keyView.byteLength === 0) {
+ callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
+ return;
+ }
+ lazy.PushService.subscribeWithKey(
+ this._scope,
+ this._principal,
+ keyView,
+ callback
+ );
+ })
+ );
+ },
+
+ _normalizeAppServerKey(appServerKey) {
+ let key;
+ if (typeof appServerKey == "string") {
+ try {
+ key = Cu.cloneInto(
+ ChromeUtils.base64URLDecode(appServerKey, {
+ padding: "reject",
+ }),
+ this._window
+ );
+ } catch (e) {
+ throw new this._window.DOMException(
+ "String contains an invalid character",
+ "InvalidCharacterError"
+ );
+ }
+ } else if (this._window.ArrayBuffer.isView(appServerKey)) {
+ key = appServerKey.buffer;
+ } else {
+ // `appServerKey` is an array buffer.
+ key = appServerKey;
+ }
+ return new this._window.Uint8Array(key);
+ },
+
+ getSubscription() {
+ lazy.console.debug("getSubscription()", this._scope);
+
+ return this.createPromise((resolve, reject) => {
+ let callback = new PushSubscriptionCallback(this, resolve, reject);
+ lazy.PushService.getSubscription(this._scope, this._principal, callback);
+ });
+ },
+
+ permissionState() {
+ lazy.console.debug("permissionState()", this._scope);
+
+ return this.createPromise((resolve, reject) => {
+ let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+
+ try {
+ permission = this._testPermission();
+ } catch (e) {
+ reject();
+ return;
+ }
+
+ let pushPermissionStatus = "prompt";
+ if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
+ pushPermissionStatus = "granted";
+ } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
+ pushPermissionStatus = "denied";
+ }
+ resolve(pushPermissionStatus);
+ });
+ },
+
+ _testPermission() {
+ let permission = Services.perms.testExactPermissionFromPrincipal(
+ this._principal,
+ "desktop-notification"
+ );
+ if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
+ return permission;
+ }
+ try {
+ if (Services.prefs.getBoolPref("dom.push.testing.ignorePermission")) {
+ permission = Ci.nsIPermissionManager.ALLOW_ACTION;
+ }
+ } catch (e) {}
+ return permission;
+ },
+
+ _requestPermission(
+ hasValidTransientUserGestureActivation,
+ allowCallback,
+ cancelCallback
+ ) {
+ // Create an array with a single nsIContentPermissionType element.
+ let type = {
+ type: "desktop-notification",
+ options: [],
+ QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionType"]),
+ };
+ let typeArray = Cc["@mozilla.org/array;1"].createInstance(
+ Ci.nsIMutableArray
+ );
+ typeArray.appendElement(type);
+
+ // create a nsIContentPermissionRequest
+ let request = {
+ QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionRequest"]),
+ types: typeArray,
+ principal: this._principal,
+ hasValidTransientUserGestureActivation,
+ topLevelPrincipal: this._topLevelPrincipal,
+ allow: allowCallback,
+ cancel: cancelCallback,
+ window: this._window,
+ };
+
+ // Using askPermission from nsIDOMWindowUtils that takes care of the
+ // remoting if needed.
+ let windowUtils = this._window.windowUtils;
+ windowUtils.askPermission(request);
+ },
+};
+
+function PushSubscriptionCallback(pushManager, resolve, reject) {
+ this.pushManager = pushManager;
+ this.resolve = resolve;
+ this.reject = reject;
+}
+
+PushSubscriptionCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIPushSubscriptionCallback"]),
+
+ onPushSubscription(ok, subscription) {
+ let { pushManager } = this;
+ if (!Components.isSuccessCode(ok)) {
+ this._rejectWithError(ok);
+ return;
+ }
+
+ if (!subscription) {
+ this.resolve(null);
+ return;
+ }
+
+ let p256dhKey = this._getKey(subscription, "p256dh");
+ let authSecret = this._getKey(subscription, "auth");
+ let options = {
+ endpoint: subscription.endpoint,
+ scope: pushManager._scope,
+ p256dhKey,
+ authSecret,
+ };
+ let appServerKey = this._getKey(subscription, "appServer");
+ if (appServerKey) {
+ // Avoid passing null keys to work around bug 1256449.
+ options.appServerKey = appServerKey;
+ }
+ let sub = new pushManager._window.PushSubscription(options);
+ this.resolve(sub);
+ },
+
+ _getKey(subscription, name) {
+ let rawKey = Cu.cloneInto(
+ subscription.getKey(name),
+ this.pushManager._window
+ );
+ if (!rawKey.length) {
+ return null;
+ }
+
+ let key = new this.pushManager._window.ArrayBuffer(rawKey.length);
+ let keyView = new this.pushManager._window.Uint8Array(key);
+ keyView.set(rawKey);
+ return key;
+ },
+
+ _rejectWithError(result) {
+ let error;
+ switch (result) {
+ case Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR:
+ error = new this.pushManager._window.DOMException(
+ "Invalid raw ECDSA P-256 public key.",
+ "InvalidAccessError"
+ );
+ break;
+
+ case Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR:
+ error = new this.pushManager._window.DOMException(
+ "A subscription with a different application server key already exists.",
+ "InvalidStateError"
+ );
+ break;
+
+ default:
+ error = new this.pushManager._window.DOMException(
+ "Error retrieving push subscription.",
+ "AbortError"
+ );
+ }
+ this.reject(error);
+ },
+};