summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/parent/ext-storage.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/components/extensions/parent/ext-storage.js228
1 files changed, 228 insertions, 0 deletions
diff --git a/toolkit/components/extensions/parent/ext-storage.js b/toolkit/components/extensions/parent/ext-storage.js
new file mode 100644
index 0000000000..ac75e9d6d1
--- /dev/null
+++ b/toolkit/components/extensions/parent/ext-storage.js
@@ -0,0 +1,228 @@
+/* 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";
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
+ ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
+ ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.jsm",
+ NativeManifests: "resource://gre/modules/NativeManifests.jsm",
+});
+
+var { ExtensionError } = ExtensionUtils;
+
+XPCOMUtils.defineLazyGetter(this, "extensionStorageSync", () => {
+ let url = Services.prefs.getBoolPref("webextensions.storage.sync.kinto")
+ ? "resource://gre/modules/ExtensionStorageSyncKinto.jsm"
+ : "resource://gre/modules/ExtensionStorageSync.jsm";
+
+ const { extensionStorageSync } = ChromeUtils.import(url, {});
+ return extensionStorageSync;
+});
+
+const enforceNoTemporaryAddon = extensionId => {
+ const EXCEPTION_MESSAGE =
+ "The storage API will not work with a temporary addon ID. " +
+ "Please add an explicit addon ID to your manifest. " +
+ "For more information see https://mzl.la/3lPk1aE.";
+ if (AddonManagerPrivate.isTemporaryInstallID(extensionId)) {
+ throw new ExtensionError(EXCEPTION_MESSAGE);
+ }
+};
+
+// WeakMap[extension -> Promise<SerializableMap?>]
+const managedStorage = new WeakMap();
+
+const lookupManagedStorage = async (extensionId, context) => {
+ if (Services.policies) {
+ let extensionPolicy = Services.policies.getExtensionPolicy(extensionId);
+ if (extensionPolicy) {
+ return ExtensionStorage._serializableMap(extensionPolicy);
+ }
+ }
+ let info = await NativeManifests.lookupManifest(
+ "storage",
+ extensionId,
+ context
+ );
+ if (info) {
+ return ExtensionStorage._serializableMap(info.manifest.data);
+ }
+ return null;
+};
+
+this.storage = class extends ExtensionAPI {
+ constructor(extension) {
+ super(extension);
+
+ const messageName = `Extension:StorageLocalOnChanged:${extension.uuid}`;
+ Services.ppmm.addMessageListener(messageName, this);
+ this.clearStorageChangedListener = () => {
+ Services.ppmm.removeMessageListener(messageName, this);
+ };
+ }
+
+ onShutdown() {
+ const { clearStorageChangedListener } = this;
+ this.clearStorageChangedListener = null;
+
+ if (clearStorageChangedListener) {
+ clearStorageChangedListener();
+ }
+ }
+
+ receiveMessage({ name, data }) {
+ if (name !== `Extension:StorageLocalOnChanged:${this.extension.uuid}`) {
+ return;
+ }
+
+ ExtensionStorageIDB.notifyListeners(this.extension.id, data);
+ }
+
+ getAPI(context) {
+ let { extension } = context;
+
+ return {
+ storage: {
+ local: {
+ async callMethodInParentProcess(method, args) {
+ const res = await ExtensionStorageIDB.selectBackend({ extension });
+ if (!res.backendEnabled) {
+ return ExtensionStorage[method](extension.id, ...args);
+ }
+
+ const persisted = extension.hasPermission("unlimitedStorage");
+ const db = await ExtensionStorageIDB.open(
+ res.storagePrincipal.deserialize(this, true),
+ persisted
+ );
+ try {
+ const changes = await db[method](...args);
+ if (changes) {
+ ExtensionStorageIDB.notifyListeners(extension.id, changes);
+ }
+ return changes;
+ } catch (err) {
+ const normalizedError = ExtensionStorageIDB.normalizeStorageError(
+ {
+ error: err,
+ extensionId: extension.id,
+ storageMethod: method,
+ }
+ ).message;
+ return Promise.reject({
+ message: String(normalizedError),
+ });
+ }
+ },
+ // Private storage.local JSONFile backend methods (used internally by the child
+ // ext-storage.js module).
+ JSONFileBackend: {
+ get(spec) {
+ return ExtensionStorage.get(extension.id, spec);
+ },
+ set(items) {
+ return ExtensionStorage.set(extension.id, items);
+ },
+ remove(keys) {
+ return ExtensionStorage.remove(extension.id, keys);
+ },
+ clear() {
+ return ExtensionStorage.clear(extension.id);
+ },
+ },
+ // Private storage.local IDB backend methods (used internally by the child ext-storage.js
+ // module).
+ IDBBackend: {
+ selectBackend() {
+ return ExtensionStorageIDB.selectBackend(context);
+ },
+ },
+ },
+
+ sync: {
+ get(spec) {
+ enforceNoTemporaryAddon(extension.id);
+ return extensionStorageSync.get(extension, spec, context);
+ },
+ set(items) {
+ enforceNoTemporaryAddon(extension.id);
+ return extensionStorageSync.set(extension, items, context);
+ },
+ remove(keys) {
+ enforceNoTemporaryAddon(extension.id);
+ return extensionStorageSync.remove(extension, keys, context);
+ },
+ clear() {
+ enforceNoTemporaryAddon(extension.id);
+ return extensionStorageSync.clear(extension, context);
+ },
+ getBytesInUse(keys) {
+ enforceNoTemporaryAddon(extension.id);
+ return extensionStorageSync.getBytesInUse(extension, keys, context);
+ },
+ },
+
+ managed: {
+ async get(keys) {
+ enforceNoTemporaryAddon(extension.id);
+ let lookup = managedStorage.get(extension);
+
+ if (!lookup) {
+ lookup = lookupManagedStorage(extension.id, context);
+ managedStorage.set(extension, lookup);
+ }
+
+ let data = await lookup;
+ if (!data) {
+ return Promise.reject({
+ message: "Managed storage manifest not found",
+ });
+ }
+ return ExtensionStorage._filterProperties(data, keys);
+ },
+ },
+
+ onChanged: new EventManager({
+ context,
+ name: "storage.onChanged",
+ register: fire => {
+ let listenerLocal = changes => {
+ fire.raw(changes, "local");
+ };
+ let listenerSync = changes => {
+ fire.async(changes, "sync");
+ };
+
+ ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
+ ExtensionStorageIDB.addOnChangedListener(
+ extension.id,
+ listenerLocal
+ );
+ extensionStorageSync.addOnChangedListener(
+ extension,
+ listenerSync,
+ context
+ );
+ return () => {
+ ExtensionStorage.removeOnChangedListener(
+ extension.id,
+ listenerLocal
+ );
+ ExtensionStorageIDB.removeOnChangedListener(
+ extension.id,
+ listenerLocal
+ );
+ extensionStorageSync.removeOnChangedListener(
+ extension,
+ listenerSync
+ );
+ };
+ },
+ }).api(),
+ },
+ };
+ }
+};