Bug 771617 - Refactoring to better share code between implementations of OS.File. r=froydnj
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Sat, 25 Aug 2012 17:18:43 -0400
changeset 105486 719af14ab13364758f9126cace04883e9733a5fb
parent 105485 1db0e95acab859db02aedabd7c210426955693e4
child 105487 e555d4f92c1dd06e24abf8734141dfc02634b636
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersfroydnj
bugs771617
milestone17.0a1
Bug 771617 - Refactoring to better share code between implementations of OS.File. r=froydnj
toolkit/components/osfile/Makefile.in
toolkit/components/osfile/osfile_shared_allthreads.jsm
toolkit/components/osfile/osfile_shared_front.jsm
toolkit/components/osfile/osfile_unix_allthreads.jsm
toolkit/components/osfile/osfile_unix_back.jsm
toolkit/components/osfile/osfile_unix_front.jsm
toolkit/components/osfile/osfile_win_allthreads.jsm
toolkit/components/osfile/osfile_win_back.jsm
toolkit/components/osfile/osfile_win_front.jsm
--- a/toolkit/components/osfile/Makefile.in
+++ b/toolkit/components/osfile/Makefile.in
@@ -34,8 +34,9 @@ libs::
 	$(NSINSTALL) $(srcdir)/osfile_unix_allthreads.jsm $(FINAL_TARGET)/modules/osfile
 	$(NSINSTALL) $(srcdir)/osfile_unix_back.jsm $(FINAL_TARGET)/modules/osfile
 	$(NSINSTALL) $(srcdir)/ospath_unix_back.jsm $(FINAL_TARGET)/modules/osfile
 	$(NSINSTALL) $(srcdir)/osfile_unix_front.jsm $(FINAL_TARGET)/modules/osfile
 	$(NSINSTALL) $(srcdir)/osfile_win_allthreads.jsm $(FINAL_TARGET)/modules/osfile
 	$(NSINSTALL) $(srcdir)/ospath_win_back.jsm $(FINAL_TARGET)/modules/osfile
 	$(NSINSTALL) $(srcdir)/osfile_win_back.jsm $(FINAL_TARGET)/modules/osfile
 	$(NSINSTALL) $(srcdir)/osfile_win_front.jsm $(FINAL_TARGET)/modules/osfile
+	$(NSINSTALL) $(srcdir)/osfile_shared_front.jsm $(FINAL_TARGET)/modules/osfile
--- a/toolkit/components/osfile/osfile_shared_allthreads.jsm
+++ b/toolkit/components/osfile/osfile_shared_allthreads.jsm
@@ -900,79 +900,10 @@
 
      Strings.decodeAll = function decodeAll(encoding, source, bytes) {
        let decoded = _decodeAll(encoding, source, bytes);
        if (!decoded) {
          return null;
        }
        return Strings.importWString(decoded);
      };
