/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ /* * Tests the Integration.jsm module. */ "use strict"; ChromeUtils.import("resource://gre/modules/Integration.jsm", this); const TestIntegration = { value: "value", get valueFromThis() { return this.value; }, get property() { return this._property; }, set property(value) { this._property = value; }, method(argument) { this.methodArgument = argument; return "method" + argument; }, async asyncMethod(argument) { this.asyncMethodArgument = argument; return "asyncMethod" + argument; }, }; let overrideFn = base => ({ value: "overridden-value", get property() { return "overridden-" + base.__lookupGetter__("property").call(this); }, set property(value) { base.__lookupSetter__("property").call(this, "overridden-" + value); }, method() { return "overridden-" + base.method.apply(this, arguments); }, async asyncMethod() { return "overridden-" + (await base.asyncMethod.apply(this, arguments)); }, }); let superOverrideFn = base => ({ __proto__: base, value: "overridden-value", get property() { return "overridden-" + super.property; }, set property(value) { super.property = "overridden-" + value; }, method() { return "overridden-" + super.method(...arguments); }, async asyncMethod() { // We cannot use the "super" keyword in methods defined using "Task.async". return "overridden-" + (await base.asyncMethod.apply(this, arguments)); }, }); /** * Fails the test if the results of method invocations on the combined object * don't match the expected results based on how many overrides are registered. * * @param combined * The combined object based on the TestIntegration root. * @param overridesCount * Zero if the root object is not overridden, or a higher value to test * the presence of one or more integration overrides. */ async function assertCombinedResults(combined, overridesCount) { let expectedValue = overridesCount > 0 ? "overridden-value" : "value"; let prefix = "overridden-".repeat(overridesCount); Assert.equal(combined.value, expectedValue); Assert.equal(combined.valueFromThis, expectedValue); combined.property = "property"; Assert.equal(combined.property, prefix.repeat(2) + "property"); combined.methodArgument = ""; Assert.equal(combined.method("-argument"), prefix + "method-argument"); Assert.equal(combined.methodArgument, "-argument"); combined.asyncMethodArgument = ""; Assert.equal( await combined.asyncMethod("-argument"), prefix + "asyncMethod-argument" ); Assert.equal(combined.asyncMethodArgument, "-argument"); } /** * Fails the test if the results of method invocations on the combined object * for the "testModule" integration point don't match the expected results based * on how many overrides are registered. * * @param overridesCount * Zero if the root object is not overridden, or a higher value to test * the presence of one or more integration overrides. */ async function assertCurrentCombinedResults(overridesCount) { let combined = Integration.testModule.getCombined(TestIntegration); await assertCombinedResults(combined, overridesCount); } /** * Checks the initial state with no integration override functions registered. */ add_task(async function test_base() { await assertCurrentCombinedResults(0); }); /** * Registers and unregisters an integration override function. */ add_task(async function test_override() { Integration.testModule.register(overrideFn); await assertCurrentCombinedResults(1); // Registering the same function more than once has no effect. Integration.testModule.register(overrideFn); await assertCurrentCombinedResults(1); Integration.testModule.unregister(overrideFn); await assertCurrentCombinedResults(0); }); /** * Registers and unregisters more than one integration override function, of * which one uses the prototype and the "super" keyword to access the base. */ add_task(async function test_override_super_multiple() { Integration.testModule.register(overrideFn); Integration.testModule.register(superOverrideFn); await assertCurrentCombinedResults(2); Integration.testModule.unregister(overrideFn); await assertCurrentCombinedResults(1); Integration.testModule.unregister(superOverrideFn); await assertCurrentCombinedResults(0); }); /** * Registers an integration override function that throws an exception, and * ensures that this does not block other functions from being registered. */ add_task(async function test_override_error() { let errorOverrideFn = base => { throw new Error("Expected error."); }; Integration.testModule.register(errorOverrideFn); Integration.testModule.register(overrideFn); await assertCurrentCombinedResults(1); Integration.testModule.unregister(errorOverrideFn); Integration.testModule.unregister(overrideFn); await assertCurrentCombinedResults(0); }); /** * Checks that state saved using the "this" reference is preserved as a shallow * copy when registering new integration override functions. */ add_task(async function test_state_preserved() { let valueObject = { toString: () => "toString" }; let combined = Integration.testModule.getCombined(TestIntegration); combined.property = valueObject; Assert.ok(combined.property === valueObject); Integration.testModule.register(overrideFn); combined = Integration.testModule.getCombined(TestIntegration); Assert.equal(combined.property, "overridden-toString"); Integration.testModule.unregister(overrideFn); combined = Integration.testModule.getCombined(TestIntegration); Assert.ok(combined.property === valueObject); }); /** * Checks that the combined integration objects cannot be used with XPCOM. * * This is limited by the fact that interfaces with the "[function]" annotation, * for example nsIObserver, do not call the QueryInterface implementation. */ add_task(async function test_xpcom_throws() { let combined = Integration.testModule.getCombined(TestIntegration); // This calls QueryInterface because it looks for nsISupportsWeakReference. Assert.throws( () => Services.obs.addObserver(combined, "test-topic", true), /NS_NOINTERFACE/ ); }); /** * Checks that getters defined by defineModuleGetter are able to retrieve the * latest version of the combined integration object. */ add_task(async function test_defineModuleGetter() { let objectForGetters = {}; // Test with and without the optional "symbol" parameter. Integration.testModule.defineModuleGetter( objectForGetters, "TestIntegration", "resource://testing-common/TestIntegration.jsm" ); Integration.testModule.defineModuleGetter( objectForGetters, "integration", "resource://testing-common/TestIntegration.jsm", "TestIntegration" ); Integration.testModule.register(overrideFn); await assertCombinedResults(objectForGetters.integration, 1); await assertCombinedResults(objectForGetters.TestIntegration, 1); Integration.testModule.unregister(overrideFn); await assertCombinedResults(objectForGetters.integration, 0); await assertCombinedResults(objectForGetters.TestIntegration, 0); });