mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-26 19:55:39 +00:00
f87ef7bd44
MozReview-Commit-ID: rqUFJw7Wsb --HG-- extra : rebase_source : cdda5d87fe33bdb9dacc1846fd67f3cb4154f230
155 lines
5.5 KiB
JavaScript
155 lines
5.5 KiB
JavaScript
"use strict";
|
|
|
|
/* import-globals-from ext-c-toolkit.js */
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
|
|
"resource://gre/modules/ExtensionStorage.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
|
|
"resource://gre/modules/TelemetryStopwatch.jsm");
|
|
|
|
var {
|
|
ExtensionError,
|
|
} = ExtensionUtils;
|
|
|
|
const storageGetHistogram = "WEBEXT_STORAGE_LOCAL_GET_MS";
|
|
const storageSetHistogram = "WEBEXT_STORAGE_LOCAL_SET_MS";
|
|
|
|
this.storage = class extends ExtensionAPI {
|
|
getAPI(context) {
|
|
/**
|
|
* Serializes the given storage items for transporting to the parent
|
|
* process.
|
|
*
|
|
* @param {Array<string>|object} items
|
|
* The items to serialize. If an object is provided, its
|
|
* values are serialized to StructuredCloneHolder objects.
|
|
* Otherwise, it is returned as-is.
|
|
* @returns {Array<string>|object}
|
|
*/
|
|
function serialize(items) {
|
|
if (items && typeof items === "object" && !Array.isArray(items)) {
|
|
let result = {};
|
|
for (let [key, value] of Object.entries(items)) {
|
|
try {
|
|
result[key] = new StructuredCloneHolder(value, context.cloneScope);
|
|
} catch (e) {
|
|
throw new ExtensionError(String(e));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
return items;
|
|
}
|
|
|
|
/**
|
|
* Deserializes the given storage items from the parent process into
|
|
* the extension context.
|
|
*
|
|
* @param {object} items
|
|
* The items to deserialize. Any property of the object which
|
|
* is a StructuredCloneHolder instance is deserialized into
|
|
* the extension scope. Any other object is cloned into the
|
|
* extension scope directly.
|
|
* @returns {object}
|
|
*/
|
|
function deserialize(items) {
|
|
let result = new context.cloneScope.Object();
|
|
for (let [key, value] of Object.entries(items)) {
|
|
if (value && typeof value === "object" && Cu.getClassName(value, true) === "StructuredCloneHolder") {
|
|
value = value.deserialize(context.cloneScope);
|
|
} else {
|
|
value = Cu.cloneInto(value, context.cloneScope);
|
|
}
|
|
result[key] = value;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function sanitize(items) {
|
|
// The schema validator already takes care of arrays (which are only allowed
|
|
// to contain strings). Strings and null are safe values.
|
|
if (typeof items != "object" || items === null || Array.isArray(items)) {
|
|
return items;
|
|
}
|
|
// If we got here, then `items` is an object generated by `ObjectType`'s
|
|
// `normalize` method from Schemas.jsm. The object returned by `normalize`
|
|
// lives in this compartment, while the values live in compartment of
|
|
// `context.contentWindow`. The `sanitize` method runs with the principal
|
|
// of `context`, so we cannot just use `ExtensionStorage.sanitize` because
|
|
// it is not allowed to access properties of `items`.
|
|
// So we enumerate all properties and sanitize each value individually.
|
|
let sanitized = {};
|
|
for (let [key, value] of Object.entries(items)) {
|
|
sanitized[key] = ExtensionStorage.sanitize(value, context);
|
|
}
|
|
return sanitized;
|
|
}
|
|
|
|
return {
|
|
storage: {
|
|
local: {
|
|
get: async function(keys) {
|
|
const stopwatchKey = {};
|
|
TelemetryStopwatch.start(storageGetHistogram, stopwatchKey);
|
|
try {
|
|
let result = await context.childManager.callParentAsyncFunction("storage.local.get", [
|
|
serialize(keys),
|
|
], null, {noClone: true}).then(deserialize);
|
|
TelemetryStopwatch.finish(storageGetHistogram, stopwatchKey);
|
|
return result;
|
|
} catch (e) {
|
|
TelemetryStopwatch.cancel(storageGetHistogram, stopwatchKey);
|
|
throw e;
|
|
}
|
|
},
|
|
set: async function(items) {
|
|
const stopwatchKey = {};
|
|
TelemetryStopwatch.start(storageSetHistogram, stopwatchKey);
|
|
try {
|
|
let result = await context.childManager.callParentAsyncFunction("storage.local.set", [
|
|
serialize(items),
|
|
], null, {noClone: true});
|
|
TelemetryStopwatch.finish(storageSetHistogram, stopwatchKey);
|
|
return result;
|
|
} catch (e) {
|
|
TelemetryStopwatch.cancel(storageSetHistogram, stopwatchKey);
|
|
throw e;
|
|
}
|
|
},
|
|
},
|
|
|
|
sync: {
|
|
get: function(keys) {
|
|
keys = sanitize(keys);
|
|
return context.childManager.callParentAsyncFunction("storage.sync.get", [
|
|
keys,
|
|
]);
|
|
},
|
|
set: function(items) {
|
|
items = sanitize(items);
|
|
return context.childManager.callParentAsyncFunction("storage.sync.set", [
|
|
items,
|
|
]);
|
|
},
|
|
},
|
|
|
|
onChanged: new EventManager(context, "storage.onChanged", fire => {
|
|
let onChanged = (data, area) => {
|
|
let changes = new context.cloneScope.Object();
|
|
for (let [key, value] of Object.entries(data)) {
|
|
changes[key] = deserialize(value);
|
|
}
|
|
fire.raw(changes, area);
|
|
};
|
|
|
|
let parent = context.childManager.getParentEvent("storage.onChanged");
|
|
parent.addListener(onChanged);
|
|
return () => {
|
|
parent.removeListener(onChanged);
|
|
};
|
|
}).api(),
|
|
},
|
|
};
|
|
}
|
|
};
|