Bug 961665 - Native implementation of OS.File.read, js code. r=froydnj
☠☠ backed out by f547db6d8bc8 ☠ ☠
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Thu, 13 Mar 2014 09:51:50 -0400
changeset 191677 d741e117a0332b13e60a9cb38b7cda2ace659e85
parent 191676 1fd26e822e737317d63cfa135a5c86fd7386b63d
child 191678 c6ca1aa3887a9b20452025a25a0c5d9616b77cae
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs961665
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 961665 - Native implementation of OS.File.read, js code. r=froydnj
toolkit/components/osfile/modules/moz.build
toolkit/components/osfile/modules/osfile_async_front.jsm
toolkit/components/osfile/modules/osfile_async_worker.js
toolkit/components/osfile/modules/osfile_native.jsm
toolkit/components/osfile/modules/osfile_shared_front.jsm
toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
toolkit/components/osfile/modules/osfile_win_allthreads.jsm
--- a/toolkit/components/osfile/modules/moz.build
+++ b/toolkit/components/osfile/modules/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 JS_MODULES_PATH = 'modules/osfile'
 
 EXTRA_JS_MODULES += [
     '_PromiseWorker.jsm',
     'osfile_async_front.jsm',
     'osfile_async_worker.js',
+    'osfile_native.jsm',
     'osfile_shared_allthreads.jsm',
     'osfile_shared_front.jsm',
     'osfile_unix_allthreads.jsm',
     'osfile_unix_back.jsm',
     'osfile_unix_front.jsm',
     'osfile_win_allthreads.jsm',
     'osfile_win_back.jsm',
     'osfile_win_front.jsm',
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -53,16 +53,17 @@ Cu.import("resource://gre/modules/Promis
 Cu.import("resource://gre/modules/Task.jsm", this);
 
 // The implementation of communications
 Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
 
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
+let Native = Cu.import("resource://gre/modules/osfile/osfile_native.jsm", {});
 
 /**
  * Constructors for decoding standard exceptions
  * received from the worker.
  */
 const EXCEPTION_CONSTRUCTORS = {
   EvalError: function(error) {
     return new EvalError(error.message, error.fileName, error.lineNumber);
@@ -343,17 +344,17 @@ const PREF_OSFILE_LOG_REDIRECT = "toolki
 
 /**
  * Safely read a PREF_OSFILE_LOG preference.
  * Returns a value read or, in case of an error, oldPref or false.
  *
  * @param bool oldPref
  *        An optional value that the DEBUG flag was set to previously.
  */
-let readDebugPref = function readDebugPref(prefName, oldPref = false) {
+function readDebugPref(prefName, oldPref = false) {
   let pref = oldPref;
   try {
     pref = Services.prefs.getBoolPref(prefName);
   } catch (x) {
     // In case of an error when reading a pref keep it as is.
   }
   // If neither pref nor oldPref were set, default it to false.
   return pref;
@@ -374,16 +375,29 @@ Services.prefs.addObserver(PREF_OSFILE_L
 SharedAll.Config.DEBUG = readDebugPref(PREF_OSFILE_LOG, false);
 
 Services.prefs.addObserver(PREF_OSFILE_LOG_REDIRECT,
   function prefObserver(aSubject, aTopic, aData) {
     SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, OS.Shared.TEST);
   }, false);
 SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, false);
 
+
+/**
+ * If |true|, use the native implementaiton of OS.File methods
+ * whenever possible. Otherwise, force the use of the JS version.
+ */
+let nativeWheneverAvailable = true;
+const PREF_OSFILE_NATIVE = "toolkit.osfile.native";
+Services.prefs.addObserver(PREF_OSFILE_NATIVE,
+  function prefObserver(aSubject, aTopic, aData) {
+    nativeWheneverAvailable = readDebugPref(PREF_OSFILE_NATIVE, nativeWheneverAvailable);
+  }, false);
+
+
 // Update worker's DEBUG flag if it's true.
 // Don't start the worker just for this, though.
 if (SharedAll.Config.DEBUG && Scheduler.launched) {
   Scheduler.post("SET_DEBUG", [true]);
 }
 
 // Observer topics used for monitoring shutdown
 const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown";
@@ -908,22 +922,42 @@ File.makeDir = function makeDir(path, op
  * - {number} bytes An upper bound to the number of bytes to read.
  * - {string} compression If "lz4" and if the file is compressed using the lz4
  * compression algorithm, decompress the file contents on the fly.
  *
  * @resolves {Uint8Array} A buffer holding the bytes
  * read from the file.
  */
 File.read = function read(path, bytes, options = {}) {
-  let promise = Scheduler.post("read",
-    [Type.path.toMsg(path), bytes, options], path);
-  return promise.then(
-    function onSuccess(data) {
-      return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
-    });
+  if (typeof bytes == "object") {
+    // Passing |bytes| as an argument is deprecated.
+    // We should now be passing it as a field of |options|.
+    options = bytes || {};
+  } else {
+    options = clone(options, ["outExecutionDuration"]);
+    if (typeof bytes != "undefined") {
+      options.bytes = bytes;
+    }
+  }
+
+  if (options.compression || !nativeWheneverAvailable) {
+    // We need to use the JS implementation.
+    let promise = Scheduler.post("read",
+      [Type.path.toMsg(path), bytes, options], path);
+    return promise.then(
+      function onSuccess(data) {
+        if (typeof data == "string") {
+          return data;
+        }
+        return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+      });
+  }
+
+  // Otherwise, use the native implementation.
+  return Scheduler.push(() => Native.read(path, options));
 };
 
 /**
  * Find outs if a file exists.
  *
  * @param {string} path The path to the file.
  *
  * @return {bool} true if the file exists, false otherwise.
--- a/toolkit/components/osfile/modules/osfile_async_worker.js
+++ b/toolkit/components/osfile/modules/osfile_async_worker.js
@@ -357,16 +357,19 @@ const EXCEPTION_NAMES = {
 
      return {
        path: openedFile.path,
        file: resourceId
      };
    },
    read: function read(path, bytes, options) {
      let data = File.read(Type.path.fromMsg(path), bytes, options);
+     if (typeof data == "string") {
+       return data;
+     }
      return new Meta({
          buffer: data.buffer,
          byteOffset: data.byteOffset,
          byteLength: data.byteLength
      }, {
        transfers: [data.buffer]
      });
    },
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_native.jsm
@@ -0,0 +1,70 @@
+/* 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/. */
+
+/**
+ * Native (xpcom) implementation of key OS.File functions
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["read"];
+
+let {results: Cr, utils: Cu, interfaces: Ci} = Components;
+
+let SharedAll = Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", {});
+
+let SysAll = {};
+if (SharedAll.Constants.Win) {
+  Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll);
+} else if (SharedAll.Constants.libc) {
+  Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll);
+} else {
+  throw new Error("I am neither under Windows nor under a Posix system");
+}
+let {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+/**
+ * The native service holding the implementation of the functions.
+ */
+XPCOMUtils.defineLazyServiceGetter(this,
+  "Internals",
+  "@mozilla.org/toolkit/osfile/native-internals;1",
+  "nsINativeOSFileInternalsService");
+
+/**
+ * Native implementation of OS.File.read
+ *
+ * This implementation does not handle option |compression|.
+ */
+this.read = function(path, options = {}) {
+  // Sanity check on types of options
+  if ("encoding" in options && typeof options.encoding != "string") {
+    return Promise.reject(new TypeError("Invalid type for option encoding"));
+  }
+  if ("compression" in options && typeof options.compression != "string") {
+    return Promise.reject(new TypeError("Invalid type for option compression"));
+  }
+  if ("bytes" in options && typeof options.bytes != "number") {
+    return Promise.reject(new TypeError("Invalid type for option bytes"));
+  }
+
+  let deferred = Promise.defer();
+  Internals.read(path,
+    options,
+    function onSuccess(success) {
+      success.QueryInterface(Ci.nsINativeOSFileResult);
+      if ("outExecutionDuration" in options) {
+        options.outExecutionDuration =
+          success.executionDurationMS +
+          (options.outExecutionDuration || 0);
+      }
+      deferred.resolve(success.result);
+    },
+    function onError(operation, oserror) {
+      deferred.reject(new SysAll.Error(operation, oserror, path));
+    }
+  );
+  return deferred.promise;
+};
--- a/toolkit/components/osfile/modules/osfile_shared_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm
@@ -326,24 +326,45 @@ AbstractFile.normalizeOpenMode = functio
  * @return {Uint8Array} A buffer holding the bytes
  * and the number of bytes read from the file.
  */
 AbstractFile.read = function read(path, bytes, options = {}) {
   if (bytes && typeof bytes == "object") {
     options = bytes;
     bytes = options.bytes || null;
   }
+  if ("encoding" in options && typeof options.encoding != "string") {
+    throw new TypeError("Invalid type for option encoding");
+  }
+  if ("compression" in options && typeof options.compression != "string") {
+    throw new TypeError("Invalid type for option compression: " + options.compression);
+  }
+  if ("bytes" in options && typeof options.bytes != "number") {
+    throw new TypeError("Invalid type for option bytes");
+  }
   let file = exports.OS.File.open(path);
   try {
     let buffer = file.read(bytes, options);
-    if ("compression" in options && options.compression == "lz4") {
-      return Lz4.decompressFileContent(buffer, options);
-    } else {
+    if ("compression" in options) {
+      if (options.compression == "lz4") {
+        buffer = Lz4.decompressFileContent(buffer, options);
+      } else {
+        throw OS.File.Error.invalidArgument("Compression");
+      }
+    }
+    if (!("encoding" in options)) {
       return buffer;
     }
+    let decoder;
+    try {
+      decoder = new TextDecoder(options.encoding);
+    } catch (ex if ex instanceof TypeError) {
+      throw OS.File.Error.invalidArgument("Decode");
+    }
+    return decoder.decode(buffer);
   } finally {
     file.close();
   }
 };
 
 /**
  * Write a file, atomically.
  *
--- a/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
@@ -134,16 +134,25 @@ Object.defineProperty(OSError.prototype,
  * |true| if the error was raised because permission is denied to
  * access a file or directory, |false| otherwise.
  */
 Object.defineProperty(OSError.prototype, "becauseAccessDenied", {
   get: function becauseAccessDenied() {
     return this.unixErrno == Const.EACCES;
   }
 });
+/**
+ * |true| if the error was raised because some invalid argument was passed,
+ * |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseInvalidArgument", {
+  get: function becauseInvalidArgument() {
+    return this.unixErrno == Const.EINVAL;
+  }
+});
 
 /**
  * Serialize an instance of OSError to something that can be
  * transmitted across threads (not necessarily a string).
  */
 OSError.toMsg = function toMsg(error) {
   return {
     operation: error.operation,
@@ -326,16 +335,20 @@ OSError.closed = function closed(operati
 OSError.exists = function exists(operation, path) {
   return new OSError(operation, Const.EEXIST, path);
 };
 
 OSError.noSuchFile = function noSuchFile(operation, path) {
   return new OSError(operation, Const.ENOENT, path);
 };
 
+OSError.invalidArgument = function invalidArgument(operation) {
+  return new OSError(operation, Const.EINVAL);
+};
+
 let EXPORTED_SYMBOLS = [
   "declareFFI",
   "libc",
   "Error",
   "AbstractInfo",
   "AbstractEntry",
   "Type",
   "POS_START",
--- a/toolkit/components/osfile/modules/osfile_win_allthreads.jsm
+++ b/toolkit/components/osfile/modules/osfile_win_allthreads.jsm
@@ -156,16 +156,25 @@ Object.defineProperty(OSError.prototype,
  * |true| if the error was raised because permission is denied to
  * access a file or directory, |false| otherwise.
  */
 Object.defineProperty(OSError.prototype, "becauseAccessDenied", {
   get: function becauseAccessDenied() {
     return this.winLastError == Const.ERROR_ACCESS_DENIED;
   }
 });
+/**
+ * |true| if the error was raised because some invalid argument was passed,
+ * |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseInvalidArgument", {
+  get: function becauseInvalidArgument() {
+    return this.winLastError == Const.ERROR_NOT_SUPPORTED;
+  }
+});
 
 /**
  * Serialize an instance of OSError to something that can be
  * transmitted across threads (not necessarily a string).
  */
 OSError.toMsg = function toMsg(error) {
   return {
     operation: error.operation,
@@ -363,16 +372,20 @@ OSError.closed = function closed(operati
 OSError.exists = function exists(operation, path) {
   return new OSError(operation, Const.ERROR_FILE_EXISTS, path);
 };
 
 OSError.noSuchFile = function noSuchFile(operation, path) {
   return new OSError(operation, Const.ERROR_FILE_NOT_FOUND, path);
 };
 
+OSError.invalidArgument = function invalidArgument(operation) {
+  return new OSError(operation, Const.ERROR_NOT_SUPPORTED);
+};
+
 let EXPORTED_SYMBOLS = [
   "declareFFI",
   "libc",
   "Error",
   "AbstractInfo",
   "AbstractEntry",
   "Type",
   "POS_START",