Files
palemoon27/browser/components/loop/modules/LoopStorage.jsm
T
roytam1 d6aa95a102 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1230352 - Update to Oculus SDK 0.8.0,r=vlad (ec5404763a)
- Bug 1237689 - Enable Oculus hardware latency tester r=daoshengmu (a914fb5c78)
- Bug 1248757 Use string ::Assign() instead of Adopt() when reading blobs as strings. r=asuth (65e361797f)
- Bug 1248757 followup - Release blob after assignment in DoGetBlobAsString on CLOSED TREE. (8f5d86a27a)
- Bug 1240583 - Odin: record and assert whether a function is defined yet (r=bbouvier) (693c0be6fe)
- Bug 1240583 - Odin: store code-range index instead of entry offset in ModuleGenerator (r=bbouvier) (ac14028824)
- Bug 1240583 - add thunkWithPatch/patchThunk (r=jandem) (9b53ac4d5b)
- Bug 1240583 - Odin: fix long jumps/calls on ARM for large modules (r=bbouvier) (3f7bacc9cf)
- Bug 1240583 - Odin: add MacroAssembler::repatchThunk (r=bbouvier) (1336b1492d)
- bug 1029797 - remove redundant "Security information for this page" section of Page Info window r=dao (a6454d75c3)
- Bug 1158112 - Move the Loop modules into a sub-directory and prepare eslint for enabling more rules for Loop. r=dmose (aff45f74ba)
- Bug 1153806 - Enable encryption of Loop room context data. r=mikedeboer (1e26b118e1)
- Bug 1160487 - Enable eslint rules for Loop: semi-colon related. r=Standard8 (e14b2b102a)
- Bug 1151862 - Get the full list of Loop rooms on first notification if we haven't got it previously. r=mikedeboer (851f5b61c6)
- Bug 1152761 - Add local storage for Loop's room keys in case recovery is required, and handle the recovery. r=mikedeboer (aa0bfa855b)
- Bug 1142515: implement updating a room with changed context information. r=Standard8 (689f967d4f)
- Bug 1152764 - Loop should encrypt room context information for rooms that aren't encrypted. r=mikedeboer (d43334409f)
- Bug 1164821 - Remove previous workarounds for not having FxA keys in LoopRooms - remove old code to cope with unencrypted rooms. r=mikedeboer (035713de3c)
- Bug 1037483 adopt microformats-shiv for microformats v2 support, r=tantek (c08afa618d)
- Bug 1224766 - forward callID to disambiguate multiple gUM requests from same window. r=jesup,smaug (547eafdbaa)
- Bug 1201393. Remove irrelevant ProcessedMediaStream for nsSpeechTask. r=eitan (f17f8e6821)
- Bug 1240583: Fix non-unified build for fuzzers; r=luke (cc7ea34899)
- Bug 1212366 - Part 1. Don't call SetAudioOutputVolume if stream is destroyed. r=roc (af1106491c)
- Bug 1212366 - Part 2. Don't release SourceMediaStream until StreamListener is called. r=roc (08cf9cf62f)
- Bug 1220320 - implement the nsSupportsWeakReference. r=baku (0cfd32c83a)
- Bug 1225347 - Apply audio setting to volume parameter of Speak(). r=eeejay (fc6dbd938c)
- Bug 1228564 - part 1 : revert the changeset of bug 1190040. r=baku. (921a0f7383)
- Bug 1195051 - Part 3: Test changes; r=padenot (438a73a408)
- Bug 1195051 - Part 4: Fix a null pointer crash happening after the destination node gets CCed (90fd50c8ac)
- Bug 1228564 - part 2 : check audio capturing when the agent is registered/unregistered. r=baku. (14f473625b)
- Bug 1228564 - Follow-up to fix static analysis build bustage. r=me (295d61a1b6)
- Bug 1247846 - Odin: switch CallIndirect to wasm binary encoding (r=bbouvier) (5997b4c59b)
- Bug 1247846 - Odin: refactor ModuleGenerator::finish (r=bbouvier) (a71293c284)
- Bug 1247846 - Odin: refactor error stub generation (r=bbouvier) (5f9f98b215)
2023-09-14 09:35:24 +08:00

378 lines
13 KiB
JavaScript

