/* 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 ); } }, };