gecko-dev/toolkit/components/extensions/parent/ext-storage.js

217 строки
6.7 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";
XPCOMUtils.defineLazyModuleGetters(this, {
AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.jsm",
extensionStorageSync: "resource://gre/modules/ExtensionStorageSync.jsm",
NativeManifests: "resource://gre/modules/NativeManifests.jsm",
});
var { ExtensionError } = ExtensionUtils;
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://bugzil.la/1323228.";
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);
},
},
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(),
},
};
}
};