"use strict"; /* globals browser */ const { AddonSettings } = ChromeUtils.importESModule( "resource://gre/modules/addons/AddonSettings.sys.mjs" ); AddonTestUtils.init(this); AddonTestUtils.overrideCertDB(); AddonTestUtils.createAppInfo( "xpcshell@tests.mozilla.org", "XPCShell", "1", "42" ); add_task(async function setup() { AddonTestUtils.overrideCertDB(); await ExtensionTestUtils.startAddonManager(); }); let fooExperimentAPIs = { foo: { schema: "schema.json", parent: { scopes: ["addon_parent"], script: "parent.js", paths: [["experiments", "foo", "parent"]], }, child: { scopes: ["addon_child"], script: "child.js", paths: [ ["experiments", "foo", "child"], ["experiments", "foo", "onChildEvent"], ], }, }, }; let fooExperimentFiles = { "schema.json": JSON.stringify([ { namespace: "experiments.foo", types: [ { id: "Meh", type: "object", properties: {}, }, ], functions: [ { name: "parent", type: "function", async: true, parameters: [], }, { name: "child", type: "function", parameters: [], returns: { type: "string" }, }, ], events: [ { name: "onChildEvent", type: "function", parameters: [], }, ], }, ]), /* globals ExtensionAPI */ "parent.js": () => { this.foo = class extends ExtensionAPI { getAPI(context) { return { experiments: { foo: { parent() { return Promise.resolve("parent"); }, }, }, }; } }; }, "child.js": () => { this.foo = class extends ExtensionAPI { getAPI(context) { const EventManagerWithAssertions = class extends ExtensionCommon.EventManager { constructor(...args) { super(...args); this.assertResetOnIdleOnEvent(); } assertResetOnIdleOnEvent() { const expectResetIdleOnEventFalse = this.context.extension.persistentBackground; if (expectResetIdleOnEventFalse && this.resetIdleOnEvent) { const details = { eventManagerName: this.name, resetIdleOnEvent: this.resetIdleOnEvent, envType: this.context.envType, viewType: this.context.viewType, isBackgroundContext: this.context.isBackgroundContext, persistentBackground: this.context.extension.persistentBackground, }; throw new Error( `EventManagerWithAssertions: resetIdleOnEvent should be forcefully set to false - ${JSON.stringify( details )}` ); } } }; return { experiments: { foo: { child() { return "child"; }, onChildEvent: new EventManagerWithAssertions({ context, name: `experiments.foo.onChildEvent`, register: fire => { return () => {}; }, }).api(), }, }, }; } }; }, }; async function testFooExperiment() { browser.test.assertEq( "object", typeof browser.experiments, "typeof browser.experiments" ); browser.test.assertEq( "object", typeof browser.experiments.foo, "typeof browser.experiments.foo" ); browser.test.assertEq( "function", typeof browser.experiments.foo.child, "typeof browser.experiments.foo.child" ); browser.test.assertEq( "function", typeof browser.experiments.foo.parent, "typeof browser.experiments.foo.parent" ); browser.test.assertEq( "child", browser.experiments.foo.child(), "foo.child()" ); browser.test.assertEq( "parent", await browser.experiments.foo.parent(), "await foo.parent()" ); } async function testFooFailExperiment() { browser.test.assertEq( "object", typeof browser.experiments, "typeof browser.experiments" ); browser.test.assertEq( "undefined", typeof browser.experiments.foo, "typeof browser.experiments.foo" ); } add_task(async function test_bundled_experiments() { let testCases = [ { isSystem: true, temporarilyInstalled: true, shouldHaveExperiments: true }, { isSystem: true, temporarilyInstalled: false, shouldHaveExperiments: true, }, { isPrivileged: true, temporarilyInstalled: true, shouldHaveExperiments: true, }, { isPrivileged: true, temporarilyInstalled: false, shouldHaveExperiments: true, }, { isPrivileged: false, temporarilyInstalled: true, shouldHaveExperiments: AddonSettings.EXPERIMENTS_ENABLED, }, { isPrivileged: false, temporarilyInstalled: false, shouldHaveExperiments: AppConstants.MOZ_APP_NAME == "thunderbird", }, ]; async function background(shouldHaveExperiments) { if (shouldHaveExperiments) { await testFooExperiment(); } else { await testFooFailExperiment(); } browser.test.notifyPass("background.experiments.foo"); } for (let testCase of testCases) { let extension = ExtensionTestUtils.loadExtension({ isPrivileged: testCase.isPrivileged, isSystem: testCase.isSystem, temporarilyInstalled: testCase.temporarilyInstalled, manifest: { experiment_apis: fooExperimentAPIs, }, background: ` ${testFooExperiment} ${testFooFailExperiment} (${background})(${testCase.shouldHaveExperiments}); `, files: fooExperimentFiles, }); if (testCase.temporarilyInstalled && !testCase.shouldHaveExperiments) { ExtensionTestUtils.failOnSchemaWarnings(false); await Assert.rejects( extension.startup(), /Using 'experiment_apis' requires a privileged add-on/, "startup failed without experimental api access" ); ExtensionTestUtils.failOnSchemaWarnings(true); } else { await extension.startup(); await extension.awaitFinish("background.experiments.foo"); await extension.unload(); } } }); add_task(async function test_unbundled_experiments() { async function background() { await testFooExperiment(); browser.test.assertEq( "object", typeof browser.experiments.crunk, "typeof browser.experiments.crunk" ); browser.test.assertEq( "function", typeof browser.experiments.crunk.child, "typeof browser.experiments.crunk.child" ); browser.test.assertEq( "function", typeof browser.experiments.crunk.parent, "typeof browser.experiments.crunk.parent" ); browser.test.assertEq( "crunk-child", browser.experiments.crunk.child(), "crunk.child()" ); browser.test.assertEq( "crunk-parent", await browser.experiments.crunk.parent(), "await crunk.parent()" ); browser.test.notifyPass("background.experiments.crunk"); } let extension = ExtensionTestUtils.loadExtension({ isPrivileged: true, manifest: { experiment_apis: fooExperimentAPIs, permissions: ["experiments.crunk"], }, background: ` ${testFooExperiment} (${background})(); `, files: fooExperimentFiles, }); let apiExtension = ExtensionTestUtils.loadExtension({ isPrivileged: true, manifest: { browser_specific_settings: { gecko: { id: "crunk@experiments.addons.mozilla.org" }, }, experiment_apis: { crunk: { schema: "schema.json", parent: { scopes: ["addon_parent"], script: "parent.js", paths: [["experiments", "crunk", "parent"]], }, child: { scopes: ["addon_child"], script: "child.js", paths: [["experiments", "crunk", "child"]], }, }, }, }, files: { "schema.json": JSON.stringify([ { namespace: "experiments.crunk", types: [ { id: "Meh", type: "object", properties: {}, }, ], functions: [ { name: "parent", type: "function", async: true, parameters: [], }, { name: "child", type: "function", parameters: [], returns: { type: "string" }, }, ], }, ]), "parent.js": () => { this.crunk = class extends ExtensionAPI { getAPI(context) { return { experiments: { crunk: { parent() { return Promise.resolve("crunk-parent"); }, }, }, }; } }; }, "child.js": () => { this.crunk = class extends ExtensionAPI { getAPI(context) { return { experiments: { crunk: { child() { return "crunk-child"; }, }, }, }; } }; }, }, }); await apiExtension.startup(); await extension.startup(); await extension.awaitFinish("background.experiments.crunk"); await extension.unload(); await apiExtension.unload(); }); add_task(async function test_eventpage_with_experiments_resetOnIdleAssert() { async function event_page() { browser.test.log("EventPage startup"); // We expect EventManagerWithAssertions instance to throw // here if the resetIdleOnEvent didn't got forcefully // set to false for the EventManager instantiated in // the child process. browser.experiments.foo.onChildEvent.addListener(() => {}); browser.test.sendMessage("eventpage:ready"); } const extension = ExtensionTestUtils.loadExtension({ isPrivileged: true, manifest: { experiment_apis: fooExperimentAPIs, background: { persistent: false }, }, background: event_page, files: fooExperimentFiles, }); await extension.startup(); await extension.awaitMessage("eventpage:ready"); await extension.unload(); });