Bug 1452307: Remove support for old-style experiment API extensions. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Fri, 06 Apr 2018 18:14:59 -0700
changeset 412788 bc2f1762365b6d43b1aeb2056ebe5384bb0522cf
parent 412787 791f0155deb4500a2acbea31711ed1485df2b79f
child 412789 107214f8a65dac49aa506f5411bf81d9ae6376c2
push id33818
push userapavel@mozilla.com
push dateWed, 11 Apr 2018 14:36:40 +0000
treeherdermozilla-central@cfe6399e142c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1452307
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1452307: Remove support for old-style experiment API extensions. r=aswan MozReview-Commit-ID: 5y48pMRQ5XW
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionCommon.jsm
toolkit/components/extensions/ExtensionParent.jsm
toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
toolkit/mozapps/extensions/internal/APIExtensionBootstrap.js
toolkit/mozapps/extensions/internal/XPIInstall.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/internal/moz.build
toolkit/mozapps/extensions/test/xpcshell/test_legacy.js
toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -1189,17 +1189,16 @@ class Extension extends ExtensionData {
 
     this.onStartup = null;
 
     this.hasShutdown = false;
     this.onShutdown = new Set();
 
     this.uninstallURL = null;
 
-    this.apis = [];
     this.whiteListedHosts = null;
     this._optionalOrigins = null;
     this.webAccessibleResources = null;
 
     this.emitter = new EventEmitter();
 
     /* eslint-disable mozilla/balanced-listeners */
     this.on("add-permissions", (ignoreEvent, permissions) => {
@@ -1392,28 +1391,16 @@ class Extension extends ExtensionData {
 
   async loadManifest() {
     let manifest = await super.loadManifest();
 
     if (this.errors.length) {
       return Promise.reject({errors: this.errors});
     }
 
-    if (this.apiNames.size) {
-      // Load Experiments APIs that this extension depends on.
-      let apis = await Promise.all(
-        Array.from(this.apiNames, api => ExtensionCommon.ExtensionAPIs.load(api)));
-
-      for (let API of apis) {
-        if (API) {
-          this.apis.push(new API(this));
-        }
-      }
-    }
-
     return manifest;
   }
 
   // Representation of the extension to send to content
   // processes. This should include anything the content process might
   // need.
   serialize() {
     return {
@@ -1649,27 +1636,29 @@ class Extension extends ExtensionData {
       this.emit("startup", this);
       Management.emit("startup", this);
 
       await this.runManifest(this.manifest);
 
       Management.emit("ready", this);
       this.emit("ready");
       TelemetryStopwatch.finish("WEBEXT_EXTENSION_STARTUP_MS", this);
-    } catch (e) {
-      dump(`Extension error: ${e.message || e} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`);
-      Cu.reportError(e);
+    } catch (errors) {
+      for (let e of [].concat(errors)) {
+        dump(`Extension error: ${e.message || e} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`);
+        Cu.reportError(e);
+      }
 
       if (this.policy) {
         this.policy.active = false;
       }
 
       this.cleanupGeneratedFile();
 
-      throw e;
+      throw errors;
     }
 
     this.startupPromise = null;
   }
 
   cleanupGeneratedFile() {
     if (!this.cleanupFile) {
       return;
@@ -1760,20 +1749,16 @@ class Extension extends ExtensionData {
     }
 
     GlobalManager.uninit(this);
 
     for (let obj of this.onShutdown) {
       obj.close();
     }
 
-    for (let api of this.apis) {
-      api.destroy();
-    }
-
     ParentAPIManager.shutdownExtension(this.id);
 
     Management.emit("shutdown", this);
     this.emit("shutdown");
 
     await this.broadcast("Extension:Shutdown", {id: this.id});
 
     MessageChannel.abortResponses({extensionId: this.id});
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -28,18 +28,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   Schemas: "resource://gre/modules/Schemas.jsm",
   SchemaRoot: "resource://gre/modules/Schemas.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                    "@mozilla.org/content/style-sheet-service;1",
                                    "nsIStyleSheetService");
 
-const global = Cu.getGlobalForObject(this);
-
 ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   DefaultMap,
   DefaultWeakMap,
   EventEmitter,
   ExtensionError,
   defineLazyGetter,
@@ -110,87 +108,16 @@ class ExtensionAPI extends ExtensionUtil
   onManifestEntry(entry) {
   }
 
   getAPI(context) {
     throw new Error("Not Implemented");
   }
 }
 
-var ExtensionAPIs = {
-  apis: new Map(),
-
-  load(apiName) {
-    let api = this.apis.get(apiName);
-    if (!api) {
-      return null;
-    }
-
-    if (api.loadPromise) {
-      return api.loadPromise;
-    }
-
-    let {script, schema} = api;
-
-    let addonId = `${apiName}@experiments.addons.mozilla.org`;
-    api.sandbox = Cu.Sandbox(global, {
-      wantXrays: false,
-      sandboxName: script,
-      addonId,
-      wantGlobalProperties: ["ChromeUtils"],
-      metadata: {addonID: addonId},
-    });
-
-    api.sandbox.ExtensionAPI = ExtensionAPI;
-
-    // Create a console getter which lazily provide a ConsoleAPI instance.
-    XPCOMUtils.defineLazyGetter(api.sandbox, "console", () => {
-      return new ConsoleAPI({prefix: addonId});
-    });
-
-    Services.scriptloader.loadSubScript(script, api.sandbox, "UTF-8");
-
-    api.loadPromise = Schemas.load(schema).then(() => {
-      let API = Cu.evalInSandbox("API", api.sandbox);
-      API.prototype.namespace = apiName;
-      return API;
-    });
-
-    return api.loadPromise;
-  },
-
-  unload(apiName) {
-    let api = this.apis.get(apiName);
-
-    let {schema} = api;
-
-    Schemas.unload(schema);
-    Cu.nukeSandbox(api.sandbox);
-
-    api.sandbox = null;
-    api.loadPromise = null;
-  },
-
-  register(namespace, schema, script) {
-    if (this.apis.has(namespace)) {
-      throw new Error(`API namespace already exists: ${namespace}`);
-    }
-
-    this.apis.set(namespace, {schema, script});
-  },
-
-  unregister(namespace) {
-    if (!this.apis.has(namespace)) {
-      throw new Error(`API namespace does not exist: ${namespace}`);
-    }
-
-    this.apis.delete(namespace);
-  },
-};
-
 /**
  * This class contains the information we have about an individual
  * extension.  It is never instantiated directly, instead subclasses
  * for each type of process extend this class and add members that are
  * relevant for that process.
  * @abstract
  */
 class BaseContext {
@@ -1418,36 +1345,16 @@ class SchemaAPIManager extends EventEmit
     // in the sandbox's context instead of here.
     let scope = Cu.createObjectIn(this.global);
 
     Services.scriptloader.loadSubScript(scriptUrl, scope, "UTF-8");
 
     // Save the scope to avoid it being garbage collected.
     this._scriptScopes.push(scope);
   }
-
-  /**
-   * Mash together all the APIs from `apis` into `obj`.
-   *
-   * @param {BaseContext} context The context for which the API bindings are
-   *     generated.
-   * @param {Array} apis A list of objects, see `registerSchemaAPI`.
-   * @param {object} obj The destination of the API.
-   */
-  static generateAPIs(context, apis, obj) {
-    function hasPermission(perm) {
-      return context.extension.hasPermission(perm, true);
-    }
-    for (let api of apis) {
-      if (Schemas.checkPermissions(api.namespace, {hasPermission})) {
-        api = api.getAPI(context);
-        deepCopy(obj, api);
-      }
-    }
-  }
 }
 
 class LazyAPIManager extends SchemaAPIManager {
   constructor(processType, moduleData, schemaURLs) {
     super(processType);
 
     this.initialized = false;
 
@@ -2127,17 +2034,16 @@ const stylesheetMap = new DefaultMap(url
 });
 
 
 ExtensionCommon = {
   BaseContext,
   CanOfAPIs,
   EventManager,
   ExtensionAPI,
-  ExtensionAPIs,
   LocalAPIImplementation,
   LocaleData,
   NoCloneSpreadArgs,
   SchemaAPIInterface,
   SchemaAPIManager,
   SpreadArgs,
   ignoreEvent,
   stylesheetMap,
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -424,20 +424,16 @@ GlobalManager = {
       browser.messageManager.sendAsyncMessage("Extension:SetFrameData",
                                               data);
     }
   },
 
   getExtension(extensionId) {
     return this.extensionMap.get(extensionId);
   },
-
-  injectInObject(context, isChromeCompat, dest) {
-    SchemaAPIManager.generateAPIs(context, context.extension.apis, dest);
-  },
 };
 
 /**
  * The proxied parent side of a context in ExtensionChild.jsm, for the
  * parent side of a proxied API.
  */
 class ProxyContextParent extends BaseContext {
   constructor(envType, extension, params, xulBrowser, principal) {
@@ -507,17 +503,16 @@ class ProxyContextParent extends BaseCon
     super.unload();
     apiManager.emit("proxy-context-unload", this);
   }
 }
 
 defineLazyGetter(ProxyContextParent.prototype, "apiCan", function() {
   let obj = {};
   let can = new CanOfAPIs(this, this.extension.apiManager, obj);
-  GlobalManager.injectInObject(this, false, obj);
   return can;
 });
 
 defineLazyGetter(ProxyContextParent.prototype, "apiObj", function() {
   return this.apiCan.root;
 });
 
 defineLazyGetter(ProxyContextParent.prototype, "sandbox", function() {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
@@ -7,192 +7,16 @@ ChromeUtils.defineModuleGetter(this, "Ad
 
 AddonTestUtils.init(this);
 
 add_task(async function setup() {
   AddonTestUtils.overrideCertDB();
   await ExtensionTestUtils.startAddonManager();
 });
 
-add_task(async function test_experiments_api() {
-  let apiAddonFile = Extension.generateZipFile({
-    "install.rdf": `<?xml version="1.0" encoding="UTF-8"?>
-      <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-           xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-          <Description about="urn:mozilla:install-manifest"
-              em:id="fooBar@experiments.addons.mozilla.org"
-              em:name="FooBar Experiment"
-              em:type="256"
-              em:version="0.1"
-              em:description="FooBar experiment"
-              em:creator="Mozilla">
-
-              <em:targetApplication>
-                  <Description
-                      em:id="xpcshell@tests.mozilla.org"
-                      em:minVersion="48"
-                      em:maxVersion="*"/>
-              </em:targetApplication>
-          </Description>
-      </RDF>
-    `,
-
-    "api.js": String.raw`
-      Components.utils.import("resource://gre/modules/Services.jsm");
-
-      Services.obs.notifyObservers(null, "webext-api-loaded", "");
-
-      class API extends ExtensionAPI {
-        getAPI(context) {
-          return {
-            fooBar: {
-              hello(text) {
-                console.log('fooBar.hello API called', text);
-                Services.obs.notifyObservers(null, "webext-api-hello", text);
-              }
-            }
-          }
-        }
-      }
-    `,
-
-    "schema.json": [
-      {
-        "namespace": "fooBar",
-        "description": "All full of fooBar.",
-        "permissions": ["experiments.fooBar"],
-        "functions": [
-          {
-            "name": "hello",
-            "type": "function",
-            "description": "Hates you. This is all.",
-            "parameters": [
-              {"type": "string", "name": "text"},
-            ],
-          },
-        ],
-      },
-    ],
-  });
-
-  let addonFile = Extension.generateXPI({
-    manifest: {
-      applications: {gecko: {id: "fooBar@web.extension"}},
-      permissions: ["experiments.fooBar"],
-    },
-
-    background() {
-      // The test code below checks that hello() is called at the right
-      // time with the string "Here I am".  Verify that the api schema is
-      // being correctly interpreted by calling hello() with bad arguments
-      // and only calling hello() with the magic string if the call with
-      // bad arguments throws.
-      try {
-        browser.fooBar.hello("I should not see this", "since two arguments are bad");
-      } catch (err) {
-        browser.fooBar.hello("Here I am");
-      }
-    },
-  });
-
-  let boringAddonFile = Extension.generateXPI({
-    manifest: {
-      applications: {gecko: {id: "boring@web.extension"}},
-    },
-    background() {
-      if (browser.fooBar) {
-        browser.fooBar.hello("Here I should not be");
-      }
-    },
-  });
-
-  registerCleanupFunction(() => {
-    for (let file of [apiAddonFile, addonFile, boringAddonFile]) {
-      Services.obs.notifyObservers(file, "flush-cache-entry");
-      file.remove(false);
-    }
-  });
-
-
-  let resolveHello;
-  let observer = (subject, topic, data) => {
-    if (topic == "webext-api-loaded") {
-      ok(!!resolveHello, "Should not see API loaded until dependent extension loads");
-    } else if (topic == "webext-api-hello") {
-      resolveHello(data);
-    }
-  };
-
-  Services.obs.addObserver(observer, "webext-api-loaded");
-  Services.obs.addObserver(observer, "webext-api-hello");
-  registerCleanupFunction(() => {
-    Services.obs.removeObserver(observer, "webext-api-loaded");
-    Services.obs.removeObserver(observer, "webext-api-hello");
-  });
-
-
-  // Install API add-on.
-  let apiAddon = await AddonManager.installTemporaryAddon(apiAddonFile);
-
-  let {ExtensionAPIs} = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm", {}).ExtensionCommon;
-  ok(ExtensionAPIs.apis.has("fooBar"), "Should have fooBar API.");
-
-
-  // Install boring WebExtension add-on.
-  let boringAddon = await AddonManager.installTemporaryAddon(boringAddonFile);
-  await AddonTestUtils.promiseWebExtensionStartup();
-
-  // Install interesting WebExtension add-on.
-  let promise = new Promise(resolve => {
-    resolveHello = resolve;
-  });
-
-  let addon = await AddonManager.installTemporaryAddon(addonFile);
-  await AddonTestUtils.promiseWebExtensionStartup();
-
-  let hello = await promise;
-  equal(hello, "Here I am", "Should get hello from add-on");
-
-  // Install management test add-on.
-  let managementAddon = ExtensionTestUtils.loadExtension({
-    manifest: {
-      applications: {gecko: {id: "management@web.extension"}},
-      permissions: ["management"],
-    },
-    async background() {
-      // Should find the simple extension.
-      let normalAddon = await browser.management.get("boring@web.extension");
-      browser.test.assertEq(normalAddon.id, "boring@web.extension", "Found boring addon");
-
-      try {
-        // Not allowed to get the API experiment.
-        await browser.management.get("fooBar@experiments.addons.mozilla.org");
-      } catch (e) {
-        browser.test.sendMessage("done");
-      }
-    },
-    useAddonManager: "temporary",
-  });
-
-  await managementAddon.startup();
-  await managementAddon.awaitMessage("done");
-  await managementAddon.unload();
-
-  // Cleanup.
-  apiAddon.uninstall();
-
-  boringAddon.userDisabled = true;
-  await new Promise(executeSoon);
-
-  equal(addon.appDisabled, true, "Add-on should be app-disabled after its dependency is removed.");
-
-  addon.uninstall();
-  boringAddon.uninstall();
-});
-
 let fooExperimentAPIs = {
   foo: {
     schema: "schema.json",
     parent: {
       scopes: ["addon_parent"],
       script: "parent.js",
       paths: [["experiments", "foo", "parent"]],
     },
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
@@ -1,44 +1,79 @@
 "use strict";
 
-ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
-
-const {ExtensionAPI, ExtensionAPIs} = ExtensionCommon;
-
 const {ExtensionManager} = ChromeUtils.import("resource://gre/modules/ExtensionChild.jsm", {});
 
 Cu.importGlobalProperties(["Blob", "URL"]);
 
-let schema = [
-  {
-    namespace: "userinputtest",
-    functions: [
-      {
-        name: "test",
-        type: "function",
-        async: true,
-        requireUserInput: true,
-        parameters: [],
-      },
-    ],
+let experimentAPIs = {
+  userinputtest: {
+    schema: "schema.json",
+    parent: {
+      scopes: ["addon_parent"],
+      script: "parent.js",
+      paths: [["userinputtest"]],
+    },
+    child: {
+      scopes: ["addon_child"],
+      script: "child.js",
+      paths: [["userinputtest", "child"]],
+    },
   },
-];
+};
+
+let experimentFiles = {
+  "schema.json": JSON.stringify([
+    {
+      namespace: "userinputtest",
+      functions: [
+        {
+          name: "test",
+          type: "function",
+          async: true,
+          requireUserInput: true,
+          parameters: [],
+        },
+        {
+          name: "child",
+          type: "function",
+          async: true,
+          requireUserInput: true,
+          parameters: [],
+        },
+      ],
+    },
+  ]),
 
-class API extends ExtensionAPI {
-  getAPI(context) {
-    return {
-      userinputtest: {
-        test() {},
-      },
+  /* globals ExtensionAPI */
+  "parent.js": () => {
+    this.userinputtest = class extends ExtensionAPI {
+      getAPI(context) {
+        return {
+          userinputtest: {
+            test() {},
+          },
+        };
+      }
     };
-  }
-}
+  },
 
-let schemaUrl = `data:,${JSON.stringify(schema)}`;
+  /* globals ExtensionAPI */
+  "child.js": () => {
+    this.userinputtest = class extends ExtensionAPI {
+      getAPI(context) {
+        return {
+          userinputtest: {
+            child() {},
+          },
+        };
+      }
+    };
+  },
+};
 
 // Set the "handlingUserInput" flag for the given extension's background page.
 // Returns an RAIIHelper that should be destruct()ed eventually.
 function setHandlingUserInput(extension) {
   let extensionChild = ExtensionManager.extensions.get(extension.extension.id);
   let bgwin = null;
   for (let view of extensionChild.views) {
     if (view.viewType == "background") {
@@ -50,88 +85,77 @@ function setHandlingUserInput(extension)
   let winutils = bgwin.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDOMWindowUtils);
   return winutils.setHandlingUserInput(true);
 }
 
 // Test that the schema requireUserInput flag works correctly for
 // proxied api implementations.
 add_task(async function test_proxy() {
-  let apiUrl = URL.createObjectURL(new Blob([API.toString()]));
-  ExtensionAPIs.register("userinputtest", schemaUrl, apiUrl);
-
   let extension = ExtensionTestUtils.loadExtension({
     background() {
       browser.test.onMessage.addListener(async () => {
         try {
           await browser.userinputtest.test();
           browser.test.sendMessage("result", null);
         } catch (err) {
           browser.test.sendMessage("result", err.message);
         }
       });
     },
     manifest: {
       permissions: ["experiments.userinputtest"],
+      experiment_apis: experimentAPIs,
     },
+    files: experimentFiles,
   });
 
   await extension.startup();
 
   extension.sendMessage("test");
   let result = await extension.awaitMessage("result");
   ok(/test may only be called from a user input handler/.test(result),
-     "function failed when not called from a user input handler");
-
-  let handle = setHandlingUserInput(extension);
-  extension.sendMessage("test");
-  result = await extension.awaitMessage("result");
-  equal(result, null, "function succeeded when called from a user input handler");
-  handle.destruct();
-
-  await extension.unload();
-  ExtensionAPIs.unregister("userinputtest");
-});
-
-// Test that the schema requireUserInput flag works correctly for
-// non-proxied api implementations.
-add_task(async function test_local() {
-  let apiString = `this.userinputtest = ${API.toString()};`;
-  let apiUrl = URL.createObjectURL(new Blob([apiString]));
-  await Schemas.load(schemaUrl);
-  const {apiManager} = ChromeUtils.import("resource://gre/modules/ExtensionPageChild.jsm", {});
-  apiManager.registerModules({
-    userinputtest: {
-      url: apiUrl,
-      scopes: ["addon_child"],
-      paths: [["userinputtest"]],
-    },
-  });
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background() {
-      browser.test.onMessage.addListener(async () => {
-        try {
-          await browser.userinputtest.test();
-          browser.test.sendMessage("result", null);
-        } catch (err) {
-          browser.test.sendMessage("result", err.message);
-        }
-      });
-    },
-    manifest: {},
-  });
-
-  await extension.startup();
-
-  extension.sendMessage("test");
-  let result = await extension.awaitMessage("result");
-  ok(/test may only be called from a user input handler/.test(result),
-     "function failed when not called from a user input handler");
+     `function failed when not called from a user input handler: ${result}`);
 
   let handle = setHandlingUserInput(extension);
   extension.sendMessage("test");
   result = await extension.awaitMessage("result");
   equal(result, null, "function succeeded when called from a user input handler");
   handle.destruct();
 
   await extension.unload();
 });
+
+// Test that the schema requireUserInput flag works correctly for
+// non-proxied api implementations.
+add_task(async function test_local() {
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {
+      browser.test.onMessage.addListener(async () => {
+        try {
+          await browser.userinputtest.child();
+          browser.test.sendMessage("result", null);
+        } catch (err) {
+          browser.test.sendMessage("result", err.message);
+        }
+      });
+    },
+    manifest: {
+      experiment_apis: experimentAPIs,
+    },
+    files: experimentFiles,
+  });
+
+  await extension.startup();
+
+  extension.sendMessage("test");
+  let result = await extension.awaitMessage("result");
+  ok(/child may only be called from a user input handler/.test(result),
+     `function failed when not called from a user input handler: ${result}`);
+
+  let handle = setHandlingUserInput(extension);
+  extension.sendMessage("test");
+  result = await extension.awaitMessage("result");
+  equal(result, null, "function succeeded when called from a user input handler");
+  handle.destruct();
+
+  await extension.unload();
+});
deleted file mode 100644
--- a/toolkit/mozapps/extensions/internal/APIExtensionBootstrap.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-/* exported startup, shutdown, install, uninstall, ExtensionAPIs */
-
-ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-const {ExtensionAPIs} = ExtensionCommon;
-
-var namespace;
-var resource;
-var resProto;
-
-function install(data, reason) {
-}
-
-function startup(data, reason) {
-  namespace = data.id.replace(/@.*/, "");
-  resource = `extension-${namespace.toLowerCase()}-api`;
-
-  resProto = Services.io.getProtocolHandler("resource")
-                     .QueryInterface(Ci.nsIResProtocolHandler);
-
-  resProto.setSubstitution(resource, data.resourceURI);
-
-  ExtensionAPIs.register(
-    namespace,
-    `resource://${resource}/schema.json`,
-    `resource://${resource}/api.js`);
-}
-
-function shutdown(data, reason) {
-  resProto.setSubstitution(resource, null);
-
-  ExtensionAPIs.unregister(namespace);
-}
-
-function uninstall(data, reason) {
-}
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -155,31 +155,30 @@ const PROP_METADATA      = ["id", "versi
 const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
 const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
 const PROP_TARGETAPP     = ["id", "minVersion", "maxVersion"];
 
 // Map new string type identifiers to old style nsIUpdateItem types.
 // Retired values:
 // 32 = multipackage xpi file
 // 8 = locale
+// 256 = apiextension
 const TYPES = {
   extension: 2,
   theme: 4,
   dictionary: 64,
   experiment: 128,
-  apiextension: 256,
 };
 
 const COMPATIBLE_BY_DEFAULT_TYPES = {
   extension: true,
   dictionary: true,
 };
 
 const RESTARTLESS_TYPES = new Set([
-  "apiextension",
   "dictionary",
   "experiment",
   "webextension",
   "webextension-theme",
 ]);
 
 // This is a random number array that can be used as "salt" when generating
 // an automatic ID based on the directory path of an add-on. It will prevent
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -189,38 +189,35 @@ const BOOTSTRAP_REASONS = {
   ADDON_UNINSTALL: 6,
   ADDON_UPGRADE: 7,
   ADDON_DOWNGRADE: 8
 };
 
 // Some add-on types that we track internally are presented as other types
 // externally
 const TYPE_ALIASES = {
-  "apiextension": "extension",
   "webextension": "extension",
   "webextension-theme": "theme",
   "webextension-langpack": "locale",
 };
 
 const CHROME_TYPES = new Set([
   "extension",
   "experiment",
 ]);
 
 const SIGNED_TYPES = new Set([
-  "apiextension",
   "extension",
   "experiment",
   "webextension",
   "webextension-langpack",
   "webextension-theme",
 ]);
 
 const LEGACY_TYPES = new Set([
-  "apiextension",
   "extension",
   "theme",
 ]);
 
 const ALL_EXTERNAL_TYPES = new Set([
   "dictionary",
   "extension",
   "experiment",
@@ -3742,18 +3739,16 @@ var XPIProvider = {
     if (isWebExtension(aType)) {
       activeAddon.bootstrapScope = Extension.getBootstrapScope(aId, aFile);
     } else if (aType === "webextension-langpack") {
       activeAddon.bootstrapScope = Langpack.getBootstrapScope(aId, aFile);
     } else {
       let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
       if (aType == "dictionary")
         uri = "resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js";
-      else if (aType == "apiextension")
-        uri = "resource://gre/modules/addons/APIExtensionBootstrap.js";
 
       activeAddon.bootstrapScope =
         new Cu.Sandbox(principal, { sandboxName: uri,
                                     addonId: aId,
                                     wantGlobalProperties: ["ChromeUtils"],
                                     metadata: { addonID: aId, URI: uri } });
 
       try {
@@ -4713,20 +4708,16 @@ AddonWrapper.prototype = {
   get type() {
     return getExternalType(addonFor(this).type);
   },
 
   get isWebExtension() {
     return isWebExtension(addonFor(this).type);
   },
 
-  get isAPIExtension() {
-    return addonFor(this).type == "apiextension";
-  },
-
   get temporarilyInstalled() {
     return addonFor(this)._installLocation == TemporaryInstallLocation;
   },
 
   get aboutURL() {
     return this.isActive ? addonFor(this).aboutURL : null;
   },
 
--- a/toolkit/mozapps/extensions/internal/moz.build
+++ b/toolkit/mozapps/extensions/internal/moz.build
@@ -3,17 +3,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXTRA_JS_MODULES.addons += [
     'AddonRepository.jsm',
     'AddonSettings.jsm',
     'AddonUpdateChecker.jsm',
-    'APIExtensionBootstrap.js',
     'Content.js',
     'GMPProvider.jsm',
     'LightweightThemeImageOptimizer.jsm',
     'ProductAddonChecker.jsm',
     'SpellCheckDictionaryBootstrap.js',
     'UpdateRDFConverter.jsm',
     'XPIInstall.jsm',
     'XPIProvider.jsm',
--- a/toolkit/mozapps/extensions/test/xpcshell/test_legacy.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_legacy.js
@@ -7,22 +7,16 @@ startupManager();
 add_task(async function test_disable() {
   let legacy = [
     {
       id: "bootstrap@tests.mozilla.org",
       name: "Bootstrap add-on",
       version: "1.0",
       bootstrap: true,
     },
-    {
-      id: "apiexperiment@tests.mozilla.org",
-      name: "WebExtension Experiment",
-      version: "1.0",
-      type: 256,
-    },
   ];
 
   let nonLegacy = [
     {
       id: "webextension@tests.mozilla.org",
       manifest: {
         applications: {gecko: {id: "webextension@tests.mozilla.org"}},
       },
--- a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
@@ -753,42 +753,8 @@ add_task(async function() {
   await promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true);
   const addon = await promiseAddonByID(ID);
 
   notEqual(addon, null);
   equal(addon.temporarilyInstalled, false);
 
   await promiseRestartManager();
 });
-
-// Check that WebExtensions experiments can only be installed temporarily
-// in builds that allow legacy extensions.
-add_task(async function() {
-  AddonTestUtils.usePrivilegedSignatures = false;
-
-  const API_ID = "apiexperiment@tests.mozilla.org";
-  let xpi = createTempXPIFile({
-    id: API_ID,
-    name: "WebExtension Experiment",
-    version: "1.0",
-    type: 256,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1"
-    }],
-  });
-
-  let addon = null;
-  try {
-    await AddonManager.installTemporaryAddon(xpi);
-    addon = await promiseAddonByID(API_ID);
-  } catch (err) {
-    // fall through, level addon null
-  }
-
-  if (AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS) {
-    notEqual(addon, null, "Temporary install of WebExtension experiment succeeded");
-    addon.uninstall();
-  } else {
-    equal(addon, null, "Temporary install of WebExtension experiment was not allowed");
-  }
-});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
@@ -272,39 +272,16 @@ add_task(async function test_experiments
   deepEqual(addon.dependencies, ["meh@experiments.addons.mozilla.org"],
             "Addon should have the expected dependencies");
 
   equal(addon.appDisabled, true, "Add-on should be app disabled due to missing dependencies");
 
   addon.uninstall();
 });
 
-// Test that experiments API extensions install correctly.
-add_task(async function test_experiments_api() {
-  const extensionId = "meh@experiments.addons.mozilla.org";
-
-  let addonFile = createTempXPIFile({
-    id: extensionId,
-    type: 256,
-    version: "0.1",
-    name: "Meh API",
-  });
-
-  await promiseInstallAllFiles([addonFile]);
-
-  let addons = await AddonManager.getAddonsByTypes(["extension"]);
-  let addon = addons.pop();
-  equal(addon.id, extensionId, "Add-on should be installed as an API extension");
-
-  addons = await AddonManager.getAddonsByTypes(["extension"]);
-  equal(addons.pop().id, extensionId, "Add-on type should be aliased to extension");
-
-  addon.uninstall();
-});
-
 add_task(async function developerShouldOverride() {
   let addon = await promiseInstallWebExtension({
     manifest: {
       default_locale: "en",
       developer: {
         name: "__MSG_name__",
         url: "__MSG_url__"
       },