Bug 1500265: Remove support for hybrid WebExtensions. r=kmag
authorAndrew Swan <aswan@mozilla.com>
Wed, 14 Nov 2018 15:47:21 -0800
changeset 446453 5861cb42b71dce8baf91679b75f4eb5b4a891cef
parent 446452 7dd88607c8e8bf196d5fe78971c77bb29fb5a1c0
child 446454 ee96952505264c6de94799afde236180d5e4dc41
push id109876
push useraswan@mozilla.com
push dateThu, 15 Nov 2018 00:53:17 +0000
treeherdermozilla-inbound@5861cb42b71d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1500265
milestone65.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 1500265: Remove support for hybrid WebExtensions. r=kmag
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js
browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
toolkit/components/extensions/ExtensionParent.jsm
toolkit/components/extensions/ExtensionTestCommon.jsm
toolkit/components/extensions/LegacyExtensionsUtils.jsm
toolkit/components/extensions/moz.build
toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_context.js
toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_embedding.js
toolkit/components/extensions/test/xpcshell/test_locale_data.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/RDFManifestConverter.jsm
toolkit/mozapps/extensions/internal/XPIDatabase.jsm
toolkit/mozapps/extensions/internal/XPIInstall.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,12 +1,11 @@
 [DEFAULT]
 tags = webextensions in-process-webextensions
 support-files =
   head.js
 
 [browser_ext_autocompletepopup.js]
-[browser_ext_legacy_extension_context_contentscript.js]
 [browser_ext_windows_allowScriptsToClose.js]
 
 [include:browser-common.ini]
 skip-if = os == 'win' # Windows WebExtensions always run OOP
 [parent:browser-common.ini]