-
-     /**
-      * Specific tools that don't really fit anywhere.
-      */
-     let _aux = {};
-     exports.OS.Shared._aux = _aux;
-
-     /**
-      * Utility function shared by implementations of |OS.File.open|:
-      * extract read/write/trunc/create/existing flags from a |mode|
-      * object.
-      *
-      * @param {*=} mode An object that may contain fields |read|,
-      * |write|, |truncate|, |create|, |existing|. These fields
-      * are interpreted only if true-ish.
-      * @return {{read:bool, write:bool, trunc:bool, create:bool,
-      * existing:bool}} an object recapitulating the options set
-      * by |mode|.
-      * @throws {TypeError} If |mode| contains other fields, or
-      * if it contains both |create| and |truncate|, or |create|
-      * and |existing|.
-      */
-     _aux.normalizeOpenMode = function normalizeOpenMode(mode) {
-       let result = {
-         read: false,
-         write: false,
-         trunc: false,
-         create: false,
-         existing: false
-       };
-       for (let key in mode) {
-         if (!mode[key]) continue; // Only interpret true-ish keys
-         switch (key) {
-         case "read":
-           result.read = true;
-           break;
-         case "write":
-           result.write = true;
-           break;
-         case "truncate": // fallthrough
-         case "trunc":
-           result.trunc = true;
-           result.write = true;
-           break;
-         case "create":
-           result.create = true;
-           result.write = true;
-           break;
-         case "existing": // fallthrough
-         case "exist":
-           result.existing = true;
-           break;
-         default:
-           throw new TypeError("Mode " + key + " not understood");
-         }
-       }
-       // Reject opposite modes
-       if (result.existing && result.create) {
-         throw new TypeError("Cannot specify both existing:true and create:true");
-       }
-       if (result.trunc && result.create) {
-         throw new TypeError("Cannot specify both trunc:true and create:true");
-       }
-       // Handle read/write
-       if (!result.write) {
-         result.read = true;
-       }
-       return result;
-     };
    })(this);
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/osfile_shared_front.jsm
@@ -0,0 +1,106 @@
+/* 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/. */
+
+/**
+ * Code shared by OS.File front-ends.
+ *
+ * This code is meant to be included by another library. It is also meant to
+ * be executed only on a worker thread.
+ */
+
+if (typeof Components != "undefined") {
+  throw new Error("osfile_shared_front.jsm cannot be used from the main thread");
+}
+(function(exports) {
+
+/**
+ * Code shared by implementations of File.
+ *
+ * @param {*} fd An OS-specific file handle.
+ * @constructor
+ */
+let AbstractFile = function AbstractFile(fd) {
+  this._fd = fd;
+};
+
+AbstractFile.prototype = {
+  /**
+   * Return the file handle.
+   *
+   * @throw OS.File.Error if the file has been closed.
+   */
+  get fd() {
+    if (this._fd) {
+      return this._fd;
+    }
+    throw OS.File.Error.closed();
+  }
+};
+
+
+/**
+ * Utility function shared by implementations of |OS.File.open|:
+ * extract read/write/trunc/create/existing flags from a |mode|
+ * object.
+ *
+ * @param {*=} mode An object that may contain fields |read|,
+ * |write|, |truncate|, |create|, |existing|. These fields
+ * are interpreted only if true-ish.
+ * @return {{read:bool, write:bool, trunc:bool, create:bool,
+ * existing:bool}} an object recapitulating the options set
+ * by |mode|.
+ * @throws {TypeError} If |mode| contains other fields, or
+ * if it contains both |create| and |truncate|, or |create|
+ * and |existing|.
+ */
+AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) {
+  let result = {
+    read: false,
+    write: false,
+    trunc: false,
+    create: false,
+    existing: false
+  };
+  for (let key in mode) {
+    if (!mode[key]) continue; // Only interpret true-ish keys
+    switch (key) {
+    case "read":
+      result.read = true;
+      break;
+    case "write":
+      result.write = true;
+      break;
+    case "truncate": // fallthrough
+    case "trunc":
+      result.trunc = true;
+      result.write = true;
+      break;
+    case "create":
+      result.create = true;
+      result.write = true;
+      break;
+    case "existing": // fallthrough
+    case "exist":
+      result.existing = true;
+      break;
+    default:
+      throw new TypeError("Mode " + key + " not understood");
+    }
+  }
+  // Reject opposite modes
+  if (result.existing && result.create) {
+    throw new TypeError("Cannot specify both existing:true and create:true");
+  }
+  if (result.trunc && result.create) {
+    throw new TypeError("Cannot specify both trunc:true and create:true");
+  }
+  // Handle read/write
+  if (!result.write) {
+    result.read = true;
+  }
+  return result;
+};
+
+   exports.OS.Shared.AbstractFile = AbstractFile;
+})(this);
\ No newline at end of file
--- a/toolkit/components/osfile/osfile_unix_allthreads.jsm
+++ b/toolkit/components/osfile/osfile_unix_allthreads.jsm
@@ -126,16 +126,25 @@ if (typeof Components != "undefined") {
    * |true| if the error was raised because a directory is not empty
    * does not exist, |false| otherwise.
    */
    Object.defineProperty(OSError.prototype, "becauseNotEmpty", {
      get: function becauseNotEmpty() {
        return this.unixErrno == OS.Constants.libc.ENOTEMPTY;
      }
    });
+  /**
+   * |true| if the error was raised because a file or directory
+   * is closed, |false| otherwise.
+   */
+  Object.defineProperty(OSError.prototype, "becauseClosed", {
+    get: function becauseClosed() {
+      return this.unixErrno == OS.Constants.libc.EBADF;
+    }
+  });
 
   /**
    * 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,
@@ -164,9 +173,14 @@ if (typeof Components != "undefined") {
 
    /**
     * Native paths
     *
     * Under Unix, expressed as C strings
     */
   Types.path = Types.cstring.withName("[in] path");
   Types.out_path = Types.out_cstring.withName("[out] path");
+
+  // Special constructors that need to be defined on all threads
+  OSError.closed = function closed(operation) {
+    return new OSError(operation, OS.Constants.libc.EBADF);
+  };
 })(this);
