Bug 795687 - Move all of OS.File to typed arrays instead of array buffers. r=froydnj
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Tue, 02 Oct 2012 20:14:39 -0400
changeset 109070 531b6090f45e0788a2ed2434e53aa9bb71234c82
parent 109069 3afaf5133beca682a9e9700f150776ddbd4aeff0
child 109071 e474cc38930bc3165d75bdfd6bfaf690dfcecfbe
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersfroydnj
bugs795687
milestone18.0a1
Bug 795687 - Move all of OS.File to typed arrays instead of array buffers. r=froydnj
toolkit/components/osfile/osfile_async_front.jsm
toolkit/components/osfile/osfile_shared_allthreads.jsm
toolkit/components/osfile/osfile_shared_front.jsm
toolkit/components/osfile/osfile_unix_front.jsm
toolkit/components/osfile/osfile_win_front.jsm
toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
toolkit/components/osfile/tests/mochi/worker_test_osfile_comms.js
toolkit/components/osfile/tests/mochi/worker_test_osfile_front.js
--- a/toolkit/components/osfile/osfile_async_front.jsm
+++ b/toolkit/components/osfile/osfile_async_front.jsm
@@ -19,16 +19,17 @@
  * @rejects {B} reason
  */
 
 let EXPORTED_SYMBOLS = ["OS"];
 
 Components.utils.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
 
 let LOG = OS.Shared.LOG.bind(OS.Shared, "Controller");
+let isTypedArray = OS.Shared.isTypedArray;
 
 // A simple flag used to control debugging messages.
 // FIXME: Once this library has been battle-tested, this flag will
 // either be removed or replaced with a preference.
 const DEBUG = true;
 
 // The constructor for file errors.
 let OSError;
