summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/mochitest/test_ext_all_apis.js')
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_all_apis.js245
1 files changed, 245 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
new file mode 100644
index 0000000000..4c82a4575c
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
@@ -0,0 +1,245 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Tests whether not too many APIs are visible by default.
+// This file is used by test_ext_all_apis.html in browser/ and mobile/android/,
+// which may modify the following variables to add or remove expected APIs.
+/* globals expectedContentApisTargetSpecific */
+/* globals expectedBackgroundApisTargetSpecific */
+
+// Generates a list of expectations.
+function generateExpectations(list) {
+ return list
+ .reduce((allApis, path) => {
+ return allApis.concat(`browser.${path}`, `chrome.${path}`);
+ }, [])
+ .sort();
+}
+
+let expectedCommonApis = [
+ "extension.getURL",
+ "extension.inIncognitoContext",
+ "extension.lastError",
+ "i18n.detectLanguage",
+ "i18n.getAcceptLanguages",
+ "i18n.getMessage",
+ "i18n.getUILanguage",
+ "runtime.OnInstalledReason",
+ "runtime.OnRestartRequiredReason",
+ "runtime.PlatformArch",
+ "runtime.PlatformOs",
+ "runtime.RequestUpdateCheckStatus",
+ "runtime.getManifest",
+ "runtime.connect",
+ "runtime.getFrameId",
+ "runtime.getURL",
+ "runtime.id",
+ "runtime.lastError",
+ "runtime.onConnect",
+ "runtime.onMessage",
+ "runtime.sendMessage",
+ // browser.test is only available in xpcshell or when
+ // Cu.isInAutomation is true.
+ "test.assertDeepEq",
+ "test.assertEq",
+ "test.assertFalse",
+ "test.assertRejects",
+ "test.assertThrows",
+ "test.assertTrue",
+ "test.fail",
+ "test.log",
+ "test.notifyFail",
+ "test.notifyPass",
+ "test.onMessage",
+ "test.sendMessage",
+ "test.succeed",
+ "test.withHandlingUserInput",
+];
+
+let expectedContentApis = [
+ ...expectedCommonApis,
+ ...expectedContentApisTargetSpecific,
+];
+
+let expectedBackgroundApis = [
+ ...expectedCommonApis,
+ ...expectedBackgroundApisTargetSpecific,
+ "contentScripts.register",
+ "experiments.APIChildScope",
+ "experiments.APIEvent",
+ "experiments.APIParentScope",
+ "extension.ViewType",
+ "extension.getBackgroundPage",
+ "extension.getViews",
+ "extension.isAllowedFileSchemeAccess",
+ "extension.isAllowedIncognitoAccess",
+ // Note: extensionTypes is not visible in Chrome.
+ "extensionTypes.CSSOrigin",
+ "extensionTypes.ImageFormat",
+ "extensionTypes.RunAt",
+ "management.ExtensionDisabledReason",
+ "management.ExtensionInstallType",
+ "management.ExtensionType",
+ "management.getSelf",
+ "management.uninstallSelf",
+ "permissions.getAll",
+ "permissions.contains",
+ "permissions.request",
+ "permissions.remove",
+ "permissions.onAdded",
+ "permissions.onRemoved",
+ "runtime.getBackgroundPage",
+ "runtime.getBrowserInfo",
+ "runtime.getPlatformInfo",
+ "runtime.onConnectExternal",
+ "runtime.onInstalled",
+ "runtime.onMessageExternal",
+ "runtime.onStartup",
+ "runtime.onSuspend",
+ "runtime.onSuspendCanceled",
+ "runtime.onUpdateAvailable",
+ "runtime.openOptionsPage",
+ "runtime.reload",
+ "runtime.setUninstallURL",
+ "theme.getCurrent",
+ "theme.onUpdated",
+ "types.LevelOfControl",
+ "types.SettingScope",
+];
+
+// APIs that are exposed to MV2 by default, but not to MV3.
+const mv2onlyBackgroundApis = new Set([
+ "extension.getURL",
+ "extension.lastError",
+ "contentScripts.register",
+ "tabs.executeScript",
+ "tabs.insertCSS",
+ "tabs.removeCSS",
+]);
+let expectedBackgroundApisMV3 = expectedBackgroundApis.filter(
+ path => !mv2onlyBackgroundApis.has(path)
+);
+
+function sendAllApis() {
+ function isEvent(key, val) {
+ if (!/^on[A-Z]/.test(key)) {
+ return false;
+ }
+ let eventKeys = [];
+ for (let prop in val) {
+ eventKeys.push(prop);
+ }
+ eventKeys = eventKeys.sort().join();
+ return eventKeys === "addListener,hasListener,removeListener";
+ }
+ // Some items are removed from the namespaces in the lazy getters after the first get. This
+ // in one case, the events namespace, leaves a namespace that is empty. Make sure we don't
+ // consider those as a part of our testing.
+ function isEmptyObject(val) {
+ return val !== null && typeof val == "object" && !Object.keys(val).length;
+ }
+ function mayRecurse(key, val) {
+ if (Object.keys(val).filter(k => !/^[A-Z\-0-9_]+$/.test(k)).length === 0) {
+ // Don't recurse on constants and empty objects.
+ return false;
+ }
+ return !isEvent(key, val);
+ }
+
+ let results = [];
+ function diveDeeper(path, obj) {
+ for (const [key, val] of Object.entries(obj)) {
+ if (typeof val == "object" && val !== null && mayRecurse(key, val)) {
+ diveDeeper(`${path}.${key}`, val);
+ } else if (val !== undefined && !isEmptyObject(val)) {
+ results.push(`${path}.${key}`);
+ }
+ }
+ }
+ diveDeeper("browser", browser);
+ diveDeeper("chrome", chrome);
+ browser.test.sendMessage("allApis", results.sort());
+ browser.test.sendMessage("namespaces", browser === chrome);
+}
+
+add_task(async function setup() {
+ // This test enumerates all APIs and may access a deprecated API. Just log a
+ // warning instead of throwing.
+ await ExtensionTestUtils.failOnSchemaWarnings(false);
+});
+
+add_task(async function test_enumerate_content_script_apis() {
+ let extensionData = {
+ manifest: {
+ content_scripts: [
+ {
+ matches: ["http://mochi.test/*/file_sample.html"],
+ js: ["contentscript.js"],
+ run_at: "document_start",
+ },
+ ],
+ },
+ files: {
+ "contentscript.js": sendAllApis,
+ },
+ };
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+
+ let win = window.open("file_sample.html");
+ let actualApis = await extension.awaitMessage("allApis");
+ win.close();
+ let expectedApis = generateExpectations(expectedContentApis);
+ isDeeply(actualApis, expectedApis, "content script APIs");
+
+ let sameness = await extension.awaitMessage("namespaces");
+ ok(sameness, "namespaces are same object");
+
+ await extension.unload();
+});
+
+add_task(async function test_enumerate_background_script_apis() {
+ let extensionData = {
+ background: sendAllApis,
+ };
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+
+ let actualApis = await extension.awaitMessage("allApis");
+ let expectedApis = generateExpectations(expectedBackgroundApis);
+ isDeeply(actualApis, expectedApis, "background script APIs");
+
+ let sameness = await extension.awaitMessage("namespaces");
+ ok(!sameness, "namespaces are different objects");
+
+ await extension.unload();
+});
+
+add_task(async function test_enumerate_background_script_apis_mv3() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.manifestV3.enabled", true]],
+ });
+ let extensionData = {
+ background: sendAllApis,
+ manifest: {
+ manifest_version: 3,
+
+ // Features that expose APIs in MV2, but should not do anything with MV3.
+ browser_action: {},
+ user_scripts: {},
+ },
+ };
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+
+ let actualApis = await extension.awaitMessage("allApis");
+ let expectedApis = generateExpectations(expectedBackgroundApisMV3);
+ isDeeply(actualApis, expectedApis, "background script APIs in MV3");
+
+ let sameness = await extension.awaitMessage("namespaces");
+ ok(sameness, "namespaces are same object");
+
+ await extension.unload();
+ await SpecialPowers.popPrefEnv();
+});