imported patch bootstrap draft
authorGeoff Lankow <geoff@darktrojan.net>
Fri, 17 May 2019 23:12:07 +1200
changeset 73762 3c6b77769dacbc6e2ec3a267104cd4c6b345c2c7
parent 73759 4516cf2270f464e5e8bc19fbf2b0ca6fff10ca58
child 73763 d91a29a084d716c1dca565d1279ea06e7f7bf96b
push id8470
push usergeoff@darktrojan.net
push dateFri, 17 May 2019 11:13:09 +0000
treeherdertry-comm-central@d91a29a084d7 [default view] [failures only]
imported patch bootstrap
common/src/BootstrapLoader.jsm
common/src/RDFDataSource.jsm
common/src/RDFManifestConverter.jsm
common/src/moz.build
common/test/xpcshell/head_addons.js
common/test/xpcshell/test_bootstrap.js
common/test/xpcshell/test_bootstrap_const.js
common/test/xpcshell/test_bootstrap_globals.js
common/test/xpcshell/test_bootstrapped_chrome_manifest.js
common/test/xpcshell/test_invalid_install_rdf.js
common/test/xpcshell/test_manifest.js
common/test/xpcshell/test_manifest_locales.js
common/test/xpcshell/xpcshell.ini
mail/components/extensions/ext-mail.json
mail/components/extensions/jar.mn
mail/components/extensions/parent/.eslintrc.js
mail/components/extensions/parent/ext-bootstrapped.js
mail/components/extensions/schemas/bootstrapped.json
mail/components/mailComponents.manifest
mail/components/mailGlue.js
mail/test/resources/jsbridge/jsbridge/extension/install.rdf
mail/test/resources/jsbridge/jsbridge/extension/manifest.json
mail/test/resources/jsbridge/setup.py
mail/test/resources/mozmill/mozmill/extension/install.rdf
mail/test/resources/mozmill/mozmill/extension/manifest.json
mail/test/resources/mozmill/setup.py
deleted file mode 100644
--- a/common/src/RDFDataSource.jsm
+++ /dev/null
@@ -1,1520 +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/. */
-
-/**
- * This module creates a new API for accessing and modifying RDF graphs. The
- * goal is to be able to serialise the graph in a human readable form. Also
- * if the graph was originally loaded from an RDF/XML the serialisation should
- * closely match the original with any new data closely following the existing
- * layout. The output should always be compatible with Mozilla's RDF parser.
- *
- * This is all achieved by using a DOM Document to hold the current state of the
- * graph in XML form. This can be initially loaded and parsed from disk or
- * a blank document used for an empty graph. As assertions are added to the
- * graph, appropriate DOM nodes are added to the document to represent them
- * along with any necessary whitespace to properly layout the XML.
- *
- * In general the order of adding assertions to the graph will impact the form
- * the serialisation takes. If a resource is first added as the object of an
- * assertion then it will eventually be serialised inside the assertion's
- * property element. If a resource is first added as the subject of an assertion
- * then it will be serialised at the top level of the XML.
- */
-
-const NS_XML = "http://www.w3.org/XML/1998/namespace";
-const NS_XMLNS = "http://www.w3.org/2000/xmlns/";
-const NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
-const NS_NC = "http://home.netscape.com/NC-rdf#";
-
-/* eslint prefer-template: 1 */
-
-function raw(strings) {
-  return strings.raw[0].replace(/\s+/, "");
-}
-
-// Copied from http://www.w3.org/TR/2000/REC-xml-20001006#CharClasses
-const XML_LETTER = raw`
-  \u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6
-  \u00F8-\u00FF\u0100-\u0131\u0134-\u013E\u0141-\u0148
-  \u014A-\u017E\u0180-\u01C3\u01CD-\u01F0\u01F4-\u01F5
-  \u01FA-\u0217\u0250-\u02A8\u02BB-\u02C1\u0386\u0388-\u038A
-  \u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03D6\u03DA\u03DC
-  \u03DE\u03E0\u03E2-\u03F3\u0401-\u040C\u040E-\u044F
-  \u0451-\u045C\u045E-\u0481\u0490-\u04C4\u04C7-\u04C8
-  \u04CB-\u04CC\u04D0-\u04EB\u04EE-\u04F5\u04F8-\u04F9
-  \u0531-\u0556\u0559\u0561-\u0586\u05D0-\u05EA\u05F0-\u05F2
-  \u0621-\u063A\u0641-\u064A\u0671-\u06B7\u06BA-\u06BE
-  \u06C0-\u06CE\u06D0-\u06D3\u06D5\u06E5-\u06E6\u0905-\u0939
-  \u093D\u0958-\u0961\u0985-\u098C\u098F-\u0990\u0993-\u09A8
-  \u09AA-\u09B0\u09B2\u09B6-\u09B9\u09DC-\u09DD\u09DF-\u09E1
-  \u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28
-  \u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39
-  \u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8B\u0A8D
-  \u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3
-  \u0AB5-\u0AB9\u0ABD\u0AE0\u0B05-\u0B0C\u0B0F-\u0B10
-  \u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B36-\u0B39
-  \u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B85-\u0B8A\u0B8E-\u0B90
-  \u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4
-  \u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0C05-\u0C0C
-  \u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39
-  \u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8
-  \u0CAA-\u0CB3\u0CB5-\u0CB9\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C
-  \u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61
-  \u0E01-\u0E2E\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82
-  \u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F
-  \u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EAE\u0EB0
-  \u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0F40-\u0F47\u0F49-\u0F69
-  \u10A0-\u10C5\u10D0-\u10F6\u1100\u1102-\u1103\u1105-\u1107
-  \u1109\u110B-\u110C\u110E-\u1112\u113C\u113E\u1140\u114C
-  \u114E\u1150\u1154-\u1155\u1159\u115F-\u1161\u1163\u1165
-  \u1167\u1169\u116D-\u116E\u1172-\u1173\u1175\u119E\u11A8
-  \u11AB\u11AE-\u11AF\u11B7-\u11B8\u11BA\u11BC-\u11C2\u11EB
-  \u11F0\u11F9\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15
-  \u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57
-  \u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC
-  \u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB
-  \u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2126\u212A-\u212B
-  \u212E\u2180-\u2182\u3041-\u3094\u30A1-\u30FA\u3105-\u312C
-  \uAC00-\uD7A3\u4E00-\u9FA5\u3007\u3021-\u3029
-`;
-const XML_DIGIT = raw`
-  \u0030-\u0039\u0660-\u0669\u06F0-\u06F9\u0966-\u096F
-  \u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F
-  \u0BE7-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F
-  \u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29
-`;
-const XML_COMBINING = raw`
-  \u0300-\u0345\u0360-\u0361\u0483-\u0486\u0591-\u05A1
-  \u05A3-\u05B9\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4
-  \u064B-\u0652\u0670\u06D6-\u06DC\u06DD-\u06DF\u06E0-\u06E4
-  \u06E7-\u06E8\u06EA-\u06ED\u0901-\u0903\u093C\u093E-\u094C
-  \u094D\u0951-\u0954\u0962-\u0963\u0981-\u0983\u09BC\u09BE
-  \u09BF\u09C0-\u09C4\u09C7-\u09C8\u09CB-\u09CD\u09D7
-  \u09E2-\u09E3\u0A02\u0A3C\u0A3E\u0A3F\u0A40-\u0A42
-  \u0A47-\u0A48\u0A4B-\u0A4D\u0A70-\u0A71\u0A81-\u0A83
-  \u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0B01-\u0B03
-  \u0B3C\u0B3E-\u0B43\u0B47-\u0B48\u0B4B-\u0B4D\u0B56-\u0B57
-  \u0B82-\u0B83\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7
-  \u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D
-  \u0C55-\u0C56\u0C82-\u0C83\u0CBE-\u0CC4\u0CC6-\u0CC8
-  \u0CCA-\u0CCD\u0CD5-\u0CD6\u0D02-\u0D03\u0D3E-\u0D43
-  \u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0E31\u0E34-\u0E3A
-  \u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EC8-\u0ECD
-  \u0F18-\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84
-  \u0F86-\u0F8B\u0F90-\u0F95\u0F97\u0F99-\u0FAD\u0FB1-\u0FB7
-  \u0FB9\u20D0-\u20DC\u20E1\u302A-\u302F\u3099\u309A
-`;
-const XML_EXTENDER = raw`
-  \u00B7\u02D0\u02D1\u0387\u0640\u0E46\u0EC6\u3005
-  \u3031-\u3035\u309D-\u309E\u30FC-\u30FE
-`;
-const XML_NCNAMECHAR = String.raw`${XML_LETTER}${XML_DIGIT}\.\-_${XML_COMBINING}${XML_EXTENDER}`;
-const XML_NCNAME = new RegExp(`^[${XML_LETTER}_][${XML_NCNAMECHAR}]*$`);
-
-const URI_SUFFIX = /[A-Za-z_][0-9A-Za-z\.\-_]*$/;
-const INDENT = /\n([ \t]*)$/;
-const RDF_LISTITEM = /^http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#_\d+$/;
-
-const RDF_NODE_INVALID_TYPES =
-        ["RDF", "ID", "about", "bagID", "parseType", "resource", "nodeID",
-         "li", "aboutEach", "aboutEachPrefix"];
-const RDF_PROPERTY_INVALID_TYPES =
-        ["Description", "RDF", "ID", "about", "bagID", "parseType", "resource",
-         "nodeID", "aboutEach", "aboutEachPrefix"];
-
-/**
- * Whether to use properly namespaces attributes for rdf:about etc...
- * When on this produces poor output in the event that the rdf namespace is the
- * default namespace, and the parser recognises unnamespaced attributes and
- * most of our rdf examples are unnamespaced so leaving off for the time being.
- */
-const USE_RDFNS_ATTR = false;
-
-var EXPORTED_SYMBOLS = ["RDFLiteral", "RDFIntLiteral", "RDFDateLiteral",
-                        "RDFBlankNode", "RDFResource", "RDFDataSource"];
-
-const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser", "Element", "XMLSerializer", "fetch"]);
-
-ChromeUtils.defineModuleGetter(this, "OS",
-                               "resource://gre/modules/osfile.jsm");
-ChromeUtils.defineModuleGetter(this, "Services",
-                               "resource://gre/modules/Services.jsm");
-
-function isAttr(obj) {
-  return obj && typeof obj == "object" && ChromeUtils.getClassName(obj) == "Attr";
-}
-function isDocument(obj) {
-  return obj && typeof obj == "object" && obj.nodeType == Element.DOCUMENT_NODE;
-}
-function isElement(obj) {
-  return Element.isInstance(obj);
-}
-function isText(obj) {
-  return obj && typeof obj == "object" && ChromeUtils.getClassName(obj) == "Text";
-}
-
-/**
- * Logs an error message to the error console
- */
-function ERROR(str) {
-  Cu.reportError(str);
-}
-
-function RDF_R(name) {
-  return NS_RDF + name;
-}
-
-function renameNode(domnode, namespaceURI, qname) {
-  if (isElement(domnode)) {
-    var newdomnode = domnode.ownerDocument.createElementNS(namespaceURI, qname);
-    if ("listCounter" in domnode)
-      newdomnode.listCounter = domnode.listCounter;
-    domnode.replaceWith(newdomnode);
-    while (domnode.firstChild)
-      newdomnode.appendChild(domnode.firstChild);
-    for (let attr of domnode.attributes) {
-      domnode.removeAttributeNode(attr);
-      newdomnode.setAttributeNode(attr);
-    }
-    return newdomnode;
-  } else if (isAttr(domnode)) {
-    if (domnode.ownerElement.hasAttribute(namespaceURI, qname))
-      throw new Error("attribute already exists");
-    var attr = domnode.ownerDocument.createAttributeNS(namespaceURI, qname);
-    attr.value = domnode.value;
-    domnode.ownerElement.setAttributeNode(attr);
-    domnode.ownerElement.removeAttributeNode(domnode);
-    return attr;
-  }
-  throw new Error("cannot rename node of this type");
-}
-
-function predicateOrder(a, b) {
-  return a.getPredicate().localeCompare(b.getPredicate());
-}
-
-/**
- * Returns either an rdf namespaced attribute or an un-namespaced attribute
- * value. Returns null if neither exists,
- */
-function getRDFAttribute(element, name) {
-  if (element.hasAttributeNS(NS_RDF, name))
-    return element.getAttributeNS(NS_RDF, name);
-  if (element.hasAttribute(name))
-    return element.getAttribute(name);
-  return undefined;
-}
-
-/**
- * Represents an assertion in the datasource
- */
-class RDFAssertion {
-  constructor(subject, predicate, object) {
-    if (!(subject instanceof RDFSubject))
-      throw new Error("subject must be an RDFSubject");
-
-    if (typeof(predicate) != "string")
-      throw new Error("predicate must be a string URI");
-
-    if (!(object instanceof RDFLiteral) && !(object instanceof RDFSubject))
-      throw new Error("object must be a concrete RDFNode");
-
-    if (object instanceof RDFSubject && object._ds != subject._ds)
-      throw new Error("object must be from the same datasource as subject");
-
-    // The subject on this assertion, an RDFSubject
-    this._subject = subject;
-    // The predicate, a string
-    this._predicate = predicate;
-    // The object, an RDFNode
-    this._object = object;
-    // The datasource this assertion exists in
-    this._ds = this._subject._ds;
-    // Marks that _DOMnode is the subject's element
-    this._isSubjectElement = false;
-    // The DOM node that represents this assertion. Could be a property element,
-    // a property attribute or the subject's element for rdf:type
-    this._DOMNode = null;
-  }
-
-  /**
-   * Adds content to _DOMnode to store this assertion in the DOM document.
-   */
-  _applyToDOMNode() {
-    if (this._object instanceof RDFLiteral)
-      this._object._applyToDOMNode(this._ds, this._DOMnode);
-    else
-      this._object._addReferenceToElement(this._DOMnode);
-  }
-
-  /**
-   * Returns the DOM Element linked to the subject that this assertion is
-   * attached to.
-   */
-  _getSubjectElement() {
-    if (isAttr(this._DOMnode))
-      return this._DOMnode.ownerElement;
-    if (this._isSubjectElement)
-      return this._DOMnode;
-    return this._DOMnode.parentNode;
-  }
-
-  getSubject() {
-    return this._subject;
-  }
-
-  getPredicate() {
-    return this._predicate;
-  }
-
-  getObject() {
-    return this._object;
-  }
-}
-
-class RDFNode {
-  equals(rdfnode) {
-    return (rdfnode.constructor === this.constructor &&
-            rdfnode._value == this._value);
-  }
-}
-
-/**
- * A simple literal value
- */
-class RDFLiteral extends RDFNode {
-  constructor(value) {
-    super();
-    this._value = value;
-  }
-
-  /**
-   * This stores the value of the literal in the given DOM node
-   */
-  _applyToDOMNode(ds, domnode) {
-    if (isElement(domnode))
-      domnode.textContent = this._value;
-    else if (isAttr(domnode))
-      domnode.value = this._value;
-    else
-      throw new Error("cannot use this node for a literal");
-  }
-
-  getValue() {
-    return this._value;
-  }
-}
-
-/**
- * A literal that is integer typed.
- */
-class RDFIntLiteral extends RDFLiteral {
-  constructor(value) {
-    super(parseInt(value));
-  }
-
-  /**
-   * This stores the value of the literal in the given DOM node
-   */
-  _applyToDOMNode(ds, domnode) {
-    if (!isElement(domnode))
-      throw new Error("cannot use this node for a literal");
-
-    RDFLiteral.prototype._applyToDOMNode.call(this, ds, domnode);
-    var prefix = ds._resolvePrefix(domnode, `${NS_NC}parseType`);
-    domnode.setAttributeNS(prefix.namespaceURI, prefix.qname, "Integer");
-  }
-}
-
-/**
- * A literal that represents a date.
- */
-class RDFDateLiteral extends RDFLiteral {
-  constructor(value) {
-    if (!(value instanceof Date))
-      throw new Error("RDFDateLiteral must be constructed with a Date object");
-
-    super(value);
-  }
-
-  /**
-   * This stores the value of the literal in the given DOM node
-   */
-  _applyToDOMNode(ds, domnode) {
-    if (!isElement(domnode))
-      throw new Error("cannot use this node for a literal");
-
-    domnode.textContent = this._value.getTime();
-    var prefix = ds._resolvePrefix(domnode, `${NS_NC}parseType`);
-    domnode.setAttributeNS(prefix.namespaceURI, prefix.qname, "Date");
-  }
-}
-
-/**
- * This is an RDF node that can be a subject so a resource or a blank node
- */
-class RDFSubject extends RDFNode {
-  constructor(ds) {
-    super();
-    // A lookup of the assertions with this as the subject. Keyed on predicate
-    this._assertions = {};
-    // A lookup of the assertions with this as the object. Keyed on predicate
-    this._backwards = {};
-    // The datasource this subject belongs to
-    this._ds = ds;
-    // The DOM elements in the document that represent this subject. Array of Element
-    this._elements = [];
-  }
-
-  /**
-   * Creates a new Element in the document for holding assertions about this
-   * subject. The URI controls what tagname to use.
-   */
-  _createElement(uri) {
-    // Seek an appropriate reference to this node to add this node under
-    var parent = null;
-    for (var p in this._backwards) {
-      for (let back of this._backwards[p]) {
-        // Don't add under an rdf:type
-        if (back.getPredicate() == RDF_R("type"))
-          continue;
-        // The assertion already has a child node, probably one of ours
-        if (back._DOMnode.firstChild)
-          continue;
-        parent = back._DOMnode;
-        var element = this._ds._addElement(parent, uri);
-        this._removeReferenceFromElement(parent);
-        break;
-      }
-      if (parent)
-        break;
-    }
-
-    // No back assertions that are sensible to use
-    if (!parent)
-      element = this._ds._addElement(this._ds._document.documentElement, uri);
-
-    element.listCounter = 1;
-    this._applyToElement(element);
-    this._elements.push(element);
-    return element;
-  }
-
-  /**
-   * When a DOM node representing this subject is removed from the document
-   * we must remove the node and recreate any child assertions elsewhere.
-   */
-  _removeElement(element) {
-    var pos = this._elements.indexOf(element);
-    if (pos < 0)
-      throw new Error("invalid element");
-    this._elements.splice(pos, 1);
-    if (element.parentNode != element.ownerDocument.documentElement)
-      this._addReferenceToElement(element.parentNode);
-    this._ds._removeElement(element);
-
-    // Find all the assertions that are represented here and create new
-    // nodes for them.
-    for (var predicate in this._assertions) {
-      for (let assertion of this._assertions[predicate]) {
-        if (assertion._getSubjectElement() == element)
-          this._createDOMNodeForAssertion(assertion);
-      }
-    }
-  }
-
-  /**
-   * Creates a DOM node to represent the assertion in the document. If the
-   * assertion has rdf:type as the predicate then an attempt will be made to
-   * create a typed subject Element, otherwise a new property Element is
-   * created. For list items an attempt is made to find an appropriate container
-   * that an rdf:li element can be added to.
-   */
-  _createDOMNodeForAssertion(assertion) {
-    let elements;
-    if (RDF_LISTITEM.test(assertion.getPredicate())) {
-      // Find all the containers
-      elements = this._elements.filter(function(element) {
-        return (element.namespaceURI == NS_RDF && (element.localName == "Seq" ||
-                                                   element.localName == "Bag" ||
-                                                   element.localName == "Alt"));
-      });
-      if (elements.length > 0) {
-        // Look for one whose listCounter matches the item we want to add
-        var item = parseInt(assertion.getPredicate().substring(NS_RDF.length + 1));
-        for (let element of elements) {
-          if (element.listCounter == item) {
-            assertion._DOMnode = this._ds._addElement(element, RDF_R("li"));
-            assertion._applyToDOMNode();
-            element.listCounter++;
-            return;
-          }
-        }
-        // No good container to add to, shove in the first real container
-        assertion._DOMnode = this._ds._addElement(elements[0], assertion.getPredicate());
-        assertion._applyToDOMNode();
-        return;
-      }
-      // TODO No containers, this will end up in a non-container for now
-    } else if (assertion.getPredicate() == RDF_R("type")) {
-      // Try renaming an existing rdf:Description
-      for (let element of this.elements) {
-        if (element.namespaceURI == NS_RDF &&
-            element.localName == "Description") {
-          try {
-            var prefix = this._ds._resolvePrefix(element.parentNode, assertion.getObject().getURI());
-            element = renameNode(element, prefix.namespaceURI, prefix.qname);
-            assertion._DOMnode = element;
-            assertion._isSubjectElement = true;
-            return;
-          } catch (e) {
-            // If the type cannot be sensibly turned into a prefix then just set
-            // as a regular property
-          }
-        }
-      }
-    }
-
-    // Filter out all the containers
-    elements = this._elements.filter(function(element) {
-      return (element.namespaceURI != NS_RDF || (element.localName != "Seq" &&
-                                                 element.localName != "Bag" &&
-                                                 element.localName != "Alt"));
-    });
-    if (elements.length == 0) {
-      // Create a new node of the right type
-      if (assertion.getPredicate() == RDF_R("type")) {
-        try {
-          assertion._DOMnode = this._createElement(assertion.getObject().getURI());
-          assertion._isSubjectElement = true;
-          return;
-        } catch (e) {
-          // If the type cannot be sensibly turned into a prefix then just set
-          // as a regular property
-        }
-      }
-      elements[0] = this._createElement(RDF_R("Description"));
-    }
-    assertion._DOMnode = this._ds._addElement(elements[0], assertion.getPredicate());
-    assertion._applyToDOMNode();
-  }
-
-  /**
-   * Removes the DOM node representing the assertion.
-   */
-  _removeDOMNodeForAssertion(assertion) {
-    if (isAttr(assertion._DOMnode)) {
-      var parent = assertion._DOMnode.ownerElement;
-      parent.removeAttributeNode(assertion._DOMnode);
-    } else if (assertion._isSubjectElement) {
-      var domnode = renameNode(assertion._DOMnode, NS_RDF, "Description");
-      if (domnode != assertion._DOMnode) {
-        var pos = this._elements.indexOf(assertion._DOMnode);
-        this._elements.splice(pos, 1, domnode);
-      }
-      parent = domnode;
-    } else {
-      var object = assertion.getObject();
-      if (object instanceof RDFSubject && assertion._DOMnode.firstChild) {
-        // Object is a subject that has an Element inside this assertion's node.
-        for (let element of object._elements) {
-          if (element.parentNode == assertion._DOMnode) {
-            object._removeElement(element);
-            break;
-          }
-        }
-      }
-      parent = assertion._DOMnode.parentNode;
-      if (assertion._DOMnode.namespaceURI == NS_RDF &&
-          assertion._DOMnode.localName == "li")
-      parent.listCounter--;
-      this._ds._removeElement(assertion._DOMnode);
-    }
-
-    // If there are no assertions left using the assertion's containing dom node
-    // then remove it from the document.
-    // TODO could do with a quick lookup list for assertions attached to a node
-    for (var p in this._assertions) {
-      for (let assertion of this._assertions[p]) {
-        if (assertion._getSubjectElement() == parent)
-          return;
-      }
-    }
-    // No assertions left in this element.
-    this._removeElement(parent);
-  }
-
-  /**
-   * Parses the given Element from the DOM document
-   */
-  /* eslint-disable complexity */
-  _parseElement(element) {
-    this._elements.push(element);
-
-    // There might be an inferred rdf:type assertion in the element name
-    if (element.namespaceURI != NS_RDF ||
-        element.localName != "Description") {
-      if (element.namespaceURI == NS_RDF && element.localName == "li")
-        throw new Error("rdf:li is not a valid type for a subject node");
-      var assertion = new RDFAssertion(this, RDF_R("type"),
-                                       this._ds.getResource(element.namespaceURI + element.localName));
-      assertion._DOMnode = element;
-      assertion._isSubjectElement = true;
-      this._addAssertion(assertion);
-    }
-
-    // Certain attributes can be literal properties
-    for (let attr of element.attributes) {
-      if (attr.namespaceURI == NS_XML || attr.namespaceURI == NS_XMLNS ||
-          attr.nodeName == "xmlns")
-        continue;
-      if ((attr.namespaceURI == NS_RDF || !attr.namespaceURI) &&
-          (["nodeID", "about", "resource", "ID", "parseType"].includes(attr.localName)))
-        continue;
-      var object = null;
-      if (attr.namespaceURI == NS_RDF) {
-        if (attr.localName == "type")
-          object = this._ds.getResource(attr.nodeValue);
-        else if (attr.localName == "li")
-          throw new Error("rdf:li is not allowed as a property attribute");
-        else if (attr.localName == "aboutEach")
-          throw new Error("rdf:aboutEach is deprecated");
-        else if (attr.localName == "aboutEachPrefix")
-          throw new Error("rdf:aboutEachPrefix is deprecated");
-        else if (attr.localName == "aboutEach")
-          throw new Error("rdf:aboutEach is deprecated");
-        else if (attr.localName == "bagID")
-          throw new Error("rdf:bagID is deprecated");
-      }
-      if (!object)
-        object = new RDFLiteral(attr.nodeValue);
-      assertion = new RDFAssertion(this, attr.namespaceURI + attr.localName, object);
-      assertion._DOMnode = attr;
-      this._addAssertion(assertion);
-    }
-
-    var child = element.firstChild;
-    element.listCounter = 1;
-    while (child) {
-      if (isText(child) && /\S/.test(child.nodeValue)) {
-        ERROR(`Text ${child.nodeValue} is not allowed in a subject node`);
-        throw new Error("subject nodes cannot contain text content");
-      } else if (isElement(child)) {
-        object = null;
-        var predicate = child.namespaceURI + child.localName;
-        if (child.namespaceURI == NS_RDF) {
-          if (RDF_PROPERTY_INVALID_TYPES.includes(child.localName) &&
-              !child.localName.match(/^_\d+$/))
-            throw new Error(`${child.nodeName} is an invalid property`);
-          if (child.localName == "li") {
-            predicate = RDF_R(`_${element.listCounter}`);
-            element.listCounter++;
-          }
-        }
-
-        // Check for and bail out on unknown attributes on the property element
-        for (let attr of child.attributes) {
-          // Ignore XML namespaced attributes
-          if (attr.namespaceURI == NS_XML)
-            continue;
-          // These are reserved by XML for future use
-          if (attr.localName.substring(0, 3).toLowerCase() == "xml")
-            continue;
-          // We can handle these RDF attributes
-          if ((!attr.namespaceURI || attr.namespaceURI == NS_RDF) &&
-              ["resource", "nodeID"].includes(attr.localName))
-            continue;
-          // This is a special attribute we handle for compatibility with Mozilla RDF
-          if (attr.namespaceURI == NS_NC &&
-              attr.localName == "parseType")
-            continue;
-          throw new Error(`Attribute ${attr.nodeName} is not supported`);
-        }
-
-        var parseType = child.getAttributeNS(NS_NC, "parseType");
-        if (parseType && parseType != "Date" && parseType != "Integer") {
-          ERROR(`parseType ${parseType} is not supported`);
-          throw new Error("unsupported parseType");
-        }
-
-        var resource = getRDFAttribute(child, "resource");
-        var nodeID = getRDFAttribute(child, "nodeID");
-        if ((resource && (nodeID || parseType)) ||
-            (nodeID && (resource || parseType))) {
-          ERROR("Cannot use more than one of parseType, resource and nodeID on a single node");
-          throw new Error("Invalid rdf assertion");
-        }
-
-        if (resource !== undefined) {
-          var base = Services.io.newURI(element.baseURI);
-          object = this._ds.getResource(base.resolve(resource));
-        } else if (nodeID !== undefined) {
-          if (!nodeID.match(XML_NCNAME))
-            throw new Error("rdf:nodeID must be a valid XML name");
-          object = this._ds.getBlankNode(nodeID);
-        } else {
-          var hasText = false;
-          var childElement = null;
-          var subchild = child.firstChild;
-          while (subchild) {
-            if (isText(subchild) && /\S/.test(subchild.nodeValue)) {
-              hasText = true;
-            } else if (isElement(subchild)) {
-              if (childElement) {
-                new Error(`Multiple object elements found in ${child.nodeName}`);
-              }
-              childElement = subchild;
-            }
-            subchild = subchild.nextSibling;
-          }
-
-          if ((resource || nodeID) && (hasText || childElement)) {
-            ERROR("Assertion references a resource so should not contain additional contents");
-            throw new Error("assertion cannot contain multiple objects");
-          }
-
-          if (hasText && childElement) {
-            ERROR(`Both literal and resource objects found in ${child.nodeName}`);
-            throw new Error("assertion cannot contain multiple objects");
-          }
-
-          if (childElement) {
-            if (parseType) {
-              ERROR("Cannot specify a parseType for an assertion with resource object");
-              throw new Error("parseType is not valid in this context");
-            }
-            object = this._ds._getSubjectForElement(childElement);
-            object._parseElement(childElement);
-          } else if (parseType == "Integer") {
-            object = new RDFIntLiteral(child.textContent);
-          } else if (parseType == "Date") {
-            object = new RDFDateLiteral(new Date(child.textContent));
-          } else {
-            object = new RDFLiteral(child.textContent);
-          }
-        }
-
-        assertion = new RDFAssertion(this, predicate, object);
-        this._addAssertion(assertion);
-        assertion._DOMnode = child;
-      }
-      child = child.nextSibling;
-    }
-  }
-  /* eslint-enable complexity */
-
-  /**
-   * Adds a new assertion to the internal hashes. Should be called for every
-   * new assertion parsed or created programmatically.
-   */
-  _addAssertion(assertion) {
-    var predicate = assertion.getPredicate();
-    if (predicate in this._assertions)
-      this._assertions[predicate].push(assertion);
-    else
-      this._assertions[predicate] = [ assertion ];
-
-    var object = assertion.getObject();
-    if (object instanceof RDFSubject) {
-      // Create reverse assertion
-      if (predicate in object._backwards)
-        object._backwards[predicate].push(assertion);
-      else
-        object._backwards[predicate] = [ assertion ];
-    }
-  }
-
-  /**
-   * Removes an assertion from the internal hashes. Should be called for all
-   * assertions that are programmatically deleted.
-   */
-  _removeAssertion(assertion) {
-    var predicate = assertion.getPredicate();
-    if (predicate in this._assertions) {
-      var pos = this._assertions[predicate].indexOf(assertion);
-      if (pos >= 0)
-        this._assertions[predicate].splice(pos, 1);
-      if (this._assertions[predicate].length == 0)
-        delete this._assertions[predicate];
-    }
-
-    var object = assertion.getObject();
-    if (object instanceof RDFSubject) {
-      // Delete reverse assertion
-      if (predicate in object._backwards) {
-        pos = object._backwards[predicate].indexOf(assertion);
-        if (pos >= 0)
-          object._backwards[predicate].splice(pos, 1);
-        if (object._backwards[predicate].length == 0)
-          delete object._backwards[predicate];
-      }
-    }
-  }
-
-  /**
-   * Returns the ordinal assertions from this subject in order.
-   */
-  _getChildAssertions() {
-    var assertions = [];
-    for (var i in this._assertions) {
-      if (RDF_LISTITEM.test(i))
-        assertions.push(...this._assertions[i]);
-    }
-    assertions.sort(predicateOrder);
-    return assertions;
-  }
-
-  /**
-   * Compares this to another rdf node
-   */
-  equals(rdfnode) {
-    // subjects are created by the datasource so no two objects ever correspond
-    // to the same one.
-    return this === rdfnode;
-  }
-
-  /**
-   * Adds a new assertion with this as the subject
-   */
-  assert(predicate, object) {
-    if (predicate == RDF_R("type") && !(object instanceof RDFResource))
-      throw new Error("rdf:type must be an RDFResource");
-
-    var assertion = new RDFAssertion(this, predicate, object);
-    this._createDOMNodeForAssertion(assertion);
-    this._addAssertion(assertion);
-  }
-
-  /**
-   * Removes an assertion matching the predicate and node given, if such an
-   * assertion exists.
-   */
-  unassert(predicate, object) {
-    if (!(predicate in this._assertions))
-      return;
-
-    for (let assertion of this._assertions[predicate]) {
-      if (assertion.getObject().equals(object)) {
-        this._removeAssertion(assertion);
-        this._removeDOMNodeForAssertion(assertion);
-        return;
-      }
-    }
-  }
-
-  /**
-   * Returns an array of all the predicates that exist in assertions from this
-   * subject.
-   */
-  getPredicates() {
-    return Object.keys(this._assertions);
-  }
-
-  /**
-   * Returns all objects in assertions with this subject and the given predicate.
-   */
-  getObjects(predicate) {
-    if (predicate in this._assertions)
-      return Array.from(this._assertions[predicate],
-                        i => i.getObject());
-
-    return [];
-  }
-
-  /**
-   * Returns all of the ordinal children of this subject in order.
-   */
-  getChildren() {
-    return Array.from(this._getChildAssertions(),
-                      i => i.getObject());
-  }
-
-  /**
-   * Removes the child at the given index. This is the index based on the
-   * children returned from getChildren. Forces a reordering of the later
-   * children.
-   */
-  removeChildAt(pos) {
-    if (pos < 0)
-      throw new Error("no such child");
-    var assertions = this._getChildAssertions();
-    if (pos >= assertions.length)
-      throw new Error("no such child");
-    for (var i = pos; i < assertions.length; i++) {
-      this._removeAssertion(assertions[i]);
-      this._removeDOMNodeForAssertion(assertions[i]);
-    }
-    var index = 1;
-    if (pos > 0)
-      index = parseInt(assertions[pos - 1].getPredicate().substring(NS_RDF.length + 1)) + 1;
-    for (let i = pos + 1; i < assertions.length; i++) {
-      assertions[i]._predicate = RDF_R(`_${index}`);
-      this._addAssertion(assertions[i]);
-      this._createDOMNodeForAssertion(assertions[i]);
-      index++;
-    }
-  }
-
-  /**
-   * Removes the child with the given object. It is unspecified which child is
-   * removed if the object features more than once.
-   */
-  removeChild(object) {
-    var assertions = this._getChildAssertions();
-    for (var pos = 0; pos < assertions.length; pos++) {
-      if (assertions[pos].getObject().equals(object)) {
-        for (var i = pos; i < assertions.length; i++) {
-          this._removeAssertion(assertions[i]);
-          this._removeDOMNodeForAssertion(assertions[i]);
-        }
-        var index = 1;
-        if (pos > 0)
-          index = parseInt(assertions[pos - 1].getPredicate().substring(NS_RDF.length + 1)) + 1;
-        for (let i = pos + 1; i < assertions.length; i++) {
-          assertions[i]._predicate = RDF_R(`_${index}`);
-          this._addAssertion(assertions[i]);
-          this._createDOMNodeForAssertion(assertions[i]);
-          index++;
-        }
-        return;
-      }
-    }
-    throw new Error("no such child");
-  }
-
-  /**
-   * Adds a new ordinal child to this subject.
-   */
-  addChild(object) {
-    var max = 0;
-    for (var i in this._assertions) {
-      if (RDF_LISTITEM.test(i))
-        max = Math.max(max, parseInt(i.substring(NS_RDF.length + 1)));
-    }
-    max++;
-    this.assert(RDF_R(`_${max}`), object);
-  }
-
-  /**
-   * This reorders the child assertions to remove duplicates and gaps in the
-   * sequence. Generally this will move all children to be under the same
-   * container element and all represented as an rdf:li
-   */
-  reorderChildren() {
-    var assertions = this._getChildAssertions();
-    for (let assertion of assertions) {
-      this._removeAssertion(assertion);
-      this._removeDOMNodeForAssertion(assertion);
-    }
-    var index = 1;
-    for (let assertion of assertions) {
-      assertion._predicate = RDF_R(`_${index}`);
-      this._addAssertion(assertion);
-      this._createDOMNodeForAssertion(assertion);
-      index++;
-    }
-  }
-
-  /**
-   * Returns the type of this subject or null if there is no specified type.
-   */
-  getType() {
-    var type = this.getProperty(RDF_R("type"));
-    if (type && type instanceof RDFResource)
-      return type.getURI();
-    return null;
-  }
-
-  /**
-   * Tests if a property exists for the given predicate.
-   */
-  hasProperty(predicate) {
-    return (predicate in this._assertions);
-  }
-
-  /**
-   * Retrieves the first property value for the given predicate.
-   */
-  getProperty(predicate) {
-    if (predicate in this._assertions)
-      return this._assertions[predicate][0].getObject();
-    return null;
-  }
-
-  /**
-   * Sets the property value for the given predicate, clearing any existing
-   * values.
-   */
-  setProperty(predicate, object) {
-    // TODO optimise by replacing the first assertion and clearing the rest
-    this.clearProperty(predicate);
-    this.assert(predicate, object);
-  }
-
-  /**
-   * Clears any existing properties for the given predicate.
-   */
-  clearProperty(predicate) {
-    if (!(predicate in this._assertions))
-      return;
-
-    var assertions = this._assertions[predicate];
-    while (assertions.length > 0) {
-      var assertion = assertions[0];
-      this._removeAssertion(assertion);
-      this._removeDOMNodeForAssertion(assertion);
-    }
-  }
-}
-
-/**
- * Creates a new RDFResource for the datasource. Private.
- */
-class RDFResource extends RDFSubject {
-  constructor(ds, uri) {
-    if (!(ds instanceof RDFDataSource))
-      throw new Error("datasource must be an RDFDataSource");
-
-    if (!uri)
-      throw new Error("An RDFResource requires a non-null uri");
-
-    super(ds);
-    // This is the uri that the resource represents.
-    this._uri = uri;
-  }
-
-  /**
-   * Sets attributes on the DOM element to mark it as representing this resource
-   */
-  _applyToElement(element) {
-    if (USE_RDFNS_ATTR) {
-      var prefix = this._ds._resolvePrefix(element, RDF_R("about"));
-      element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._uri);
-    } else {
-      element.setAttribute("about", this._uri);
-    }
-  }
-
-  /**
-   * Adds a reference to this resource to the given property Element.
-   */
-  _addReferenceToElement(element) {
-    if (USE_RDFNS_ATTR) {
-      var prefix = this._ds._resolvePrefix(element, RDF_R("resource"));
-      element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._uri);
-    } else {
-      element.setAttribute("resource", this._uri);
-    }
-  }
-
-    /**
-     * Removes any reference to this resource from the given property Element.
-     */
-    _removeReferenceFromElement(element) {
-      if (element.hasAttributeNS(NS_RDF, "resource"))
-        element.removeAttributeNS(NS_RDF, "resource");
-      if (element.hasAttribute("resource"))
-        element.removeAttribute("resource");
-    }
-
-  getURI() {
-    return this._uri;
-  }
-}
-
-/**
- * Creates a new blank node. Private.
- */
-class RDFBlankNode extends RDFSubject {
-  constructor(ds, nodeID) {
-    if (!(ds instanceof RDFDataSource))
-      throw new Error("datasource must be an RDFDataSource");
-
-    super(ds);
-    // The nodeID of this node. May be null if there is no ID.
-    this._nodeID = nodeID;
-  }
-
-  /**
-   * Sets attributes on the DOM element to mark it as representing this node
-   */
-  _applyToElement(element) {
-    if (!this._nodeID)
-      return;
-    if (USE_RDFNS_ATTR) {
-      var prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));
-      element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);
-    } else {
-      element.setAttribute("nodeID", this._nodeID);
-    }
-  }
-
-  /**
-   * Creates a new Element in the document for holding assertions about this
-   * subject. The URI controls what tagname to use.
-   */
-  _createNewElement(uri) {
-    // If there are already nodes representing this in the document then we need
-    // a nodeID to match them
-    if (!this._nodeID && this._elements.length > 0) {
-      this._ds._createNodeID(this);
-      for (let element of this._elements)
-        this._applyToElement(element);
-    }
-
-    return super._createNewElement.call(uri);
-  }
-
-  /**
-   * Adds a reference to this node to the given property Element.
-   */
-  _addReferenceToElement(element) {
-    if (this._elements.length > 0 && !this._nodeID) {
-      // In document elsewhere already
-      // Create a node ID and update the other nodes referencing
-      this._ds._createNodeID(this);
-      for (let element of this._elements)
-        this._applyToElement(element);
-    }
-
-    if (this._nodeID) {
-      if (USE_RDFNS_ATTR) {
-        let prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));
-        element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);
-      } else {
-        element.setAttribute("nodeID", this._nodeID);
-      }
-    } else {
-      // Add the empty blank node, this is generally right since further
-      // assertions will be added to fill this out
-      var newelement = this._ds._addElement(element, RDF_R("Description"));
-      newelement.listCounter = 1;
-      this._elements.push(newelement);
-    }
-  }
-
-    /**
-     * Removes any reference to this node from the given property Element.
-     */
-    _removeReferenceFromElement(element) {
-      if (element.hasAttributeNS(NS_RDF, "nodeID"))
-        element.removeAttributeNS(NS_RDF, "nodeID");
-      if (element.hasAttribute("nodeID"))
-        element.removeAttribute("nodeID");
-    }
-
-  getNodeID() {
-    return this._nodeID;
-  }
-}
-
-/**
- * Creates a new RDFDataSource from the given document. The document will be
- * changed as assertions are added and removed to the RDF. Pass a null document
- * to start with an empty graph.
- */
-class RDFDataSource {
-  constructor(document) {
-    // All known resources, indexed on URI
-    this._resources = {};
-    // All blank nodes
-    this._allBlankNodes = [];
-    // All blank nodes with IDs, indexed on ID
-    this._blankNodes = {};
-    // Suggested prefixes to use for namespaces, index is prefix, value is namespaceURI.
-    this._prefixes = {
-      rdf: NS_RDF,
-      NC: NS_NC,
-    };
-
-    if (!document) {
-      // Creating a document through xpcom leaves out the xml prolog so just parse
-      // something small
-      var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
-                   createInstance(Ci.nsIDOMParser);
-      var doctext = `<?xml version="1.0"?>\n<rdf:RDF xmlns:rdf="${NS_RDF}"/>\n`;
-      document = parser.parseFromString(doctext, "text/xml");
-    }
-    // The underlying DOM document for this datasource
-    this._document = document;
-    this._parseDocument();
-  }
-
-  static loadFromString(text) {
-    let parser = new DOMParser();
-    let document = parser.parseFromString(text, "application/xml");
-
-    return new this(document);
-  }
-
-  static loadFromBuffer(buffer) {
-    let parser = new DOMParser();
-    let document = parser.parseFromBuffer(new Uint8Array(buffer), "application/xml");
-
-    return new this(document);
-  }
-
-  static async loadFromFile(uri) {
-    if (uri instanceof Ci.nsIFile)
-      uri = Services.io.newFileURI(uri);
-    else if (typeof(uri) == "string")
-      uri = Services.io.newURI(uri);
-
-    let resp = await fetch(uri.spec);
-    return this.loadFromBuffer(await resp.arrayBuffer());
-  }
-
-  get uri() {
-    return this._document.documentURI;
-  }
-
-  /**
-   * Creates a new nodeID for an unnamed blank node. Just node<number>.
-   */
-  _createNodeID(blanknode) {
-    var i = 1;
-    while (`node${i}` in this._blankNodes)
-      i++;
-    blanknode._nodeID = `node${i}`;
-    this._blankNodes[blanknode._nodeID] = blanknode;
-  }
-
-  /**
-   * Returns an rdf subject for the given DOM Element. If the subject has not
-   * been seen before a new one is created.
-   */
-  _getSubjectForElement(element) {
-    if (element.namespaceURI == NS_RDF &&
-        RDF_NODE_INVALID_TYPES.includes(element.localName))
-      throw new Error(`${element.nodeName} is not a valid class for a subject node`);
-
-    var about = getRDFAttribute(element, "about");
-    var id = getRDFAttribute(element, "ID");
-    var nodeID = getRDFAttribute(element, "nodeID");
-
-    if ((about && (id || nodeID)) ||
-        (nodeID && (id || about))) {
-      ERROR("More than one of about, ID and nodeID present on the same subject");
-      throw new Error("invalid subject in rdf");
-    }
-
-    if (about !== undefined) {
-      let base = Services.io.newURI(element.baseURI);
-      return this.getResource(base.resolve(about));
-    }
-    if (id !== undefined) {
-      if (!id.match(XML_NCNAME))
-        throw new Error("rdf:ID must be a valid XML name");
-      let base = Services.io.newURI(element.baseURI);
-      return this.getResource(base.resolve(`#${id}`));
-    }
-    if (nodeID !== undefined)
-      return this.getBlankNode(nodeID);
-    return this.getBlankNode(null);
-  }
-
-  /**
-   * Parses the document for subjects at the top level.
-   */
-  _parseDocument() {
-    if (!this._document.documentElement) {
-      ERROR("No document element in document");
-      throw new Error("document contains no root element");
-    }
-
-    if (this._document.documentElement.namespaceURI != NS_RDF ||
-        this._document.documentElement.localName != "RDF") {
-      ERROR(`${this._document.documentElement.nodeName} is not rdf:RDF`);
-      throw new Error("document does not appear to be RDF");
-    }
-
-    var domnode = this._document.documentElement.firstChild;
-    while (domnode) {
-      if (isText(domnode) && /\S/.test(domnode.nodeValue)) {
-        ERROR("RDF does not allow for text in the root of the document");
-        throw new Error("invalid markup in document");
-      } else if (isElement(domnode)) {
-        var subject = this._getSubjectForElement(domnode);
-        subject._parseElement(domnode);
-      }
-      domnode = domnode.nextSibling;
-    }
-  }
-
-  /**
-   * Works out a sensible namespace prefix to use for the given uri. node should
-   * be the parent of where the element is to be inserted, or the node that an
-   * attribute is to be added to. This will recursively walk to the top of the
-   * document finding an already registered prefix that matches for the uri.
-   * If none is found a new prefix is registered.
-   * This returns an object with keys namespaceURI, prefix, localName and qname.
-   * Pass null or undefined for badPrefixes for the first call.
-   */
-  _resolvePrefix(domnode, uri, badPrefixes) {
-    if (!badPrefixes)
-      badPrefixes = [];
-
-    // No known prefix, try to create one from the lookup list
-    if (!domnode || isDocument(domnode)) {
-      for (let i in this._prefixes) {
-        if (badPrefixes.includes(i))
-          continue;
-        if (this._prefixes[i] == uri.substring(0, this._prefixes[i].length)) {
-          var local = uri.substring(this._prefixes[i].length);
-          var test = URI_SUFFIX.exec(local);
-          // Remaining part of uri is a good XML Name
-          if (test && test[0] == local) {
-            this._document.documentElement.setAttributeNS(NS_XMLNS, `xmlns:${i}`, this._prefixes[i]);
-            return {
-              namespaceURI: this._prefixes[i],
-              prefix: i,
-              localName: local,
-              qname: i ? `${i}:${local}` : local,
-            };
-          }
-        }
-      }
-
-      // No match, make something up
-      test = URI_SUFFIX.exec(uri);
-      if (test) {
-        var namespaceURI = uri.substring(0, uri.length - test[0].length);
-        local = test[0];
-        let i = 1;
-        while (badPrefixes.includes(`NS${i}`))
-          i++;
-        this._document.documentElement.setAttributeNS(NS_XMLNS, `xmlns:NS${i}`, namespaceURI);
-        return {
-          namespaceURI,
-          prefix: `NS${i}`,
-          localName: local,
-          qname: `NS${i}:${local}`,
-        };
-      }
-      // There is no end part of this URI that is an XML Name
-      throw new Error(`invalid node name: ${uri}`);
-    }
-
-    for (let attr of domnode.attributes) {
-      // Not a namespace declaration, ignore this attribute
-      if (attr.namespaceURI != NS_XMLNS && attr.nodeName != "xmlns")
-        continue;
-
-      var prefix = attr.prefix ? attr.localName : "";
-      // Seen this prefix before, cannot use it
-      if (badPrefixes.includes(prefix))
-        continue;
-
-      // Namespace matches the start of the uri
-      if (attr.value == uri.substring(0, attr.value.length)) {
-        local = uri.substring(attr.value.length);
-        test = URI_SUFFIX.exec(local);
-        // Remaining part of uri is a good XML Name
-        if (test && test[0] == local) {
-          return {
-            namespaceURI: attr.value,
-            prefix,
-            localName: local,
-            qname: prefix ? `${prefix}:${local}` : local,
-          };
-        }
-      }
-
-      badPrefixes.push(prefix);
-    }
-
-    // No prefix found here, move up the document
-    return this._resolvePrefix(domnode.parentNode, uri, badPrefixes);
-  }
-
-  /**
-   * Guess the indent level within the given Element. The method looks for
-   * elements that are preceded by whitespace including a newline. The
-   * whitespace following the newline is presumed to be the indentation for the
-   * element.
-   * If the indentation cannot be guessed then it recurses up the document
-   * hierarchy until it can guess the indent or until the Document is reached.
-   */
-  _guessIndent(element) {
-    // The indent at document level is 0
-    if (!element || isDocument(element))
-      return "";
-
-    // Check the text immediately preceding each child node. One could be
-    // a valid indent
-    var pretext = "";
-    var child = element.firstChild;
-    while (child) {
-      if (isText(child)) {
-        pretext += child.nodeValue;
-      } else if (isElement(child)) {
-        var result = INDENT.exec(pretext);
-        if (result)
-          return result[1];
-        pretext = "";
-      }
-      child = child.nextSibling;
-    }
-
-    // pretext now contains any trailing text in the element. This can be
-    // the indent of the end tag. If so add a little to it.
-    result = INDENT.exec(pretext);
-    if (result)
-      return `${result[1]}  `;
-
-    // Check the text immediately before this node
-    pretext = "";
-    var sibling = element.previousSibling;
-    while (sibling && isText(sibling)) {
-      pretext += sibling.nodeValue;
-      sibling = sibling.previousSibling;
-    }
-
-    // If there is a sensible indent then just add to it.
-    result = INDENT.exec(pretext);
-    if (result)
-      return `${result[1]}  `;
-
-    // Last chance, get the indent level for the tag above and add to it
-    return `${this._guessIndent(element.parentNode)}  `;
-  }
-
-  _addElement(parent, uri) {
-    var prefix = this._resolvePrefix(parent, uri);
-    var element = this._document.createElementNS(prefix.namespaceURI, prefix.qname);
-
-    if (parent.lastChild) {
-      // We want to insert immediately after the last child element
-      var last = parent.lastChild;
-      while (last && isText(last))
-        last = last.previousSibling;
-      // No child elements so insert at the start
-      if (!last)
-        last = parent.firstChild;
-      else
-        last = last.nextSibling;
-
-      let indent = this._guessIndent(parent);
-      parent.insertBefore(this._document.createTextNode(`\n${indent}`), last);
-      parent.insertBefore(element, last);
-    } else {
-      // No children, must indent our element and the end tag
-      let indent = this._guessIndent(parent.parentNode);
-      parent.append(`\n${indent}  `, element, `\n${indent}`);
-    }
-    return element;
-  }
-
-  /**
-   * Removes the element from its parent. Should also remove surrounding
-   * white space as appropriate.
-   */
-  _removeElement(element) {
-    var parent = element.parentNode;
-    var sibling = element.previousSibling;
-    // Drop any text nodes immediately preceding the element
-    while (sibling && isText(sibling)) {
-      var temp = sibling;
-      sibling = sibling.previousSibling;
-      parent.removeChild(temp);
-    }
-
-    sibling = element.nextSibling;
-    // Drop the element
-    parent.removeChild(element);
-
-    // If the next node after element is now the first child then element was
-    // the first child. If there are no other child elements then remove the
-    // remaining child nodes.
-    if (parent.firstChild == sibling) {
-      while (sibling && isText(sibling))
-        sibling = sibling.nextSibling;
-      if (!sibling) {
-        // No other child elements
-        while (parent.lastChild)
-          parent.removeChild(parent.lastChild);
-      }
-    }
-  }
-
-  /**
-   * Requests that a given prefix be used for the namespace where possible.
-   * This must be called before any assertions are made using the namespace
-   * and the registration will not override any existing prefix used in the
-   * document.
-   */
-  registerPrefix(prefix, namespaceURI) {
-    this._prefixes[prefix] = namespaceURI;
-  }
-
-  /**
-   * Gets a blank node. nodeID may be null and if so a new blank node is created.
-   * If a nodeID is given then the blank node with that ID is returned or created.
-   */
-  getBlankNode(nodeID) {
-    if (nodeID && nodeID in this._blankNodes)
-      return this._blankNodes[nodeID];
-
-    if (nodeID && !nodeID.match(XML_NCNAME))
-      throw new Error("rdf:nodeID must be a valid XML name");
-
-    var rdfnode = new RDFBlankNode(this, nodeID);
-    this._allBlankNodes.push(rdfnode);
-    if (nodeID)
-      this._blankNodes[nodeID] = rdfnode;
-    return rdfnode;
-  }
-
-  /**
-   * Gets all blank nodes
-   */
-  getAllBlankNodes() {
-    return this._allBlankNodes.slice();
-  }
-
-  /**
-   * Gets the resource for the URI. The resource is created if it has not been
-   * used already.
-   */
-  getResource(uri) {
-    if (uri in this._resources)
-      return this._resources[uri];
-
-    var resource = new RDFResource(this, uri);
-    this._resources[uri] = resource;
-    return resource;
-  }
-
-  /**
-   * Gets all resources that have been used.
-   */
-  getAllResources() {
-    return Object.values(this._resources);
-  }
-
-  /**
-   * Returns all blank nodes and resources
-   */
-  getAllSubjects() {
-    return [...Object.values(this._resources),
-            ...this._allBlankNodes];
-  }
-
-  /**
-   * Saves the RDF/XML to a string.
-   */
-  serializeToString() {
-    var serializer = new XMLSerializer();
-    return serializer.serializeToString(this._document);
-  }
-
-  /**
-   * Saves the RDF/XML to a file.
-   */
-  async saveToFile(file) {
-    return OS.File.writeAtomic(file, new TextEncoder().encode(this.serializeToString()));
-  }
-}
deleted file mode 100644
--- a/common/src/RDFManifestConverter.jsm
+++ /dev/null
@@ -1,110 +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";
-
-var EXPORTED_SYMBOLS = ["InstallRDF"];
-
-ChromeUtils.defineModuleGetter(this, "RDFDataSource",
-                               "resource:///modules/RDFDataSource.jsm");
-
-const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";
-
-function EM_R(aProperty) {
-  return `http://www.mozilla.org/2004/em-rdf#${aProperty}`;
-}
-
-function getValue(literal) {
-  return literal && literal.getValue();
-}
-
-function getProperty(resource, property) {
-  return getValue(resource.getProperty(EM_R(property)));
-}
-
-class Manifest {
-  constructor(ds) {
-    this.ds = ds;
-  }
-
-  static loadFromString(text) {
-    return new this(RDFDataSource.loadFromString(text));
-  }
-
-  static loadFromBuffer(buffer) {
-    return new this(RDFDataSource.loadFromBuffer(buffer));
-  }
-
-  static async loadFromFile(uri) {
-    return new this(await RDFDataSource.loadFromFile(uri));
-  }
-}
-
-class InstallRDF extends Manifest {
-  _readProps(source, obj, props) {
-    for (let prop of props) {
-      let val = getProperty(source, prop);
-      if (val != null) {
-        obj[prop] = val;
-      }
-    }
-  }
-
-  _readArrayProp(source, obj, prop, target, decode = getValue) {
-    let result = Array.from(source.getObjects(EM_R(prop)),
-                            target => decode(target));
-    if (result.length) {
-      obj[target] = result;
-    }
-  }
-
-  _readArrayProps(source, obj, props, decode = getValue) {
-    for (let [prop, target] of Object.entries(props)) {
-      this._readArrayProp(source, obj, prop, target, decode);
-    }
-  }
-
-  _readLocaleStrings(source, obj) {
-    this._readProps(source, obj, ["name", "description", "creator", "homepageURL"]);
-    this._readArrayProps(source, obj, {
-      locale: "locales",
-      developer: "developers",
-      translator: "translators",
-      contributor: "contributors",
-    });
-  }
-
-  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"];
-    this._readProps(root, result, props);
-
-    let decodeTargetApplication = source => {
-      let app = {};
-      this._readProps(source, app, ["id", "minVersion", "maxVersion"]);
-      return app;
-    };
-
-    let decodeLocale = source => {
-      let localized = {};
-      this._readLocaleStrings(source, localized);
-      return localized;
-    };
-
-    this._readLocaleStrings(root, result);
-
-    this._readArrayProps(root, result, {"targetPlatform": "targetPlatforms"});
-    this._readArrayProps(root, result, {"targetApplication": "targetApplications"},
-                         decodeTargetApplication);
-    this._readArrayProps(root, result, {"localized": "localized"},
-                         decodeLocale);
-    this._readArrayProps(root, result, {"dependency": "dependencies"},
-                         source => getProperty(source, "id"));
-
-    return result;
-  }
-}
--- a/common/src/moz.build
+++ b/common/src/moz.build
@@ -1,20 +1,17 @@
 # vim: set filetype=python:
 # 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 += [