--- a/toolkit/components/osfile/osfile_unix_back.jsm
+++ b/toolkit/components/osfile/osfile_unix_back.jsm
@@ -173,17 +173,17 @@
          stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_SIZE,
                         "st_size", Types.size_t.implementation);
          Types.stat = stat.getType();
        }
 
        // Declare libc functions as functions of |OS.Unix.File|
 
        // Finalizer-related functions
-       let _close =
+       let _close = UnixFile._close =
          libc.declare("close", ctypes.default_abi,
                         /*return */ctypes.int,
                         /*fd*/     ctypes.int);
 
        UnixFile.close = function close(fd) {
          // Detach the finalizer and call |_close|.
          return fd.dispose();
        };
--- a/toolkit/components/osfile/osfile_unix_front.jsm
+++ b/toolkit/components/osfile/osfile_unix_front.jsm
@@ -13,16 +13,17 @@
   if (typeof Components != "undefined") {
     // We do not wish osfile_unix_front.jsm to be used directly as a main thread
     // module yet.
 
     throw new Error("osfile_unix_front.jsm cannot be used from the main thread yet");
   }
   importScripts("resource://gre/modules/osfile/osfile_unix_back.jsm");
   importScripts("resource://gre/modules/osfile/ospath_unix_back.jsm");
