diff options
Diffstat (limited to 'dom/notification')
-rw-r--r-- | dom/notification/Notification.cpp | 114 | ||||
-rw-r--r-- | dom/notification/NotificationStorage.sys.mjs | 60 | ||||
-rw-r--r-- | dom/notification/components.conf | 6 | ||||
-rw-r--r-- | dom/notification/moz.build | 1 | ||||
-rw-r--r-- | dom/notification/old/MemoryNotificationDB.sys.mjs | 21 | ||||
-rw-r--r-- | dom/notification/old/NotificationDB.sys.mjs | 222 | ||||
-rw-r--r-- | dom/notification/test/unit/head_notificationdb.js | 3 | ||||
-rw-r--r-- | dom/notification/test/unit/test_notificationdb.js | 86 |
8 files changed, 350 insertions, 163 deletions
diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index fb5b0ea9a3..d0006941e9 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -161,29 +161,36 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END +nsCOMPtr<nsINotificationStorage> GetNotificationStorage(bool isPrivate) { + return do_GetService(isPrivate ? NS_MEMORY_NOTIFICATION_STORAGE_CONTRACTID + : NS_NOTIFICATION_STORAGE_CONTRACTID); +} + class NotificationGetRunnable final : public Runnable { + bool mIsPrivate; const nsString mOrigin; const nsString mTag; nsCOMPtr<nsINotificationStorageCallback> mCallback; public: NotificationGetRunnable(const nsAString& aOrigin, const nsAString& aTag, - nsINotificationStorageCallback* aCallback) + nsINotificationStorageCallback* aCallback, + bool aIsPrivate) : Runnable("NotificationGetRunnable"), + mIsPrivate(aIsPrivate), mOrigin(aOrigin), mTag(aTag), mCallback(aCallback) {} NS_IMETHOD Run() override { - nsresult rv; nsCOMPtr<nsINotificationStorage> notificationStorage = - do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; + GetNotificationStorage(mIsPrivate); + if (NS_WARN_IF(!notificationStorage)) { + return NS_ERROR_UNEXPECTED; } - rv = notificationStorage->Get(mOrigin, mTag, mCallback); + nsresult rv = notificationStorage->Get(mOrigin, mTag, mCallback); // XXXnsm Is it guaranteed mCallback will be called in case of failure? Unused << NS_WARN_IF(NS_FAILED(rv)); return rv; @@ -236,7 +243,7 @@ class ReleaseNotificationControlRunnable final public: explicit ReleaseNotificationControlRunnable(Notification* aNotification) - : MainThreadWorkerControlRunnable(aNotification->mWorkerPrivate), + : MainThreadWorkerControlRunnable("ReleaseNotificationControlRunnable"), mNotification(aNotification) {} bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { @@ -310,7 +317,7 @@ class NotificationWorkerRunnable : public MainThreadWorkerRunnable { explicit NotificationWorkerRunnable( WorkerPrivate* aWorkerPrivate, const char* aName = "NotificationWorkerRunnable") - : MainThreadWorkerRunnable(aWorkerPrivate, aName) {} + : MainThreadWorkerRunnable(aName) {} bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { aWorkerPrivate->AssertIsOnWorkerThread(); @@ -426,10 +433,10 @@ class NotificationRef final { RefPtr<ReleaseNotificationRunnable> r = new ReleaseNotificationRunnable(notification); - if (!r->Dispatch()) { + if (!r->Dispatch(notification->mWorkerPrivate)) { RefPtr<ReleaseNotificationControlRunnable> r = new ReleaseNotificationControlRunnable(notification); - MOZ_ALWAYS_TRUE(r->Dispatch()); + MOZ_ALWAYS_TRUE(r->Dispatch(notification->mWorkerPrivate)); } } else { notification->AssertIsOnTargetThread(); @@ -822,15 +829,15 @@ Notification::ConstructFromFields( nsresult Notification::PersistNotification() { AssertIsOnMainThread(); - nsresult rv; + nsCOMPtr<nsINotificationStorage> notificationStorage = - do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); - if (NS_FAILED(rv)) { - return rv; + GetNotificationStorage(IsInPrivateBrowsing()); + if (NS_WARN_IF(!notificationStorage)) { + return NS_ERROR_UNEXPECTED; } nsString origin; - rv = GetOrigin(GetPrincipal(), origin); + nsresult rv = GetOrigin(GetPrincipal(), origin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -862,7 +869,7 @@ void Notification::UnpersistNotification() { AssertIsOnMainThread(); if (IsStored()) { nsCOMPtr<nsINotificationStorage> notificationStorage = - do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); + GetNotificationStorage(IsInPrivateBrowsing()); if (notificationStorage) { nsString origin; nsresult rv = GetOrigin(GetPrincipal(), origin); @@ -1067,15 +1074,16 @@ class NotificationClickWorkerRunnable final "NotificationClickWorkerRunnable"), mNotification(aNotification), mWindow(aWindow) { - MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !mWindow); + MOZ_ASSERT_IF(mNotification->mWorkerPrivate->IsServiceWorker(), !mWindow); } void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override { bool doDefaultAction = mNotification->DispatchClickEvent(); - MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !doDefaultAction); + MOZ_ASSERT_IF(mNotification->mWorkerPrivate->IsServiceWorker(), + !doDefaultAction); if (doDefaultAction) { RefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow); - mWorkerPrivate->DispatchToMainThread(r.forget()); + mNotification->mWorkerPrivate->DispatchToMainThread(r.forget()); } } }; @@ -1175,7 +1183,7 @@ WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, MOZ_ASSERT(notification->mWorkerPrivate); - RefPtr<WorkerRunnable> r; + RefPtr<WorkerThreadRunnable> r; if (!strcmp("alertclickcallback", aTopic)) { nsPIDOMWindowInner* window = nullptr; if (!notification->mWorkerPrivate->IsServiceWorker()) { @@ -1207,7 +1215,7 @@ WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, } MOZ_ASSERT(r); - if (!r->Dispatch()) { + if (!r->Dispatch(notification->mWorkerPrivate)) { NS_WARNING("Could not dispatch event to worker notification"); } return NS_OK; @@ -1256,7 +1264,7 @@ ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject, // Remove closed or dismissed persistent notifications. nsCOMPtr<nsINotificationStorage> notificationStorage = - do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); + GetNotificationStorage(mPrincipal->GetPrivateBrowsingId() != 0); if (notificationStorage) { notificationStorage->Delete(origin, mID); } @@ -1316,6 +1324,8 @@ bool Notification::IsInPrivateBrowsing() { return false; } +// Step 4 of +// https://notifications.spec.whatwg.org/#dom-notification-notification void Notification::ShowInternal() { AssertIsOnMainThread(); MOZ_ASSERT(mTempRef, @@ -1330,13 +1340,15 @@ void Notification::ShowInternal() { std::swap(ownership, mTempRef); MOZ_ASSERT(ownership->GetNotification() == this); - nsresult rv = PersistNotification(); - if (NS_FAILED(rv)) { - NS_WARNING("Could not persist Notification"); - } - nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service(); + // Step 4.1: If the result of getting the notifications permission state is + // not "granted", then queue a task to fire an event named error on this, and + // abort these steps. + // + // XXX(krosylight): But this function is also triggered by + // Notification::ShowPersistentNotification which already does its own + // permission check. Can we split this? ErrorResult result; NotificationPermission permission = NotificationPermission::Denied; if (mWorkerPrivate) { @@ -1351,19 +1363,34 @@ void Notification::ShowInternal() { if (mWorkerPrivate) { RefPtr<NotificationEventWorkerRunnable> r = new NotificationEventWorkerRunnable(this, u"error"_ns); - if (!r->Dispatch()) { + if (!r->Dispatch(mWorkerPrivate)) { NS_WARNING("Could not dispatch event to worker notification"); } } else { DispatchTrustedEvent(u"error"_ns); } + mIsClosed = true; return; } + // Preparing for Step 4.2 the fetch steps. The actual work happens in + // nsIAlertNotification::LoadImage nsAutoString iconUrl; nsAutoString soundUrl; ResolveIconAndSoundURL(iconUrl, soundUrl); + // Step 4.3 the show steps, which are almost all about processing `tag` and + // then displaying the notification. Both are handled by + // nsIAlertsService::ShowAlert/PersistentNotification. The below is all about + // constructing the observer (for show and close events) right and ultimately + // call the alerts service function. + + // XXX(krosylight): Non-persistent notifications probably don't need this + nsresult rv = PersistNotification(); + if (NS_FAILED(rv)) { + NS_WARNING("Could not persist Notification"); + } + bool isPersistent = false; nsCOMPtr<nsIObserver> observer; if (mScope.IsEmpty()) { @@ -1688,8 +1715,8 @@ already_AddRefed<Promise> Notification::Get( nsCOMPtr<nsINotificationStorageCallback> callback = new NotificationStorageCallback(aWindow->AsGlobal(), aScope, promise); - RefPtr<NotificationGetRunnable> r = - new NotificationGetRunnable(origin, aFilter.mTag, callback); + RefPtr<NotificationGetRunnable> r = new NotificationGetRunnable( + origin, aFilter.mTag, callback, doc->IsInPrivateBrowsing()); aRv = aWindow->AsGlobal()->Dispatch(r.forget()); if (NS_WARN_IF(aRv.Failed())) { @@ -1765,7 +1792,7 @@ class WorkerGetCallback final : public ScopeCheckingGetCallback { RefPtr<WorkerGetResultRunnable> r = new WorkerGetResultRunnable( proxy->GetWorkerPrivate(), proxy, std::move(mStrings)); - r->Dispatch(); + r->Dispatch(proxy->GetWorkerPrivate()); return NS_OK; } @@ -1793,25 +1820,26 @@ class WorkerGetRunnable final : public Runnable { NS_IMETHOD Run() override { AssertIsOnMainThread(); - nsCOMPtr<nsINotificationStorageCallback> callback = - new WorkerGetCallback(mPromiseProxy, mScope); - - nsresult rv; - nsCOMPtr<nsINotificationStorage> notificationStorage = - do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) { - callback->Done(); - return rv; - } MutexAutoLock lock(mPromiseProxy->Lock()); if (mPromiseProxy->CleanedUp()) { return NS_OK; } + auto* principal = mPromiseProxy->GetWorkerPrivate()->GetPrincipal(); + auto isPrivate = principal->GetPrivateBrowsingId() != 0; + + nsCOMPtr<nsINotificationStorageCallback> callback = + new WorkerGetCallback(mPromiseProxy, mScope); + + nsCOMPtr<nsINotificationStorage> notificationStorage = + GetNotificationStorage(isPrivate); + if (NS_WARN_IF(!notificationStorage)) { + callback->Done(); + return NS_ERROR_UNEXPECTED; + } nsString origin; - rv = Notification::GetOrigin( - mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), origin); + nsresult rv = Notification::GetOrigin(principal, origin); if (NS_WARN_IF(NS_FAILED(rv))) { callback->Done(); return rv; diff --git a/dom/notification/NotificationStorage.sys.mjs b/dom/notification/NotificationStorage.sys.mjs index e33277b598..115b9714fe 100644 --- a/dom/notification/NotificationStorage.sys.mjs +++ b/dom/notification/NotificationStorage.sys.mjs @@ -11,17 +11,10 @@ ChromeUtils.defineLazyGetter(lazy, "console", () => { }); }); -const kMessageNotificationGetAllOk = "Notification:GetAll:Return:OK"; -const kMessageNotificationGetAllKo = "Notification:GetAll:Return:KO"; -const kMessageNotificationSaveKo = "Notification:Save:Return:KO"; -const kMessageNotificationDeleteKo = "Notification:Delete:Return:KO"; - -const kMessages = [ - kMessageNotificationGetAllOk, - kMessageNotificationGetAllKo, - kMessageNotificationSaveKo, - kMessageNotificationDeleteKo, -]; +const kMessageGetAllOk = "GetAll:Return:OK"; +const kMessageGetAllKo = "GetAll:Return:KO"; +const kMessageSaveKo = "Save:Return:KO"; +const kMessageDeleteKo = "Delete:Return:KO"; export class NotificationStorage { #requests = {}; @@ -34,14 +27,35 @@ export class NotificationStorage { this.registerListeners(); } + storageQualifier() { + return "Notification"; + } + + prefixStorageQualifier(message) { + return `${this.storageQualifier()}:${message}`; + } + + formatMessageType(message) { + return this.prefixStorageQualifier(message); + } + + supportedMessages() { + return [ + this.formatMessageType(kMessageGetAllOk), + this.formatMessageType(kMessageGetAllKo), + this.formatMessageType(kMessageSaveKo), + this.formatMessageType(kMessageDeleteKo), + ]; + } + registerListeners() { - for (let message of kMessages) { + for (let message of this.supportedMessages()) { Services.cpmm.addMessageListener(message, this); } } unregisterListeners() { - for (let message of kMessages) { + for (let message of this.supportedMessages()) { Services.cpmm.removeMessageListener(message, this); } } @@ -85,7 +99,7 @@ export class NotificationStorage { serviceWorkerRegistrationScope, }; - Services.cpmm.sendAsyncMessage("Notification:Save", { + Services.cpmm.sendAsyncMessage(this.formatMessageType("Save"), { origin, notification, }); @@ -98,7 +112,7 @@ export class NotificationStorage { delete(origin, id) { lazy.console.debug(`DELETE: ${id}`); - Services.cpmm.sendAsyncMessage("Notification:Delete", { + Services.cpmm.sendAsyncMessage(this.formatMessageType("Delete"), { origin, id, }); @@ -108,12 +122,12 @@ export class NotificationStorage { var request = this.#requests[message.data.requestID]; switch (message.name) { - case kMessageNotificationGetAllOk: + case this.formatMessageType(kMessageGetAllOk): delete this.#requests[message.data.requestID]; this.#returnNotifications(message.data.notifications, request.callback); break; - case kMessageNotificationGetAllKo: + case this.formatMessageType(kMessageGetAllKo): delete this.#requests[message.data.requestID]; try { request.callback.done(); @@ -121,8 +135,8 @@ export class NotificationStorage { lazy.console.debug(`Error calling callback done: ${e}`); } break; - case kMessageNotificationSaveKo: - case kMessageNotificationDeleteKo: + case this.formatMessageType(kMessageSaveKo): + case this.formatMessageType(kMessageDeleteKo): lazy.console.debug( `Error received when treating: '${message.name}': ${message.data.errorMsg}` ); @@ -149,7 +163,7 @@ export class NotificationStorage { }; var requestID = this.#getUniqueRequestID(); this.#requests[requestID] = request; - Services.cpmm.sendAsyncMessage("Notification:GetAll", { + Services.cpmm.sendAsyncMessage(this.formatMessageType("GetAll"), { origin, tag, requestID, @@ -190,3 +204,9 @@ export class NotificationStorage { QueryInterface = ChromeUtils.generateQI(["nsINotificationStorage"]); } + +export class MemoryNotificationStorage extends NotificationStorage { + storageQualifier() { + return "MemoryNotification"; + } +} diff --git a/dom/notification/components.conf b/dom/notification/components.conf index 2cb6bdb33a..bafb6c9976 100644 --- a/dom/notification/components.conf +++ b/dom/notification/components.conf @@ -11,4 +11,10 @@ Classes = [ 'esModule': 'resource://gre/modules/NotificationStorage.sys.mjs', 'constructor': 'NotificationStorage', }, + { + 'cid': '{37f819b0-0b5c-11e3-8ffd-0800200c9a67}', + 'contract_ids': ['@mozilla.org/memoryNotificationStorage;1'], + 'esModule': 'resource://gre/modules/NotificationStorage.sys.mjs', + 'constructor': 'MemoryNotificationStorage', + } ] diff --git a/dom/notification/moz.build b/dom/notification/moz.build index b42e7a13ab..492cb30cfc 100644 --- a/dom/notification/moz.build +++ b/dom/notification/moz.build @@ -44,5 +44,6 @@ if CONFIG["MOZ_NEW_NOTIFICATION_STORE"]: ] else: EXTRA_JS_MODULES += [ + "old/MemoryNotificationDB.sys.mjs", "old/NotificationDB.sys.mjs", ] diff --git a/dom/notification/old/MemoryNotificationDB.sys.mjs b/dom/notification/old/MemoryNotificationDB.sys.mjs new file mode 100644 index 0000000000..ecd1b7c015 --- /dev/null +++ b/dom/notification/old/MemoryNotificationDB.sys.mjs @@ -0,0 +1,21 @@ +/* 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 { NotificationDB } from "./NotificationDB.sys.mjs"; + +class MemoryNotificationDB extends NotificationDB { + storageQualifier() { + return "MemoryNotification"; + } + + async load() { + this.loaded = true; + } + + async createStore() {} + async createFile() {} + async save() {} +} + +new MemoryNotificationDB(); diff --git a/dom/notification/old/NotificationDB.sys.mjs b/dom/notification/old/NotificationDB.sys.mjs index 8cd2bff8f6..7cd7e6f926 100644 --- a/dom/notification/old/NotificationDB.sys.mjs +++ b/dom/notification/old/NotificationDB.sys.mjs @@ -21,64 +21,84 @@ const NOTIFICATION_STORE_PATH = PathUtils.join( "notificationstore.json" ); -const kMessages = [ - "Notification:Save", - "Notification:Delete", - "Notification:GetAll", -]; - -var NotificationDB = { +export class NotificationDB { // Ensure we won't call init() while xpcom-shutdown is performed - _shutdownInProgress: false, + #shutdownInProgress = false; // A promise that resolves once the ongoing task queue has been drained. // The value will be reset when the queue starts again. - _queueDrainedPromise: null, - _queueDrainedPromiseResolve: null, - - init() { - if (this._shutdownInProgress) { + #queueDrainedPromise = null; + #queueDrainedPromiseResolve = null; + + #byTag = Object.create(null); + #notifications = Object.create(null); + #loaded = false; + #tasks = []; + #runningTask = null; + + storageQualifier() { + return "Notification"; + } + + prefixStorageQualifier(message) { + return `${this.storageQualifier()}:${message}`; + } + + formatMessageType(message) { + return this.prefixStorageQualifier(message); + } + + supportedMessageTypes() { + return [ + this.formatMessageType("Save"), + this.formatMessageType("Delete"), + this.formatMessageType("GetAll"), + ]; + } + + constructor() { + if (this.#shutdownInProgress) { return; } - this.notifications = Object.create(null); - this.byTag = Object.create(null); - this.loaded = false; + this.#notifications = Object.create(null); + this.#byTag = Object.create(null); + this.#loaded = false; - this.tasks = []; // read/write operation queue - this.runningTask = null; + this.#tasks = []; // read/write operation queue + this.#runningTask = null; Services.obs.addObserver(this, "xpcom-shutdown"); this.registerListeners(); // This assumes that nothing will queue a new task at profile-change-teardown phase, - // potentially replacing the _queueDrainedPromise if there was no existing task run. + // potentially replacing the #queueDrainedPromise if there was no existing task run. lazy.AsyncShutdown.profileChangeTeardown.addBlocker( "NotificationDB: Need to make sure that all notification messages are processed", - () => this._queueDrainedPromise + () => this.#queueDrainedPromise ); - }, + } registerListeners() { - for (let message of kMessages) { + for (let message of this.supportedMessageTypes()) { Services.ppmm.addMessageListener(message, this); } - }, + } unregisterListeners() { - for (let message of kMessages) { + for (let message of this.supportedMessageTypes()) { Services.ppmm.removeMessageListener(message, this); } - }, + } observe(aSubject, aTopic) { lazy.console.debug(`Topic: ${aTopic}`); if (aTopic == "xpcom-shutdown") { - this._shutdownInProgress = true; + this.#shutdownInProgress = true; Services.obs.removeObserver(this, "xpcom-shutdown"); this.unregisterListeners(); } - }, + } filterNonAppNotifications(notifications) { let result = Object.create(null); @@ -100,7 +120,7 @@ var NotificationDB = { } return result; - }, + } // Attempt to read notification file, if it's not there we will create it. load() { @@ -110,32 +130,34 @@ var NotificationDB = { if (data.length) { // Preprocessing phase intends to cleanly separate any migration-related // tasks. - this.notifications = this.filterNonAppNotifications(JSON.parse(data)); + this.#notifications = this.filterNonAppNotifications( + JSON.parse(data) + ); } // populate the list of notifications by tag - if (this.notifications) { - for (var origin in this.notifications) { - this.byTag[origin] = Object.create(null); - for (var id in this.notifications[origin]) { - var curNotification = this.notifications[origin][id]; + if (this.#notifications) { + for (var origin in this.#notifications) { + this.#byTag[origin] = Object.create(null); + for (var id in this.#notifications[origin]) { + var curNotification = this.#notifications[origin][id]; if (curNotification.tag) { - this.byTag[origin][curNotification.tag] = curNotification; + this.#byTag[origin][curNotification.tag] = curNotification; } } } } - this.loaded = true; + this.#loaded = true; }, // If read failed, we assume we have no notifications to load. () => { - this.loaded = true; + this.#loaded = true; return this.createStore(); } ); - }, + } // Creates the notification directory. createStore() { @@ -143,30 +165,30 @@ var NotificationDB = { ignoreExisting: true, }); return promise.then(this.createFile.bind(this)); - }, + } // Creates the notification file once the directory is created. createFile() { return IOUtils.writeUTF8(NOTIFICATION_STORE_PATH, "", { tmpPath: NOTIFICATION_STORE_PATH + ".tmp", }); - }, + } // Save current notifications to the file. save() { - var data = JSON.stringify(this.notifications); + var data = JSON.stringify(this.#notifications); return IOUtils.writeUTF8(NOTIFICATION_STORE_PATH, data, { tmpPath: NOTIFICATION_STORE_PATH + ".tmp", }); - }, + } // Helper function: promise will be resolved once file exists and/or is loaded. - ensureLoaded() { - if (!this.loaded) { + #ensureLoaded() { + if (!this.#loaded) { return this.load(); } return Promise.resolve(); - }, + } receiveMessage(message) { lazy.console.debug(`Received message: ${message.name}`); @@ -182,17 +204,17 @@ var NotificationDB = { } switch (message.name) { - case "Notification:GetAll": + case this.formatMessageType("GetAll"): this.queueTask("getall", message.data) - .then(function (notifications) { - returnMessage("Notification:GetAll:Return:OK", { + .then(notifications => { + returnMessage(this.formatMessageType("GetAll:Return:OK"), { requestID: message.data.requestID, origin: message.data.origin, notifications, }); }) - .catch(function (error) { - returnMessage("Notification:GetAll:Return:KO", { + .catch(error => { + returnMessage(this.formatMessageType("GetAll:Return:KO"), { requestID: message.data.requestID, origin: message.data.origin, errorMsg: error, @@ -200,30 +222,30 @@ var NotificationDB = { }); break; - case "Notification:Save": + case this.formatMessageType("Save"): this.queueTask("save", message.data) - .then(function () { - returnMessage("Notification:Save:Return:OK", { + .then(() => { + returnMessage(this.formatMessageType("Save:Return:OK"), { requestID: message.data.requestID, }); }) - .catch(function (error) { - returnMessage("Notification:Save:Return:KO", { + .catch(error => { + returnMessage(this.formatMessageType("Save:Return:KO"), { requestID: message.data.requestID, errorMsg: error, }); }); break; - case "Notification:Delete": + case this.formatMessageType("Delete"): this.queueTask("delete", message.data) - .then(function () { - returnMessage("Notification:Delete:Return:OK", { + .then(() => { + returnMessage(this.formatMessageType("Delete:Return:OK"), { requestID: message.data.requestID, }); }) - .catch(function (error) { - returnMessage("Notification:Delete:Return:KO", { + .catch(error => { + returnMessage(this.formatMessageType("Delete:Return:KO"), { requestID: message.data.requestID, errorMsg: error, }); @@ -233,7 +255,7 @@ var NotificationDB = { default: lazy.console.debug(`Invalid message name ${message.name}`); } - }, + } // We need to make sure any read/write operations are atomic, // so use a queue to run each operation sequentially. @@ -242,48 +264,48 @@ var NotificationDB = { var defer = {}; - this.tasks.push({ + this.#tasks.push({ operation, data, defer, }); - var promise = new Promise(function (resolve, reject) { + var promise = new Promise((resolve, reject) => { defer.resolve = resolve; defer.reject = reject; }); // Only run immediately if we aren't currently running another task. - if (!this.runningTask) { + if (!this.#runningTask) { lazy.console.debug("Task queue was not running, starting now..."); this.runNextTask(); - this._queueDrainedPromise = new Promise(resolve => { - this._queueDrainedPromiseResolve = resolve; + this.#queueDrainedPromise = new Promise(resolve => { + this.#queueDrainedPromiseResolve = resolve; }); } return promise; - }, + } runNextTask() { - if (this.tasks.length === 0) { + if (this.#tasks.length === 0) { lazy.console.debug("No more tasks to run, queue depleted"); - this.runningTask = null; - if (this._queueDrainedPromiseResolve) { - this._queueDrainedPromiseResolve(); + this.#runningTask = null; + if (this.#queueDrainedPromiseResolve) { + this.#queueDrainedPromiseResolve(); } else { lazy.console.debug( - "_queueDrainedPromiseResolve was null somehow, no promise to resolve" + "#queueDrainedPromiseResolve was null somehow, no promise to resolve" ); } return; } - this.runningTask = this.tasks.shift(); + this.#runningTask = this.#tasks.shift(); // Always make sure we are loaded before performing any read/write tasks. - this.ensureLoaded() + this.#ensureLoaded() .then(() => { - var task = this.runningTask; + var task = this.#runningTask; switch (task.operation) { case "getall": @@ -302,85 +324,85 @@ var NotificationDB = { } }) .then(payload => { - lazy.console.debug(`Finishing task: ${this.runningTask.operation}`); - this.runningTask.defer.resolve(payload); + lazy.console.debug(`Finishing task: ${this.#runningTask.operation}`); + this.#runningTask.defer.resolve(payload); }) .catch(err => { lazy.console.debug( - `Error while running ${this.runningTask.operation}: ${err}` + `Error while running ${this.#runningTask.operation}: ${err}` ); - this.runningTask.defer.reject(err); + this.#runningTask.defer.reject(err); }) .then(() => { this.runNextTask(); }); - }, + } taskGetAll(data) { lazy.console.debug("Task, getting all"); var origin = data.origin; var notifications = []; // Grab only the notifications for specified origin. - if (this.notifications[origin]) { + if (this.#notifications[origin]) { if (data.tag) { let n; - if ((n = this.byTag[origin][data.tag])) { + if ((n = this.#byTag[origin][data.tag])) { notifications.push(n); } } else { - for (var i in this.notifications[origin]) { - notifications.push(this.notifications[origin][i]); + for (var i in this.#notifications[origin]) { + notifications.push(this.#notifications[origin][i]); } } } return Promise.resolve(notifications); - }, + } taskSave(data) { lazy.console.debug("Task, saving"); var origin = data.origin; var notification = data.notification; - if (!this.notifications[origin]) { - this.notifications[origin] = Object.create(null); - this.byTag[origin] = Object.create(null); + if (!this.#notifications[origin]) { + this.#notifications[origin] = Object.create(null); + this.#byTag[origin] = Object.create(null); } // We might have existing notification with this tag, // if so we need to remove it before saving the new one. if (notification.tag) { - var oldNotification = this.byTag[origin][notification.tag]; + var oldNotification = this.#byTag[origin][notification.tag]; if (oldNotification) { - delete this.notifications[origin][oldNotification.id]; + delete this.#notifications[origin][oldNotification.id]; } - this.byTag[origin][notification.tag] = notification; + this.#byTag[origin][notification.tag] = notification; } - this.notifications[origin][notification.id] = notification; + this.#notifications[origin][notification.id] = notification; return this.save(); - }, + } taskDelete(data) { lazy.console.debug("Task, deleting"); var origin = data.origin; var id = data.id; - if (!this.notifications[origin]) { + if (!this.#notifications[origin]) { lazy.console.debug(`No notifications found for origin: ${origin}`); return Promise.resolve(); } // Make sure we can find the notification to delete. - var oldNotification = this.notifications[origin][id]; + var oldNotification = this.#notifications[origin][id]; if (!oldNotification) { lazy.console.debug(`No notification found with id: ${id}`); return Promise.resolve(); } if (oldNotification.tag) { - delete this.byTag[origin][oldNotification.tag]; + delete this.#byTag[origin][oldNotification.tag]; } - delete this.notifications[origin][id]; + delete this.#notifications[origin][id]; return this.save(); - }, -}; + } +} -NotificationDB.init(); +new NotificationDB(); diff --git a/dom/notification/test/unit/head_notificationdb.js b/dom/notification/test/unit/head_notificationdb.js index 1b23d88729..44b0d7c01b 100644 --- a/dom/notification/test/unit/head_notificationdb.js +++ b/dom/notification/test/unit/head_notificationdb.js @@ -31,6 +31,9 @@ var calendarNotification = getNotificationObject( // Helper to start the NotificationDB function startNotificationDB() { + ChromeUtils.importESModule( + "resource://gre/modules/MemoryNotificationDB.sys.mjs" + ); ChromeUtils.importESModule("resource://gre/modules/NotificationDB.sys.mjs"); } diff --git a/dom/notification/test/unit/test_notificationdb.js b/dom/notification/test/unit/test_notificationdb.js index b6ca8bd79c..4fc1e6389d 100644 --- a/dom/notification/test/unit/test_notificationdb.js +++ b/dom/notification/test/unit/test_notificationdb.js @@ -338,3 +338,89 @@ add_test(function test_delete_previous() { requestID, }); }); + +add_test(function test_notification_onDiskPersistence() { + let verifyDisk = async function (expectedId) { + const NOTIFICATION_STORE_PATH = PathUtils.join( + PathUtils.profileDir, + "notificationstore.json" + ); + + const onDiskNotificationStr = await IOUtils.readUTF8( + NOTIFICATION_STORE_PATH + ); + return onDiskNotificationStr.includes(expectedId); + }; + + let persistedNotification = getNotificationObject( + systemNotification.origin, + "{315aaf98-6c72-48fe-8e2c-a841e1b00027}", + "" /* tag */, + true /* scope */ + ); + + addAndSend( + "Notification:Save", + "Notification:Save:Return:OK", + async () => { + Assert.ok(await verifyDisk(persistedNotification.id)); + }, + { + origin: persistedNotification.origin, + notification: persistedNotification, + requestID: 2, + } + ); + + let nonPersistedNotification = getNotificationObject( + systemNotification.origin, + "{8110ed62-303f-4f9b-a257-a62487aaa09c}", + "" /* tag */, + true /* scope */ + ); + + addAndSend( + "MemoryNotification:Save", + "MemoryNotification:Save:Return:OK", + async () => { + // memoryonly notification must not exist on disk. + Assert.ok(!(await verifyDisk(nonPersistedNotification.id))); + }, + { + origin: nonPersistedNotification.origin, + notification: nonPersistedNotification, + requestID: 3, + } + ); + + let verifyMemory = function (message, expectedId) { + return message.data.notifications.some(notification => { + return notification.id == expectedId; + }); + }; + + addAndSend( + "Notification:GetAll", + "Notification:GetAll:Return:OK", + message => { + Assert.ok(verifyMemory(message, persistedNotification.id)); + }, + { + origin: persistedNotification.origin, + requestID: 4, + } + ); + + addAndSend( + "MemoryNotification:GetAll", + "MemoryNotification:GetAll:Return:OK", + message => { + // memoryonly notification must exist in-memory + Assert.ok(verifyMemory(message, nonPersistedNotification.id)); + }, + { + origin: persistedNotification.origin, + requestID: 5, + } + ); +}); |