Bug 1457749: Part 3 - Migrate install.rdf parser to use RDFDataSource.jsm. r?Mossop draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 28 Apr 2018 19:27:33 -0700
changeset 789551 86b6a154923b7f6c2d5efa8e57593ddcf94f1750
parent 789550 ff2559cef77d7918f5bfc3053a6604ad716ef15a
push id108277
push usermaglione.k@gmail.com
push dateSun, 29 Apr 2018 03:22:58 +0000
reviewersMossop
bugs1457749
milestone61.0a1
Bug 1457749: Part 3 - Migrate install.rdf parser to use RDFDataSource.jsm. r?Mossop This is based on a script I'd already created to migrate test extensions to the JSON format we use to generate fixture add-ons. The delay update add-ons used invalid XML in their manifests, which the built-in parser ignored but the new parser doesn't accept, so I had to fix those, too. MozReview-Commit-ID: BnuxZiBhhJL
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
toolkit/mozapps/extensions/internal/RDFManifestConverter.jsm
toolkit/mozapps/extensions/internal/UpdateRDFConverter.jsm
toolkit/mozapps/extensions/internal/XPIInstall.jsm
toolkit/mozapps/extensions/internal/moz.build
toolkit/mozapps/extensions/test/addons/test_delay_update_complete_v2/install.rdf
toolkit/mozapps/extensions/test/addons/test_delay_update_defer_v2/install.rdf
toolkit/mozapps/extensions/test/xpcshell/test_update_rdf.js
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -29,23 +29,24 @@ XPCOMUtils.defineLazyGetter(this, "Manag
   let {Management} = ChromeUtils.import("resource://gre/modules/Extension.jsm", {});
   return Management;
 });
 
 ChromeUtils.defineModuleGetter(this, "FileTestUtils",
                                "resource://testing-common/FileTestUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "HttpServer",
                                "resource://testing-common/httpd.js");
+ChromeUtils.defineModuleGetter(this, "InstallRDF",
+                               "resource://gre/modules/addons/RDFManifestConverter.jsm");
 ChromeUtils.defineModuleGetter(this, "MockRegistrar",
                                "resource://testing-common/MockRegistrar.jsm");
 
 XPCOMUtils.defineLazyServiceGetters(this, {
   aomStartup: ["@mozilla.org/addons/addon-manager-startup;1", "amIAddonManagerStartup"],
   proxyService: ["@mozilla.org/network/protocol-proxy-service;1", "nsIProtocolProxyService"],
-  rdfService: ["@mozilla.org/rdf/rdf-service;1", "nsIRDFService"],
   uuidGen: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"],
 });
 
 XPCOMUtils.defineLazyGetter(this, "AppInfo", () => {
   let AppInfo = {};
   ChromeUtils.import("resource://testing-common/AppInfo.jsm", AppInfo);
   return AppInfo;
 });