-    'BootstrapLoader.jsm',
     'ChromeManifest.jsm',
     'ExtensionSupport.jsm',
     'Overlays.jsm',
-    'RDFDataSource.jsm',
-    'RDFManifestConverter.jsm',
 ]
 
 SOURCES += [
     'nsCommonModule.cpp',
     'nsComponentManagerExtra.cpp',
 ]
 
 LOCAL_INCLUDES += [
--- a/common/test/xpcshell/head_addons.js
+++ b/common/test/xpcshell/head_addons.js
@@ -63,29 +63,31 @@ const {
   overrideBuiltIns,
   promiseAddonEvent,
   promiseCompleteAllInstalls,
   promiseCompleteInstall,
   promiseConsoleOutput,
   promiseFindAddonUpdates,
   promiseInstallAllFiles,
   promiseInstallFile,
+  promiseRestartManager,
   promiseSetExtensionModifiedTime,
   promiseShutdownManager,
+  promiseStartupManager,
   promiseWebExtensionStartup,
   promiseWriteProxyFileToDir,
   registerDirectory,
   setExtensionModifiedTime,
   writeFilesToZip,
 } = AddonTestUtils;
 
 // WebExtension wrapper for ease of testing
 ExtensionTestUtils.init(this);
 
-AddonTestUtils.init(this);
+AddonTestUtils.init(this, false);
 AddonTestUtils.overrideCertDB();
 
 XPCOMUtils.defineLazyGetter(this, "BOOTSTRAP_REASONS",
                             () => AddonManagerPrivate.BOOTSTRAP_REASONS);
 
 function getReasonName(reason) {
   for (let key of Object.keys(BOOTSTRAP_REASONS)) {
     if (BOOTSTRAP_REASONS[key] == reason) {
@@ -143,28 +145,16 @@ Object.defineProperty(this, "TEST_UNPACK
    return AddonTestUtils.testUnpacked = val;
   },
 });
 
 // We need some internal bits of AddonManager
 var AMscope = ChromeUtils.import("resource://gre/modules/AddonManager.jsm", null);
 var { AddonManager, AddonManagerInternal, AddonManagerPrivate } = AMscope;
 
-// Wrap the startup functions to ensure the bootstrap loader is added.
-function promiseStartupManager(newVersion) {
-  const {BootstrapLoader} = ChromeUtils.import("resource:///modules/BootstrapLoader.jsm");
-  AddonManager.addExternalExtensionLoader(BootstrapLoader);
-  return AddonTestUtils.promiseStartupManager(newVersion);
-}
-
-async function promiseRestartManager(newVersion) {
-  await promiseShutdownManager(false);
-  await promiseStartupManager(newVersion);
-}
-
 const promiseAddonByID = AddonManager.getAddonByID;
 const promiseAddonsByIDs = AddonManager.getAddonsByIDs;
 
 var gPort = null;
 var gUrlToFileMap = {};
 
 // Map resource://xpcshell-data/ to the data directory
 var resHandler = Services.io.getProtocolHandler("resource")
@@ -268,30 +258,30 @@ this.BootstrapMonitor = {
     let jarFile = uri.QueryInterface(Ci.nsIJARURI).JARFile;
     let installPath = jarFile.QueryInterface(Ci.nsIFileURL).file;
 
     let isRegistered = isManifestRegistered(installPath);
     Assert.ok(isRegistered);
   },
 
   checkAddonNotStarted(id) {
-    Assert.ok(!this.started.has(id));
+    Assert.ok(!this.started.has(id), "checkAddonNotStarted");
   },
 
   checkAddonInstalled(id, version = undefined) {
     const installed = this.installed.get(id);
     notEqual(installed, undefined);
     if (version !== undefined) {
       equal(installed.data.version, version);
     }
     return installed;
   },
 
   checkAddonNotInstalled(id) {
-    Assert.ok(!this.installed.has(id));
+    Assert.ok(!this.installed.has(id), "checkAddonNotInstalled");
   },
 
   observe(subject, topic, data) {
     let info = JSON.parse(data);
     let id = info.data.id;
 
     // Get the install path from the resource URI, since it is not passed any longer.
     let uri = Services.io.newURI(info.data.resourceURI);
@@ -802,111 +792,16 @@ function isThemeInAddonsList(aDir, aId) 
   return AddonTestUtils.addonsList.hasTheme(aDir, aId);
 }
 
 function isExtensionInBootstrappedList(aDir, aId) {
   return AddonTestUtils.addonsList.hasExtension(aDir, aId);
 }
 
 /**
- * Writes an install.rdf manifest into a directory using the properties passed
- * in a JS object. The objects should contain a property for each property to
- * appear in the RDF. The object may contain an array of objects with id,
- * minVersion and maxVersion in the targetApplications property to give target
- * application compatibility.
- *
- * @param   aData
- *          The object holding data about the add-on
- * @param   aDir
- *          The directory to add the install.rdf to
- * @param   aId
- *          An optional string to override the default installation aId
- * @param   aExtraFile
- *          An optional dummy file to create in the directory
- * @return  An nsIFile for the directory in which the add-on is installed.
- */
-async function promiseWriteInstallRDFToDir(aData, aDir, aId = aData.id, aExtraFile = null) {
-  let files = {
-    "install.rdf": createInstallRDF(aData),
-  };
-  if (typeof aExtraFile === "object")
-    Object.assign(files, aExtraFile);
-  else
-    files[aExtraFile] = "";
-
-  let dir = aDir.clone();
-  dir.append(aId);
-
-  await AddonTestUtils.promiseWriteFilesToDir(dir.path, files);
-  return dir;
-}
-
-/**
- * Writes an install.rdf manifest into a packed extension using the properties passed
- * in a JS object. The objects should contain a property for each property to
- * appear in the RDF. The object may contain an array of objects with id,
- * minVersion and maxVersion in the targetApplications property to give target
- * application compatibility.
- *
- * @param   aData
- *          The object holding data about the add-on
- * @param   aDir
- *          The install directory to add the extension to
- * @param   aId
- *          An optional string to override the default installation aId
- * @param   aExtraFile
- *          An optional dummy file to create in the extension
- * @return  A file pointing to where the extension was installed
- */
-async function promiseWriteInstallRDFToXPI(aData, aDir, aId = aData.id, aExtraFile = null) {
-  let files = {
-    "install.rdf": createInstallRDF(aData),
-  };
-  if (typeof aExtraFile === "object")
-    Object.assign(files, aExtraFile);
-  else
-  if (aExtraFile)
-    files[aExtraFile] = "";
-
-  if (!aDir.exists())
-    aDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-
-  var file = aDir.clone();
-  file.append(`${aId}.xpi`);
-
-  AddonTestUtils.writeFilesToZip(file.path, files);
-
-  return file;
-}
-
-/**
- * Writes an install.rdf manifest into an extension using the properties passed
- * in a JS object. The objects should contain a property for each property to
- * appear in the RDF. The object may contain an array of objects with id,
- * minVersion and maxVersion in the targetApplications property to give target
- * application compatibility.
- *
- * @param   aData
- *          The object holding data about the add-on
- * @param   aDir
- *          The install directory to add the extension to
- * @param   aId
- *          An optional string to override the default installation aId
- * @param   aExtraFile
- *          An optional dummy file to create in the extension
- * @return  A file pointing to where the extension was installed
- */
-function promiseWriteInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
-  if (TEST_UNPACKED) {
-    return promiseWriteInstallRDFToDir(aData, aDir, aId, aExtraFile);
-  }
-  return promiseWriteInstallRDFToXPI(aData, aDir, aId, aExtraFile);
-}
-
-/**
  * Writes a manifest.json manifest into an extension using the properties passed
  * in a JS object.
  *
  * @param   aManifest
  *          The data to write
  * @param   aDir
  *          The install directory to add the extension to
  * @param   aId
@@ -935,20 +830,16 @@ function createTempXPIFile(aData, aExtra
   if (typeof aExtraFile == "object")
     Object.assign(files, aExtraFile);
   else if (aExtraFile)
     files[aExtraFile] = "";
 
   return AddonTestUtils.createTempXPIFile(files);
 }
 
-function promiseInstallXPI(installRDF) {
-  return AddonTestUtils.promiseInstallXPI({"install.rdf": installRDF});
-}
-
 var gExpectedEvents = {};
 var gExpectedInstalls = [];
 var gNext = null;
 
 function getExpectedEvent(aId) {
   if (!(aId in gExpectedEvents))
     do_throw("Wasn't expecting events for " + aId);
   if (gExpectedEvents[aId].length == 0)
@@ -1438,60 +1329,8 @@ function _writeArrayProps(obj, props, in
 function _writeLocaleStrings(data) {
   let items = [];
 
   items.push(this._writeProps(data, ["name", "description", "creator", "homepageURL"]));
   items.push(this._writeArrayProps(data, ["developer", "translator", "contributor"]));
 
   return items.join("");
 }
-
-function createInstallRDF(data) {
-  let defaults = {
-    bootstrap: true,
-    version: "1.0",
-    name: `Test Extension ${data.id}`,
-    targetApplications: [
-      {
-        "id": "xpcshell@tests.mozilla.org",
-        "minVersion": "1",
-        "maxVersion": "64.*",
-      },
-    ],
-  };
-
-  var rdf = '<?xml version="1.0"?>\n';
-  rdf += '<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' +
-         '     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"];
-  rdf += _writeProps(data, props);
-
-  rdf += _writeLocaleStrings(data);
-
-  for (let platform of data.targetPlatforms || [])
-    rdf += escaped`<em:targetPlatform>${platform}</em:targetPlatform>\n`;
-
-  for (let app of data.targetApplications || []) {
-    rdf += "<em:targetApplication><Description>\n";
-    rdf += _writeProps(app, ["id", "minVersion", "maxVersion"]);
-    rdf += "</Description></em:targetApplication>\n";
-  }
-
-  for (let localized of data.localized || []) {
-    rdf += "<em:localized><Description>\n";
-    rdf += _writeArrayProps(localized, ["locale"]);
-    rdf += _writeLocaleStrings(localized);
-    rdf += "</Description></em:localized>\n";
-  }
-
-  for (let dep of data.dependencies || [])
-    rdf += escaped`<em:dependency><Description em:id="${dep}"/></em:dependency>\n`;
-
-  rdf += "</Description>\n</RDF>\n";
-  return rdf;
-}
--- a/common/test/xpcshell/test_bootstrap.js
+++ b/common/test/xpcshell/test_bootstrap.js
@@ -29,53 +29,84 @@ const profileDir = gProfD.clone();
 profileDir.append("extensions");
 const userExtDir = gProfD.clone();
 userExtDir.append("extensions2");
 userExtDir.append(gAppInfo.ID);
 registerDirectory("XREUSysExt", userExtDir.parent);
 
 const ADDONS = {
   test_bootstrap1_1: {
-    "install.rdf": createInstallRDF({
-      id: "bootstrap1@tests.mozilla.org",
+    "manifest.json": JSON.stringify({
+      applications: {
+        gecko: {
+          id: "bootstrap1@tests.mozilla.org",
+        },
+      },
+      bootstrapped: true,
+      manifest_version: 2,
 
       name: "Test Bootstrap 1",
+      version: "1.0",
 
       iconURL: "chrome://foo/skin/icon.png",
       aboutURL: "chrome://foo/content/about.xul",
       optionsURL: "chrome://foo/content/options.xul",
     }),
     "bootstrap.js": BOOTSTRAP_MONITOR_BOOTSTRAP_JS,
   },
   test_bootstrap1_2: {
-    "install.rdf": createInstallRDF({
-      id: "bootstrap1@tests.mozilla.org",
+    "manifest.json": JSON.stringify({
+      applications: {
+        gecko: {
+          id: "bootstrap1@tests.mozilla.org",
+        },
+      },
+      bootstrapped: true,
+      manifest_version: 2,
       version: "2.0",
 
       name: "Test Bootstrap 1",
     }),
     "bootstrap.js": BOOTSTRAP_MONITOR_BOOTSTRAP_JS,
   },
   test_bootstrap1_3: {
-    "install.rdf": createInstallRDF({
-      id: "bootstrap1@tests.mozilla.org",
+    "manifest.json": JSON.stringify({
+      applications: {
+        gecko: {
+          id: "bootstrap1@tests.mozilla.org",
+        },
+      },
+      bootstrapped: true,
+      manifest_version: 2,
       version: "3.0",
 
       name: "Test Bootstrap 1",
 
       targetApplications: [{
-        id: "undefined",
+        applications: {
+          gecko: {
+            id: "undefined",
+          },
+        },
+        bootstrapped: true,
+        manifest_version: 2,
         minVersion: "1",
         maxVersion: "1"}],
     }),
     "bootstrap.js": BOOTSTRAP_MONITOR_BOOTSTRAP_JS,
   },
   test_bootstrap2_1: {
-    "install.rdf": createInstallRDF({
-      id: "bootstrap2@tests.mozilla.org",
+    "manifest.json": JSON.stringify({
+      applications: {
+        gecko: {
+          id: "bootstrap2@tests.mozilla.org",
+        },
+      },
+      bootstrapped: true,
+      manifest_version: 2,
     }),
     "bootstrap.js": BOOTSTRAP_MONITOR_BOOTSTRAP_JS,
   },
 };
 
 var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 
 const XPIS = {};
@@ -433,17 +464,17 @@ add_task(async function test_7() {
         AddonManager.OP_NEEDS_RESTART_UNINSTALL, 0);
   await b1.uninstall();
 
   await checkBootstrappedPref();
 
   ensure_test_completed();
   BootstrapMonitor.checkAddonNotInstalled(ID1);
   BootstrapMonitor.checkAddonNotStarted(ID1);
-  equal(getShutdownReason(), ADDON_UNINSTALL);
+  equal(getShutdownReason(), ADDON_DISABLE);
   equal(getShutdownNewVersion(), undefined);
   do_check_not_in_crash_annotation(ID1, "2.0");
 
   b1 = await AddonManager.getAddonByID(ID1);
   equal(b1, null);
 
   await promiseRestartManager();
 
@@ -644,221 +675,16 @@ add_task(async function test_12() {
   do_check_in_crash_annotation(ID1, "1.0");
 
   await b1.uninstall();
 
   await promiseRestartManager();
   await checkBootstrappedPref();
 });
 
-
-// Tests that installing a bootstrapped extension with an invalid application
-// entry doesn't call it's startup method
-add_task(async function test_13() {
-  prepare_test({}, [
-    "onNewInstall",
-  ]);
-
-  let install = await AddonManager.getInstallForFile(XPIS.test_bootstrap1_3);
-  ensure_test_completed();
-
-  notEqual(install, null);
-  equal(install.type, "extension");
-  equal(install.version, "3.0");
-  equal(install.name, "Test Bootstrap 1");
-  equal(install.state, AddonManager.STATE_DOWNLOADED);
-  do_check_not_in_crash_annotation(ID1, "3.0");
-
-  await new Promise(resolve => {
-    prepare_test({
-      [ID1]: [
-        ["onInstalling", false],
-        "onInstalled",
-      ],
-    }, [
-      "onInstallStarted",
-      "onInstallEnded",
-    ], resolve);
-    install.install();
-  });
-
-  let installs = await AddonManager.getAllInstalls();
-
-  // There should be no active installs now since the install completed and
-  // doesn't require a restart.
-  equal(installs.length, 0);
-
-  let b1 = await AddonManager.getAddonByID(ID1);
-  notEqual(b1, null);
-  equal(b1.version, "3.0");
-  ok(b1.appDisabled);
-  ok(!b1.userDisabled);
-  ok(!b1.isActive);
-  BootstrapMonitor.checkAddonInstalled(ID1, "3.0"); // We call install even for disabled add-ons
-  BootstrapMonitor.checkAddonNotStarted(ID1); // Should not have called startup though
-  do_check_not_in_crash_annotation(ID1, "3.0");
-
-  await promiseRestartManager();
-
-  b1 = await AddonManager.getAddonByID(ID1);
-  notEqual(b1, null);
-  equal(b1.version, "3.0");
-  ok(b1.appDisabled);
-  ok(!b1.userDisabled);
-  ok(!b1.isActive);
-  BootstrapMonitor.checkAddonInstalled(ID1, "3.0"); // We call install even for disabled add-ons
-  BootstrapMonitor.checkAddonNotStarted(ID1); // Should not have called startup though
-  do_check_not_in_crash_annotation(ID1, "3.0");
-
-  await checkBootstrappedPref();
-  await b1.uninstall();
-});
-
-// Tests that a bootstrapped extension with an invalid target application entry
-// does not get loaded when detected during startup
-add_task(async function test_14() {
-  await promiseRestartManager();
-
-  await promiseShutdownManager();
-
-  await manuallyInstall(XPIS.test_bootstrap1_3, profileDir, ID1);
-
-  await promiseStartupManager();
-
-  let b1 = await AddonManager.getAddonByID(ID1);
-  notEqual(b1, null);
-  equal(b1.version, "3.0");
-  ok(b1.appDisabled);
-  ok(!b1.userDisabled);
-  ok(!b1.isActive);
-  BootstrapMonitor.checkAddonInstalled(ID1, "3.0"); // We call install even for disabled add-ons
-  BootstrapMonitor.checkAddonNotStarted(ID1); // Should not have called startup though
-  do_check_not_in_crash_annotation(ID1, "3.0");
-
-  await checkBootstrappedPref();
-  await b1.uninstall();
-});
-
-// Tests that upgrading a disabled bootstrapped extension still calls uninstall
-// and install but doesn't startup the new version
-add_task(async function test_15() {
-  await Promise.all([
-    BootstrapMonitor.promiseAddonStartup(ID1),
-    promiseInstallFile(XPIS.test_bootstrap1_1),
-  ]);
-
-  let b1 = await AddonManager.getAddonByID(ID1);
-  notEqual(b1, null);
-  equal(b1.version, "1.0");
-  ok(!b1.appDisabled);
-  ok(!b1.userDisabled);
-  ok(b1.isActive);
-  ok(!b1.isSystem);
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "1.0");
-
-  await b1.disable();
-  ok(!b1.isActive);
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonNotStarted(ID1);
-
-  prepare_test({}, [
-    "onNewInstall",
-  ]);
-
-  let install = await AddonManager.getInstallForFile(XPIS.test_bootstrap1_2);
-  ensure_test_completed();
-
-  notEqual(install, null);
-  ok(install.addon.userDisabled);
-
-  await new Promise(resolve => {
-    prepare_test({
-      [ID1]: [
-        ["onInstalling", false],
-        "onInstalled",
-      ],
-    }, [
-      "onInstallStarted",
-      "onInstallEnded",
-    ], resolve);
-    install.install();
-  });
-
-  b1 = await AddonManager.getAddonByID(ID1);
-  notEqual(b1, null);
-  equal(b1.version, "2.0");
-  ok(!b1.appDisabled);
-  ok(b1.userDisabled);
-  ok(!b1.isActive);
-  BootstrapMonitor.checkAddonInstalled(ID1, "2.0");
-  BootstrapMonitor.checkAddonNotStarted(ID1);
-
-  await checkBootstrappedPref();
-  await promiseRestartManager();
-
-  let b1_2 = await AddonManager.getAddonByID(ID1);
-  notEqual(b1_2, null);
-  equal(b1_2.version, "2.0");
-  ok(!b1_2.appDisabled);
-  ok(b1_2.userDisabled);
-  ok(!b1_2.isActive);
-  BootstrapMonitor.checkAddonInstalled(ID1, "2.0");
-  BootstrapMonitor.checkAddonNotStarted(ID1);
-
-  await b1_2.uninstall();
-});
-
-// Tests that bootstrapped extensions don't get loaded when in safe mode
-add_task(async function test_16() {
-  await Promise.all([
-    BootstrapMonitor.promiseAddonStartup(ID1),
-    promiseInstallFile(XPIS.test_bootstrap1_1),
-  ]);
-
-  let b1 = await AddonManager.getAddonByID(ID1);
-  // Should have installed and started
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "1.0");
-  ok(b1.isActive);
-  ok(!b1.isSystem);
-  equal(b1.iconURL, "chrome://foo/skin/icon.png");
-  equal(b1.aboutURL, "chrome://foo/content/about.xul");
-  equal(b1.optionsURL, "chrome://foo/content/options.xul");
-
-  await promiseShutdownManager();
-
-  // Should have stopped
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonNotStarted(ID1);
-
-  gAppInfo.inSafeMode = true;
-  await promiseStartupManager();
-
-  let b1_2 = await AddonManager.getAddonByID(ID1);
-  // Should still be stopped
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonNotStarted(ID1);
-  ok(!b1_2.isActive);
-  equal(b1_2.iconURL, null);
-  equal(b1_2.aboutURL, null);
-  equal(b1_2.optionsURL, null);
-
-  await promiseShutdownManager();
-  gAppInfo.inSafeMode = false;
-  await promiseStartupManager();
-
-  // Should have started
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "1.0");
-
-  let b1_3 = await AddonManager.getAddonByID(ID1);
-  await b1_3.uninstall();
-});
-
 // Check that a bootstrapped extension in a non-profile location is loaded
 add_task(async function test_17() {
   await promiseShutdownManager();
 
   await manuallyInstall(XPIS.test_bootstrap1_1, userExtDir, ID1);
 
   await promiseStartupManager();
 
@@ -937,250 +763,8 @@ add_task(async function test_19() {
 
   equal(getShutdownNewVersion(), "1.0");
   equal(getUninstallNewVersion(), "1.0");
   equal(getInstallOldVersion(), "2.0");
   equal(getStartupOldVersion(), "2.0");
 
   await checkBootstrappedPref();
 });
-
-// Check that a new profile extension detected at startup replaces the non-profile
-// one
-add_task(async function test_20() {
-  await promiseShutdownManager();
-
-  await manuallyInstall(XPIS.test_bootstrap1_2, profileDir, ID1);
-
-  await promiseStartupManager();
-
-  let b1 = await AddonManager.getAddonByID(ID1);
-  // Should have installed and started
-  BootstrapMonitor.checkAddonInstalled(ID1, "2.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "2.0");
-  notEqual(b1, null);
-  equal(b1.version, "2.0");
-  ok(b1.isActive);
-  ok(!b1.isSystem);
-
-  equal(getShutdownReason(), APP_SHUTDOWN);
-  equal(getUninstallReason(), ADDON_UPGRADE);
-  equal(getInstallReason(), ADDON_UPGRADE);
-  equal(getStartupReason(), APP_STARTUP);
-
-  equal(getShutdownNewVersion(), undefined);
-  equal(getUninstallNewVersion(), 2);
-  equal(getInstallOldVersion(), 1);
-  equal(getStartupOldVersion(), undefined);
-});
-
-// Check that a detected removal reveals the non-profile one
-add_task(async function test_21() {
-  await promiseShutdownManager();
-
-  equal(getShutdownReason(), APP_SHUTDOWN);
-  equal(getShutdownNewVersion(), undefined);
-
-  manuallyUninstall(profileDir, ID1);
-  BootstrapMonitor.clear(ID1);
-
-  await promiseStartupManager();
-
-  let b1 = await AddonManager.getAddonByID(ID1);
-  // Should have installed and started
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "1.0");
-  notEqual(b1, null);
-  equal(b1.version, "1.0");
-  ok(b1.isActive);
-  ok(!b1.isSystem);
-
-  // This won't be set as the bootstrap script was gone so we couldn't
-  // uninstall it properly
-  equal(getUninstallReason(), undefined);
-  equal(getUninstallNewVersion(), undefined);
-
-  equal(getInstallReason(), ADDON_DOWNGRADE);
-  equal(getInstallOldVersion(), 2);
-
-  equal(getStartupReason(), APP_STARTUP);
-  equal(getStartupOldVersion(), undefined);
-
-  await checkBootstrappedPref();
-  await promiseShutdownManager();
-
-  manuallyUninstall(userExtDir, ID1);
-  BootstrapMonitor.clear(ID1);
-
-  await promiseStartupManager();
-});
-
-// Check that an upgrade from the filesystem is detected and applied correctly
-add_task(async function test_22() {
-  await promiseShutdownManager();
-
-  let file = await manuallyInstall(XPIS.test_bootstrap1_1, profileDir, ID1);
-  if (file.isDirectory())
-    file.append("install.rdf");
-
-  // Make it look old so changes are detected
-  setExtensionModifiedTime(file, file.lastModifiedTime - 5000);
-
-  await promiseStartupManager();
-
-  let b1 = await AddonManager.getAddonByID(ID1);
-  // Should have installed and started
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "1.0");
-  notEqual(b1, null);
-  equal(b1.version, "1.0");
-  ok(b1.isActive);
-  ok(!b1.isSystem);
-
-  await promiseShutdownManager();
-
-  equal(getShutdownReason(), APP_SHUTDOWN);
-  equal(getShutdownNewVersion(), undefined);
-
-  manuallyUninstall(profileDir, ID1);
-  BootstrapMonitor.clear(ID1);
-  await manuallyInstall(XPIS.test_bootstrap1_2, profileDir, ID1);
-
-  await promiseStartupManager();
-
-  let b1_2 = await AddonManager.getAddonByID(ID1);
-  // Should have installed and started
-  BootstrapMonitor.checkAddonInstalled(ID1, "2.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "2.0");
-  notEqual(b1_2, null);
-  equal(b1_2.version, "2.0");
-  ok(b1_2.isActive);
-  ok(!b1_2.isSystem);
-
-  // This won't be set as the bootstrap script was gone so we couldn't
-  // uninstall it properly
-  equal(getUninstallReason(), undefined);
-  equal(getUninstallNewVersion(), undefined);
-
-  equal(getInstallReason(), ADDON_UPGRADE);
-  equal(getInstallOldVersion(), 1);
-  equal(getStartupReason(), APP_STARTUP);
-  equal(getStartupOldVersion(), undefined);
-
-  await checkBootstrappedPref();
-  await b1_2.uninstall();
-});
-
-
-// Tests that installing from a URL doesn't require a restart
-add_task(async function test_23() {
-  prepare_test({}, [
-    "onNewInstall",
-  ]);
-
-  let url = "http://example.com/addons/test_bootstrap1_1.xpi";
-  let install = await AddonManager.getInstallForURL(url, {});
-
-  ensure_test_completed();
-
-  notEqual(install, null);
-
-  await new Promise(resolve => {
-    prepare_test({}, [
-      "onDownloadStarted",
-      "onDownloadEnded",
-    ], function() {
-      equal(install.type, "extension");
-      equal(install.version, "1.0");
-      equal(install.name, "Test Bootstrap 1");
-      equal(install.state, AddonManager.STATE_DOWNLOADED);
-      equal(install.addon.operationsRequiringRestart &
-                   AddonManager.OP_NEEDS_RESTART_INSTALL, 0);
-      do_check_not_in_crash_annotation(ID1, "1.0");
-
-      prepare_test({
-        [ID1]: [
-          ["onInstalling", false],
-          "onInstalled",
-        ],
-      }, [
-        "onInstallStarted",
-        "onInstallEnded",
-      ], resolve);
-    });
-    install.install();
-  });
-
-  await checkBootstrappedPref();
-
-  let installs = await AddonManager.getAllInstalls();
-
-  // There should be no active installs now since the install completed and
-  // doesn't require a restart.
-  equal(installs.length, 0);
-
-  let b1 = await AddonManager.getAddonByID(ID1);
-
-  notEqual(b1, null);
-  equal(b1.version, "1.0");
-  ok(!b1.appDisabled);
-  ok(!b1.userDisabled);
-  ok(b1.isActive);
-  ok(!b1.isSystem);
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "1.0");
-  equal(getStartupReason(), ADDON_INSTALL);
-  equal(getStartupOldVersion(), undefined);
-  do_check_in_crash_annotation(ID1, "1.0");
-
-  let dir = do_get_addon_root_uri(profileDir, ID1);
-  equal(b1.getResourceURI("bootstrap.js").spec, dir + "bootstrap.js");
-
-  await promiseRestartManager();
-
-  let b1_2 = await AddonManager.getAddonByID(ID1);
-  await b1_2.uninstall();
-});
-
-// Tests that we recover from a broken preference
-add_task(async function test_24() {
-  info("starting 24");
-
-  await Promise.all([
-    BootstrapMonitor.promiseAddonStartup(ID2),
-    promiseInstallAllFiles([XPIS.test_bootstrap1_1, XPIS.test_bootstrap2_1]),
-  ]);
-
-  info("test 24 got prefs");
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "1.0");
-  BootstrapMonitor.checkAddonInstalled(ID2, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID2, "1.0");
-
-  await promiseRestartManager();
-
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "1.0");
-  BootstrapMonitor.checkAddonInstalled(ID2, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID2, "1.0");
-
-  await promiseShutdownManager();
-
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonNotStarted(ID1);
-  BootstrapMonitor.checkAddonInstalled(ID2, "1.0");
-  BootstrapMonitor.checkAddonNotStarted(ID2);
-
-  // Break the JSON.
-  let data = aomStartup.readStartupData();
-  data["app-profile"].addons[ID1].path += "foo";
-
-  await OS.File.writeAtomic(gAddonStartup.path,
-                            new TextEncoder().encode(JSON.stringify(data)),
-                            {compression: "lz4"});
-
-  await promiseStartupManager();
-
-  BootstrapMonitor.checkAddonInstalled(ID1, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID1, "1.0");
-  BootstrapMonitor.checkAddonInstalled(ID2, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID2, "1.0");
-});
--- a/common/test/xpcshell/test_bootstrap_const.js
+++ b/common/test/xpcshell/test_bootstrap_const.js
@@ -1,18 +1,26 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
 
 const ADDONS = {
   test_bootstrap_const: {
-    "install.rdf": createInstallRDF({
-      "id": "bootstrap@tests.mozilla.org",
+    "manifest.json": JSON.stringify({
+      applications: {
+        gecko: {
+          id: "bootstrap@tests.mozilla.org",
+        },
+      },
+      bootstrapped: true,
+      manifest_version: 2,
+      name: "Test Bootstrap 1",
+      version: "1.0",
     }),
     "bootstrap.js": "var {Services} = ChromeUtils.import(\"resource://gre/modules/Services.jsm\");\n\nconst install = function() {\n  Services.obs.notifyObservers(null, \"addon-install\");\n};\n",
   },
 };
 
 add_task(async function() {
   await promiseStartupManager();
 
--- a/common/test/xpcshell/test_bootstrap_globals.js
+++ b/common/test/xpcshell/test_bootstrap_globals.js
@@ -4,18 +4,26 @@
 
 // This verifies that bootstrap.js has the expected globals defined
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
 
 const ADDONS = {
   bootstrap_globals: {
-    "install.rdf": createInstallRDF({
-      "id": "bootstrap_globals@tests.mozilla.org",
+    "manifest.json": JSON.stringify({
+      applications: {
+        gecko: {
+          id: "bootstrap_globals@tests.mozilla.org",
+        },
+      },
+      bootstrapped: true,
+      manifest_version: 2,
+      name: "Test Bootstrap 1",
+      version: "1.0",
     }),
     "bootstrap.js": String.raw`var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 var seenGlobals = new Set();
 var scope = this;
 function checkGlobal(name, type) {
   if (scope[name] && typeof(scope[name]) == type)
     seenGlobals.add(name);
--- a/common/test/xpcshell/test_bootstrapped_chrome_manifest.js
+++ b/common/test/xpcshell/test_bootstrapped_chrome_manifest.js
@@ -1,15 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 const ADDON = {
-  "install.rdf": createInstallRDF({
-    "id": "bug675371@tests.mozilla.org",
+  "manifest.json": JSON.stringify({
+    applications: {
+      gecko: {
+        id: "bug675371@tests.mozilla.org",
+      },
+    },
+    bootstrapped: true,
+    manifest_version: 2,
+    name: "Test Bootstrap 1",
+    version: "1.0",
   }),
   "chrome.manifest": `content bug675371 .`,
   "test.js": `var active = true;`,
 };
 
 add_task(async function run_test() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
   await promiseStartupManager();
deleted file mode 100644
--- a/common/test/xpcshell/test_invalid_install_rdf.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Test that side-loaded extensions with invalid install.rdf files are
-// not initialized at startup.
-
-const APP_ID = "xpcshell@tests.mozilla.org";
-
-Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_USER);
-
-createAppInfo(APP_ID, "XPCShell", "1", "1.9.2");
-
-const userAppDir = AddonTestUtils.profileDir.clone();
-userAppDir.append("app-extensions");
-userAppDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-AddonTestUtils.registerDirectory("XREUSysExt", userAppDir);
-
-const userExtensions = userAppDir.clone();
-userExtensions.append(APP_ID);
-userExtensions.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-
-XPCOMUtils.defineLazyServiceGetters(this, {
-  ChromeRegistry: ["@mozilla.org/chrome/chrome-registry;1", "nsIChromeRegistry"],
-});
-
-function hasChromeEntry(package) {
-  try {
-    void ChromeRegistry.convertChromeURL(Services.io.newURI(`chrome://${package}/content/`));
-    return true;
-  } catch (e) {
-    return false;
-  }
-}
-
-add_task(async function() {
-  await promiseWriteInstallRDFToXPI({
-    id: "langpack-foo@addons.mozilla.org",
-    version: "1.0",
-    type: 8,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1",
-    }],
-    name: "Invalid install.rdf extension",
-  }, userExtensions, undefined, {
-    "chrome.manifest": `
-      content foo-langpack ./
-    `,
-  });
-
-  await promiseWriteInstallRDFToXPI({
-    id: "foo@addons.mozilla.org",
-    version: "1.0",
-    bootstrap: true,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1",
-    }],
-    name: "Invalid install.rdf extension",
-  }, userExtensions, undefined, {
-    "chrome.manifest": `
-      content foo ./
-    `,
-  });
-
-  await promiseWriteInstallRDFToXPI({
-    id: "foo-legacy-legacy@addons.mozilla.org",
-    version: "1.0",
-    bootstrap: false,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1",
-    }],
-    name: "Invalid install.rdf extension",
-  }, userExtensions, undefined, {
-    "chrome.manifest": `
-      content foo-legacy-legacy ./
-    `,
-  });
-
-  equal(hasChromeEntry("foo-langpack"), false,
-        "Should not have registered foo-langpack resource before AOM startup");
-  equal(hasChromeEntry("foo-legacy-legacy"), false,
-        "Should not have registered foo-legacy-legacy resource before AOM startup");
-  equal(hasChromeEntry("foo"), false,
-        "Should not have registered foo resource before AOM startup");
-
-  await promiseStartupManager();
-
-  equal(hasChromeEntry("foo-langpack"), false,
-        "Should not have registered chrome manifest for invalid extension");
-  equal(hasChromeEntry("foo-legacy-legacy"), false,
-        "Should not have registered chrome manifest for non-restartless extension");
-  equal(hasChromeEntry("foo"), true,
-        "Should have registered chrome manifest for valid extension");
-
-  await promiseRestartManager();
-
-  equal(hasChromeEntry("foo-langpack"), false,
-        "Should still not have registered chrome manifest for invalid extension after restart");
-  equal(hasChromeEntry("foo-legacy-legacy"), false,
-        "Should still not have registered chrome manifest for non-restartless extension");
-  equal(hasChromeEntry("foo"), true,
-        "Should still have registered chrome manifest for valid extension after restart");
-
-  await promiseShutdownManager();
-
-  userAppDir.remove(true);
-});
deleted file mode 100644
--- a/common/test/xpcshell/test_manifest.js
+++ /dev/null
@@ -1,752 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// This tests that all properties are read from the install manifests and that
-// items are correctly enabled/disabled based on them (blocklist tests are
-// elsewhere)
-
-const ADDONS = [
-  {
-    "install.rdf": {
-      id: "addon1@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      aboutURL: "chrome://test/content/about.xul",
-      iconURL: "chrome://test/skin/icon.png",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 1",
-      description: "Test Description",
-      creator: "Test Creator",
-      homepageURL: "http://www.example.com",
-      developer: [
-        "Test Developer 1",
-        "Test Developer 2",
-      ],
-      translator: [
-        "Test Translator 1",
-        "Test Translator 2",
-      ],
-      contributor: [
-        "Test Contributor 1",
-        "Test Contributor 2",
-      ],
-    },
-
-    expected: {
-      id: "addon1@tests.mozilla.org",
-      type: "extension",
-      version: "1.0",
-      optionsType: null,
-      aboutURL: "chrome://test/content/about.xul",
-      iconURL: "chrome://test/skin/icon.png",
-      icons: {32: "chrome://test/skin/icon.png", 48: "chrome://test/skin/icon.png"},
-      name: "Test Addon 1",
-      description: "Test Description",
-      creator: "Test Creator",
-      homepageURL: "http://www.example.com",
-      developers: ["Test Developer 1", "Test Developer 2"],
-      translators: ["Test Translator 1", "Test Translator 2"],
-      contributors: ["Test Contributor 1", "Test Contributor 2"],
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-      providesUpdatesSecurely: true,
-      blocklistState: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon2@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      updateURL: "https://www.foo.com",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 2",
-    },
-
-    expected: {
-      id: "addon2@tests.mozilla.org",
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      providesUpdatesSecurely: true,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon3@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      updateURL: "http://www.foo.com",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 3",
-    },
-
-    expected: {
-      id: "addon3@tests.mozilla.org",
-      isActive: false,
-      userDisabled: false,
-      appDisabled: true,
-      providesUpdatesSecurely: false,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon4@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      updateURL: "http://www.foo.com",
-      updateKey: "foo",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 4",
-    },
-
-    expected: {
-      id: "addon4@tests.mozilla.org",
-      isActive: false,
-      userDisabled: false,
-      appDisabled: true,
-      providesUpdatesSecurely: false,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon5@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "*",
-      }],
-      name: "Test Addon 5",
-    },
-
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon6@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "0",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 6",
-    },
-
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon7@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "0",
-        maxVersion: "0",
-      }],
-      name: "Test Addon 7",
-    },
-
-    expected: {
-      isActive: false,
-      userDisabled: false,
-      appDisabled: true,
-      isCompatible: false,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon8@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1.1",
-        maxVersion: "*",
-      }],
-      name: "Test Addon 8",
-    },
-
-    expected: {
-      isActive: false,
-      userDisabled: false,
-      appDisabled: true,
-      isCompatible: false,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon9@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "toolkit@mozilla.org",
-        minVersion: "1.9.2",
-        maxVersion: "1.9.*",
-      }],
-      name: "Test Addon 9",
-    },
-
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon10@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "toolkit@mozilla.org",
-        minVersion: "1.9.2.1",
-        maxVersion: "1.9.*",
-      }],
-      name: "Test Addon 10",
-    },
-
-    expected: {
-      isActive: false,
-      userDisabled: false,
-      appDisabled: true,
-      isCompatible: false,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon11@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "toolkit@mozilla.org",
-        minVersion: "1.9",
-        maxVersion: "1.9.2",
-      }],
-      name: "Test Addon 11",
-    },
-
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon12@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "toolkit@mozilla.org",
-        minVersion: "1.9",
-        maxVersion: "1.9.1.*",
-      }],
-      name: "Test Addon 12",
-    },
-
-    expected: {
-      isActive: false,
-      userDisabled: false,
-      appDisabled: true,
-      isCompatible: false,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon13@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "toolkit@mozilla.org",
-        minVersion: "1.9",
-        maxVersion: "1.9.*",
-      }, {
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "0",
-        maxVersion: "0.5",
-      }],
-      name: "Test Addon 13",
-    },
-
-    expected: {
-      isActive: false,
-      userDisabled: false,
-      appDisabled: true,
-      isCompatible: false,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon14@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "toolkit@mozilla.org",
-        minVersion: "1.9",
-        maxVersion: "1.9.1",
-      }, {
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 14",
-    },
-
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon15@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      updateKey: "foo",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 15",
-    },
-
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-      providesUpdatesSecurely: true,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon16@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      updateKey: "foo",
-      updateURL: "https://www.foo.com",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 16",
-    },
-
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-      providesUpdatesSecurely: true,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon17@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      optionsURL: "chrome://test/content/options.xul",
-      optionsType: "2",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 17",
-    },
-
-    // An obsolete optionsType means the add-on isn't registered.
-    expected: null,
-  },
-
-  {
-    "install.rdf": {
-      id: "addon18@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 18",
-    },
-    extraFiles: {"options.xul": ""},
-
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-      optionsURL: null,
-      optionsType: null,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon19@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      optionsType: "99",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 19",
-    },
-
-    expected: null,
-  },
-
-  {
-    "install.rdf": {
-      id: "addon20@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      optionsURL: "chrome://test/content/options.xul",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 20",
-    },
-
-    // Even with a defined optionsURL optionsType is null by default.
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-      optionsURL: "chrome://test/content/options.xul",
-      optionsType: null,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon21@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      optionsType: "3",
-      optionsURL: "chrome://test/content/options.xul",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 21",
-    },
-
-    expected: {
-      isActive: true,
-      userDisabled: false,
-      appDisabled: false,
-      isCompatible: true,
-      optionsURL: "chrome://test/content/options.xul",
-      optionsType: AddonManager.OPTIONS_TYPE_TAB,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon22@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      optionsType: "2",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 22",
-    },
-
-    // An obsolete optionsType means the add-on isn't registered.
-    expected: null,
-  },
-
-  {
-    "install.rdf": {
-      id: "addon23@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      optionsType: "2",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 23",
-    },
-    extraFiles: {"options.xul": ""},
-
-    // An obsolete optionsType means the add-on isn't registered.
-    expected: null,
-  },
-
-  {
-    "install.rdf": {
-      id: "addon24@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 24",
-    },
-    extraFiles: {"options.xul": ""},
-
-    expected: {
-      optionsType: null,
-      optionsURL: null,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon25@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      optionsType: "3",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 25",
-    },
-
-    expected: {
-      optionsType: null,
-      optionsURL: null,
-    },
-  },
-
-  {
-    "install.rdf": {
-      id: "addon26@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      optionsType: "4",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-      name: "Test Addon 26",
-    },
-    extraFiles: {"options.xul": ""},
-    expected: null,
-  },
-
-  // Tests compatibility based on target platforms.
-
-  // No targetPlatforms so should be compatible
-  {
-    "install.rdf": {
-      id: "tp-addon1@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      name: "Test 1",
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-    },
-
-    expected: {
-      appDisabled: false,
-      isPlatformCompatible: true,
-      isActive: true,
-    },
-  },
-
-  // Matches the OS
-  {
-    "install.rdf": {
-      id: "tp-addon2@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      name: "Test 2",
-      targetPlatforms: [
-        "XPCShell",
-        "WINNT_x86",
-        "XPCShell",
-      ],
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-    },
-
-    expected: {
-      appDisabled: false,
-      isPlatformCompatible: true,
-      isActive: true,
-    },
-  },
-
-  // Matches the OS and ABI
-  {
-    "install.rdf": {
-      id: "tp-addon3@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      name: "Test 3",
-      targetPlatforms: [
-        "WINNT",
-        "XPCShell_noarch-spidermonkey",
-      ],
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-    },
-
-    expected: {
-      appDisabled: false,
-      isPlatformCompatible: true,
-      isActive: true,
-    },
-  },
-
-  // Doesn't match
-  {
-    "install.rdf": {
-      id: "tp-addon4@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      name: "Test 4",
-      targetPlatforms: [
-        "WINNT_noarch-spidermonkey",
-        "Darwin",
-        "WINNT_noarch-spidermonkey",
-      ],
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-    },
-
-    expected: {
-      appDisabled: true,
-      isPlatformCompatible: false,
-      isActive: false,
-    },
-  },
-
-  // Matches the OS but since a different entry specifies ABI this doesn't match.
-  {
-    "install.rdf": {
-      id: "tp-addon5@tests.mozilla.org",
-      version: "1.0",
-      bootstrap: true,
-      name: "Test 5",
-      targetPlatforms: [
-        "XPCShell",
-        "XPCShell_foo",
-      ],
-      targetApplications: [{
-        id: "xpcshell@tests.mozilla.org",
-        minVersion: "1",
-        maxVersion: "1",
-      }],
-    },
-
-    expected: {
-      appDisabled: true,
-      isPlatformCompatible: false,
-      isActive: false,
-    },
-  },
-];
-
-const IDS = ADDONS.map(a => a["install.rdf"].id);
-
-add_task(async function setup() {
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
-  const profileDir = gProfD.clone();
-  profileDir.append("extensions");
-
-  for (let addon of ADDONS) {
-    await promiseWriteInstallRDFForExtension(addon["install.rdf"], profileDir, undefined, addon.extraFiles);
-  }
-});
-
-add_task(async function test_values() {
-  await promiseStartupManager();
-
-  let addons = await getAddons(IDS);
-
-  for (let addon of ADDONS) {
-    let {id} = addon["install.rdf"];
-    checkAddon(id, addons.get(id), addon.expected);
-  }
-
-  await promiseShutdownManager();
-});
deleted file mode 100644
--- a/common/test/xpcshell/test_manifest_locales.js
+++ /dev/null
@@ -1,134 +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/.
- */
-
-const ID = "bug397778@tests.mozilla.org";
-
-const ADDON = createInstallRDF({
-  id: "bug397778@tests.mozilla.org",
-  version: "1.0",
-  name: "Fallback Name",
-  description: "Fallback Description",
-  bootstrap: true,
-
-  targetApplications: [{
-    id: "xpcshell@tests.mozilla.org",
-    minVersion: "1",
-    maxVersion: "1"}],
-
-  localized: [
-    {
-      locale: ["fr"],
-      name: "fr Name",
-      description: "fr Description",
-    },
-    {
-      locale: ["de-DE"],
-      name: "Deutsches W\u00f6rterbuch",
-    },
-    {
-      locale: ["es-ES"],
-      name: "es-ES Name",
-      description: "es-ES Description",
-    },
-    {
-      locale: ["zh-TW"],
-      name: "zh-TW Name",
-      description: "zh-TW Description",
-    },
-    {
-      locale: ["zh-CN"],
-      name: "zh-CN Name",
-      description: "zh-CN Description",
-    },
-    {
-      locale: ["en-GB"],
-      name: "en-GB Name",
-      description: "en-GB Description",
-    },
-    {
-      locale: ["en"],
-      name: "en Name",
-      description: "en Description",
-    },
-    {
-      locale: ["en-CA"],
-      name: "en-CA Name",
-      description: "en-CA Description",
-    },
-  ],
-});
-
-add_task(async function setup() {
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1");
-  Services.locale.requestedLocales = ["fr-FR"];
-
-  await promiseStartupManager();
-  await promiseInstallXPI(ADDON);
-});
-
-add_task(async function test_1() {
-  let addon = await AddonManager.getAddonByID(ID);
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.name, "fr Name");
-  Assert.equal(addon.description, "fr Description");
-
-  await addon.disable();
-  await promiseRestartManager();
-
-  let newAddon = await AddonManager.getAddonByID(ID);
-  Assert.notEqual(newAddon, null);
-  Assert.equal(newAddon.name, "fr Name");
-});
-
-add_task(async function test_2() {
-  // Change locale. The more specific de-DE is the best match
-  await restartWithLocales(["de"]);
-
-  let addon = await AddonManager.getAddonByID(ID);
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.name, "Deutsches W\u00f6rterbuch");
-  Assert.equal(addon.description, null);
-});
-
-add_task(async function test_3() {
-  // Change locale. Locale case should have no effect
-  await restartWithLocales(["DE-de"]);
-
-  let addon = await AddonManager.getAddonByID(ID);
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.name, "Deutsches W\u00f6rterbuch");
-  Assert.equal(addon.description, null);
-});
-
-add_task(async function test_4() {
-  // Change locale. es-ES should closely match
-  await restartWithLocales(["es-AR"]);
-
-  let addon = await AddonManager.getAddonByID(ID);
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.name, "es-ES Name");
-  Assert.equal(addon.description, "es-ES Description");
-});
-
-add_task(async function test_5() {
-  // Change locale. Either zh-CN or zh-TW could match
-  await restartWithLocales(["zh"]);
-
-  let addon = await AddonManager.getAddonByID(ID);
-  Assert.notEqual(addon, null);
-  ok(addon.name == "zh-TW Name" || addon.name == "zh-CN Name",
-     `Add-on name mismatch: ${addon.name}`);
-});
-
-add_task(async function test_6() {
-  // Unknown locale should try to match against en-US as well. Of en,en-GB
-  // en should match as being less specific
-  await restartWithLocales(["nl-NL"]);
-
-  let addon = await AddonManager.getAddonByID(ID);
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.name, "en Name");
-  Assert.equal(addon.description, "en Description");
-});
--- a/common/test/xpcshell/xpcshell.ini
+++ b/common/test/xpcshell/xpcshell.ini
@@ -3,11 +3,8 @@ tags = addons
 head = head_addons.js
 support-files =
   data/**
 
 [test_bootstrap.js]
 [test_bootstrap_const.js]
 [test_bootstrap_globals.js]
 [test_bootstrapped_chrome_manifest.js]
-[test_invalid_install_rdf.js]
-[test_manifest.js]
-[test_manifest_locales.js]
--- a/mail/components/extensions/ext-mail.json
+++ b/mail/components/extensions/ext-mail.json
@@ -10,16 +10,23 @@
   "addressBook": {
     "url": "chrome://messenger/content/parent/ext-addressBook.js",
     "schema": "chrome://messenger/content/schemas/addressBook.json",
     "scopes": ["addon_parent"],
     "paths": [
       ["addressBooks"], ["contacts"], ["mailingLists"]
     ]
   },
+  "bootstrapped": {
+    "url": "chrome://messenger/content/parent/ext-bootstrapped.js",
+    "schema": "chrome://messenger/content/schemas/bootstrapped.json",
+    "scopes": ["addon_parent"],
+    "manifest": ["bootstrapped"],
+    "events": ["disable", "uninstall", "update"]
+  },
   "browserAction": {
     "url": "chrome://messenger/content/parent/ext-browserAction.js",
     "schema": "chrome://messenger/content/schemas/browserAction.json",
     "scopes": ["addon_parent"],
     "manifest": ["browser_action"],
     "paths": [
       ["browserAction"]
     ]
--- a/mail/components/extensions/jar.mn
+++ b/mail/components/extensions/jar.mn
@@ -8,16 +8,17 @@ messenger.jar:
 
     content/messenger/child/ext-mail.js            (child/ext-mail.js)
     content/messenger/child/ext-menus-child.js     (child/ext-menus-child.js)
     content/messenger/child/ext-menus.js           (child/ext-menus.js)
     content/messenger/child/ext-tabs.js            (child/ext-tabs.js)
 
     content/messenger/parent/ext-accounts.js       (parent/ext-accounts.js)
     content/messenger/parent/ext-addressBook.js    (parent/ext-addressBook.js)
+    content/messenger/parent/ext-bootstrapped.js   (parent/ext-bootstrapped.js)
     content/messenger/parent/ext-browserAction.js  (parent/ext-browserAction.js)
     content/messenger/parent/ext-chrome-settings-overrides.js      (parent/ext-chrome-settings-overrides.js)
     content/messenger/parent/ext-cloudFile.js      (parent/ext-cloudFile.js)
     content/messenger/parent/ext-commands.js       (../../../../browser/components/extensions/parent/ext-commands.js)
     content/messenger/parent/ext-compose.js        (parent/ext-compose.js)
     content/messenger/parent/ext-composeAction.js  (parent/ext-composeAction.js)
     content/messenger/parent/ext-folders.js        (parent/ext-folders.js)
     content/messenger/parent/ext-legacy.js         (parent/ext-legacy.js)
@@ -26,16 +27,17 @@ messenger.jar:
     content/messenger/parent/ext-menus.js          (parent/ext-menus.js)
     content/messenger/parent/ext-messages.js       (parent/ext-messages.js)
     content/messenger/parent/ext-pkcs11.js         (../../../../browser/components/extensions/parent/ext-pkcs11.js)
     content/messenger/parent/ext-tabs.js           (parent/ext-tabs.js)
     content/messenger/parent/ext-windows.js        (parent/ext-windows.js)
 
     content/messenger/schemas/accounts.json        (schemas/accounts.json)
     content/messenger/schemas/addressBook.json     (schemas/addressBook.json)
+    content/messenger/schemas/bootstrapped.json    (schemas/bootstrapped.json)
     content/messenger/schemas/browserAction.json   (schemas/browserAction.json)
     content/messenger/schemas/chrome_settings_overrides.json       (schemas/chrome_settings_overrides.json)
     content/messenger/schemas/cloudFile.json       (schemas/cloudFile.json)
     content/messenger/schemas/commands.json        (../../../../browser/components/extensions/schemas/commands.json)
     content/messenger/schemas/compose.json         (schemas/compose.json)
     content/messenger/schemas/composeAction.json   (schemas/composeAction.json)
     content/messenger/schemas/folders.json         (schemas/folders.json)
     content/messenger/schemas/legacy.json          (schemas/legacy.json)
--- a/mail/components/extensions/parent/.eslintrc.js
+++ b/mail/components/extensions/parent/.eslintrc.js
@@ -47,9 +47,16 @@ module.exports = {
     "messageTracker": true,
     "tabGetSender": true,
     "tabTracker": true,
     "windowTracker": true,
 
     // ext-browserAction.js
     "browserActionFor": true,
   },
+
+  rules: {
+  "no-unused-vars": ["error", {
+    "args": "none",
+    "vars": "all",
+  }],
+},
 };
rename from common/src/BootstrapLoader.jsm
rename to mail/components/extensions/parent/ext-bootstrapped.js
--- a/common/src/BootstrapLoader.jsm
+++ b/mail/components/extensions/parent/ext-bootstrapped.js
@@ -1,339 +1,138 @@
 /* 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 = ["BootstrapLoader"];
-
-const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
-const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetters(this, {
-  AddonInternal: "resource://gre/modules/addons/XPIDatabase.jsm",
-  Blocklist: "resource://gre/modules/Blocklist.jsm",
-  ConsoleAPI: "resource://gre/modules/Console.jsm",
-  InstallRDF: "resource:///modules/RDFManifestConverter.jsm",
-  Services: "resource://gre/modules/Services.jsm",
-});
+var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+var {ConsoleAPI} = ChromeUtils.import("resource://gre/modules/Console.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "BOOTSTRAP_REASONS", () => {
   const {XPIProvider} = ChromeUtils.import("resource://gre/modules/addons/XPIProvider.jsm");
   return XPIProvider.BOOTSTRAP_REASONS;
 });
 
 const {Log} = ChromeUtils.import("resource://gre/modules/Log.jsm");
 var logger = Log.repository.getLogger("addons.bootstrap");
 
-/**
- * Valid IDs fit this pattern.
- */
-var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
-
-// Properties that exist in the install manifest
-const PROP_METADATA      = ["id", "version", "type", "internalName", "updateURL",
-                            "optionsURL", "optionsType", "aboutURL", "iconURL"];
-const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
-const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
-
-// Map new string type identifiers to old style nsIUpdateItem types.
-// Retired values:
-// 32 = multipackage xpi file
-// 8 = locale
-// 256 = apiextension
-// 128 = experiment
-// theme = 4
-const TYPES = {
-  extension: 2,
-  dictionary: 64,
-};
-
-const COMPATIBLE_BY_DEFAULT_TYPES = {
-  extension: true,
-  dictionary: true,
-};
-
-const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
+let bootstrapScopes = new Map();
+let cachedParams = new Map();
+let running = new Set();
 
