author | Tim Taubert <ttaubert@mozilla.com> |
Sun, 22 Jul 2012 10:38:00 +0200 | |
changeset 100050 | 462106f027af8eeadfa78e357f77465df6813ef3 |
parent 100040 | defbe00ca0916c2f2488c641190c547c84603d72 (current diff) |
parent 100049 | b258d6ce2e47882f008079aeb817c9be30308077 (diff) |
child 100067 | 19fe7ecd0af8e6319db784e01410fb0a716365ae |
child 100070 | 2a8871238241a99975b3acce32dca5dcb77daaa7 |
push id | 23163 |
push user | ttaubert@mozilla.com |
push date | Sun, 22 Jul 2012 08:38:10 +0000 |
treeherder | mozilla-central@462106f027af [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 17.0a1 |
first release with | nightly linux32
462106f027af
/
17.0a1
/
20120722030555
/
files
nightly linux64
462106f027af
/
17.0a1
/
20120722030555
/
files
nightly mac
462106f027af
/
17.0a1
/
20120722030555
/
files
nightly win32
462106f027af
/
17.0a1
/
20120722030555
/
files
nightly win64
462106f027af
/
17.0a1
/
20120722030555
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
17.0a1
/
20120722030555
/
pushlog to previous
nightly linux64
17.0a1
/
20120722030555
/
pushlog to previous
nightly mac
17.0a1
/
20120722030555
/
pushlog to previous
nightly win32
17.0a1
/
20120722030555
/
pushlog to previous
nightly win64
17.0a1
/
20120722030555
/
pushlog to previous
|
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -4857,20 +4857,27 @@ function toggleSidebar(commandID, forceO var sidebarBroadcaster = document.getElementById(commandID); var sidebar = document.getElementById("sidebar"); // xul:browser var sidebarTitle = document.getElementById("sidebar-title"); var sidebarSplitter = document.getElementById("sidebar-splitter"); if (sidebarBroadcaster.getAttribute("checked") == "true") { if (!forceOpen) { + // Replace the document currently displayed in the sidebar with about:blank + // so that we can free memory by unloading the page. We need to explicitly + // create a new content viewer because the old one doesn't get destroyed + // until about:blank has loaded (which does not happen as long as the + // element is hidden). + sidebar.setAttribute("src", "about:blank"); + sidebar.docShell.createAboutBlankContentViewer(null); + sidebarBroadcaster.removeAttribute("checked"); sidebarBox.setAttribute("sidebarcommand", ""); sidebarTitle.value = ""; - sidebar.setAttribute("src", "about:blank"); sidebarBox.hidden = true; sidebarSplitter.hidden = true; content.focus(); } else { fireSidebarFocusedEvent(); } return; }
--- a/build/automationutils.py +++ b/build/automationutils.py @@ -421,17 +421,17 @@ def wrapCommand(cmd): return cmd class ShutdownLeakLogger(object): """ Parses the mochitest run log when running a debug build, assigns all leaked DOM windows (that are still around after test suite shutdown, despite running the GC) to the tests that created them and prints leak statistics. """ - MAX_LEAK_COUNT = 5 + MAX_LEAK_COUNT = 3 def __init__(self, logger): self.logger = logger self.tests = [] self.leakedWindows = {} self.leakedDocShells = set() self.currentTest = None self.seenShutdown = False
--- a/dom/system/OSFileConstants.cpp +++ b/dom/system/OSFileConstants.cpp @@ -5,16 +5,17 @@ #include "fcntl.h" #include "errno.h" #include "prsystem.h" #if defined(XP_UNIX) #include "unistd.h" #include "dirent.h" +#include "sys/stat.h" #endif // defined(XP_UNIX) #if defined(XP_MACOSX) #include "copyfile.h" #endif // defined(XP_MACOSX) #if defined(XP_WIN) #include <windows.h> @@ -272,28 +273,49 @@ static dom::ConstantSpec gLibcProperties INT_CONSTANT(DT_CHR), INT_CONSTANT(DT_DIR), INT_CONSTANT(DT_BLK), INT_CONSTANT(DT_REG), INT_CONSTANT(DT_LNK), INT_CONSTANT(DT_SOCK), #endif // defined(DT_UNKNOWN) +#if defined(S_IFIFO) + // Constants for |stat| + INT_CONSTANT(S_IFMT), + INT_CONSTANT(S_IFIFO), + INT_CONSTANT(S_IFCHR), + INT_CONSTANT(S_IFDIR), + INT_CONSTANT(S_IFBLK), + INT_CONSTANT(S_IFREG), + INT_CONSTANT(S_IFLNK), + INT_CONSTANT(S_IFSOCK), +#endif // defined(S_IFIFO) + // Constants used to define data structures // // Many data structures have different fields/sizes/etc. on // various OSes / versions of the same OS / platforms. For these // data structures, we need to compute and export from C the size // and, if necessary, the offset of fields, so as to be able to // define the structure in JS. #if defined(XP_UNIX) // The size of |mode_t|. { "OSFILE_SIZEOF_MODE_T", INT_TO_JSVAL(sizeof (mode_t)) }, + // The size of |gid_t|. + { "OSFILE_SIZEOF_GID_T", INT_TO_JSVAL(sizeof (gid_t)) }, + + // The size of |uid_t|. + { "OSFILE_SIZEOF_UID_T", INT_TO_JSVAL(sizeof (uid_t)) }, + + // The size of |time_t|. + { "OSFILE_SIZEOF_TIME_T", INT_TO_JSVAL(sizeof (time_t)) }, + // Defining |dirent|. // Size { "OSFILE_SIZEOF_DIRENT", INT_TO_JSVAL(sizeof (dirent)) }, // Offset of field |d_name|. { "OSFILE_OFFSETOF_DIRENT_D_NAME", INT_TO_JSVAL(offsetof (struct dirent, d_name)) }, // An upper bound to the length of field |d_name| of struct |dirent|. // (may not be exact, depending on padding). @@ -301,30 +323,56 @@ static dom::ConstantSpec gLibcProperties #if defined(DT_UNKNOWN) // Position of field |d_type| in |dirent| // Not strictly posix, but seems defined on all platforms // except mingw32. { "OSFILE_OFFSETOF_DIRENT_D_TYPE", INT_TO_JSVAL(offsetof (struct dirent, d_type)) }, #endif // defined(DT_UNKNOWN) + + // Defining |stat| + + { "OSFILE_SIZEOF_STAT", INT_TO_JSVAL(sizeof (struct stat)) }, + + { "OSFILE_OFFSETOF_STAT_ST_MODE", INT_TO_JSVAL(offsetof (struct stat, st_mode)) }, + { "OSFILE_OFFSETOF_STAT_ST_UID", INT_TO_JSVAL(offsetof (struct stat, st_uid)) }, + { "OSFILE_OFFSETOF_STAT_ST_GID", INT_TO_JSVAL(offsetof (struct stat, st_gid)) }, + { "OSFILE_OFFSETOF_STAT_ST_SIZE", INT_TO_JSVAL(offsetof (struct stat, st_size)) }, + +#if defined(HAVE_ST_ATIMESPEC) + { "OSFILE_OFFSETOF_STAT_ST_ATIME", INT_TO_JSVAL(offsetof (struct stat, st_atimespec)) }, + { "OSFILE_OFFSETOF_STAT_ST_MTIME", INT_TO_JSVAL(offsetof (struct stat, st_mtimespec)) }, + { "OSFILE_OFFSETOF_STAT_ST_CTIME", INT_TO_JSVAL(offsetof (struct stat, st_ctimespec)) }, +#else + { "OSFILE_OFFSETOF_STAT_ST_ATIME", INT_TO_JSVAL(offsetof (struct stat, st_atime)) }, + { "OSFILE_OFFSETOF_STAT_ST_MTIME", INT_TO_JSVAL(offsetof (struct stat, st_mtime)) }, + { "OSFILE_OFFSETOF_STAT_ST_CTIME", INT_TO_JSVAL(offsetof (struct stat, st_ctime)) }, +#endif // defined(HAVE_ST_ATIME) + #endif // defined(XP_UNIX) + // System configuration // Under MacOSX, to avoid using deprecated functions that do not // match the constants we define in this object (including // |sizeof|/|offsetof| stuff, but not only), for a number of // functions, we need to adapt the name of the symbols we are using, // whenever macro _DARWIN_FEATURE_64_BIT_INODE is set. We export // this value to be able to do so from JavaScript. #if defined(_DARWIN_FEATURE_64_BIT_INODE) - { "_DARWIN_FEATURE_64_BIT_INODE", INT_TO_JSVAL(1) }, -#endif // defind(_DARWIN_FEATURE_64_BIT_INODE) + { "_DARWIN_FEATURE_64_BIT_INODE", INT_TO_JSVAL(1) }, +#endif // defined(_DARWIN_FEATURE_64_BIT_INODE) + + // Similar feature for Linux +#if defined(_STAT_VER) + INT_CONSTANT(_STAT_VER), +#endif // defined(_STAT_VER) PROP_END }; #if defined(XP_WIN) /** * The properties defined in windows.h. @@ -363,16 +411,17 @@ static dom::ConstantSpec gWinProperties[ // CreateFile attributes INT_CONSTANT(FILE_ATTRIBUTE_ARCHIVE), INT_CONSTANT(FILE_ATTRIBUTE_DIRECTORY), INT_CONSTANT(FILE_ATTRIBUTE_NORMAL), INT_CONSTANT(FILE_ATTRIBUTE_READONLY), INT_CONSTANT(FILE_ATTRIBUTE_REPARSE_POINT), INT_CONSTANT(FILE_ATTRIBUTE_TEMPORARY), + INT_CONSTANT(FILE_FLAG_BACKUP_SEMANTICS), // CreateFile error constant { "INVALID_HANDLE_VALUE", INT_TO_JSVAL(INT_PTR(INVALID_HANDLE_VALUE)) }, // CreateFile flags INT_CONSTANT(FILE_FLAG_DELETE_ON_CLOSE),
--- a/toolkit/components/osfile/osfile_shared.jsm +++ b/toolkit/components/osfile/osfile_shared.jsm @@ -146,48 +146,50 @@ } }); return ptr_t; }, /** * Attach a finalizer to a type. */ - releaseWith: function(finalizer) { + releaseWith: function releaseWith(finalizer) { let parent = this; let type = new Type("[auto " + finalizer +"] " + this.name, this.implementation, - function (value, operation) { + function release(value, operation) { return ctypes.CDataFinalizer( parent.convert_from_c(value, operation), finalizer); }); return type; + }, + + /** + * Return an alias to a type with a different name. + */ + withName: function withName(name) { + return Object.create(this, {name: {value: name}}); } }; exports.OS.Shared.Type = Type; let Types = Type; /* * Some values are large integers on 64 bit platforms. Unfortunately, * in practice, 64 bit integers cannot be manipulated in JS. We * therefore project them to regular numbers whenever possible. */ let projectLargeInt = function projectLargeInt(x) { return parseInt(x.toString(), 10); }; let projectLargeUInt = function projectLargeUInt(x) { - if (ctypes.UInt64.hi(x)) { - throw new Error("Number too large " + x + - "(unsigned, hi: " + ctypes.UInt64.hi(x) + - ", lo:" + ctypes.UInt64.lo(x) + ")"); - } - return ctypes.UInt64.lo(x); + return parseInt(x.toString(), 10); }; let projectValue = function projectValue(x) { if (!(x instanceof ctypes.CData)) { return x; } if (!("value" in x)) { // Sanity check throw new TypeError("Number " + x.toSource() + " has no field |value|"); } @@ -474,17 +476,17 @@ /** * Add a field at a given offset. * * @param {number} offset The offset at which to insert the field. * @param {string} name The name of the field. * @param {CType|Type} type The type of the field. */ add_field_at: function add_field_at(offset, name, type) { - if (offset === null) { + if (offset == null) { throw new TypeError("add_field_at requires a non-null offset"); } if (!name) { throw new TypeError("add_field_at requires a non-null name"); } if (!type) { throw new TypeError("add_field_at requires a non-null type"); }
--- a/toolkit/components/osfile/osfile_unix_back.jsm +++ b/toolkit/components/osfile/osfile_unix_back.jsm @@ -141,20 +141,24 @@ // Note: support for strings in js-ctypes is very limited. // Once bug 552551 has progressed, we should extend this // type using ctypes.readString/ctypes.writeString /** * Type |mode_t| */ - Types.mode_t = Object.create( - Types.intn_t(OS.Constants.libc.OSFILE_SIZEOF_MODE_T), - {name: {value: "mode_t"}}); + Types.mode_t = + Types.intn_t(OS.Constants.libc.OSFILE_SIZEOF_MODE_T).withName("mode_t"); + /** + * Type |time_t| + */ + Types.time_t = + Types.intn_t(OS.Constants.libc.OSFILE_SIZEOF_TIME_T).withName("time_t"); Types.DIR = new Type("DIR", ctypes.StructType("DIR")); Types.null_or_DIR_ptr = new Type("null_or_DIR*", Types.DIR.out_ptr.implementation, @@ -186,16 +190,46 @@ // We now have built |dirent|. Types.dirent = dirent.getType(); LOG("dirent is: " + Types.dirent.implementation.toSource()); } Types.null_or_dirent_ptr = new Type("null_of_dirent", Types.dirent.out_ptr.implementation); + // Structure |stat| + // Same technique + { + let stat = new OS.Shared.HollowStructure("stat", + OS.Constants.libc.OSFILE_SIZEOF_STAT); + stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_MODE, + "st_mode", Types.mode_t.implementation); + stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_UID, + "st_uid", ctypes.int); + stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_GID, + "st_gid", ctypes.int); + + // Here, things get complicated with different data structures. + // Some platforms have |time_t st_atime| and some platforms have + // |timespec st_atimespec|. However, since |timespec| starts with + // a |time_t|, followed by nanoseconds, we just cheat and pretend + // that everybody has |time_t st_atime|, possibly followed by padding + stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_ATIME, + "st_atime", Types.time_t.implementation); + stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_MTIME, + "st_mtime", Types.time_t.implementation); + stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_CTIME, + "st_ctime", Types.time_t.implementation); + + 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 = libc.declare("close", ctypes.default_abi, /*return */ctypes.int, /*fd*/ ctypes.int); @@ -310,16 +344,32 @@ /*fd*/ Types.fd); // Note: MacOS/BSD-specific UnixFile.ftruncate = declareFFI("ftruncate", ctypes.default_abi, /*return*/ Types.negativeone_or_nothing, /*fd*/ Types.fd, /*length*/ Types.off_t); + if (OS.Constants.libc._DARWIN_FEATURE_64_BIT_INODE) { + UnixFile.fstat = + declareFFI("fstat$INODE64", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*path*/ Types.fd, + /*buf*/ Types.stat.out_ptr + ); + } else { + UnixFile.fstat = + declareFFI("fstat", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*path*/ Types.fd, + /*buf*/ Types.stat.out_ptr + ); + } + UnixFile.lchown = declareFFI("lchown", ctypes.default_abi, /*return*/ Types.negativeone_or_nothing, /*path*/ Types.string, /*uid_t*/ Types.uid_t, /*gid_t*/ Types.gid_t); UnixFile.link = @@ -444,23 +494,98 @@ declareFFI("write", ctypes.default_abi, /*return*/ Types.negativeone_or_ssize_t, /*fd*/ Types.fd, /*buf*/ Types.char.in_ptr, /*nbytes*/ Types.size_t); // Weird cases that require special treatment + // OSes use a variety of hacks to differentiate between + // 32-bits and 64-bits versions of |stat|, |lstat|, |fstat|. + if (OS.Constants.libc._DARWIN_FEATURE_64_BIT_INODE) { + // MacOS X 64-bits + UnixFile.stat = + declareFFI("stat$INODE64", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*path*/ Types.string, + /*buf*/ Types.stat.out_ptr + ); + UnixFile.lstat = + declareFFI("lstat$INODE64", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*path*/ Types.string, + /*buf*/ Types.stat.out_ptr + ); + UnixFile.fstat = + declareFFI("fstat$INODE64", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*path*/ Types.fd, + /*buf*/ Types.stat.out_ptr + ); + } else if (OS.Constants.libc._STAT_VER != undefined) { + const ver = OS.Constants.libc._STAT_VER; + // Linux, all widths + let xstat = + declareFFI("__xstat", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*_stat_ver*/ Types.int, + /*path*/ Types.string, + /*buf*/ Types.stat.out_ptr); + let lxstat = + declareFFI("__lxstat", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*_stat_ver*/ Types.int, + /*path*/ Types.string, + /*buf*/ Types.stat.out_ptr); + let fxstat = + declareFFI("__fxstat", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*_stat_ver*/ Types.int, + /*fd*/ Types.fd, + /*buf*/ Types.stat.out_ptr); + + UnixFile.stat = function stat(path, buf) { + return xstat(ver, path, buf); + }; + UnixFile.lstat = function stat(path, buf) { + return lxstat(ver, path, buf); + }; + UnixFile.fstat = function stat(fd, buf) { + return fxstat(ver, fd, buf); + }; + } else { + // Mac OS X 32-bits, other Unix + UnixFile.stat = + declareFFI("stat", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*path*/ Types.string, + /*buf*/ Types.stat.out_ptr + ); + UnixFile.lstat = + declareFFI("lstat", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*path*/ Types.string, + /*buf*/ Types.stat.out_ptr + ); + UnixFile.fstat = + declareFFI("fstat", ctypes.default_abi, + /*return*/ Types.negativeone_or_nothing, + /*fd*/ Types.fd, + /*buf*/ Types.stat.out_ptr + ); + } + // We cannot make a C array of CDataFinalizer, so // pipe cannot be directly defined as a C function. let _pipe = - declareFFI("pipe", ctypes.default_abi, - /*return*/ Types.negativeone_or_nothing, - /*fds*/ Types.int.out_ptr); + libc.declare("pipe", ctypes.default_abi, + /*return*/ ctypes.int, + /*fds*/ ctypes.ArrayType(ctypes.int, 2)); // A shared per-thread buffer used to communicate with |pipe| let _pipebuf = new (ctypes.ArrayType(ctypes.int, 2))(); UnixFile.pipe = function pipe(array) { let result = _pipe(_pipebuf); if (result == -1) { return result;
--- a/toolkit/components/osfile/osfile_unix_front.jsm +++ b/toolkit/components/osfile/osfile_unix_front.jsm @@ -155,16 +155,26 @@ // In this implementation, // OS.File.POS_START == OS.Constants.libc.SEEK_SET // OS.File.POS_CURRENT == OS.Constants.libc.SEEK_CUR // OS.File.POS_END == OS.Constants.libc.SEEK_END whence = (whence == undefined)?OS.Constants.libc.SEEK_SET:whence; 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)); + return new File.Info(gStatData); } }; /** * A File-related error. * * To obtain a human-readable error message, use method |toString|. * To determine the cause of the error, use the various |becauseX| @@ -669,17 +679,17 @@ */ get isDir() { return this._d_type == OS.Constants.libc.DT_DIR; }, /** * |true| if the entry is a symbolic link, |false| otherwise */ - get isLink() { + get isSymLink() { return this._d_type == OS.Constants.libc.DT_LNK; }, /** * The name of the entry. * @type {string} */ get name() { @@ -692,16 +702,136 @@ get path() { delete this.path; let path = OS.Unix.Path.join(this._parent, this.name); Object.defineProperty(this, "path", {value: path}); return path; } }; + let gStatData = new OS.Shared.Type.stat.implementation(); + let gStatDataPtr = gStatData.address(); + let MODE_MASK = 4095 /*= 07777*/; + File.Info = function Info(stat) { + this._st_mode = stat.st_mode; + this._st_uid = stat.st_uid; + this._st_gid = stat.st_gid; + this._st_atime = stat.st_atime; + this._st_mtime = stat.st_mtime; + this._st_ctime = stat.st_ctime; + this._st_size = stat.st_size; + }; + File.Info.prototype = { + /** + * |true| if this file is a directory, |false| otherwise + */ + get isDir() { + return (this._st_mode & OS.Constants.libc.S_IFMT) == OS.Constants.libc.S_IFDIR; + }, + /** + * |true| if this file is a symbolink link, |false| otherwise + */ + get isSymLink() { + return (this._st_mode & OS.Constants.libc.S_IFMT) == OS.Constants.libc.S_IFLNK; + }, + /** + * The size of the file, in bytes. + * + * Note that the result may be |NaN| if the size of the file cannot be + * represented in JavaScript. + * + * @type {number} + */ + get size() { + delete this.size; + let size; + try { + size = OS.Shared.projectValue(this._st_size); + } catch(x) { + LOG("get size error", x); + size = NaN; + } + Object.defineProperty(this, "size", { value: size }); + return size; + }, + /** + * The date of creation of this file + * + * @type {Date} + */ + get creationDate() { + delete this.creationDate; + let date = new Date(this._st_ctime * 1000); + Object.defineProperty(this, "creationDate", { value: date }); + return date; + }, + /** + * The date of last access to this file. + * + * Note that the definition of last access may depend on the + * underlying operating system and file system. + * + * @type {Date} + */ + get lastAccessDate() { + delete this.lastAccessDate; + let date = new Date(this._st_atime * 1000); + Object.defineProperty(this, "lastAccessDate", {value: date}); + return date; + }, + /** + * Return the date of last modification of this file. + */ + get lastModificationDate() { + delete this.lastModificationDate; + let date = new Date(this._st_mtime * 1000); + Object.defineProperty(this, "lastModificationDate", {value: date}); + return date; + }, + /** + * Return the Unix owner of this file. + */ + get unixOwner() { + return this._st_uid; + }, + /** + * Return the Unix group of this file. + */ + get unixGroup() { + return this._st_gid; + }, + /** + * Return the Unix mode of this file. + */ + get unixMode() { + return this._st_mode & MODE_MASK; + } + }; + + /** + * Fetch the information on a file. + * + * @param {string} path The full name of the file to open. + * @param {*=} options Additional options. In this implementation: + * + * - {bool} unixNoFollowingLinks If set and |true|, if |path| + * represents a symbolic link, the call will return the information + * of the link itself, rather than that of the target file. + * + * @return {File.Information} + */ + File.stat = function stat(path, options) { + options = options || noOptions; + if (options.unixNoFollowingLinks) { + throw_on_negative("stat", UnixFile.lstat(path, gStatDataPtr)); + } else { + throw_on_negative("stat", UnixFile.stat(path, gStatDataPtr)); + } + return new File.Info(gStatData); + }; /** * Get/set the current directory. */ Object.defineProperty(File, "curDir", { set: function(path) { throw_on_negative("curDir", UnixFile.chdir(path)
--- a/toolkit/components/osfile/osfile_win_back.jsm +++ b/toolkit/components/osfile/osfile_win_back.jsm @@ -140,32 +140,46 @@ new Type("zero_or_nothing", Types.bool.implementation); Types.FILETIME = new Type("FILETIME", ctypes.StructType("FILETIME", [ { lo: Types.DWORD.implementation }, { hi: Types.DWORD.implementation }])); - + Types.FindData = new Type("FIND_DATA", ctypes.StructType("FIND_DATA", [ { dwFileAttributes: ctypes.uint32_t }, { ftCreationTime: Types.FILETIME.implementation }, { ftLastAccessTime: Types.FILETIME.implementation }, { ftLastWriteTime: Types.FILETIME.implementation }, { nFileSizeHigh: Types.DWORD.implementation }, { nFileSizeLow: Types.DWORD.implementation }, { dwReserved0: Types.DWORD.implementation }, { dwReserved1: Types.DWORD.implementation }, { cFileName: ctypes.ArrayType(ctypes.jschar, exports.OS.Constants.Win.MAX_PATH) }, { cAlternateFileName: ctypes.ArrayType(ctypes.jschar, 14) } ])); - + + Types.FILE_INFORMATION = + new Type("FILE_INFORMATION", + ctypes.StructType("FILE_INFORMATION", [ + { dwFileAttributes: ctypes.uint32_t }, + { ftCreationTime: Types.FILETIME.implementation }, + { ftLastAccessTime: Types.FILETIME.implementation }, + { ftLastWriteTime: Types.FILETIME.implementation }, + { dwVolumeSerialNumber: ctypes.uint32_t }, + { nFileSizeHigh: Types.DWORD.implementation }, + { nFileSizeLow: Types.DWORD.implementation }, + { nNumberOfLinks: ctypes.uint32_t }, + { nFileIndex: ctypes.uint64_t } + ])); + Types.SystemTime = new Type("SystemTime", ctypes.StructType("SystemTime", [ { wYear: ctypes.int16_t }, { wMonth: ctypes.int16_t }, { wDayOfWeek: ctypes.int16_t }, { wDay: ctypes.int16_t }, { wHour: ctypes.int16_t }, @@ -251,16 +265,22 @@ WinFile.GetCurrentDirectory = declareFFI("GetCurrentDirectoryW", ctypes.winapi_abi, /*return*/ Types.zero_or_DWORD, /*length*/ Types.DWORD, /*buf*/ Types.jschar.out_ptr ); + WinFile.GetFileInformationByHandle = + declareFFI("GetFileInformationByHandle", ctypes.winapi_abi, + /*return*/ Types.zero_or_nothing, + /*handle*/ Types.HANDLE, + /*info*/ Types.FILE_INFORMATION.out_ptr); + WinFile.MoveFileEx = declareFFI("MoveFileExW", ctypes.winapi_abi, /*return*/ Types.zero_or_nothing, /*sourcePath*/ Types.jschar.in_ptr, /*destPath*/ Types.jschar.in_ptr, /*flags*/ Types.DWORD );
--- a/toolkit/components/osfile/osfile_win_front.jsm +++ b/toolkit/components/osfile/osfile_win_front.jsm @@ -41,16 +41,20 @@ // of the fact that the state is thread-private -- hence that two // |read|/|write| operations cannot take place at the same time -- // and we use the following global mutable values: let gBytesRead = new ctypes.int32_t(-1); let gBytesReadPtr = gBytesRead.address(); let gBytesWritten = new ctypes.int32_t(-1); let gBytesWrittenPtr = gBytesWritten.address(); + // Same story for GetFileInformationByHandle + let gFileInfo = new OS.Shared.Type.FILE_INFORMATION.implementation(); + let gFileInfoPtr = gFileInfo.address(); + /** * Representation of a file. * * 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 @@ -174,17 +178,28 @@ setPosition: function setPosition(pos, whence) { // We are cheating to avoid one unnecessary conversion: // In this implementation, // OS.File.POS_START == OS.Constants.Win.FILE_BEGIN // OS.File.POS_CURRENT == OS.Constants.Win.FILE_CURRENT // OS.File.POS_END == OS.Constants.Win.FILE_END whence = (whence == undefined)?Const.FILE_BEGIN:whence; return throw_on_negative("setPosition", - WinFile.SetFilePointer(this.fd, pos, null, whence)); + 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); } }; /** * A File-related error. * * To obtain a human-readable error message, use method |toString|. * To determine the cause of the error, use the various |becauseX| @@ -316,17 +331,17 @@ * |winAccess| and this replaces argument |mode|. If unspecified, * uses the string |mode|. * * @return {File} A file object. * @throws {OS.File.Error} If the file could not be opened. */ File.open = function Win_open(path, mode, options) { options = options || noOptions; - + mode = mode || noOptions; let share = options.winShare || DEFAULT_SHARE; let security = options.winSecurity || null; let flags = options.winFlags || DEFAULT_FLAGS; let template = options.winTemplate?options.winTemplate._fd:null; let access; let disposition; if ("winAccess" in options && "winDisposition" in options) { access = options.winAccess; @@ -466,27 +481,31 @@ */ let gSystemTime = new OS.Shared.Type.SystemTime.implementation(); let gSystemTimePtr = gSystemTime.address(); /** * Utility function: convert a FILETIME to a JavaScript Date. */ let FILETIME_to_Date = function FILETIME_to_Date(fileTime) { - LOG("fileTimeToDate:", fileTime); if (fileTime == null) { throw new TypeError("Expecting a non-null filetime"); } - LOG("fileTimeToDate normalized:", fileTime); - throw_on_zero("FILETIME_to_Date", WinFile.FileTimeToSystemTime(fileTime.address(), + throw_on_zero("FILETIME_to_Date", + WinFile.FileTimeToSystemTime(fileTime.address(), gSystemTimePtr)); - return new Date(gSystemTime.wYear, gSystemTime.wMonth, - gSystemTime.wDay, gSystemTime.wHour, - gSystemTime.wMinute, gSystemTime.wSecond, - gSystemTime.wMilliSeconds); + // Windows counts hours, minutes, seconds from UTC, + // JS counts from local time, so we need to go through UTC. + let utc = Date.UTC(gSystemTime.wYear, + gSystemTime.wMonth - 1 + /*Windows counts months from 1, JS from 0*/, + gSystemTime.wDay, gSystemTime.wHour, + gSystemTime.wMinute, gSystemTime.wSecond, + gSystemTime.wMilliSeconds); + return new Date(utc); }; /** * Iterate on one directory. * * This iterator will not enter subdirectories. * * @param {string} path The directory upon which to iterate. @@ -610,23 +629,23 @@ } this._parent = parent; }; File.DirectoryIterator.Entry.prototype = { /** * |true| if the entry is a directory, |false| otherwise */ get isDir() { - return this._dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY; + return !!(this._dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY); }, /** * |true| if the entry is a symbolic link, |false| otherwise */ - get isLink() { - return this._dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT; + get isSymLink() { + return !!(this._dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT); }, /** * The name of the entry. * @type {string} */ get name() { return this._name; }, @@ -668,16 +687,139 @@ delete this.path; let path = OS.Win.Path.join(this._parent, this.name); Object.defineProperty(this, "path", {value: path}); return path; } }; /** + * Information on a file. + * + * To obtain the latest information on a file, use |File.stat| + * (for an unopened file) or |File.prototype.stat| (for an + * already opened file). + * + * @constructor + */ + File.Info = function Info(stat) { + this._dwFileAttributes = stat.dwFileAttributes; + this._ftCreationTime = stat.ftCreationTime; + this._ftLastAccessTime = stat.ftLastAccessTime; + this._ftLastWriteTime = stat.ftLastAccessTime; + this._nFileSizeHigh = stat.nFileSizeHigh; + this._nFileSizeLow = stat.nFileSizeLow; + }; + File.Info.prototype = { + /** + * |true| if this file is a directory, |false| otherwise + */ + get isDir() { + return !!(this._dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY); + }, + /** + * |true| if this file is a symbolink link, |false| otherwise + */ + get isSymLink() { + return !!(this._dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT); + }, + /** + * The size of the file, in bytes. + * + * Note that the result may be |NaN| if the size of the file cannot be + * represented in JavaScript. + * + * @type {number} + */ + get size() { + try { + return OS.Shared.projectValue( + ctypes.uint64_t("" + + this._nFileSizeHigh + + this._nFileSizeLow)); + } catch (x) { + return NaN; + } + }, + /** + * The date of creation of this file + * + * @type {Date} + */ + get creationDate() { + delete this.creationDate; + let date = FILETIME_to_Date(this._ftCreationTime); + Object.defineProperty(this, "creationDate", { value: date }); + return date; + }, + /** + * The date of last access to this file. + * + * Note that the definition of last access may depend on the + * underlying operating system and file system. + * + * @type {Date} + */ + get lastAccessDate() { + delete this.lastAccess; + let date = FILETIME_to_Date(this._ftLastAccessTime); + Object.defineProperty(this, "lastAccessDate", { value: date }); + return date; + }, + /** + * Return the date of last modification of this file. + * + * Note that the definition of last access may depend on the + * underlying operating system and file system. + * + * @type {Date} + */ + get lastModificationDate() { + delete this.lastModification; + let date = FILETIME_to_Date(this._ftLastWriteTime); + Object.defineProperty(this, "lastModificationDate", { value: date }); + return date; + } + }; + + /** + * Fetch the information on a file. + * + * Performance note: if you have opened the file already, + * method |File.prototype.stat| is generally much faster + * than method |File.stat|. + * + * Platform-specific note: under Windows, if the file is + * already opened without sharing of the read capability, + * this function will fail. + * + * @return {File.Information} + */ + File.stat = function stat(path) { + let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS); + try { + return file.stat(); + } finally { + file.close(); + } + }; + // All of the following is required to ensure that File.stat + // also works on directories. + const FILE_STAT_MODE = { + read:true + }; + const FILE_STAT_OPTIONS = { + // Directories can be opened neither for reading(!) nor for writing + winAccess: 0, + // Directories can only be opened with backup semantics(!) + winFlags: OS.Constants.Win.FILE_FLAG_BACKUP_SEMANTICS, + winDisposition: OS.Constants.Win.OPEN_EXISTING + }; + + /** * Get/set the current directory. */ Object.defineProperty(File, "curDir", { set: function(path) { throw_on_zero("set curDir", WinFile.SetCurrentDirectory(path)); }, get: function() {
--- a/toolkit/components/osfile/tests/mochi/test_osfile_front.xul +++ b/toolkit/components/osfile/tests/mochi/test_osfile_front.xul @@ -22,25 +22,25 @@ function test() { SimpleTest.waitForExplicitFinish(); ok(true, "test_osfile_front.xul: Chrome worker created"); dump("MAIN: go\n"); worker.onerror = function(error) { error.preventDefault(); ok(false, "error "+error); } worker.onmessage = function(msg) { - ok(true, "MAIN: onmessage "+JSON.stringify(msg)); switch (msg.data.kind) { case "is": return SimpleTest.is(msg.data.a, msg.data.b, msg.data.description); case "isnot": return SimpleTest.isnot(msg.data.a, msg.data.b, msg.data.description); case "ok": return SimpleTest.ok(msg.data.condition, msg.data.description); case "finish": + worker.terminate(); SimpleTest.finish(); return; default: SimpleTest.ok(false, "test_osfile_front.xul: wrong message "+JSON.stringify(msg.data)); return; } }; worker.postMessage(0);
--- a/toolkit/components/osfile/tests/mochi/worker_test_osfile_front.js +++ b/toolkit/components/osfile/tests/mochi/worker_test_osfile_front.js @@ -4,28 +4,29 @@ function log(text) { dump("WORKER "+text+"\n"); } function send(message) { self.postMessage(message); } -self.onmessage = function(msg) { - self.onmessage = function(msg) { - log("ignored message "+JSON.stringify(msg.data)); +self.onmessage = function onmessage_start(msg) { + self.onmessage = function onmessage_ignored(msg) { + log("ignored message " + JSON.stringify(msg.data)); }; try { test_init(); test_open_existing_file(); test_open_non_existing_file(); test_copy_existing_file(); test_read_write_file(); test_move_file(); test_iter_dir(); + test_info(); } catch (x) { log("Catching error: " + x); log("Stack: " + x.stack); log("Source: " + x.toSource()); ok(false, x.toString() + "\n" + x.stack); } finish(); }; @@ -138,17 +139,17 @@ function test_read_write_file() let dest = OS.File.open(tmp_file_name, {write: true, trunc:true}); let buf = new ArrayBuffer(4096); for (let bytesAvailable = source.read(buf, 4096); bytesAvailable != 0; bytesAvailable = source.read(buf, 4096)) { let bytesWritten = dest.write(buf, bytesAvailable); if (bytesWritten != bytesAvailable) { - eq(bytesWritten, bytesAvailable, "test_read_write_file: writing all bytes"); + is(bytesWritten, bytesAvailable, "test_read_write_file: writing all bytes"); } } ok(true, "test_read_write_file: copy complete"); source.close(); dest.close(); compare_files("test_read_write_file", src_file_name, tmp_file_name); @@ -215,17 +216,17 @@ function test_iter_dir() let encountered_tmp_file = false; for (let entry in iterator) { // Checking that |name| can be decoded properly ok(true, "test_iter_dir: encountering entry " + entry.name); if (entry.name == tmp_file_name) { encountered_tmp_file = true; isnot(entry.isDir, "test_iter_dir: The temporary file is not a directory"); - isnot(entry.isLink, "test_iter_dir: The temporary file is not a link"); + isnot(entry.isSymLink, "test_iter_dir: The temporary file is not a link"); } let file; let success = true; try { file = OS.File.open(entry.path); } catch (x) { if (x.becauseNoSuchFile) { @@ -254,8 +255,101 @@ function test_iter_dir() } ok(encountered_tmp_file, "test_iter_dir: We have found the temporary file"); ok(true, "test_iter_dir: Cleaning up"); iterator.close(); ok(true, "test_iter_dir: Complete"); } + +function test_info() { + ok(true, "test_info: Starting"); + + let filename = "test_info.tmp"; + let size = 261;// An arbitrary file length + let start = new Date(); + + // Cleanup any leftover from previous tests + try { + OS.File.remove(filename); + ok(true, "test_info: Cleaned up previous garbage"); + } catch (x) { + if (!x.becauseNoSuchFile) { + throw x; + } + ok(true, "test_info: No previous garbage"); + } + + let file = OS.File.open(filename, {trunc: true}); + let buf = new ArrayBuffer(size); + file.write(buf, size); + file.close(); + + // Test OS.File.stat on new file + let info = OS.File.stat(filename); + ok(!!info, "test_info: info acquired"); + ok(!info.isDir, "test_info: file is not a directory"); + is(info.isSymLink, false, "test_info: file is not a link"); + is(info.size.toString(), size, "test_info: correct size"); + + let stop = new Date(); + + // We round down/up by 1s as file system precision is lower than Date precision + let startMs = start.getTime() - 1000; + let stopMs = stop.getTime() + 1000; + + let birth = info.creationDate; + ok(birth.getTime() <= stopMs, + "test_info: file was created before now - " + stop + ", " + birth); + // Note: Previous versions of this test checked whether the file has + // been created after the start of the test. Unfortunately, this sometimes + // failed under Windows, in specific circumstances: if the file has been + // removed at the start of the test and recreated immediately, the Windows + // file system detects this and decides that the file was actually truncated + // rather than recreated, hence that it should keep its previous creation date. + // Debugging hilarity ensues. + + let change = info.lastModificationDate; + ok(change.getTime() >= startMs + && change.getTime() <= stopMs, + "test_info: file has changed between the start of the test and now - " + start + ", " + stop + ", " + change); + + // Test OS.File.prototype.stat on new file + file = OS.File.open(filename); + try { + info = file.stat(); + } finally { + file.close(); + } + + ok(!!info, "test_info: info acquired 2"); + ok(!info.isDir, "test_info: file is not a directory 2"); + ok(!info.isSymLink, "test_info: file is not a link 2"); + is(info.size.toString(), size, "test_info: correct size 2"); + + stop = new Date(); + + // We round down/up by 1s as file system precision is lower than Date precision + startMs = start.getTime() - 1000; + stopMs = stop.getTime() + 1000; + + birth = info.creationDate; + ok(birth.getTime() <= stopMs, + "test_info: file 2 was created between the start of the test and now - " + start + ", " + stop + ", " + birth); + + let access = info.lastModificationDate; + ok(access.getTime() >= startMs + && access.getTime() <= stopMs, + "test_info: file 2 was accessed between the start of the test and now - " + start + ", " + stop + ", " + access); + + change = info.lastModificationDate; + ok(change.getTime() >= startMs + && change.getTime() <= stopMs, + "test_info: file 2 has changed between the start of the test and now - " + start + ", " + stop + ", " + change); + + // Test OS.File.stat on directory + info = OS.File.stat(OS.File.curDir); + ok(!!info, "test_info: info on directory acquired"); + ok(info.isDir, "test_info: directory is a directory"); + + ok(true, "test_info: Complete"); +}