summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_contexts.js')
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contexts.js201
1 files changed, 201 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
new file mode 100644
index 0000000000..028f5b5638
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
@@ -0,0 +1,201 @@
+"use strict";
+
+const global = this;
+
+var { BaseContext, EventManager, EventEmitter } = ExtensionCommon;
+
+class FakeExtension extends EventEmitter {
+ constructor(id) {
+ super();
+ this.id = id;
+ }
+}
+
+class StubContext extends BaseContext {
+ constructor() {
+ let fakeExtension = new FakeExtension("test@web.extension");
+ super("testEnv", fakeExtension);
+ this.sandbox = Cu.Sandbox(global);
+ }
+
+ logActivity(type, name, data) {
+ // no-op required by subclass
+ }
+
+ get cloneScope() {
+ return this.sandbox;
+ }
+
+ get principal() {
+ return Cu.getObjectPrincipal(this.sandbox);
+ }
+}
+
+add_task(async function test_post_unload_promises() {
+ let context = new StubContext();
+
+ let fail = result => {
+ ok(false, `Unexpected callback: ${result}`);
+ };
+
+ // Make sure promises resolve normally prior to unload.
+ let promises = [
+ context.wrapPromise(Promise.resolve()),
+ context.wrapPromise(Promise.reject({ message: "" })).catch(() => {}),
+ ];
+
+ await Promise.all(promises);
+
+ // Make sure promises that resolve after unload do not trigger
+ // resolution handlers.
+
+ context.wrapPromise(Promise.resolve("resolved")).then(fail);
+
+ context.wrapPromise(Promise.reject({ message: "rejected" })).then(fail, fail);
+
+ 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.
+ await new Promise(resolve => setTimeout(resolve, 0));
+});
+
+add_task(async function test_post_unload_listeners() {
+ let context = new StubContext();
+
+ let fire;
+ let manager = new EventManager({
+ context,
+ name: "EventManager",
+ register: _fire => {
+ fire = () => {
+ _fire.async();
+ };
+ return () => {};
+ },
+ });
+
+ let fail = event => {
+ ok(false, `Unexpected event: ${event}`);
+ };
+
+ // Check that event listeners isn't called after it has been removed.
+ manager.addListener(fail);
+
+ let promise = new Promise(resolve => manager.addListener(resolve));
+
+ fire();
+
+ // The `fireSingleton` call ia dispatched asynchronously, so it won't
+ // have fired by this point. The `fail` listener that we remove now
+ // should not be called, even though the event has already been
+ // enqueued.
+ manager.removeListener(fail);
+
+ // Wait for the remaining listener to be called, which should always
+ // happen after the `fail` listener would normally be called.
+ await promise;
+
+ // Check that the event listener isn't called after the context has
+ // unloaded.
+ manager.addListener(fail);
+
+ // The `fire` callback always dispatches events
+ // asynchronously, so we need to test that any pending event callbacks
+ // aren't fired after the context unloads. We also need to test that
+ // any `fire` calls that happen *after* the context is unloaded also
+ // do not trigger callbacks.
+ fire();
+ Promise.resolve().then(fire);
+
+ 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.
+ await new Promise(resolve => setTimeout(resolve, 0));
+});
+
+class Context extends BaseContext {
+ constructor(principal) {
+ let fakeExtension = new FakeExtension("test@web.extension");
+ super("testEnv", fakeExtension);
+ Object.defineProperty(this, "principal", {
+ value: principal,
+ configurable: true,
+ });
+ this.sandbox = Cu.Sandbox(principal, { wantXrays: false });
+ }
+
+ logActivity(type, name, data) {
+ // no-op required by subclass
+ }
+
+ get cloneScope() {
+ return this.sandbox;
+ }
+}
+
+let ssm = Services.scriptSecurityManager;
+const PRINCIPAL1 = ssm.createContentPrincipalFromOrigin(
+ "http://www.example.org"
+);
+const PRINCIPAL2 = ssm.createContentPrincipalFromOrigin(
+ "http://www.somethingelse.org"
+);
+
+// Test that toJSON() works in the json sandbox
+add_task(async 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(async 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);
+ }, /Permission denied to access property "toJSON"/);
+});
+
+add_task(async function test_stringify_accessible() {
+ // Test that an accessible property from another global is included
+ let principal = Cu.getObjectPrincipal(Cu.Sandbox([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"
+ );
+});