"use strict"; const global = this; Cu.import("resource://gre/modules/Timer.jsm"); Cu.import("resource://gre/modules/ExtensionCommon.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); var { BaseContext, } = ExtensionCommon; var { EventManager, SingletonEventManager, } = ExtensionUtils; class StubContext extends BaseContext { constructor() { let fakeExtension = {id: "test@web.extension"}; super("testEnv", fakeExtension); this.sandbox = Cu.Sandbox(global); } get cloneScope() { return this.sandbox; } } add_task(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(() => {}), ]; yield 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. yield new Promise(resolve => setTimeout(resolve, 0)); }); add_task(function* test_post_unload_listeners() { let context = new StubContext(); let fireEvent; let onEvent = new EventManager(context, "onEvent", fire => { fireEvent = fire; return () => {}; }); let fireSingleton; let onSingleton = new SingletonEventManager(context, "onSingleton", callback => { fireSingleton = () => { Promise.resolve().then(callback); }; return () => {}; }); let fail = event => { ok(false, `Unexpected event: ${event}`); }; // Check that event listeners aren't called after they've been removed. onEvent.addListener(fail); onSingleton.addListener(fail); let promises = [ new Promise(resolve => onEvent.addListener(resolve)), new Promise(resolve => onSingleton.addListener(resolve)), ]; fireEvent("onEvent"); fireSingleton("onSingleton"); // Both `fireEvent` calls are dispatched asynchronously, so they won't // have fired by this point. The `fail` listeners that we remove now // should not be called, even though the events have already been // enqueued. onEvent.removeListener(fail); onSingleton.removeListener(fail); // Wait for the remaining listeners to be called, which should always // happen after the `fail` listeners would normally be called. yield Promise.all(promises); // Check that event listeners aren't called after the context has // unloaded. onEvent.addListener(fail); onSingleton.addListener(fail); // The EventManager `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. fireEvent("onEvent"); Promise.resolve("onEvent").then(fireEvent); fireSingleton("onSingleton"); Promise.resolve("onSingleton").then(fireSingleton); 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) { let fakeExtension = {id: "test@web.extension"}; super("testEnv", fakeExtension); Object.defineProperty(this, "principal", { value: principal, configurable: true, }); this.sandbox = Cu.Sandbox(principal, {wantXrays: false}); } 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 = 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"); });