@@ -56,24 +57,16 @@ const PREF_DISABLE_SECURITY = ("security
 const ArrayBufferInputStream = Components.Constructor(
   "@mozilla.org/io/arraybuffer-input-stream;1",
   "nsIArrayBufferInputStream", "setData");
 
 const nsFile = Components.Constructor(
   "@mozilla.org/file/local;1",
   "nsIFile", "initWithPath");
 
-const RDFXMLParser = Components.Constructor(
-  "@mozilla.org/rdf/xml-parser;1",
-  "nsIRDFXMLParser", "parseString");
-
-const RDFDataSource = Components.Constructor(
-  "@mozilla.org/rdf/datasource;1?name=in-memory-datasource",
-  "nsIRDFDataSource");
-
 const ZipReader = Components.Constructor(
   "@mozilla.org/libjar/zip-reader;1",
   "nsIZipReader", "open");
 
 const ZipWriter = Components.Constructor(
   "@mozilla.org/zipwriter;1",
   "nsIZipWriter", "open");
 
@@ -608,25 +601,18 @@ var AddonTestUtils = {
   getIDFromExtension(file) {
     return this.getIDFromManifest(this.getManifestURI(file));
   },
 
   async getIDFromManifest(manifestURI) {
     let body = await fetch(manifestURI.spec);
 
     if (manifestURI.spec.endsWith(".rdf")) {
-      let data = await body.text();
-
-      let ds = new RDFDataSource();
-      new RDFXMLParser(ds, manifestURI, data);
-
-      let rdfID = ds.GetTarget(rdfService.GetResource("urn:mozilla:install-manifest"),
-                               rdfService.GetResource("http://www.mozilla.org/2004/em-rdf#id"),
-                               true);
-      return rdfID.QueryInterface(Ci.nsIRDFLiteral).Value;
+      let manifest = InstallRDF.loadFromBuffer(await body.arrayBuffer()).decode();
+      return manifest.id;
     }
 
     let manifest = await body.json();
     try {
       return manifest.applications.gecko.id;
     } catch (e) {
       // IDs for WebExtensions are extracted from the certificate when
       // not present in the manifest, so just generate a random one.
--- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
@@ -27,17 +27,17 @@ ChromeUtils.defineModuleGetter(this, "Ad
                                "resource://gre/modules/addons/AddonRepository.jsm");
 ChromeUtils.defineModuleGetter(this, "Blocklist",
                                "resource://gre/modules/Blocklist.jsm");
 ChromeUtils.defineModuleGetter(this, "CertUtils",
                                "resource://gre/modules/CertUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "ServiceRequest",
                                "resource://gre/modules/ServiceRequest.jsm");
 ChromeUtils.defineModuleGetter(this, "UpdateRDFConverter",
-                               "resource://gre/modules/addons/UpdateRDFConverter.jsm");
+                               "resource://gre/modules/addons/RDFManifestConverter.jsm");
 
 ChromeUtils.import("resource://gre/modules/Log.jsm");
 const LOGGER_ID = "addons.update-checker";
 
 // Create a new logger for use by the Addons Update Checker
 // (Requires AddonManager.jsm)
 var logger = Log.repository.getLogger(LOGGER_ID);
 
rename from toolkit/mozapps/extensions/internal/UpdateRDFConverter.jsm
rename to toolkit/mozapps/extensions/internal/RDFManifestConverter.jsm
--- a/toolkit/mozapps/extensions/internal/UpdateRDFConverter.jsm
+++ b/toolkit/mozapps/extensions/internal/RDFManifestConverter.jsm
@@ -1,26 +1,28 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
+ /* 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 = ["UpdateRDFConverter"];
+var EXPORTED_SYMBOLS = ["InstallRDF", "UpdateRDFConverter"];
 
 ChromeUtils.defineModuleGetter(this, "RDFDataSource",
                                "resource://gre/modules/addons/RDFDataSource.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 
 const PREFIX_ITEM           = "urn:mozilla:item:";
 const PREFIX_EXTENSION      = "urn:mozilla:extension:";
 const PREFIX_THEME          = "urn:mozilla:theme:";
 
 const TOOLKIT_ID            = "toolkit@mozilla.org";
 
+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();
 }
 
@@ -45,16 +47,87 @@ class Manifest {
     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", "icon64URL",
+                 "bootstrap", "unpack", "strictCompatibility",
+                 "hasEmbeddedWebExtension"];
+    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._readArrayProps(source, localized, ["locale"]);
+      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;
+  }
+}
+
 class UpdateRDF extends Manifest {
   decode() {
     let addons = {};
     let result = {addons};
 
     for (let resource of this.ds.getAllResources()) {
       let id;
       let uri = resource.getURI();
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -40,16 +40,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   FileUtils: "resource://gre/modules/FileUtils.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   ProductAddonChecker: "resource://gre/modules/addons/ProductAddonChecker.jsm",
   UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
   ZipUtils: "resource://gre/modules/ZipUtils.jsm",
 
   AddonInternal: "resource://gre/modules/addons/XPIDatabase.jsm",
+  InstallRDF: "resource://gre/modules/addons/RDFManifestConverter.jsm",
   XPIDatabase: "resource://gre/modules/addons/XPIDatabase.jsm",
   XPIInternal: "resource://gre/modules/addons/XPIProvider.jsm",
   XPIProvider: "resource://gre/modules/addons/XPIProvider.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "IconDetails", () => {
   return ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm", {}).ExtensionParent.IconDetails;
 });
@@ -63,26 +64,21 @@ const BinaryOutputStream = Components.Co
                                                   "nsIBinaryOutputStream", "setOutputStream");
 const CryptoHash = Components.Constructor("@mozilla.org/security/hash;1",
                                           "nsICryptoHash", "initWithString");
 const FileOutputStream = Components.Constructor("@mozilla.org/network/file-output-stream;1",
                                                 "nsIFileOutputStream", "init");
 const ZipReader = Components.Constructor("@mozilla.org/libjar/zip-reader;1",
                                          "nsIZipReader", "open");
 
-const RDFDataSource = Components.Constructor(
-  "@mozilla.org/rdf/datasource;1?name=in-memory-datasource", "nsIRDFDataSource");
-const parseRDFString = Components.Constructor(
-  "@mozilla.org/rdf/xml-parser;1", "nsIRDFXMLParser", "parseString");
-
 XPCOMUtils.defineLazyServiceGetters(this, {
   gCertDB: ["@mozilla.org/security/x509certdb;1", "nsIX509CertDB"],
-  gRDF: ["@mozilla.org/rdf/rdf-service;1", "nsIRDFService"],
 });
 
+const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
 
 const PREF_ALLOW_NON_RESTARTLESS      = "extensions.legacy.non-restartless.enabled";
 const PREF_DISTRO_ADDONS_PERMS        = "extensions.distroAddons.promptForPermissions";
 const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
 const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
 const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
 const PREF_XPI_ENABLED                = "xpinstall.enabled";
 const PREF_XPI_DIRECT_WHITELISTED     = "xpinstall.whitelist.directRequest";
@@ -160,26 +156,22 @@ const FILE_WEB_MANIFEST               = 
 const KEY_PROFILEDIR                  = "ProfD";
 const KEY_TEMPDIR                     = "TmpD";
 
 const KEY_APP_PROFILE                 = "app-profile";
 
 const DIR_STAGE                       = "staged";
 const DIR_TRASH                       = "trash";
 
-const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
-const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
-
 // Properties that exist in the install manifest
 const PROP_METADATA      = ["id", "version", "type", "internalName", "updateURL",
                             "optionsURL", "optionsType", "aboutURL",
                             "iconURL", "icon64URL"];
 const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
 const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
-const PROP_TARGETAPP     = ["id", "minVersion", "maxVersion"];
 
 // Map new string type identifiers to old style nsIUpdateItem types.
 // Retired values:
 // 32 = multipackage xpi file
 // 8 = locale
 // 256 = apiextension
 // 128 = experiment
 // theme = 4
@@ -443,54 +435,16 @@ function waitForAllPromises(promises) {
         rejectValue = value;
       })
     );
     Promise.all(newPromises)
            .then((results) => shouldReject ? reject(rejectValue) : resolve(results));
   });
 }
 
-function EM_R(aProperty) {
-  return gRDF.GetResource(PREFIX_NS_EM + aProperty);
-}
-
-/**
- * Converts an RDF literal, resource or integer into a string.
- *
- * @param {nsISupports} aLiteral
- *        The RDF object to convert
- * @returns {string?}
- *        A string if the object could be converted or null
- */
-function getRDFValue(aLiteral) {
-  if (aLiteral instanceof Ci.nsIRDFLiteral)
-    return aLiteral.Value;
-  if (aLiteral instanceof Ci.nsIRDFResource)
-    return aLiteral.Value;
-  if (aLiteral instanceof Ci.nsIRDFInt)
-    return aLiteral.Value;
-  return null;
-}
-
-/**
- * Gets an RDF property as a string
- *
- * @param {nsIRDFDataSource} aDs
- *        The RDF datasource to read the property from
- * @param {nsIRDFResource} aResource
- *        The RDF resource to read the property from
- * @param {string} aProperty
- *        The property to read
- * @returns {string?}
- *        A string if the property existed or null
- */
-function getRDFProperty(aDs, aResource, aProperty) {
-  return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
-}
-
 /**
  * Reads an AddonInternal object from a manifest stream.
  *
  * @param {nsIURI} aUri
  *        A |file:| or |jar:| URL for the manifest
  * @returns {AddonInternal}
  * @throws if the install manifest in the stream is corrupt or could not
  *         be read
@@ -623,50 +577,37 @@ async function loadManifestFromWebManife
  *        The URI that the manifest is being read from
  * @param {string} aData
  *        The manifest text
  * @returns {AddonInternal}
  * @throws if the install manifest in the RDF stream is corrupt or could not
  *         be read
  */
 async function loadManifestFromRDF(aUri, aData) {
-  function getPropertyArray(aDs, aSource, aProperty) {
-    let values = [];
-    let targets = aDs.GetTargets(aSource, EM_R(aProperty), true);
-    while (targets.hasMoreElements())
-      values.push(getRDFValue(targets.getNext()));
-
-    return values;
-  }
-
   /**
    * Reads locale properties from either the main install manifest root or
    * an em:localized section in the install manifest.
    *
-   * @param {nsIRDFDataSource} aDs
-   *         The datasource to read from.
-   * @param {nsIRDFResource} aSource
-   *         The resource to read the properties from.
+   * @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(aDs, aSource, isDefault, aSeenLocales) {
-    let locale = { };
+  function readLocale(aSource, isDefault, aSeenLocales) {
+    let locale = {};
     if (!isDefault) {
       locale.locales = [];
-      let targets = ds.GetTargets(aSource, EM_R("locale"), true);
-      while (targets.hasMoreElements()) {
-        let localeName = getRDFValue(targets.getNext());
+      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;
         }
@@ -675,42 +616,36 @@ async function loadManifestFromRDF(aUri,
       }
 
       if (locale.locales.length == 0) {
         logger.warn("Ignoring localized properties with no listed locales");
         return null;
       }
     }
 
-    for (let prop of PROP_LOCALE_SINGLE) {
-      locale[prop] = getRDFProperty(aDs, aSource, prop);
-    }
-
-    for (let prop of PROP_LOCALE_MULTI) {
-      // Don't store empty arrays
-      let props = getPropertyArray(aDs, aSource,
-                                   prop.substring(0, prop.length - 1));
-      if (props.length > 0)
-        locale[prop] = props;
+    for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) {
+      if (hasOwnProperty(aSource, prop)) {
+        locale[prop] = aSource[prop];
+      }
     }
 
     return locale;
   }
 
-  let ds = new RDFDataSource();
-  parseRDFString(ds, aUri, aData);
-
-  let root = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
+  let manifest = InstallRDF.loadFromString(aData).decode();
+
   let addon = new AddonInternal();
   for (let prop of PROP_METADATA) {
-    addon[prop] = getRDFProperty(ds, root, prop);
+    if (hasOwnProperty(manifest, prop)) {
+      addon[prop] = manifest[prop];
+    }
   }
 
   if (!addon.type) {
-    addon.type = addon.internalName ? "theme" : "extension";
+    addon.type = "extension";
   } else {
     let type = addon.type;
     addon.type = null;
     for (let name in TYPES) {
       if (TYPES[name] == type) {
         addon.type = name;
         break;
       }
@@ -722,26 +657,26 @@ async function loadManifestFromRDF(aUri,
 
   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");
 
-  addon.strictCompatibility = !(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
-                              getRDFProperty(ds, root, "strictCompatibility") == "true";
+  addon.strictCompatibility = (!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
+                               manifest.strictCompatibility == "true");
 
   // Only read these properties for extensions.
   if (addon.type == "extension") {
-    addon.bootstrap = getRDFProperty(ds, root, "bootstrap") == "true";
+    addon.bootstrap = manifest.bootstrap == "true";
     if (!addon.bootstrap && !Services.prefs.getBoolPref(PREF_ALLOW_NON_RESTARTLESS, false))
         throw new Error(`Non-restartless extensions no longer supported`);
 
-    addon.hasEmbeddedWebExtension = getRDFProperty(ds, root, "hasEmbeddedWebExtension") == "true";
+    addon.hasEmbeddedWebExtension = manifest.hasEmbeddedWebExtension == "true";
 
     if (addon.optionsType &&
         addon.optionsType != AddonManager.OPTIONS_INLINE_BROWSER &&
         addon.optionsType != AddonManager.OPTIONS_TYPE_TAB) {
       throw new Error("Install manifest specifies unknown optionsType: " + addon.optionsType);
     }
 
     if (addon.hasEmbeddedWebExtension) {
@@ -763,73 +698,52 @@ async function loadManifestFromRDF(aUri,
     }
 
     // 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;
-
-    if (addon.type == "theme") {
-      if (!addon.internalName)
-        throw new Error("Themes must include an internalName property");
-      addon.skinnable = getRDFProperty(ds, root, "skinnable") == "true";
-    }
   }
 
-  addon.defaultLocale = readLocale(ds, root, true);
+  addon.defaultLocale = readLocale(manifest, true);
 
   let seenLocales = [];
   addon.locales = [];
-  let targets = ds.GetTargets(root, EM_R("localized"), true);
-  while (targets.hasMoreElements()) {
-    let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
-    let locale = readLocale(ds, target, false, seenLocales);
+  for (let localeData of manifest.localized || []) {
+    let locale = readLocale(localeData, false, seenLocales);
     if (locale)
       addon.locales.push(locale);
   }
 
-  let dependencies = new Set();
-  targets = ds.GetTargets(root, EM_R("dependency"), true);
-  while (targets.hasMoreElements()) {
-    let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
-    let id = getRDFProperty(ds, target, "id");
-    dependencies.add(id);
-  }
+  let dependencies = new Set(manifest.dependencies);
   addon.dependencies = Object.freeze(Array.from(dependencies));
 
   let seenApplications = [];
   addon.targetApplications = [];
-  targets = ds.GetTargets(root, EM_R("targetApplication"), true);
-  while (targets.hasMoreElements()) {
-    let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
-    let targetAppInfo = {};
-    for (let prop of PROP_TARGETAPP) {
-      targetAppInfo[prop] = getRDFProperty(ds, target, prop);
-    }
-    if (!targetAppInfo.id || !targetAppInfo.minVersion ||
-        !targetAppInfo.maxVersion) {
+  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(targetAppInfo.id)) {
-      logger.warn("Ignoring duplicate targetApplication entry for " + targetAppInfo.id +
+    if (seenApplications.includes(targetApp.id)) {
+      logger.warn("Ignoring duplicate targetApplication entry for " + targetApp.id +
            " in install manifest");
       continue;
     }
-    seenApplications.push(targetAppInfo.id);
-    addon.targetApplications.push(targetAppInfo);
+    seenApplications.push(targetApp.id);
+    addon.targetApplications.push(targetApp);
   }
 
   // Note that we don't need to check for duplicate targetPlatform entries since
   // the RDF service coalesces them for us.
-  let targetPlatforms = getPropertyArray(ds, root, "targetPlatform");
   addon.targetPlatforms = [];
-  for (let targetPlatform of 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);
--- a/toolkit/mozapps/extensions/internal/moz.build
+++ b/toolkit/mozapps/extensions/internal/moz.build
@@ -8,18 +8,18 @@ EXTRA_JS_MODULES.addons += [
     'AddonRepository.jsm',
     'AddonSettings.jsm',
     'AddonUpdateChecker.jsm',
     'Content.js',
     'GMPProvider.jsm',
     'LightweightThemeImageOptimizer.jsm',
     'ProductAddonChecker.jsm',
     'RDFDataSource.jsm',
+    'RDFManifestConverter.jsm',
     'SpellCheckDictionaryBootstrap.js',
-    'UpdateRDFConverter.jsm',
     'XPIDatabase.jsm',
     'XPIInstall.jsm',
     'XPIProvider.jsm',
 ]
 
 TESTING_JS_MODULES += [
     'AddonTestUtils.jsm',
 ]
--- a/toolkit/mozapps/extensions/test/addons/test_delay_update_complete_v2/install.rdf
+++ b/toolkit/mozapps/extensions/test/addons/test_delay_update_complete_v2/install.rdf
@@ -20,8 +20,9 @@
       <Description>
         <em:id>xpcshell@tests.mozilla.org</em:id>
         <em:minVersion>1</em:minVersion>
         <em:maxVersion>1</em:maxVersion>
       </Description>
     </em:targetApplication>
 
   </Description>
+</RDF>
--- a/toolkit/mozapps/extensions/test/addons/test_delay_update_defer_v2/install.rdf
+++ b/toolkit/mozapps/extensions/test/addons/test_delay_update_defer_v2/install.rdf
@@ -20,8 +20,9 @@
       <Description>
         <em:id>xpcshell@tests.mozilla.org</em:id>
         <em:minVersion>1</em:minVersion>
         <em:maxVersion>1</em:maxVersion>
       </Description>
     </em:targetApplication>
 
   </Description>
+</RDF>
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update_rdf.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_rdf.js
@@ -1,14 +1,15 @@
 "use strict";
 
 // Tests the update RDF to JSON converter.
 
 ChromeUtils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm");
-ChromeUtils.import("resource://gre/modules/addons/UpdateRDFConverter.jsm");
+ChromeUtils.defineModuleGetter(this, "UpdateRDFConverter",
+                               "resource://gre/modules/addons/RDFManifestConverter.jsm");
 
 var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 
 testserver.registerDirectory("/data/", do_get_file("data"));
 
 const BASE_URL = "http://example.com/data";
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");