diff options
Diffstat (limited to 'toolkit/components/extensions/storage/ExtensionStorageComponents.sys.mjs')
-rw-r--r-- | toolkit/components/extensions/storage/ExtensionStorageComponents.sys.mjs | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/toolkit/components/extensions/storage/ExtensionStorageComponents.sys.mjs b/toolkit/components/extensions/storage/ExtensionStorageComponents.sys.mjs new file mode 100644 index 0000000000..b02b0d53e8 --- /dev/null +++ b/toolkit/components/extensions/storage/ExtensionStorageComponents.sys.mjs @@ -0,0 +1,114 @@ +/* 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 lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", +}); + +const StorageSyncArea = Components.Constructor( + "@mozilla.org/extensions/storage/internal/sync-area;1", + "mozIConfigurableExtensionStorageArea", + "configure" +); + +/** + * An XPCOM service for the WebExtension `storage.sync` API. The service manages + * a storage area for storing and syncing extension data. + * + * The service configures its storage area with the database path, and hands + * out references to the configured area via `getInterface`. It also registers + * a shutdown blocker to automatically tear down the area. + * + * ## What's the difference between `storage/internal/storage-sync-area;1` and + * `storage/sync;1`? + * + * `components.conf` has two classes: + * `@mozilla.org/extensions/storage/internal/sync-area;1` and + * `@mozilla.org/extensions/storage/sync;1`. + * + * The `storage/internal/sync-area;1` class is implemented in Rust, and can be + * instantiated using `createInstance` and `Components.Constructor`. It's not + * a singleton, so creating a new instance will create a new `storage.sync` + * area, with its own database connection. It's useful for testing, but not + * meant to be used outside of this module. + * + * The `storage/sync;1` class is implemented in this file. It's a singleton, + * ensuring there's only one `storage.sync` area, with one database connection. + * The service implements `nsIInterfaceRequestor`, so callers can access the + * storage interface like this: + * + * let storageSyncArea = Cc["@mozilla.org/extensions/storage/sync;1"] + * .getService(Ci.nsIInterfaceRequestor) + * .getInterface(Ci.mozIExtensionStorageArea); + * + * ...And the Sync interface like this: + * + * let extensionStorageEngine = Cc["@mozilla.org/extensions/storage/sync;1"] + * .getService(Ci.nsIInterfaceRequestor) + * .getInterface(Ci.mozIBridgedSyncEngine); + * + * @class + */ +export function StorageSyncService() { + if (StorageSyncService._singleton) { + return StorageSyncService._singleton; + } + + let file = lazy.FileUtils.getFile("ProfD", ["storage-sync-v2.sqlite"]); + let kintoFile = lazy.FileUtils.getFile("ProfD", ["storage-sync.sqlite"]); + this._storageArea = new StorageSyncArea(file, kintoFile); + + // Register a blocker to close the storage connection on shutdown. + this._shutdownBound = () => this._shutdown(); + lazy.AsyncShutdown.profileChangeTeardown.addBlocker( + "StorageSyncService: shutdown", + this._shutdownBound + ); + + StorageSyncService._singleton = this; +} + +StorageSyncService._singleton = null; + +StorageSyncService.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), + + // Returns the storage and syncing interfaces. This just hands out a + // reference to the underlying storage area, with a quick check to make sure + // that callers are asking for the right interfaces. + getInterface(iid) { + if ( + iid.equals(Ci.mozIExtensionStorageArea) || + iid.equals(Ci.mozIBridgedSyncEngine) + ) { + return this._storageArea.QueryInterface(iid); + } + throw Components.Exception( + "This interface isn't implemented", + Cr.NS_ERROR_NO_INTERFACE + ); + }, + + // Tears down the storage area and lifts the blocker so that shutdown can + // continue. + async _shutdown() { + try { + await new Promise((resolve, reject) => { + this._storageArea.teardown({ + handleSuccess: resolve, + handleError(code, message) { + reject(Components.Exception(message, code)); + }, + }); + }); + } finally { + lazy.AsyncShutdown.profileChangeTeardown.removeBlocker( + this._shutdownBound + ); + } + }, +}; |