@@ -301,110 +302,118 @@ File.prototype = {
     return Scheduler.post("File_prototype_stat", [this._fdmsg], this).then(
       File.Info.fromMsg
     );
   },
 
   /**
    * Read a number of bytes from the file and into a buffer.
    *
-   * @param {ArrayBuffer|C void*} buffer This buffer will be modified
-   * by another thread. Using this buffer before the |read| operation
-   * has completed is a BAD IDEA.
+   * @param {Typed array | C pointer} buffer This buffer will be
+   * modified by another thread. Using this buffer before the |read|
+   * operation has completed is a BAD IDEA.
    * @param {JSON} options
    *
    * @return {promise}
    * @resolves {number} The number of bytes effectively read.
    * @rejects {OS.File.Error}
    */
   readTo: function readTo(buffer, options) {
-    // If |buffer| is an ArrayBuffer and there is no |bytes| options,
-    // we need to extract the |byteLength| now, as it will be lost
-    // by communication
-    if ("byteLength" in buffer && (!options || !"bytes" in options)) {
+    // If |buffer| is a typed array and there is no |bytes| options, we
+    // need to extract the |byteLength| now, as it will be lost by
+    // communication
+    if (isTypedArray(buffer) && (!options || !"bytes" in options)) {
       options = clone(options || noOptions);
       options.bytes = buffer.byteLength;
     }
-    // Note: Classic semantics for ArrayBuffer communication would imply
-    // that posting the ArrayBuffer removes ownership from the sender
-    // thread. Here, we use Type.voidptr_t.toMsg to ensure that these
-    // semantics do not apply.
+    // Note: Type.void_t.out_ptr.toMsg ensures that
+    // - the buffer is effectively shared (not neutered) between both
+    //   threads;
+    // - we take care of any |byteOffset|.
     return Scheduler.post("File_prototype_readTo",
       [this._fdmsg,
-      Type.void_t.out_ptr.toMsg(buffer),
-      options],
-      buffer/*Ensure that |buffer| is not gc-ed*/);
+       Type.void_t.out_ptr.toMsg(buffer),
+       options],
+       buffer/*Ensure that |buffer| is not gc-ed*/);
   },
   /**
-   * Write a number of bytes from the file and into a buffer.
+   * Write bytes from a buffer to this file.
+   *
+   * Note that, by default, this function may perform several I/O
+   * operations to ensure that the buffer is fully written.
    *
-   * @param {ArrayBuffer|C void*} buffer This buffer will be accessed
-   * by another thread. Using this buffer before the |write| operation
-   * has completed is a BAD IDEA.
+   * @param {Typed array | C pointer} buffer The buffer in which the
+   * the bytes are stored. The buffer must be large enough to
+   * accomodate |bytes| bytes. Using the buffer before the operation
+   * is complete is a BAD IDEA.
+   * @param {*=} options Optionally, an object that may contain the
+   * following fields:
+   * - {number} bytes The number of |bytes| to write from the buffer. If
+   * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
+   * if |buffer| is a C pointer.
    *
-   * @return {promise}
-   * @resolves {number} The number of bytes effectively written.
-   * @rejects {OS.File.Error}
+   * @return {number} The number of bytes actually written.
    */
   write: function write(buffer, options) {
-    // If |buffer| is an ArrayBuffer and there is no |bytes| options,
+    // If |buffer| is a typed array and there is no |bytes| options,
     // we need to extract the |byteLength| now, as it will be lost
     // by communication
-    if ("byteLength" in buffer && (!options || !"bytes" in options)) {
+    if (isTypedArray(buffer) && (!options || !"bytes" in options)) {
       options = clone(options || noOptions);
       options.bytes = buffer.byteLength;
     }
-    // Note: Classic semantics for ArrayBuffer communication would imply
-    // that posting the ArrayBuffer removes ownership from the sender
-    // thread. Here, we use Type.voidptr_t.toMsg to ensure that these
-    // semantics do not apply.
+    // Note: Type.void_t.out_ptr.toMsg ensures that
+    // - the buffer is effectively shared (not neutered) between both
+    //   threads;
+    // - we take care of any |byteOffset|.
     return Scheduler.post("File_prototype_write",
       [this._fdmsg,
-      Type.void_t.in_ptr.toMsg(buffer),
-      options],
-      buffer/*Ensure that |buffer| is not gc-ed*/);
+       Type.void_t.in_ptr.toMsg(buffer),
+       options],
+       buffer/*Ensure that |buffer| is not gc-ed*/);
   },
 
   /**
    * Read bytes from this file to a new buffer.
    *
    * @param {number=} bytes If unspecified, read all the remaining bytes from
    * this file. If specified, read |bytes| bytes, or less if the file does not
    * contain that many bytes.
    * @return {promise}
-   * @resolves {buffer: ArrayBuffer, bytes: bytes} A buffer containing the
-   * bytes read and the number of bytes read. Note that |buffer| may be
-   * larger than the number of bytes actually read.
+   * @resolves {Uint8Array} An array containing the bytes read.
    */
   read: function read(nbytes) {
-    // FIXME: Once bug 720949 has landed, we should be able to simplify
-    // considerably the implementation of |readAll|
+    // FIXME: Once bug 720949 has landed, we might be able to simplify
+    // the implementation of |readAll|
     let self = this;
     let promise;
     if (nbytes != null) {
       promise = Promise.resolve(nbytes);
     } else {
       promise = this.stat();
       promise = promise.then(function withStat(stat) {
         return stat.size;
       });
     }
-    let buffer;
+    let array;
+    let size;
     promise = promise.then(
-      function withSize(size) {
-        buffer = new ArrayBuffer(size);
-        return self.readTo(buffer);
+      function withSize(aSize) {
+        size = aSize;
+        array = new Uint8Array(size);
+        return self.readTo(array);
       }
     );
     promise = promise.then(
       function afterReadTo(bytes) {
-        return {
-          bytes: bytes,
-          buffer: buffer
-        };
+        if (bytes == size) {
+          return array;
+        } else {
+          return array.subarray(0, bytes);
+        }
       }
     );
     return promise;
   },
 
   /**
    * Return the current position in the file, as bytes.
    *
@@ -596,18 +605,18 @@ File.makeDir = function makeDir(path, op
 
 /**
  * Return the contents of a file
  *
  * @param {string} path The path to the file.
  * @param {number=} bytes Optionally, an upper bound to the number of bytes
  * to read.
  *
- * @resolves {{buffer: ArrayBuffer, bytes: number}} A buffer holding the bytes
- * and the number of bytes read from the file.
+ * @resolves {Uint8Array} A buffer holding the bytes
+ * read from the file.
  */
 File.read = function read(path, bytes) {
   return Scheduler.post("read",
     [Type.path.toMsg(path), bytes], path);
 };
 
 /**
  * Write a file, atomically.
@@ -615,40 +624,44 @@ File.read = function read(path, bytes) {
  * By opposition to a regular |write|, this operation ensures that,
  * until the contents are fully written, the destination file is
  * not modified.
  *
  * Important note: In the current implementation, option |tmpPath|
  * is required. This requirement should disappear as part of bug 793660.
  *
  * @param {string} path The path of the file to modify.
- * @param {ArrayByffer} buffer A buffer containing the bytes to write.
- * @param {number} bytes The number of bytes to write.
+ * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
  * @param {*=} options Optionally, an object determining the behavior
  * of this function. This object may contain the following fields:
- * - {number} offset The offset in |buffer| at which to start extracting
- * data
+ * - {number} bytes The number of bytes to write. If unspecified,
+ * |buffer.byteLength|. Required if |buffer| is a C pointer.
  * - {string} tmpPath The path at which to write the temporary file.
  *
- * @return {number} The number of bytes actually written.
+ * @return {promise}
+ * @resolves {number} The number of bytes actually written.
  */
 File.writeAtomic = function writeAtomic(path, buffer, options) {
   // Copy |options| to avoid modifying the original object
   options = clone(options || noOptions);
   // As options.tmpPath is a path, we need to encode it as |Type.path| message
   if ("tmpPath" in options) {
     options.tmpPath = Type.path.toMsg(options.tmpPath);
   };
-  if ("byteLength" in buffer && (!("bytes" in options))) {
+  if (isTypedArray(buffer) && (!("bytes" in options))) {
     options.bytes = buffer.byteLength;
   };
+  // Note: Type.void_t.out_ptr.toMsg ensures that
+  // - the buffer is effectively shared (not neutered) between both
+  //   threads;
+  // - we take care of any |byteOffset|.
   return Scheduler.post("writeAtomic",
     [Type.path.toMsg(path),
-    Type.void_t.in_ptr.toMsg(buffer),
-    options], [options, buffer]);
+     Type.void_t.in_ptr.toMsg(buffer),
+     options], [options, buffer]);
 };
 
 /**
  * Information on a file, as returned by OS.File.stat or
  * OS.File.prototype.stat
  *
  * @constructor
  */
