Bug 1889930: Remove source code corresponding to deprecated notification store.r=saschanaz
Differential Revision: https://phabricator.services.mozilla.com/D217143
This commit is contained in:
@@ -38,12 +38,7 @@ XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.toml"]
|
||||
MOCHITEST_MANIFESTS += ["test/mochitest/mochitest.toml"]
|
||||
MOCHITEST_CHROME_MANIFESTS += ["test/chrome/chrome.toml"]
|
||||
|
||||
if CONFIG["MOZ_NEW_NOTIFICATION_STORE"]:
|
||||
EXTRA_JS_MODULES += [
|
||||
"new/NotificationDB.sys.mjs",
|
||||
]
|
||||
else:
|
||||
EXTRA_JS_MODULES += [
|
||||
"old/MemoryNotificationDB.sys.mjs",
|
||||
"old/NotificationDB.sys.mjs",
|
||||
]
|
||||
EXTRA_JS_MODULES += [
|
||||
"MemoryNotificationDB.sys.mjs",
|
||||
"NotificationDB.sys.mjs",
|
||||
]
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
const DEBUG = false;
|
||||
function debug(s) {
|
||||
dump("-*- NotificationDB component: " + s + "\n");
|
||||
}
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
|
||||
KeyValueService: "resource://gre/modules/kvstore.sys.mjs",
|
||||
});
|
||||
|
||||
const kMessages = [
|
||||
"Notification:Save",
|
||||
"Notification:Delete",
|
||||
"Notification:GetAll",
|
||||
];
|
||||
|
||||
// Given its origin and ID, produce the key that uniquely identifies
|
||||
// a notification.
|
||||
function makeKey(origin, id) {
|
||||
return origin.concat("\t", id);
|
||||
}
|
||||
|
||||
var NotificationDB = {
|
||||
// Ensure we won't call init() while xpcom-shutdown is performed
|
||||
_shutdownInProgress: false,
|
||||
|
||||
// A handle to the kvstore, retrieved lazily when we load the data.
|
||||
_store: null,
|
||||
|
||||
// A promise that resolves once the store has been loaded.
|
||||
// The promise doesn't resolve to a value; it merely captures the state
|
||||
// of the load via its resolution.
|
||||
_loadPromise: null,
|
||||
|
||||
// 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
|
||||
"NotificationDB: Need to make sure that all notification messages are processed",
|
||||
() => this._queueDrainedPromise
|
||||
);
|
||||
},
|
||||
|
||||
registerListeners() {
|
||||
for (let message of kMessages) {
|
||||
Services.ppmm.addMessageListener(message, this);
|
||||
}
|
||||
},
|
||||
|
||||
unregisterListeners() {
|
||||
for (let message of kMessages) {
|
||||
Services.ppmm.removeMessageListener(message, this);
|
||||
}
|
||||
},
|
||||
|
||||
observe(aSubject, aTopic) {
|
||||
if (DEBUG) {
|
||||
debug("Topic: " + aTopic);
|
||||
}
|
||||
if (aTopic == "xpcom-shutdown") {
|
||||
this._shutdownInProgress = true;
|
||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||
this.unregisterListeners();
|
||||
}
|
||||
},
|
||||
|
||||
filterNonAppNotifications(notifications) {
|
||||
for (let origin in notifications) {
|
||||
let persistentNotificationCount = 0;
|
||||
for (let id in notifications[origin]) {
|
||||
if (notifications[origin][id].serviceWorkerRegistrationScope) {
|
||||
persistentNotificationCount++;
|
||||
} else {
|
||||
delete notifications[origin][id];
|
||||
}
|
||||
}
|
||||
if (persistentNotificationCount == 0) {
|
||||
if (DEBUG) {
|
||||
debug(
|
||||
"Origin " + origin + " is not linked to an app manifest, deleting."
|
||||
);
|
||||
}
|
||||
delete notifications[origin];
|
||||
}
|
||||
}
|
||||
|
||||
return notifications;
|
||||
},
|
||||
|
||||
async maybeMigrateData() {
|
||||
const oldStore = PathUtils.join(
|
||||
Services.dirsvc.get("ProfD", Ci.nsIFile).path,
|
||||
"notificationstore.json"
|
||||
);
|
||||
|
||||
if (!(await IOUtils.exists(oldStore))) {
|
||||
if (DEBUG) {
|
||||
debug("Old store doesn't exist; not migrating data.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = await IOUtils.readUTF8(oldStore);
|
||||
} catch (ex) {
|
||||
// If read failed, we assume we have no notifications to migrate.
|
||||
if (DEBUG) {
|
||||
debug("Failed to read old store; not migrating data.");
|
||||
}
|
||||
return;
|
||||
} finally {
|
||||
// Finally, delete the old file so we don't try to migrate it again.
|
||||
await IOUtils.remove(oldStore);
|
||||
}
|
||||
|
||||
if (data.length) {
|
||||
// Preprocessing phase intends to cleanly separate any migration-related
|
||||
// tasks.
|
||||
//
|
||||
// NB: This code existed before we migrated the data to a kvstore,
|
||||
// and the "migration-related tasks" it references are from an earlier
|
||||
// migration. We used to do it every time we read the JSON file;
|
||||
// now we do it once, when migrating the JSON file to the kvstore.
|
||||
const notifications = this.filterNonAppNotifications(JSON.parse(data));
|
||||
|
||||
// Copy the data from the JSON file to the kvstore.
|
||||
// TODO: use a transaction to improve the performance of these operations
|
||||
// once the kvstore API supports it (bug 1515096).
|
||||
for (const origin in notifications) {
|
||||
for (const id in notifications[origin]) {
|
||||
await this._store.put(
|
||||
makeKey(origin, id),
|
||||
JSON.stringify(notifications[origin][id])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Attempt to read notification file, if it's not there we will create it.
|
||||
async load() {
|
||||
// Get and cache a handle to the kvstore.
|
||||
const dir = PathUtils.join(PathUtils.profileDir, "notificationstore");
|
||||
await IOUtils.makeDirectory(dir, { ignoreExisting: true });
|
||||
this._store = await lazy.KeyValueService.getOrCreate(dir, "notifications");
|
||||
|
||||
// Migrate data from the old JSON file to the new kvstore if the old file
|
||||
// is present in the user's profile directory.
|
||||
await this.maybeMigrateData();
|
||||
},
|
||||
|
||||
// Helper function: promise will be resolved once file exists and/or is loaded.
|
||||
ensureLoaded() {
|
||||
if (!this._loadPromise) {
|
||||
this._loadPromise = this.load();
|
||||
}
|
||||
return this._loadPromise;
|
||||
},
|
||||
|
||||
receiveMessage(message) {
|
||||
if (DEBUG) {
|
||||
debug("Received message:" + message.name);
|
||||
}
|
||||
|
||||
// sendAsyncMessage can fail if the child process exits during a
|
||||
// notification storage operation, so always wrap it in a try/catch.
|
||||
function returnMessage(name, data) {
|
||||
try {
|
||||
message.target.sendAsyncMessage(name, data);
|
||||
} catch (e) {
|
||||
if (DEBUG) {
|
||||
debug("Return message failed, " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (message.name) {
|
||||
case "Notification:GetAll":
|
||||
this.queueTask("getall", message.data)
|
||||
.then(function (notifications) {
|
||||
returnMessage("Notification:GetAll:Return:OK", {
|
||||
requestID: message.data.requestID,
|
||||
origin: message.data.origin,
|
||||
notifications,
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
returnMessage("Notification:GetAll:Return:KO", {
|
||||
requestID: message.data.requestID,
|
||||
origin: message.data.origin,
|
||||
errorMsg: error,
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case "Notification:Save":
|
||||
this.queueTask("save", message.data)
|
||||
.then(function () {
|
||||
returnMessage("Notification:Save:Return:OK", {
|
||||
requestID: message.data.requestID,
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
returnMessage("Notification:Save:Return:KO", {
|
||||
requestID: message.data.requestID,
|
||||
errorMsg: error,
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case "Notification:Delete":
|
||||
this.queueTask("delete", message.data)
|
||||
.then(function () {
|
||||
returnMessage("Notification:Delete:Return:OK", {
|
||||
requestID: message.data.requestID,
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
returnMessage("Notification:Delete:Return:KO", {
|
||||
requestID: message.data.requestID,
|
||||
errorMsg: error,
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
if (DEBUG) {
|
||||
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.
|
||||
queueTask(operation, data) {
|
||||
if (DEBUG) {
|
||||
debug("Queueing task: " + operation);
|
||||
}
|
||||
|
||||
var defer = {};
|
||||
|
||||
this.tasks.push({ operation, data, defer });
|
||||
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
defer.resolve = resolve;
|
||||
defer.reject = reject;
|
||||
});
|
||||
|
||||
// Only run immediately if we aren't currently running another task.
|
||||
if (!this.runningTask) {
|
||||
if (DEBUG) {
|
||||
debug("Task queue was not running, starting now...");
|
||||
}
|
||||
this.runNextTask();
|
||||
this._queueDrainedPromise = new Promise(resolve => {
|
||||
this._queueDrainedPromiseResolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
runNextTask() {
|
||||
if (this.tasks.length === 0) {
|
||||
if (DEBUG) {
|
||||
debug("No more tasks to run, queue depleted");
|
||||
}
|
||||
this.runningTask = null;
|
||||
if (this._queueDrainedPromiseResolve) {
|
||||
this._queueDrainedPromiseResolve();
|
||||
} else if (DEBUG) {
|
||||
debug(
|
||||
"_queueDrainedPromiseResolve was null somehow, no promise to resolve"
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.runningTask = this.tasks.shift();
|
||||
|
||||
// Always make sure we are loaded before performing any read/write tasks.
|
||||
this.ensureLoaded()
|
||||
.then(() => {
|
||||
var task = this.runningTask;
|
||||
|
||||
switch (task.operation) {
|
||||
case "getall":
|
||||
return this.taskGetAll(task.data);
|
||||
|
||||
case "save":
|
||||
return this.taskSave(task.data);
|
||||
|
||||
case "delete":
|
||||
return this.taskDelete(task.data);
|
||||
}
|
||||
|
||||
throw new Error(`Unknown task operation: ${task.operation}`);
|
||||
})
|
||||
.then(payload => {
|
||||
if (DEBUG) {
|
||||
debug("Finishing task: " + this.runningTask.operation);
|
||||
}
|
||||
this.runningTask.defer.resolve(payload);
|
||||
})
|
||||
.catch(err => {
|
||||
if (DEBUG) {
|
||||
debug(
|
||||
"Error while running " + this.runningTask.operation + ": " + err
|
||||
);
|
||||
}
|
||||
this.runningTask.defer.reject(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.runNextTask();
|
||||
});
|
||||
},
|
||||
|
||||
enumerate(origin) {
|
||||
// The "from" and "to" key parameters to nsIKeyValueStore.enumerate()
|
||||
// are inclusive and exclusive, respectively, and keys are tuples
|
||||
// of origin and ID joined by a tab (\t), which is character code 9;
|
||||
// so enumerating ["origin", "origin\n"), where the line feed (\n)
|
||||
// is character code 10, enumerates all pairs with the given origin.
|
||||
return this._store.enumerate(origin, `${origin}\n`);
|
||||
},
|
||||
|
||||
async taskGetAll(data) {
|
||||
if (DEBUG) {
|
||||
debug("Task, getting all");
|
||||
}
|
||||
var origin = data.origin;
|
||||
var notifications = [];
|
||||
|
||||
for (const { value } of await this.enumerate(origin)) {
|
||||
notifications.push(JSON.parse(value));
|
||||
}
|
||||
|
||||
if (data.tag) {
|
||||
notifications = notifications.filter(n => n.tag === data.tag);
|
||||
}
|
||||
|
||||
return notifications;
|
||||
},
|
||||
|
||||
async taskSave(data) {
|
||||
if (DEBUG) {
|
||||
debug("Task, saving");
|
||||
}
|
||||
var origin = data.origin;
|
||||
var notification = data.notification;
|
||||
|
||||
// We might have existing notification with this tag,
|
||||
// if so we need to remove it before saving the new one.
|
||||
if (notification.tag) {
|
||||
for (const { key, value } of await this.enumerate(origin)) {
|
||||
const oldNotification = JSON.parse(value);
|
||||
if (oldNotification.tag === notification.tag) {
|
||||
await this._store.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this._store.put(
|
||||
makeKey(origin, notification.id),
|
||||
JSON.stringify(notification)
|
||||
);
|
||||
},
|
||||
|
||||
async taskDelete(data) {
|
||||
if (DEBUG) {
|
||||
debug("Task, deleting");
|
||||
}
|
||||
await this._store.delete(makeKey(data.origin, data.id));
|
||||
},
|
||||
};
|
||||
|
||||
NotificationDB.init();
|
||||
@@ -1,129 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const { AppConstants } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
);
|
||||
|
||||
const fooNotification = getNotificationObject(
|
||||
"foo",
|
||||
"a4f1d54a-98b7-4231-9120-5afc26545bad",
|
||||
null,
|
||||
true
|
||||
);
|
||||
const barNotification = getNotificationObject(
|
||||
"bar",
|
||||
"a4f1d54a-98b7-4231-9120-5afc26545bad",
|
||||
"baz",
|
||||
true
|
||||
);
|
||||
const msg = "Notification:GetAll";
|
||||
const msgReply = "Notification:GetAll:Return:OK";
|
||||
|
||||
do_get_profile();
|
||||
const OLD_STORE_PATH = PathUtils.join(
|
||||
PathUtils.profileDir,
|
||||
"notificationstore.json"
|
||||
);
|
||||
|
||||
let nextRequestID = 0;
|
||||
|
||||
// Create the old datastore and populate it with data before we initialize
|
||||
// the notification database so it has data to migrate. This is a setup step,
|
||||
// not a test, but it seems like we need to do it in a test function
|
||||
// rather than in run_test() because the test runner doesn't handle async steps
|
||||
// in run_test().
|
||||
add_task(
|
||||
{
|
||||
skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
|
||||
},
|
||||
async function test_create_old_datastore() {
|
||||
const notifications = {
|
||||
[fooNotification.origin]: {
|
||||
[fooNotification.id]: fooNotification,
|
||||
},
|
||||
[barNotification.origin]: {
|
||||
[barNotification.id]: barNotification,
|
||||
},
|
||||
};
|
||||
|
||||
await IOUtils.writeJSON(OLD_STORE_PATH, notifications);
|
||||
|
||||
startNotificationDB();
|
||||
}
|
||||
);
|
||||
|
||||
add_test(
|
||||
{
|
||||
skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
|
||||
},
|
||||
function test_get_system_notification() {
|
||||
const requestID = nextRequestID++;
|
||||
const msgHandler = function (message) {
|
||||
Assert.equal(requestID, message.data.requestID);
|
||||
Assert.equal(0, message.data.notifications.length);
|
||||
};
|
||||
|
||||
addAndSend(msg, msgReply, msgHandler, {
|
||||
origin: systemNotification.origin,
|
||||
requestID,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
add_test(
|
||||
{
|
||||
skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
|
||||
},
|
||||
function test_get_foo_notification() {
|
||||
const requestID = nextRequestID++;
|
||||
const msgHandler = function (message) {
|
||||
Assert.equal(requestID, message.data.requestID);
|
||||
Assert.equal(1, message.data.notifications.length);
|
||||
Assert.deepEqual(
|
||||
fooNotification,
|
||||
message.data.notifications[0],
|
||||
"Notification data migrated"
|
||||
);
|
||||
};
|
||||
|
||||
addAndSend(msg, msgReply, msgHandler, {
|
||||
origin: fooNotification.origin,
|
||||
requestID,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
add_test(
|
||||
{
|
||||
skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
|
||||
},
|
||||
function test_get_bar_notification() {
|
||||
const requestID = nextRequestID++;
|
||||
const msgHandler = function (message) {
|
||||
Assert.equal(requestID, message.data.requestID);
|
||||
Assert.equal(1, message.data.notifications.length);
|
||||
Assert.deepEqual(
|
||||
barNotification,
|
||||
message.data.notifications[0],
|
||||
"Notification data migrated"
|
||||
);
|
||||
};
|
||||
|
||||
addAndSend(msg, msgReply, msgHandler, {
|
||||
origin: barNotification.origin,
|
||||
requestID,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
{
|
||||
skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
|
||||
},
|
||||
async function test_old_datastore_deleted() {
|
||||
Assert.ok(
|
||||
!(await IOUtils.exists(OLD_STORE_PATH)),
|
||||
"old datastore no longer exists"
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -5,5 +5,3 @@ skip-if = ["os == 'android'"]
|
||||
["test_notificationdb.js"]
|
||||
|
||||
["test_notificationdb_bug1024090.js"]
|
||||
|
||||
["test_notificationdb_migration.js"]
|
||||
|
||||
@@ -69,7 +69,6 @@
|
||||
"MOZ_MACBUNDLE_NAME": "Nightly.app",
|
||||
"MOZ_MAINTENANCE_SERVICE": false,
|
||||
"MOZ_MOZILLA_API_KEY": "no-mozilla-api-key",
|
||||
"MOZ_NEW_NOTIFICATION_STORE": true,
|
||||
"MOZ_NEW_XULSTORE": true,
|
||||
"MOZ_NORMANDY": true,
|
||||
"MOZ_OFFICIAL_BRANDING": false,
|
||||
@@ -138,7 +137,6 @@
|
||||
"MOZ_MACBUNDLE_NAME": "Nightly.app",
|
||||
"MOZ_MAINTENANCE_SERVICE": false,
|
||||
"MOZ_MOZILLA_API_KEY": "no-mozilla-api-key",
|
||||
"MOZ_NEW_NOTIFICATION_STORE": true,
|
||||
"MOZ_NEW_XULSTORE": true,
|
||||
"MOZ_NORMANDY": true,
|
||||
"MOZ_OFFICIAL_BRANDING": false,
|
||||
|
||||
@@ -403,13 +403,6 @@ export var AppConstants = Object.freeze({
|
||||
|
||||
TELEMETRY_PING_FORMAT_VERSION: @TELEMETRY_PING_FORMAT_VERSION@,
|
||||
|
||||
MOZ_NEW_NOTIFICATION_STORE:
|
||||
#ifdef MOZ_NEW_NOTIFICATION_STORE
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
#endif
|
||||
|
||||
ENABLE_WEBDRIVER:
|
||||
#ifdef ENABLE_WEBDRIVER
|
||||
true,
|
||||
|
||||
Reference in New Issue
Block a user