Bug 1274708 Add BaseContext.jsonStringify() r?kmag
MozReview-Commit-ID: E4F1e8hDA5a
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -143,16 +143,17 @@ let gContextId = 0;
class BaseContext {
constructor(extensionId) {
this.onClose = new Set();
this.checkedLastError = false;
this._lastError = null;
this.contextId = ++gContextId;
this.unloaded = false;
this.extensionId = extensionId;
+ this.jsonSandbox = null;
}
get cloneScope() {
throw new Error("Not implemented");
}
get principal() {
throw new Error("Not implemented");
@@ -191,16 +192,34 @@ class BaseContext {
try {
ssm.checkLoadURIStrWithPrincipal(this.principal, url, flags);
} catch (e) {
return false;
}
return true;
}
+ /**
+ * Safely call JSON.stringify() on an object that comes from an
+ * extension.
+ *
+ * @param {array<any>} args Arguments for JSON.stringify()
+ * @returns {string} The stringified representation of obj
+ */
+ jsonStringify(...args) {
+ if (!this.jsonSandbox) {
+ this.jsonSandbox = Cu.Sandbox(this.principal, {
+ sameZoneAs: this.cloneScope,
+ wantXrays: false,
+ });
+ }
+
+ return Cu.waiveXrays(this.jsonSandbox.JSON).stringify(...args);
+ }
+
callOnClose(obj) {
this.onClose.add(obj);
}
forgetOnClose(obj) {
this.onClose.delete(obj);
}
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
@@ -9,17 +9,17 @@ var {
BaseContext,
EventManager,
SingletonEventManager,
} = ExtensionUtils;
class StubContext extends BaseContext {
constructor() {
super();
- this.sandbox = new Cu.Sandbox(global);
+ this.sandbox = Cu.Sandbox(global);
}
get cloneScope() {
return this. sandbox;
}
get extension() {
return {id: "test@web.extension"};
@@ -122,8 +122,67 @@ add_task(function* test_post_unload_list
context.unload();
// The `setTimeout` ensures that we return to the event loop after
// promise resolution, which means we're guaranteed to return after
// any micro-tasks that get enqueued by the resolution handlers above.
yield new Promise(resolve => setTimeout(resolve, 0));
});
+
+class Context extends BaseContext {
+ constructor(principal) {
+ super();
+ Object.defineProperty(this, "principal", {
+ value: principal,
+ configurable: true,
+ });
+ this.sandbox = Cu.Sandbox(principal, {wantXrays: false});
+ this.extension = {id: "test@web.extension"};
+ }
+
+ get cloneScope() {
+ return this.sandbox;
+ }
+}
+
+let ssm = Services.scriptSecurityManager;
+const PRINCIPAL1 = ssm.createCodebasePrincipalFromOrigin("http://www.example.org");
+const PRINCIPAL2 = ssm.createCodebasePrincipalFromOrigin("http://www.somethingelse.org");
+
+// Test that toJSON() works in the json sandbox
+add_task(function* test_stringify_toJSON() {
+ let context = new Context(PRINCIPAL1);
+ let obj = Cu.evalInSandbox("({hidden: true, toJSON() { return {visible: true}; } })", context.sandbox);
+
+ let stringified = context.jsonStringify(obj);
+ let expected = JSON.stringify({visible: true});
+ equal(stringified, expected, "Stringified object with toJSON() method is as expected");
+});
+
+// Test that stringifying in inaccessible property throws
+add_task(function* test_stringify_inaccessible() {
+ let context = new Context(PRINCIPAL1);
+ let sandbox = context.sandbox;
+ let sandbox2 = Cu.Sandbox(PRINCIPAL2);
+
+ Cu.waiveXrays(sandbox).subobj = Cu.evalInSandbox("({ subobject: true })", sandbox2);
+ let obj = Cu.evalInSandbox("({ local: true, nested: subobj })", sandbox);
+ Assert.throws(() => {
+ context.jsonStringify(obj);
+ });
+});
+
+add_task(function* test_stringify_accessible() {
+ // Test that an accessible property from another global is included
+ let principal = ssm.createExpandedPrincipal([PRINCIPAL1, PRINCIPAL2]);
+ let context = new Context(principal);
+ let sandbox = context.sandbox;
+ let sandbox2 = Cu.Sandbox(PRINCIPAL2);
+
+ Cu.waiveXrays(sandbox).subobj = Cu.evalInSandbox("({ subobject: true })", sandbox2);
+ let obj = Cu.evalInSandbox("({ local: true, nested: subobj })", sandbox);
+ let stringified = context.jsonStringify(obj);
+
+ let expected = JSON.stringify({local: true, nested: {subobject: true}});
+ equal(stringified, expected, "Stringified object with accessible property is as expected");
+});
+