The base .eslintrc is essentially a merge of the root Toolkit .eslintrc and the devtools .eslintrc, with some minor changes to match our prevalent style. For the most enforces the coding styles that we've been using most consistently. There are a couple of significant differences, though: * The rule for opening brace alignment can only be applied globally, and doesn't make exceptions for top-level functions. I chose to turn it on, and change the brace style of existing top-level functions that violated it, since the rule seemed worth using, and that's the direction most Toolkit JS code has been headed anyway. * The rule for switch/case statements requires an added indentation level for case statements. Most of our switch statements did not use an extra level of indentation, and I initially wrote the rule to enforce that style, until I came across case statements that used blocks, and required the extra indentation level for sanity.
194 lines
5.3 KiB
JavaScript
194 lines
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();
|