summaryrefslogtreecommitdiffstats
path: root/dom/notification
diff options
context:
space:
mode:
Diffstat (limited to 'dom/notification')
-rw-r--r--dom/notification/Notification.cpp114
-rw-r--r--dom/notification/NotificationStorage.sys.mjs60
-rw-r--r--dom/notification/components.conf6
-rw-r--r--dom/notification/moz.build1
-rw-r--r--dom/notification/old/MemoryNotificationDB.sys.mjs21
-rw-r--r--dom/notification/old/NotificationDB.sys.mjs222
-rw-r--r--dom/notification/test/unit/head_notificationdb.js3
-rw-r--r--dom/notification/test/unit/test_notificationdb.js86
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,
+ }
+ );
+});