diff options
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_contexts.js')
-rw-r--r-- | toolkit/components/extensions/test/xpcshell/test_ext_contexts.js | 198 |
1 files changed, 198 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..7c06fe33a5 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js @@ -0,0 +1,198 @@ +"use strict"; + +const global = this; + +const { ExtensionCommon } = ChromeUtils.import( + "resource://gre/modules/ExtensionCommon.jsm" +); + +var { BaseContext, EventManager } = ExtensionCommon; + +class StubContext extends BaseContext { + constructor() { + let fakeExtension = { id: "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 = { id: "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" + ); +}); |