/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
// Make it possible to load LoopStorage.jsm in xpcshell tests
try {
Cu.importGlobalProperties(["indexedDB"]);
} catch (ex) {
// don't write this is out in xpcshell, since it's expected there
if (typeof window !== 'undefined' && "console" in window) {
console.log("Failed to import indexedDB; if this isn't a unit test," +
" something is wrong", ex);
}
}
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
return new EventEmitter();
});
this.EXPORTED_SYMBOLS = ["LoopStorage"];
const kDatabasePrefix = "loop-";
const kDefaultDatabaseName = "default";
let gDatabaseName = kDatabasePrefix + kDefaultDatabaseName;
const kDatabaseVersion = 1;
let gWaitForOpenCallbacks = new Set();
let gDatabase = null;
let gClosed = false;
/**
* Properly shut the database instance down. This is done on application shutdown.
*/
const closeDatabase = function() {
Services.obs.removeObserver(closeDatabase, "quit-application");
if (!gDatabase) {
return;
}
gDatabase.close();
gDatabase = null;
gClosed = true;
};
/**
* Open a connection to the IndexedDB database.
* This function is different than IndexedDBHelper.jsm provides, as it ensures
* only one connection is open during the lifetime of this API. Callbacks are
* queued when a connection attempt is in progress and are invoked once the
* connection is established.
*
* @param {Function} onOpen Callback to be invoked once a database connection is
* established. It takes an Error object as first argument
* and the database connection object as second argument,
* if successful.
*/
const ensureDatabaseOpen = function(onOpen) {
if (gClosed) {
onOpen(new Error("Database already closed"));
return;
}
if (gDatabase) {
onOpen(null, gDatabase);
return;
}
if (!gWaitForOpenCallbacks.has(onOpen)) {
gWaitForOpenCallbacks.add(onOpen);
if (gWaitForOpenCallbacks.size !== 1) {
return;
}
}
let invokeCallbacks = err => {
for (let callback of gWaitForOpenCallbacks) {
callback(err, gDatabase);
}
gWaitForOpenCallbacks.clear();
};
let openRequest = indexedDB.open(gDatabaseName, kDatabaseVersion);
openRequest.onblocked = function(event) {
invokeCallbacks(new Error("Database cannot be upgraded cause in use: " + event.target.error));
};
openRequest.onerror = function(event) {
// Try to delete the old database so that we can start this process over
// next time.
indexedDB.deleteDatabase(gDatabaseName);
invokeCallbacks(new Error("Error while opening database: " + event.target.errorCode));
};
openRequest.onupgradeneeded = function(event) {
let db = event.target.result;
eventEmitter.emit("upgrade", db, event.oldVersion, kDatabaseVersion);
};
openRequest.onsuccess = function(event) {
gDatabase = event.target.result;
invokeCallbacks();
// Close the database instance properly on application shutdown.
Services.obs.addObserver(closeDatabase, "quit-application", false);
};
};
/**
* Switch to a database with a different name by closing the current connection
* and making sure that the next connection attempt will be made using the updated
* name.
*
* @param {String} name New name of the database to switch to.
*/
const switchDatabase = function(name) {
if (!name) {
name = kDefaultDatabaseName;
}
name = kDatabasePrefix + name;
if (name == gDatabaseName) {
// This is already the current database, so there's no need to switch.
return;
}
gDatabaseName = name;
if (gDatabase) {
try {
gDatabase.close();
} finally {
gDatabase = null;
}
}
};
/**
* Start a transaction on the loop database and return it.
*
* @param {String} store Name of the object store to start a transaction on
* @param {Function} callback Callback to be invoked once a database connection
* is established and a transaction can be started.
* It takes an Error object as first argument and the
* transaction object as second argument.
* @param {String} mode Mode of the transaction. May be 'readonly' or 'readwrite'
*
* @note we can't use a Promise here, as they are resolved after a spin of the
* event loop; the transaction will have finished by then and no operations
* are possible anymore, yielding an error.
*/
const getTransaction = function(store, callback, mode) {
ensureDatabaseOpen((err, db) => {
if (err) {
callback(err);
return;
}
let trans;
try {
trans = db.transaction(store, mode);
} catch(ex) {
callback(ex);
return;
}
callback(null, trans);
});
};
/**
* Start a transaction on the loop database and return the requested store.
*
* @param {String} store Name of the object store to retrieve
* @param {Function} callback Callback to be invoked once a database connection
* is established and a transaction can be started.
* It takes an Error object as first argument and the
* store object as second argument.
* @param {String} mode Mode of the transaction. May be 'readonly' or 'readwrite'
*
* @note we can't use a Promise here, as they are resolved after a spin of the
* event loop; the transaction will have finished by then and no operations
* are possible anymore, yielding an error.
*/
const getStore = function(store, callback, mode) {
getTransaction(store, (err, trans) => {
if (err) {
callback(err);
return;
}
callback(null, trans.objectStore(store));
}, mode);
};
/**
* Public Loop Storage API.
*
* Since IndexedDB transaction can not stand a spin of the event loop _before_
* using a IDBTransaction object, we can't use Promise.jsm promises. Therefore
* LoopStorage provides two async helper functions, `asyncForEach` and `asyncParallel`.
*
* LoopStorage implements the EventEmitter interface by exposing two methods, `on`
* and `off`, to subscribe to events.
* At this point only the `upgrade` event will be emitted. This happens when the
* database is loaded in memory and consumers will be able to change its structure.
*/
this.LoopStorage = Object.freeze({
/**
* @var {String} databaseName The name of the database that is currently active,
* WITHOUT the prefix
*/
get databaseName() {
return gDatabaseName.substr(kDatabasePrefix.length);
},
/**
* Open a connection to the IndexedDB database and return the database object.
*
* @param {Function} callback Callback to be invoked once a database connection
* is established. It takes an Error object as first
* argument and the database connection object as
* second argument, if successful.
*/
getSingleton: function(callback) {
ensureDatabaseOpen(callback);
},
/**
* Switch to a database with a different name.
*
* @param {String} name New name of the database to switch to. Defaults to
* `kDefaultDatabaseName`
*/
switchDatabase: function(name = kDefaultDatabaseName) {
switchDatabase(name);
},
/**
* Start a transaction on the loop database and return it.
* If only two arguments are passed, the default mode will be assumed and the
* second argument is assumed to be a callback.
*
* @param {String} store Name of the object store to start a transaction on
* @param {Function} callback Callback to be invoked once a database connection
* is established and a transaction can be started.
* It takes an Error object as first argument and the
* transaction object as second argument.
* @param {String} mode Mode of the transaction. May be 'readonly' or 'readwrite'
*
* @note we can't use a Promise here, as they are resolved after a spin of the
* event loop; the transaction will have finished by then and no operations
* are possible anymore, yielding an error.
*/
getTransaction: function(store, callback, mode = "readonly") {
getTransaction(store, callback, mode);
},
/**
* Start a transaction on the loop database and return the requested store.
* If only two arguments are passed, the default mode will be assumed and the
* second argument is assumed to be a callback.
*
* @param {String} store Name of the object store to retrieve
* @param {Function} callback Callback to be invoked once a database connection
* is established and a transaction can be started.
* It takes an Error object as first argument and the
* store object as second argument.
* @param {String} mode Mode of the transaction. May be 'readonly' or 'readwrite'
*
* @note we can't use a Promise here, as they are resolved after a spin of the
* event loop; the transaction will have finished by then and no operations
* are possible anymore, yielding an error.
*/
getStore: function(store, callback, mode = "readonly") {
getStore(store, callback, mode);
},
/**
* Perform an async function in serial on each of the list items and call a
* callback Function when all list items are done.
* IMPORTANT: only use this iteration method if you are sure that the operations
* performed in `onItem` are guaranteed to be async in the success case.
*
* @param {Array} list Non-empty list of items to iterate
* @param {Function} onItem Callback to invoke for each item in the list. It
* takes the item is first argument and a callback
* function as second, which is to be invoked once
* the consumer is done with its async operation. If
* an error is passed as the first argument to this
* callback function, the iteration will stop and
* `onDone` callback will be invoked with that error.
* @param {callback} onDone Callback to invoke when the list is completed or
* on error. It takes an Error object as first
* argument.
*/
asyncForEach: function(list, onItem, onDone) {
let i = 0;
let len = list.length;
if (!len) {
onDone(new Error("Argument error: empty list"));
return;
}
onItem(list[i], function handler(err) {
if (err) {
onDone(err);
return;
}
i++;
if (i < len) {
onItem(list[i], handler, i);
} else {
onDone();
}
}, i);
},
/**
* Perform an async function in parallel on each of the list items and call a
* callback Function when all list items are done.
* IMPORTANT: only use this iteration method if you are sure that the operations
* performed in `onItem` are guaranteed to be async in the success case.
*
* @param {Array} list Non-empty list of items to iterate
* @param {Function} onItem Callback to invoke for each item in the list. It
* takes the item is first argument and a callback
* function as second, which is to be invoked once
* the consumer is done with its async operation. If
* an error is passed as the first argument to this
* callback function, the iteration will stop and
* `onDone` callback will be invoked with that error.
* @param {callback} onDone Callback to invoke when the list is completed or
* on error. It takes an Error object as first
* argument.
*/
asyncParallel: function(list, onItem, onDone) {
let i = 0;
let done = 0;
let callbackCalled = false;
let len = list.length;
if (!len) {
onDone(new Error("Argument error: empty list"));
return;
}
function handleCallback(err) {
if (callbackCalled) {
return;
}
if (err) {
onDone(err);
callbackCalled = true;
return;
}
if (++done === len) {
onDone();
callbackCalled = true;
}
}
for (; i < len; ++i) {
onItem(list[i], handleCallback, i);
}
},
on: (...params) => eventEmitter.on(...params),
off: (...params) => eventEmitter.off(...params)
});