-function isXPI(filename) {
-  let ext = filename.slice(-4).toLowerCase();
-  return ext === ".xpi" || ext === ".zip";
-}
-
-/**
- * Gets an nsIURI for a file within another file, either a directory or an XPI
- * file. If aFile is a directory then this will return a file: URI, if it is an
- * XPI file then it will return a jar: URI.
- *
- * @param {nsIFile} aFile
- *        The file containing the resources, must be either a directory or an
- *        XPI file
- * @param {string} aPath
- *        The path to find the resource at, "/" separated. If aPath is empty
- *        then the uri to the root of the contained files will be returned
- * @returns {nsIURI}
- *        An nsIURI pointing at the resource
- */
-function getURIForResourceInFile(aFile, aPath) {
-  if (!isXPI(aFile.leafName)) {
-    let resource = aFile.clone();
-    if (aPath)
-      aPath.split("/").forEach(part => resource.append(part));
-
-    return Services.io.newFileURI(resource);
+Services.obs.addObserver(() => {
+  for (let [id, scope] of bootstrapScopes.entries()) {
+    if (running.has(id)) {
+      scope.shutdown({...cachedParams.get(id)}, BOOTSTRAP_REASONS.APP_SHUTDOWN);
+    }
   }
-
-  return buildJarURI(aFile, aPath);
-}
+}, "quit-application-granted");
 
-/**
- * Creates a jar: URI for a file inside a ZIP file.
- *
- * @param {nsIFile} aJarfile
- *        The ZIP file as an nsIFile
- * @param {string} aPath
- *        The path inside the ZIP file
- * @returns {nsIURI}
- *        An nsIURI for the file
- */
-function buildJarURI(aJarfile, aPath) {
-  let uri = Services.io.newFileURI(aJarfile);
-  uri = "jar:" + uri.spec + "!/" + aPath;
-  return Services.io.newURI(uri);
-}
+this.bootstrapped = class extends ExtensionAPI {
+  async onManifestEntry(entryName) {
+    if (this.extension.manifest.bootstrapped) {
+      let oldParams = cachedParams.get(this.extension.id);
+      let params = {
+        id: this.extension.id,
+        version: this.extension.version,
+        resourceURI: Services.io.newURI(this.extension.resourceURL),
+        installPath: this.extensionFile.path,
+      };
+      cachedParams.set(this.extension.id, {...params});
 
-var BootstrapLoader = {
-  name: "bootstrap",
-  manifestFile: "install.rdf",
-  async loadManifest(pkg) {
-    /**
-     * Reads locale properties from either the main install manifest root or
-     * an em:localized section in the install manifest.
-     *
-     * @param {Object} aSource
-     *        The resource to read the properties from.
-     * @param {boolean} isDefault
-     *        True if the locale is to be read from the main install manifest
-     *        root
-     * @param {string[]} aSeenLocales
-     *        An array of locale names already seen for this install manifest.
-     *        Any locale names seen as a part of this function will be added to
-     *        this array
-     * @returns {Object}
-     *        an object containing the locale properties
-     */
-    function readLocale(aSource, isDefault, aSeenLocales) {
-      let locale = {};
-      if (!isDefault) {
-        locale.locales = [];
-        for (let localeName of aSource.locales || []) {
-          if (!localeName) {
-            logger.warn("Ignoring empty locale in localized properties");
-            continue;
-          }
-          if (aSeenLocales.includes(localeName)) {
-            logger.warn("Ignoring duplicate locale in localized properties");
-            continue;
-          }
-          aSeenLocales.push(localeName);
-          locale.locales.push(localeName);
-        }
-
-        if (locale.locales.length == 0) {
-          logger.warn("Ignoring localized properties with no listed locales");
-          return null;
-        }
-      }
-
-      for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) {
-        if (hasOwnProperty(aSource, prop)) {
-          locale[prop] = aSource[prop];
-        }
+      if (["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(this.extension.startupReason)) {
+        params.oldVersion = oldParams.version;
       }
 
-      return locale;
-    }
-
-    let manifestData = await pkg.readString("install.rdf");
-    let manifest = InstallRDF.loadFromString(manifestData).decode();
-
-    let addon = new AddonInternal();
-    for (let prop of PROP_METADATA) {
-      if (hasOwnProperty(manifest, prop)) {
-        addon[prop] = manifest[prop];
-      }
-    }
-
-    if (!addon.type) {
-      addon.type = "extension";
-    } else {
-      let type = addon.type;
-      addon.type = null;
-      for (let name in TYPES) {
-        if (TYPES[name] == type) {
-          addon.type = name;
-          break;
-        }
-      }
-    }
-
-    if (!(addon.type in TYPES))
-      throw new Error("Install manifest specifies unknown type: " + addon.type);
-
-    if (!addon.id)
-      throw new Error("No ID in install manifest");
-    if (!gIDTest.test(addon.id))
-      throw new Error("Illegal add-on ID " + addon.id);
-    if (!addon.version)
-      throw new Error("No version in install manifest");
+      let scope = await this.loadScope();
+      bootstrapScopes.set(this.extension.id, scope);
 
-    addon.strictCompatibility = (!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
-                                 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");
-      }
-
-      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_INSTALL", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(this.extension.startupReason)) {
+        scope.install(params, BOOTSTRAP_REASONS[this.extension.startupReason]);
       }
-    } else {
-      // Convert legacy dictionaries into a format the WebExtension
-      // dictionary loader can process.
-      if (addon.type === "dictionary") {
-        addon.loader = null;
-        let dictionaries = {};
-        await pkg.iterFiles(({path}) => {
-          let match = /^dictionaries\/([^\/]+)\.dic$/.exec(path);
-          if (match) {
-            let lang = match[1].replace(/_/g, "-");
-            dictionaries[lang] = match[0];
-          }
-        });
-        addon.startupData = {dictionaries};
-      }
+      scope.startup(params, BOOTSTRAP_REASONS[this.extension.startupReason]);
+      running.add(this.extension.id);
 
-      // Only extensions are allowed to provide an optionsURL, optionsType,
-      // optionsBrowserStyle, or aboutURL. For all other types they are silently ignored
-      addon.aboutURL = null;
-      addon.optionsBrowserStyle = null;
-      addon.optionsType = null;
-      addon.optionsURL = null;
+      this.extension.callOnClose(this);
     }
-
-    addon.defaultLocale = readLocale(manifest, true);
+  }
 
-    let seenLocales = [];
-    addon.locales = [];
-    for (let localeData of manifest.localized || []) {
-      let locale = readLocale(localeData, false, seenLocales);
-      if (locale)
-        addon.locales.push(locale);
-    }
-
-    let dependencies = new Set(manifest.dependencies);
-    addon.dependencies = Object.freeze(Array.from(dependencies));
+  close() {
+  }
 
-    let seenApplications = [];
-    addon.targetApplications = [];
-    for (let targetApp of manifest.targetApplications || []) {
-      if (!targetApp.id || !targetApp.minVersion ||
-          !targetApp.maxVersion) {
-            logger.warn("Ignoring invalid targetApplication entry in install manifest");
-            continue;
-      }
-      if (seenApplications.includes(targetApp.id)) {
-        logger.warn("Ignoring duplicate targetApplication entry for " + targetApp.id +
-                    " in install manifest");
-        continue;
-      }
-      seenApplications.push(targetApp.id);
-      addon.targetApplications.push(targetApp);
-    }
+  static onDisable(id) {
+    bootstrapScopes.get(id).shutdown({...cachedParams.get(id)}, BOOTSTRAP_REASONS.ADDON_DISABLE);
+    running.delete(id);
+  }
 
-    // Note that we don't need to check for duplicate targetPlatform entries since
-    // the RDF service coalesces them for us.
-    addon.targetPlatforms = [];
-    for (let targetPlatform of manifest.targetPlatforms || []) {
-      let platform = {
-        os: null,
-        abi: null,
-      };
-
-      let pos = targetPlatform.indexOf("_");
-      if (pos != -1) {
-        platform.os = targetPlatform.substring(0, pos);
-        platform.abi = targetPlatform.substring(pos + 1);
-      } else {
-        platform.os = targetPlatform;
-      }
-
-      addon.targetPlatforms.push(platform);
+  static onUpdate(id, manifest) {
+    let scope = {
+      ...cachedParams.get(id),
+      newVersion: manifest.version,
+    };
+    let reason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
+    if (Services.vc.compare(scope.newVersion, scope.version) < 0) {
+      reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
     }
+    bootstrapScopes.get(id).shutdown(scope, reason);
+    bootstrapScopes.get(id).uninstall(scope, reason);
+    running.delete(id);
+    bootstrapScopes.delete(id);
+  }
 
-    addon.userDisabled = false;
-    addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;
-    addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+  static onUninstall(id) {
+    bootstrapScopes.get(id).uninstall({...cachedParams.get(id)}, BOOTSTRAP_REASONS.ADDON_UNINSTALL);
+    bootstrapScopes.delete(id);
+  }
 
-    addon.userPermissions = null;
-
-    addon.icons = {};
-    if (await pkg.hasResource("icon.png")) {
-      addon.icons[32] = "icon.png";
-      addon.icons[48] = "icon.png";
+  get extensionFile() {
+    let uri = Services.io.newURI(this.extension.resourceURL);
+    if (uri instanceof Ci.nsIJARURI) {
+      uri = uri.QueryInterface(Ci.nsIJARURI).JARFile;
     }
-
-    if (await pkg.hasResource("icon64.png")) {
-      addon.icons[64] = "icon64.png";
-    }
+    return uri.QueryInterface(Ci.nsIFileURL).file;
+  }
 
-    return addon;
-  },
-
-  loadScope(addon) {
-    let file = addon.file || addon._sourceBundle;
-    let uri = getURIForResourceInFile(file, "bootstrap.js").spec;
+  loadScope() {
+    let {extension} = this;
+    let file = this.extensionFile;
+    let uri = this.extension.getURL("bootstrap.js");
     let principal = Services.scriptSecurityManager.getSystemPrincipal();
 
     let sandbox = new Cu.Sandbox(principal, {
       sandboxName: uri,
-      addonId: addon.id,
+      addonId: this.extension.id,
       wantGlobalProperties: ["ChromeUtils"],
-      metadata: { addonID: addon.id, URI: uri },
+      metadata: { addonID: this.extension.id, URI: uri },
     });
 
     try {
       Object.assign(sandbox, BOOTSTRAP_REASONS);
 
       XPCOMUtils.defineLazyGetter(sandbox, "console", () =>
-        new ConsoleAPI({ consoleID: `addon/${addon.id}` }));
+        new ConsoleAPI({ consoleID: `addon/${this.extension.id}` }));
 
       Services.scriptloader.loadSubScript(uri, sandbox);
     } catch (e) {
-      logger.warn(`Error loading bootstrap.js for ${addon.id}`, e);
+      logger.warn(`Error loading bootstrap.js for ${this.extension.id}`, e);
     }
 
     function findMethod(name) {
       if (sandbox.name) {
         return sandbox.name;
       }
 
       try {
         let method = Cu.evalInSandbox(name, sandbox);
         return method;
       } catch (err) { }
 
       return () => {
-        logger.warn(`Add-on ${addon.id} is missing bootstrap method ${name}`);
+        logger.warn(`Add-on ${extension.id} is missing bootstrap method ${name}`);
       };
     }
 
     let install = findMethod("install");
     let uninstall = findMethod("uninstall");
     let startup = findMethod("startup");
     let shutdown = findMethod("shutdown");
 
@@ -342,31 +141,28 @@ var BootstrapLoader = {
 
       uninstall(...args) {
         uninstall(...args);
         // Forget any cached files we might've had from this extension.
         Services.obs.notifyObservers(null, "startupcache-invalidate");
       },
 
       startup(...args) {
-        if (addon.type == "extension") {
-          logger.debug(`Registering manifest for ${file.path}\n`);
-          Components.manager.addBootstrappedManifestLocation(file);
-        }
+        logger.debug(`Registering manifest for ${file.path}\n`);
+        Components.manager.addBootstrappedManifestLocation(file);
         return startup(...args);
       },
 
       shutdown(data, reason) {
         try {
           return shutdown(data, reason);
         } catch (err) {
           throw err;
         } finally {
           if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
             logger.debug(`Removing manifest for ${file.path}\n`);
             Components.manager.removeBootstrappedManifestLocation(file);
           }
         }
       },
     };
-  },
+  }
 };
-
new file mode 100644
--- /dev/null
+++ b/mail/components/extensions/schemas/bootstrapped.json
@@ -0,0 +1,37 @@
+[
+  {
+    "namespace": "manifest",
+    "types": [
+      {
+        "$extend": "WebExtensionManifest",
+        "properties": {
+          "bootstrapped": {
+            "optional": true,
+            "choices": [
+              {
+                "type": "boolean"
+              },
+              {
+                "type": "object",
+                "properties": {
+                  "options": {
+                    "type": "object",
+                    "properties": {
+                      "page": {
+                        "type": "string"
+                      },
+                      "open_in_tab": {
+                        "type": "boolean",
+                        "optional": true
+                      }
+                    }
+                  }
+                }
+              }
+            ]
+          }
+        }
+      }
+    ]
+  }
+]
--- a/mail/components/mailComponents.manifest
+++ b/mail/components/mailComponents.manifest
@@ -14,9 +14,8 @@ category command-line-handler x-default 
 category command-line-validator b-default @mozilla.org/mail/clh;1
 
 component {1c73f03a-b817-4640-b984-18c3478a9ae3} mailContentHandler.js
 contract @mozilla.org/uriloader/content-handler;1?type=text/html {1c73f03a-b817-4640-b984-18c3478a9ae3}
 contract @mozilla.org/uriloader/content-handler;1?type=text/plain {1c73f03a-b817-4640-b984-18c3478a9ae3}
 
 component {eb239c82-fac9-431e-98d7-11cacd0f71b8} mailGlue.js
 contract @mozilla.org/mail/mailglue;1 {eb239c82-fac9-431e-98d7-11cacd0f71b8}
-category app-startup MailGlue service,@mozilla.org/mail/mailglue;1
--- a/mail/components/mailGlue.js
+++ b/mail/components/mailGlue.js
@@ -96,20 +96,16 @@ MailGlue.prototype = {
 
     ExtensionSupport.unregisterWindowListener("Thunderbird-internal-Toolbox");
     ExtensionSupport.unregisterWindowListener("Thunderbird-internal-BrowserConsole");
   },
 
   // nsIObserver implementation
   observe(aSubject, aTopic, aData) {
     switch (aTopic) {
-    case "app-startup":
-      const {BootstrapLoader} = ChromeUtils.import("resource:///modules/BootstrapLoader.jsm");
-      AddonManager.addExternalExtensionLoader(BootstrapLoader);
-      break;
     case "xpcom-shutdown":
       this._dispose();
       break;
     case "final-ui-startup":
       this._onProfileStartup();
       break;
     case "mail-startup-done":
       this._onMailStartupDone();
deleted file mode 100644
--- a/mail/test/resources/jsbridge/jsbridge/extension/install.rdf
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0"?>
-<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>jsbridge@mozilla.com</em:id>
-     <em:name>jsbridge</em:name>
-     <em:version>2.4.14</em:version>
-     <em:bootstrap>true</em:bootstrap>
-     <em:creator>Mikeal Rogers</em:creator>
-     <em:description>Python to JavaScript bridge</em:description>
-     <em:targetApplication>
-       <!-- Firefox -->
-       <Description>
-         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-         <em:minVersion>4.0</em:minVersion>
-         <em:maxVersion>20.*</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-       <!-- Thunderbird -->
-       <Description>
-         <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
-         <em:minVersion>5.0</em:minVersion>
-         <em:maxVersion>*</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-       <!-- Sunbird -->
-       <Description>
-         <em:id>{718e30fb-e89b-41dd-9da7-e25a45638b28}</em:id>
-         <em:minVersion>1.0b5</em:minVersion>
-         <em:maxVersion>1.6b1</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-       <!-- SeaMonkey -->
-       <Description>
-         <em:id>{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}</em:id>
-         <em:minVersion>2.1</em:minVersion>
-         <em:maxVersion>2.16</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-         <Description>
-          <em:id>toolkit@mozilla.org</em:id>
-          <em:minVersion>2.0</em:minVersion>
-          <em:maxVersion>20.*</em:maxVersion>
-         </Description>
-     </em:targetApplication>
-   </Description>
-</RDF>
new file mode 100644
--- /dev/null
+++ b/mail/test/resources/jsbridge/jsbridge/extension/manifest.json
@@ -0,0 +1,11 @@
+{
+  "manifest_version": 2,
+  "name": "jsbridge",
+  "version": "2.5.2",
+  "applications": {
+    "gecko": {
+      "id": "jsbridge@mozilla.com"
+    }
+  },
+  "bootstrapped": true
+}
--- a/mail/test/resources/jsbridge/setup.py
+++ b/mail/test/resources/jsbridge/setup.py
@@ -37,17 +37,17 @@
 
 from setuptools import setup, find_packages
 
 desc = """Python to JavaScript bridge interface."""
 summ = """A powerful and extensible Python to JavaScript bridge interface."""
 
 PACKAGE_NAME = "jsbridge_thunderbird"
 # You must update the required version in mozmill's setup.py to match this.
-PACKAGE_VERSION = "2.5.1"
+PACKAGE_VERSION = "2.5.2"
 
 requires = ['mozrunner >= 6.0']
 
 setup(name=PACKAGE_NAME,
       version=PACKAGE_VERSION,
       description=desc,
       long_description=summ,
       author='Mikeal Rogers, Mozilla',
deleted file mode 100755
--- a/mail/test/resources/mozmill/mozmill/extension/install.rdf
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0"?>
-<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>mozmill@mozilla.com</em:id>
-     <em:name>MozMill</em:name>
-     <em:version>1.5.16</em:version>
-     <em:creator>Adam Christian</em:creator>
-     <em:description>A testing extension based on the Windmill Testing Framework client source</em:description>
-     <em:bootstrap>true</em:bootstrap>
-     <em:optionsURL>chrome://mozmill/content/prefs.xul</em:optionsURL>
-     <em:targetApplication>
-       <!-- Firefox -->
-       <Description>
-         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-         <em:minVersion>4.0</em:minVersion>
-         <em:maxVersion>20.*</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-       <!-- Thunderbird -->
-       <Description>
-         <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
-         <em:minVersion>5.0</em:minVersion>
-         <em:maxVersion>*</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-       <!-- Sunbird -->
-       <Description>
-         <em:id>{718e30fb-e89b-41dd-9da7-e25a45638b28}</em:id>
-         <em:minVersion>1.0b5</em:minVersion>
-         <em:maxVersion>1.6b1</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-       <!-- SeaMonkey -->
-       <Description>
-         <em:id>{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}</em:id>
-         <em:minVersion>2.1</em:minVersion>
-         <em:maxVersion>2.16</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-         <Description>
-          <em:id>toolkit@mozilla.org</em:id>
-          <em:minVersion>2.0</em:minVersion>
-          <em:maxVersion>20.*</em:maxVersion>
-         </Description>
-     </em:targetApplication>
-   </Description>
-</RDF>
new file mode 100644
--- /dev/null
+++ b/mail/test/resources/mozmill/mozmill/extension/manifest.json
@@ -0,0 +1,11 @@
+{
+  "manifest_version": 2,
+  "name": "MozMill",
+  "version": "1.6.2",
+  "applications": {
+    "gecko": {
+      "id": "mozmill@mozilla.com"
+    }
+  },
+  "bootstrapped": true
+}
--- a/mail/test/resources/mozmill/setup.py
+++ b/mail/test/resources/mozmill/setup.py
@@ -36,17 +36,17 @@
 # ***** END LICENSE BLOCK *****
 
 from setuptools import setup, find_packages
 
 desc = """UI Automation tool for Mozilla applications."""
 summ = """A tool for full UI automation of Mozilla applications."""
 
 PACKAGE_NAME = "mozmill_thunderbird"
-PACKAGE_VERSION = "1.6.1"
+PACKAGE_VERSION = "1.6.2"
 
 setup(name=PACKAGE_NAME,
       version=PACKAGE_VERSION,
       description=desc,
       long_description=summ,
       author='Mozilla, Mikeal Rogers',
       author_email='mikeal.rogers@gmail.com',
       url='http://github.com/mozautomation/mozmill',