deleted file mode 100644
--- a/browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js
+++ /dev/null
@@ -1,173 +0,0 @@
-"use strict";
-
-const {
-  LegacyExtensionContext,
-} = ChromeUtils.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {});
-
-function promiseAddonStartup(extension) {
-  const {Management} = ChromeUtils.import("resource://gre/modules/Extension.jsm", {});
-
-  return new Promise((resolve) => {
-    let listener = (evt, extensionInstance) => {
-      Management.off("startup", listener);
-      resolve(extensionInstance);
-    };
-    Management.on("startup", listener);
-  });
-}
-
-/**
- * This test case ensures that the LegacyExtensionContext can receive a connection
- * from a content script and that the received port contains the expected sender
- * tab info.
- */
-add_task(async function test_legacy_extension_context_contentscript_connection() {
-  function backgroundScript() {
-    // Extract the assigned uuid from the background page url and send it
-    // in a test message.
-    let uuid = window.location.hostname;
-
-    browser.test.onMessage.addListener(async msg => {
-      if (msg == "open-test-tab") {
-        let tab = await browser.tabs.create({url: "http://example.com/"});
-        browser.test.sendMessage("get-expected-sender-info",
-                                 {uuid, tab});
-      } else if (msg == "close-current-tab") {
-        try {
-          let [tab] = await browser.tabs.query({active: true});
-          await browser.tabs.remove(tab.id);
-          browser.test.sendMessage("current-tab-closed", true);
-        } catch (e) {
-          browser.test.sendMessage("current-tab-closed", false);
-        }
-      }
-    });
-
-    browser.test.sendMessage("ready");
-  }
-
-  function contentScript() {
-    browser.runtime.sendMessage("webextension -> legacy_extension message", (reply) => {
-      browser.test.assertEq("legacy_extension -> webextension reply", reply,
-                            "Got the expected reply from the LegacyExtensionContext");
-      browser.test.sendMessage("got-reply-message");
-    });
-
-    let port = browser.runtime.connect();
-
-    port.onMessage.addListener(msg => {
-      browser.test.assertEq("legacy_extension -> webextension port message", msg,
-                            "Got the expected message from the LegacyExtensionContext");
-      port.postMessage("webextension -> legacy_extension port message");
-    });
-  }
-
-  let extensionData = {
-    background: `new ${backgroundScript}`,
-    manifest: {
-      content_scripts: [
-        {
-          matches: ["http://example.com/*"],
-          js: ["content-script.js"],
-        },
-      ],
-    },
-    files: {
-      "content-script.js": `new ${contentScript}`,
-    },
-  };
-
-  let extension = ExtensionTestUtils.loadExtension(extensionData);
-
-  let waitForExtensionReady = extension.awaitMessage("ready");
-
-  let waitForExtensionInstance = promiseAddonStartup(extension);
-
-  extension.startup();
-
-  let extensionInstance = await waitForExtensionInstance;
-
-  // Connect to the target extension.id as an external context
-  // using the given custom sender info.
-  let legacyContext = new LegacyExtensionContext(extensionInstance);
-
-  let waitConnectPort = new Promise(resolve => {
-    let {browser} = legacyContext.api;
-    browser.runtime.onConnect.addListener(port => {
-      resolve(port);
-    });
-  });
-
-  let waitMessage = new Promise(resolve => {
-    let {browser} = legacyContext.api;
-    browser.runtime.onMessage.addListener((singleMsg, msgSender, sendReply) => {
-      sendReply("legacy_extension -> webextension reply");
-      resolve({singleMsg, msgSender});
-    });
-  });
-
-  is(legacyContext.envType, "legacy_extension",
-     "LegacyExtensionContext instance has the expected type");
-
-  ok(legacyContext.api, "Got the API object");
-
-  await waitForExtensionReady;
-
-  extension.sendMessage("open-test-tab");
-
-  let {tab} = await extension.awaitMessage("get-expected-sender-info");
-
-  let {singleMsg, msgSender} = await waitMessage;
-  is(singleMsg, "webextension -> legacy_extension message",
-     "Got the expected message");
-  ok(msgSender, "Got a message sender object");
-
-  is(msgSender.id, extension.id, "The sender has the expected id property");
-  is(msgSender.url, "http://example.com/", "The sender has the expected url property");
-  ok(msgSender.tab, "The sender has a tab property");
-  is(msgSender.tab.id, tab.id, "The port sender has the expected tab.id");
-
-  // Wait confirmation that the reply has been received.
-  await extension.awaitMessage("got-reply-message");
-
-  let port = await waitConnectPort;
-
-  ok(port, "Got the Port API object");
-  ok(port.sender, "The port has a sender property");
-
-  is(port.sender.id, extension.id, "The port sender has an id property");
-  is(port.sender.url, "http://example.com/", "The port sender has the expected url property");
-  ok(port.sender.tab, "The port sender has a tab property");
-  is(port.sender.tab.id, tab.id, "The port sender has the expected tab.id");
-
-  let waitPortMessage = new Promise(resolve => {
-    port.onMessage.addListener((msg) => {
-      resolve(msg);
-    });
-  });
-
-  port.postMessage("legacy_extension -> webextension port message");
-
-  let msg = await waitPortMessage;
-
-  is(msg, "webextension -> legacy_extension port message",
-     "LegacyExtensionContext received the expected message from the webextension");
-
-  let waitForDisconnect = new Promise(resolve => {
-    port.onDisconnect.addListener(resolve);
-  });
-
-  let waitForTestDone = extension.awaitMessage("current-tab-closed");
-
-  extension.sendMessage("close-current-tab");
-
-  await waitForDisconnect;
-
-  info("Got the disconnect event on tab closed");
-
-  let success = await waitForTestDone;
-
-  ok(success, "Test completed successfully");
-
-  await extension.unload();
-});
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -1,26 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-requestLongerTimeout(2);
-
-function add_tasks(task) {
-  add_task(task.bind(null, {embedded: false}));
-
-  add_task(task.bind(null, {embedded: true}));
-}
-
 async function loadExtension(options) {
   let extension = ExtensionTestUtils.loadExtension({
     useAddonManager: "temporary",
 
-    embedded: options.embedded,
-
     manifest: Object.assign({
       "permissions": ["tabs"],
     }, options.manifest),
 
     files: {
       "options.html": `<!DOCTYPE html>
         <html>
           <head>
@@ -64,22 +54,22 @@ async function loadExtension(options) {
     background: options.background,
   });
 
   await extension.startup();
 
   return extension;
 }
 
-add_tasks(async function test_inline_options(extraOptions) {
-  info(`Test options opened inline (${JSON.stringify(extraOptions)})`);
+add_task(async function test_inline_options() {
+  info(`Test options opened inline`);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
-  let extension = await loadExtension(Object.assign({}, extraOptions, {
+  let extension = await loadExtension({
     manifest: {
       applications: {gecko: {id: "inline_options@tests.mozilla.org"}},
       "options_ui": {
         "page": "options.html",
       },
     },
 
     background: async function() {
@@ -189,17 +179,17 @@ add_tasks(async function test_inline_opt
         await browser.tabs.remove(tab.id);
 
         browser.test.notifyPass("options-ui");
       } catch (error) {
         browser.test.fail(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("options-ui");
       }
     },
-  }));
+  });
 
   await Promise.all([
     extension.awaitMessage("options-html-inbound-pong"),
     extension.awaitMessage("options-html-outbound-pong"),
     extension.awaitMessage("bg-inbound-pong"),
     extension.awaitMessage("bg-outbound-pong"),
   ]);
 
@@ -207,22 +197,22 @@ add_tasks(async function test_inline_opt
 
   await extension.awaitFinish("options-ui");
 
   await extension.unload();
 
   BrowserTestUtils.removeTab(tab);
 });
 
-add_tasks(async function test_tab_options(extraOptions) {
-  info(`Test options opened in a tab (${JSON.stringify(extraOptions)})`);
+add_task(async function test_tab_options() {
+  info(`Test options opened in a tab`);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
-  let extension = await loadExtension(Object.assign({}, extraOptions, {
+  let extension = await loadExtension({
     manifest: {
       applications: {gecko: {id: "tab_options@tests.mozilla.org"}},
       "options_ui": {
         "page": "options.html",
         "open_in_tab": true,
       },
     },
 
@@ -302,39 +292,39 @@ add_tasks(async function test_tab_option
         await browser.tabs.remove(tab.id);
 
         browser.test.notifyPass("options-ui-tab");
       } catch (error) {
         browser.test.fail(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("options-ui-tab");
       }
     },
-  }));
+  });
 
   await extension.awaitFinish("options-ui-tab");
   await extension.unload();
 
   BrowserTestUtils.removeTab(tab);
 });
 
-add_tasks(async function test_options_no_manifest(extraOptions) {
-  info(`Test with no manifest key (${JSON.stringify(extraOptions)})`);
+add_task(async function test_options_no_manifest() {
+  info(`Test with no manifest key`);
 
-  let extension = await loadExtension(Object.assign({}, extraOptions, {
+  let extension = await loadExtension({
     manifest: {
       applications: {gecko: {id: "no_options@tests.mozilla.org"}},
     },
 
     async background() {
       browser.test.log("Try to open options page when not specified in the manifest.");
 
       await browser.test.assertRejects(
         browser.runtime.openOptionsPage(),
         /No `options_ui` declared/,
         "Expected error from openOptionsPage()");
 
       browser.test.notifyPass("options-no-manifest");
     },
-  }));
+  });
 
   await extension.awaitFinish("options-no-manifest");
   await extension.unload();
 });
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -397,25 +397,24 @@ ProxyMessenger = {
       }
     } else if (messageName === "Extension:Port:Disconnect") {
       let port = this.portsById.get(data.portId);
       if (port) {
         port.unregister();
       }
     }
 
-
-    if (!(extension.isEmbedded || recipient.toProxyScript) || !extension.remote) {
+    if (!(recipient.toProxyScript && extension.remote)) {
       return promise1;
     }
 
-    // If we have a proxy script sandbox or a remote, embedded extension, where
-    // the legacy side is running in a different process than the WebExtension
-    // side. As a result, we need to dispatch the message to both the parent and
-    // extension processes, and manually merge the results.
+    // Proxy scripts run in the parent process so we need to dispatch
+    // the message to both the parent and extension process and merge
+    // the results.
+    // Once proxy scripts are gone (bug 1443259) we can remove this
     let promise2 = MessageChannel.sendMessage(Services.ppmm.getChildAt(0), messageName, data, {
       sender,
       recipient,
       responseType,
     });
 
     let result = undefined;
     let failures = 0;
--- a/toolkit/components/extensions/ExtensionTestCommon.jsm
+++ b/toolkit/components/extensions/ExtensionTestCommon.jsm
@@ -55,20 +55,19 @@ XPCOMUtils.defineLazyGetter(this, "conso
  * A skeleton Extension-like object, used for testing, which installs an
  * add-on via the add-on manager when startup() is called, and
  * uninstalles it on shutdown().
  *
  * @param {string} id
  * @param {nsIFile} file
  * @param {nsIURI} rootURI
  * @param {string} installType
- * @param {boolean} [embedded = false]
  */
 class MockExtension {
-  constructor(file, rootURI, installType, embedded) {
+  constructor(file, rootURI, installType) {
     this.id = null;
     this.file = file;
     this.rootURI = rootURI;
     this.installType = installType;
     this.addon = null;
 
     let promiseEvent = eventName => new Promise(resolve => {
       let onstartup = async (msg, extension) => {
@@ -84,19 +83,17 @@ class MockExtension {
         }
       };
       apiManager.on(eventName, onstartup);
     });
 
     this._extension = null;
     this._extensionPromise = promiseEvent("startup");
     this._readyPromise = promiseEvent("ready");
-    if (!embedded) {
-      this._uninstallPromise = promiseEvent("uninstall-complete");
-    }
+    this._uninstallPromise = promiseEvent("uninstall-complete");
   }
 
   maybeSetID(uri, id) {
     if (!this.id && uri instanceof Ci.nsIJARURI &&
         uri.JARFile.QueryInterface(Ci.nsIFileURL)
            .file.equals(this.file)) {
       this.id = id;
     }
@@ -226,70 +223,16 @@ var ExtensionTestCommon = class Extensio
       let bgScript = uuidGen.generateUUID().number + ".js";
 
       provide(manifest, ["background", "scripts"], [bgScript], true);
       files[bgScript] = data.background;
     }
 
     provide(files, ["manifest.json"], manifest);
 
-    if (data.embedded) {
-      // Package this as a webextension embedded inside a legacy
-      // extension.
-
-      let xpiFiles = {
-        "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="${manifest.applications.gecko.id}"
-                  em:name="${manifest.name}"
-                  em:type="2"
-                  em:version="${manifest.version}"
-                  em:description=""
-                  em:multiprocessCompatible="true"
-                  em:hasEmbeddedWebExtension="true"
-                  em:bootstrap="true">
-
-                  <!-- Firefox -->
-                  <em:targetApplication>
-                      <Description
-                          em:id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
-                          em:minVersion="51.0a1"
-                          em:maxVersion="*"/>
-                  </em:targetApplication>
-                  <em:targetApplication>
-                    <Description>
-                      <em:id>toolkit@mozilla.org</em:id>
-                      <em:minVersion>0</em:minVersion>
-                      <em:maxVersion>*</em:maxVersion>
-                    </Description>
-                  </em:targetApplication>
-              </Description>
-          </RDF>
-        `,
-
-        "bootstrap.js": `
-          function install() {}
-          function uninstall() {}
-          function shutdown() {}
-
-          function startup(data) {
-            data.webExtension.startup();
-          }
-        `,
-      };
-
-      for (let [path, data] of Object.entries(files)) {
-        xpiFiles[`webextension/${path}`] = data;
-      }
-
-      files = xpiFiles;
-    }
-
     return this.generateZipFile(files);
   }
 
   static generateZipFile(files, baseName = "generated-extension.xpi") {
     let ZipWriter = Components.Constructor("@mozilla.org/zipwriter;1", "nsIZipWriter");
     let zipW = new ZipWriter();
 
     let file = FileUtils.getFile("TmpD", [baseName]);
@@ -384,17 +327,17 @@ var ExtensionTestCommon = class Extensio
     flushJarCache(file.path);
     Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", {path: file.path});
 
     let fileURI = Services.io.newFileURI(file);
     let jarURI = Services.io.newURI("jar:" + fileURI.spec + "!/");
 
     // This may be "temporary" or "permanent".
     if (data.useAddonManager) {
-      return new MockExtension(file, jarURI, data.useAddonManager, data.embedded);
+      return new MockExtension(file, jarURI, data.useAddonManager);
     }
 
     let id;
     if (data.manifest) {
       if (data.manifest.applications && data.manifest.applications.gecko) {
         id = data.manifest.applications.gecko.id;
       } else if (data.manifest.browser_specific_settings && data.manifest.browser_specific_settings.gecko) {
         id = data.manifest.browser_specific_settings.gecko.id;
deleted file mode 100644
--- a/toolkit/components/extensions/LegacyExtensionsUtils.jsm
+++ /dev/null
@@ -1,259 +0,0 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
-/* 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";
-
-var EXPORTED_SYMBOLS = ["LegacyExtensionsUtils"];
-
-/* exported LegacyExtensionsUtils, LegacyExtensionContext */
-
-/**
- * This file exports helpers for Legacy Extensions that want to embed a webextensions
- * and exchange messages with the embedded WebExtension.
- */
-
-ChromeUtils.defineModuleGetter(this, "Extension",
-                               "resource://gre/modules/Extension.jsm");
-ChromeUtils.defineModuleGetter(this, "ExtensionChild",
-                               "resource://gre/modules/ExtensionChild.jsm");
-ChromeUtils.defineModuleGetter(this, "Services",
-                               "resource://gre/modules/Services.jsm");
-
-ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
-
-var {
-  BaseContext,
-} = ExtensionCommon;
-
-/**
- * Instances created from this class provide to a legacy extension
- * a simple API to exchange messages with a webextension.
- */
-var LegacyExtensionContext = class extends BaseContext {
-  /**
-   * Create a new LegacyExtensionContext given a target Extension instance.
-   *
-   * @param {Extension} targetExtension
-   *   The webextension instance associated with this context. This will be the
-   *   instance of the newly created embedded webextension when this class is
-   *   used through the EmbeddedWebExtensionsUtils.
-   */
-  constructor(targetExtension) {
-    super("legacy_extension", targetExtension);
-
-    // Legacy Extensions (xul overlays, bootstrap restartless and Addon SDK)
-    // runs with a systemPrincipal.
-    let addonPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
-    Object.defineProperty(
-      this, "principal",
-      {value: addonPrincipal, enumerable: true, configurable: true}
-    );
-
-    let cloneScope = Cu.Sandbox(this.principal, {});
-    Cu.setSandboxMetadata(cloneScope, {addonId: targetExtension.id});
-    Object.defineProperty(
-      this, "cloneScope",
-      {value: cloneScope, enumerable: true, configurable: true, writable: true}
-    );
-
-    let sender = {id: targetExtension.id};
-    let filter = {extensionId: targetExtension.id};
-    // Legacy addons live in the main process. Messages from other addons are
-    // Messages from WebExtensions are sent to the main process and forwarded via
-    // the parent process manager to the legacy extension.
-    this.messenger = new ExtensionChild.Messenger(this, [Services.cpmm], sender, filter);
-
-    this.api = {
-      browser: {
-        runtime: {
-          onConnect: this.messenger.onConnect("runtime.onConnect"),
-          onMessage: this.messenger.onMessage("runtime.onMessage"),
-        },
-      },
-    };
-  }
-
-  /**
-   * This method is called when the extension shuts down or is unloaded,
-   * and it nukes the cloneScope sandbox, if any.
-   */
-  unload() {
-    if (this.unloaded) {
-      throw new Error("Error trying to unload LegacyExtensionContext twice.");
-    }
-    super.unload();
-    Cu.nukeSandbox(this.cloneScope);
-    this.cloneScope = null;
-  }
-};
-
-var EmbeddedExtensionManager;
-
-/**
- * Instances of this class are used internally by the exported EmbeddedWebExtensionsUtils
- * to manage the embedded webextension instance and the related LegacyExtensionContext
- * instance used to exchange messages with it.
- */
-class EmbeddedExtension {
-  /**
-   * Create a new EmbeddedExtension given the add-on id and the base resource URI of the
-   * container add-on (the webextension resources will be loaded from the "webextension/"
-   * subdir of the base resource URI for the legacy extension add-on).
-   *
-   * @param {Object} containerAddonParams
-   *   An object with the following properties:
-   * @param {string} containerAddonParams.id
-   *   The Add-on id of the Legacy Extension which will contain the embedded webextension.
-   * @param {string} containerAddonParams.version
-   *   The add-on version.
-   * @param {nsIURI} containerAddonParams.resourceURI
-   *   The nsIURI of the Legacy Extension container add-on.
-   */
-  constructor({id, resourceURI, version}) {
-    this.addonId = id;
-    this.resourceURI = resourceURI;
-    this.version = version;
-
-    // Setup status flag.
-    this.started = false;
-  }
-
-  /**
-   * Start the embedded webextension.
-   *
-   * @param {string} reason
-   *   The add-on startup bootstrap reason received from the XPIProvider, one of
-   *   the keys in BOOTSTRAP_REASONS.
-   * @param {object} [addonData]
-   *   Additional data to pass to the Extension constructor.
-   *
-   * @returns {Promise<LegacyContextAPI>} A promise which resolve to the API exposed to the
-   *   legacy context.
-   */
-  startup(reason, addonData = {}) {
-    if (this.started) {
-      return Promise.reject(new Error("This embedded extension has already been started"));
-    }
-
-    // Setup the startup promise.
-    this.startupPromise = new Promise((resolve, reject) => {
-      let embeddedExtensionURI = Services.io.newURI("webextension/", null, this.resourceURI);
-
-      let {builtIn, signedState, temporarilyInstalled} = addonData;
-
-      // This is the instance of the WebExtension embedded in the hybrid add-on.
-      this.extension = new Extension({
-        builtIn,
-        signedState,
-        temporarilyInstalled,
-        id: this.addonId,
-        resourceURI: embeddedExtensionURI,
-        version: this.version,
-      }, reason);
-
-      this.extension.isEmbedded = true;
-
-      // This callback is register to the "startup" event, emitted by the Extension instance
-      // after the extension manifest.json has been loaded without any errors, but before
-      // starting any of the defined contexts (which give the legacy part a chance to subscribe
-      // runtime.onMessage/onConnect listener before the background page has been loaded).
-      const onBeforeStarted = () => {
-        this.extension.off("startup", onBeforeStarted);
-
-        // Resolve the startup promise and reset the startupError.
-        this.started = true;
-        this.startupPromise = null;
-
-        // Create the legacy extension context, the legacy container addon
-        // needs to use it before the embedded webextension startup,
-        // because it is supposed to be used during the legacy container startup
-        // to subscribe its message listeners (which are supposed to be able to
-        // receive any message that the embedded part can try to send to it
-        // during its startup).
-        this.context = new LegacyExtensionContext(this.extension);
-
-        // Destroy the LegacyExtensionContext cloneScope when
-        // the embedded webextensions is unloaded.
-        this.extension.callOnClose({
-          close: () => {
-            this.context.unload();
-          },
-        });
-
-        // resolve startupPromise to execute any pending shutdown that has been
-        // chained to it.
-        resolve(this.context.api);
-      };
-
-      this.extension.on("startup", onBeforeStarted);
-
-      // Run embedded extension startup and catch any error during embedded extension
-      // startup.
-      this.extension.startup().catch((err) => {
-        this.started = false;
-        this.startupPromise = null;
-        this.extension.off("startup", onBeforeStarted);
-
-        reject(err);
-      });
-    });
-
-    return this.startupPromise;
-  }
-
-  /**
-   * Shuts down the embedded webextension.
-   *
-   * @param {string} reason
-   *   The add-on shutdown bootstrap reason received from the XPIProvider, one
-   *   of the keys in BOOTSTRAP_REASONS.
-   *
-   * @returns {Promise<void>} a promise that is resolved when the shutdown has been done
-   */
-  async shutdown(reason) {
-    EmbeddedExtensionManager.untrackEmbeddedExtension(this);
-
-    if (this.extension && !this.extension.hasShutdown) {
-      let {extension} = this;
-      this.extension = null;
-
-      await extension.shutdown(reason);
-    }
-    return undefined;
-  }
-}
-
-// Keep track on the created EmbeddedExtension instances and destroy
-// them when their container addon is going to be disabled or uninstalled.
-EmbeddedExtensionManager = {
-  // Map of the existent EmbeddedExtensions instances by addon id.
-  embeddedExtensionsByAddonId: new Map(),
-
-  untrackEmbeddedExtension(embeddedExtensionInstance) {
-    // Remove this instance from the tracked embedded extensions
-    let id = embeddedExtensionInstance.addonId;
-    if (this.embeddedExtensionsByAddonId.get(id) == embeddedExtensionInstance) {
-      this.embeddedExtensionsByAddonId.delete(id);
-    }
-  },
-
-  getEmbeddedExtensionFor({id, resourceURI, version}) {
-    let embeddedExtension = this.embeddedExtensionsByAddonId.get(id);
-
-    if (!embeddedExtension) {
-      embeddedExtension = new EmbeddedExtension({id, resourceURI, version});
-      // Keep track of the embedded extension instance.
-      this.embeddedExtensionsByAddonId.set(id, embeddedExtension);
-    }
-
-    return embeddedExtension;
-  },
-};
-
-var LegacyExtensionsUtils = {
-  getEmbeddedExtensionFor: (addon) => {
-    return EmbeddedExtensionManager.getEmbeddedExtensionFor(addon);
-  },
-};
--- a/toolkit/components/extensions/moz.build
+++ b/toolkit/components/extensions/moz.build
@@ -20,17 +20,16 @@ EXTRA_JS_MODULES += [
     'ExtensionPreferencesManager.jsm',
     'ExtensionSettingsStore.jsm',
     'ExtensionStorage.jsm',
     'ExtensionStorageIDB.jsm',
     'ExtensionStorageSync.jsm',
     'ExtensionTelemetry.jsm',
     'ExtensionUtils.jsm',
     'FindContent.jsm',
-    'LegacyExtensionsUtils.jsm',
     'MessageChannel.jsm',
     'MessageManagerProxy.jsm',
     'NativeManifests.jsm',
     'NativeMessaging.jsm',
     'PerformanceCounters.jsm',
     'ProxyScriptContext.jsm',
     'Schemas.jsm',
 ]
deleted file mode 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_context.js
+++ /dev/null
@@ -1,168 +0,0 @@
-"use strict";
-
-/* globals browser */
-
-ChromeUtils.import("resource://gre/modules/Extension.jsm");
-
-const {LegacyExtensionContext} = ChromeUtils.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {});
-
-/**
- * This test case ensures that LegacyExtensionContext instances:
- *  - expose the expected API object and can join the messaging
- *    of a webextension given its addon id
- *  - the exposed API object can receive a port related to a `runtime.connect`
- *    Port created in the webextension's background page
- *  - the received Port instance can exchange messages with the background page
- *  - the received Port receive a disconnect event when the webextension is
- *    shutting down
- */
-add_task(async function test_legacy_extension_context() {
-  function background() {
-    let bgURL = window.location.href;
-
-    let extensionInfo = {
-      bgURL,
-      // Extract the assigned uuid from the background page url.
-      uuid: window.location.hostname,
-    };
-
-    browser.test.sendMessage("webextension-ready", extensionInfo);
-
-    let port;
-
-    browser.test.onMessage.addListener(async msg => {
-      if (msg == "do-send-message") {
-        let reply = await browser.runtime.sendMessage("webextension -> legacy_extension message");
-
-        browser.test.assertEq("legacy_extension -> webextension reply", reply,
-                              "Got the expected message from the LegacyExtensionContext");
-        browser.test.sendMessage("got-reply-message");
-      } else if (msg == "do-connect") {
-        port = browser.runtime.connect();
-
-        port.onMessage.addListener(portMsg => {
-          browser.test.assertEq("legacy_extension -> webextension port message", portMsg,
-                                "Got the expected message from the LegacyExtensionContext");
-          port.postMessage("webextension -> legacy_extension port message");
-        });
-      } else if (msg == "do-disconnect") {
-        port.disconnect();
-      }
-    });
-  }
-
-  let extensionData = {
-    background,
-  };
-
-  let extension = Extension.generate(extensionData);
-  extension.isEmbedded = true;
-
-  let waitForExtensionInfo = new Promise((resolve, reject) => {
-    extension.on("test-message", function testMessageListener(kind, msg, ...args) {
-      if (msg != "webextension-ready") {
-        reject(new Error(`Got an unexpected test-message: ${msg}`));
-      } else {
-        extension.off("test-message", testMessageListener);
-        resolve(args[0]);
-      }
-    });
-  });
-
-  // Connect to the target extension as an external context
-  // using the given custom sender info.
-  let legacyContext;
-
-  extension.on("startup", function onStartup() {
-    extension.off("startup", onStartup);
-    legacyContext = new LegacyExtensionContext(extension);
-    extension.callOnClose({
-      close: () => legacyContext.unload(),
-    });
-  });
-
-  await extension.startup();
-
-  let extensionInfo = await waitForExtensionInfo;
-
-  equal(legacyContext.envType, "legacy_extension",
-        "LegacyExtensionContext instance has the expected type");
-
-  ok(legacyContext.api, "Got the expected API object");
-  ok(legacyContext.api.browser, "Got the expected browser property");
-
-  let waitMessage = new Promise(resolve => {
-    const {browser} = legacyContext.api;
-    browser.runtime.onMessage.addListener((singleMsg, msgSender) => {
-      resolve({singleMsg, msgSender});
-
-      // Send a reply to the sender.
-      return Promise.resolve("legacy_extension -> webextension reply");
-    });
-  });
-
-  extension.testMessage("do-send-message");
-
-  let {singleMsg, msgSender} = await waitMessage;
-  equal(singleMsg, "webextension -> legacy_extension message",
-        "Got the expected message");
-  ok(msgSender, "Got a message sender object");
-
-  equal(msgSender.id, extension.id, "The sender has the expected id property");
-  equal(msgSender.url, extensionInfo.bgURL, "The sender has the expected url property");
-
-  // Wait confirmation that the reply has been received.
-  await new Promise((resolve, reject) => {
-    extension.on("test-message", function testMessageListener(kind, msg, ...args) {
-      if (msg != "got-reply-message") {
-        reject(new Error(`Got an unexpected test-message: ${msg}`));
-      } else {
-        extension.off("test-message", testMessageListener);
-        resolve();
-      }
-    });
-  });
-
-  let waitConnectPort = new Promise(resolve => {
-    let {browser} = legacyContext.api;
-    browser.runtime.onConnect.addListener(port => {
-      resolve(port);
-    });
-  });
-
-  extension.testMessage("do-connect");
-
-  let port = await waitConnectPort;
-
-  ok(port, "Got the Port API object");
-  ok(port.sender, "The port has a sender property");
-  equal(port.sender.id, extension.id,
-        "The port sender has the expected id property");
-  equal(port.sender.url, extensionInfo.bgURL,
-        "The port sender has the expected url property");
-
-  let waitPortMessage = new Promise(resolve => {
-    port.onMessage.addListener((msg) => {
-      resolve(msg);
-    });
-  });
-
-  port.postMessage("legacy_extension -> webextension port message");
-
-  let msg = await waitPortMessage;
-
-  equal(msg, "webextension -> legacy_extension port message",
-        "LegacyExtensionContext received the expected message from the webextension");
-
-  let waitForDisconnect = new Promise(resolve => {
-    port.onDisconnect.addListener(resolve);
-  });
-
-  extension.testMessage("do-disconnect");
-
-  await waitForDisconnect;
-
-  info("Got the disconnect event on unload");
-
-  await extension.shutdown();
-});
deleted file mode 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_embedding.js
+++ /dev/null
@@ -1,188 +0,0 @@
-"use strict";
-
-/* globals browser */
-
-ChromeUtils.import("resource://gre/modules/LegacyExtensionsUtils.jsm");
-
-// Import EmbeddedExtensionManager to be able to check that the
-// tacked instances are cleared after the embedded extension shutdown.
-const {
-  EmbeddedExtensionManager,
-} = ChromeUtils.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {});
-
-/**
- * This test case ensures that the LegacyExtensionsUtils.EmbeddedExtension:
- *  - load the embedded webextension resources from a "/webextension/" dir
- *    inside the XPI.
- *  - EmbeddedExtension.prototype.api returns an API object which exposes
- *    a working `runtime.onConnect` event object (e.g. the API can receive a port
- *    when the embedded webextension is started  and it can exchange messages
- *    with the background page).
- *  - EmbeddedExtension.prototype.startup/shutdown methods manage the embedded
- *    webextension lifecycle as expected.
- */
-add_task(async function test_embedded_webextension_utils() {
-  function backgroundScript() {
-    let port = browser.runtime.connect();
-
-    port.onMessage.addListener((msg) => {
-      if (msg == "legacy_extension -> webextension") {
-        port.postMessage("webextension -> legacy_extension");
-        port.disconnect();
-      }
-    });
-  }
-
-  const id = "@test.embedded.web.extension";
-
-  // Extensions.generateXPI is used here (and in the other hybrid addons tests in this same
-  // test dir) to be able to generate an xpi with the directory layout that we expect from
-  // an hybrid legacy+webextension addon (where all the embedded webextension resources are
-  // loaded from a 'webextension/' directory).
-  let fakeHybridAddonFile = Extension.generateZipFile({
-    "webextension/manifest.json": {
-      applications: {gecko: {id}},
-      name: "embedded webextension name",
-      manifest_version: 2,
-      version: "1.0",
-      background: {
-        scripts: ["bg.js"],
-      },
-    },
-    "webextension/bg.js": `new ${backgroundScript}`,
-  });
-
-  // Remove the generated xpi file and flush the its jar cache
-  // on cleanup.
-  registerCleanupFunction(() => {
-    Services.obs.notifyObservers(fakeHybridAddonFile, "flush-cache-entry");
-    fakeHybridAddonFile.remove(false);
-  });
-
-  let fileURI = Services.io.newFileURI(fakeHybridAddonFile);
-  let resourceURI = Services.io.newURI(`jar:${fileURI.spec}!/`);
-
-  let embeddedExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({
-    id, resourceURI,
-  });
-
-  ok(embeddedExtension, "Got the embeddedExtension object");
-
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1,
-        "Got the expected number of tracked embedded extension instances");
-
-  info("waiting embeddedExtension.startup");
-  let embeddedExtensionAPI = await embeddedExtension.startup();
-  ok(embeddedExtensionAPI, "Got the embeddedExtensionAPI object");
-
-  let waitConnectPort = new Promise(resolve => {
-    let {browser} = embeddedExtensionAPI;
-    browser.runtime.onConnect.addListener(port => {
-      resolve(port);
-    });
-  });
-
-  let port = await waitConnectPort;
-
-  ok(port, "Got the Port API object");
-
-  let waitPortMessage = new Promise(resolve => {
-    port.onMessage.addListener((msg) => {
-      resolve(msg);
-    });
-  });
-
-  port.postMessage("legacy_extension -> webextension");
-
-  let msg = await waitPortMessage;
-
-  equal(msg, "webextension -> legacy_extension",
-        "LegacyExtensionContext received the expected message from the webextension");
-
-  let waitForDisconnect = new Promise(resolve => {
-    port.onDisconnect.addListener(resolve);
-  });
-
-  info("Wait for the disconnect port event");
-  await waitForDisconnect;
-  info("Got the disconnect port event");
-
-  await embeddedExtension.shutdown();
-
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0,
-        "EmbeddedExtension instances has been untracked from the EmbeddedExtensionManager");
-});
-
-async function createManifestErrorTestCase(id, xpi, expectedError) {
-  // Remove the generated xpi file and flush the its jar cache
-  // on cleanup.
-  registerCleanupFunction(() => {
-    Services.obs.notifyObservers(xpi, "flush-cache-entry");
-    xpi.remove(false);
-  });
-
-  let fileURI = Services.io.newFileURI(xpi);
-  let resourceURI = Services.io.newURI(`jar:${fileURI.spec}!/`);
-
-  let embeddedExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({
-    id, resourceURI,
-  });
-
-  await Assert.rejects(embeddedExtension.startup(), expectedError,
-                       "embedded extension startup rejected");
-
-  // Shutdown a "never-started" addon with an embedded webextension should not
-  // raise any exception, and if it does this test will fail.
-  await embeddedExtension.shutdown();
-}
-
-add_task(async function test_startup_error_empty_manifest() {
-  const id = "empty-manifest@test.embedded.web.extension";
-  const files = {
-    "webextension/manifest.json": ``,
-  };
-  const expectedError = /NS_BASE_STREAM_CLOSED/;
-
-  let fakeHybridAddonFile = Extension.generateZipFile(files);
-
-  await createManifestErrorTestCase(id, fakeHybridAddonFile, expectedError);
-});
-
-add_task(async function test_startup_error_invalid_json_manifest() {
-  const id = "invalid-json-manifest@test.embedded.web.extension";
-  const files = {
-    "webextension/manifest.json": `{ "name": }`,
-  };
-  const expectedError = /JSON.parse:/;
-
-  let fakeHybridAddonFile = Extension.generateZipFile(files);
-
-  await createManifestErrorTestCase(id, fakeHybridAddonFile, expectedError);
-});
-
-add_task(async function test_startup_error_blocking_validation_errors() {
-  const id = "blocking-manifest-validation-error@test.embedded.web.extension";
-  const files = {
-    "webextension/manifest.json": {
-      name: "embedded webextension name",
-      manifest_version: 2,
-      version: "1.0",
-      background: {
-        scripts: {},
-      },
-    },
-  };
-
-  function expectedError(actual) {
-    if (actual.errors && actual.errors.length == 1 &&
-        actual.errors[0].startsWith("Reading manifest:")) {
-      return true;
-    }
-
-    return false;
-  }
-
-  let fakeHybridAddonFile = Extension.generateZipFile(files);
-
-  await createManifestErrorTestCase(id, fakeHybridAddonFile, expectedError);
-});
--- a/toolkit/components/extensions/test/xpcshell/test_locale_data.js
+++ b/toolkit/components/extensions/test/xpcshell/test_locale_data.js
@@ -4,27 +4,27 @@ ChromeUtils.import("resource://gre/modul
 
 /* globals ExtensionData */
 
 const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
 async function generateAddon(data) {
   let id = uuidGenerator.generateUUID().number;
 
-  data = Object.assign({embedded: true}, data);
-  data.manifest = Object.assign({applications: {gecko: {id}}}, data.manifest);
+  data = Object.assign({}, data);
+  data.manifest = Object.assign({applications: {gecko: {id}}}, data.manifest || {});
 
   let xpi = Extension.generateXPI(data);
   registerCleanupFunction(() => {
     Services.obs.notifyObservers(xpi, "flush-cache-entry");
     xpi.remove(false);
   });
 
   let fileURI = Services.io.newFileURI(xpi);
-  let jarURI = NetUtil.newURI(`jar:${fileURI.spec}!/webextension/`);
+  let jarURI = NetUtil.newURI(`jar:${fileURI.spec}!/`);
 
   let extension = new ExtensionData(jarURI);
   await extension.loadManifest();
 
   return extension;
 }
 
 add_task(async function testMissingDefaultLocale() {
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -55,18 +55,16 @@ fail-if = appname == "thunderbird"
 [test_ext_extension.js]
 [test_ext_extensionPreferencesManager.js]
 [test_ext_extensionSettingsStore.js]
 [test_ext_extension_content_telemetry.js]
 skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
 [test_ext_extension_startup_telemetry.js]
 [test_ext_geturl.js]
 [test_ext_idle.js]
-[test_ext_legacy_extension_context.js]
-[test_ext_legacy_extension_embedding.js]
 [test_ext_localStorage.js]
 [test_ext_management.js]
 skip-if = (os == "win" && !debug) #Bug 1419183 disable on Windows
 [test_ext_management_uninstall_self.js]
 [test_ext_messaging_startup.js]
 skip-if = appname == "thunderbird" || (os == "android" && debug)
 [test_ext_onmessage_removelistener.js]
 skip-if = true # This test no longer tests what it is meant to test.
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -954,18 +954,17 @@ var AddonTestUtils = {
            '     xmlns:em="http://www.mozilla.org/2004/em-rdf#">\n';
 
     rdf += '<Description about="urn:mozilla:install-manifest">\n';
 
     data = Object.assign({}, defaults, data);
 
     let props = ["id", "version", "type", "internalName", "updateURL",
                  "optionsURL", "optionsType", "aboutURL", "iconURL",
-                 "skinnable", "bootstrap", "strictCompatibility",
-                 "hasEmbeddedWebExtension"];
+                 "skinnable", "bootstrap", "strictCompatibility"];
     rdf += this._writeProps(data, props);
 
     rdf += this._writeLocaleStrings(data);
 
     for (let platform of data.targetPlatforms || [])
       rdf += escaped`<em:targetPlatform>${platform}</em:targetPlatform>\n`;
 
     for (let app of data.targetApplications || []) {
--- a/toolkit/mozapps/extensions/internal/RDFManifestConverter.jsm
+++ b/toolkit/mozapps/extensions/internal/RDFManifestConverter.jsm
@@ -87,18 +87,17 @@ class InstallRDF extends Manifest {
   }
 
   decode() {
     let root = this.ds.getResource(RDFURI_INSTALL_MANIFEST_ROOT);
     let result = {};
 
     let props = ["id", "version", "type", "updateURL", "optionsURL",
                  "optionsType", "aboutURL", "iconURL",
-                 "bootstrap", "unpack", "strictCompatibility",
-                 "hasEmbeddedWebExtension"];
+                 "bootstrap", "unpack", "strictCompatibility"];
     this._readProps(root, result, props);
 
     let decodeTargetApplication = source => {
       let app = {};
       this._readProps(source, app, ["id", "minVersion", "maxVersion"]);
       return app;
     };
 
--- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
@@ -109,17 +109,17 @@ const PROP_JSON_FIELDS = ["id", "syncGUI
                           "optionsType", "optionsBrowserStyle", "aboutURL",
                           "defaultLocale", "visible", "active", "userDisabled",
                           "appDisabled", "pendingUninstall", "installDate",
                           "updateDate", "applyBackgroundUpdates", "path",
                           "skinnable", "sourceURI", "releaseNotesURI",
                           "softDisabled", "foreignInstall",
                           "strictCompatibility", "locales", "targetApplications",
                           "targetPlatforms", "signedState",
-                          "seen", "dependencies", "hasEmbeddedWebExtension",
+                          "seen", "dependencies",
                           "userPermissions", "icons", "iconURL",
                           "blocklistState", "blocklistURL", "startupData",
                           "previewImage", "hidden", "installTelemetryInfo"];
 
 const LEGACY_TYPES = new Set([
   "extension",
 ]);
 
@@ -296,17 +296,16 @@ class AddonInternal {
 
     /**
      * @property {Array<string>} dependencies
      *   An array of bootstrapped add-on IDs on which this add-on depends.
      *   The add-on will remain appDisabled if any of the dependent
      *   add-ons is not installed and enabled.
      */
     this.dependencies = EMPTY_ARRAY;
-    this.hasEmbeddedWebExtension = false;
 
     if (addonData) {
       copyProperties(addonData, PROP_JSON_FIELDS, this);
       this.location = addonData.location;
 
       if (!this.dependencies)
         this.dependencies = [];
       Object.freeze(this.dependencies);
@@ -705,20 +704,16 @@ AddonWrapper = class {
   get __AddonInternal__() {
     return AppConstants.DEBUG ? addonFor(this) : undefined;
   }
 
   get seen() {
     return addonFor(this).seen;
   }
 
-  get hasEmbeddedWebExtension() {
-    return addonFor(this).hasEmbeddedWebExtension;
-  }
-
   markAsSeen() {
     addonFor(this).seen = true;
     XPIDatabase.saveChanges();
   }
 
   get installTelemetryInfo() {
     const addon = addonFor(this);
     if (!addon.installTelemetryInfo && addon.location) {
@@ -752,17 +747,17 @@ AddonWrapper = class {
 
   get optionsURL() {
     if (!this.isActive) {
       return null;
     }
 
     let addon = addonFor(this);
     if (addon.optionsURL) {
-      if (this.isWebExtension || this.hasEmbeddedWebExtension) {
+      if (this.isWebExtension) {
         // The internal object's optionsURL property comes from the addons
         // DB and should be a relative URL.  However, extensions with
         // options pages installed before bug 1293721 was fixed got absolute
         // URLs in the addons db.  This code handles both cases.
         let policy = WebExtensionPolicy.getByID(addon.id);
         if (!policy) {
           return null;
         }
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -640,36 +640,21 @@ async function loadManifestFromRDF(aUri,
                                manifest.strictCompatibility == "true");
 
   // Only read these properties for extensions.
   if (addon.type == "extension") {
     if (manifest.bootstrap != "true") {
       throw new Error("Non-restartless extensions no longer supported");
     }
 
-    addon.hasEmbeddedWebExtension = manifest.hasEmbeddedWebExtension == "true";
-
     if (addon.optionsType &&
         addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_BROWSER &&
         addon.optionsType != AddonManager.OPTIONS_TYPE_TAB) {
       throw new Error("Install manifest specifies unknown optionsType: " + addon.optionsType);
     }
-
-    if (addon.hasEmbeddedWebExtension) {
-      let uri = Services.io.newURI("webextension/manifest.json", null, aUri);
-      let embeddedAddon = await loadManifestFromWebManifest(uri, aPackage);
-      if (embeddedAddon.optionsURL) {
-        if (addon.optionsType || addon.optionsURL)
-          logger.warn(`Addon ${addon.id} specifies optionsType or optionsURL ` +
-                      `in both install.rdf and manifest.json`);
-
-        addon.optionsURL = embeddedAddon.optionsURL;
-        addon.optionsType = embeddedAddon.optionsType;
-      }
-    }
   } else {
     // Convert legacy dictionaries into a format the WebExtension
     // dictionary loader can process.
     if (addon.type === "dictionary") {
       addon.type = "webextension-dictionary";
       let dictionaries = {};
       await aPackage.iterFiles(({path}) => {
         let match = /^dictionaries\/([^\/]+)\.dic$/.exec(path);
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -30,17 +30,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   Dictionary: "resource://gre/modules/Extension.jsm",
   Extension: "resource://gre/modules/Extension.jsm",
   Langpack: "resource://gre/modules/Extension.jsm",
   FileUtils: "resource://gre/modules/FileUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   ConsoleAPI: "resource://gre/modules/Console.jsm",
   JSONFile: "resource://gre/modules/JSONFile.jsm",
-  LegacyExtensionsUtils: "resource://gre/modules/LegacyExtensionsUtils.jsm",
   TelemetrySession: "resource://gre/modules/TelemetrySession.jsm",
 
   XPIDatabase: "resource://gre/modules/addons/XPIDatabase.jsm",
   XPIDatabaseReconcile: "resource://gre/modules/addons/XPIDatabase.jsm",
   XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetters(this, {
@@ -371,17 +370,16 @@ function* iterDirectory(aDir) {
  * The on-disk state of an individual XPI, created from an Object
  * as stored in the addonStartup.json file.
  */
 const JSON_FIELDS = Object.freeze([
   "changed",
   "dependencies",
   "enabled",
   "file",
-  "hasEmbeddedWebExtension",
   "lastModifiedTime",
   "path",
   "runInSafeMode",
   "signedState",
   "startupData",
   "telemetryKey",
   "type",
   "version",
@@ -455,17 +453,16 @@ class XPIState {
    * data, to be saved to addonStartup.json.
    *
    * @returns {Object}
    */
   toJSON() {
     let json = {
       dependencies: this.dependencies,
       enabled: this.enabled,
-      hasEmbeddedWebExtension: this.hasEmbeddedWebExtension,
       lastModifiedTime: this.lastModifiedTime,
       path: this.relativePath,
       runInSafeMode: this.runInSafeMode,
       signedState: this.signedState,
       telemetryKey: this.telemetryKey,
       version: this.version,
     };
     if (this.type != "extension") {
@@ -535,17 +532,16 @@ class XPIState {
     this.version = aDBAddon.version;
     this.type = aDBAddon.type;
     if (aDBAddon.startupData) {
       this.startupData = aDBAddon.startupData;
     }
 
     this.telemetryKey = this.getTelemetryKey();
 
-    this.hasEmbeddedWebExtension = aDBAddon.hasEmbeddedWebExtension;
     this.dependencies = aDBAddon.dependencies;
     this.runInSafeMode = canRunInSafeMode(aDBAddon);
     this.signedState = aDBAddon.signedState;
     this.file = aDBAddon._sourceBundle;
 
     if (aUpdated || mustGetMod) {
       this.getModTime(this.file);
       if (this.lastModifiedTime != aDBAddon.updateDate) {
@@ -1574,31 +1570,16 @@ class BootstrapScope {
       };
 
       if (aMethod == "startup" && addon.startupData) {
         params.startupData = addon.startupData;
       }
 
       Object.assign(params, aExtraParams);
 
-      if (addon.hasEmbeddedWebExtension) {
-        let reason = Object.keys(BOOTSTRAP_REASONS).find(
-          key => BOOTSTRAP_REASONS[key] == aReason
-        );
-
-        if (aMethod == "startup") {
-          const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor(params);
-          params.webExtension = {
-            startup: () => webExtension.startup(reason),
-          };
-        } else if (aMethod == "shutdown") {
-          LegacyExtensionsUtils.getEmbeddedExtensionFor(params).shutdown(reason);
-        }
-      }
-
       let result;
       if (!method) {
         logger.warn(`Add-on ${addon.id} is missing bootstrap method ${aMethod}`);
       } else {
         logger.debug(`Calling bootstrap method ${aMethod} on ${addon.id} version ${addon.version}`);
 
         this._beforeCallBootstrapMethod(aMethod, params, aReason);
 
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js
+++ /dev/null
@@ -1,368 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-BootstrapMonitor.init();
-
-const profileDir = gProfD.clone();
-profileDir.append("extensions");
-
-createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49");
-
-// NOTE: the following import needs to be called after the `createAppInfo`
-// or it will fail Extension.jsm internally imports AddonManager.jsm and
-// AddonManager will raise a ReferenceError exception because it tried to
-// access an undefined `Services.appinfo` object.
-const { Management } = ChromeUtils.import("resource://gre/modules/Extension.jsm", {});
-
-const {
-  EmbeddedExtensionManager,
-} = ChromeUtils.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {});
-
-function promiseWebExtensionShutdown() {
-  return new Promise(resolve => {
-    let listener = (event, extension) => {
-      Management.off("shutdown", listener);
-      resolve(extension);
-    };
-
-    Management.on("shutdown", listener);
-  });
-}
-
-const BOOTSTRAP_WITHOUT_SHUTDOWN = String.raw`
-  Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this, [
-    "install", "startup", "uninstall",
-  ]);
-`;
-
-const EMBEDDED_WEBEXT_MANIFEST = JSON.stringify({
-  name: "embedded webextension addon",
-  manifest_version: 2,
-  version: "1.0",
-});
-
-/**
- *  This test case checks that an hasEmbeddedWebExtension addon property
- *  is persisted and restored correctly across restarts.
- */
-add_task(async function has_embedded_webextension_persisted() {
-  await promiseStartupManager();
-  const ID = "embedded-webextension-addon-persist@tests.mozilla.org";
-
-  const xpiFile = createTempXPIFile({
-    id: ID,
-    name: "Test Add-on",
-    version: "1.0",
-    bootstrap: true,
-    hasEmbeddedWebExtension: true,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1.9.2",
-    }],
-  }, {
-    "bootstrap.js": BOOTSTRAP_MONITOR_BOOTSTRAP_JS,
-    "webextension/manifest.json": EMBEDDED_WEBEXT_MANIFEST,
-  });
-
-  await promiseInstallFile(xpiFile);
-
-  let addon = await promiseAddonByID(ID);
-
-  notEqual(addon, null, "Got an addon object as expected");
-  equal(addon.version, "1.0", "Got the expected version");
-  equal(addon.hasEmbeddedWebExtension, true,
-        "Got the expected hasEmbeddedWebExtension value");
-
-  // Check that the addon has been installed and started.
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
-
-  let startupInfo = BootstrapMonitor.started.get(ID);
-  ok(("webExtension" in startupInfo.data),
-     "Got an webExtension property in the startup bootstrap method params");
-  ok(("startup" in startupInfo.data.webExtension),
-     "Got the expected 'startup' property in the webExtension object");
-
-  // After restarting the manager, the add-on should still have the
-  // hasEmbeddedWebExtension property as expected.
-  await promiseRestartManager();
-
-  let persisted = aomStartup.readStartupData()["app-profile"].addons;
-
-  ok(ID in persisted, "Hybrid add-on persisted to bootstrappedAddons.");
-  equal(persisted[ID].hasEmbeddedWebExtension, true,
-        "hasEmbeddedWebExtension flag persisted to bootstrappedAddons.");
-
-  // Check that the addon has been installed and started.
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
-
-  addon = await promiseAddonByID(ID);
-  notEqual(addon, null, "Got an addon object as expected");
-  equal(addon.hasEmbeddedWebExtension, true, "Got the expected hasEmbeddedWebExtension value");
-
-  // Check that the addon has been installed and started.
-  let newStartupInfo = BootstrapMonitor.started.get(ID);
-  ok(("webExtension" in newStartupInfo.data),
-     "Got an webExtension property in the startup bootstrap method params");
-  ok(("startup" in newStartupInfo.data.webExtension),
-     "Got the expected 'startup' property in the webExtension object");
-
-  let waitUninstall = promiseAddonEvent("onUninstalled");
-  await addon.uninstall();
-  await waitUninstall;
-});
-
-/**
- *  This test case checks that an addon with hasEmbeddedWebExtension set to true
- *  in its install.rdf gets the expected `embeddedWebExtension` object in the
- *  parameters of its bootstrap methods.
- */
-add_task(async function run_embedded_webext_bootstrap() {
-  const ID = "embedded-webextension-addon2@tests.mozilla.org";
-
-  const xpiFile = createTempXPIFile({
-    id: ID,
-    name: "Test Add-on",
-    version: "1.0",
-    bootstrap: true,
-    hasEmbeddedWebExtension: true,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1.9.2",
-    }],
-  }, {
-    "bootstrap.js": BOOTSTRAP_MONITOR_BOOTSTRAP_JS,
-    "webextension/manifest.json": EMBEDDED_WEBEXT_MANIFEST,
-  });
-
-  await AddonManager.installTemporaryAddon(xpiFile);
-
-  let addon = await promiseAddonByID(ID);
-
-  notEqual(addon, null, "Got an addon object as expected");
-  equal(addon.version, "1.0", "Got the expected version");
-  equal(addon.hasEmbeddedWebExtension, true,
-        "Got the expected hasEmbeddedWebExtension value");
-
-  // Check that the addon has been installed and started.
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-
-  let installInfo = BootstrapMonitor.installed.get(ID);
-  ok(!("webExtension" in installInfo.data),
-     "No webExtension property is expected in the install bootstrap method params");
-
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
-
-  let startupInfo = BootstrapMonitor.started.get(ID);
-
-  ok(("webExtension" in startupInfo.data),
-     "Got an webExtension property in the startup bootstrap method params");
-
-  ok(("startup" in startupInfo.data.webExtension),
-     "Got the expected 'startup' property in the webExtension object");
-
-  const waitForWebExtensionStartup = promiseWebExtensionStartup();
-
-  const embeddedAPI = await startupInfo.data.webExtension.startup();
-
-  // WebExtension startup should have been fully resolved.
-  await waitForWebExtensionStartup;
-
-  Assert.deepEqual(
-    Object.keys(embeddedAPI.browser.runtime).sort(),
-    ["onConnect", "onMessage"],
-    `Got the expected 'runtime' in the 'browser' API object`
-  );
-
-  // Uninstall the addon and wait that the embedded webextension has been stopped and
-  // test the params of the shutdown and uninstall bootstrap method.
-  let waitForWebExtensionShutdown = promiseWebExtensionShutdown();
-  let waitUninstall = promiseAddonEvent("onUninstalled");
-  await addon.uninstall();
-  await waitForWebExtensionShutdown;
-  await waitUninstall;
-
-  BootstrapMonitor.checkAddonNotStarted(ID, "1.0");
-
-  let shutdownInfo = BootstrapMonitor.stopped.get(ID);
-  ok(!("webExtension" in shutdownInfo.data),
-     "No webExtension property is expected in the shutdown bootstrap method params");
-
-  let uninstallInfo = BootstrapMonitor.uninstalled.get(ID);
-  ok(!("webExtension" in uninstallInfo.data),
-     "No webExtension property is expected in the uninstall bootstrap method params");
-});
-
-/**
- *  This test case checks that an addon with hasEmbeddedWebExtension can be reloaded
- *  without raising unexpected exceptions due to race conditions.
- */
-add_task(async function reload_embedded_webext_bootstrap() {
-  const ID = "embedded-webextension-addon2@tests.mozilla.org";
-
-  // No embedded webextension should be currently around.
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0,
-        "No embedded extension instance should be tracked here");
-
-  const xpiFile = createTempXPIFile({
-    id: ID,
-    name: "Test Add-on",
-    version: "1.0",
-    bootstrap: true,
-    hasEmbeddedWebExtension: true,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1.9.2",
-    }],
-  }, {
-    "bootstrap.js": BOOTSTRAP_MONITOR_BOOTSTRAP_JS,
-    "webextension/manifest.json": EMBEDDED_WEBEXT_MANIFEST,
-  });
-
-  await AddonManager.installTemporaryAddon(xpiFile);
-
-  let addon = await promiseAddonByID(ID);
-
-  notEqual(addon, null, "Got an addon object as expected");
-  equal(addon.version, "1.0", "Got the expected version");
-  equal(addon.isActive, true, "The Addon is active");
-  equal(addon.appDisabled, false, "The addon is not app disabled");
-  equal(addon.userDisabled, false, "The addon is not user disabled");
-
-  // Check that the addon has been installed and started.
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
-
-  // Only one embedded extension.
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1,
-        "Got the expected number of tracked extension instances");
-
-  const embeddedWebExtension = EmbeddedExtensionManager.embeddedExtensionsByAddonId.get(ID);
-
-  let startupInfo = BootstrapMonitor.started.get(ID);
-  await startupInfo.data.webExtension.startup();
-
-  const waitForAddonDisabled = promiseAddonEvent("onDisabled");
-  await addon.disable();
-  await waitForAddonDisabled;
-
-  // No embedded webextension should be currently around.
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0,
-        "No embedded extension instance should be tracked here");
-
-  const waitForAddonEnabled = promiseAddonEvent("onEnabled");
-  await addon.enable();
-  await waitForAddonEnabled;
-
-  // Only one embedded extension.
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1,
-        "Got the expected number of tracked extension instances");
-
-  const embeddedWebExtensionAfterEnabled = EmbeddedExtensionManager.embeddedExtensionsByAddonId.get(ID);
-  notEqual(embeddedWebExtensionAfterEnabled, embeddedWebExtension,
-           "Got a new EmbeddedExtension instance after the addon has been disabled and then enabled");
-
-  startupInfo = BootstrapMonitor.started.get(ID);
-  await startupInfo.data.webExtension.startup();
-
-  const waitForReinstalled = promiseAddonEvent("onInstalled");
-  await addon.reload();
-  await waitForReinstalled;
-
-  // No leaked embedded extension after the previous reloads.
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1,
-        "Got the expected number of tracked extension instances");
-
-  const embeddedWebExtensionAfterReload = EmbeddedExtensionManager.embeddedExtensionsByAddonId.get(ID);
-  notEqual(embeddedWebExtensionAfterReload, embeddedWebExtensionAfterEnabled,
-           "Got a new EmbeddedExtension instance after the addon has been reloaded");
-
-  startupInfo = BootstrapMonitor.started.get(ID);
-  await startupInfo.data.webExtension.startup();
-
-  // Uninstall the test addon
-  let waitUninstalled = promiseAddonEvent("onUninstalled");
-  await addon.uninstall();
-  await waitUninstalled;
-
-  // No leaked embedded extension after uninstalling.
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0,
-        "No embedded extension instance should be tracked after the addon uninstall");
-});
-
-/**
- *  This test case checks that an addon with hasEmbeddedWebExtension without
- *  a bootstrap shutdown method stops the embedded webextension.
- */
-add_task(async function shutdown_embedded_webext_without_bootstrap_shutdown() {
-  const ID = "embedded-webextension-without-shutdown@tests.mozilla.org";
-
-  // No embedded webextension should be currently around.
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0,
-        "No embedded extension instance should be tracked here");
-
-  const xpiFile = createTempXPIFile({
-    id: ID,
-    name: "Test Add-on",
-    version: "1.0",
-    bootstrap: true,
-    hasEmbeddedWebExtension: true,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1.9.2",
-    }],
-  }, {
-    "bootstrap.js": BOOTSTRAP_WITHOUT_SHUTDOWN,
-    "webextension/manifest.json": EMBEDDED_WEBEXT_MANIFEST,
-  });
-
-  await AddonManager.installTemporaryAddon(xpiFile);
-
-  let addon = await promiseAddonByID(ID);
-
-  notEqual(addon, null, "Got an addon object as expected");
-  equal(addon.version, "1.0", "Got the expected version");
-  equal(addon.isActive, true, "The Addon is active");
-  equal(addon.appDisabled, false, "The addon is not app disabled");
-  equal(addon.userDisabled, false, "The addon is not user disabled");
-
-  // Check that the addon has been installed and started.
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
-
-  // Only one embedded extension.
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1,
-        "Got the expected number of tracked extension instances");
-
-  const startupInfo = BootstrapMonitor.started.get(ID);
-
-  const waitForWebExtensionStartup = promiseWebExtensionStartup();
-
-  await startupInfo.data.webExtension.startup();
-
-  // WebExtension startup should have been fully resolved.
-  await waitForWebExtensionStartup;
-
-  // Fake the BootstrapMonitor notification, or the shutdown checks defined in head_addons.js
-  // will fail because of the missing shutdown method.
-  const fakeShutdownInfo = Object.assign(startupInfo, {
-    event: "shutdown",
-    reason: 2,
-  });
-  Services.obs.notifyObservers({}, "bootstrapmonitor-event", JSON.stringify(fakeShutdownInfo));
-
-  // Uninstall the addon.
-  const waitUninstalled = promiseAddonEvent("onUninstalled");
-  await addon.uninstall();
-  await waitUninstalled;
-
-  // No leaked embedded extension after uninstalling.
-  equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0,
-        "No embedded extension instance should be tracked after the addon uninstall");
-});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -274,18 +274,16 @@ skip-if = os == "android"
 skip-if = os == "android"
 [test_upgrade.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 run-sequentially = Uses global XCurProcD dir.
 [test_upgrade_incompatible.js]
 [test_webextension.js]
 tags = webextensions
-[test_webextension_embedded.js]
-tags = webextensions
 [test_webextension_events.js]
 tags = webextensions
 [test_webextension_icons.js]
 tags = webextensions
 [test_webextension_install.js]
 tags = webextensions
 [test_webextension_install_syntax_error.js]
 tags = webextensions