Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android' && verify
- Manifest: toolkit/components/extensions/test/xpcshell/webidl-api/xpcshell.toml
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
AddonTestUtils.init(this);
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"1",
"42"
);
add_task(async function setup() {
await AddonTestUtils.promiseStartupManager();
});
add_task(async function test_ext_context_does_have_webidl_bindings() {
await runExtensionAPITest("should have a browser global object", {
backgroundScript() {
const { browser, chrome } = self;
return {
hasExtensionAPI: !!browser,
hasExtensionMockAPI: !!browser?.mockExtensionAPI,
hasChromeCompatGlobal: !!chrome,
hasChromeMockAPI: !!chrome?.mockExtensionAPI,
};
},
assertResults({ testResult, testError }) {
Assert.deepEqual(testError, undefined);
Assert.deepEqual(
testResult,
{
hasExtensionAPI: true,
hasExtensionMockAPI: true,
hasChromeCompatGlobal: true,
hasChromeMockAPI: true,
},
"browser and browser.test WebIDL API bindings found"
);
},
});
});
add_task(async function test_propagated_extension_error() {
await runExtensionAPITest(
"should throw an extension error on ResultType::EXTENSION_ERROR",
{
backgroundScript({ testAsserts }) {
try {
const api = self.browser.mockExtensionAPI;
api.methodSyncWithReturn("arg0", 1, { value: "arg2" });
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new Error("Fake Extension Error"),
};
},
assertResults({ testError }) {
Assert.deepEqual(testError?.message, "Fake Extension Error");
},
}
);
});
add_task(async function test_system_errors_donot_leak() {
function assertResults({ testError }) {
ok(
testError?.message?.match(/An unexpected error occurred/),
`Got the general unexpected error as expected: ${testError?.message}`
);
}
function mockAPIRequestHandler() {
throw new Error("Fake handleAPIRequest exception");
}
const msg =
"should throw an unexpected error occurred if handleAPIRequest throws";
await runExtensionAPITest(`sync method ${msg}`, {
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodSyncWithReturn("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler,
assertResults,
});
await runExtensionAPITest(`async method ${msg}`, {
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodAsync("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler,
assertResults,
});
await runExtensionAPITest(`no return method ${msg}`, {
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodNoReturn("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler,
assertResults,
});
});
add_task(async function test_call_sync_function_result() {
await runExtensionAPITest(
"sync API methods should support structured clonable return values",
{
backgroundScript({ testAsserts }) {
const api = self.browser.mockExtensionAPI;
const results = {
string: api.methodSyncWithReturn("string-result"),
nested_prop: api.methodSyncWithReturn({
string: "123",
number: 123,
date: new Date("2020-09-20"),
map: new Map([
["a", 1],
["b", 2],
]),
}),
};
testAsserts.isInstanceOf(results.nested_prop.date, "Date");
testAsserts.isInstanceOf(results.nested_prop.map, "Map");
return results;
},
mockAPIRequestHandler(policy, request) {
if (request.apiName === "methodSyncWithReturn") {
// Return the first argument unmodified, which will be checked in the
// resultAssertFn above.
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: request.args[0],
};
}
throw new Error("Unexpected API method");
},
assertResults({ testResult, testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(testResult, {
string: "string-result",
nested_prop: {
string: "123",
number: 123,
date: new Date("2020-09-20"),
map: new Map([
["a", 1],
["b", 2],
]),
},
});
},
}
);
});
add_task(async function test_call_sync_fn_missing_return() {
await runExtensionAPITest(
"should throw an unexpected error occurred on missing return value",
{
backgroundScript() {
self.browser.mockExtensionAPI.methodSyncWithReturn("arg0");
},
mockAPIRequestHandler() {
return undefined;
},
assertResults({ testError }) {
ok(
testError?.message?.match(/An unexpected error occurred/),
`Got the general unexpected error as expected: ${testError?.message}`
);
},
}
);
});
add_task(async function test_call_async_throw_extension_error() {
await runExtensionAPITest(
"an async function can throw an error occurred for param validation errors",
{
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodAsync("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new Error("Fake Param Validation Error"),
};
},
assertResults({ testError }) {
Assert.deepEqual(testError?.message, "Fake Param Validation Error");
},
}
);
});
add_task(async function test_call_async_reject_error() {
await runExtensionAPITest(
"an async function rejected promise should propagate extension errors",
{
async backgroundScript({ testAsserts }) {
try {
await self.browser.mockExtensionAPI.methodAsync("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: Promise.reject(new Error("Fake API rejected error object")),
};
},
assertResults({ testError }) {
Assert.deepEqual(testError?.message, "Fake API rejected error object");
},
}
);
});
add_task(async function test_call_async_function_result() {
await runExtensionAPITest(
"async API methods should support structured clonable resolved values",
{
async backgroundScript({ testAsserts }) {
const api = self.browser.mockExtensionAPI;
const results = {
string: await api.methodAsync("string-result"),
nested_prop: await api.methodAsync({
string: "123",
number: 123,
date: new Date("2020-09-20"),
map: new Map([
["a", 1],
["b", 2],
]),
}),
};
testAsserts.isInstanceOf(results.nested_prop.date, "Date");
testAsserts.isInstanceOf(results.nested_prop.map, "Map");
return results;
},
mockAPIRequestHandler(policy, request) {
if (request.apiName === "methodAsync") {
// Return the first argument unmodified, which will be checked in the
// resultAssertFn above.
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: Promise.resolve(request.args[0]),
};
}
throw new Error("Unexpected API method");
},
assertResults({ testResult, testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(testResult, {
string: "string-result",
nested_prop: {
string: "123",
number: 123,
date: new Date("2020-09-20"),
map: new Map([
["a", 1],
["b", 2],
]),
},
});
},
}
);
});
add_task(async function test_call_no_return_throw_extension_error() {
await runExtensionAPITest(
"no return function call throw an error occurred for param validation errors",
{
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodNoReturn("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new Error("Fake Param Validation Error"),
};
},
assertResults({ testError }) {
Assert.deepEqual(testError?.message, "Fake Param Validation Error");
},
}
);
});
add_task(async function test_call_no_return_without_errors() {
await runExtensionAPITest(
"handleAPIHandler can return undefined on api calls to methods with no return",
{
backgroundScript() {
self.browser.mockExtensionAPI.methodNoReturn("arg0");
},
mockAPIRequestHandler() {
return undefined;
},
assertResults({ testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
}
);
});
add_task(async function test_async_method_chrome_compatible_callback() {
function mockAPIRequestHandler(policy, request) {
if (request.args[0] === "fake-async-method-failure") {
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: Promise.reject("this-should-not-be-passed-to-cb-as-parameter"),
};
}
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: Promise.resolve(request.args),
};
}
await runExtensionAPITest(
"async method should support an optional chrome-compatible callback",
{
mockAPIRequestHandler,
async backgroundScript({ testAsserts }) {
const api = self.browser.mockExtensionAPI;
const success_cb_params = await new Promise(resolve => {
const res = api.methodAsync(
{ prop: "fake-async-method-success" },
(...results) => {
resolve(results);
}
);
testAsserts.equal(res, undefined, "no promise should be returned");
});
const error_cb_params = await new Promise(resolve => {
const res = api.methodAsync(
"fake-async-method-failure",
(...results) => {
resolve(results);
}
);
testAsserts.equal(res, undefined, "no promise should be returned");
});
return { success_cb_params, error_cb_params };
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult,
{
success_cb_params: [[{ prop: "fake-async-method-success" }]],
error_cb_params: [],
},
"Got the expected results from the chrome compatible callbacks"
);
},
}
);
await runExtensionAPITest(
"async method with ambiguous args called with a chrome-compatible callback",
{
mockAPIRequestHandler,
async backgroundScript({ testAsserts }) {
const api = self.browser.mockExtensionAPI;
const success_cb_params = await new Promise(resolve => {
const res = api.methodAmbiguousArgsAsync(
"arg0",
{ prop: "arg1" },
3,
(...results) => {
resolve(results);
}
);
testAsserts.equal(res, undefined, "no promise should be returned");
});
const error_cb_params = await new Promise(resolve => {
const res = api.methodAmbiguousArgsAsync(
"fake-async-method-failure",
(...results) => {
resolve(results);
}
);
testAsserts.equal(res, undefined, "no promise should be returned");
});
return { success_cb_params, error_cb_params };
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult,
{
success_cb_params: [["arg0", { prop: "arg1" }, 3]],
error_cb_params: [],
},
"Got the expected results from the chrome compatible callbacks"
);
},
}
);
});
add_task(async function test_get_property() {
await runExtensionAPITest(
"getProperty API request does return a value synchrously",
{
backgroundScript() {
return self.browser.mockExtensionAPI.propertyAsString;
},
mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: "property-value",
};
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult,
"property-value",
"Got the expected result"
);
},
}
);
await runExtensionAPITest(
"getProperty API request can return an error object",
{
backgroundScript({ testAsserts }) {
const errObj = self.browser.mockExtensionAPI.propertyAsErrorObject;
testAsserts.isErrorInstance(errObj);
testAsserts.equal(errObj.message, "fake extension error");
},
mockAPIRequestHandler(policy, request) {
let savedFrame = request.calledSavedFrame;
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: ChromeUtils.createError("fake extension error", savedFrame),
};
},
assertResults({ testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
}
);
});