Bug 974498 - Refactor XPIProvider's private zip utilities into a ZipUtils.jsm. r=Mossop
authorJan Keromnes <janx@linux.com>
Tue, 11 Mar 2014 17:01:29 -0400
changeset 173134 b5fec3059a699719ca69a37dfb9bbefb1841f7f6
parent 173133 7a6b4371379fdd624bb41d6d5fbda1257bafc699
child 173135 0dd61eada6c925e48f8949c6858915ed7f8709d5
push id26391
push usercbook@mozilla.com
push dateWed, 12 Mar 2014 11:20:34 +0000
treeherdermozilla-central@a56837cfc67c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMossop
bugs974498
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 974498 - Refactor XPIProvider's private zip utilities into a ZipUtils.jsm. r=Mossop
toolkit/modules/ZipUtils.jsm
toolkit/modules/moz.build
toolkit/modules/tests/xpcshell/test_ZipUtils.js
toolkit/modules/tests/xpcshell/xpcshell.ini
toolkit/modules/tests/xpcshell/zips/zen.zip
toolkit/mozapps/extensions/internal/XPIProvider.jsm
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/ZipUtils.jsm
@@ -0,0 +1,223 @@
+/* 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.EXPORTED_SYMBOLS = [ "ZipUtils" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+                                  "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+
+// The maximum amount of file data to buffer at a time during file extraction
+const EXTRACTION_BUFFER               = 1024 * 512;
+
+
+/**
+ * Asynchronously writes data from an nsIInputStream to an OS.File instance.
+ * The source stream and OS.File are closed regardless of whether the operation
+ * succeeds or fails.
+ * Returns a promise that will be resolved when complete.
+ *
+ * @param  aPath
+ *         The name of the file being extracted for logging purposes.
+ * @param  aStream
+ *         The source nsIInputStream.
+ * @param  aFile
+ *         The open OS.File instance to write to.
+ */
+function saveStreamAsync(aPath, aStream, aFile) {
+  let deferred = Promise.defer();
+
+  // Read the input stream on a background thread
+  let sts = Cc["@mozilla.org/network/stream-transport-service;1"].
+            getService(Ci.nsIStreamTransportService);
+  let transport = sts.createInputTransport(aStream, -1, -1, true);
+  let input = transport.openInputStream(0, 0, 0)
+                       .QueryInterface(Ci.nsIAsyncInputStream);
+  let source = Cc["@mozilla.org/binaryinputstream;1"].
+               createInstance(Ci.nsIBinaryInputStream);
+  source.setInputStream(input);
+
+  let data = Uint8Array(EXTRACTION_BUFFER);
+
+  function readFailed(error) {
+    try {
+      aStream.close();
+    }
+    catch (e) {
+      logger.error("Failed to close JAR stream for " + aPath);
+    }
+
+    aFile.close().then(function() {
+      deferred.reject(error);
+    }, function(e) {
+      logger.error("Failed to close file for " + aPath);
+      deferred.reject(error);
+    });
+  }
+
+  function readData() {
+    try {
+      let count = Math.min(source.available(), data.byteLength);
+      source.readArrayBuffer(count, data.buffer);
+
+      aFile.write(data, { bytes: count }).then(function() {
+        input.asyncWait(readData, 0, 0, Services.tm.currentThread);
+      }, readFailed);
+    }
+    catch (e if e.result == Cr.NS_BASE_STREAM_CLOSED) {
+      deferred.resolve(aFile.close());
+    }
+    catch (e) {
+      readFailed(e);
+    }
+  }
+
+  input.asyncWait(readData, 0, 0, Services.tm.currentThread);
+
+  return deferred.promise;
+}
+
+
+this.ZipUtils = {
+
+  /**
+   * Asynchronously extracts files from a ZIP file into a directory.
+   * Returns a promise that will be resolved when the extraction is complete.
+   *
+   * @param  aZipFile
+   *         The source ZIP file that contains the add-on.
+   * @param  aDir
+   *         The nsIFile to extract to.
+   */
+  extractFilesAsync: function ZipUtils_extractFilesAsync(aZipFile, aDir) {
+    let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+                    createInstance(Ci.nsIZipReader);
+
+    try {
+      zipReader.open(aZipFile);
+    }
+    catch (e) {
+      return Promise.reject(e);
+    }
+
+    return Task.spawn(function() {
+      // Get all of the entries in the zip and sort them so we create directories
+      // before files
+      let entries = zipReader.findEntries(null);
+      let names = [];
+      while (entries.hasMore())
+        names.push(entries.getNext());
+      names.sort();
+
+      for (let name of names) {
+        let entryName = name;
+        let zipentry = zipReader.getEntry(name);
+        let path = OS.Path.join(aDir.path, ...name.split("/"));
+
+        if (zipentry.isDirectory) {
+          try {
+            yield OS.File.makeDir(path);
+          }
+          catch (e) {
+            dump("extractFilesAsync: failed to create directory " + path + "\n");
+            throw e;
+          }
+        }
+        else {
+          let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE };
+          try {
+            let file = yield OS.File.open(path, { truncate: true }, options);
+            if (zipentry.realSize == 0)
+              yield file.close();
+            else
+              yield saveStreamAsync(path, zipReader.getInputStream(entryName), file);
+          }
+          catch (e) {
+            dump("extractFilesAsync: failed to extract file " + path + "\n");
+            throw e;
+          }
+        }
+      }
+
+      zipReader.close();
+    }).then(null, (e) => {
+      zipReader.close();
+      throw e;
+    });
+  },
+
+  /**
+   * Extracts files from a ZIP file into a directory.
+   *
+   * @param  aZipFile
+   *         The source ZIP file that contains the add-on.
+   * @param  aDir
+   *         The nsIFile to extract to.
+   */
+  extractFiles: function ZipUtils_extractFiles(aZipFile, aDir) {
+    function getTargetFile(aDir, entry) {
+      let target = aDir.clone();
+      entry.split("/").forEach(function(aPart) {
+        target.append(aPart);
+      });
+      return target;
+    }
+
+    let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+                    createInstance(Ci.nsIZipReader);
+    zipReader.open(aZipFile);
+
+    try {
+      // create directories first
+      let entries = zipReader.findEntries("*/");
+      while (entries.hasMore()) {
+        let entryName = entries.getNext();
+        let target = getTargetFile(aDir, entryName);
+        if (!target.exists()) {
+          try {
+            target.create(Ci.nsIFile.DIRECTORY_TYPE,
+                          FileUtils.PERMS_DIRECTORY);
+          }
+          catch (e) {
+            dump("extractFiles: failed to create target directory for extraction file = " + target.path + "\n");
+          }
+        }
+      }
+
+      entries = zipReader.findEntries(null);
+      while (entries.hasMore()) {
+        let entryName = entries.getNext();
+        let target = getTargetFile(aDir, entryName);
+        if (target.exists())
+          continue;
+
+        zipReader.extract(entryName, target);
+        try {
+          target.permissions |= FileUtils.PERMS_FILE;
+        }
+        catch (e) {
+          dump("Failed to set permissions " + aPermissions.toString(8) + " on " + target.path + "\n");
+        }
+      }
+    }
+    finally {
+      zipReader.close();
+    }
+  }
+
+};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -46,16 +46,17 @@ EXTRA_JS_MODULES += [
     'sessionstore/XPathGenerator.jsm',
     'ShortcutUtils.jsm',
     'Sntp.jsm',
     'SpatialNavigation.jsm',
     'Sqlite.jsm',
     'Task.jsm',
     'TelemetryTimestamps.jsm',
     'Timer.jsm',
+    'ZipUtils.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'CertUtils.jsm',
     'ResetProfile.jsm',
     'Services.jsm',
     'Troubleshoot.jsm',
     'UpdateChannel.jsm',
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_ZipUtils.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ARCHIVE = "zips/zen.zip";
+const SUBDIR = "zen";
+const ENTRIES = ["beyond.txt", "waterwood.txt"];
+
+Components.utils.import("resource://gre/modules/ZipUtils.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+
+const archive = do_get_file(ARCHIVE, false);
+const dir = do_get_profile().clone();
+dir.append("test_ZipUtils");
+
+function run_test() {
+  run_next_test();
+}
+
+function ensureExtracted(target) {
+  target.append(SUBDIR);
+  do_check_true(target.exists());
+
+  for (let i = 0; i < ENTRIES.length; i++) {
+    let entry = target.clone();
+    entry.append(ENTRIES[i]);
+    do_print("ENTRY " + entry.path);
+    do_check_true(entry.exists());
+  }
+}
+
+
+add_task(function test_extractFiles() {
+  let target = dir.clone();
+  target.append("test_extractFiles");
+
+  try {
+    ZipUtils.extractFiles(archive, target);
+  } catch(e) {
+    do_throw("Failed to extract synchronously!");
+  }
+
+  ensureExtracted(target);
+});
+
+add_task(function test_extractFilesAsync() {
+  let target = dir.clone();
+  target.append("test_extractFilesAsync");
+  target.create(Components.interfaces.nsIFile.DIRECTORY_TYPE,
+    FileUtils.PERMS_DIRECTORY);
+
+  yield ZipUtils.extractFilesAsync(archive, target).then(
+    function success() {
+      do_print("SUCCESS");
+      ensureExtracted(target);
+    },
+    function failure() {
+      do_print("FAILURE");
+      do_throw("Failed to extract asynchronously!");
+    }
+  );
+});
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 head =
 tail =
 support-files =
   propertyLists/bug710259_propertyListBinary.plist
   propertyLists/bug710259_propertyListXML.plist
   chromeappsstore.sqlite
+  zips/zen.zip
 
 [test_AsyncShutdown.js]
 [test_DeferredTask.js]
 [test_dict.js]
 [test_FileUtils.js]
 [test_Http.js]
 [test_Log.js]
 [test_PermissionsUtils.js]
@@ -17,9 +18,9 @@ support-files =
 [test_Promise.js]
 [test_propertyListsUtils.js]
 [test_readCertPrefs.js]
 [test_Services.js]
 [test_sqlite.js]
 [test_task.js]
 [test_TelemetryTimestamps.js]
 [test_timer.js]
-
+[test_ZipUtils.js]
new file mode 100644
index 0000000000000000000000000000000000000000..372010a3cc72306dd133841b48cd4f65337944d2
GIT binary patch
literal 1058
zc$^FHW@h1H0D;qG=`IXNfQ3PZp(-^`KQx4sfw}j*WN;r4msW5yFtWU021<j70HAIW
zpjr-wtw7z9I{H2*fV>+(%m>w-lv<gem!elvQ35t64QNIhra7{Gd$X7vMOuGXc3oZ?
z7_SieW^r%AUW@W_wrkb~_NU5{a)OQ=YUogs@JJS(XtJqS|M9Lj(L1s$E#&W8#-8Hw
z=9y9^AvN1ean;RjF?wD9gG*;H|K*IVaFvKGl2fZ|JLvsj$=aBX9kRmex`n%I?wT);
zo)`W)|B77`*W(Vu+$AgC<`|ewvs$#HPGtY<qL0CKa*gvodKF(1P^(z3YRi7l^mKUP
z`Shpn*56v+^;xoFb-$Tx>Xio@{emaW$XY)kLTTOK#@Zz}OOG%k!ecuyJdPUk>eMkZ
zFuVt1L1=iCCzhlZmFMRpf+PzV9EF%c;v0B2OT<xN-}m4eet~bQ-<~-7GoF9z^WM+d
z|N4cykFB=<;b>#=6zE-(aZSd|YtaPW-3M7dW^PMQyMHLFSt-OhwDjuttKp_o&Y9lo
z`xwIZben<BidFxPxa<^o``hyNuYW75E9Ll~9Z>Gx<fykbGvSMeqR1&tkGg3NnoeO8
zg6GvQ5q<klXWpMz@v+D5-v0NBdqeC5|5&316GE5jFeR$=Hg0=kXtDiior2l?{p!C<
z#QObjUypB8@0q9eSSu}Iite&M(@$+ZzvQ&5eeLmU_x5z!hrix^`AS=l*E+AK62jHY
z7wck5POtv=X_xhmm&=W>9o=&N|K$az(%q)YnRqGp^6q@*cp)ucP`o>@RQh~);kNlK
zno~k<D{TGx;AUV(vW=RRNC>;fk1i*sQ`z!ti)PQyEwfuwA9JKT^ZT??+dPNJu-Oe=
zYCdxnYL?FB+iDV^KjpN^dL8B0Tlx2>ZM<olwSM}?9~*3cUOPAKz=!vu>My(aIehYp
zo(7dInLF!)E&C($0B=SnIc8iLLjuYYVEF3@q9NIY6_QObvIxi|T$u!75-@!*Y-zNE
snuMHXSRq*kkC~VW4cSaTMh0xjjun#ZFpOno18HRj!mmJDoEgLe0A|{NZU6uP
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -18,16 +18,18 @@ Components.utils.import("resource://gre/
 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                   "resource://gre/modules/addons/AddonRepository.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ChromeManifestParser",
                                   "resource://gre/modules/ChromeManifestParser.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils",
+                                  "resource://gre/modules/ZipUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
                                   "resource://gre/modules/PermissionsUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
@@ -107,19 +109,16 @@ const KEY_APP_SYSTEM_USER             = 
 const NOTIFICATION_FLUSH_PERMISSIONS  = "flush-pending-permissions";
 const XPI_PERMISSION                  = "install";
 
 const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
 const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
 
 const TOOLKIT_ID                      = "toolkit@mozilla.org";
 
-// The maximum amount of file data to buffer at a time during file extraction
-const EXTRACTION_BUFFER               = 1024 * 512;
-
 // The value for this is in Makefile.in
 #expand const DB_SCHEMA                       = __MOZ_EXTENSIONS_DB_SCHEMA__;
 
 // Properties that exist in the install manifest
 const PROP_METADATA      = ["id", "version", "type", "internalName", "updateURL",
                             "updateKey", "optionsURL", "optionsType", "aboutURL",
                             "iconURL", "icon64URL"];
 const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
@@ -1124,209 +1123,16 @@ function getTemporaryFile() {
   let random = Math.random().toString(36).replace(/0./, '').substr(-3);
   file.append("tmp-" + random + ".xpi");
   file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
 
   return file;
 }
 
 /**
- * Asynchronously writes data from an nsIInputStream to an OS.File instance.
- * The source stream and OS.File are closed regardless of whether the operation
- * succeeds or fails.
- * Returns a promise that will be resolved when complete.
- *
- * @param  aPath
- *         The name of the file being extracted for logging purposes.
- * @param  aStream
- *         The source nsIInputStream.
- * @param  aFile
- *         The open OS.File instance to write to.
- */
-function saveStreamAsync(aPath, aStream, aFile) {
-  let deferred = Promise.defer();
-
-  // Read the input stream on a background thread
-  let sts = Cc["@mozilla.org/network/stream-transport-service;1"].
-            getService(Ci.nsIStreamTransportService);
-  let transport = sts.createInputTransport(aStream, -1, -1, true);
-  let input = transport.openInputStream(0, 0, 0)
-                       .QueryInterface(Ci.nsIAsyncInputStream);
-  let source = Cc["@mozilla.org/binaryinputstream;1"].
-               createInstance(Ci.nsIBinaryInputStream);
-  source.setInputStream(input);
-
-  let data = Uint8Array(EXTRACTION_BUFFER);
-
-  function readFailed(error) {
-    try {
-      aStream.close();
-    }
-    catch (e) {
-      logger.error("Failed to close JAR stream for " + aPath);
-    }
-
-    aFile.close().then(function() {
-      deferred.reject(error);
-    }, function(e) {
-      logger.error("Failed to close file for " + aPath);
-      deferred.reject(error);
-    });
-  }
-
-  function readData() {
-    try {
-      let count = Math.min(source.available(), data.byteLength);
-      source.readArrayBuffer(count, data.buffer);
-
-      aFile.write(data, { bytes: count }).then(function() {
-        input.asyncWait(readData, 0, 0, Services.tm.currentThread);
-      }, readFailed);
-    }
-    catch (e if e.result == Cr.NS_BASE_STREAM_CLOSED) {
-      deferred.resolve(aFile.close());
-    }
-    catch (e) {
-      readFailed(e);
-    }
-  }
-
-  input.asyncWait(readData, 0, 0, Services.tm.currentThread);
-
-  return deferred.promise;
-}
-
-/**
- * Asynchronously extracts files from a ZIP file into a directory.
- * Returns a promise that will be resolved when the extraction is complete.
- *
- * @param  aZipFile
- *         The source ZIP file that contains the add-on.
- * @param  aDir
- *         The nsIFile to extract to.
- */
-function extractFilesAsync(aZipFile, aDir) {
-  let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
-                  createInstance(Ci.nsIZipReader);
-
-  try {
-    zipReader.open(aZipFile);
-  }
-  catch (e) {
-    return Promise.reject(e);
-  }
-
-  return Task.spawn(function() {
-    // Get all of the entries in the zip and sort them so we create directories
-    // before files
-    let entries = zipReader.findEntries(null);
-    let names = [];
-    while (entries.hasMore())
-      names.push(entries.getNext());
-    names.sort();
-
-    for (let name of names) {
-      let entryName = name;
-      let zipentry = zipReader.getEntry(name);
-      let path = OS.Path.join(aDir.path, ...name.split("/"));
-
-      if (zipentry.isDirectory) {
-        try {
-          yield OS.File.makeDir(path);
-        }
-        catch (e) {
-          logger.error("extractFilesAsync: failed to create directory " + path, e);
-          throw e;
-        }
-      }
-      else {
-        let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE };
-        try {
-          let file = yield OS.File.open(path, { truncate: true }, options);
-          if (zipentry.realSize == 0)
-            yield file.close();
-          else
-            yield saveStreamAsync(path, zipReader.getInputStream(entryName), file);
-        }
-        catch (e) {
-          logger.error("extractFilesAsync: failed to extract file " + path, e);
-          throw e;
-        }
-      }
-    }
-
-    zipReader.close();
-  }).then(null, (e) => {
-    zipReader.close();
-    throw e;
-  });
-}
-
-/**
- * Extracts files from a ZIP file into a directory.
- *
- * @param  aZipFile
- *         The source ZIP file that contains the add-on.
- * @param  aDir
- *         The nsIFile to extract to.
- */
-function extractFiles(aZipFile, aDir) {
-  function getTargetFile(aDir, entry) {
-    let target = aDir.clone();
-    entry.split("/").forEach(function(aPart) {
-      target.append(aPart);
-    });
-    return target;
-  }
-
-  let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
-                  createInstance(Ci.nsIZipReader);
-  zipReader.open(aZipFile);
-
-  try {
-    // create directories first
-    let entries = zipReader.findEntries("*/");
-    while (entries.hasMore()) {
-      var entryName = entries.getNext();
-      let target = getTargetFile(aDir, entryName);
-      if (!target.exists()) {
-        try {
-          target.create(Ci.nsIFile.DIRECTORY_TYPE,
-                        FileUtils.PERMS_DIRECTORY);
-        }
-        catch (e) {
-          logger.error("extractFiles: failed to create target directory for " +
-                "extraction file = " + target.path, e);
-        }
-      }
-    }
-
-    entries = zipReader.findEntries(null);
-    while (entries.hasMore()) {
-      let entryName = entries.getNext();
-      let target = getTargetFile(aDir, entryName);
-      if (target.exists())
-        continue;
-
-      zipReader.extract(entryName, target);
-      try {
-        target.permissions |= FileUtils.PERMS_FILE;
-      }
-      catch (e) {
-        logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
-             target.path, e);
-      }
-    }
-  }
-  finally {
-    zipReader.close();
-  }
-}
-
-/**
  * Verifies that a zip file's contents are all signed by the same principal.
  * Directory entries and anything in the META-INF directory are not checked.
  *
  * @param  aZip
  *         A nsIZipReader to check
  * @param  aPrincipal
  *         The nsIPrincipal to compare against
  * @return true if all the contents that should be signed were signed by the
@@ -2420,17 +2226,17 @@ var XPIProvider = {
               targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
             }
             catch (e) {
               logger.error("Failed to create staging directory for add-on " + addon.id, e);
               continue;
             }
 
             try {
-              extractFiles(stagedXPI, targetDir);
+              ZipUtils.extractFiles(stagedXPI, targetDir);
             }
             catch (e) {
               logger.error("Failed to extract staged XPI for add-on " + addon.id + " in " +
                     aLocation.name, e);
             }
           }
           else {
             try {
@@ -5546,17 +5352,17 @@ AddonInstall.prototype = {
 
       // First stage the file regardless of whether restarting is necessary
       if (this.addon.unpack || Prefs.getBoolPref(PREF_XPI_UNPACK, false)) {
         logger.debug("Addon " + this.addon.id + " will be installed as " +
             "an unpacked directory");
         stagedAddon.append(this.addon.id);
         yield removeAsync(stagedAddon);
         yield OS.File.makeDir(stagedAddon.path);
-        yield extractFilesAsync(this.file, stagedAddon);
+        yield ZipUtils.extractFilesAsync(this.file, stagedAddon);
         installedUnpacked = 1;
       }
       else {
         logger.debug("Addon " + this.addon.id + " will be installed as " +
             "a packed xpi");
         stagedAddon.append(this.addon.id + ".xpi");
         yield removeAsync(stagedAddon);
         yield OS.File.copy(this.file.path, stagedAddon.path);