+  importScripts("resource://gre/modules/osfile/osfile_shared_front.jsm");
   (function(exports) {
      "use strict";
 
      // exports.OS.Unix is created by osfile_unix_back.jsm
      if (exports.OS.File) {
        return; // Avoid double-initialization
      }
      exports.OS.Unix.File._init();
@@ -35,143 +36,132 @@
       *
       * You generally do not need to call this constructor yourself. Rather,
       * to open a file, use function |OS.File.open|.
       *
       * @param fd A OS-specific file descriptor.
       * @constructor
       */
      let File = function File(fd) {
-       this._fd = fd;
+       exports.OS.Shared.AbstractFile.call(this, fd);
+       this._closeResult = null;
      };
-     File.prototype = {
-       /**
-        * If the file is open, this returns the file descriptor.
-        * Otherwise, throw a |File.Error|.
-        */
-       get fd() {
-         return this._fd;
-       },
-
-       // Placeholder getter, used to replace |get fd| once
-       // the file is closed.
-       _nofd: function nofd(operation) {
-         operation = operation || "unknown operation";
-         throw new File.Error(operation, Const.EBADF);
-       },
+     File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
 
-       /**
-        * Close the file.
-        *
-        * This method has no effect if the file is already closed. However,
-        * if the first call to |close| has thrown an error, further calls
-        * will throw the same error.
-        *
-        * @throws File.Error If closing the file revealed an error that could
-        * not be reported earlier.
-        */
-       close: function close() {
-         if (this._fd) {
-           let fd = this._fd;
-           this._fd = null;
-           delete this.fd;
-           Object.defineProperty(this, "fd", {get: File.prototype._nofd});
-           // Call |close(fd)|, detach finalizer
-           if (UnixFile.close(fd) == -1) {
-             this._closeResult = new File.Error("close", ctypes.errno);
-           }
+     /**
+      * Close the file.
+      *
+      * This method has no effect if the file is already closed. However,
+      * if the first call to |close| has thrown an error, further calls
+      * will throw the same error.
+      *
+      * @throws File.Error If closing the file revealed an error that could
+      * not be reported earlier.
+      */
+     File.prototype.close = function close() {
+       if (this._fd) {
+         let fd = this._fd;
+         this._fd = null;
+        // Call |close(fd)|, detach finalizer if any
+         // (|fd| may not be a CDataFinalizer if it has been
+         // instantiated from a controller thread).
+         let result = UnixFile._close(fd);
+         if (typeof fd == "object" && "forget" in fd) {
+           fd.forget();
+         }
+         if (result == -1) {
+           this._closeResult = new File.Error("close");
          }
-         if (this._closeResult) {
-           throw this._closeResult;
-         }
-         return;
-       },
-       _closeResult: null,
+       }
+       if (this._closeResult) {
+         throw this._closeResult;
+       }
+       return;
+     };
+
+     /**
+      * Read some bytes from a file.
+      *
+      * @param {ArrayBuffer} 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,
+      * the end of the file has been reached.
+      * @throws {OS.File.Error} In case of I/O error.
+      */
+     File.prototype.read = function read(buffer, nbytes, options) {
+       return throw_on_negative("read",
+         UnixFile.read(this.fd, buffer, nbytes)
+       );
+     };
 
-       /**
-        * Read some bytes from a file.
-        *
-        * @param {ArrayBuffer} 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,
-        * the end of the file has been reached.
-        * @throws {OS.File.Error} In case of I/O error.
-        */
-       read: function read(buffer, nbytes, options) {
-         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
+      * 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.
+      */
+     File.prototype.write = function write(buffer, nbytes, options) {
+       return throw_on_negative("write",
+         UnixFile.write(this.fd, buffer, nbytes)
+       );
+     };
 
-       /**
-        * Write some bytes to a file.
-        *
-        * @param {ArrayBuffer} 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.
-        */
-       write: function write(buffer, nbytes, options) {
-         return throw_on_negative("write",
-           UnixFile.write(this.fd, buffer, nbytes)
-         );
-       },
+     /**
+      * Return the current position in the file.
+      */
+     File.prototype.getPosition = function getPosition(pos) {
+         return this.setPosition(0, File.POS_CURRENT);
+     };
 
-       /**
-        * Return the current position in the file.
-        */
-       getPosition: function getPosition(pos) {
-         return this.setPosition(0, File.POS_CURRENT);
-       },
+     /**
+      * Change the current position in the file.
+      *
+      * @param {number} pos The new position. Whether this position
+      * is considered from the current position, from the start of
+      * the file or from the end of the file is determined by
+      * argument |whence|.  Note that |pos| may exceed the length of
+      * the file.
+      * @param {number=} whence The reference position. If omitted
+      * or |OS.File.POS_START|, |pos| is relative to the start of the
+      * file.  If |OS.File.POS_CURRENT|, |pos| is relative to the
+      * current position in the file. If |OS.File.POS_END|, |pos| is
+      * relative to the end of the file.
+      *
+      * @return The new position in the file.
+      */
+     File.prototype.setPosition = function setPosition(pos, whence) {
+       if (whence === undefined) {
+         whence = Const.SEEK_START;
+       }
+       return throw_on_negative("setPosition",
+         UnixFile.lseek(this.fd, pos, whence)
+       );
+     };
 
-       /**
-        * Change the current position in the file.
-        *
-        * @param {number} pos The new position. Whether this position
-        * is considered from the current position, from the start of
-        * the file or from the end of the file is determined by
-        * argument |whence|.  Note that |pos| may exceed the length of
-        * the file.
-        * @param {number=} whence The reference position. If omitted
-        * or |OS.File.POS_START|, |pos| is relative to the start of the
-        * file.  If |OS.File.POS_CURRENT|, |pos| is relative to the
-        * current position in the file. If |OS.File.POS_END|, |pos| is
-        * relative to the end of the file.
-        *
-        * @return The new position in the file.
-        */
-       setPosition: function setPosition(pos, whence) {
-         if (whence === undefined) {
-           whence = Const.SEEK_START;
-         }
-         return throw_on_negative("setPosition",
-           UnixFile.lseek(this.fd, pos, whence)
-         );
-       },
-
-       /**
-        * Fetch the information on the file.
-        *
-        * @return File.Info The information on |this| file.
-        */
-       stat: function stat() {
-         throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr));
+     /**
+      * Fetch the information on the file.
+      *
+      * @return File.Info The information on |this| file.
+      */
+     File.prototype.stat = function stat() {
+       throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr));
          return new File.Info(gStatData);
-       }
      };
 
 
      // Constant used to normalize options.
      const noOptions = {};
 
      // The default unix mode for opening (0600)
      const DEFAULT_UNIX_MODE = 384;
@@ -221,17 +211,17 @@
       */
      File.open = function Unix_open(path, mode, options) {
        options = options || noOptions;
        let omode = options.unixMode || DEFAULT_UNIX_MODE;
        let flags;
        if (options.unixFlags) {
          flags = options.unixFlags;
        } else {
-         mode = OS.Shared._aux.normalizeOpenMode(mode);
+         mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
          // Handle read/write
          if (!mode.write) {
            flags = Const.O_RDONLY;
          } else if (mode.read) {
            flags = Const.O_RDWR;
          } else {
            flags = Const.O_WRONLY;
          }
--- a/toolkit/components/osfile/osfile_win_allthreads.jsm
+++ b/toolkit/components/osfile/osfile_win_allthreads.jsm
@@ -136,16 +136,25 @@ if (typeof Components != "undefined") {
    * |true| if the error was raised because a directory is not empty
    * does not exist, |false| otherwise.
    */
   Object.defineProperty(OSError.prototype, "becauseNotEmpty", {
     get: function becauseNotEmpty() {
       return this.winLastError == OS.Constants.Win.ERROR_DIR_NOT_EMPTY;
     }
   });
+  /**
+   * |true| if the error was raised because a file or directory
+   * is closed, |false| otherwise.
+   */
+  Object.defineProperty(OSError.prototype, "becauseClosed", {
+    get: function becauseClosed() {
+      return this.winLastError == exports.OS.Constants.Win.INVALID_HANDLE_VALUE;
+    }
+  });
 
   /**
    * 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,
@@ -174,9 +183,15 @@ if (typeof Components != "undefined") {
 
   /**
    * Native paths
    *
    * Under Windows, expressed as wide strings
    */
   Types.path = Types.wstring.withName("[in] path");
   Types.out_path = Types.out_wstring.withName("[out] path");
+
+  // Special constructors that need to be defined on all threads
+  OSError.closed = function closed(operation) {
+    return new OSError(operation, exports.OS.Constants.Win.INVALID_HANDLE_VALUE);
+  };
+
 })(this);
--- a/toolkit/components/osfile/osfile_win_back.jsm
+++ b/toolkit/components/osfile/osfile_win_back.jsm
@@ -172,17 +172,17 @@
                   { wHour:      ctypes.int16_t },
                   { wMinute:    ctypes.int16_t },
                   { wSecond:    ctypes.int16_t },
                   { wMilliSeconds: ctypes.int16_t }
                   ]));
 
        // Special case: these functions are used by the
        // finalizer