--- a/toolkit/components/osfile/osfile_shared_allthreads.jsm
+++ b/toolkit/components/osfile/osfile_shared_allthreads.jsm
@@ -231,16 +231,24 @@
         * This may not be defined, e.g. for |void_t|, array types
         * without length, etc.
         */
        get size() {
          return this.implementation.size;
        }
      };
 
+     /**
+      * Utility function used to determine whether an object is a typed array
+      */
+     let isTypedArray = function isTypedArray(obj) {
+       return typeof obj == "object"
+         && "byteOffset" in obj;
+     };
+     exports.OS.Shared.isTypedArray = isTypedArray;
 
      /**
       * A |Type| of pointers.
       *
       * @param {string} name The name of this type.
       * @param {CType} implementation The type of this pointer.
       * @param {Type} targetType The target type.
       */
@@ -259,30 +267,33 @@
      PtrType.prototype = Object.create(Type.prototype);
 
      /**
       * Convert a value to a pointer.
       *
       * Protocol:
       * - |null| returns |null|
       * - a string returns |{string: value}|
-      * - an ArrayBuffer returns |{ptr: address_of_buffer}|
+      * - a typed array returns |{ptr: address_of_buffer}|
       * - a C array returns |{ptr: address_of_buffer}|
       * everything else raises an error
       */
      PtrType.prototype.toMsg = function ptr_toMsg(value) {
        if (value == null) {
          return null;
        }
        if (typeof value == "string") {
          return { string: value };
        }
        let normalized;
-       if ("byteLength" in value) { // ArrayBuffer
-         normalized = Types.uint8_t.in_ptr.implementation(value);
+       if (isTypedArray(value)) { // Typed array
+         normalized = Types.uint8_t.in_ptr.implementation(value.buffer);
+         if (value.byteOffset != 0) {
+           normalized = exports.OS.Shared.offsetBy(normalized, value.byteOffset);
+         }
        } else if ("addressOfElement" in value) { // C array
          normalized = value.addressOfElement(0);
        } else if ("isNull" in value) { // C pointer
          normalized = value;
        } else {
          throw new TypeError("Value " + value +
            " cannot be converted to a pointer");
        }
--- a/toolkit/components/osfile/osfile_shared_front.jsm
+++ b/toolkit/components/osfile/osfile_shared_front.jsm
@@ -13,16 +13,17 @@ if (typeof Components != "undefined") {
   throw new Error("osfile_shared_front.jsm cannot be used from the main thread");
 }
 (function(exports) {
 
 let LOG = exports.OS.Shared.LOG.bind(OS.Shared, "Shared front-end");
 
 const noOptions = {};
 
+
 /**
  * Code shared by implementations of File.
  *
  * @param {*} fd An OS-specific file handle.
  * @constructor
  */
 let AbstractFile = function AbstractFile(fd) {
   this._fd = fd;
@@ -41,58 +42,52 @@ AbstractFile.prototype = {
     throw OS.File.Error.closed();
   },
   /**
    * Read bytes from this file to a new buffer.
    *
    * @param {number=} bytes If unspecified, read all the remaining bytes from
    * this file. If specified, read |bytes| bytes, or less if the file does not
    * contain that many bytes.
-   * @return {buffer: ArrayBuffer, bytes: bytes} A buffer containing the
-   * bytes read and the number of bytes read. Note that |buffer| may be
-   * larger than the number of bytes actually read.
+   * @return {Uint8Array} An array containing the bytes read.
    */
   read: function read(bytes) {
     if (bytes == null) {
       bytes = this.stat().size;
     }
-    let buffer = new ArrayBuffer(bytes);
+    let buffer = new Uint8Array(bytes);
     let size = this.readTo(buffer, bytes);
-    return {
-      buffer: buffer,
-      bytes: size
-    };
+    if (size == bytes) {
+      return buffer;
+    } else {
+      return buffer.subarray(0, size);
+    }
   },
 
   /**
    * Read bytes from this file to an existing buffer.
    *
    * Note that, by default, this function may perform several I/O
    * operations to ensure that the buffer is as full as possible.
    *
-   * @param {ArrayBuffer | C pointer} buffer The buffer in which to
-   * store the bytes. If options.offset is not given, bytes are stored
-   * from the start of the array. The buffer must be large enough to
+   * @param {Typed Array | C pointer} buffer The buffer in which to
+   * store the bytes. The buffer must be large enough to
    * accomodate |bytes| bytes.
    * @param {*=} options Optionally, an object that may contain the
    * following fields:
-   * - {number} offset The offset in |buffer| at which to start placing
-   * data
    * - {number} bytes The number of |bytes| to write from the buffer. If
    * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
    * if |buffer| is a C pointer.
    *
    * @return {number} The number of bytes actually read, which may be
    * less than |bytes| if the file did not contain that many bytes left.
    */
   readTo: function readTo(buffer, options) {
     options = options || noOptions;
-
-    let {ptr, bytes} = AbstractFile.normalizeToPointer(buffer, options.bytes,
-      options.offset);
+    let {ptr, bytes} = AbstractFile.normalizeToPointer(buffer, options.bytes);
     let pos = 0;
     while (pos < bytes) {
       let chunkSize = this._read(ptr, bytes - pos, options);
       if (chunkSize == 0) {
         break;
       }
       pos += chunkSize;
       ptr = exports.OS.Shared.offsetBy(ptr, chunkSize);
@@ -102,95 +97,83 @@ AbstractFile.prototype = {
   },
 
   /**
    * Write bytes from a buffer to this file.
    *
    * Note that, by default, this function may perform several I/O
    * operations to ensure that the buffer is fully written.
    *
-   * @param {ArrayBuffer | C pointer} buffer The buffer in which the
-   * the bytes are stored. If options.offset is not given, bytes are stored
-   * from the start of the array. The buffer must be large enough to
+   * @param {Typed array | C pointer} buffer The buffer in which the
+   * the bytes are stored. The buffer must be large enough to
    * accomodate |bytes| bytes.
    * @param {*=} options Optionally, an object that may contain the
    * following fields:
-   * - {number} offset The offset in |buffer| at which to start extracting
-   * data
    * - {number} bytes The number of |bytes| to write from the buffer. If
    * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
    * if |buffer| is a C pointer.
    *
    * @return {number} The number of bytes actually written.
    */
   write: function write(buffer, options) {
     options = options || noOptions;
 
-    let {ptr, bytes} = AbstractFile.normalizeToPointer(buffer, options.bytes,
-      options.offset);
+    let {ptr, bytes} = AbstractFile.normalizeToPointer(buffer, options.bytes);
 
     let pos = 0;
     while (pos < bytes) {
       let chunkSize = this._write(ptr, bytes - pos, options);
       pos += chunkSize;
       ptr = exports.OS.Shared.offsetBy(ptr, chunkSize);
     }
     return pos;
   }
 };
 
 /**
- * Utility function used to normalize a ArrayBuffer or C pointer into a uint8_t
- * C pointer.
+ * Utility function used to normalize a Typed Array or C
+ * pointer into a uint8_t C pointer.
  *
  * Future versions might extend this to other data structures.
  *
- * @param {ArrayBuffer|C pointer} candidate Either an ArrayBuffer or a
- * non-null C pointer.
+ * @param {Typed array | C pointer} candidate The buffer. If
+ * a C pointer, it must be non-null.
  * @param {number} bytes The number of bytes that |candidate| should contain.
  * Used for sanity checking if the size of |candidate| can be determined.
- * @param {number=} offset Optionally, a number of bytes by which to shift
- * |candidate|.
  *
  * @return {ptr:{C pointer}, bytes:number} A C pointer of type uint8_t,
- * corresponding to the start of |candidate| + |offset| bytes.
+ * corresponding to the start of |candidate|.
  */
-AbstractFile.normalizeToPointer = function normalizeToPointer(candidate, bytes, offset) {
+AbstractFile.normalizeToPointer = function normalizeToPointer(candidate, bytes) {
   if (!candidate) {
-    throw new TypeError("Expecting a C pointer or an ArrayBuffer");
-  }
-  if (offset == null) {
-    offset = 0;
+    throw new TypeError("Expecting  a Typed Array or a C pointer");
   }
   let ptr;
   if ("isNull" in candidate) {
     if (candidate.isNull()) {
       throw new TypeError("Expecting a non-null pointer");
     }
     ptr = exports.OS.Shared.Type.uint8_t.out_ptr.cast(candidate);
     if (bytes == null) {
       throw new TypeError("C pointer missing bytes indication.");
     }
-  } else if ("byteLength" in candidate) {
-    ptr = exports.OS.Shared.Type.uint8_t.out_ptr.implementation(candidate);
+  } else if (exports.OS.Shared.isTypedArray(candidate)) {
+    // Typed Array
+    ptr = exports.OS.Shared.Type.uint8_t.out_ptr.implementation(candidate.buffer);
     if (bytes == null) {
-      bytes = candidate.byteLength - offset;
-    }
-    if (candidate.byteLength < offset + bytes) {
+      bytes = candidate.byteLength;
+    } else if (candidate.byteLength < bytes) {
       throw new TypeError("Buffer is too short. I need at least " +
-                         (offset + bytes) +
+                         bytes +
                          " bytes but I have only " +
-                         buffer.byteLength +
+                         candidate.byteLength +
                           "bytes");
     }
   } else {
-    throw new TypeError("Expecting a C pointer or an ArrayBuffer");
-  }
-  if (offset != 0) {
-    ptr = exports.OS.Shared.offsetBy(ptr, offset);
+    throw new TypeError("Expecting  a Typed Array or a C pointer");
   }
   return {ptr: ptr, bytes: bytes};
 };
 
 /**
  * Code shared by iterators.
  */
 AbstractFile.AbstractIterator = function AbstractIterator() {
@@ -307,17 +290,17 @@ AbstractFile.normalizeOpenMode = functio
 
 /**
  * Return the contents of a file.
  *
  * @param {string} path The path to the file.
  * @param {number=} bytes Optionally, an upper bound to the number of bytes
  * to read.
  *
- * @return {{buffer: ArrayBuffer, bytes: number}} A buffer holding the bytes
+ * @return {Uint8Array} A buffer holding the bytes
  * and the number of bytes read from the file.
  */
 AbstractFile.read = function read(path, bytes) {
   let file = exports.OS.File.open(path);
   try {
     return file.read(bytes);
   } finally {
     file.close();
@@ -330,33 +313,30 @@ AbstractFile.read = function read(path, 
  * By opposition to a regular |write|, this operation ensures that,
  * until the contents are fully written, the destination file is
  * not modified.
  *
  * Important note: In the current implementation, option |tmpPath|
  * is required. This requirement should disappear as part of bug 793660.
  *
  * @param {string} path The path of the file to modify.
- * @param {ArrayByffer} buffer A buffer containing the bytes to write.
+ * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
  * @param {*=} options Optionally, an object determining the behavior
  * of this function. This object may contain the following fields:
- * - {number} offset The offset in |buffer| at which to start extracting
- * data. If unspecified, 0.
- * - {number} bytes The number of bytes to write. If unspecified, all
- * the bytes in |buffer|.
+ * - {number} bytes The number of bytes to write. If unspecified,
+ * |buffer.byteLength|. Required if |buffer| is a C pointer.
  * - {string} tmpPath The path at which to write the temporary file.
  *
  * @return {number} The number of bytes actually written.
  */
 AbstractFile.writeAtomic =
      function writeAtomic(path, buffer, options) {
   options = options || noOptions;
 
   let tmpPath = options.tmpPath;
-
   if (!tmpPath) {
     throw new TypeError("Expected option tmpPath");
   }
   let tmpFile = OS.File.open(tmpPath, {write: true, truncate: true});
   let bytesWritten;
   try {
     bytesWritten = tmpFile.write(buffer, options);
     tmpFile.flush();
--- a/toolkit/components/osfile/osfile_unix_front.jsm
+++ b/toolkit/components/osfile/osfile_unix_front.jsm
@@ -75,17 +75,17 @@
          throw this._closeResult;
        }
        return;
      };
 
      /**
       * Read some bytes from a file.
       *
-      * @param {ArrayBuffer} buffer A buffer for holding the data
+      * @param {C pointer} buffer A buffer for holding the data
       * once it is read.
       * @param {number} nbytes The number of bytes to read. It must not
       * exceed the size of |buffer| in bytes but it may exceed the number
       * of bytes unread in the file.
       * @param {*=} options Additional options for reading. Ignored in
       * this implementation.
       *
       * @return {number} The number of bytes effectively read. If zero,
@@ -96,17 +96,17 @@
        return throw_on_negative("read",
          UnixFile.read(this.fd, buffer, nbytes)
        );
      };
 
      /**
       * Write some bytes to a file.
       *
-      * @param {ArrayBuffer} buffer A buffer holding the data that must be
+      * @param {C pointer} buffer A buffer holding the data that must be
       * written.
       * @param {number} nbytes The number of bytes to write. It must not
       * exceed the size of |buffer| in bytes.
       * @param {*=} options Additional options for writing. Ignored in
       * this implementation.
       *
       * @return {number} The number of bytes effectively written.
       * @throws {OS.File.Error} In case of I/O error.
--- a/toolkit/components/osfile/osfile_win_front.jsm
+++ b/toolkit/components/osfile/osfile_win_front.jsm
@@ -94,17 +94,17 @@
          throw this._closeResult;
        }
        return;
      };
 
      /**
       * Read some bytes from a file.
       *
-      * @param {ArrayBuffer} buffer A buffer for holding the data
+      * @param {C pointer} buffer A buffer for holding the data
       * once it is read.
       * @param {number} nbytes The number of bytes to read. It must not
       * exceed the size of |buffer| in bytes but it may exceed the number
       * of bytes unread in the file.
       * @param {*=} options Additional options for reading. Ignored in
       * this implementation.
       *
       * @return {number} The number of bytes effectively read. If zero,
@@ -117,17 +117,17 @@
          WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null)
        );
        return gBytesRead.value;
      };
 
      /**
       * Write some bytes to a file.
       *
-      * @param {ArrayBuffer} buffer A buffer holding the data that must be
+      * @param {C pointer} buffer A buffer holding the data that must be
       * written.
       * @param {number} nbytes The number of bytes to write. It must not
       * exceed the size of |buffer| in bytes.
       * @param {*=} options Additional options for writing. Ignored in
       * this implementation.
       *
       * @return {number} The number of bytes effectively written.
       * @throws {OS.File.Error} In case of I/O error.
--- a/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
+++ b/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
@@ -35,16 +35,27 @@ let always = function always(promise, fu
   let onreject = function(rejection) {
     fun();
     p2.reject(rejection);
   };
   promise.then(onsuccess, onreject);
   return p2.promise;
 };
 
+let ensureSuccess = function ensureSuccess(promise, test) {
+  let p2 = Promise.defer();
+  promise.then(function onSuccess(x) {
+    p2.resolve(x);
+  }, function onFailure(err) {
+    test.fail("Uncaught error " + err + "\n" + err.stack);
+    p2.reject(err);
+  });
+
+  return p2.promise;
+};
 
 let maketest = function(prefix, test) {
   let utils = {
     ok: function ok(t, m) {
       myok(t, prefix + ": " + m);
     },
     is: function is(l, r, m) {
       myis(l, r, prefix + ": " + m);
@@ -333,17 +344,17 @@ let test_stat = maketest("stat",
 });
 
 /**
  * Test OS.File.prototype.{read, readTo, write}
  */
 let test_read_write = maketest("read_write",
   function read_write(test) {
     let promise;
-    let buffer;
+    let array;
     let fileSource, fileDest;
     let pathSource;
     let pathDest = OS.Path.join(OS.Constants.Path.tmpDir,
        "osfile async test.tmp");
 
     // Test readTo/write
 
     promise = OS.File.getCurrentDirectory();
@@ -372,27 +383,27 @@ let test_read_write = maketest("read_wri
       }
     );
 
     let size;
     promise = promise.then(
       function input_stat_worked(stat) {
         test.info("Input stat worked");
         size = stat.size;
-        buffer = new ArrayBuffer(size);
+        array = new Uint8Array(size);
         test.info("Now calling readTo");
-        return fileSource.readTo(buffer);
+        return fileSource.readTo(array);
       }
     );
 
     promise = promise.then(
       function read_worked(length) {
         test.info("ReadTo worked");
         test.is(length, size, "ReadTo got all bytes");
-        return fileDest.write(buffer);
+        return fileDest.write(array);
       }
     );
 
     promise = promise.then(
       function write_worked(length) {
         test.info("Write worked");
         test.is(length, size, "Write wrote all bytes");
         return;
@@ -408,21 +419,19 @@ let test_read_write = maketest("read_wri
     promise = promise.then(
       function setposition_worked() {
         return fileSource.read();
       }
     );
     promise = promise.then(
       function readall_worked(result) {
         test.info("ReadAll worked");
-        test.is(result.bytes, size, "ReadAll read all bytes");
-        let result_view = new Uint8Array(result.buffer);
-        let buffer_view = new Uint8Array(buffer);
-        test.is(Array.prototype.join.call(result_view),
-                Array.prototype.join.call(buffer_view),
+        test.is(result.length, size, "ReadAll read all bytes");
+        test.is(Array.prototype.join.call(result),
+                Array.prototype.join.call(array),
                 "ReadAll result is correct");
       }
     );
 
 
     // Close stuff
 
     promise = always(promise,
@@ -467,59 +476,93 @@ let test_read_write_all = maketest(
     let promise = OS.File.getCurrentDirectory();
     promise = promise.then(
       function obtained_current_directory(path) {
         test.ok(path, "Obtained current directory");
         pathSource = OS.Path.join(path, EXISTING_FILE);
         return OS.File.read(pathSource);
       }
     );
+    promise = ensureSuccess(promise, test);
 
-    let buffer;
-    let bytes;
+    let contents;
     promise = promise.then(
-      function read_complete(contents) {
-        test.ok(contents, "Obtained contents");
-        buffer = contents.buffer;
-        bytes = contents.bytes;
+      function read_complete(result) {
+        test.ok(result, "Obtained contents");
+        contents = result;
         options = {tmpPath: tmpPath};
         optionsBackup = {tmpPath: tmpPath};
-        return OS.File.writeAtomic(pathDest, buffer, options);
+        return OS.File.writeAtomic(pathDest, contents, options);
       }
     );
+    promise = ensureSuccess(promise, test);
 
 // Check that options are not altered
 
     promise = promise.then(
       function atomicWrite_complete(bytesWritten) {
-        test.is(bytes, bytesWritten, "Wrote the correct number of bytes");
+        test.is(contents.byteLength, bytesWritten, "Wrote the correct number of bytes");
         test.is(Object.keys(options).length, Object.keys(optionsBackup).length,
                 "The number of options was not changed");
         for (let k in options) {
           test.is(options[k], optionsBackup[k], "Option was not changed");
         }
         return reference_compare_files(pathSource, pathDest, test);
       }
     );
+    promise = ensureSuccess(promise, test);
 
 // Check that temporary file was removed
 
     promise = promise.then(
       function compare_complete() {
         test.info("Compare complete");
         test.ok(!(new FileUtils.File(tmpPath).exists()), "Temporary file was removed");
       }
     );
+    promise = ensureSuccess(promise, test);
+
+// Now write a subset
+
+    let START = 10;
+    let LENGTH = 100;
+    promise = promise.then(
+      function() {
+        let view = new Uint8Array(contents.buffer, START, LENGTH);
+        return OS.File.writeAtomic(pathDest, view, {tmpPath: tmpPath});
+      }
+    );
+
+    promise = promise.then(
+      function partial_write_complete(bytesWritten) {
+        test.is(bytesWritten, LENGTH, "Partial write wrote the correct number of bytes");
+        return OS.File.read(pathDest);
+      }
+    );
+
+    promise = promise.then(
+      function read_partial_write_complete(array2) {
+        let view1 = new Uint8Array(contents.buffer, START, LENGTH);
+        test.is(view1.length, array2.length, "Re-read partial write with the correct number of bytes");
+        for (let i = 0; i < LENGTH; ++i) {
+          if (view1[i] != array2[i]) {
+            test.is(view1[i], array2[i], "Offset " + i + " is correct");
+          }
+          test.ok(true, "Compared re-read of partial write");
+        }
+      }
+    );
+    promise = ensureSuccess(promise, test);
 
 // Check that writeAtomic fails if there is no tmpPath
 // FIXME: Remove this as part of bug 793660
 
     promise = promise.then(
       function check_without_tmpPath() {
-        return OS.File.writeAtomic(pathDest, buffer, {});
+        return OS.File.writeAtomic(pathDest, contents, {});
       },
       function onFailure() {
         test.info("Resetting failure");
       }
     );
 
     promise = promise.then(
       function onSuccess() {
@@ -542,58 +585,58 @@ let test_position = maketest(
 
     promise = promise.then(
       function input_file_opened(aFile) {
         file = aFile;
         return file.stat();
       }
     );
 
-    let buf;
+    let view;
     promise = promise.then(
       function obtained_stat(stat) {
         test.info("Obtained file length");
-        buf = new ArrayBuffer(stat.size);
-        return file.readTo(buf);
+        view = new Uint8Array(stat.size);
+        return file.readTo(view);
       });
 
     promise = promise.then(
       function input_file_read() {
         test.info("First batch of content read");
         return file.getPosition();
       }
     );
 
     let pos;
     let CHUNK_SIZE = 178;// An arbitrary number of bytes to read from the file
 
     promise = promise.then(
       function obtained_position(aPos) {
         test.info("Obtained position");
-        test.is(aPos, buf.byteLength, "getPosition returned the end of the file");
+        test.is(aPos, view.byteLength, "getPosition returned the end of the file");
         return file.setPosition(-CHUNK_SIZE, OS.File.POS_END);
       }
     );
 
-    let buf2;
+    let view2;
     promise = promise.then(
       function changed_position(aPos) {
         test.info("Changed position");
-        test.is(aPos, buf.byteLength - CHUNK_SIZE, "setPosition returned the correct position");
-        buf2 = new ArrayBuffer(CHUNK_SIZE);
-        return file.readTo(buf2);
+        test.is(aPos, view.byteLength - CHUNK_SIZE, "setPosition returned the correct position");
+        view2 = new Uint8Array(CHUNK_SIZE);
+        return file.readTo(view2);
       }
     );
 
     promise = promise.then(
       function input_file_reread() {
         test.info("Read the end of the file");
         for (let i = 0; i < CHUNK_SIZE; ++i) {
-          if (buf2[i] != buf[i + buf.byteLength - CHUNK_SIZE]) {
-            test.is(buf2[i], buf[i], "setPosition put us in the right position");
+          if (view2[i] != view[i + view.byteLength - CHUNK_SIZE]) {
+            test.is(view2[i], view[i], "setPosition put us in the right position");
           }
         }
       }
     );
 
     promise = always(promise,
       function () {
         if (file) {
--- a/toolkit/components/osfile/tests/mochi/worker_test_osfile_comms.js
+++ b/toolkit/components/osfile/tests/mochi/worker_test_osfile_comms.js
@@ -43,24 +43,23 @@ self.onmessage = function(msg) {
     { typename: "OS.Shared.Type.char.in_ptr",
       valuedescr: "String",
       value: "This is a test",
       type: OS.Shared.Type.char.in_ptr,
       check: function check_string(candidate, prefix) {
         is(candidate, "This is a test", prefix);
       }},
     { typename: "OS.Shared.Type.char.in_ptr",
-      valuedescr: "ArrayBuffer",
+      valuedescr: "Typed array",
       value: (function() {
-                let buf = new ArrayBuffer(15);
-                let view = new Uint8Array(buf);
+                let view = new Uint8Array(15);
                 for (let i = 0; i < 15; ++i) {
                   view[i] = i;
                 }
-                return buf;
+                return view;
               })(),
       type: OS.Shared.Type.char.in_ptr,
       check: function check_ArrayBuffer(candidate, prefix) {
         let cast = ctypes.cast(candidate, ctypes.uint8_t.ptr);
         for (let i = 0; i < 15; ++i) {
           is(cast.contents, i % 256, prefix + "Checking that the contents of the ArrayBuffer were preserved");
           cast = cast.increment();
         }
--- a/toolkit/components/osfile/tests/mochi/worker_test_osfile_front.js
+++ b/toolkit/components/osfile/tests/mochi/worker_test_osfile_front.js
@@ -205,22 +205,20 @@ function compare_files(test, sourcePath,
   try {
     if (prefix != undefined) {
       sourceResult = source.read(prefix);
       destResult = dest.read(prefix);
     } else {
       sourceResult = source.read();
       destResult = dest.read();
     }
-    is(sourceResult.bytes, destResult.bytes, test + ": Both files have the same size");
-    let sourceView = new Uint8Array(sourceResult.buffer);
-    let destView = new Uint8Array(destResult.buffer);
-    for (let i = 0; i < sourceResult.bytes; ++i) {
-      if (sourceView[i] != destView[i]) {
-        is(sourceView[i] != destView[i], test + ": Comparing char " + i);
+    is(sourceResult.length, destResult.length, test + ": Both files have the same size");
+    for (let i = 0; i < sourceResult.length; ++i) {
+      if (sourceResult[i] != destResult[i]) {
+        is(sourceResult[i] != destResult[i], test + ": Comparing char " + i);
         break;
       }
     }
   } finally {
     source.close();
     dest.close();
   }
   ok(true, test + ": Comparison complete");
@@ -233,17 +231,17 @@ function test_readall_writeall_file()
   ok(true, "Starting test_readall_writeall_file");
 
   // read, ArrayBuffer
 
   let source = OS.File.open(src_file_name);
   let dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
   let size = source.stat().size;
 
-  let buf = new ArrayBuffer(size);
+  let buf = new Uint8Array(size);
   let readResult = source.readTo(buf);
   is(readResult, size, "test_readall_writeall_file: read the right number of bytes");
 
   dest.write(buf);
 
   ok(true, "test_readall_writeall_file: copy complete (manual allocation)");
   source.close();
   dest.close();
@@ -279,77 +277,57 @@ function test_readall_writeall_file()
 
   source.close();
   dest.close();
 
   // readTo, ArrayBuffer + offset
   let OFFSET = 12;
   let LEFT = size - OFFSET;
   buf = new ArrayBuffer(size);
+  let offset_view = new Uint8Array(buf, OFFSET);
   source = OS.File.open(src_file_name);
   dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
 
-  readResult = source.readTo(buf, {offset: OFFSET});
+  readResult = source.readTo(offset_view);
   is(readResult, LEFT, "test_readall_writeall_file: read the right number of bytes (with offset)");
 
-  dest.write(buf, {offset: OFFSET});
-  is(dest.stat().size, LEFT, "test_readall_writeall_file: wrote the right number of bytes (with offset)");
-
-  ok(true, "test_readall_writeall_file: copy complete (with offset)");
-  source.close();
-  dest.close();
-
-  compare_files("test_readall_writeall_file (with offset)", src_file_name, tmp_file_name, LEFT);
-  OS.File.remove(tmp_file_name);
-
-  // readTo, C buffer + offset
-  buf = new ArrayBuffer(size);
-  ptr = OS.Shared.Type.voidptr_t.implementation(buf);
-
-  source = OS.File.open(src_file_name);
-  dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
-
-  readResult = source.readTo(ptr, {bytes: LEFT, offset: OFFSET});
-  is(readResult, LEFT, "test_readall_writeall_file: read the right number of bytes (with offset)");
-
-  dest.write(ptr, {bytes: LEFT, offset: OFFSET});
+  dest.write(offset_view);
   is(dest.stat().size, LEFT, "test_readall_writeall_file: wrote the right number of bytes (with offset)");
 
   ok(true, "test_readall_writeall_file: copy complete (with offset)");
   source.close();
   dest.close();
 
   compare_files("test_readall_writeall_file (with offset)", src_file_name, tmp_file_name, LEFT);
   OS.File.remove(tmp_file_name);
 
   // read
-  buf = new ArrayBuffer(size);
+  buf = new Uint8Array(size);
   source = OS.File.open(src_file_name);
   dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
 
   readResult = source.read();
-  is(readResult.bytes, size, "test_readall_writeall_file: read the right number of bytes (auto allocation)");
+  is(readResult.length, size, "test_readall_writeall_file: read the right number of bytes (auto allocation)");
 
-  dest.write(readResult.buffer, {bytes: readResult.bytes});
+  dest.write(readResult);
 
   ok(true, "test_readall_writeall_file: copy complete (auto allocation)");
   source.close();
   dest.close();
 
   compare_files("test_readall_writeall_file (auto allocation)", src_file_name, tmp_file_name);
   OS.File.remove(tmp_file_name);
 
   // File.readAll
   readResult = OS.File.read(src_file_name);
-  is(readResult.bytes, size, "test_readall_writeall_file: read the right number of bytes (OS.File.readAll)");
+  is(readResult.length, size, "test_readall_writeall_file: read the right number of bytes (OS.File.readAll)");
  
   // File.writeAtomic on top of nothing
-  OS.File.writeAtomic(tmp_file_name, readResult.buffer,
-    {bytes: readResult.bytes,
-     tmpPath: tmp_file_name + ".tmp"});
+  OS.File.writeAtomic(tmp_file_name, readResult,
+    {tmpPath: tmp_file_name + ".tmp"});
   try {
     let stat = OS.File.stat(tmp_file_name);
     ok(true, "readAll + writeAtomic created a file");
     is(stat.size, size, "readAll + writeAtomic created a file of the right size");
   } catch (x) {
     ok(false, "readAll + writeAtomic somehow failed");
     if(x.becauseNoSuchFile) {
       ok(false, "readAll + writeAtomic did not create file");
@@ -367,29 +345,28 @@ function test_readall_writeall_file()
 
 
   // File.writeAtomic on top of existing file
   // Remove content and set arbitrary size, to avoid potential false negatives
   dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
   dest.setPosition(1234);
   dest.close();
 
-  OS.File.writeAtomic(tmp_file_name, readResult.buffer,
-    {bytes: readResult.bytes,
-     tmpPath: tmp_file_name + ".tmp"});
+  OS.File.writeAtomic(tmp_file_name, readResult,
+    {tmpPath: tmp_file_name + ".tmp"});
   compare_files("test_readall_writeall_file (OS.File.readAll + writeAtomic 2)",
                 src_file_name, tmp_file_name);
 
   // Ensure that File.writeAtomic fails if no temporary file name is provided
   // (FIXME: Remove this test as part of bug 793660)
 
   exn = null;
   try {
     OS.File.writeAtomic(tmp_file_name, readResult.buffer,
-      {bytes: readResult.bytes});
+      {bytes: readResult.length});
   } catch (x) {
     exn = x;
   }
   ok(!!exn && exn instanceof TypeError, "wrietAtomic fails if tmpPath is not provided");
 }
 
 /**
  * Test that copying a file using |copy| works.
@@ -402,17 +379,17 @@ function test_copy_existing_file()
   OS.File.copy(src_file_name, tmp_file_name);
 
   ok(true, "test_copy_existing: Copy complete");
   compare_files("test_copy_existing", src_file_name, tmp_file_name);
 
   // Create a bogus file with arbitrary content, then attempt to overwrite
   // it with |copy|.
   let dest = OS.File.open(tmp_file_name, {trunc: true});
-  let buf = new ArrayBuffer(50);
+  let buf = new Uint8Array(50);
   dest.write(buf);
   dest.close();
 
   OS.File.copy(src_file_name, tmp_file_name);
 
   compare_files("test_copy_existing 2", src_file_name, tmp_file_name);
 
   // Attempt to overwrite with noOverwrite