gecko-dev/toolkit/components/extensions/ExtensionStorage.jsm

194 строки
5.3 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";
this.EXPORTED_SYMBOLS = ["ExtensionStorage"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
/* globals OS ExtensionStorage */
var Path = OS.Path;
var profileDir = OS.Constants.Path.profileDir;
this.ExtensionStorage = {
cache: new Map(),
listeners: new Map(),
extensionDir: Path.join(profileDir, "browser-extension-data"),
getExtensionDir(extensionId) {
return Path.join(this.extensionDir, extensionId);
},
getStorageFile(extensionId) {
return Path.join(this.extensionDir, extensionId, "storage.js");
},
read(extensionId) {
if (this.cache.has(extensionId)) {
return this.cache.get(extensionId);
}
let path = this.getStorageFile(extensionId);
let decoder = new TextDecoder();
let promise = OS.File.read(path);
promise = promise.then(array => {
return JSON.parse(decoder.decode(array));
}).catch(() => {
Cu.reportError("Unable to parse JSON data for extension storage.");
return {};
});
this.cache.set(extensionId, promise);
return promise;
},
write(extensionId) {
let promise = this.read(extensionId).then(extData => {
let encoder = new TextEncoder();
let array = encoder.encode(JSON.stringify(extData));
let path = this.getStorageFile(extensionId);
OS.File.makeDir(this.getExtensionDir(extensionId), {ignoreExisting: true, from: profileDir});
let promise = OS.File.writeAtomic(path, array);
return promise;
}).catch(() => {
// Make sure this promise is never rejected.
Cu.reportError("Unable to write JSON data for extension storage.");
});
AsyncShutdown.profileBeforeChange.addBlocker(
"ExtensionStorage: Finish writing extension data",
promise);
return promise.then(() => {
AsyncShutdown.profileBeforeChange.removeBlocker(promise);
});
},
set(extensionId, items) {
return this.read(extensionId).then(extData => {
let changes = {};
for (let prop in items) {
changes[prop] = {oldValue: extData[prop], newValue: items[prop]};
extData[prop] = items[prop];
}
this.notifyListeners(extensionId, changes);
return this.write(extensionId);
});
},
remove(extensionId, items) {
return this.read(extensionId).then(extData => {
let changes = {};
if (Array.isArray(items)) {
for (let prop of items) {
changes[prop] = {oldValue: extData[prop]};
delete extData[prop];
}
} else {
let prop = items;
changes[prop] = {oldValue: extData[prop]};
delete extData[prop];
}
this.notifyListeners(extensionId, changes);
return this.write(extensionId);
});
},
clear(extensionId) {
return this.read(extensionId).then(extData => {
let changes = {};
if (extData) {
for (let prop of Object.keys(extData)) {
changes[prop] = {oldValue: extData[prop]};
delete extData[prop];
}
}
this.notifyListeners(extensionId, changes);
return this.write(extensionId);
});
},
get(extensionId, keys) {
return this.read(extensionId).then(extData => {
let result = {};
if (keys === null) {
Object.assign(result, extData);
} else if (typeof(keys) == "object" && !Array.isArray(keys)) {
for (let prop in keys) {
if (prop in extData) {
result[prop] = extData[prop];
} else {
result[prop] = keys[prop];
}
}
} else if (typeof(keys) == "string") {
let prop = keys;
if (prop in extData) {
result[prop] = extData[prop];
}
} else {
for (let prop of keys) {
if (prop in extData) {
result[prop] = extData[prop];
}
}
}
return result;
});
},
addOnChangedListener(extensionId, listener) {
let listeners = this.listeners.get(extensionId) || new Set();
listeners.add(listener);
this.listeners.set(extensionId, listeners);
},
removeOnChangedListener(extensionId, listener) {
let listeners = this.listeners.get(extensionId);
listeners.delete(listener);
},
notifyListeners(extensionId, changes) {
let listeners = this.listeners.get(extensionId);
if (listeners) {
for (let listener of listeners) {
listener(changes);
}
}
},
init() {
Services.obs.addObserver(this, "extension-invalidate-storage-cache", false);
Services.obs.addObserver(this, "xpcom-shutdown", false);
},
observe(subject, topic, data) {
if (topic == "xpcom-shutdown") {
Services.obs.removeObserver(this, "extension-invalidate-storage-cache");
Services.obs.removeObserver(this, "xpcom-shutdown");
} else if (topic == "extension-invalidate-storage-cache") {
this.cache.clear();
}
},
};
ExtensionStorage.init();