-       let _CloseHandle =
+       let _CloseHandle = WinFile._CloseHandle =
          libc.declare("CloseHandle", ctypes.winapi_abi,
                         /*return */ctypes.bool,
                         /*handle*/ ctypes.voidptr_t);
 
        WinFile.CloseHandle = function(fd) {
          return fd.dispose(); // Returns the value of |CloseHandle|.
        };
 
--- a/toolkit/components/osfile/osfile_win_front.jsm
+++ b/toolkit/components/osfile/osfile_win_front.jsm
@@ -11,19 +11,19 @@
 
 {
   if (typeof Components != "undefined") {
     // We do not wish osfile_win_front.jsm to be used directly as a main thread
     // module yet.
     throw new Error("osfile_win_front.jsm cannot be used from the main thread yet");
   }
 
-  importScripts("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
   importScripts("resource://gre/modules/osfile/osfile_win_back.jsm");
   importScripts("resource://gre/modules/osfile/ospath_win_back.jsm");
+  importScripts("resource://gre/modules/osfile/osfile_shared_front.jsm");
 
   (function(exports) {
      "use strict";
 
      // exports.OS.Win is created by osfile_win_back.jsm
      if (exports.OS.File) {
        return; // Avoid double-initialization
      }
@@ -55,149 +55,136 @@
       *
       * You generally do not need to call this constructor yourself. Rather,
       * to open a file, use function |OS.File.open|.
       *
       * @param fd A OS-specific file descriptor.
       * @constructor
       */
      let File = function File(fd) {
-       this._fd = fd;
+       exports.OS.Shared.AbstractFile.call(this, fd);
+       this._closeResult = null;
      };
-     File.prototype = {
-       /**
-        * If the file is open, this returns the file descriptor.
-        * Otherwise, throw a |File.Error|.
-        */
-       get fd() {
-         return this._fd;
-       },
-
-       // Placeholder getter, used to replace |get fd| once
-       // the file is closed.
-       _nofd: function nofd(operation) {
-         operation = operation ||
-             this._nofd.caller.name ||
-             "unknown operation";
-         throw new File.Error(operation, Const.INVALID_HANDLE_VALUE);
-       },
+     File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
 
-       /**
-        * Close the file.
-        *
-        * This method has no effect if the file is already closed. However,
-        * if the first call to |close| has thrown an error, further calls
-        * will throw the same error.
-        *
-        * @throws File.Error If closing the file revealed an error that could
-        * not be reported earlier.
-        */
-       close: function close() {
-         if (this._fd) {
-           let fd = this._fd;
-           this._fd = null;
-           delete this.fd;
-           Object.defineProperty(this, "fd", {get: File.prototype._nofd});
-           // Call CloseHandle(fd), detach finalizer
-           if (fd.dispose() == 0) {
-             this._closeResult = new File.Error("close", ctypes.errno);
-           }
+     /**
+      * Close the file.
+      *
+      * This method has no effect if the file is already closed. However,
+      * if the first call to |close| has thrown an error, further calls
+      * will throw the same error.
+      *
+      * @throws File.Error If closing the file revealed an error that could
+      * not be reported earlier.
+      */
+     File.prototype.close = function close() {
+       if (this._fd) {
+         let fd = this._fd;
+         this._fd = null;
+         // Call |close(fd)|, detach finalizer if any
+         // (|fd| may not be a CDataFinalizer if it has been
+         // instantiated from a controller thread).
+         let result = WinFile._CloseHandle(fd);
+         if (typeof fd == "object" && "forget" in fd) {
+           fd.forget();
+         }
+         if (result == -1) {
+           this._closeResult = new File.Error("close");
          }
-         if (this._closeResult) {
-           throw this._closeResult;
-         }
-         return;
-       },
-       _closeResult: null,
+       }
+       if (this._closeResult) {
+         throw this._closeResult;
+       }
+       return;
+     };
+
+     /**
+      * Read some bytes from a file.
+      *
+      * @param {ArrayBuffer} 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,
+      * the end of the file has been reached.
+      * @throws {OS.File.Error} In case of I/O error.
+      */
+     File.prototype.read = function read(buffer, nbytes, options) {
+       // |gBytesReadPtr| is a pointer to |gBytesRead|.
+       throw_on_zero("read",
+         WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null)
+       );
+       return gBytesRead.value;
+     };
 
-       /**
-        * Read some bytes from a file.
-        *
-        * @param {ArrayBuffer} 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,
-        * the end of the file has been reached.
-        * @throws {OS.File.Error} In case of I/O error.
-        */
-       read: function read(buffer, nbytes, options) {
-         // |gBytesReadPtr| is a pointer to |gBytesRead|.
-         throw_on_zero("read",
-           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
+      * 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.
+      */
+     File.prototype.write = function write(buffer, nbytes, options) {
+       // |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
+       throw_on_zero("write",
+         WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null)
+       );
+       return gBytesWritten.value;
+     };
+
+     /**
+      * Return the current position in the file.
+      */
+     File.prototype.getPosition = function getPosition(pos) {
+       return this.setPosition(0, File.POS_CURRENT);
+     };
 
-       /**
-        * Write some bytes to a file.
-        *
-        * @param {ArrayBuffer} 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.
-        */
-       write: function write(buffer, nbytes, options) {
-         // |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
-         throw_on_zero("write",
-           WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null)
-         );
-         return gBytesWritten.value;
-       },
-
-       /**
-        * Return the current position in the file.
-        */
-       getPosition: function getPosition(pos) {
-         return this.setPosition(0, File.POS_CURRENT);
-       },
+     /**
+      * Change the current position in the file.
+      *
+      * @param {number} pos The new position. Whether this position
+      * is considered from the current position, from the start of
+      * the file or from the end of the file is determined by
+      * argument |whence|.  Note that |pos| may exceed the length of
+      * the file.
+      * @param {number=} whence The reference position. If omitted
+      * or |OS.File.POS_START|, |pos| is relative to the start of the
+      * file.  If |OS.File.POS_CURRENT|, |pos| is relative to the
+      * current position in the file. If |OS.File.POS_END|, |pos| is
+      * relative to the end of the file.
+      *
+      * @return The new position in the file.
+      */
+     File.prototype.setPosition = function setPosition(pos, whence) {
+       if (whence === undefined) {
+         whence = Const.FILE_BEGIN;
+       }
+       return throw_on_negative("setPosition",
+         WinFile.SetFilePointer(this.fd, pos, null, whence));
+     };
 
-       /**
-        * Change the current position in the file.
-        *
-        * @param {number} pos The new position. Whether this position
-        * is considered from the current position, from the start of
-        * the file or from the end of the file is determined by
-        * argument |whence|.  Note that |pos| may exceed the length of
-        * the file.
-        * @param {number=} whence The reference position. If omitted
-        * or |OS.File.POS_START|, |pos| is relative to the start of the
-        * file.  If |OS.File.POS_CURRENT|, |pos| is relative to the
-        * current position in the file. If |OS.File.POS_END|, |pos| is
-        * relative to the end of the file.
-        *
-        * @return The new position in the file.
-        */
-       setPosition: function setPosition(pos, whence) {
-         if (whence === undefined) {
-           whence = Const.FILE_BEGIN;
-         }
-         return throw_on_negative("setPosition",
-           WinFile.SetFilePointer(this.fd, pos, null, whence));
-       },
-
-       /**
-        * Fetch the information on the file.
-        *
-        * @return File.Info The information on |this| file.
-        */
-       stat: function stat() {
-         throw_on_zero("stat",
-           WinFile.GetFileInformationByHandle(this.fd, gFileInfoPtr));
-         return new File.Info(gFileInfo);
-       }
+     /**
+      * Fetch the information on the file.
+      *
+      * @return File.Info The information on |this| file.
+      */
+     File.prototype.stat = function stat() {
+       throw_on_zero("stat",
+         WinFile.GetFileInformationByHandle(this.fd, gFileInfoPtr));
+       return new File.Info(gFileInfo);
      };
 
      // Constant used to normalize options.
      const noOptions = {};
 
      // The default sharing mode for opening files: files are not
      // locked against being reopened for reading/writing or against
      // being deleted by the same process or another process.
@@ -274,17 +261,17 @@
        if ("winAccess" in options && "winDisposition" in options) {
          access = options.winAccess;
          disposition = options.winDisposition;
        } else if (("winAccess" in options && !("winDisposition" in options))
                  ||(!("winAccess" in options) && "winDisposition" in options)) {
          throw new TypeError("OS.File.open requires either both options " +
            "winAccess and winDisposition or neither");
        } else {
-         mode = OS.Shared._aux.normalizeOpenMode(mode);
+         mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
          if (mode.read) {
            access |= Const.GENERIC_READ;
          }
          if (mode.write) {
            access |= Const.GENERIC_WRITE;
          }
          // Finally, handle create/existing/trunc
          if (mode.trunc) {