forked from mirrors/gecko-dev
		
	Bug 1776480 - Remove OS.File et al. r=Gijs,webidl,smaug
This patch removes the vast majority of OS.File and support code. A few things remain: - The nsIOSFileConstantsService still exists, but the path related constants (OS.Constants.Path.*) are no longer added to the OS object. The plan is to replace this with a proper service e.g. Services.osConstants or similar) in bug 1786885. - There is still support for OS.File errors in ErrorSanitizer, which will be removed in bug 1775167. - The OS.File to IOUtils migration guide will be rewritten as general IOUtils documentation in bug 1830097. - dom/base/Document.cpp has a workaround for not loading osfile.jsm at startup, which may want to be reconsidered in bug 1830100. So long, and thanks for all the I/O. Differential Revision: https://phabricator.services.mozilla.com/D176543
This commit is contained in:
		
							parent
							
								
									c417b90099
								
							
						
					
					
						commit
						a645de723e
					
				
					 104 changed files with 3 additions and 17821 deletions
				
			
		|  | @ -263,7 +263,6 @@ const extraChromeTestPaths = [ | |||
|   "layout/svg/tests/", | ||||
|   "layout/xul/test/", | ||||
|   "toolkit/components/aboutmemory/tests/", | ||||
|   "toolkit/components/osfile/tests/mochi/", | ||||
|   "toolkit/components/printing/tests/", | ||||
|   "toolkit/components/url-classifier/tests/mochitest/", | ||||
|   "toolkit/components/viewsource/test/", | ||||
|  |  | |||
|  | @ -310,7 +310,6 @@ module.exports = { | |||
|         "toolkit/components/narrate/Narrator.jsm", | ||||
|         "toolkit/components/nimbus/**", | ||||
|         "toolkit/components/normandy/**", | ||||
|         "toolkit/components/osfile/**", | ||||
|         "toolkit/components/passwordmgr/**", | ||||
|         "toolkit/components/pdfjs/**", | ||||
|         "toolkit/components/pictureinpicture/**", | ||||
|  | @ -613,13 +612,6 @@ module.exports = { | |||
|         "devtools/**", | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       // Turn off the osfile rule for osfile.
 | ||||
|       files: ["toolkit/components/osfile/**"], | ||||
|       rules: { | ||||
|         "mozilla/reject-osfile": "off", | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       // Exempt files with these paths since they have to use http for full coverage
 | ||||
|       files: httpTestingPaths.map(path => `${path}**`), | ||||
|  |  | |||
|  | @ -102,7 +102,6 @@ const startupPhases = { | |||
|       modules: new Set([ | ||||
|         "resource://gre/modules/AsyncPrefs.sys.mjs", | ||||
|         "resource://gre/modules/LoginManagerContextMenu.sys.mjs", | ||||
|         "resource://gre/modules/osfile.jsm", | ||||
|         "resource://pdf.js/PdfStreamConverter.sys.mjs", | ||||
|       ]), | ||||
|     }, | ||||
|  |  | |||
|  | @ -71,12 +71,6 @@ var gExceptionPaths = [ | |||
| 
 | ||||
|   // Localization file added programatically in featureCallout.jsm
 | ||||
|   "resource://app/localization/en-US/browser/featureCallout.ftl", | ||||
| 
 | ||||
|   // Will be removed in bug 1737308
 | ||||
|   "resource://gre/modules/lz4.js", | ||||
|   "resource://gre/modules/lz4_internal.js", | ||||
|   "resource://gre/modules/osfile.jsm", | ||||
|   "resource://gre/modules/osfile/", | ||||
| ]; | ||||
| 
 | ||||
| // These are not part of the omni.ja file, so we find them only when running
 | ||||
|  |  | |||
|  | @ -58,7 +58,6 @@ The plugin implements the following rules: | |||
|    eslint-plugin-mozilla/reject-lazy-imports-into-globals | ||||
|    eslint-plugin-mozilla/reject-mixing-eager-and-lazy | ||||
|    eslint-plugin-mozilla/reject-multiple-getters-calls | ||||
|    eslint-plugin-mozilla/reject-osfile | ||||
|    eslint-plugin-mozilla/reject-relative-requires | ||||
|    eslint-plugin-mozilla/reject-requires-await | ||||
|    eslint-plugin-mozilla/reject-scriptableunicodeconverter | ||||
|  |  | |||
|  | @ -1,13 +0,0 @@ | |||
| reject-osfile | ||||
| ============= | ||||
| 
 | ||||
| Rejects calls into ``OS.File`` and ``OS.Path``. This is configured as a warning. | ||||
| You should use |IOUtils|_ and |PathUtils|_ respectively for new code.  If | ||||
| modifying old code, please consider swapping it in if possible; if this is | ||||
| tricky please ensure a bug is on file. | ||||
| 
 | ||||
| .. |IOUtils| replace:: ``IOUtils`` | ||||
| .. _IOUtils: https://searchfox.org/mozilla-central/source/dom/chrome-webidl/IOUtils.webidl | ||||
| 
 | ||||
| .. |PathUtils| replace:: ``PathUtils`` | ||||
| .. _PathUtils: https://searchfox.org/mozilla-central/source/dom/chrome-webidl/PathUtils.webidl | ||||
|  | @ -835,97 +835,12 @@ bool OSFileConstantsService::DefineOSFileConstants( | |||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   nsCOMPtr<nsIXULRuntime> runtime = | ||||
|       do_GetService(XULRUNTIME_SERVICE_CONTRACTID); | ||||
|   if (runtime) { | ||||
|     nsAutoCString os; | ||||
|     DebugOnly<nsresult> rv = runtime->GetOS(os); | ||||
|     MOZ_ASSERT(NS_SUCCEEDED(rv)); | ||||
| 
 | ||||
|     JSString* strVersion = JS_NewStringCopyZ(aCx, os.get()); | ||||
|     if (!strVersion) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     JS::Rooted<JS::Value> valVersion(aCx, JS::StringValue(strVersion)); | ||||
|     if (!JS_SetProperty(aCx, objSys, "Name", valVersion)) { | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| #if defined(DEBUG) | ||||
|   JS::Rooted<JS::Value> valDebug(aCx, JS::TrueValue()); | ||||
|   if (!JS_SetProperty(aCx, objSys, "DEBUG", valDebug)) { | ||||
|     return false; | ||||
|   } | ||||
| #endif | ||||
| 
 | ||||
| #if defined(HAVE_64BIT_BUILD) | ||||
|   JS::Rooted<JS::Value> valBits(aCx, JS::Int32Value(64)); | ||||
| #else | ||||
|   JS::Rooted<JS::Value> valBits(aCx, JS::Int32Value(32)); | ||||
| #endif  // defined (HAVE_64BIT_BUILD)
 | ||||
|   if (!JS_SetProperty(aCx, objSys, "bits", valBits)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   if (!JS_DefineProperty( | ||||
|           aCx, objSys, "umask", mUserUmask, | ||||
|           JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   // Build OS.Constants.Path
 | ||||
| 
 | ||||
|   JS::Rooted<JSObject*> objPath(aCx); | ||||
|   if (!(objPath = GetOrCreateObjectProperty(aCx, objConstants, "Path"))) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   // Locate libxul
 | ||||
|   // Note that we don't actually provide the full path, only the name of the
 | ||||
|   // library, which is sufficient to link to the library using js-ctypes.
 | ||||
| 
 | ||||
| #if defined(XP_MACOSX) | ||||
|   // Under MacOS X, for some reason, libxul is called simply "XUL",
 | ||||
|   // and we need to provide the full path.
 | ||||
|   nsAutoString libxul; | ||||
|   libxul.Append(mPaths->libDir); | ||||
|   libxul.AppendLiteral("/XUL"); | ||||
| #else | ||||
|   // On other platforms, libxul is a library "xul" with regular
 | ||||
|   // library prefix/suffix.
 | ||||
|   nsAutoString libxul; | ||||
|   libxul.AppendLiteral(MOZ_DLL_PREFIX); | ||||
|   libxul.AppendLiteral("xul"); | ||||
|   libxul.AppendLiteral(MOZ_DLL_SUFFIX); | ||||
| #endif  // defined(XP_MACOSX)
 | ||||
| 
 | ||||
|   if (!SetStringProperty(aCx, objPath, "libxul", libxul)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   if (!SetStringProperty(aCx, objPath, "libDir", mPaths->libDir)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   if (!SetStringProperty(aCx, objPath, "tmpDir", mPaths->tmpDir)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   // Configure profileDir only if it is available at this stage
 | ||||
|   if (!mPaths->profileDir.IsVoid() && | ||||
|       !SetStringProperty(aCx, objPath, "profileDir", mPaths->profileDir)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   // Configure localProfileDir only if it is available at this stage
 | ||||
|   if (!mPaths->localProfileDir.IsVoid() && | ||||
|       !SetStringProperty(aCx, objPath, "localProfileDir", | ||||
|                          mPaths->localProfileDir)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,9 +8,6 @@ | |||
| with Files("**"): | ||||
|     BUG_COMPONENT = ("Core", "DOM: Core & HTML") | ||||
| 
 | ||||
| with Files("*OSFile*"): | ||||
|     BUG_COMPONENT = ("Toolkit", "OS.File") | ||||
| 
 | ||||
| with Files("*ocationProvider*"): | ||||
|     BUG_COMPONENT = ("Core", "DOM: Geolocation") | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| [DEFAULT] | ||||
| support-files = | ||||
|   worker_constants.js | ||||
| 
 | ||||
| [test_constants.xhtml] | ||||
| support-files = | ||||
|   worker_constants.js | ||||
| [test_pathutils.html] | ||||
| [test_pathutils_worker.xhtml] | ||||
| support-files = | ||||
|  |  | |||
|  | @ -15,11 +15,6 @@ | |||
|     const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm"); | ||||
|     const { ObjectUtils } = ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm"); | ||||
| 
 | ||||
|     // This is presently only used to test compatability between OS.File and | ||||
|     // IOUtils when it comes to writing compressed files. The import and the | ||||
|     // test `test_lz4_osfile_compat` can be removed with OS.File is removed. | ||||
|     const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
|     add_task(async function test_read_failure() { | ||||
|       const doesNotExist = PathUtils.join(PathUtils.tempDir, "does_not_exist.tmp"); | ||||
|       await Assert.rejects( | ||||
|  | @ -340,31 +335,6 @@ | |||
|       await cleanup(tmpFileName); | ||||
|     }); | ||||
| 
 | ||||
|     add_task(async function test_lz4_osfile_compat() { | ||||
|       const osfileTmpFile = PathUtils.join(PathUtils.tempDir, "test_ioutils_lz4_compat_osfile.tmp"); | ||||
|       const ioutilsTmpFile = PathUtils.join(PathUtils.tempDir, "test_ioutils_lz4_compat_ioutils.tmp"); | ||||
| 
 | ||||
|       info("Test OS.File and IOUtils write the same file with LZ4 compression enabled") | ||||
|       const repeatedBytes = Uint8Array.of(...new Array(50).fill(1)); | ||||
|       let expectedBytes = 23; | ||||
|       let ioutilsBytes = await IOUtils.write(ioutilsTmpFile, repeatedBytes, { compress: true }); | ||||
|       let osfileBytes = await OS.File.writeAtomic(osfileTmpFile, repeatedBytes, { compression: "lz4" }); | ||||
|       is(ioutilsBytes, expectedBytes, "IOUtils writes the expected number of bytes for compression"); | ||||
|       is(osfileBytes, ioutilsBytes, "OS.File and IOUtils write the same number of bytes for LZ4 compression"); | ||||
| 
 | ||||
|       info("Test OS.File can read a file compressed by IOUtils"); | ||||
|       const osfileReadBytes = await OS.File.read(ioutilsTmpFile, { compression: "lz4" }); | ||||
|       ok(osfileReadBytes.every(byte => byte === 1), "OS.File can read a file compressed by IOUtils"); | ||||
|       is(osfileReadBytes.length, 50, "OS.File reads the right number of bytes from a file compressed by IOUtils") | ||||
| 
 | ||||
|       info("Test IOUtils can read a file compressed by OS.File"); | ||||
|       const ioutilsReadBytes = await IOUtils.read(osfileTmpFile, { decompress: true }); | ||||
|       ok(ioutilsReadBytes.every(byte => byte === 1), "IOUtils can read a file compressed by OS.File"); | ||||
|       is(ioutilsReadBytes.length, 50, "IOUtils reads the right number of bytes from a file compressed by OS.File") | ||||
| 
 | ||||
|       await cleanup(osfileTmpFile, ioutilsTmpFile); | ||||
|     }); | ||||
| 
 | ||||
|     add_task(async function test_lz4_bad_call() { | ||||
|       const tmpFileName = PathUtils.join(PathUtils.tempDir, "test_ioutils_lz4_bad_call.tmp"); | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,11 +15,6 @@ | |||
|     const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm"); | ||||
|     const { ObjectUtils } = ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm"); | ||||
| 
 | ||||
|     // This is presently only used to test compatability between OS.File and | ||||
|     // IOUtils when it comes to writing compressed files. The import and the | ||||
|     // test `test_lz4_osfile_compat` can be removed with OS.File is removed. | ||||
|     const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
|     // This is an impossible sequence of bytes in an UTF-8 encoded file. | ||||
|     // See section 3.5.3 of this text: | ||||
|     // https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt | ||||
|  | @ -290,29 +285,6 @@ | |||
|       await cleanup(tmpFileName); | ||||
|     }); | ||||
| 
 | ||||
|     add_task(async function test_utf8_lz4_osfile_compat() { | ||||
|       const osfileTmpFile = PathUtils.join(PathUtils.tempDir, "test_ioutils_utf8_lz4_compat_osfile.tmp"); | ||||
|       const ioutilsTmpFile = PathUtils.join(PathUtils.tempDir, "test_ioutils_utf8_lz4_compat_ioutils.tmp"); | ||||
| 
 | ||||
|       info("Test OS.File and IOUtils write the same UTF-8 file with LZ4 compression enabled") | ||||
|       const emoji = "☕️ ⚧️ 😀 🖖🏿 🤠 🏳️🌈 🥠 🏴☠️ 🪐"; | ||||
|       let expectedBytes = 83; | ||||
|       let ioutilsBytes = await IOUtils.writeUTF8(ioutilsTmpFile, emoji, { compress: true }); | ||||
|       let osfileBytes = await OS.File.writeAtomic(osfileTmpFile, emoji, { compression: "lz4" }); | ||||
|       is(ioutilsBytes, expectedBytes, "IOUtils writes the expected number of bytes for compression"); | ||||
|       is(osfileBytes, ioutilsBytes, "OS.File and IOUtils write the same number of bytes for LZ4 compression"); | ||||
| 
 | ||||
|       info("Test OS.File can read an UTF-8 file compressed by IOUtils"); | ||||
|       const osfileReadStr = await OS.File.read(ioutilsTmpFile, { compression: "lz4", encoding: "utf-8" }); | ||||
|       is(osfileReadStr, emoji, "OS.File can read an UTF-8 file compressed by IOUtils") | ||||
| 
 | ||||
|       info("Test IOUtils can read an UTF-8 file compressed by OS.File"); | ||||
|       const ioutilsReadString = await IOUtils.readUTF8(ioutilsTmpFile, { decompress: true }); | ||||
|       is(ioutilsReadString, emoji, "IOUtils can read an UTF-8 file compressed by OS.File"); | ||||
| 
 | ||||
|       await cleanup(osfileTmpFile, ioutilsTmpFile); | ||||
|     }); | ||||
| 
 | ||||
|     add_task(async function test_utf8_lz4_bad_call() { | ||||
|       const tmpFileName = PathUtils.join(PathUtils.tempDir, "test_ioutils_utf8_lz4_bad_call.tmp"); | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,21 +16,6 @@ | |||
| 
 | ||||
| let worker; | ||||
| 
 | ||||
| function test_xul() { | ||||
|   let lib; | ||||
|   isnot(null, OS.Constants.Path.libxul, "libxulpath is defined"); | ||||
|   try { | ||||
|     lib = ctypes.open(OS.Constants.Path.libxul); | ||||
|     lib.declare("DumpJSStack", ctypes.default_abi, ctypes.void_t); | ||||
|   } catch (x) { | ||||
|     ok(false, "Could not open libxul " + x); | ||||
|   } | ||||
|   if (lib) { | ||||
|     lib.close(); | ||||
|   } | ||||
|   ok(true, "test_xul: opened libxul successfully"); | ||||
| } | ||||
| 
 | ||||
| // Test that OS.Constants.libc is defined | ||||
| function test_libc() { | ||||
|   isnot(null, OS.Constants.libc, "OS.Constants.libc is defined"); | ||||
|  | @ -58,11 +43,6 @@ function test_Win() { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| // Test that OS.Constants.Sys.DEBUG is set properly on main thread | ||||
| function test_debugBuildMainThread(isDebugBuild) { | ||||
|   is(isDebugBuild, !!OS.Constants.Sys.DEBUG, "OS.Constants.Sys.DEBUG is set properly on main thread"); | ||||
| } | ||||
| 
 | ||||
| // Test that OS.Constants.Sys.umask is set properly on main thread | ||||
| function test_umaskMainThread(umask) { | ||||
|   is(umask, OS.Constants.Sys.umask, | ||||
|  | @ -79,13 +59,9 @@ function test() { | |||
|     getService(Ci.nsIOSFileConstantsService). | ||||
|     init(); | ||||
|   ({ctypes} = ChromeUtils.import("resource://gre/modules/ctypes.jsm")); | ||||
|   test_xul(); | ||||
|   test_libc(); | ||||
|   test_Win(); | ||||
| 
 | ||||
|   let isDebugBuild = Cc["@mozilla.org/xpcom/debug;1"] | ||||
|                             .getService(Ci.nsIDebug2).isDebugBuild; | ||||
|   test_debugBuildMainThread(isDebugBuild); | ||||
| 
 | ||||
|   let umask = Cc["@mozilla.org/system-info;1"]. | ||||
|     getService(Ci.nsIPropertyBag2). | ||||
|  | @ -123,7 +99,6 @@ function test() { | |||
|   // pass expected values that are unavailable off-main-thread | ||||
|   // to the worker | ||||
|   worker.postMessage({ | ||||
|     isDebugBuild: isDebugBuild, | ||||
|     umask: umask | ||||
|   }); | ||||
|   ok(true, "test_constants.xhtml: Test in progress"); | ||||
|  |  | |||
|  | @ -15,13 +15,9 @@ self.onmessage = function(msg) { | |||
|   self.onmessage = function(msgInner) { | ||||
|     log("ignored message " + JSON.stringify(msgInner.data)); | ||||
|   }; | ||||
|   let { isDebugBuild, umask } = msg.data; | ||||
|   let { umask } = msg.data; | ||||
|   try { | ||||
|     test_name(); | ||||
|     test_xul(); | ||||
|     test_debugBuildWorkerThread(isDebugBuild); | ||||
|     test_umaskWorkerThread(umask); | ||||
|     test_bits(); | ||||
|   } catch (x) { | ||||
|     log("Catching error: " + x); | ||||
|     log("Stack: " + x.stack); | ||||
|  | @ -45,20 +41,6 @@ function isnot(a, b, description) { | |||
|   send({ kind: "isnot", a, b, description }); | ||||
| } | ||||
| 
 | ||||
| // Test that OS.Constants.Sys.Name is defined
 | ||||
| function test_name() { | ||||
|   isnot(null, OS.Constants.Sys.Name, "OS.Constants.Sys.Name is defined"); | ||||
| } | ||||
| 
 | ||||
| // Test that OS.Constants.Sys.DEBUG is set properly in ChromeWorker thread
 | ||||
| function test_debugBuildWorkerThread(isDebugBuild) { | ||||
|   is( | ||||
|     isDebugBuild, | ||||
|     !!OS.Constants.Sys.DEBUG, | ||||
|     "OS.Constants.Sys.DEBUG is set properly on worker thread" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| // Test that OS.Constants.Sys.umask is set properly in ChromeWorker thread
 | ||||
| function test_umaskWorkerThread(umask) { | ||||
|   is( | ||||
|  | @ -68,28 +50,3 @@ function test_umaskWorkerThread(umask) { | |||
|       ("0000" + umask.toString(8)).slice(-4) | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| // Test that OS.Constants.Path.libxul lets us open libxul
 | ||||
| function test_xul() { | ||||
|   let lib; | ||||
|   isnot(null, OS.Constants.Path.libxul, "libxul is defined"); | ||||
|   try { | ||||
|     lib = ctypes.open(OS.Constants.Path.libxul); | ||||
|     lib.declare("DumpJSStack", ctypes.default_abi, ctypes.void_t); | ||||
|   } catch (x) { | ||||
|     ok(false, "test_xul: Could not open libxul: " + x); | ||||
|   } | ||||
|   if (lib) { | ||||
|     lib.close(); | ||||
|   } | ||||
|   ok(true, "test_xul: opened libxul successfully"); | ||||
| } | ||||
| 
 | ||||
| // Check if the value of OS.Constants.Sys.bits is 32 or 64
 | ||||
| function test_bits() { | ||||
|   is( | ||||
|     OS.Constants.Sys.bits, | ||||
|     ctypes.int.ptr.size * 8, | ||||
|     "OS.Constants.Sys.bits is either 32 or 64" | ||||
|   ); | ||||
| } | ||||
|  |  | |||
|  | @ -1,60 +0,0 @@ | |||
| /* 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 obtaone at http://mozilla.org/MPL/2.0/. */ | ||||
| 
 | ||||
| /** | ||||
|  * Options for nsINativeOSFileInternals::Read | ||||
|  */ | ||||
| [GenerateInit] | ||||
| dictionary NativeOSFileReadOptions | ||||
| { | ||||
|   /** | ||||
|    * If specified, convert the raw bytes to a String | ||||
|    * with the specified encoding. Otherwise, return | ||||
|    * the raw bytes as a TypedArray. | ||||
|    */ | ||||
|   DOMString? encoding; | ||||
| 
 | ||||
|   /** | ||||
|    * If specified, limit the number of bytes to read. | ||||
|    */ | ||||
|   unsigned long long? bytes; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Options for nsINativeOSFileInternals::WriteAtomic | ||||
|  */ | ||||
| [GenerateInit] | ||||
| dictionary NativeOSFileWriteAtomicOptions | ||||
| { | ||||
|   /** | ||||
|    * If specified, specify the number of bytes to write. | ||||
|    * NOTE: This takes (and should take) a uint64 here but the actual | ||||
|    * value is limited to int32. This needs to be fixed, see Bug 1063635. | ||||
|    */ | ||||
|   unsigned long long? bytes; | ||||
| 
 | ||||
|   /** | ||||
|    * If specified, write all data to a temporary file in the | ||||
|    * |tmpPath|. Else, write to the given path directly. | ||||
|    */ | ||||
|   DOMString? tmpPath = null; | ||||
| 
 | ||||
|   /** | ||||
|    * If specified and true, a failure will occur if the file | ||||
|    * already exists in the given path. | ||||
|    */ | ||||
|   boolean noOverwrite = false; | ||||
| 
 | ||||
|   /** | ||||
|    * If specified and true, this will sync any buffered data | ||||
|    * for the file to disk. This might be slower, but safer. | ||||
|    */ | ||||
|   boolean flush = false; | ||||
| 
 | ||||
|   /** | ||||
|    * If specified, this will backup the destination file as | ||||
|    * specified. | ||||
|    */ | ||||
|   DOMString? backupTo = null; | ||||
| }; | ||||
|  | @ -229,9 +229,6 @@ with Files("Mouse*"): | |||
| with Files("MutationEvent.webidl"): | ||||
|     BUG_COMPONENT = ("Core", "DOM: Events") | ||||
| 
 | ||||
| with Files("NativeOSFileInternals.webidl"): | ||||
|     BUG_COMPONENT = ("Toolkit", "OS.File") | ||||
| 
 | ||||
| with Files("NavigationPreloadManager.webidl"): | ||||
|     BUG_COMPONENT = ("Core", "DOM: Service Workers") | ||||
| 
 | ||||
|  | @ -746,7 +743,6 @@ WEBIDL_FILES = [ | |||
|     "MutationEvent.webidl", | ||||
|     "MutationObserver.webidl", | ||||
|     "NamedNodeMap.webidl", | ||||
|     "NativeOSFileInternals.webidl", | ||||
|     "NavigationPreloadManager.webidl", | ||||
|     "Navigator.webidl", | ||||
|     "NetErrorInfo.webidl", | ||||
|  |  | |||
|  | @ -28,7 +28,6 @@ const JSMs = [ | |||
|   "resource://gre/modules/PrivateBrowsingUtils.jsm", | ||||
|   "resource://gre/modules/Timer.jsm", | ||||
|   "resource://gre/modules/XPCOMUtils.jsm", | ||||
|   "resource://gre/modules/osfile.jsm", | ||||
|   "resource://gre/modules/addons/XPIDatabase.jsm", | ||||
|   "resource://gre/modules/addons/XPIProvider.jsm", | ||||
|   "resource://gre/modules/addons/XPIInstall.jsm", | ||||
|  |  | |||
|  | @ -624,9 +624,6 @@ pref("gfx.use_text_smoothing_setting", false); | |||
| // Number of characters to consider emphasizing for rich autocomplete results
 | ||||
| pref("toolkit.autocomplete.richBoundaryCutoff", 200); | ||||
| 
 | ||||
| // Variable controlling logging for osfile.
 | ||||
| pref("toolkit.osfile.log", false); | ||||
| 
 | ||||
| pref("toolkit.scrollbox.smoothScroll", true); | ||||
| pref("toolkit.scrollbox.scrollIncrement", 20); | ||||
| pref("toolkit.scrollbox.clickToScroll.scrollDelay", 150); | ||||
|  |  | |||
|  | @ -1564,7 +1564,6 @@ | |||
|     "toolkit/components/extensions/test/xpcshell/xpcshell.ini": 19.12, | ||||
|     "toolkit/components/featuregates/test/unit/xpcshell.ini": 1.71, | ||||
|     "toolkit/components/mozintl/test/xpcshell.ini": 1.82, | ||||
|     "toolkit/components/osfile/tests/xpcshell/xpcshell.ini": 27.1, | ||||
|     "toolkit/components/telemetry/tests/unit/xpcshell.ini": 42.36, | ||||
|     "toolkit/components/timermanager/tests/unit/xpcshell.ini": 9.07, | ||||
|     "toolkit/components/utils/test/unit/xpcshell.ini": 2.22, | ||||
|  |  | |||
|  | @ -386,7 +386,6 @@ | |||
|     "toolkit/components/certviewer/tests/chrome/chrome.ini": 2.78, | ||||
|     "toolkit/components/ctypes/tests/chrome/chrome.ini": 1.4, | ||||
|     "toolkit/components/extensions/test/mochitest/chrome.ini": 11.29, | ||||
|     "toolkit/components/osfile/tests/mochi/chrome.ini": 9.12, | ||||
|     "toolkit/components/places/tests/chrome/chrome.ini": 1.87, | ||||
|     "toolkit/components/prompts/test/chrome.ini": 106.31, | ||||
|     "toolkit/components/resistfingerprinting/tests/chrome.ini": 3.99, | ||||
|  |  | |||
|  | @ -388,7 +388,6 @@ | |||
|     "toolkit/components/certviewer/tests/chrome/chrome.ini": 2.35, | ||||
|     "toolkit/components/ctypes/tests/chrome/chrome.ini": 1.38, | ||||
|     "toolkit/components/extensions/test/mochitest/chrome.ini": 8.63, | ||||
|     "toolkit/components/osfile/tests/mochi/chrome.ini": 7.73, | ||||
|     "toolkit/components/places/tests/chrome/chrome.ini": 1.89, | ||||
|     "toolkit/components/prompts/test/chrome.ini": 41.2, | ||||
|     "toolkit/components/resistfingerprinting/tests/chrome.ini": 3.59, | ||||
|  |  | |||
|  | @ -74,12 +74,6 @@ Classes = [ | |||
|         'headers': ['/toolkit/components/reputationservice/LoginReputation.h'], | ||||
|         'constructor': 'mozilla::LoginReputationService::GetSingleton', | ||||
|     }, | ||||
|     { | ||||
|         'cid': '{63a69303-8a64-45a9-848c-d4e2792794e6}', | ||||
|         'contract_ids': ['@mozilla.org/toolkit/osfile/native-internals;1'], | ||||
|         'type': 'mozilla::NativeOSFileInternalsService', | ||||
|         'headers': ['mozilla/NativeOSFileInternals.h'], | ||||
|     }, | ||||
|     { | ||||
|         'name': 'Alerts', | ||||
|         'cid': '{a0ccaaf8-09da-44d8-b250-9ac3e93c8117}', | ||||
|  |  | |||
|  | @ -1,66 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| #include "mozilla/Compression.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * LZ4 is a very fast byte-wise compression algorithm. | ||||
|  * | ||||
|  * Compared to Google's Snappy it is faster to compress and decompress and | ||||
|  * generally produces output of about the same size. | ||||
|  * | ||||
|  * Compared to zlib it compresses at about 10x the speed, decompresses at about | ||||
|  * 4x the speed and produces output of about 1.5x the size. | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| using namespace mozilla::Compression; | ||||
| 
 | ||||
| /**
 | ||||
|  * Compresses 'inputSize' bytes from 'source' into 'dest'. | ||||
|  * Destination buffer must be already allocated, | ||||
|  * and must be sized to handle worst cases situations (input data not | ||||
|  * compressible) Worst case size evaluation is provided by function | ||||
|  * LZ4_compressBound() | ||||
|  * | ||||
|  * @param inputSize is the input size. Max supported value is ~1.9GB | ||||
|  * @param return the number of bytes written in buffer dest | ||||
|  */ | ||||
| extern "C" MOZ_EXPORT size_t workerlz4_compress(const char* source, | ||||
|                                                 size_t inputSize, char* dest) { | ||||
|   return LZ4::compress(source, inputSize, dest); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * If the source stream is malformed, the function will stop decoding | ||||
|  * and return a negative result, indicating the byte position of the | ||||
|  * faulty instruction | ||||
|  * | ||||
|  * This function never writes outside of provided buffers, and never | ||||
|  * modifies input buffer. | ||||
|  * | ||||
|  * note : destination buffer must be already allocated. | ||||
|  *        its size must be a minimum of 'outputSize' bytes. | ||||
|  * @param outputSize is the output size, therefore the original size | ||||
|  * @return true/false | ||||
|  */ | ||||
| extern "C" MOZ_EXPORT int workerlz4_decompress(const char* source, | ||||
|                                                size_t inputSize, char* dest, | ||||
|                                                size_t maxOutputSize, | ||||
|                                                size_t* bytesOutput) { | ||||
|   return LZ4::decompress(source, inputSize, dest, maxOutputSize, bytesOutput); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|   Provides the maximum size that LZ4 may output in a "worst case" | ||||
|   scenario (input data not compressible) primarily useful for memory | ||||
|   allocation of output buffer. | ||||
|   note : this function is limited by "int" range (2^31-1) | ||||
| 
 | ||||
|   @param inputSize is the input size. Max supported value is ~1.9GB | ||||
|   @return maximum output size in a "worst case" scenario | ||||
| */ | ||||
| extern "C" MOZ_EXPORT size_t workerlz4_maxCompressedSize(size_t inputSize) { | ||||
|   return LZ4::maxCompressedSize(inputSize); | ||||
| } | ||||
|  | @ -1,188 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var SharedAll; | ||||
| if (typeof Components != "undefined") { | ||||
|   SharedAll = ChromeUtils.import( | ||||
|     "resource://gre/modules/osfile/osfile_shared_allthreads.jsm" | ||||
|   ); | ||||
|   var { Primitives } = ChromeUtils.import( | ||||
|     "resource://gre/modules/lz4_internal.js" | ||||
|   ); | ||||
|   var { ctypes } = ChromeUtils.importESModule( | ||||
|     "resource://gre/modules/ctypes.sys.mjs" | ||||
|   ); | ||||
| 
 | ||||
|   this.EXPORTED_SYMBOLS = ["Lz4"]; | ||||
|   this.exports = {}; | ||||
| } else if (typeof module != "undefined" && typeof require != "undefined") { | ||||
|   /* eslint-env commonjs */ | ||||
|   SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); | ||||
|   Primitives = require("resource://gre/modules/lz4_internal.js"); | ||||
|   ctypes = self.ctypes; | ||||
| } else { | ||||
|   throw new Error( | ||||
|     "Please load this module with Component.utils.import or with require()" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| const MAGIC_NUMBER = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]); // "mozLz40\0"
 | ||||
| 
 | ||||
| const BYTES_IN_SIZE_HEADER = ctypes.uint32_t.size; | ||||
| 
 | ||||
| const HEADER_SIZE = MAGIC_NUMBER.byteLength + BYTES_IN_SIZE_HEADER; | ||||
| 
 | ||||
| /** | ||||
|  * An error during (de)compression | ||||
|  * | ||||
|  * @param {string} operation The name of the operation ("compress", "decompress") | ||||
|  * @param {string} reason A reason to be used when matching errors. Must start | ||||
|  * with "because", e.g. "becauseInvalidContent". | ||||
|  * @param {string} message A human-readable message. | ||||
|  */ | ||||
| function LZError(operation, reason, message) { | ||||
|   SharedAll.OSError.call(this); | ||||
|   this.operation = operation; | ||||
|   this[reason] = true; | ||||
|   this.message = message; | ||||
| } | ||||
| LZError.prototype = Object.create(SharedAll.OSError); | ||||
| LZError.prototype.toString = function toString() { | ||||
|   return this.message; | ||||
| }; | ||||
| exports.Error = LZError; | ||||
| 
 | ||||
| /** | ||||
|  * Compress a block to a form suitable for writing to disk. | ||||
|  * | ||||
|  * Compatibility note: For the moment, we are basing our code on lz4 | ||||
|  * 1.3, which does not specify a *file* format. Therefore, we define | ||||
|  * our own format. Once lz4 defines a complete file format, we will | ||||
|  * migrate both |compressFileContent| and |decompressFileContent| to this file | ||||
|  * format. For backwards-compatibility, |decompressFileContent| will however | ||||
|  * keep the ability to decompress files provided with older versions of | ||||
|  * |compressFileContent|. | ||||
|  * | ||||
|  * Compressed files have the following layout: | ||||
|  * | ||||
|  * | MAGIC_NUMBER (8 bytes) | content size (uint32_t, little endian) | content, as obtained from lz4_compress | | ||||
|  * | ||||
|  * @param {TypedArray|void*} buffer The buffer to write to the disk. | ||||
|  * @param {object=} options An object that may contain the following fields: | ||||
|  *  - {number} bytes The number of bytes to read from |buffer|. If |buffer| | ||||
|  *    is an |ArrayBuffer|, |bytes| defaults to |buffer.byteLength|. If | ||||
|  *    |buffer| is a |void*|, |bytes| MUST be provided. | ||||
|  * @return {Uint8Array} An array of bytes suitable for being written to the | ||||
|  * disk. | ||||
|  */ | ||||
| function compressFileContent(array, options = {}) { | ||||
|   // Prepare the output array
 | ||||
|   let inputBytes; | ||||
|   if (SharedAll.isTypedArray(array) && !(options && "bytes" in options)) { | ||||
|     inputBytes = array.byteLength; | ||||
|   } else if (options && options.bytes) { | ||||
|     inputBytes = options.bytes; | ||||
|   } else { | ||||
|     throw new TypeError("compressFileContent requires a size"); | ||||
|   } | ||||
|   let maxCompressedSize = Primitives.maxCompressedSize(inputBytes); | ||||
|   let outputArray = new Uint8Array(HEADER_SIZE + maxCompressedSize); | ||||
| 
 | ||||
|   // Compress to output array
 | ||||
|   let payload = new Uint8Array( | ||||
|     outputArray.buffer, | ||||
|     outputArray.byteOffset + HEADER_SIZE | ||||
|   ); | ||||
|   let compressedSize = Primitives.compress(array, inputBytes, payload); | ||||
| 
 | ||||
|   // Add headers
 | ||||
|   outputArray.set(MAGIC_NUMBER); | ||||
|   let view = new DataView(outputArray.buffer); | ||||
|   view.setUint32(MAGIC_NUMBER.byteLength, inputBytes, true); | ||||
| 
 | ||||
|   return new Uint8Array(outputArray.buffer, 0, HEADER_SIZE + compressedSize); | ||||
| } | ||||
| exports.compressFileContent = compressFileContent; | ||||
| 
 | ||||
| function decompressFileContent(array, options = {}) { | ||||
|   let bytes = SharedAll.normalizeBufferArgs(array, options.bytes || null); | ||||
|   if (bytes < HEADER_SIZE) { | ||||
|     throw new LZError( | ||||
|       "decompress", | ||||
|       "becauseLZNoHeader", | ||||
|       `Buffer is too short (no header) - Data: ${options.path || array}` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   // Read headers
 | ||||
|   let expectMagicNumber = new DataView( | ||||
|     array.buffer, | ||||
|     0, | ||||
|     MAGIC_NUMBER.byteLength | ||||
|   ); | ||||
|   for (let i = 0; i < MAGIC_NUMBER.byteLength; ++i) { | ||||
|     if (expectMagicNumber.getUint8(i) != MAGIC_NUMBER[i]) { | ||||
|       throw new LZError( | ||||
|         "decompress", | ||||
|         "becauseLZWrongMagicNumber", | ||||
|         `Invalid header (no magic number) - Data: ${options.path || array}` | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   let sizeBuf = new DataView( | ||||
|     array.buffer, | ||||
|     MAGIC_NUMBER.byteLength, | ||||
|     BYTES_IN_SIZE_HEADER | ||||
|   ); | ||||
|   let expectDecompressedSize = | ||||
|     sizeBuf.getUint8(0) + | ||||
|     (sizeBuf.getUint8(1) << 8) + | ||||
|     (sizeBuf.getUint8(2) << 16) + | ||||
|     (sizeBuf.getUint8(3) << 24); | ||||
|   if (expectDecompressedSize == 0) { | ||||
|     // The underlying algorithm cannot handle a size of 0
 | ||||
|     return new Uint8Array(0); | ||||
|   } | ||||
| 
 | ||||
|   // Prepare the input buffer
 | ||||
|   let inputData = new DataView(array.buffer, HEADER_SIZE); | ||||
| 
 | ||||
|   // Prepare the output buffer
 | ||||
|   let outputBuffer = new Uint8Array(expectDecompressedSize); | ||||
|   let decompressedBytes = new SharedAll.Type.size_t.implementation(0); | ||||
| 
 | ||||
|   // Decompress
 | ||||
|   let success = Primitives.decompress( | ||||
|     inputData, | ||||
|     bytes - HEADER_SIZE, | ||||
|     outputBuffer, | ||||
|     outputBuffer.byteLength, | ||||
|     decompressedBytes.address() | ||||
|   ); | ||||
|   if (!success) { | ||||
|     throw new LZError( | ||||
|       "decompress", | ||||
|       "becauseLZInvalidContent", | ||||
|       `Invalid content: Decompression stopped at ${ | ||||
|         decompressedBytes.value | ||||
|       } - Data: ${options.path || array}` | ||||
|     ); | ||||
|   } | ||||
|   return new Uint8Array( | ||||
|     outputBuffer.buffer, | ||||
|     outputBuffer.byteOffset, | ||||
|     decompressedBytes.value | ||||
|   ); | ||||
| } | ||||
| exports.decompressFileContent = decompressFileContent; | ||||
| 
 | ||||
| if (typeof Components != "undefined") { | ||||
|   this.Lz4 = { | ||||
|     compressFileContent, | ||||
|     decompressFileContent, | ||||
|   }; | ||||
| } | ||||
|  | @ -1,76 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| /* eslint-env commonjs */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var Primitives = {}; | ||||
| 
 | ||||
| var SharedAll; | ||||
| if (typeof Components != "undefined") { | ||||
|   SharedAll = ChromeUtils.import( | ||||
|     "resource://gre/modules/osfile/osfile_shared_allthreads.jsm" | ||||
|   ); | ||||
| 
 | ||||
|   this.EXPORTED_SYMBOLS = ["Primitives"]; | ||||
|   this.Primitives = Primitives; | ||||
|   this.exports = {}; | ||||
| } else if (typeof module != "undefined" && typeof require != "undefined") { | ||||
|   SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); | ||||
| } else { | ||||
|   throw new Error( | ||||
|     "Please load this module with Component.utils.import or with require()" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| var libxul = new SharedAll.Library("libxul", SharedAll.Constants.Path.libxul); | ||||
| var Type = SharedAll.Type; | ||||
| 
 | ||||
| libxul.declareLazyFFI( | ||||
|   Primitives, | ||||
|   "compress", | ||||
|   "workerlz4_compress", | ||||
|   null, | ||||
|   /* return*/ Type.size_t, | ||||
|   /* const source*/ Type.void_t.in_ptr, | ||||
|   /* inputSize*/ Type.size_t, | ||||
|   /* dest*/ Type.void_t.out_ptr | ||||
| ); | ||||
| 
 | ||||
| libxul.declareLazyFFI( | ||||
|   Primitives, | ||||
|   "decompress", | ||||
|   "workerlz4_decompress", | ||||
|   null, | ||||
|   /* return*/ Type.int, | ||||
|   /* const source*/ Type.void_t.in_ptr, | ||||
|   /* inputSize*/ Type.size_t, | ||||
|   /* dest*/ Type.void_t.out_ptr, | ||||
|   /* maxOutputSize*/ Type.size_t, | ||||
|   /* actualOutputSize*/ Type.size_t.out_ptr | ||||
| ); | ||||
| 
 | ||||
| libxul.declareLazyFFI( | ||||
|   Primitives, | ||||
|   "maxCompressedSize", | ||||
|   "workerlz4_maxCompressedSize", | ||||
|   null, | ||||
|   /* return*/ Type.size_t, | ||||
|   /* inputSize*/ Type.size_t | ||||
| ); | ||||
| 
 | ||||
| if (typeof module != "undefined") { | ||||
|   module.exports = { | ||||
|     get compress() { | ||||
|       return Primitives.compress; | ||||
|     }, | ||||
|     get decompress() { | ||||
|       return Primitives.decompress; | ||||
|     }, | ||||
|     get maxCompressedSize() { | ||||
|       return Primitives.maxCompressedSize; | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
|  | @ -1,21 +0,0 @@ | |||
| # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- | ||||
| # vim: set filetype=python: | ||||
| # 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/. | ||||
| 
 | ||||
| with Files("**"): | ||||
|     BUG_COMPONENT = ("Toolkit", "OS.File") | ||||
| 
 | ||||
| XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"] | ||||
| 
 | ||||
| EXTRA_JS_MODULES += [ | ||||
|     "lz4.js", | ||||
|     "lz4_internal.js", | ||||
| ] | ||||
| 
 | ||||
| SOURCES += [ | ||||
|     "lz4.cpp", | ||||
| ] | ||||
| 
 | ||||
| FINAL_LIBRARY = "xul" | ||||
|  | @ -1 +0,0 @@ | |||
| content test_lz4 ./ | ||||
										
											Binary file not shown.
										
									
								
							|  | @ -1,166 +0,0 @@ | |||
| /* eslint-env mozilla/chrome-worker */ | ||||
| 
 | ||||
| /* import-globals-from /toolkit/components/workerloader/require.js */ | ||||
| importScripts("resource://gre/modules/workers/require.js"); | ||||
| /* import-globals-from /toolkit/components/osfile/osfile.jsm */ | ||||
| importScripts("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| function info(x) { | ||||
|   // self.postMessage({kind: "do_print", args: [x]});
 | ||||
|   dump("TEST-INFO: " + x + "\n"); | ||||
| } | ||||
| 
 | ||||
| const Assert = { | ||||
|   ok(x) { | ||||
|     self.postMessage({ kind: "assert_ok", args: [!!x] }); | ||||
|     if (x) { | ||||
|       dump("TEST-PASS: " + x + "\n"); | ||||
|     } else { | ||||
|       throw new Error("Assert.ok failed"); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   equal(a, b) { | ||||
|     let result = a == b; | ||||
|     self.postMessage({ kind: "assert_ok", args: [result] }); | ||||
|     if (!result) { | ||||
|       throw new Error("Assert.equal failed " + a + " != " + b); | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| function do_test_complete() { | ||||
|   self.postMessage({ kind: "do_test_complete", args: [] }); | ||||
| } | ||||
| 
 | ||||
| self.onmessage = function() { | ||||
|   try { | ||||
|     run_test(); | ||||
|   } catch (ex) { | ||||
|     let { message, moduleStack, moduleName, lineNumber } = ex; | ||||
|     let error = new Error(message, moduleName, lineNumber); | ||||
|     error.stack = moduleStack; | ||||
|     dump("Uncaught error: " + error + "\n"); | ||||
|     dump("Full stack: " + moduleStack + "\n"); | ||||
|     throw error; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| var Lz4; | ||||
| var Internals; | ||||
| function test_import() { | ||||
|   Lz4 = require("resource://gre/modules/lz4.js"); | ||||
|   Internals = require("resource://gre/modules/lz4_internal.js"); | ||||
| } | ||||
| 
 | ||||
| function test_bound() { | ||||
|   for (let k of ["compress", "decompress", "maxCompressedSize"]) { | ||||
|     try { | ||||
|       info("Checking the existence of " + k + "\n"); | ||||
|       Assert.ok(!!Internals[k]); | ||||
|       info(k + " exists"); | ||||
|     } catch (ex) { | ||||
|       // Ignore errors
 | ||||
|       info(k + " doesn't exist!"); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function test_reference_file() { | ||||
|   info("Decompress reference file"); | ||||
|   let path = OS.Path.join("data", "compression.lz"); | ||||
|   let data = OS.File.read(path); | ||||
|   let decompressed = Lz4.decompressFileContent(data); | ||||
|   let text = new TextDecoder().decode(decompressed); | ||||
|   Assert.equal(text, "Hello, lz4"); | ||||
| } | ||||
| 
 | ||||
| function compare_arrays(a, b) { | ||||
|   return Array.prototype.join.call(a) == Array.prototype.join.call(b); | ||||
| } | ||||
| 
 | ||||
| function run_rawcompression(name, array) { | ||||
|   info("Raw compression test " + name); | ||||
|   let length = array.byteLength; | ||||
|   let compressedArray = new Uint8Array(Internals.maxCompressedSize(length)); | ||||
|   let compressedBytes = Internals.compress(array, length, compressedArray); | ||||
|   compressedArray = new Uint8Array(compressedArray.buffer, 0, compressedBytes); | ||||
|   info("Raw compressed: " + length + " into " + compressedBytes); | ||||
| 
 | ||||
|   let decompressedArray = new Uint8Array(length); | ||||
|   let decompressedBytes = new ctypes.size_t(); | ||||
|   let success = Internals.decompress( | ||||
|     compressedArray, | ||||
|     compressedBytes, | ||||
|     decompressedArray, | ||||
|     length, | ||||
|     decompressedBytes.address() | ||||
|   ); | ||||
|   info("Raw decompression success? " + success); | ||||
|   info("Raw decompression size: " + decompressedBytes.value); | ||||
|   Assert.ok(compare_arrays(array, decompressedArray)); | ||||
| } | ||||
| 
 | ||||
| function run_filecompression(name, array) { | ||||
|   info("File compression test " + name); | ||||
|   let compressed = Lz4.compressFileContent(array); | ||||
|   info( | ||||
|     "Compressed " + array.byteLength + " bytes into " + compressed.byteLength | ||||
|   ); | ||||
| 
 | ||||
|   let decompressed = Lz4.decompressFileContent(compressed); | ||||
|   info( | ||||
|     "Decompressed " + | ||||
|       compressed.byteLength + | ||||
|       " bytes into " + | ||||
|       decompressed.byteLength | ||||
|   ); | ||||
|   Assert.ok(compare_arrays(array, decompressed)); | ||||
| } | ||||
| 
 | ||||
| function run_faileddecompression(name, array) { | ||||
|   info("invalid decompression test " + name); | ||||
| 
 | ||||
|   // Ensure that raw decompression doesn't segfault
 | ||||
|   let length = 1 << 14; | ||||
|   let decompressedArray = new Uint8Array(length); | ||||
|   let decompressedBytes = new ctypes.size_t(); | ||||
|   Internals.decompress( | ||||
|     array, | ||||
|     array.byteLength, | ||||
|     decompressedArray, | ||||
|     length, | ||||
|     decompressedBytes.address() | ||||
|   ); | ||||
| 
 | ||||
|   // File decompression should fail with an acceptable exception
 | ||||
|   let exn = null; | ||||
|   try { | ||||
|     Lz4.decompressFileContent(array); | ||||
|   } catch (ex) { | ||||
|     exn = ex; | ||||
|   } | ||||
|   Assert.ok(exn); | ||||
|   if (array.byteLength < 10) { | ||||
|     Assert.ok(exn.becauseLZNoHeader); | ||||
|   } else { | ||||
|     Assert.ok(exn.becauseLZWrongMagicNumber); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function run_test() { | ||||
|   test_import(); | ||||
|   test_bound(); | ||||
|   test_reference_file(); | ||||
|   for (let length of [0, 1, 1024]) { | ||||
|     let array = new Uint8Array(length); | ||||
|     for (let i = 0; i < length; ++i) { | ||||
|       array[i] = i % 256; | ||||
|     } | ||||
|     let name = length + " bytes"; | ||||
|     run_rawcompression(name, array); | ||||
|     run_filecompression(name, array); | ||||
|     run_faileddecompression(name, array); | ||||
|   } | ||||
|   do_test_complete(); | ||||
| } | ||||
|  | @ -1,35 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| var WORKER_SOURCE_URI = "chrome://test_lz4/content/worker_lz4.js"; | ||||
| do_load_manifest("data/chrome.manifest"); | ||||
| 
 | ||||
| add_task(function() { | ||||
|   let worker = new ChromeWorker(WORKER_SOURCE_URI); | ||||
|   return new Promise((resolve, reject) => { | ||||
|     worker.onmessage = function(event) { | ||||
|       let data = event.data; | ||||
|       switch (data.kind) { | ||||
|         case "assert_ok": | ||||
|           try { | ||||
|             Assert.ok(data.args[0]); | ||||
|           } catch (ex) { | ||||
|             // Ignore errors
 | ||||
|           } | ||||
|           return; | ||||
|         case "do_test_complete": | ||||
|           resolve(); | ||||
|           worker.terminate(); | ||||
|           break; | ||||
|         case "do_print": | ||||
|           info(data.args[0]); | ||||
|       } | ||||
|     }; | ||||
|     worker.onerror = function(event) { | ||||
|       let error = new Error(event.message, event.filename, event.lineno); | ||||
|       worker.terminate(); | ||||
|       reject(error); | ||||
|     }; | ||||
|     worker.postMessage("START"); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,41 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| const { Lz4 } = ChromeUtils.import("resource://gre/modules/lz4.js"); | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| function compare_arrays(a, b) { | ||||
|   return Array.prototype.join.call(a) == Array.prototype.join.call(b); | ||||
| } | ||||
| 
 | ||||
| add_task(async function() { | ||||
|   let path = OS.Path.join("data", "compression.lz"); | ||||
|   let data = await OS.File.read(path); | ||||
|   let decompressed = Lz4.decompressFileContent(data); | ||||
|   let text = new TextDecoder().decode(decompressed); | ||||
|   Assert.equal(text, "Hello, lz4"); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function() { | ||||
|   for (let length of [0, 1, 1024]) { | ||||
|     let array = new Uint8Array(length); | ||||
|     for (let i = 0; i < length; ++i) { | ||||
|       array[i] = i % 256; | ||||
|     } | ||||
| 
 | ||||
|     let compressed = Lz4.compressFileContent(array); | ||||
|     info( | ||||
|       "Compressed " + array.byteLength + " bytes into " + compressed.byteLength | ||||
|     ); | ||||
| 
 | ||||
|     let decompressed = Lz4.decompressFileContent(compressed); | ||||
|     info( | ||||
|       "Decompressed " + | ||||
|         compressed.byteLength + | ||||
|         " bytes into " + | ||||
|         decompressed.byteLength | ||||
|     ); | ||||
| 
 | ||||
|     Assert.ok(compare_arrays(array, decompressed)); | ||||
|   } | ||||
| }); | ||||
|  | @ -1,10 +0,0 @@ | |||
| [DEFAULT] | ||||
| head = | ||||
| skip-if = toolkit == 'android' | ||||
| support-files = | ||||
|   data/worker_lz4.js | ||||
|   data/chrome.manifest | ||||
|   data/compression.lz | ||||
| 
 | ||||
| [test_lz4.js] | ||||
| [test_lz4_sync.js] | ||||
|  | @ -46,11 +46,9 @@ DIRS += [ | |||
|     "httpsonlyerror", | ||||
|     "jsoncpp/src/lib_json", | ||||
|     "kvstore", | ||||
|     "lz4", | ||||
|     "mediasniffer", | ||||
|     "mozintl", | ||||
|     "mozprotocol", | ||||
|     "osfile", | ||||
|     "parentalcontrols", | ||||
|     "passwordmgr", | ||||
|     "pdfjs", | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,29 +0,0 @@ | |||
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ | ||||
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */ | ||||
| /* 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/. */
 | ||||
| 
 | ||||
| #ifndef mozilla_nativeosfileinternalservice_h__ | ||||
| #define mozilla_nativeosfileinternalservice_h__ | ||||
| 
 | ||||
| #include "nsINativeOSFileInternals.h" | ||||
| 
 | ||||
| #include "nsISupports.h" | ||||
| 
 | ||||
| namespace mozilla { | ||||
| 
 | ||||
| class NativeOSFileInternalsService final | ||||
|     : public nsINativeOSFileInternalsService { | ||||
|  public: | ||||
|   NS_DECL_ISUPPORTS | ||||
|   NS_DECL_NSINATIVEOSFILEINTERNALSSERVICE | ||||
|  private: | ||||
|   ~NativeOSFileInternalsService() = default; | ||||
|   // Avoid accidental use of built-in operator=
 | ||||
|   void operator=(const NativeOSFileInternalsService& other) = delete; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace mozilla
 | ||||
| 
 | ||||
| #endif  // mozilla_finalizationwitnessservice_h__
 | ||||
|  | @ -1,29 +0,0 @@ | |||
| # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- | ||||
| # vim: set filetype=python: | ||||
| # 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/. | ||||
| 
 | ||||
| EXTRA_JS_MODULES.osfile += [ | ||||
|     "osfile_async_front.jsm", | ||||
|     "osfile_async_worker.js", | ||||
|     "osfile_native.jsm", | ||||
|     "osfile_shared_allthreads.jsm", | ||||
|     "osfile_shared_front.js", | ||||
|     "ospath.jsm", | ||||
|     "ospath_unix.jsm", | ||||
|     "ospath_win.jsm", | ||||
| ] | ||||
| 
 | ||||
| if CONFIG["OS_TARGET"] == "WINNT": | ||||
|     EXTRA_JS_MODULES.osfile += [ | ||||
|         "osfile_win_allthreads.jsm", | ||||
|         "osfile_win_back.js", | ||||
|         "osfile_win_front.js", | ||||
|     ] | ||||
| else: | ||||
|     EXTRA_JS_MODULES.osfile += [ | ||||
|         "osfile_unix_allthreads.jsm", | ||||
|         "osfile_unix_back.js", | ||||
|         "osfile_unix_front.js", | ||||
|     ] | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,450 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| /* eslint-env worker */ | ||||
| 
 | ||||
| if (this.Components) { | ||||
|   throw new Error("This worker can only be loaded from a worker thread"); | ||||
| } | ||||
| 
 | ||||
| // Worker thread for osfile asynchronous front-end
 | ||||
| 
 | ||||
| (function(exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   // Timestamps, for use in Telemetry.
 | ||||
|   // The object is set to |null| once it has been sent
 | ||||
|   // to the main thread.
 | ||||
|   let timeStamps = { | ||||
|     entered: Date.now(), | ||||
|     loaded: null, | ||||
|   }; | ||||
| 
 | ||||
|   // NOTE: osfile.jsm imports require.js
 | ||||
|   /* import-globals-from /toolkit/components/workerloader/require.js */ | ||||
|   /* import-globals-from /toolkit/components/osfile/osfile.jsm */ | ||||
|   importScripts("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
|   let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js"); | ||||
|   let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); | ||||
|   let LOG = SharedAll.LOG.bind(SharedAll, "Agent"); | ||||
| 
 | ||||
|   let worker = new PromiseWorker.AbstractWorker(); | ||||
|   worker.dispatch = function(method, args = []) { | ||||
|     let startTime = performance.now(); | ||||
|     try { | ||||
|       return Agent[method](...args); | ||||
|     } finally { | ||||
|       let text = method; | ||||
|       if (args.length && args[0] instanceof Object && args[0].string) { | ||||
|         // Including the path in the marker text here means it will be part of
 | ||||
|         // profiles. It's fine to include personally identifiable information
 | ||||
|         // in profiles, because when a profile is captured only the user will
 | ||||
|         // see it, and before uploading it a sanitization step will be offered.
 | ||||
|         // The 'OS.File' name will help the profiler know that these markers
 | ||||
|         // should be sanitized.
 | ||||
|         text += " — " + args[0].string; | ||||
|       } | ||||
|       ChromeUtils.addProfilerMarker("OS.File", startTime, text); | ||||
|     } | ||||
|   }; | ||||
|   worker.log = LOG; | ||||
|   worker.postMessage = function(message, ...transfers) { | ||||
|     if (timeStamps) { | ||||
|       message.timeStamps = timeStamps; | ||||
|       timeStamps = null; | ||||
|     } | ||||
|     self.postMessage(message, ...transfers); | ||||
|   }; | ||||
|   worker.close = function() { | ||||
|     self.close(); | ||||
|   }; | ||||
|   let Meta = PromiseWorker.Meta; | ||||
| 
 | ||||
|   self.addEventListener("message", msg => worker.handleMessage(msg)); | ||||
|   self.addEventListener("unhandledrejection", function(error) { | ||||
|     throw error.reason; | ||||
|   }); | ||||
| 
 | ||||
|   /** | ||||
|    * A data structure used to track opened resources | ||||
|    */ | ||||
|   let ResourceTracker = function ResourceTracker() { | ||||
|     // A number used to generate ids
 | ||||
|     this._idgen = 0; | ||||
|     // A map from id to resource
 | ||||
|     this._map = new Map(); | ||||
|   }; | ||||
|   ResourceTracker.prototype = { | ||||
|     /** | ||||
|      * Get a resource from its unique identifier. | ||||
|      */ | ||||
|     get(id) { | ||||
|       let result = this._map.get(id); | ||||
|       if (result == null) { | ||||
|         return result; | ||||
|       } | ||||
|       return result.resource; | ||||
|     }, | ||||
|     /** | ||||
|      * Remove a resource from its unique identifier. | ||||
|      */ | ||||
|     remove(id) { | ||||
|       if (!this._map.has(id)) { | ||||
|         throw new Error("Cannot find resource id " + id); | ||||
|       } | ||||
|       this._map.delete(id); | ||||
|     }, | ||||
|     /** | ||||
|      * Add a resource, return a new unique identifier | ||||
|      * | ||||
|      * @param {*} resource A resource. | ||||
|      * @param {*=} info Optional information. For debugging purposes. | ||||
|      * | ||||
|      * @return {*} A unique identifier. For the moment, this is a number, | ||||
|      * but this might not remain the case forever. | ||||
|      */ | ||||
|     add(resource, info) { | ||||
|       let id = this._idgen++; | ||||
|       this._map.set(id, { resource, info }); | ||||
|       return id; | ||||
|     }, | ||||
|     /** | ||||
|      * Return a list of all open resources i.e. the ones still present in | ||||
|      * ResourceTracker's _map. | ||||
|      */ | ||||
|     listOpenedResources: function listOpenedResources() { | ||||
|       return Array.from(this._map, ([id, resource]) => resource.info.path); | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * A map of unique identifiers to opened files. | ||||
|    */ | ||||
|   let OpenedFiles = new ResourceTracker(); | ||||
| 
 | ||||
|   /** | ||||
|    * Execute a function in the context of a given file. | ||||
|    * | ||||
|    * @param {*} id A unique identifier, as used by |OpenFiles|. | ||||
|    * @param {Function} f A function to call. | ||||
|    * @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception. | ||||
|    * @return The return value of |f()| | ||||
|    * | ||||
|    * This function attempts to get the file matching |id|. If | ||||
|    * the file exists, it executes |f| within the |this| set | ||||
|    * to the corresponding file. Otherwise, it throws an error. | ||||
|    */ | ||||
|   let withFile = function withFile(id, f, ignoreAbsent) { | ||||
|     let file = OpenedFiles.get(id); | ||||
|     if (file == null) { | ||||
|       if (!ignoreAbsent) { | ||||
|         throw OS.File.Error.closed("accessing file"); | ||||
|       } | ||||
|       return undefined; | ||||
|     } | ||||
|     return f.call(file); | ||||
|   }; | ||||
| 
 | ||||
|   let OpenedDirectoryIterators = new ResourceTracker(); | ||||
|   let withDir = function withDir(fd, f, ignoreAbsent) { | ||||
|     let file = OpenedDirectoryIterators.get(fd); | ||||
|     if (file == null) { | ||||
|       if (!ignoreAbsent) { | ||||
|         throw OS.File.Error.closed("accessing directory"); | ||||
|       } | ||||
|       return undefined; | ||||
|     } | ||||
|     if (!(file instanceof File.DirectoryIterator)) { | ||||
|       throw new Error( | ||||
|         "file is not a directory iterator " + | ||||
|           Object.getPrototypeOf(file).toSource() | ||||
|       ); | ||||
|     } | ||||
|     return f.call(file); | ||||
|   }; | ||||
| 
 | ||||
|   let Type = exports.OS.Shared.Type; | ||||
| 
 | ||||
|   let File = exports.OS.File; | ||||
| 
 | ||||
|   /** | ||||
|    * The agent. | ||||
|    * | ||||
|    * It is in charge of performing method-specific deserialization | ||||
|    * of messages, calling the function/method of OS.File and serializing | ||||
|    * back the results. | ||||
|    */ | ||||
|   let Agent = { | ||||
|     // Update worker's OS.Shared.DEBUG flag message from controller.
 | ||||
|     SET_DEBUG(aDEBUG) { | ||||
|       SharedAll.Config.DEBUG = aDEBUG; | ||||
|     }, | ||||
|     // Return worker's current OS.Shared.DEBUG value to controller.
 | ||||
|     // Note: This is used for testing purposes.
 | ||||
|     GET_DEBUG() { | ||||
|       return SharedAll.Config.DEBUG; | ||||
|     }, | ||||
|     /** | ||||
|      * Execute shutdown sequence, returning data on leaked file descriptors. | ||||
|      * | ||||
|      * @param {bool} If |true|, kill the worker if this would not cause | ||||
|      * leaks. | ||||
|      */ | ||||
|     Meta_shutdown(kill) { | ||||
|       let result = { | ||||
|         openedFiles: OpenedFiles.listOpenedResources(), | ||||
|         openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(), | ||||
|         killed: false, // Placeholder
 | ||||
|       }; | ||||
| 
 | ||||
|       // Is it safe to kill the worker?
 | ||||
|       let safe = | ||||
|         !result.openedFiles.length && !result.openedDirectoryIterators.length; | ||||
|       result.killed = safe && kill; | ||||
| 
 | ||||
|       return new Meta(result, { shutdown: result.killed }); | ||||
|     }, | ||||
|     // Functions of OS.File
 | ||||
|     stat: function stat(path, options) { | ||||
|       return exports.OS.File.Info.toMsg( | ||||
|         exports.OS.File.stat(Type.path.fromMsg(path), options) | ||||
|       ); | ||||
|     }, | ||||
|     setPermissions: function setPermissions(path, options = {}) { | ||||
|       return exports.OS.File.setPermissions(Type.path.fromMsg(path), options); | ||||
|     }, | ||||
|     setDates: function setDates(path, accessDate, modificationDate) { | ||||
|       return exports.OS.File.setDates( | ||||
|         Type.path.fromMsg(path), | ||||
|         accessDate, | ||||
|         modificationDate | ||||
|       ); | ||||
|     }, | ||||
|     getCurrentDirectory: function getCurrentDirectory() { | ||||
|       return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory()); | ||||
|     }, | ||||
|     copy: function copy(sourcePath, destPath, options) { | ||||
|       return File.copy( | ||||
|         Type.path.fromMsg(sourcePath), | ||||
|         Type.path.fromMsg(destPath), | ||||
|         options | ||||
|       ); | ||||
|     }, | ||||
|     move: function move(sourcePath, destPath, options) { | ||||
|       return File.move( | ||||
|         Type.path.fromMsg(sourcePath), | ||||
|         Type.path.fromMsg(destPath), | ||||
|         options | ||||
|       ); | ||||
|     }, | ||||
|     makeDir: function makeDir(path, options) { | ||||
|       return File.makeDir(Type.path.fromMsg(path), options); | ||||
|     }, | ||||
|     removeEmptyDir: function removeEmptyDir(path, options) { | ||||
|       return File.removeEmptyDir(Type.path.fromMsg(path), options); | ||||
|     }, | ||||
|     remove: function remove(path, options) { | ||||
|       return File.remove(Type.path.fromMsg(path), options); | ||||
|     }, | ||||
|     open: function open(path, mode, options) { | ||||
|       let filePath = Type.path.fromMsg(path); | ||||
|       let file = File.open(filePath, mode, options); | ||||
|       return OpenedFiles.add(file, { | ||||
|         // Adding path information to keep track of opened files
 | ||||
|         // to report leaks when debugging.
 | ||||
|         path: filePath, | ||||
|       }); | ||||
|     }, | ||||
|     openUnique: function openUnique(path, options) { | ||||
|       let filePath = Type.path.fromMsg(path); | ||||
|       let openedFile = OS.Shared.AbstractFile.openUnique(filePath, options); | ||||
|       let resourceId = OpenedFiles.add(openedFile.file, { | ||||
|         // Adding path information to keep track of opened files
 | ||||
|         // to report leaks when debugging.
 | ||||
|         path: openedFile.path, | ||||
|       }); | ||||
| 
 | ||||
|       return { | ||||
|         path: openedFile.path, | ||||
|         file: resourceId, | ||||
|       }; | ||||
|     }, | ||||
|     read: function read(path, bytes, options) { | ||||
|       let data = File.read(Type.path.fromMsg(path), bytes, options); | ||||
|       if (typeof data == "string") { | ||||
|         return data; | ||||
|       } | ||||
|       return new Meta( | ||||
|         { | ||||
|           buffer: data.buffer, | ||||
|           byteOffset: data.byteOffset, | ||||
|           byteLength: data.byteLength, | ||||
|         }, | ||||
|         { | ||||
|           transfers: [data.buffer], | ||||
|         } | ||||
|       ); | ||||
|     }, | ||||
|     exists: function exists(path) { | ||||
|       return File.exists(Type.path.fromMsg(path)); | ||||
|     }, | ||||
|     writeAtomic: function writeAtomic(path, buffer, options) { | ||||
|       if (options.tmpPath) { | ||||
|         options.tmpPath = Type.path.fromMsg(options.tmpPath); | ||||
|       } | ||||
|       return File.writeAtomic( | ||||
|         Type.path.fromMsg(path), | ||||
|         Type.voidptr_t.fromMsg(buffer), | ||||
|         options | ||||
|       ); | ||||
|     }, | ||||
|     removeDir(path, options) { | ||||
|       return File.removeDir(Type.path.fromMsg(path), options); | ||||
|     }, | ||||
|     new_DirectoryIterator: function new_DirectoryIterator(path, options) { | ||||
|       let directoryPath = Type.path.fromMsg(path); | ||||
|       let iterator = new File.DirectoryIterator(directoryPath, options); | ||||
|       return OpenedDirectoryIterators.add(iterator, { | ||||
|         // Adding path information to keep track of opened directory
 | ||||
|         // iterators to report leaks when debugging.
 | ||||
|         path: directoryPath, | ||||
|       }); | ||||
|     }, | ||||
|     // Methods of OS.File
 | ||||
|     File_prototype_close: function close(fd) { | ||||
|       return withFile(fd, function do_close() { | ||||
|         try { | ||||
|           return this.close(); | ||||
|         } finally { | ||||
|           OpenedFiles.remove(fd); | ||||
|         } | ||||
|       }); | ||||
|     }, | ||||
|     File_prototype_stat: function stat(fd) { | ||||
|       return withFile(fd, function do_stat() { | ||||
|         return exports.OS.File.Info.toMsg(this.stat()); | ||||
|       }); | ||||
|     }, | ||||
|     File_prototype_setPermissions: function setPermissions(fd, options = {}) { | ||||
|       return withFile(fd, function do_setPermissions() { | ||||
|         return this.setPermissions(options); | ||||
|       }); | ||||
|     }, | ||||
|     File_prototype_setDates: function setDates( | ||||
|       fd, | ||||
|       accessTime, | ||||
|       modificationTime | ||||
|     ) { | ||||
|       return withFile(fd, function do_setDates() { | ||||
|         return this.setDates(accessTime, modificationTime); | ||||
|       }); | ||||
|     }, | ||||
|     File_prototype_read: function read(fd, nbytes, options) { | ||||
|       return withFile(fd, function do_read() { | ||||
|         let data = this.read(nbytes, options); | ||||
|         return new Meta( | ||||
|           { | ||||
|             buffer: data.buffer, | ||||
|             byteOffset: data.byteOffset, | ||||
|             byteLength: data.byteLength, | ||||
|           }, | ||||
|           { | ||||
|             transfers: [data.buffer], | ||||
|           } | ||||
|         ); | ||||
|       }); | ||||
|     }, | ||||
|     File_prototype_readTo: function readTo(fd, buffer, options) { | ||||
|       return withFile(fd, function do_readTo() { | ||||
|         return this.readTo( | ||||
|           exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), | ||||
|           options | ||||
|         ); | ||||
|       }); | ||||
|     }, | ||||
|     File_prototype_write: function write(fd, buffer, options) { | ||||
|       return withFile(fd, function do_write() { | ||||
|         return this.write( | ||||
|           exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), | ||||
|           options | ||||
|         ); | ||||
|       }); | ||||
|     }, | ||||
|     File_prototype_setPosition: function setPosition(fd, pos, whence) { | ||||
|       return withFile(fd, function do_setPosition() { | ||||
|         return this.setPosition(pos, whence); | ||||
|       }); | ||||
|     }, | ||||
|     File_prototype_getPosition: function getPosition(fd) { | ||||
|       return withFile(fd, function do_getPosition() { | ||||
|         return this.getPosition(); | ||||
|       }); | ||||
|     }, | ||||
|     File_prototype_flush: function flush(fd) { | ||||
|       return withFile(fd, function do_flush() { | ||||
|         return this.flush(); | ||||
|       }); | ||||
|     }, | ||||
|     // Methods of OS.File.DirectoryIterator
 | ||||
|     DirectoryIterator_prototype_next: function next(dir) { | ||||
|       return withDir( | ||||
|         dir, | ||||
|         function do_next() { | ||||
|           let { value, done } = this.next(); | ||||
|           if (done) { | ||||
|             OpenedDirectoryIterators.remove(dir); | ||||
|             return { value: undefined, done: true }; | ||||
|           } | ||||
|           return { | ||||
|             value: File.DirectoryIterator.Entry.toMsg(value), | ||||
|             done: false, | ||||
|           }; | ||||
|         }, | ||||
|         false | ||||
|       ); | ||||
|     }, | ||||
|     DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) { | ||||
|       return withDir( | ||||
|         dir, | ||||
|         function do_nextBatch() { | ||||
|           let result; | ||||
|           try { | ||||
|             result = this.nextBatch(size); | ||||
|           } catch (x) { | ||||
|             OpenedDirectoryIterators.remove(dir); | ||||
|             throw x; | ||||
|           } | ||||
|           return result.map(File.DirectoryIterator.Entry.toMsg); | ||||
|         }, | ||||
|         false | ||||
|       ); | ||||
|     }, | ||||
|     DirectoryIterator_prototype_close: function close(dir) { | ||||
|       return withDir( | ||||
|         dir, | ||||
|         function do_close() { | ||||
|           this.close(); | ||||
|           OpenedDirectoryIterators.remove(dir); | ||||
|         }, | ||||
|         true | ||||
|       ); // ignore error to support double-closing |DirectoryIterator|
 | ||||
|     }, | ||||
|     DirectoryIterator_prototype_exists: function exists(dir) { | ||||
|       return withDir(dir, function do_exists() { | ||||
|         return this.exists(); | ||||
|       }); | ||||
|     }, | ||||
|   }; | ||||
|   if (!SharedAll.Constants.Win) { | ||||
|     Agent.unixSymLink = function unixSymLink(sourcePath, destPath) { | ||||
|       return File.unixSymLink( | ||||
|         Type.path.fromMsg(sourcePath), | ||||
|         Type.path.fromMsg(destPath) | ||||
|       ); | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   timeStamps.loaded = Date.now(); | ||||
| })(this); | ||||
|  | @ -1,126 +0,0 @@ | |||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||||
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||||
| 
 | ||||
| /** | ||||
|  * Native (xpcom) implementation of key OS.File functions | ||||
|  */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var EXPORTED_SYMBOLS = ["read", "writeAtomic"]; | ||||
| 
 | ||||
| var { Constants } = ChromeUtils.import( | ||||
|   "resource://gre/modules/osfile/osfile_shared_allthreads.jsm" | ||||
| ); | ||||
| 
 | ||||
| var SysAll; | ||||
| if (Constants.Win) { | ||||
|   SysAll = ChromeUtils.import( | ||||
|     "resource://gre/modules/osfile/osfile_win_allthreads.jsm" | ||||
|   ); | ||||
| } else if (Constants.libc) { | ||||
|   SysAll = ChromeUtils.import( | ||||
|     "resource://gre/modules/osfile/osfile_unix_allthreads.jsm" | ||||
|   ); | ||||
| } else { | ||||
|   throw new Error("I am neither under Windows nor under a Posix system"); | ||||
| } | ||||
| var { XPCOMUtils } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/XPCOMUtils.sys.mjs" | ||||
| ); | ||||
| 
 | ||||
| const lazy = {}; | ||||
| 
 | ||||
| /** | ||||
|  * The native service holding the implementation of the functions. | ||||
|  */ | ||||
| XPCOMUtils.defineLazyServiceGetter( | ||||
|   lazy, | ||||
|   "Internals", | ||||
|   "@mozilla.org/toolkit/osfile/native-internals;1", | ||||
|   "nsINativeOSFileInternalsService" | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * Native implementation of OS.File.read | ||||
|  * | ||||
|  * This implementation does not handle option |compression|. | ||||
|  */ | ||||
| var read = function(path, options = {}) { | ||||
|   // Sanity check on types of options
 | ||||
|   if ("encoding" in options && typeof options.encoding != "string") { | ||||
|     return Promise.reject(new TypeError("Invalid type for option encoding")); | ||||
|   } | ||||
|   if ("compression" in options && typeof options.compression != "string") { | ||||
|     return Promise.reject(new TypeError("Invalid type for option compression")); | ||||
|   } | ||||
|   if ("bytes" in options && typeof options.bytes != "number") { | ||||
|     return Promise.reject(new TypeError("Invalid type for option bytes")); | ||||
|   } | ||||
| 
 | ||||
|   return new Promise((resolve, reject) => { | ||||
|     lazy.Internals.read( | ||||
|       path, | ||||
|       options, | ||||
|       function onSuccess(success) { | ||||
|         success.QueryInterface(Ci.nsINativeOSFileResult); | ||||
|         if ("outExecutionDuration" in options) { | ||||
|           options.outExecutionDuration = | ||||
|             success.executionDurationMS + (options.outExecutionDuration || 0); | ||||
|         } | ||||
|         resolve(success.result); | ||||
|       }, | ||||
|       function onError(operation, oserror) { | ||||
|         reject(new SysAll.Error(operation, oserror, path)); | ||||
|       } | ||||
|     ); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Native implementation of OS.File.writeAtomic. | ||||
|  * This should not be called when |buffer| is a view with some non-zero byte offset. | ||||
|  * Does not handle option |compression|. | ||||
|  */ | ||||
| var writeAtomic = function(path, buffer, options = {}) { | ||||
|   // Sanity check on types of options - we check only the encoding, since
 | ||||
|   // the others are checked inside Internals.writeAtomic.
 | ||||
|   if ("encoding" in options && typeof options.encoding !== "string") { | ||||
|     return Promise.reject(new TypeError("Invalid type for option encoding")); | ||||
|   } | ||||
| 
 | ||||
|   if (typeof buffer == "string") { | ||||
|     // Normalize buffer to a C buffer by encoding it
 | ||||
|     buffer = new TextEncoder().encode(buffer); | ||||
|   } | ||||
| 
 | ||||
|   if (ArrayBuffer.isView(buffer)) { | ||||
|     // We need to throw an error if it's a buffer with some byte offset.
 | ||||
|     if ("byteOffset" in buffer && buffer.byteOffset > 0) { | ||||
|       return Promise.reject( | ||||
|         new Error("Invalid non-zero value of Typed Array byte offset") | ||||
|       ); | ||||
|     } | ||||
|     buffer = buffer.buffer; | ||||
|   } | ||||
| 
 | ||||
|   return new Promise((resolve, reject) => { | ||||
|     lazy.Internals.writeAtomic( | ||||
|       path, | ||||
|       buffer, | ||||
|       options, | ||||
|       function onSuccess(success) { | ||||
|         success.QueryInterface(Ci.nsINativeOSFileResult); | ||||
|         if ("outExecutionDuration" in options) { | ||||
|           options.outExecutionDuration = | ||||
|             success.executionDurationMS + (options.outExecutionDuration || 0); | ||||
|         } | ||||
|         resolve(success.result); | ||||
|       }, | ||||
|       function onError(operation, oserror) { | ||||
|         reject(new SysAll.Error(operation, oserror, path)); | ||||
|       } | ||||
|     ); | ||||
|   }); | ||||
| }; | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,607 +0,0 @@ | |||
| /* 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. | ||||
|  */ | ||||
| 
 | ||||
| /* eslint-env node */ | ||||
| /* global OS */ | ||||
| 
 | ||||
| if (typeof Components != "undefined") { | ||||
|   throw new Error("osfile_shared_front.js cannot be used from the main thread"); | ||||
| } | ||||
| (function(exports) { | ||||
|   var SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); | ||||
|   var Path = require("resource://gre/modules/osfile/ospath.jsm"); | ||||
|   var Lz4 = require("resource://gre/modules/lz4.js"); | ||||
|   SharedAll.LOG.bind(SharedAll, "Shared front-end"); | ||||
|   var clone = SharedAll.clone; | ||||
| 
 | ||||
|   /** | ||||
|    * Code shared by implementations of File. | ||||
|    * | ||||
|    * @param {*} fd An OS-specific file handle. | ||||
|    * @param {string} path File path of the file handle, used for error-reporting. | ||||
|    * @constructor | ||||
|    */ | ||||
|   var AbstractFile = function AbstractFile(fd, path) { | ||||
|     this._fd = fd; | ||||
|     if (!path) { | ||||
|       throw new TypeError("path is expected"); | ||||
|     } | ||||
|     this._path = path; | ||||
|   }; | ||||
| 
 | ||||
|   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("accessing file", this._path); | ||||
|     }, | ||||
|     /** | ||||
|      * Read bytes from this file to a new buffer. | ||||
|      * | ||||
|      * @param {number=} maybeBytes (deprecated, please use options.bytes) | ||||
|      * @param {JSON} options | ||||
|      * @return {Uint8Array} An array containing the bytes read. | ||||
|      */ | ||||
|     read: function read(maybeBytes, options = {}) { | ||||
|       if (typeof maybeBytes === "object") { | ||||
|         // Caller has skipped `maybeBytes` and provided an options object.
 | ||||
|         options = clone(maybeBytes); | ||||
|         maybeBytes = null; | ||||
|       } else { | ||||
|         options = options || {}; | ||||
|       } | ||||
|       let bytes = options.bytes || undefined; | ||||
|       if (bytes === undefined) { | ||||
|         bytes = maybeBytes == null ? this.stat().size : maybeBytes; | ||||
|       } | ||||
|       let buffer = new Uint8Array(bytes); | ||||
|       let pos = 0; | ||||
|       while (pos < bytes) { | ||||
|         let length = bytes - pos; | ||||
|         let view = new DataView(buffer.buffer, pos, length); | ||||
|         let chunkSize = this._read(view, length, options); | ||||
|         if (chunkSize == 0) { | ||||
|           break; | ||||
|         } | ||||
|         pos += chunkSize; | ||||
|       } | ||||
|       if (pos == bytes) { | ||||
|         return buffer; | ||||
|       } | ||||
|       return buffer.subarray(0, pos); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * 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 {Typed array} 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} bytes The number of |bytes| to write from the buffer. If | ||||
|      * unspecified, this is |buffer.byteLength|. | ||||
|      * | ||||
|      * @return {number} The number of bytes actually written. | ||||
|      */ | ||||
|     write: function write(buffer, options = {}) { | ||||
|       let bytes = SharedAll.normalizeBufferArgs( | ||||
|         buffer, | ||||
|         "bytes" in options ? options.bytes : undefined | ||||
|       ); | ||||
|       let pos = 0; | ||||
|       while (pos < bytes) { | ||||
|         let length = bytes - pos; | ||||
|         let view = new DataView(buffer.buffer, buffer.byteOffset + pos, length); | ||||
|         let chunkSize = this._write(view, length, options); | ||||
|         pos += chunkSize; | ||||
|       } | ||||
|       return pos; | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Creates and opens a file with a unique name. By default, generate a random HEX number and use it to create a unique new file name. | ||||
|    * | ||||
|    * @param {string} path The path to the file. | ||||
|    * @param {*=} options Additional options for file opening. This | ||||
|    * implementation interprets the following fields: | ||||
|    * | ||||
|    * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext. | ||||
|    *  If |false| use HEX numbers ie: filename-A65BC0.ext | ||||
|    * - {number} maxReadableNumber Used to limit the amount of tries after a failed | ||||
|    *  file creation. Default is 20. | ||||
|    * | ||||
|    * @return {Object} contains A file object{file} and the path{path}. | ||||
|    * @throws {OS.File.Error} If the file could not be opened. | ||||
|    */ | ||||
|   AbstractFile.openUnique = function openUnique(path, options = {}) { | ||||
|     let mode = { | ||||
|       create: true, | ||||
|     }; | ||||
| 
 | ||||
|     let dirName = Path.dirname(path); | ||||
|     let leafName = Path.basename(path); | ||||
|     let lastDotCharacter = leafName.lastIndexOf("."); | ||||
|     let fileName = leafName.substring( | ||||
|       0, | ||||
|       lastDotCharacter != -1 ? lastDotCharacter : leafName.length | ||||
|     ); | ||||
|     let suffix = | ||||
|       lastDotCharacter != -1 ? leafName.substring(lastDotCharacter) : ""; | ||||
|     let uniquePath = ""; | ||||
|     let maxAttempts = options.maxAttempts || 99; | ||||
|     let humanReadable = !!options.humanReadable; | ||||
|     const HEX_RADIX = 16; | ||||
|     // We produce HEX numbers between 0 and 2^24 - 1.
 | ||||
|     const MAX_HEX_NUMBER = 16777215; | ||||
| 
 | ||||
|     try { | ||||
|       return { | ||||
|         path, | ||||
|         file: OS.File.open(path, mode), | ||||
|       }; | ||||
|     } catch (ex) { | ||||
|       if (ex instanceof OS.File.Error && ex.becauseExists) { | ||||
|         for (let i = 0; i < maxAttempts; ++i) { | ||||
|           try { | ||||
|             if (humanReadable) { | ||||
|               uniquePath = Path.join( | ||||
|                 dirName, | ||||
|                 fileName + "-" + (i + 1) + suffix | ||||
|               ); | ||||
|             } else { | ||||
|               let hexNumber = Math.floor( | ||||
|                 Math.random() * MAX_HEX_NUMBER | ||||
|               ).toString(HEX_RADIX); | ||||
|               uniquePath = Path.join( | ||||
|                 dirName, | ||||
|                 fileName + "-" + hexNumber + suffix | ||||
|               ); | ||||
|             } | ||||
|             return { | ||||
|               path: uniquePath, | ||||
|               file: OS.File.open(uniquePath, mode), | ||||
|             }; | ||||
|           } catch (ex) { | ||||
|             if (ex instanceof OS.File.Error && ex.becauseExists) { | ||||
|               // keep trying ...
 | ||||
|             } else { | ||||
|               throw ex; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         throw OS.File.Error.exists("could not find an unused file name.", path); | ||||
|       } | ||||
|       throw ex; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Code shared by iterators. | ||||
|    */ | ||||
|   AbstractFile.AbstractIterator = function AbstractIterator() {}; | ||||
|   AbstractFile.AbstractIterator.prototype = { | ||||
|     /** | ||||
|      * Allow iterating with |for-of| | ||||
|      */ | ||||
|     [Symbol.iterator]() { | ||||
|       return this; | ||||
|     }, | ||||
|     /** | ||||
|      * Apply a function to all elements of the directory sequentially. | ||||
|      * | ||||
|      * @param {Function} cb This function will be applied to all entries | ||||
|      * of the directory. It receives as arguments | ||||
|      *  - the OS.File.Entry corresponding to the entry; | ||||
|      *  - the index of the entry in the enumeration; | ||||
|      *  - the iterator itself - calling |close| on the iterator stops | ||||
|      *   the loop. | ||||
|      */ | ||||
|     forEach: function forEach(cb) { | ||||
|       let index = 0; | ||||
|       for (let entry of this) { | ||||
|         cb(entry, index++, this); | ||||
|       } | ||||
|     }, | ||||
|     /** | ||||
|      * Return several entries at once. | ||||
|      * | ||||
|      * Entries are returned in the same order as a walk with |forEach| or | ||||
|      * |for(...)|. | ||||
|      * | ||||
|      * @param {number=} length If specified, the number of entries | ||||
|      * to return. If unspecified, return all remaining entries. | ||||
|      * @return {Array} An array containing the next |length| entries, or | ||||
|      * less if the iteration contains less than |length| entries left. | ||||
|      */ | ||||
|     nextBatch: function nextBatch(length) { | ||||
|       let array = []; | ||||
|       let i = 0; | ||||
|       for (let entry of this) { | ||||
|         array.push(entry); | ||||
|         if (++i >= length) { | ||||
|           return array; | ||||
|         } | ||||
|       } | ||||
|       return array; | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 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, | ||||
|       append: true, | ||||
|     }; | ||||
|     for (let key in mode) { | ||||
|       let val = !!mode[key]; // bool cast.
 | ||||
|       switch (key) { | ||||
|         case "read": | ||||
|           result.read = val; | ||||
|           break; | ||||
|         case "write": | ||||
|           result.write = val; | ||||
|           break; | ||||
|         case "truncate": // fallthrough
 | ||||
|         case "trunc": | ||||
|           result.trunc = val; | ||||
|           result.write |= val; | ||||
|           break; | ||||
|         case "create": | ||||
|           result.create = val; | ||||
|           result.write |= val; | ||||
|           break; | ||||
|         case "existing": // fallthrough
 | ||||
|         case "exist": | ||||
|           result.existing = val; | ||||
|           break; | ||||
|         case "append": | ||||
|           result.append = val; | ||||
|           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; | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 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. DEPRECATED - please use options.bytes instead. | ||||
|    * @param {object=} options Optionally, an object with some of the following | ||||
|    * fields: | ||||
|    * - {number} bytes An upper bound to the number of bytes to read. | ||||
|    * - {string} compression If "lz4" and if the file is compressed using the lz4 | ||||
|    *   compression algorithm, decompress the file contents on the fly. | ||||
|    * | ||||
|    * @return {Uint8Array} A buffer holding the bytes | ||||
|    * and the number of bytes read from the file. | ||||
|    */ | ||||
|   AbstractFile.read = function read(path, bytes, options = {}) { | ||||
|     if (bytes && typeof bytes == "object") { | ||||
|       options = bytes; | ||||
|       bytes = options.bytes || null; | ||||
|     } | ||||
|     if ("encoding" in options && typeof options.encoding != "string") { | ||||
|       throw new TypeError("Invalid type for option encoding"); | ||||
|     } | ||||
|     if ("compression" in options && typeof options.compression != "string") { | ||||
|       throw new TypeError( | ||||
|         "Invalid type for option compression: " + options.compression | ||||
|       ); | ||||
|     } | ||||
|     if ("bytes" in options && typeof options.bytes != "number") { | ||||
|       throw new TypeError("Invalid type for option bytes"); | ||||
|     } | ||||
|     let file = exports.OS.File.open(path); | ||||
|     try { | ||||
|       let buffer = file.read(bytes, options); | ||||
|       if ("compression" in options) { | ||||
|         if (options.compression == "lz4") { | ||||
|           options = Object.create(options); | ||||
|           options.path = path; | ||||
|           buffer = Lz4.decompressFileContent(buffer, options); | ||||
|         } else { | ||||
|           throw OS.File.Error.invalidArgument("Compression"); | ||||
|         } | ||||
|       } | ||||
|       if (!("encoding" in options)) { | ||||
|         return buffer; | ||||
|       } | ||||
|       let decoder; | ||||
|       try { | ||||
|         decoder = new TextDecoder(options.encoding); | ||||
|       } catch (ex) { | ||||
|         if (ex instanceof RangeError) { | ||||
|           throw OS.File.Error.invalidArgument("Decode"); | ||||
|         } else { | ||||
|           throw ex; | ||||
|         } | ||||
|       } | ||||
|       return decoder.decode(buffer); | ||||
|     } finally { | ||||
|       file.close(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Write a file, atomically. | ||||
|    * | ||||
|    * By opposition to a regular |write|, this operation ensures that, | ||||
|    * until the contents are fully written, the destination file is | ||||
|    * not modified. | ||||
|    * | ||||
|    * Limitation: In a few extreme cases (hardware failure during the | ||||
|    * write, user unplugging disk during the write, etc.), data may be | ||||
|    * corrupted. If your data is user-critical (e.g. preferences, | ||||
|    * application data, etc.), you may wish to consider adding options | ||||
|    * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as | ||||
|    * detailed below. Note that no combination of options can be | ||||
|    * guaranteed to totally eliminate the risk of corruption. | ||||
|    * | ||||
|    * @param {string} path The path of the file to modify. | ||||
|    * @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} bytes The number of bytes to write. If unspecified, | ||||
|    * |buffer.byteLength|. Required if |buffer| is a C pointer. | ||||
|    * - {string} tmpPath If |null| or unspecified, write all data directly | ||||
|    * to |path|. If specified, write all data to a temporary file called | ||||
|    * |tmpPath| and, once this write is complete, rename the file to | ||||
|    * replace |path|. Performing this additional operation is a little | ||||
|    * slower but also a little safer. | ||||
|    * - {bool} noOverwrite - If set, this function will fail if a file already | ||||
|    * exists at |path|. | ||||
|    * - {bool} flush - If |false| or unspecified, return immediately once the | ||||
|    * write is complete. If |true|, before writing, force the operating system | ||||
|    * to write its internal disk buffers to the disk. This is considerably slower | ||||
|    * (not just for the application but for the whole system) but also safer: | ||||
|    * if the system shuts down improperly (typically due to a kernel freeze | ||||
|    * or a power failure) or if the device is disconnected before the buffer | ||||
|    * is flushed, the file has more chances of not being corrupted. | ||||
|    * - {string} compression - If empty or unspecified, do not compress the file. | ||||
|    * If "lz4", compress the contents of the file atomically using lz4. For the | ||||
|    * time being, the container format is specific to Mozilla and cannot be read | ||||
|    * by means other than OS.File.read(..., { compression: "lz4"}) | ||||
|    * - {string} backupTo - If specified, backup the destination file as |backupTo|. | ||||
|    * Note that this function renames the destination file before overwriting it. | ||||
|    * If the process or the operating system freezes or crashes | ||||
|    * during the short window between these operations, | ||||
|    * the destination file will have been moved to its backup. | ||||
|    * | ||||
|    * @return {number} The number of bytes actually written. | ||||
|    */ | ||||
|   AbstractFile.writeAtomic = function writeAtomic(path, buffer, options = {}) { | ||||
|     // Verify that path is defined and of the correct type
 | ||||
|     if (typeof path != "string" || path == "") { | ||||
|       throw new TypeError("File path should be a (non-empty) string"); | ||||
|     } | ||||
|     let noOverwrite = options.noOverwrite; | ||||
|     if (noOverwrite && OS.File.exists(path)) { | ||||
|       throw OS.File.Error.exists("writeAtomic", path); | ||||
|     } | ||||
| 
 | ||||
|     if (typeof buffer == "string") { | ||||
|       // Normalize buffer to a C buffer by encoding it
 | ||||
|       buffer = new TextEncoder().encode(buffer); | ||||
|     } | ||||
| 
 | ||||
|     if ("compression" in options && options.compression == "lz4") { | ||||
|       buffer = Lz4.compressFileContent(buffer, options); | ||||
|       options = Object.create(options); | ||||
|       options.bytes = buffer.byteLength; | ||||
|     } | ||||
| 
 | ||||
|     let bytesWritten = 0; | ||||
| 
 | ||||
|     if (!options.tmpPath) { | ||||
|       if (options.backupTo) { | ||||
|         try { | ||||
|           OS.File.move(path, options.backupTo, { noCopy: true }); | ||||
|         } catch (ex) { | ||||
|           if (ex.becauseNoSuchFile) { | ||||
|             // The file doesn't exist, nothing to backup.
 | ||||
|           } else { | ||||
|             throw ex; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       // Just write, without any renaming trick
 | ||||
|       let dest = OS.File.open(path, { write: true, truncate: true }); | ||||
|       try { | ||||
|         bytesWritten = dest.write(buffer, options); | ||||
|         if (options.flush) { | ||||
|           dest.flush(); | ||||
|         } | ||||
|       } finally { | ||||
|         dest.close(); | ||||
|       } | ||||
|       return bytesWritten; | ||||
|     } | ||||
| 
 | ||||
|     let tmpFile = OS.File.open(options.tmpPath, { | ||||
|       write: true, | ||||
|       truncate: true, | ||||
|     }); | ||||
|     try { | ||||
|       bytesWritten = tmpFile.write(buffer, options); | ||||
|       if (options.flush) { | ||||
|         tmpFile.flush(); | ||||
|       } | ||||
|     } catch (x) { | ||||
|       OS.File.remove(options.tmpPath); | ||||
|       throw x; | ||||
|     } finally { | ||||
|       tmpFile.close(); | ||||
|     } | ||||
| 
 | ||||
|     if (options.backupTo) { | ||||
|       try { | ||||
|         OS.File.move(path, options.backupTo, { noCopy: true }); | ||||
|       } catch (ex) { | ||||
|         if (ex.becauseNoSuchFile) { | ||||
|           // The file doesn't exist, nothing to backup.
 | ||||
|         } else { | ||||
|           throw ex; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     OS.File.move(options.tmpPath, path, { noCopy: true }); | ||||
|     return bytesWritten; | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * This function is used by removeDir to avoid calling lstat for each | ||||
|    * files under the specified directory. External callers should not call | ||||
|    * this function directly. | ||||
|    */ | ||||
|   AbstractFile.removeRecursive = function(path, options = {}) { | ||||
|     let iterator = new OS.File.DirectoryIterator(path); | ||||
|     if (!iterator.exists()) { | ||||
|       if (!("ignoreAbsent" in options) || options.ignoreAbsent) { | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       for (let entry of iterator) { | ||||
|         if (entry.isDir) { | ||||
|           if (entry.isLink) { | ||||
|             // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
 | ||||
|             // directories are directories themselves. OS.File.remove()
 | ||||
|             // will not work for them.
 | ||||
|             OS.File.removeEmptyDir(entry.path, options); | ||||
|           } else { | ||||
|             // Normal directories.
 | ||||
|             AbstractFile.removeRecursive(entry.path, options); | ||||
|           } | ||||
|         } else { | ||||
|           // NTFS symlinks to files, Unix symlinks, or regular files.
 | ||||
|           OS.File.remove(entry.path, options); | ||||
|         } | ||||
|       } | ||||
|     } finally { | ||||
|       iterator.close(); | ||||
|     } | ||||
| 
 | ||||
|     OS.File.removeEmptyDir(path); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Create a directory and, optionally, its parent directories. | ||||
|    * | ||||
|    * @param {string} path The name of the directory. | ||||
|    * @param {*=} options Additional options. | ||||
|    * | ||||
|    * - {string} from If specified, the call to |makeDir| creates all the | ||||
|    * ancestors of |path| that are descendants of |from|. Note that |path| | ||||
|    * must be a descendant of |from|, and that |from| and its existing | ||||
|    * subdirectories present in |path|  must be user-writeable. | ||||
|    * Example: | ||||
|    *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir }); | ||||
|    *  creates directories profileDir/foo, profileDir/foo/bar | ||||
|    * - {bool} ignoreExisting If |false|, throw an error if the directory | ||||
|    * already exists. |true| by default. Ignored if |from| is specified. | ||||
|    * - {number} unixMode Under Unix, if specified, a file creation mode, | ||||
|    * as per libc function |mkdir|. If unspecified, dirs are | ||||
|    * created with a default mode of 0700 (dir is private to | ||||
|    * the user, the user can read, write and execute). Ignored under Windows | ||||
|    * or if the file system does not support file creation modes. | ||||
|    * - {C pointer} winSecurity Under Windows, if specified, security | ||||
|    * attributes as per winapi function |CreateDirectory|. If | ||||
|    * unspecified, use the default security descriptor, inherited from | ||||
|    * the parent directory. Ignored under Unix or if the file system | ||||
|    * does not support security descriptors. | ||||
|    */ | ||||
|   AbstractFile.makeDir = function(path, options = {}) { | ||||
|     let from = options.from; | ||||
|     if (!from) { | ||||
|       OS.File._makeDir(path, options); | ||||
|       return; | ||||
|     } | ||||
|     if (!path.startsWith(from)) { | ||||
|       // Apparently, `from` is not a parent of `path`. However, we may
 | ||||
|       // have false negatives due to non-normalized paths, e.g.
 | ||||
|       // "foo//bar" is a parent of "foo/bar/sna".
 | ||||
|       path = Path.normalize(path); | ||||
|       from = Path.normalize(from); | ||||
|       if (!path.startsWith(from)) { | ||||
|         throw new Error( | ||||
|           "Incorrect use of option |from|: " + | ||||
|             path + | ||||
|             " is not a descendant of " + | ||||
|             from | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|     let innerOptions = Object.create(options, { | ||||
|       ignoreExisting: { | ||||
|         value: true, | ||||
|       }, | ||||
|     }); | ||||
|     // Compute the elements that appear in |path| but not in |from|.
 | ||||
|     let items = Path.split(path).components.slice( | ||||
|       Path.split(from).components.length | ||||
|     ); | ||||
|     let current = from; | ||||
|     for (let item of items) { | ||||
|       current = Path.join(current, item); | ||||
|       OS.File._makeDir(current, innerOptions); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   if (!exports.OS.Shared) { | ||||
|     exports.OS.Shared = {}; | ||||
|   } | ||||
|   exports.OS.Shared.AbstractFile = AbstractFile; | ||||
| })(this); | ||||
|  | @ -1,412 +0,0 @@ | |||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||||
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||||
| 
 | ||||
| /** | ||||
|  * This module defines the thread-agnostic components of the Unix version | ||||
|  * of OS.File. It depends on the thread-agnostic cross-platform components | ||||
|  * of OS.File. | ||||
|  * | ||||
|  * It serves the following purposes: | ||||
|  * - open libc; | ||||
|  * - define OS.Unix.Error; | ||||
|  * - define a few constants and types that need to be defined on all platforms. | ||||
|  * | ||||
|  * This module can be: | ||||
|  * - opened from the main thread as a jsm module; | ||||
|  * - opened from a chrome worker through require(). | ||||
|  */ | ||||
| 
 | ||||
| /* eslint-env node */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var SharedAll; | ||||
| if (typeof Components != "undefined") { | ||||
|   // Module is opened as a jsm module
 | ||||
|   const { ctypes } = ChromeUtils.importESModule( | ||||
|     "resource://gre/modules/ctypes.sys.mjs" | ||||
|   ); | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.ctypes = ctypes; | ||||
| 
 | ||||
|   SharedAll = ChromeUtils.import( | ||||
|     "resource://gre/modules/osfile/osfile_shared_allthreads.jsm" | ||||
|   ); | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.exports = {}; | ||||
| } else if (typeof module != "undefined" && typeof require != "undefined") { | ||||
|   // Module is loaded with require()
 | ||||
|   SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); | ||||
| } else { | ||||
|   throw new Error( | ||||
|     "Please open this module with Component.utils.import or with require()" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| SharedAll.LOG.bind(SharedAll, "Unix", "allthreads"); | ||||
| var Const = SharedAll.Constants.libc; | ||||
| 
 | ||||
| // Open libc
 | ||||
| var libc = new SharedAll.Library( | ||||
|   "libc", | ||||
|   "libc.so", | ||||
|   "libSystem.B.dylib", | ||||
|   "a.out" | ||||
| ); | ||||
| exports.libc = libc; | ||||
| 
 | ||||
| // Define declareFFI
 | ||||
| var declareFFI = SharedAll.declareFFI.bind(null, libc); | ||||
| exports.declareFFI = declareFFI; | ||||
| 
 | ||||
| // Define lazy binding
 | ||||
| var LazyBindings = {}; | ||||
| libc.declareLazy( | ||||
|   LazyBindings, | ||||
|   "strerror", | ||||
|   "strerror", | ||||
|   ctypes.default_abi, | ||||
|   /* return*/ ctypes.char.ptr, | ||||
|   /* errnum*/ ctypes.int | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * 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| | ||||
|  * getters. To determine the operation that failed, use field | ||||
|  * |operation|. | ||||
|  * | ||||
|  * Additionally, this implementation offers a field | ||||
|  * |unixErrno|, which holds the OS-specific error | ||||
|  * constant. If you need this level of detail, you may match the value | ||||
|  * of this field against the error constants of |OS.Constants.libc|. | ||||
|  * | ||||
|  * @param {string=} operation The operation that failed. If unspecified, | ||||
|  * the name of the calling function is taken to be the operation that | ||||
|  * failed. | ||||
|  * @param {number=} lastError The OS-specific constant detailing the | ||||
|  * reason of the error. If unspecified, this is fetched from the system | ||||
|  * status. | ||||
|  * @param {string=} path The file path that manipulated. If unspecified, | ||||
|  * assign the empty string. | ||||
|  * | ||||
|  * @constructor | ||||
|  * @extends {OS.Shared.Error} | ||||
|  */ | ||||
| var OSError = function OSError( | ||||
|   operation = "unknown operation", | ||||
|   errno = ctypes.errno, | ||||
|   path = "" | ||||
| ) { | ||||
|   SharedAll.OSError.call(this, operation, path); | ||||
|   this.unixErrno = errno; | ||||
| }; | ||||
| OSError.prototype = Object.create(SharedAll.OSError.prototype); | ||||
| OSError.prototype.toString = function toString() { | ||||
|   return ( | ||||
|     "Unix error " + | ||||
|     this.unixErrno + | ||||
|     " during operation " + | ||||
|     this.operation + | ||||
|     (this.path ? " on file " + this.path : "") + | ||||
|     " (" + | ||||
|     LazyBindings.strerror(this.unixErrno).readStringReplaceMalformed() + | ||||
|     ")" | ||||
|   ); | ||||
| }; | ||||
| OSError.prototype.toMsg = function toMsg() { | ||||
|   return OSError.toMsg(this); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * |true| if the error was raised because a file or directory | ||||
|  * already exists, |false| otherwise. | ||||
|  */ | ||||
| Object.defineProperty(OSError.prototype, "becauseExists", { | ||||
|   get: function becauseExists() { | ||||
|     return this.unixErrno == Const.EEXIST; | ||||
|   }, | ||||
| }); | ||||
| /** | ||||
|  * |true| if the error was raised because a file or directory | ||||
|  * does not exist, |false| otherwise. | ||||
|  */ | ||||
| Object.defineProperty(OSError.prototype, "becauseNoSuchFile", { | ||||
|   get: function becauseNoSuchFile() { | ||||
|     return this.unixErrno == Const.ENOENT; | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * |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 == Const.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 == Const.EBADF; | ||||
|   }, | ||||
| }); | ||||
| /** | ||||
|  * |true| if the error was raised because permission is denied to | ||||
|  * access a file or directory, |false| otherwise. | ||||
|  */ | ||||
| Object.defineProperty(OSError.prototype, "becauseAccessDenied", { | ||||
|   get: function becauseAccessDenied() { | ||||
|     return this.unixErrno == Const.EACCES; | ||||
|   }, | ||||
| }); | ||||
| /** | ||||
|  * |true| if the error was raised because some invalid argument was passed, | ||||
|  * |false| otherwise. | ||||
|  */ | ||||
| Object.defineProperty(OSError.prototype, "becauseInvalidArgument", { | ||||
|   get: function becauseInvalidArgument() { | ||||
|     return this.unixErrno == Const.EINVAL; | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Serialize an instance of OSError to something that can be | ||||
|  * transmitted across threads (not necessarily a string). | ||||
|  */ | ||||
| OSError.toMsg = function toMsg(error) { | ||||
|   return { | ||||
|     exn: "OS.File.Error", | ||||
|     fileName: error.moduleName, | ||||
|     lineNumber: error.lineNumber, | ||||
|     stack: error.moduleStack, | ||||
|     operation: error.operation, | ||||
|     unixErrno: error.unixErrno, | ||||
|     path: error.path, | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Deserialize a message back to an instance of OSError | ||||
|  */ | ||||
| OSError.fromMsg = function fromMsg(msg) { | ||||
|   let error = new OSError(msg.operation, msg.unixErrno, msg.path); | ||||
|   error.stack = msg.stack; | ||||
|   error.fileName = msg.fileName; | ||||
|   error.lineNumber = msg.lineNumber; | ||||
|   return error; | ||||
| }; | ||||
| exports.Error = OSError; | ||||
| 
 | ||||
| /** | ||||
|  * Code shared by implementations of File.Info on Unix | ||||
|  * | ||||
|  * @constructor | ||||
|  */ | ||||
| var AbstractInfo = function AbstractInfo( | ||||
|   path, | ||||
|   isDir, | ||||
|   isSymLink, | ||||
|   size, | ||||
|   lastAccessDate, | ||||
|   lastModificationDate, | ||||
|   unixLastStatusChangeDate, | ||||
|   unixOwner, | ||||
|   unixGroup, | ||||
|   unixMode | ||||
| ) { | ||||
|   this._path = path; | ||||
|   this._isDir = isDir; | ||||
|   this._isSymlLink = isSymLink; | ||||
|   this._size = size; | ||||
|   this._lastAccessDate = lastAccessDate; | ||||
|   this._lastModificationDate = lastModificationDate; | ||||
|   this._unixLastStatusChangeDate = unixLastStatusChangeDate; | ||||
|   this._unixOwner = unixOwner; | ||||
|   this._unixGroup = unixGroup; | ||||
|   this._unixMode = unixMode; | ||||
| }; | ||||
| 
 | ||||
| AbstractInfo.prototype = { | ||||
|   /** | ||||
|    * The path of the file, used for error-reporting. | ||||
|    * | ||||
|    * @type {string} | ||||
|    */ | ||||
|   get path() { | ||||
|     return this._path; | ||||
|   }, | ||||
|   /** | ||||
|    * |true| if this file is a directory, |false| otherwise | ||||
|    */ | ||||
|   get isDir() { | ||||
|     return this._isDir; | ||||
|   }, | ||||
|   /** | ||||
|    * |true| if this file is a symbolink link, |false| otherwise | ||||
|    */ | ||||
|   get isSymLink() { | ||||
|     return this._isSymlLink; | ||||
|   }, | ||||
|   /** | ||||
|    * 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() { | ||||
|     return this._size; | ||||
|   }, | ||||
|   /** | ||||
|    * 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() { | ||||
|     return this._lastAccessDate; | ||||
|   }, | ||||
|   /** | ||||
|    * Return the date of last modification of this file. | ||||
|    */ | ||||
|   get lastModificationDate() { | ||||
|     return this._lastModificationDate; | ||||
|   }, | ||||
|   /** | ||||
|    * Return the date at which the status of this file was last modified | ||||
|    * (this is the date of the latest write/renaming/mode change/... | ||||
|    * of the file) | ||||
|    */ | ||||
|   get unixLastStatusChangeDate() { | ||||
|     return this._unixLastStatusChangeDate; | ||||
|   }, | ||||
|   /* | ||||
|    * Return the Unix owner of this file | ||||
|    */ | ||||
|   get unixOwner() { | ||||
|     return this._unixOwner; | ||||
|   }, | ||||
|   /* | ||||
|    * Return the Unix group of this file | ||||
|    */ | ||||
|   get unixGroup() { | ||||
|     return this._unixGroup; | ||||
|   }, | ||||
|   /* | ||||
|    * Return the Unix group of this file | ||||
|    */ | ||||
|   get unixMode() { | ||||
|     return this._unixMode; | ||||
|   }, | ||||
| }; | ||||
| exports.AbstractInfo = AbstractInfo; | ||||
| 
 | ||||
| /** | ||||
|  * Code shared by implementations of File.DirectoryIterator.Entry on Unix | ||||
|  * | ||||
|  * @constructor | ||||
|  */ | ||||
| var AbstractEntry = function AbstractEntry(isDir, isSymLink, name, path) { | ||||
|   this._isDir = isDir; | ||||
|   this._isSymlLink = isSymLink; | ||||
|   this._name = name; | ||||
|   this._path = path; | ||||
| }; | ||||
| 
 | ||||
| AbstractEntry.prototype = { | ||||
|   /** | ||||
|    * |true| if the entry is a directory, |false| otherwise | ||||
|    */ | ||||
|   get isDir() { | ||||
|     return this._isDir; | ||||
|   }, | ||||
|   /** | ||||
|    * |true| if the entry is a directory, |false| otherwise | ||||
|    */ | ||||
|   get isSymLink() { | ||||
|     return this._isSymlLink; | ||||
|   }, | ||||
|   /** | ||||
|    * The name of the entry | ||||
|    * @type {string} | ||||
|    */ | ||||
|   get name() { | ||||
|     return this._name; | ||||
|   }, | ||||
|   /** | ||||
|    * The full path to the entry | ||||
|    */ | ||||
|   get path() { | ||||
|     return this._path; | ||||
|   }, | ||||
| }; | ||||
| exports.AbstractEntry = AbstractEntry; | ||||
| 
 | ||||
| // Special constants that need to be defined on all platforms
 | ||||
| 
 | ||||
| exports.POS_START = Const.SEEK_SET; | ||||
| exports.POS_CURRENT = Const.SEEK_CUR; | ||||
| exports.POS_END = Const.SEEK_END; | ||||
| 
 | ||||
| // Special types that need to be defined for communication
 | ||||
| // between threads
 | ||||
| var Type = Object.create(SharedAll.Type); | ||||
| exports.Type = Type; | ||||
| 
 | ||||
| /** | ||||
|  * Native paths | ||||
|  * | ||||
|  * Under Unix, expressed as C strings | ||||
|  */ | ||||
| Type.path = Type.cstring.withName("[in] path"); | ||||
| Type.out_path = Type.out_cstring.withName("[out] path"); | ||||
| 
 | ||||
| // Special constructors that need to be defined on all threads
 | ||||
| OSError.closed = function closed(operation, path) { | ||||
|   return new OSError(operation, Const.EBADF, path); | ||||
| }; | ||||
| 
 | ||||
| OSError.exists = function exists(operation, path) { | ||||
|   return new OSError(operation, Const.EEXIST, path); | ||||
| }; | ||||
| 
 | ||||
| OSError.noSuchFile = function noSuchFile(operation, path) { | ||||
|   return new OSError(operation, Const.ENOENT, path); | ||||
| }; | ||||
| 
 | ||||
| OSError.invalidArgument = function invalidArgument(operation) { | ||||
|   return new OSError(operation, Const.EINVAL); | ||||
| }; | ||||
| 
 | ||||
| var EXPORTED_SYMBOLS = [ | ||||
|   "declareFFI", | ||||
|   "libc", | ||||
|   "Error", | ||||
|   "AbstractInfo", | ||||
|   "AbstractEntry", | ||||
|   "Type", | ||||
|   "POS_START", | ||||
|   "POS_CURRENT", | ||||
|   "POS_END", | ||||
| ]; | ||||
| 
 | ||||
| // ////////// Boilerplate
 | ||||
| if (typeof Components != "undefined") { | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS; | ||||
|   for (let symbol of EXPORTED_SYMBOLS) { | ||||
|     // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|     this[symbol] = exports[symbol]; | ||||
|   } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,444 +0,0 @@ | |||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||||
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||||
| 
 | ||||
| /** | ||||
|  * This module defines the thread-agnostic components of the Win version | ||||
|  * of OS.File. It depends on the thread-agnostic cross-platform components | ||||
|  * of OS.File. | ||||
|  * | ||||
|  * It serves the following purposes: | ||||
|  * - open kernel32; | ||||
|  * - define OS.Shared.Win.Error; | ||||
|  * - define a few constants and types that need to be defined on all platforms. | ||||
|  * | ||||
|  * This module can be: | ||||
|  * - opened from the main thread as a jsm module; | ||||
|  * - opened from a chrome worker through require(). | ||||
|  */ | ||||
| 
 | ||||
| /* eslint-env node */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var SharedAll; | ||||
| if (typeof Components != "undefined") { | ||||
|   // Module is opened as a jsm module
 | ||||
|   const { ctypes } = ChromeUtils.importESModule( | ||||
|     "resource://gre/modules/ctypes.sys.mjs" | ||||
|   ); | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.ctypes = ctypes; | ||||
| 
 | ||||
|   SharedAll = ChromeUtils.import( | ||||
|     "resource://gre/modules/osfile/osfile_shared_allthreads.jsm" | ||||
|   ); | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.exports = {}; | ||||
| } else if (typeof module != "undefined" && typeof require != "undefined") { | ||||
|   // Module is loaded with require()
 | ||||
|   SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); | ||||
| } else { | ||||
|   throw new Error( | ||||
|     "Please open this module with Component.utils.import or with require()" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| SharedAll.LOG.bind(SharedAll, "Win", "allthreads"); | ||||
| var Const = SharedAll.Constants.Win; | ||||
| 
 | ||||
| // Open libc
 | ||||
| var libc = new SharedAll.Library("libc", "kernel32.dll"); | ||||
| exports.libc = libc; | ||||
| 
 | ||||
| // Define declareFFI
 | ||||
| var declareFFI = SharedAll.declareFFI.bind(null, libc); | ||||
| exports.declareFFI = declareFFI; | ||||
| 
 | ||||
| var Scope = {}; | ||||
| 
 | ||||
| // Define Error
 | ||||
| libc.declareLazy( | ||||
|   Scope, | ||||
|   "FormatMessage", | ||||
|   "FormatMessageW", | ||||
|   ctypes.winapi_abi, | ||||
|   /* return*/ ctypes.uint32_t, | ||||
|   ctypes.uint32_t, | ||||
|   /* source*/ ctypes.voidptr_t, | ||||
|   ctypes.uint32_t, | ||||
|   /* langid*/ ctypes.uint32_t, | ||||
|   ctypes.char16_t.ptr, | ||||
|   ctypes.uint32_t, | ||||
|   /* Arguments*/ ctypes.voidptr_t | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * 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| | ||||
|  * getters. To determine the operation that failed, use field | ||||
|  * |operation|. | ||||
|  * | ||||
|  * Additionally, this implementation offers a field | ||||
|  * |winLastError|, which holds the OS-specific error | ||||
|  * constant. If you need this level of detail, you may match the value | ||||
|  * of this field against the error constants of |OS.Constants.Win|. | ||||
|  * | ||||
|  * @param {string=} operation The operation that failed. If unspecified, | ||||
|  * the name of the calling function is taken to be the operation that | ||||
|  * failed. | ||||
|  * @param {number=} lastError The OS-specific constant detailing the | ||||
|  * reason of the error. If unspecified, this is fetched from the system | ||||
|  * status. | ||||
|  * @param {string=} path The file path that manipulated. If unspecified, | ||||
|  * assign the empty string. | ||||
|  * | ||||
|  * @constructor | ||||
|  * @extends {OS.Shared.Error} | ||||
|  */ | ||||
| var OSError = function OSError( | ||||
|   operation = "unknown operation", | ||||
|   lastError = ctypes.winLastError, | ||||
|   path = "" | ||||
| ) { | ||||
|   SharedAll.OSError.call(this, operation, path); | ||||
|   this.winLastError = lastError; | ||||
| }; | ||||
| OSError.prototype = Object.create(SharedAll.OSError.prototype); | ||||
| OSError.prototype.toString = function toString() { | ||||
|   let buf = new (ctypes.ArrayType(ctypes.char16_t, 1024))(); | ||||
|   let result = Scope.FormatMessage( | ||||
|     Const.FORMAT_MESSAGE_FROM_SYSTEM | Const.FORMAT_MESSAGE_IGNORE_INSERTS, | ||||
|     null, | ||||
|     /* The error number */ this.winLastError, | ||||
|     /* Default language */ 0, | ||||
|     buf, | ||||
|     /* Minimum size of buffer */ 1024, | ||||
|     null | ||||
|   ); | ||||
|   if (!result) { | ||||
|     buf = | ||||
|       "additional error " + | ||||
|       ctypes.winLastError + | ||||
|       " while fetching system error message"; | ||||
|   } | ||||
|   return ( | ||||
|     "Win error " + | ||||
|     this.winLastError + | ||||
|     " during operation " + | ||||
|     this.operation + | ||||
|     (this.path ? " on file " + this.path : "") + | ||||
|     " (" + | ||||
|     buf.readString() + | ||||
|     ")" | ||||
|   ); | ||||
| }; | ||||
| OSError.prototype.toMsg = function toMsg() { | ||||
|   return OSError.toMsg(this); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * |true| if the error was raised because a file or directory | ||||
|  * already exists, |false| otherwise. | ||||
|  */ | ||||
| Object.defineProperty(OSError.prototype, "becauseExists", { | ||||
|   get: function becauseExists() { | ||||
|     return ( | ||||
|       this.winLastError == Const.ERROR_FILE_EXISTS || | ||||
|       this.winLastError == Const.ERROR_ALREADY_EXISTS | ||||
|     ); | ||||
|   }, | ||||
| }); | ||||
| /** | ||||
|  * |true| if the error was raised because a file or directory | ||||
|  * does not exist, |false| otherwise. | ||||
|  */ | ||||
| Object.defineProperty(OSError.prototype, "becauseNoSuchFile", { | ||||
|   get: function becauseNoSuchFile() { | ||||
|     return ( | ||||
|       this.winLastError == Const.ERROR_FILE_NOT_FOUND || | ||||
|       this.winLastError == Const.ERROR_PATH_NOT_FOUND | ||||
|     ); | ||||
|   }, | ||||
| }); | ||||
| /** | ||||
|  * |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 == Const.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 == Const.ERROR_INVALID_HANDLE; | ||||
|   }, | ||||
| }); | ||||
| /** | ||||
|  * |true| if the error was raised because permission is denied to | ||||
|  * access a file or directory, |false| otherwise. | ||||
|  */ | ||||
| Object.defineProperty(OSError.prototype, "becauseAccessDenied", { | ||||
|   get: function becauseAccessDenied() { | ||||
|     return this.winLastError == Const.ERROR_ACCESS_DENIED; | ||||
|   }, | ||||
| }); | ||||
| /** | ||||
|  * |true| if the error was raised because some invalid argument was passed, | ||||
|  * |false| otherwise. | ||||
|  */ | ||||
| Object.defineProperty(OSError.prototype, "becauseInvalidArgument", { | ||||
|   get: function becauseInvalidArgument() { | ||||
|     return ( | ||||
|       this.winLastError == Const.ERROR_NOT_SUPPORTED || | ||||
|       this.winLastError == Const.ERROR_BAD_ARGUMENTS | ||||
|     ); | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Serialize an instance of OSError to something that can be | ||||
|  * transmitted across threads (not necessarily a string). | ||||
|  */ | ||||
| OSError.toMsg = function toMsg(error) { | ||||
|   return { | ||||
|     exn: "OS.File.Error", | ||||
|     fileName: error.moduleName, | ||||
|     lineNumber: error.lineNumber, | ||||
|     stack: error.moduleStack, | ||||
|     operation: error.operation, | ||||
|     winLastError: error.winLastError, | ||||
|     path: error.path, | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Deserialize a message back to an instance of OSError | ||||
|  */ | ||||
| OSError.fromMsg = function fromMsg(msg) { | ||||
|   let error = new OSError(msg.operation, msg.winLastError, msg.path); | ||||
|   error.stack = msg.stack; | ||||
|   error.fileName = msg.fileName; | ||||
|   error.lineNumber = msg.lineNumber; | ||||
|   return error; | ||||
| }; | ||||
| exports.Error = OSError; | ||||
| 
 | ||||
| /** | ||||
|  * Code shared by implementation of File.Info on Windows | ||||
|  * | ||||
|  * @constructor | ||||
|  */ | ||||
| var AbstractInfo = function AbstractInfo( | ||||
|   path, | ||||
|   isDir, | ||||
|   isSymLink, | ||||
|   size, | ||||
|   lastAccessDate, | ||||
|   lastWriteDate, | ||||
|   winAttributes | ||||
| ) { | ||||
|   this._path = path; | ||||
|   this._isDir = isDir; | ||||
|   this._isSymLink = isSymLink; | ||||
|   this._size = size; | ||||
|   this._lastAccessDate = lastAccessDate; | ||||
|   this._lastModificationDate = lastWriteDate; | ||||
|   this._winAttributes = winAttributes; | ||||
| }; | ||||
| 
 | ||||
| AbstractInfo.prototype = { | ||||
|   /** | ||||
|    * The path of the file, used for error-reporting. | ||||
|    * | ||||
|    * @type {string} | ||||
|    */ | ||||
|   get path() { | ||||
|     return this._path; | ||||
|   }, | ||||
|   /** | ||||
|    * |true| if this file is a directory, |false| otherwise | ||||
|    */ | ||||
|   get isDir() { | ||||
|     return this._isDir; | ||||
|   }, | ||||
|   /** | ||||
|    * |true| if this file is a symbolic link, |false| otherwise | ||||
|    */ | ||||
|   get isSymLink() { | ||||
|     return this._isSymLink; | ||||
|   }, | ||||
|   /** | ||||
|    * 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() { | ||||
|     return this._size; | ||||
|   }, | ||||
|   /** | ||||
|    * 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() { | ||||
|     return this._lastAccessDate; | ||||
|   }, | ||||
|   /** | ||||
|    * 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() { | ||||
|     return this._lastModificationDate; | ||||
|   }, | ||||
|   /** | ||||
|    * The Object with following boolean properties of this file. | ||||
|    * {readOnly, system, hidden} | ||||
|    * | ||||
|    * @type {object} | ||||
|    */ | ||||
|   get winAttributes() { | ||||
|     return this._winAttributes; | ||||
|   }, | ||||
| }; | ||||
| exports.AbstractInfo = AbstractInfo; | ||||
| 
 | ||||
| /** | ||||
|  * Code shared by implementation of File.DirectoryIterator.Entry on Windows | ||||
|  * | ||||
|  * @constructor | ||||
|  */ | ||||
| var AbstractEntry = function AbstractEntry( | ||||
|   isDir, | ||||
|   isSymLink, | ||||
|   name, | ||||
|   winLastWriteDate, | ||||
|   winLastAccessDate, | ||||
|   path | ||||
| ) { | ||||
|   this._isDir = isDir; | ||||
|   this._isSymLink = isSymLink; | ||||
|   this._name = name; | ||||
|   this._winLastWriteDate = winLastWriteDate; | ||||
|   this._winLastAccessDate = winLastAccessDate; | ||||
|   this._path = path; | ||||
| }; | ||||
| 
 | ||||
| AbstractEntry.prototype = { | ||||
|   /** | ||||
|    * |true| if the entry is a directory, |false| otherwise | ||||
|    */ | ||||
|   get isDir() { | ||||
|     return this._isDir; | ||||
|   }, | ||||
|   /** | ||||
|    * |true| if the entry is a symbolic link, |false| otherwise | ||||
|    */ | ||||
|   get isSymLink() { | ||||
|     return this._isSymLink; | ||||
|   }, | ||||
|   /** | ||||
|    * The name of the entry. | ||||
|    * @type {string} | ||||
|    */ | ||||
|   get name() { | ||||
|     return this._name; | ||||
|   }, | ||||
|   /** | ||||
|    * The last modification time of this file. | ||||
|    * @type {Date} | ||||
|    */ | ||||
|   get winLastWriteDate() { | ||||
|     return this._winLastWriteDate; | ||||
|   }, | ||||
|   /** | ||||
|    * The last access time of this file. | ||||
|    * @type {Date} | ||||
|    */ | ||||
|   get winLastAccessDate() { | ||||
|     return this._winLastAccessDate; | ||||
|   }, | ||||
|   /** | ||||
|    * The full path of the entry | ||||
|    * @type {string} | ||||
|    */ | ||||
|   get path() { | ||||
|     return this._path; | ||||
|   }, | ||||
| }; | ||||
| exports.AbstractEntry = AbstractEntry; | ||||
| 
 | ||||
| // Special constants that need to be defined on all platforms
 | ||||
| 
 | ||||
| exports.POS_START = Const.FILE_BEGIN; | ||||
| exports.POS_CURRENT = Const.FILE_CURRENT; | ||||
| exports.POS_END = Const.FILE_END; | ||||
| 
 | ||||
| // Special types that need to be defined for communication
 | ||||
| // between threads
 | ||||
| var Type = Object.create(SharedAll.Type); | ||||
| exports.Type = Type; | ||||
| 
 | ||||
| /** | ||||
|  * Native paths | ||||
|  * | ||||
|  * Under Windows, expressed as wide strings | ||||
|  */ | ||||
| Type.path = Type.wstring.withName("[in] path"); | ||||
| Type.out_path = Type.out_wstring.withName("[out] path"); | ||||
| 
 | ||||
| // Special constructors that need to be defined on all threads
 | ||||
| OSError.closed = function closed(operation, path) { | ||||
|   return new OSError(operation, Const.ERROR_INVALID_HANDLE, path); | ||||
| }; | ||||
| 
 | ||||
| OSError.exists = function exists(operation, path) { | ||||
|   return new OSError(operation, Const.ERROR_FILE_EXISTS, path); | ||||
| }; | ||||
| 
 | ||||
| OSError.noSuchFile = function noSuchFile(operation, path) { | ||||
|   return new OSError(operation, Const.ERROR_FILE_NOT_FOUND, path); | ||||
| }; | ||||
| 
 | ||||
| OSError.invalidArgument = function invalidArgument(operation) { | ||||
|   return new OSError(operation, Const.ERROR_NOT_SUPPORTED); | ||||
| }; | ||||
| 
 | ||||
| var EXPORTED_SYMBOLS = [ | ||||
|   "declareFFI", | ||||
|   "libc", | ||||
|   "Error", | ||||
|   "AbstractInfo", | ||||
|   "AbstractEntry", | ||||
|   "Type", | ||||
|   "POS_START", | ||||
|   "POS_CURRENT", | ||||
|   "POS_END", | ||||
| ]; | ||||
| 
 | ||||
| // ////////// Boilerplate
 | ||||
| if (typeof Components != "undefined") { | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS; | ||||
|   for (let symbol of EXPORTED_SYMBOLS) { | ||||
|     // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|     this[symbol] = exports[symbol]; | ||||
|   } | ||||
| } | ||||
|  | @ -1,542 +0,0 @@ | |||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||||
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||||
| 
 | ||||
| /** | ||||
|  * This file can be used in the following contexts: | ||||
|  * | ||||
|  *  1. included from a non-osfile worker thread using importScript | ||||
|  *   (it serves to define a synchronous API for that worker thread) | ||||
|  *   (bug 707681) | ||||
|  * | ||||
|  *  2. included from the main thread using Components.utils.import | ||||
|  *   (it serves to define the asynchronous API, whose implementation | ||||
|  *    resides in the worker thread) | ||||
|  *   (bug 729057) | ||||
|  * | ||||
|  * 3. included from the osfile worker thread using importScript | ||||
|  *   (it serves to define the implementation of the asynchronous API) | ||||
|  *   (bug 729057) | ||||
|  */ | ||||
| 
 | ||||
| /* eslint-env mozilla/chrome-worker, node */ | ||||
| 
 | ||||
| // eslint-disable-next-line no-lone-blocks
 | ||||
| { | ||||
|   if (typeof Components != "undefined") { | ||||
|     // We do not wish osfile_win_back.js to be used directly as a main thread
 | ||||
|     // module yet. When time comes, it will be loaded by a combination of
 | ||||
|     // a main thread front-end/worker thread implementation that makes sure
 | ||||
|     // that we are not executing synchronous IO code in the main thread.
 | ||||
| 
 | ||||
|     throw new Error( | ||||
|       "osfile_win_back.js cannot be used from the main thread yet" | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   (function(exports) { | ||||
|     "use strict"; | ||||
|     if (exports.OS && exports.OS.Win && exports.OS.Win.File) { | ||||
|       return; // Avoid double initialization
 | ||||
|     } | ||||
| 
 | ||||
|     let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); | ||||
|     let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm"); | ||||
|     SharedAll.LOG.bind(SharedAll, "Unix", "back"); | ||||
|     let libc = SysAll.libc; | ||||
|     let advapi32 = new SharedAll.Library("advapi32", "advapi32.dll"); | ||||
|     let Const = SharedAll.Constants.Win; | ||||
| 
 | ||||
|     /** | ||||
|      * Initialize the Windows module. | ||||
|      * | ||||
|      * @param {function=} declareFFI | ||||
|      */ | ||||
|     // FIXME: Both |init| and |aDeclareFFI| are deprecated, we should remove them
 | ||||
|     let init = function init(aDeclareFFI) { | ||||
|       let declareFFI; | ||||
|       if (aDeclareFFI) { | ||||
|         declareFFI = aDeclareFFI.bind(null, libc); | ||||
|       } else { | ||||
|         declareFFI = SysAll.declareFFI; // eslint-disable-line no-unused-vars
 | ||||
|       } | ||||
|       let declareLazyFFI = SharedAll.declareLazyFFI; // eslint-disable-line no-unused-vars
 | ||||
| 
 | ||||
|       // Initialize types that require additional OS-specific
 | ||||
|       // support - either finalization or matching against
 | ||||
|       // OS-specific constants.
 | ||||
|       let Type = Object.create(SysAll.Type); | ||||
|       let SysFile = (exports.OS.Win.File = { Type }); | ||||
| 
 | ||||
|       // Initialize types
 | ||||
| 
 | ||||
|       /** | ||||
|        * A C integer holding INVALID_HANDLE_VALUE in case of error or | ||||
|        * a file descriptor in case of success. | ||||
|        */ | ||||
|       Type.HANDLE = Type.voidptr_t.withName("HANDLE"); | ||||
|       Type.HANDLE.importFromC = function importFromC(maybe) { | ||||
|         if (Type.int.cast(maybe).value == INVALID_HANDLE) { | ||||
|           // Ensure that API clients can effectively compare against
 | ||||
|           // Const.INVALID_HANDLE_VALUE. Without this cast,
 | ||||
|           // == would always return |false|.
 | ||||
|           return INVALID_HANDLE; | ||||
|         } | ||||
|         return ctypes.CDataFinalizer(maybe, this.finalizeHANDLE); | ||||
|       }; | ||||
|       Type.HANDLE.finalizeHANDLE = function placeholder() { | ||||
|         throw new Error("finalizeHANDLE should be implemented"); | ||||
|       }; | ||||
|       let INVALID_HANDLE = Const.INVALID_HANDLE_VALUE; | ||||
| 
 | ||||
|       Type.file_HANDLE = Type.HANDLE.withName("file HANDLE"); | ||||
|       SharedAll.defineLazyGetter( | ||||
|         Type.file_HANDLE, | ||||
|         "finalizeHANDLE", | ||||
|         function() { | ||||
|           return SysFile._CloseHandle; | ||||
|         } | ||||
|       ); | ||||
| 
 | ||||
|       Type.find_HANDLE = Type.HANDLE.withName("find HANDLE"); | ||||
|       SharedAll.defineLazyGetter( | ||||
|         Type.find_HANDLE, | ||||
|         "finalizeHANDLE", | ||||
|         function() { | ||||
|           return SysFile._FindClose; | ||||
|         } | ||||
|       ); | ||||
| 
 | ||||
|       Type.DWORD = Type.uint32_t.withName("DWORD"); | ||||
| 
 | ||||
|       /* A special type used to represent flags passed as DWORDs to a function. | ||||
|        * In JavaScript, bitwise manipulation of numbers, such as or-ing flags, | ||||
|        * can produce negative numbers. Since DWORD is unsigned, these negative | ||||
|        * numbers simply cannot be converted to DWORD. For this reason, whenever | ||||
|        * bit manipulation is called for, you should rather use DWORD_FLAGS, | ||||
|        * which is represented as a signed integer, hence has the correct | ||||
|        * semantics. | ||||
|        */ | ||||
|       Type.DWORD_FLAGS = Type.int32_t.withName("DWORD_FLAGS"); | ||||
| 
 | ||||
|       /** | ||||
|        * A C integer holding 0 in case of error or a positive integer | ||||
|        * in case of success. | ||||
|        */ | ||||
|       Type.zero_or_DWORD = Type.DWORD.withName("zero_or_DWORD"); | ||||
| 
 | ||||
|       /** | ||||
|        * A C integer holding 0 in case of error, any other value in | ||||
|        * case of success. | ||||
|        */ | ||||
|       Type.zero_or_nothing = Type.int.withName("zero_or_nothing"); | ||||
| 
 | ||||
|       /** | ||||
|        * A C integer holding flags related to NTFS security. | ||||
|        */ | ||||
|       Type.SECURITY_ATTRIBUTES = Type.void_t.withName("SECURITY_ATTRIBUTES"); | ||||
| 
 | ||||
|       /** | ||||
|        * A C integer holding pointers related to NTFS security. | ||||
|        */ | ||||
|       Type.PSID = Type.voidptr_t.withName("PSID"); | ||||
| 
 | ||||
|       Type.PACL = Type.voidptr_t.withName("PACL"); | ||||
| 
 | ||||
|       Type.PSECURITY_DESCRIPTOR = Type.voidptr_t.withName( | ||||
|         "PSECURITY_DESCRIPTOR" | ||||
|       ); | ||||
| 
 | ||||
|       /** | ||||
|        * A C integer holding Win32 local memory handle. | ||||
|        */ | ||||
|       Type.HLOCAL = Type.voidptr_t.withName("HLOCAL"); | ||||
| 
 | ||||
|       Type.FILETIME = new SharedAll.Type( | ||||
|         "FILETIME", | ||||
|         ctypes.StructType("FILETIME", [ | ||||
|           { lo: Type.DWORD.implementation }, | ||||
|           { hi: Type.DWORD.implementation }, | ||||
|         ]) | ||||
|       ); | ||||
| 
 | ||||
|       Type.FindData = new SharedAll.Type( | ||||
|         "FIND_DATA", | ||||
|         ctypes.StructType("FIND_DATA", [ | ||||
|           { dwFileAttributes: ctypes.uint32_t }, | ||||
|           { ftCreationTime: Type.FILETIME.implementation }, | ||||
|           { ftLastAccessTime: Type.FILETIME.implementation }, | ||||
|           { ftLastWriteTime: Type.FILETIME.implementation }, | ||||
|           { nFileSizeHigh: Type.DWORD.implementation }, | ||||
|           { nFileSizeLow: Type.DWORD.implementation }, | ||||
|           { dwReserved0: Type.DWORD.implementation }, | ||||
|           { dwReserved1: Type.DWORD.implementation }, | ||||
|           { cFileName: ctypes.ArrayType(ctypes.char16_t, Const.MAX_PATH) }, | ||||
|           { cAlternateFileName: ctypes.ArrayType(ctypes.char16_t, 14) }, | ||||
|         ]) | ||||
|       ); | ||||
| 
 | ||||
|       Type.FILE_INFORMATION = new SharedAll.Type( | ||||
|         "FILE_INFORMATION", | ||||
|         ctypes.StructType("FILE_INFORMATION", [ | ||||
|           { dwFileAttributes: ctypes.uint32_t }, | ||||
|           { ftCreationTime: Type.FILETIME.implementation }, | ||||
|           { ftLastAccessTime: Type.FILETIME.implementation }, | ||||
|           { ftLastWriteTime: Type.FILETIME.implementation }, | ||||
|           { dwVolumeSerialNumber: ctypes.uint32_t }, | ||||
|           { nFileSizeHigh: Type.DWORD.implementation }, | ||||
|           { nFileSizeLow: Type.DWORD.implementation }, | ||||
|           { nNumberOfLinks: ctypes.uint32_t }, | ||||
|           { nFileIndex: ctypes.uint64_t }, | ||||
|         ]) | ||||
|       ); | ||||
| 
 | ||||
|       Type.SystemTime = new SharedAll.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 }, | ||||
|           { wMinute: ctypes.int16_t }, | ||||
|           { wSecond: ctypes.int16_t }, | ||||
|           { wMilliSeconds: ctypes.int16_t }, | ||||
|         ]) | ||||
|       ); | ||||
| 
 | ||||
|       // Special case: these functions are used by the
 | ||||
|       // finalizer
 | ||||
|       libc.declareLazy( | ||||
|         SysFile, | ||||
|         "_CloseHandle", | ||||
|         "CloseHandle", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return */ ctypes.bool, | ||||
|         /* handle*/ ctypes.voidptr_t | ||||
|       ); | ||||
| 
 | ||||
|       SysFile.CloseHandle = function(fd) { | ||||
|         if (fd == INVALID_HANDLE) { | ||||
|           return true; | ||||
|         } | ||||
|         return fd.dispose(); // Returns the value of |CloseHandle|.
 | ||||
|       }; | ||||
| 
 | ||||
|       libc.declareLazy( | ||||
|         SysFile, | ||||
|         "_FindClose", | ||||
|         "FindClose", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return */ ctypes.bool, | ||||
|         /* handle*/ ctypes.voidptr_t | ||||
|       ); | ||||
| 
 | ||||
|       SysFile.FindClose = function(handle) { | ||||
|         if (handle == INVALID_HANDLE) { | ||||
|           return true; | ||||
|         } | ||||
|         return handle.dispose(); // Returns the value of |FindClose|.
 | ||||
|       }; | ||||
| 
 | ||||
|       // Declare libc functions as functions of |OS.Win.File|
 | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "CopyFile", | ||||
|         "CopyFileW", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         /* sourcePath*/ Type.path, | ||||
|         Type.path, | ||||
|         /* bailIfExist*/ Type.bool | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "CreateDirectory", | ||||
|         "CreateDirectoryW", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         Type.char16_t.in_ptr, | ||||
|         /* security*/ Type.SECURITY_ATTRIBUTES.in_ptr | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "CreateFile", | ||||
|         "CreateFileW", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.file_HANDLE, | ||||
|         Type.path, | ||||
|         Type.DWORD_FLAGS, | ||||
|         Type.DWORD_FLAGS, | ||||
|         /* security*/ Type.SECURITY_ATTRIBUTES.in_ptr, | ||||
|         /* creation*/ Type.DWORD_FLAGS, | ||||
|         Type.DWORD_FLAGS, | ||||
|         /* template*/ Type.HANDLE | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "DeleteFile", | ||||
|         "DeleteFileW", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         Type.path | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "FileTimeToSystemTime", | ||||
|         "FileTimeToSystemTime", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         /* filetime*/ Type.FILETIME.in_ptr, | ||||
|         /* systime*/ Type.SystemTime.out_ptr | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "SystemTimeToFileTime", | ||||
|         "SystemTimeToFileTime", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.zero_or_nothing, | ||||
|         Type.SystemTime.in_ptr, | ||||
|         /* filetime*/ Type.FILETIME.out_ptr | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "FindFirstFile", | ||||
|         "FindFirstFileW", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.find_HANDLE, | ||||
|         /* pattern*/ Type.path, | ||||
|         Type.FindData.out_ptr | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "FindNextFile", | ||||
|         "FindNextFileW", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         Type.find_HANDLE, | ||||
|         Type.FindData.out_ptr | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "FormatMessage", | ||||
|         "FormatMessageW", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.DWORD, | ||||
|         Type.DWORD_FLAGS, | ||||
|         /* source*/ Type.void_t.in_ptr, | ||||
|         Type.DWORD_FLAGS, | ||||
|         /* langid*/ Type.DWORD_FLAGS, | ||||
|         Type.out_wstring, | ||||
|         Type.DWORD, | ||||
|         /* Arguments*/ Type.void_t.in_ptr | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "GetCurrentDirectory", | ||||
|         "GetCurrentDirectoryW", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_DWORD, | ||||
|         /* length*/ Type.DWORD, | ||||
|         Type.out_path | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "GetFullPathName", | ||||
|         "GetFullPathNameW", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.zero_or_DWORD, | ||||
|         /* fileName*/ Type.path, | ||||
|         Type.DWORD, | ||||
|         Type.out_path, | ||||
|         /* filePart*/ Type.DWORD | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "GetDiskFreeSpaceEx", | ||||
|         "GetDiskFreeSpaceExW", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         /* directoryName*/ Type.path, | ||||
|         /* freeBytesForUser*/ Type.uint64_t.out_ptr, | ||||
|         /* totalBytesForUser*/ Type.uint64_t.out_ptr, | ||||
|         /* freeTotalBytesOnDrive*/ Type.uint64_t.out_ptr | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "GetFileInformationByHandle", | ||||
|         "GetFileInformationByHandle", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         /* handle*/ Type.HANDLE, | ||||
|         Type.FILE_INFORMATION.out_ptr | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "MoveFileEx", | ||||
|         "MoveFileExW", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.zero_or_nothing, | ||||
|         /* sourcePath*/ Type.path, | ||||
|         /* destPath*/ Type.path, | ||||
|         Type.DWORD | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "ReadFile", | ||||
|         "ReadFile", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         Type.HANDLE, | ||||
|         /* buffer*/ Type.voidptr_t, | ||||
|         /* nbytes*/ Type.DWORD, | ||||
|         /* nbytes_read*/ Type.DWORD.out_ptr, | ||||
|         /* overlapped*/ Type.void_t.inout_ptr // FIXME: Implement?
 | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "RemoveDirectory", | ||||
|         "RemoveDirectoryW", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         Type.path | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "SetEndOfFile", | ||||
|         "SetEndOfFile", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         Type.HANDLE | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "SetFilePointer", | ||||
|         "SetFilePointer", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.DWORD, | ||||
|         Type.HANDLE, | ||||
|         /* distlow*/ Type.long, | ||||
|         /* disthi*/ Type.long.in_ptr, | ||||
|         /* method*/ Type.DWORD | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "SetFileTime", | ||||
|         "SetFileTime", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.zero_or_nothing, | ||||
|         Type.HANDLE, | ||||
|         /* creation*/ Type.FILETIME.in_ptr, | ||||
|         Type.FILETIME.in_ptr, | ||||
|         Type.FILETIME.in_ptr | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "WriteFile", | ||||
|         "WriteFile", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         Type.HANDLE, | ||||
|         /* buffer*/ Type.voidptr_t, | ||||
|         /* nbytes*/ Type.DWORD, | ||||
|         /* nbytes_wr*/ Type.DWORD.out_ptr, | ||||
|         /* overlapped*/ Type.void_t.inout_ptr // FIXME: Implement?
 | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "FlushFileBuffers", | ||||
|         "FlushFileBuffers", | ||||
|         ctypes.winapi_abi, | ||||
|         /* return*/ Type.zero_or_nothing, | ||||
|         Type.HANDLE | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "GetFileAttributes", | ||||
|         "GetFileAttributesW", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.DWORD_FLAGS, | ||||
|         /* fileName*/ Type.path | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "SetFileAttributes", | ||||
|         "SetFileAttributesW", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.zero_or_nothing, | ||||
|         Type.path, | ||||
|         /* fileAttributes*/ Type.DWORD_FLAGS | ||||
|       ); | ||||
| 
 | ||||
|       advapi32.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "GetNamedSecurityInfo", | ||||
|         "GetNamedSecurityInfoW", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.DWORD, | ||||
|         Type.path, | ||||
|         Type.DWORD, | ||||
|         /* securityInfo*/ Type.DWORD, | ||||
|         Type.PSID.out_ptr, | ||||
|         Type.PSID.out_ptr, | ||||
|         Type.PACL.out_ptr, | ||||
|         Type.PACL.out_ptr, | ||||
|         /* securityDesc*/ Type.PSECURITY_DESCRIPTOR.out_ptr | ||||
|       ); | ||||
| 
 | ||||
|       advapi32.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "SetNamedSecurityInfo", | ||||
|         "SetNamedSecurityInfoW", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.DWORD, | ||||
|         Type.path, | ||||
|         Type.DWORD, | ||||
|         /* securityInfo*/ Type.DWORD, | ||||
|         Type.PSID, | ||||
|         Type.PSID, | ||||
|         Type.PACL, | ||||
|         Type.PACL | ||||
|       ); | ||||
| 
 | ||||
|       libc.declareLazyFFI( | ||||
|         SysFile, | ||||
|         "LocalFree", | ||||
|         "LocalFree", | ||||
|         ctypes.winapi_abi, | ||||
|         Type.HLOCAL, | ||||
|         Type.HLOCAL | ||||
|       ); | ||||
|     }; | ||||
| 
 | ||||
|     exports.OS.Win = { | ||||
|       File: { | ||||
|         _init: init, | ||||
|       }, | ||||
|     }; | ||||
|   })(this); | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,50 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| /** | ||||
|  * Handling native paths. | ||||
|  * | ||||
|  * This module contains a number of functions destined to simplify | ||||
|  * working with native paths through a cross-platform API. Functions | ||||
|  * of this module will only work with the following assumptions: | ||||
|  * | ||||
|  * - paths are valid; | ||||
|  * - paths are defined with one of the grammars that this module can | ||||
|  *   parse (see later); | ||||
|  * - all path concatenations go through function |join|. | ||||
|  */ | ||||
| 
 | ||||
| /* global OS */ | ||||
| /* eslint-env node */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| if (typeof Components == "undefined") { | ||||
|   let Path; | ||||
|   if (OS.Constants.Win) { | ||||
|     Path = require("resource://gre/modules/osfile/ospath_win.jsm"); | ||||
|   } else { | ||||
|     Path = require("resource://gre/modules/osfile/ospath_unix.jsm"); | ||||
|   } | ||||
|   module.exports = Path; | ||||
| } else { | ||||
|   let Scope = ChromeUtils.import( | ||||
|     "resource://gre/modules/osfile/osfile_shared_allthreads.jsm" | ||||
|   ); | ||||
| 
 | ||||
|   let Path; | ||||
|   if (Scope.OS.Constants.Win) { | ||||
|     Path = ChromeUtils.import("resource://gre/modules/osfile/ospath_win.jsm"); | ||||
|   } else { | ||||
|     Path = ChromeUtils.import("resource://gre/modules/osfile/ospath_unix.jsm"); | ||||
|   } | ||||
| 
 | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.EXPORTED_SYMBOLS = []; | ||||
|   for (let k in Path) { | ||||
|     EXPORTED_SYMBOLS.push(k); | ||||
|     // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|     this[k] = Path[k]; | ||||
|   } | ||||
| } | ||||
|  | @ -1,204 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| /** | ||||
|  * Handling native paths. | ||||
|  * | ||||
|  * This module contains a number of functions destined to simplify | ||||
|  * working with native paths through a cross-platform API. Functions | ||||
|  * of this module will only work with the following assumptions: | ||||
|  * | ||||
|  * - paths are valid; | ||||
|  * - paths are defined with one of the grammars that this module can | ||||
|  *   parse (see later); | ||||
|  * - all path concatenations go through function |join|. | ||||
|  */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| // Boilerplate used to be able to import this module both from the main
 | ||||
| // thread and from worker threads.
 | ||||
| if (typeof Components != "undefined") { | ||||
|   // Global definition of |exports|, to keep everybody happy.
 | ||||
|   // In non-main thread, |exports| is provided by the module
 | ||||
|   // loader.
 | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.exports = {}; | ||||
| } else if (typeof module == "undefined" || typeof exports == "undefined") { | ||||
|   throw new Error("Please load this module using require()"); | ||||
| } | ||||
| 
 | ||||
| var EXPORTED_SYMBOLS = [ | ||||
|   "basename", | ||||
|   "dirname", | ||||
|   "join", | ||||
|   "normalize", | ||||
|   "split", | ||||
|   "toFileURI", | ||||
|   "fromFileURI", | ||||
| ]; | ||||
| 
 | ||||
| /** | ||||
|  * Return the final part of the path. | ||||
|  * The final part of the path is everything after the last "/". | ||||
|  */ | ||||
| var basename = function(path) { | ||||
|   return path.slice(path.lastIndexOf("/") + 1); | ||||
| }; | ||||
| exports.basename = basename; | ||||
| 
 | ||||
| /** | ||||
|  * Return the directory part of the path. | ||||
|  * The directory part of the path is everything before the last | ||||
|  * "/". If the last few characters of this part are also "/", | ||||
|  * they are ignored. | ||||
|  * | ||||
|  * If the path contains no directory, return ".". | ||||
|  */ | ||||
| var dirname = function(path) { | ||||
|   let index = path.lastIndexOf("/"); | ||||
|   if (index == -1) { | ||||
|     return "."; | ||||
|   } | ||||
|   while (index >= 0 && path[index] == "/") { | ||||
|     --index; | ||||
|   } | ||||
|   return path.slice(0, index + 1); | ||||
| }; | ||||
| exports.dirname = dirname; | ||||
| 
 | ||||
| /** | ||||
|  * Join path components. | ||||
|  * This is the recommended manner of getting the path of a file/subdirectory | ||||
|  * in a directory. | ||||
|  * | ||||
|  * Example: Obtaining $TMP/foo/bar in an OS-independent manner | ||||
|  *  var tmpDir = OS.Constants.Path.tmpDir; | ||||
|  *  var path = OS.Path.join(tmpDir, "foo", "bar"); | ||||
|  * | ||||
|  * Under Unix, this will return "/tmp/foo/bar". | ||||
|  * | ||||
|  * Empty components are ignored, i.e. `OS.Path.join("foo", "", "bar)` is the | ||||
|  * same as `OS.Path.join("foo", "bar")`. | ||||
|  */ | ||||
| var join = function(...path) { | ||||
|   // If there is a path that starts with a "/", eliminate everything before
 | ||||
|   let paths = []; | ||||
|   for (let subpath of path) { | ||||
|     if (subpath == null) { | ||||
|       throw new TypeError("invalid path component"); | ||||
|     } | ||||
|     if (!subpath.length) { | ||||
|       continue; | ||||
|     } else if (subpath[0] == "/") { | ||||
|       paths = [subpath]; | ||||
|     } else { | ||||
|       paths.push(subpath); | ||||
|     } | ||||
|   } | ||||
|   return paths.join("/"); | ||||
| }; | ||||
| exports.join = join; | ||||
| 
 | ||||
| /** | ||||
|  * Normalize a path by removing any unneeded ".", "..", "//". | ||||
|  */ | ||||
| var normalize = function(path) { | ||||
|   let stack = []; | ||||
|   let absolute; | ||||
|   if (path.length >= 0 && path[0] == "/") { | ||||
|     absolute = true; | ||||
|   } else { | ||||
|     absolute = false; | ||||
|   } | ||||
|   path.split("/").forEach(function(v) { | ||||
|     switch (v) { | ||||
|       case "": | ||||
|       case ".": // fallthrough
 | ||||
|         break; | ||||
|       case "..": | ||||
|         if (!stack.length) { | ||||
|           if (absolute) { | ||||
|             throw new Error("Path is ill-formed: attempting to go past root"); | ||||
|           } else { | ||||
|             stack.push(".."); | ||||
|           } | ||||
|         } else if (stack[stack.length - 1] == "..") { | ||||
|           stack.push(".."); | ||||
|         } else { | ||||
|           stack.pop(); | ||||
|         } | ||||
|         break; | ||||
|       default: | ||||
|         stack.push(v); | ||||
|     } | ||||
|   }); | ||||
|   let string = stack.join("/"); | ||||
|   return absolute ? "/" + string : string; | ||||
| }; | ||||
| exports.normalize = normalize; | ||||
| 
 | ||||
| /** | ||||
|  * Return the components of a path. | ||||
|  * You should generally apply this function to a normalized path. | ||||
|  * | ||||
|  * @return {{ | ||||
|  *   {bool} absolute |true| if the path is absolute, |false| otherwise | ||||
|  *   {array} components the string components of the path | ||||
|  * }} | ||||
|  * | ||||
|  * Other implementations may add additional OS-specific informations. | ||||
|  */ | ||||
| var split = function(path) { | ||||
|   return { | ||||
|     absolute: path.length && path[0] == "/", | ||||
|     components: path.split("/"), | ||||
|   }; | ||||
| }; | ||||
| exports.split = split; | ||||
| 
 | ||||
| /** | ||||
|  * Returns the file:// URI file path of the given local file path.
 | ||||
|  */ | ||||
| // The case of %3b is designed to match Services.io, but fundamentally doesn't matter.
 | ||||
| var toFileURIExtraEncodings = { ";": "%3b", "?": "%3F", "#": "%23" }; | ||||
| var toFileURI = function toFileURI(path) { | ||||
|   // Per https://url.spec.whatwg.org we should not encode [] in the path
 | ||||
|   let dontNeedEscaping = { "%5B": "[", "%5D": "]" }; | ||||
|   let uri = encodeURI(this.normalize(path)).replace( | ||||
|     /%(5B|5D)/gi, | ||||
|     match => dontNeedEscaping[match] | ||||
|   ); | ||||
| 
 | ||||
|   // add a prefix, and encodeURI doesn't escape a few characters that we do
 | ||||
|   // want to escape, so fix that up
 | ||||
|   let prefix = "file://"; | ||||
|   uri = prefix + uri.replace(/[;?#]/g, match => toFileURIExtraEncodings[match]); | ||||
| 
 | ||||
|   return uri; | ||||
| }; | ||||
| exports.toFileURI = toFileURI; | ||||
| 
 | ||||
| /** | ||||
|  * Returns the local file path from a given file URI. | ||||
|  */ | ||||
| var fromFileURI = function fromFileURI(uri) { | ||||
|   let url = new URL(uri); | ||||
|   if (url.protocol != "file:") { | ||||
|     throw new Error("fromFileURI expects a file URI"); | ||||
|   } | ||||
|   let path = this.normalize(decodeURIComponent(url.pathname)); | ||||
|   return path; | ||||
| }; | ||||
| exports.fromFileURI = fromFileURI; | ||||
| 
 | ||||
| // ////////// Boilerplate
 | ||||
| if (typeof Components != "undefined") { | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS; | ||||
|   for (let symbol of EXPORTED_SYMBOLS) { | ||||
|     // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|     this[symbol] = exports[symbol]; | ||||
|   } | ||||
| } | ||||
|  | @ -1,382 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| /** | ||||
|  * Handling native paths. | ||||
|  * | ||||
|  * This module contains a number of functions destined to simplify | ||||
|  * working with native paths through a cross-platform API. Functions | ||||
|  * of this module will only work with the following assumptions: | ||||
|  * | ||||
|  * - paths are valid; | ||||
|  * - paths are defined with one of the grammars that this module can | ||||
|  *   parse (see later); | ||||
|  * - all path concatenations go through function |join|. | ||||
|  * | ||||
|  * Limitations of this implementation. | ||||
|  * | ||||
|  * Windows supports 6 distinct grammars for paths. For the moment, this | ||||
|  * implementation supports the following subset: | ||||
|  * | ||||
|  * - drivename:backslash-separated components | ||||
|  * - backslash-separated components | ||||
|  * - \\drivename\ followed by backslash-separated components | ||||
|  * | ||||
|  * Additionally, |normalize| can convert a path containing slash- | ||||
|  * separated components to a path containing backslash-separated | ||||
|  * components. | ||||
|  */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| // Boilerplate used to be able to import this module both from the main
 | ||||
| // thread and from worker threads.
 | ||||
| if (typeof Components != "undefined") { | ||||
|   // Global definition of |exports|, to keep everybody happy.
 | ||||
|   // In non-main thread, |exports| is provided by the module
 | ||||
|   // loader.
 | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.exports = {}; | ||||
| } else if (typeof module == "undefined" || typeof exports == "undefined") { | ||||
|   throw new Error("Please load this module using require()"); | ||||
| } | ||||
| 
 | ||||
| var EXPORTED_SYMBOLS = [ | ||||
|   "basename", | ||||
|   "dirname", | ||||
|   "join", | ||||
|   "normalize", | ||||
|   "split", | ||||
|   "winGetDrive", | ||||
|   "winIsAbsolute", | ||||
|   "toFileURI", | ||||
|   "fromFileURI", | ||||
| ]; | ||||
| 
 | ||||
| /** | ||||
|  * Return the final part of the path. | ||||
|  * The final part of the path is everything after the last "\\". | ||||
|  */ | ||||
| var basename = function(path) { | ||||
|   if (path.startsWith("\\\\")) { | ||||
|     // UNC-style path
 | ||||
|     let index = path.lastIndexOf("\\"); | ||||
|     if (index != 1) { | ||||
|       return path.slice(index + 1); | ||||
|     } | ||||
|     return ""; // Degenerate case
 | ||||
|   } | ||||
|   return path.slice( | ||||
|     Math.max(path.lastIndexOf("\\"), path.lastIndexOf(":")) + 1 | ||||
|   ); | ||||
| }; | ||||
| exports.basename = basename; | ||||
| 
 | ||||
| /** | ||||
|  * Return the directory part of the path. | ||||
|  * | ||||
|  * If the path contains no directory, return the drive letter, | ||||
|  * or "." if the path contains no drive letter or if option | ||||
|  * |winNoDrive| is set. | ||||
|  * | ||||
|  * Otherwise, return everything before the last backslash, | ||||
|  * including the drive/server name. | ||||
|  * | ||||
|  * | ||||
|  * @param {string} path The path. | ||||
|  * @param {*=} options Platform-specific options controlling the behavior | ||||
|  * of this function. This implementation supports the following options: | ||||
|  *  - |winNoDrive| If |true|, also remove the letter from the path name. | ||||
|  */ | ||||
| var dirname = function(path, options) { | ||||
|   let noDrive = options && options.winNoDrive; | ||||
| 
 | ||||
|   // Find the last occurrence of "\\"
 | ||||
|   let index = path.lastIndexOf("\\"); | ||||
|   if (index == -1) { | ||||
|     // If there is no directory component...
 | ||||
|     if (!noDrive) { | ||||
|       // Return the drive path if possible, falling back to "."
 | ||||
|       return this.winGetDrive(path) || "."; | ||||
|     } | ||||
|     // Or just "."
 | ||||
|     return "."; | ||||
|   } | ||||
| 
 | ||||
|   if (index == 1 && path.charAt(0) == "\\") { | ||||
|     // The path is reduced to a UNC drive
 | ||||
|     if (noDrive) { | ||||
|       return "."; | ||||
|     } | ||||
|     return path; | ||||
|   } | ||||
| 
 | ||||
|   // Ignore any occurrence of "\\: immediately before that one
 | ||||
|   while (index >= 0 && path[index] == "\\") { | ||||
|     --index; | ||||
|   } | ||||
| 
 | ||||
|   // Compute what is left, removing the drive name if necessary
 | ||||
|   let start; | ||||
|   if (noDrive) { | ||||
|     start = (this.winGetDrive(path) || "").length; | ||||
|   } else { | ||||
|     start = 0; | ||||
|   } | ||||
|   return path.slice(start, index + 1); | ||||
| }; | ||||
| exports.dirname = dirname; | ||||
| 
 | ||||
| /** | ||||
|  * Join path components. | ||||
|  * This is the recommended manner of getting the path of a file/subdirectory | ||||
|  * in a directory. | ||||
|  * | ||||
|  * Example: Obtaining $TMP/foo/bar in an OS-independent manner | ||||
|  *  var tmpDir = OS.Constants.Path.tmpDir; | ||||
|  *  var path = OS.Path.join(tmpDir, "foo", "bar"); | ||||
|  * | ||||
|  * Under Windows, this will return "$TMP\foo\bar". | ||||
|  * | ||||
|  * Empty components are ignored, i.e. `OS.Path.join("foo", "", "bar)` is the | ||||
|  * same as `OS.Path.join("foo", "bar")`. | ||||
|  */ | ||||
| var join = function(...path) { | ||||
|   let paths = []; | ||||
|   let root; | ||||
|   let absolute = false; | ||||
|   for (let subpath of path) { | ||||
|     if (subpath == null) { | ||||
|       throw new TypeError("invalid path component"); | ||||
|     } | ||||
|     if (subpath == "") { | ||||
|       continue; | ||||
|     } | ||||
|     let drive = this.winGetDrive(subpath); | ||||
|     if (drive) { | ||||
|       root = drive; | ||||
|       let component = trimBackslashes(subpath.slice(drive.length)); | ||||
|       if (component) { | ||||
|         paths = [component]; | ||||
|       } else { | ||||
|         paths = []; | ||||
|       } | ||||
|       absolute = true; | ||||
|     } else if (this.winIsAbsolute(subpath)) { | ||||
|       paths = [trimBackslashes(subpath)]; | ||||
|       absolute = true; | ||||
|     } else { | ||||
|       paths.push(trimBackslashes(subpath)); | ||||
|     } | ||||
|   } | ||||
|   let result = ""; | ||||
|   if (root) { | ||||
|     result += root; | ||||
|   } | ||||
|   if (absolute) { | ||||
|     result += "\\"; | ||||
|   } | ||||
|   result += paths.join("\\"); | ||||
|   return result; | ||||
| }; | ||||
| exports.join = join; | ||||
| 
 | ||||
| /** | ||||
|  * Return the drive name of a path, or |null| if the path does | ||||
|  * not contain a drive name. | ||||
|  * | ||||
|  * Drive name appear either as "DriveName:..." (the return drive | ||||
|  * name includes the ":") or "\\\\DriveName..." (the returned drive name | ||||
|  * includes "\\\\"). | ||||
|  */ | ||||
| var winGetDrive = function(path) { | ||||
|   if (path == null) { | ||||
|     throw new TypeError("path is invalid"); | ||||
|   } | ||||
| 
 | ||||
|   if (path.startsWith("\\\\")) { | ||||
|     // UNC path
 | ||||
|     if (path.length == 2) { | ||||
|       return null; | ||||
|     } | ||||
|     let index = path.indexOf("\\", 2); | ||||
|     if (index == -1) { | ||||
|       return path; | ||||
|     } | ||||
|     return path.slice(0, index); | ||||
|   } | ||||
|   // Non-UNC path
 | ||||
|   let index = path.indexOf(":"); | ||||
|   if (index <= 0) { | ||||
|     return null; | ||||
|   } | ||||
|   return path.slice(0, index + 1); | ||||
| }; | ||||
| exports.winGetDrive = winGetDrive; | ||||
| 
 | ||||
| /** | ||||
|  * Return |true| if the path is absolute, |false| otherwise. | ||||
|  * | ||||
|  * We consider that a path is absolute if it starts with "\\" | ||||
|  * or "driveletter:\\". | ||||
|  */ | ||||
| var winIsAbsolute = function(path) { | ||||
|   let index = path.indexOf(":"); | ||||
|   return path.length > index + 1 && path[index + 1] == "\\"; | ||||
| }; | ||||
| exports.winIsAbsolute = winIsAbsolute; | ||||
| 
 | ||||
| /** | ||||
|  * Normalize a path by removing any unneeded ".", "..", "\\". | ||||
|  * Also convert any "/" to a "\\". | ||||
|  */ | ||||
| var normalize = function(path) { | ||||
|   let stack = []; | ||||
| 
 | ||||
|   if (!path.startsWith("\\\\")) { | ||||
|     // Normalize "/" to "\\"
 | ||||
|     path = path.replace(/\//g, "\\"); | ||||
|   } | ||||
| 
 | ||||
|   // Remove the drive (we will put it back at the end)
 | ||||
|   let root = this.winGetDrive(path); | ||||
|   if (root) { | ||||
|     path = path.slice(root.length); | ||||
|   } | ||||
| 
 | ||||
|   // Remember whether we need to restore a leading "\\" or drive name.
 | ||||
|   let absolute = this.winIsAbsolute(path); | ||||
| 
 | ||||
|   // And now, fill |stack| from the components,
 | ||||
|   // popping whenever there is a ".."
 | ||||
|   path.split("\\").forEach(function loop(v) { | ||||
|     switch (v) { | ||||
|       case "": | ||||
|       case ".": // Ignore
 | ||||
|         break; | ||||
|       case "..": | ||||
|         if (!stack.length) { | ||||
|           if (absolute) { | ||||
|             throw new Error("Path is ill-formed: attempting to go past root"); | ||||
|           } else { | ||||
|             stack.push(".."); | ||||
|           } | ||||
|         } else if (stack[stack.length - 1] == "..") { | ||||
|           stack.push(".."); | ||||
|         } else { | ||||
|           stack.pop(); | ||||
|         } | ||||
|         break; | ||||
|       default: | ||||
|         stack.push(v); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   // Put everything back together
 | ||||
|   let result = stack.join("\\"); | ||||
|   if (absolute || root) { | ||||
|     result = "\\" + result; | ||||
|   } | ||||
|   if (root) { | ||||
|     result = root + result; | ||||
|   } | ||||
|   return result; | ||||
| }; | ||||
| exports.normalize = normalize; | ||||
| 
 | ||||
| /** | ||||
|  * Return the components of a path. | ||||
|  * You should generally apply this function to a normalized path. | ||||
|  * | ||||
|  * @return {{ | ||||
|  *   {bool} absolute |true| if the path is absolute, |false| otherwise | ||||
|  *   {array} components the string components of the path | ||||
|  *   {string?} winDrive the drive or server for this path | ||||
|  * }} | ||||
|  * | ||||
|  * Other implementations may add additional OS-specific informations. | ||||
|  */ | ||||
| var split = function(path) { | ||||
|   return { | ||||
|     absolute: this.winIsAbsolute(path), | ||||
|     winDrive: this.winGetDrive(path), | ||||
|     components: path.split("\\"), | ||||
|   }; | ||||
| }; | ||||
| exports.split = split; | ||||
| 
 | ||||
| /** | ||||
|  * Return the file:// URI file path of the given local file path.
 | ||||
|  */ | ||||
| // The case of %3b is designed to match Services.io, but fundamentally doesn't matter.
 | ||||
| var toFileURIExtraEncodings = { ";": "%3b", "?": "%3F", "#": "%23" }; | ||||
| var toFileURI = function toFileURI(path) { | ||||
|   // URI-escape forward slashes and convert backward slashes to forward
 | ||||
|   path = this.normalize(path).replace(/[\\\/]/g, m => | ||||
|     m == "\\" ? "/" : "%2F" | ||||
|   ); | ||||
|   // Per https://url.spec.whatwg.org we should not encode [] in the path
 | ||||
|   let dontNeedEscaping = { "%5B": "[", "%5D": "]" }; | ||||
|   let uri = encodeURI(path).replace( | ||||
|     /%(5B|5D)/gi, | ||||
|     match => dontNeedEscaping[match] | ||||
|   ); | ||||
| 
 | ||||
|   // add a prefix, and encodeURI doesn't escape a few characters that we do
 | ||||
|   // want to escape, so fix that up
 | ||||
|   let prefix = "file:///"; | ||||
|   uri = prefix + uri.replace(/[;?#]/g, match => toFileURIExtraEncodings[match]); | ||||
| 
 | ||||
|   // turn e.g., file:///C: into file:///C:/
 | ||||
|   if (uri.charAt(uri.length - 1) === ":") { | ||||
|     uri += "/"; | ||||
|   } | ||||
| 
 | ||||
|   return uri; | ||||
| }; | ||||
| exports.toFileURI = toFileURI; | ||||
| 
 | ||||
| /** | ||||
|  * Returns the local file path from a given file URI. | ||||
|  */ | ||||
| var fromFileURI = function fromFileURI(uri) { | ||||
|   let url = new URL(uri); | ||||
|   if (url.protocol != "file:") { | ||||
|     throw new Error("fromFileURI expects a file URI"); | ||||
|   } | ||||
| 
 | ||||
|   // strip leading slash, since Windows paths don't start with one
 | ||||
|   uri = url.pathname.substr(1); | ||||
| 
 | ||||
|   let path = decodeURI(uri); | ||||
|   // decode a few characters where URL's parsing is overzealous
 | ||||
|   path = path.replace(/%(3b|3f|23)/gi, match => decodeURIComponent(match)); | ||||
|   path = this.normalize(path); | ||||
| 
 | ||||
|   // this.normalize() does not remove the trailing slash if the path
 | ||||
|   // component is a drive letter. eg. 'C:\'' will not get normalized.
 | ||||
|   if (path.endsWith(":\\")) { | ||||
|     path = path.substr(0, path.length - 1); | ||||
|   } | ||||
|   return this.normalize(path); | ||||
| }; | ||||
| exports.fromFileURI = fromFileURI; | ||||
| 
 | ||||
| /** | ||||
|  * Utility function: Remove any leading/trailing backslashes | ||||
|  * from a string. | ||||
|  */ | ||||
| var trimBackslashes = function trimBackslashes(string) { | ||||
|   return string.replace(/^\\+|\\+$/g, ""); | ||||
| }; | ||||
| 
 | ||||
| // ////////// Boilerplate
 | ||||
| if (typeof Components != "undefined") { | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS; | ||||
|   for (let symbol of EXPORTED_SYMBOLS) { | ||||
|     // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|     this[symbol] = exports[symbol]; | ||||
|   } | ||||
| } | ||||
|  | @ -1,35 +0,0 @@ | |||
| # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- | ||||
| # vim: set filetype=python: | ||||
| # 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/. | ||||
| 
 | ||||
| DIRS += [ | ||||
|     "modules", | ||||
| ] | ||||
| 
 | ||||
| MOCHITEST_CHROME_MANIFESTS += ["tests/mochi/chrome.ini"] | ||||
| XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"] | ||||
| 
 | ||||
| SOURCES += [ | ||||
|     "NativeOSFileInternals.cpp", | ||||
| ] | ||||
| 
 | ||||
| XPIDL_MODULE = "toolkit_osfile" | ||||
| 
 | ||||
| XPIDL_SOURCES += [ | ||||
|     "nsINativeOSFileInternals.idl", | ||||
| ] | ||||
| 
 | ||||
| EXPORTS.mozilla += [ | ||||
|     "NativeOSFileInternals.h", | ||||
| ] | ||||
| 
 | ||||
| EXTRA_JS_MODULES += [ | ||||
|     "osfile.jsm", | ||||
| ] | ||||
| 
 | ||||
| FINAL_LIBRARY = "xul" | ||||
| 
 | ||||
| with Files("**"): | ||||
|     BUG_COMPONENT = ("Toolkit", "OS.File") | ||||
|  | @ -1,120 +0,0 @@ | |||
| /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ | ||||
| /* vim: set ts=2 et sw=2 tw=40: */ | ||||
| /* 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/. */ | ||||
| 
 | ||||
| #include "nsISupports.idl" | ||||
| 
 | ||||
| /** | ||||
|  * The result of a successful asynchronous operation. | ||||
|  */ | ||||
| [scriptable, builtinclass, uuid(08B4CF29-3D65-4E79-B522-A694C322ED07)] | ||||
| interface nsINativeOSFileResult: nsISupports | ||||
| { | ||||
|   /** | ||||
|    * The actual value produced by the operation. | ||||
|    * | ||||
|    * Actual type of this value depends on the options passed to the | ||||
|    * operation. | ||||
|    */ | ||||
|   [implicit_jscontext] | ||||
|   readonly attribute jsval result; | ||||
| 
 | ||||
|   /** | ||||
|    * Delay between when the operation was requested on the main thread and | ||||
|    * when the operation was started off main thread. | ||||
|    */ | ||||
|   readonly attribute double dispatchDurationMS; | ||||
| 
 | ||||
|   /** | ||||
|    * Duration of the off main thread execution. | ||||
|    */ | ||||
|   readonly attribute double executionDurationMS; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * A callback invoked in case of success. | ||||
|  */ | ||||
| [scriptable, function, uuid(2C1922CA-CA1B-4099-8B61-EC23CFF49412)] | ||||
| interface nsINativeOSFileSuccessCallback: nsISupports | ||||
| { | ||||
|   void complete(in nsINativeOSFileResult result); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * A callback invoked in case of error. | ||||
|  */ | ||||
| [scriptable, function, uuid(F612E0FC-6736-4D24-AA50-FD661B3B40B6)] | ||||
| interface nsINativeOSFileErrorCallback: nsISupports | ||||
| { | ||||
|   /** | ||||
|    * @param operation The name of the failed operation. Provided to aid | ||||
|    * debugging only, may change without notice. | ||||
|    * @param OSstatus The OS status of the operation (errno under Unix, | ||||
|    * GetLastError under Windows). | ||||
|    */ | ||||
|   void complete(in ACString operation, in long OSstatus); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * A service providing native implementations of some of the features | ||||
|  * of OS.File. | ||||
|  */ | ||||
| [scriptable, builtinclass, uuid(913362AD-1526-4623-9E6B-A2EB08AFBBB9)] | ||||
| interface nsINativeOSFileInternalsService: nsISupports | ||||
| { | ||||
|   /** | ||||
|    * Implementation of OS.File.read | ||||
|    * | ||||
|    * @param path The absolute path to the file to read. | ||||
|    * @param options An object that may contain some of the following fields | ||||
|    * - {number} bytes The maximal number of bytes to read. | ||||
|    * - {string} encoding If provided, return the result as a string, decoded | ||||
|    *   using this encoding. Otherwise, pass the result as an ArrayBuffer. | ||||
|    *   Invalid encodings cause onError to be called with the platform-specific | ||||
|    *   "invalid argument" constant. | ||||
|    * - {string} compression Unimplemented at the moment. | ||||
|    * @param onSuccess The success callback. | ||||
|    * @param onError The error callback. | ||||
|    */ | ||||
|   [implicit_jscontext] | ||||
|   void read(in AString path, in jsval options, | ||||
|             in nsINativeOSFileSuccessCallback onSuccess, | ||||
|             in nsINativeOSFileErrorCallback onError); | ||||
| 
 | ||||
|   /** | ||||
|    * Implementation of OS.File.writeAtomic | ||||
|    * | ||||
|    * @param path the absolute path of the file to write to. | ||||
|    * @param buffer the data as an array buffer to be written to the file. | ||||
|    * @param options An object that may contain the following fields | ||||
|    * - {number} bytes If provided, the number of bytes written is equal to this. | ||||
|    *   The default value is the size of the |buffer|. | ||||
|    * - {string} tmpPath If provided and not null, first write to this path, and | ||||
|    *   move to |path| after writing. | ||||
|    * - {string} backupPath if provided, backup file at |path| to this path | ||||
|    *   before overwriting it. | ||||
|    * - {bool} flush if provided and true, flush the contents of the buffer after | ||||
|    *   writing. This is slower, but safer. | ||||
|    * - {bool} noOverwrite if provided and true, do not write if a file already | ||||
|    *   exists at |path|. | ||||
|    * @param onSuccess The success callback. | ||||
|    * @param onError The error callback. | ||||
|    */ | ||||
|   [implicit_jscontext] | ||||
|   void writeAtomic(in AString path, | ||||
|                    in jsval buffer, | ||||
|                    in jsval options, | ||||
|                    in nsINativeOSFileSuccessCallback onSuccess, | ||||
|                    in nsINativeOSFileErrorCallback onError); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| %{ C++ | ||||
| 
 | ||||
| #define NATIVE_OSFILE_INTERNALS_SERVICE_CID {0x63A69303,0x8A64,0x45A9,{0x84, 0x8C, 0xD4, 0xE2, 0x79, 0x27, 0x94, 0xE6}} | ||||
| #define NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID "@mozilla.org/toolkit/osfile/native-internals;1" | ||||
| 
 | ||||
| %} | ||||
|  | @ -1,46 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| /** | ||||
|  * Common front for various implementations of OS.File | ||||
|  */ | ||||
| 
 | ||||
| if (typeof Components != "undefined") { | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.EXPORTED_SYMBOLS = ["OS"]; | ||||
|   const { OS } = ChromeUtils.import( | ||||
|     "resource://gre/modules/osfile/osfile_async_front.jsm" | ||||
|   ); | ||||
|   // eslint-disable-next-line mozilla/reject-global-this
 | ||||
|   this.OS = OS; | ||||
| } else { | ||||
|   /* eslint-env worker */ | ||||
|   /* import-globals-from /toolkit/components/workerloader/require.js */ | ||||
|   importScripts("resource://gre/modules/workers/require.js"); | ||||
| 
 | ||||
|   var SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); | ||||
| 
 | ||||
|   /* eslint-disable no-unused-vars */ | ||||
| 
 | ||||
|   // At this stage, we need to import all sources at once to avoid
 | ||||
|   // a unique failure on tbpl + talos that seems caused by a
 | ||||
|   // what looks like a nested event loop bug (see bug 794091).
 | ||||
|   if (SharedAll.Constants.Win) { | ||||
|     importScripts( | ||||
|       "resource://gre/modules/osfile/osfile_win_back.js", | ||||
|       "resource://gre/modules/osfile/osfile_shared_front.js", | ||||
|       "resource://gre/modules/osfile/osfile_win_front.js" | ||||
|     ); | ||||
|   } else { | ||||
|     importScripts( | ||||
|       "resource://gre/modules/osfile/osfile_unix_back.js", | ||||
|       "resource://gre/modules/osfile/osfile_shared_front.js", | ||||
|       "resource://gre/modules/osfile/osfile_unix_front.js" | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /* eslint-enable no-unused-vars */ | ||||
| 
 | ||||
|   OS.Path = require("resource://gre/modules/osfile/ospath.jsm"); | ||||
| } | ||||
|  | @ -1,15 +0,0 @@ | |||
| [DEFAULT] | ||||
| skip-if = os == 'android' | ||||
| support-files = | ||||
|   main_test_osfile_async.js | ||||
|   worker_test_osfile_comms.js | ||||
|   worker_test_osfile_front.js | ||||
|   worker_test_osfile_unix.js | ||||
|   worker_test_osfile_win.js | ||||
| 
 | ||||
| [test_osfile_async.xhtml] | ||||
| [test_osfile_back.xhtml] | ||||
| [test_osfile_comms.xhtml] | ||||
| [test_osfile_front.xhtml] | ||||
| skip-if = | ||||
|   win11_2009 && bits == 32  # Bug 1809355 | ||||
|  | @ -1,501 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| // The following are used to compare against a well-tested reference
 | ||||
| // implementation of file I/O.
 | ||||
| const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); | ||||
| const { FileUtils } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/FileUtils.sys.mjs" | ||||
| ); | ||||
| 
 | ||||
| var myok = ok; | ||||
| var myis = is; | ||||
| var myinfo = info; | ||||
| var myisnot = isnot; | ||||
| 
 | ||||
| var isPromise = function ispromise(value) { | ||||
|   return value != null && typeof value == "object" && "then" in value; | ||||
| }; | ||||
| 
 | ||||
| var 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); | ||||
|     }, | ||||
|     isnot: function isnot(l, r, m) { | ||||
|       myisnot(l, r, prefix + ": " + m); | ||||
|     }, | ||||
|     info: function info(m) { | ||||
|       myinfo(prefix + ": " + m); | ||||
|     }, | ||||
|     fail: function fail(m) { | ||||
|       utils.ok(false, m); | ||||
|     }, | ||||
|     okpromise: function okpromise(t, m) { | ||||
|       return t.then( | ||||
|         function onSuccess() { | ||||
|           utils.ok(true, m); | ||||
|         }, | ||||
|         function onFailure() { | ||||
|           utils.ok(false, m); | ||||
|         } | ||||
|       ); | ||||
|     }, | ||||
|   }; | ||||
|   return function runtest() { | ||||
|     utils.info("Entering"); | ||||
|     try { | ||||
|       let result = test.call(this, utils); | ||||
|       if (!isPromise(result)) { | ||||
|         throw new TypeError("The test did not return a promise"); | ||||
|       } | ||||
|       utils.info("This was a promise"); | ||||
|       // The test returns a promise
 | ||||
|       result = result.then( | ||||
|         function test_complete() { | ||||
|           utils.info("Complete"); | ||||
|         }, | ||||
|         function catch_uncaught_errors(err) { | ||||
|           utils.fail("Uncaught error " + err); | ||||
|           if (err && typeof err == "object" && "message" in err) { | ||||
|             utils.fail("(" + err.message + ")"); | ||||
|           } | ||||
|           if (err && typeof err == "object" && "stack" in err) { | ||||
|             utils.fail("at " + err.stack); | ||||
|           } | ||||
|         } | ||||
|       ); | ||||
|       return result; | ||||
|     } catch (x) { | ||||
|       utils.fail("Error " + x + " at " + x.stack); | ||||
|       return null; | ||||
|     } | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Fetch asynchronously the contents of a file using xpcom. | ||||
|  * | ||||
|  * Used for comparing xpcom-based results to os.file-based results. | ||||
|  * | ||||
|  * @param {string} path The _absolute_ path to the file. | ||||
|  * @return {promise} | ||||
|  * @resolves {string} The contents of the file. | ||||
|  */ | ||||
| var reference_fetch_file = function reference_fetch_file(path, test) { | ||||
|   test.info("Fetching file " + path); | ||||
|   return new Promise((resolve, reject) => { | ||||
|     let file = new FileUtils.File(path); | ||||
|     NetUtil.asyncFetch( | ||||
|       { | ||||
|         uri: NetUtil.newURI(file), | ||||
|         loadUsingSystemPrincipal: true, | ||||
|       }, | ||||
|       function(stream, status) { | ||||
|         if (!Components.isSuccessCode(status)) { | ||||
|           reject(status); | ||||
|           return; | ||||
|         } | ||||
|         let result, reject; | ||||
|         try { | ||||
|           result = NetUtil.readInputStreamToString(stream, stream.available()); | ||||
|         } catch (x) { | ||||
|           reject = x; | ||||
|         } | ||||
|         stream.close(); | ||||
|         if (reject) { | ||||
|           reject(reject); | ||||
|         } else { | ||||
|           resolve(result); | ||||
|         } | ||||
|       } | ||||
|     ); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| var reference_dir_contents = function reference_dir_contents(path) { | ||||
|   let result = []; | ||||
|   let entries = new FileUtils.File(path).directoryEntries; | ||||
|   while (entries.hasMoreElements()) { | ||||
|     let entry = entries.nextFile; | ||||
|     result.push(entry.path); | ||||
|   } | ||||
|   return result; | ||||
| }; | ||||
| 
 | ||||
| // Set/Unset OS.Shared.DEBUG, OS.Shared.TEST and a console listener.
 | ||||
| function toggleDebugTest(pref, consoleListener) { | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", pref); | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log.redirect", pref); | ||||
|   Services.console[pref ? "registerListener" : "unregisterListener"]( | ||||
|     consoleListener | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| var test = maketest("Main", function main(test) { | ||||
|   return (async function() { | ||||
|     SimpleTest.waitForExplicitFinish(); | ||||
|     await test_stat(); | ||||
|     await test_debug(); | ||||
|     await test_info_features_detect(); | ||||
|     await test_position(); | ||||
|     await test_iter(); | ||||
|     await test_exists(); | ||||
|     await test_debug_test(); | ||||
|     info("Test is over"); | ||||
|     SimpleTest.finish(); | ||||
|   })(); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * A file that we know exists and that can be used for reading. | ||||
|  */ | ||||
| var EXISTING_FILE = OS.Path.join( | ||||
|   "chrome", | ||||
|   "toolkit", | ||||
|   "components", | ||||
|   "osfile", | ||||
|   "tests", | ||||
|   "mochi", | ||||
|   "main_test_osfile_async.js" | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * Test OS.File.stat and OS.File.prototype.stat | ||||
|  */ | ||||
| var test_stat = maketest("stat", function stat(test) { | ||||
|   return (async function() { | ||||
|     // Open a file and stat it
 | ||||
|     let file = await OS.File.open(EXISTING_FILE); | ||||
|     let stat1; | ||||
| 
 | ||||
|     try { | ||||
|       test.info("Stating file"); | ||||
|       stat1 = await file.stat(); | ||||
|       test.ok(true, "stat has worked " + stat1); | ||||
|       test.ok(stat1, "stat is not empty"); | ||||
|     } finally { | ||||
|       await file.close(); | ||||
|     } | ||||
| 
 | ||||
|     // Stat the same file without opening it
 | ||||
|     test.info("Stating a file without opening it"); | ||||
|     let stat2 = await OS.File.stat(EXISTING_FILE); | ||||
|     test.ok(true, "stat 2 has worked " + stat2); | ||||
|     test.ok(stat2, "stat 2 is not empty"); | ||||
|     for (let key in stat2) { | ||||
|       test.is( | ||||
|         "" + stat1[key], | ||||
|         "" + stat2[key], | ||||
|         "Stat field " + key + "is the same" | ||||
|       ); | ||||
|     } | ||||
|   })(); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Test feature detection using OS.File.Info.prototype on main thread | ||||
|  */ | ||||
| var test_info_features_detect = maketest( | ||||
|   "features_detect", | ||||
|   function features_detect(test) { | ||||
|     return (async function() { | ||||
|       if (!OS.Constants.Win && OS.Constants.libc) { | ||||
|         // see if unixGroup is defined
 | ||||
|         if ("unixGroup" in OS.File.Info.prototype) { | ||||
|           test.ok(true, "unixGroup is defined"); | ||||
|         } else { | ||||
|           test.fail("unixGroup is not defined though we are under Unix"); | ||||
|         } | ||||
|       } | ||||
|     })(); | ||||
|   } | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * Test file.{getPosition, setPosition} | ||||
|  */ | ||||
| var test_position = maketest("position", function position(test) { | ||||
|   return (async function() { | ||||
|     let file = await OS.File.open(EXISTING_FILE); | ||||
| 
 | ||||
|     try { | ||||
|       let view = await file.read(); | ||||
|       test.info("First batch of content read"); | ||||
|       let CHUNK_SIZE = 178; // An arbitrary number of bytes to read from the file
 | ||||
|       let pos = await file.getPosition(); | ||||
|       test.info("Obtained position"); | ||||
|       test.is(pos, view.byteLength, "getPosition returned the end of the file"); | ||||
|       pos = await file.setPosition(-CHUNK_SIZE, OS.File.POS_END); | ||||
|       test.info("Changed position"); | ||||
|       test.is( | ||||
|         pos, | ||||
|         view.byteLength - CHUNK_SIZE, | ||||
|         "setPosition returned the correct position" | ||||
|       ); | ||||
| 
 | ||||
|       let view2 = await file.read(); | ||||
|       test.info("Read the end of the file"); | ||||
|       for (let i = 0; i < CHUNK_SIZE; ++i) { | ||||
|         if (view2[i] != view[i + view.byteLength - CHUNK_SIZE]) { | ||||
|           test.is( | ||||
|             view2[i], | ||||
|             view[i], | ||||
|             "setPosition put us in the right position" | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|     } finally { | ||||
|       await file.close(); | ||||
|     } | ||||
|   })(); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Test OS.File.prototype.{DirectoryIterator} | ||||
|  */ | ||||
| var test_iter = maketest("iter", function iter(test) { | ||||
|   return (async function() { | ||||
|     let currentDir = await OS.File.getCurrentDirectory(); | ||||
| 
 | ||||
|     // Trivial walks through the directory
 | ||||
|     test.info("Preparing iteration"); | ||||
|     let iterator = new OS.File.DirectoryIterator(currentDir); | ||||
|     let temporary_file_name = OS.Path.join( | ||||
|       currentDir, | ||||
|       "empty-temporary-file.tmp" | ||||
|     ); | ||||
|     try { | ||||
|       await OS.File.remove(temporary_file_name); | ||||
|     } catch (err) { | ||||
|       // Ignore errors removing file
 | ||||
|     } | ||||
|     let allFiles1 = await iterator.nextBatch(); | ||||
|     test.info("Obtained all files through nextBatch"); | ||||
|     test.isnot(allFiles1.length, 0, "There is at least one file"); | ||||
|     test.isnot(allFiles1[0].path, null, "Files have a path"); | ||||
| 
 | ||||
|     // Ensure that we have the same entries with |reference_dir_contents|
 | ||||
|     let referenceEntries = new Set(); | ||||
|     for (let entry of reference_dir_contents(currentDir)) { | ||||
|       referenceEntries.add(entry); | ||||
|     } | ||||
|     test.is( | ||||
|       referenceEntries.size, | ||||
|       allFiles1.length, | ||||
|       "All the entries in the directory have been listed" | ||||
|     ); | ||||
|     for (let entry of allFiles1) { | ||||
|       test.ok( | ||||
|         referenceEntries.has(entry.path), | ||||
|         "File " + entry.path + " effectively exists" | ||||
|       ); | ||||
|       // Ensure that we have correct isDir and isSymLink
 | ||||
|       // Current directory is {objdir}/_tests/testing/mochitest/, assume it has some dirs and symlinks.
 | ||||
|       var f = new FileUtils.File(entry.path); | ||||
|       test.is( | ||||
|         entry.isDir, | ||||
|         f.isDirectory(), | ||||
|         "Get file " + entry.path + " isDir correctly" | ||||
|       ); | ||||
|       test.is( | ||||
|         entry.isSymLink, | ||||
|         f.isSymlink(), | ||||
|         "Get file " + entry.path + " isSymLink correctly" | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     await iterator.close(); | ||||
|     test.info("Closed iterator"); | ||||
| 
 | ||||
|     test.info("Double closing DirectoryIterator"); | ||||
|     iterator = new OS.File.DirectoryIterator(currentDir); | ||||
|     await iterator.close(); | ||||
|     await iterator.close(); // double closing |DirectoryIterator|
 | ||||
|     test.ok(true, "|DirectoryIterator| was closed twice successfully"); | ||||
| 
 | ||||
|     let allFiles2 = []; | ||||
|     let i = 0; | ||||
|     iterator = new OS.File.DirectoryIterator(currentDir); | ||||
|     await iterator.forEach(function(entry, index) { | ||||
|       test.is(i++, index, "Getting the correct index"); | ||||
|       allFiles2.push(entry); | ||||
|     }); | ||||
|     test.info("Obtained all files through forEach"); | ||||
|     is( | ||||
|       allFiles1.length, | ||||
|       allFiles2.length, | ||||
|       "Both runs returned the same number of files" | ||||
|     ); | ||||
|     for (let i = 0; i < allFiles1.length; ++i) { | ||||
|       if (allFiles1[i].path != allFiles2[i].path) { | ||||
|         test.is( | ||||
|           allFiles1[i].path, | ||||
|           allFiles2[i].path, | ||||
|           "Both runs return the same files" | ||||
|         ); | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // Testing batch iteration + whether an iteration can be stopped early
 | ||||
|     let BATCH_LENGTH = 10; | ||||
|     test.info("Getting some files through nextBatch"); | ||||
|     await iterator.close(); | ||||
| 
 | ||||
|     iterator = new OS.File.DirectoryIterator(currentDir); | ||||
|     let someFiles1 = await iterator.nextBatch(BATCH_LENGTH); | ||||
|     let someFiles2 = await iterator.nextBatch(BATCH_LENGTH); | ||||
|     await iterator.close(); | ||||
| 
 | ||||
|     iterator = new OS.File.DirectoryIterator(currentDir); | ||||
|     await iterator.forEach(function cb(entry, index, iterator) { | ||||
|       if (index < BATCH_LENGTH) { | ||||
|         test.is( | ||||
|           entry.path, | ||||
|           someFiles1[index].path, | ||||
|           "Both runs return the same files (part 1)" | ||||
|         ); | ||||
|       } else if (index < 2 * BATCH_LENGTH) { | ||||
|         test.is( | ||||
|           entry.path, | ||||
|           someFiles2[index - BATCH_LENGTH].path, | ||||
|           "Both runs return the same files (part 2)" | ||||
|         ); | ||||
|       } else if (index == 2 * BATCH_LENGTH) { | ||||
|         test.info("Attempting to stop asynchronous forEach"); | ||||
|         return iterator.close(); | ||||
|       } else { | ||||
|         test.fail("Can we stop an asynchronous forEach? " + index); | ||||
|       } | ||||
|       return null; | ||||
|     }); | ||||
|     await iterator.close(); | ||||
| 
 | ||||
|     // Ensuring that we find new files if they appear
 | ||||
|     let file = await OS.File.open(temporary_file_name, { write: true }); | ||||
|     file.close(); | ||||
|     iterator = new OS.File.DirectoryIterator(currentDir); | ||||
|     try { | ||||
|       let files = await iterator.nextBatch(); | ||||
|       is( | ||||
|         files.length, | ||||
|         allFiles1.length + 1, | ||||
|         "The directory iterator has noticed the new file" | ||||
|       ); | ||||
|       let exists = await iterator.exists(); | ||||
|       test.ok( | ||||
|         exists, | ||||
|         "After nextBatch, iterator detects that the directory exists" | ||||
|       ); | ||||
|     } finally { | ||||
|       await iterator.close(); | ||||
|     } | ||||
| 
 | ||||
|     // Ensuring that opening a non-existing directory fails consistently
 | ||||
|     // once iteration starts.
 | ||||
|     try { | ||||
|       iterator = null; | ||||
|       iterator = new OS.File.DirectoryIterator("/I do not exist"); | ||||
|       let exists = await iterator.exists(); | ||||
|       test.ok( | ||||
|         !exists, | ||||
|         "Before any iteration, iterator detects that the directory doesn't exist" | ||||
|       ); | ||||
|       let exn = null; | ||||
|       try { | ||||
|         await iterator.next(); | ||||
|       } catch (ex) { | ||||
|         if (ex instanceof OS.File.Error && ex.becauseNoSuchFile) { | ||||
|           exn = ex; | ||||
|           let exists = await iterator.exists(); | ||||
|           test.ok( | ||||
|             !exists, | ||||
|             "After one iteration, iterator detects that the directory doesn't exist" | ||||
|           ); | ||||
|         } else { | ||||
|           throw ex; | ||||
|         } | ||||
|       } | ||||
|       test.ok( | ||||
|         exn, | ||||
|         "Iterating through a directory that does not exist has failed with becauseNoSuchFile" | ||||
|       ); | ||||
|     } finally { | ||||
|       if (iterator) { | ||||
|         iterator.close(); | ||||
|       } | ||||
|     } | ||||
|     test.ok( | ||||
|       !!iterator, | ||||
|       "The directory iterator for a non-existing directory was correctly created" | ||||
|     ); | ||||
|   })(); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Test OS.File.prototype.{exists} | ||||
|  */ | ||||
| var test_exists = maketest("exists", function exists(test) { | ||||
|   return (async function() { | ||||
|     let fileExists = await OS.File.exists(EXISTING_FILE); | ||||
|     test.ok(fileExists, "file exists"); | ||||
|     fileExists = await OS.File.exists(EXISTING_FILE + ".tmp"); | ||||
|     test.ok(!fileExists, "file does not exists"); | ||||
|   })(); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Test changes to OS.Shared.DEBUG flag. | ||||
|  */ | ||||
| var test_debug = maketest("debug", function debug(test) { | ||||
|   return (async function() { | ||||
|     function testSetDebugPref(pref) { | ||||
|       try { | ||||
|         Services.prefs.setBoolPref("toolkit.osfile.log", pref); | ||||
|       } catch (x) { | ||||
|         test.fail( | ||||
|           "Setting OS.Shared.DEBUG to " + pref + " should not cause error." | ||||
|         ); | ||||
|       } finally { | ||||
|         test.is(OS.Shared.DEBUG, pref, "OS.Shared.DEBUG is set correctly."); | ||||
|       } | ||||
|     } | ||||
|     testSetDebugPref(true); | ||||
|     let workerDEBUG = await OS.File.GET_DEBUG(); | ||||
|     test.is(workerDEBUG, true, "Worker's DEBUG is set."); | ||||
|     testSetDebugPref(false); | ||||
|     workerDEBUG = await OS.File.GET_DEBUG(); | ||||
|     test.is(workerDEBUG, false, "Worker's DEBUG is unset."); | ||||
|   })(); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Test logging in the main thread with set OS.Shared.DEBUG and | ||||
|  * OS.Shared.TEST flags. | ||||
|  */ | ||||
| var test_debug_test = maketest("debug_test", function debug_test(test) { | ||||
|   return (async function() { | ||||
|     // Create a console listener.
 | ||||
|     let consoleListener = { | ||||
|       observe(aMessage) { | ||||
|         // Ignore unexpected messages.
 | ||||
|         if (!(aMessage instanceof Ci.nsIConsoleMessage)) { | ||||
|           return; | ||||
|         } | ||||
|         if (!aMessage.message.includes("TEST OS")) { | ||||
|           return; | ||||
|         } | ||||
|         test.ok(true, "DEBUG TEST messages are logged correctly."); | ||||
|       }, | ||||
|     }; | ||||
|     toggleDebugTest(true, consoleListener); | ||||
|     // Execution of OS.File.exist method will trigger OS.File.LOG several times.
 | ||||
|     await OS.File.exists(EXISTING_FILE); | ||||
|     toggleDebugTest(false, consoleListener); | ||||
|   })(); | ||||
| }); | ||||
|  | @ -1,21 +0,0 @@ | |||
| <?xml version="1.0"?> | ||||
| <!-- | ||||
|   Any copyright is dedicated to the Public Domain. | ||||
|   http://creativecommons.org/publicdomain/zero/1.0/ | ||||
| --> | ||||
| <window title="Testing async I/O with OS.File" | ||||
|         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" | ||||
|         onload="test();"> | ||||
| 
 | ||||
|   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> | ||||
|   <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> | ||||
|   <script type="application/javascript" | ||||
|           src="main_test_osfile_async.js"/> | ||||
| 
 | ||||
|   <body xmlns="http://www.w3.org/1999/xhtml"> | ||||
|     <p id="display"></p> | ||||
|     <div id="content" style="display:none;"></div> | ||||
|     <pre id="test"></pre> | ||||
|   </body> | ||||
|   <label id="test-result"/> | ||||
| </window> | ||||
|  | @ -1,44 +0,0 @@ | |||
| <?xml version="1.0"?> | ||||
| <!-- | ||||
|   Any copyright is dedicated to the Public Domain. | ||||
|   http://creativecommons.org/publicdomain/zero/1.0/ | ||||
| --> | ||||
| <window title="Testing OS.File on a chrome worker thread" | ||||
|         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" | ||||
|         onload="test();"> | ||||
| 
 | ||||
|   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> | ||||
|   <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> | ||||
|   <script type="application/javascript" | ||||
|           src="chrome://mochikit/content/tests/SimpleTest/WorkerHandler.js"/> | ||||
|   <script type="application/javascript"> | ||||
|   <![CDATA[ | ||||
| 
 | ||||
| let worker; | ||||
| 
 | ||||
| function test() { | ||||
|   ok(true, "test_osfile.xul: Starting test"); | ||||
|   if (navigator.platform.includes("Win")) { | ||||
|     ok(true, "test_osfile.xul: Using Windows test suite"); | ||||
|     worker = new ChromeWorker("worker_test_osfile_win.js"); | ||||
|   } else { | ||||
|     ok(true, "test_osfile.xul: Using Unix test suite"); | ||||
|     worker = new ChromeWorker("worker_test_osfile_unix.js"); | ||||
|   } | ||||
|   SimpleTest.waitForExplicitFinish(); | ||||
|   ok(true, "test_osfile.xul: Chrome worker created"); | ||||
|   dump("MAIN: go\n"); | ||||
|   listenForTests(worker); | ||||
|   worker.postMessage(0); | ||||
|   ok(true, "test_osfile.xul: Test in progress"); | ||||
| }; | ||||
| ]]> | ||||
|   </script> | ||||
| 
 | ||||
|   <body xmlns="http://www.w3.org/1999/xhtml"> | ||||
|     <p id="display"></p> | ||||
|     <div id="content" style="display:none;"></div> | ||||
|     <pre id="test"></pre> | ||||
|   </body> | ||||
|   <label id="test-result"/> | ||||
| </window> | ||||
|  | @ -1,88 +0,0 @@ | |||
| <?xml version="1.0"?> | ||||
| <!-- | ||||
|   Any copyright is dedicated to the Public Domain. | ||||
|   http://creativecommons.org/publicdomain/zero/1.0/ | ||||
| --> | ||||
| <window title="Testing OS.File on a chrome worker thread" | ||||
|         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" | ||||
|         onload="test();"> | ||||
| 
 | ||||
|   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> | ||||
|   <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> | ||||
|   <script type="application/javascript"> | ||||
|   <![CDATA[ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| let worker; | ||||
| 
 | ||||
| SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal", | ||||
|                                     true]]}); | ||||
| let test = function test() { | ||||
|   SimpleTest.info("test_osfile_comms.xhtml: Starting test"); | ||||
|   // These are used in the worker. | ||||
|   // eslint-disable-next-line no-unused-vars | ||||
|   let { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm"); | ||||
|   // eslint-disable-next-line no-unused-vars | ||||
|   let { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
|   worker = new ChromeWorker("worker_test_osfile_comms.js"); | ||||
|   SimpleTest.waitForExplicitFinish(); | ||||
|   try { | ||||
|     worker.onerror = function onerror(error) { | ||||
|       SimpleTest.ok(false, "received error "+error); | ||||
|     } | ||||
|     worker.onmessage = function onmessage(msg) { | ||||
|       Cu.forceShrinkingGC(); | ||||
|       switch (msg.data.kind) { | ||||
|         case "is": | ||||
|           SimpleTest.ok(msg.data.outcome, msg.data.description + | ||||
|           " ("+ msg.data.a + " ==? " + msg.data.b + ")" ); | ||||
|           return; | ||||
|         case "isnot": | ||||
|           SimpleTest.ok(msg.data.outcome, msg.data.description + | ||||
|           " ("+ msg.data.a + " !=? " + msg.data.b + ")" ); | ||||
|           return; | ||||
|         case "ok": | ||||
|           SimpleTest.ok(msg.data.condition, msg.data.description); | ||||
|           return; | ||||
|         case "info": | ||||
|           SimpleTest.info(msg.data.description); | ||||
|           return; | ||||
|         case "finish": | ||||
|           SimpleTest.finish(); | ||||
|           return; | ||||
|         case "value": | ||||
|           SimpleTest.ok(true, "test_osfile_comms.xhtml: Received value " + JSON.stringify(msg.data.value)); | ||||
|           // eslint-disable-next-line no-eval | ||||
|           let type = eval(msg.data.typename); | ||||
|           // eslint-disable-next-line no-eval | ||||
|           let check = eval(msg.data.check); | ||||
|           let value = msg.data.value; | ||||
|           let deserialized = type.fromMsg(value); | ||||
|           check(deserialized, "Main thread test: "); | ||||
|           return; | ||||
|       default: | ||||
|         SimpleTest.ok(false, "test_osfile_comms.xhtml: wrong message "+JSON.stringify(msg.data)); | ||||
|       } | ||||
|     }; | ||||
|     worker.postMessage(0) | ||||
|     ok(true, "Worker launched"); | ||||
|   } catch(x) { | ||||
|     // As we have set |waitForExplicitFinish|, we add this fallback | ||||
|     // in case of uncaught error, to ensure that the test does not | ||||
|     // just freeze. | ||||
|     ok(false, "Caught exception " + x + " at " + x.stack); | ||||
|     SimpleTest.finish(); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| ]]> | ||||
|   </script> | ||||
| 
 | ||||
|   <body xmlns="http://www.w3.org/1999/xhtml"> | ||||
|     <p id="display"></p> | ||||
|     <div id="content" style="display:none;"></div> | ||||
|     <pre id="test"></pre> | ||||
|   </body> | ||||
|   <label id="test-result"/> | ||||
| </window> | ||||
|  | @ -1,42 +0,0 @@ | |||
| <?xml version="1.0"?> | ||||
| <!-- | ||||
|   Any copyright is dedicated to the Public Domain. | ||||
|   http://creativecommons.org/publicdomain/zero/1.0/ | ||||
| --> | ||||
| <window title="Testing OS.File on a chrome worker thread" | ||||
|         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" | ||||
|         onload="test();"> | ||||
| 
 | ||||
|   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> | ||||
|   <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> | ||||
|   <script type="application/javascript" | ||||
|           src="chrome://mochikit/content/tests/SimpleTest/WorkerHandler.js"/> | ||||
|   <script type="application/javascript"> | ||||
|   <![CDATA[ | ||||
| 
 | ||||
| let worker; | ||||
| let main = this; | ||||
| 
 | ||||
| function test() { | ||||
|   info("test_osfile_front.xhtml: Starting test"); | ||||
| 
 | ||||
|   // Test the OS.File worker | ||||
| 
 | ||||
|   worker = new ChromeWorker("worker_test_osfile_front.js"); | ||||
|   SimpleTest.waitForExplicitFinish(); | ||||
|   info("test_osfile_front.xhtml: Chrome worker created"); | ||||
|   dump("MAIN: go\n"); | ||||
|   listenForTests(worker); | ||||
|   worker.postMessage(0); | ||||
|   ok(true, "test_osfile_front.xhtml: Test in progress"); | ||||
| }; | ||||
| ]]> | ||||
|   </script> | ||||
| 
 | ||||
|   <body xmlns="http://www.w3.org/1999/xhtml"> | ||||
|     <p id="display"></p> | ||||
|     <div id="content" style="display:none;"></div> | ||||
|     <pre id="test"></pre> | ||||
|   </body> | ||||
|   <label id="test-result"/> | ||||
| </window> | ||||
|  | @ -1,205 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| /* eslint-env mozilla/chrome-worker, node */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| /* import-globals-from /testing/mochitest/tests/SimpleTest/WorkerSimpleTest.js */ | ||||
| importScripts("chrome://mochikit/content/tests/SimpleTest/WorkerSimpleTest.js"); | ||||
| 
 | ||||
| // The set of samples for communications test. Declare as a global
 | ||||
| // variable to prevent this from being garbage-collected too early.
 | ||||
| var samples; | ||||
| 
 | ||||
| self.onmessage = function(msg) { | ||||
|   info("Initializing"); | ||||
|   self.onmessage = function on_unexpected_message(msg) { | ||||
|     throw new Error("Unexpected message " + JSON.stringify(msg.data)); | ||||
|   }; | ||||
|   /* import-globals-from /toolkit/components/osfile/osfile.jsm */ | ||||
|   importScripts("resource://gre/modules/osfile.jsm"); | ||||
|   info("Initialization complete"); | ||||
| 
 | ||||
|   samples = [ | ||||
|     { | ||||
|       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: "Typed array", | ||||
|       value: (function() { | ||||
|         let view = new Uint8Array(15); | ||||
|         for (let i = 0; i < 15; ++i) { | ||||
|           view[i] = i; | ||||
|         } | ||||
|         return view; | ||||
|       })(), | ||||
|       type: OS.Shared.Type.char.in_ptr, | ||||
|       check: function check_ArrayBuffer(candidate, prefix) { | ||||
|         for (let i = 0; i < 15; ++i) { | ||||
|           is( | ||||
|             candidate[i], | ||||
|             i % 256, | ||||
|             prefix + | ||||
|               "Checking that the contents of the ArrayBuffer were preserved" | ||||
|           ); | ||||
|         } | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       typename: "OS.Shared.Type.char.in_ptr", | ||||
|       valuedescr: "Pointer", | ||||
|       value: new OS.Shared.Type.char.in_ptr.implementation(1), | ||||
|       type: OS.Shared.Type.char.in_ptr, | ||||
|       check: function check_ptr(candidate, prefix) { | ||||
|         let address = ctypes.cast(candidate, ctypes.uintptr_t).value.toString(); | ||||
|         is( | ||||
|           address, | ||||
|           "1", | ||||
|           prefix + "Checking that the pointer address was preserved" | ||||
|         ); | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       typename: "OS.Shared.Type.char.in_ptr", | ||||
|       valuedescr: "C array", | ||||
|       value: (function() { | ||||
|         let buf = new (ctypes.ArrayType(ctypes.uint8_t, 15))(); | ||||
|         for (let i = 0; i < 15; ++i) { | ||||
|           buf[i] = i % 256; | ||||
|         } | ||||
|         return buf; | ||||
|       })(), | ||||
|       type: OS.Shared.Type.char.in_ptr, | ||||
|       check: function check_array(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 C array were preserved, index " + | ||||
|               i | ||||
|           ); | ||||
|           cast = cast.increment(); | ||||
|         } | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       typename: "OS.File.Error", | ||||
|       valuedescr: "OS Error", | ||||
|       type: OS.File.Error, | ||||
|       value: new OS.File.Error("foo", 1), | ||||
|       check: function check_error(candidate, prefix) { | ||||
|         ok( | ||||
|           candidate instanceof OS.File.Error, | ||||
|           prefix + "Error is an OS.File.Error" | ||||
|         ); | ||||
|         ok( | ||||
|           candidate.unixErrno == 1 || candidate.winLastError == 1, | ||||
|           prefix + "Error code is correct" | ||||
|         ); | ||||
|         try { | ||||
|           let string = candidate.toString(); | ||||
|           info(prefix + ".toString() works " + string); | ||||
|         } catch (x) { | ||||
|           ok(false, prefix + ".toString() fails " + x); | ||||
|         } | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
|   samples.forEach(function test(sample) { | ||||
|     let type = sample.type; | ||||
|     let value = sample.value; | ||||
|     let check = sample.check; | ||||
|     info( | ||||
|       "Testing handling of type " + | ||||
|         sample.typename + | ||||
|         " communicating " + | ||||
|         sample.valuedescr | ||||
|     ); | ||||
| 
 | ||||
|     // 1. Test serialization
 | ||||
|     let serialized; | ||||
|     let exn; | ||||
|     try { | ||||
|       serialized = type.toMsg(value); | ||||
|     } catch (ex) { | ||||
|       exn = ex; | ||||
|     } | ||||
|     is( | ||||
|       exn, | ||||
|       null, | ||||
|       "Can I serialize the following value? " + | ||||
|         value + | ||||
|         " aka " + | ||||
|         JSON.stringify(value) | ||||
|     ); | ||||
|     if (exn) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if ("data" in serialized) { | ||||
|       // Unwrap from `Meta`
 | ||||
|       serialized = serialized.data; | ||||
|     } | ||||
| 
 | ||||
|     // 2. Test deserialization
 | ||||
|     let deserialized; | ||||
|     try { | ||||
|       deserialized = type.fromMsg(serialized); | ||||
|     } catch (ex) { | ||||
|       exn = ex; | ||||
|     } | ||||
|     is( | ||||
|       exn, | ||||
|       null, | ||||
|       "Can I deserialize the following message? " + | ||||
|         serialized + | ||||
|         " aka " + | ||||
|         JSON.stringify(serialized) | ||||
|     ); | ||||
|     if (exn) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 3. Local test deserialized value
 | ||||
|     info( | ||||
|       "Running test on deserialized value " + | ||||
|         deserialized + | ||||
|         " aka " + | ||||
|         JSON.stringify(deserialized) | ||||
|     ); | ||||
|     check(deserialized, "Local test: "); | ||||
| 
 | ||||
|     // 4. Test sending serialized
 | ||||
|     info("Attempting to send message"); | ||||
|     try { | ||||
|       self.postMessage({ | ||||
|         kind: "value", | ||||
|         typename: sample.typename, | ||||
|         value: serialized, | ||||
|         check: check.toSource(), | ||||
|       }); | ||||
|     } catch (ex) { | ||||
|       exn = ex; | ||||
|     } | ||||
|     is( | ||||
|       exn, | ||||
|       null, | ||||
|       "Can I send the following message? " + | ||||
|         serialized + | ||||
|         " aka " + | ||||
|         JSON.stringify(serialized) | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   finish(); | ||||
| }; | ||||
|  | @ -1,696 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| /* eslint-env mozilla/chrome-worker, node */ | ||||
| 
 | ||||
| /* import-globals-from /testing/mochitest/tests/SimpleTest/WorkerSimpleTest.js */ | ||||
| importScripts("chrome://mochikit/content/tests/SimpleTest/WorkerSimpleTest.js"); | ||||
| /* import-globals-from /toolkit/components/workerloader/require.js */ | ||||
| importScripts("resource://gre/modules/workers/require.js"); | ||||
| 
 | ||||
| var SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); | ||||
| SharedAll.Config.DEBUG = true; | ||||
| 
 | ||||
| function should_throw(f) { | ||||
|   try { | ||||
|     f(); | ||||
|   } catch (x) { | ||||
|     return x; | ||||
|   } | ||||
|   return null; | ||||
| } | ||||
| 
 | ||||
| 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_flush_open_file(); | ||||
|     test_copy_existing_file(); | ||||
|     test_position(); | ||||
|     test_move_file(); | ||||
|     test_iter_dir(); | ||||
|     test_info(); | ||||
|     test_path(); | ||||
|     test_exists_file(); | ||||
|     test_remove_file(); | ||||
|   } catch (x) { | ||||
|     log("Catching error: " + x); | ||||
|     log("Stack: " + x.stack); | ||||
|     log("Source: " + x.toSource()); | ||||
|     ok(false, x.toString() + "\n" + x.stack); | ||||
|   } | ||||
|   finish(); | ||||
| }; | ||||
| 
 | ||||
| function test_init() { | ||||
|   info("Starting test_init"); | ||||
|   /* import-globals-from /toolkit/components/osfile/osfile.jsm */ | ||||
|   importScripts("resource://gre/modules/osfile.jsm"); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test that we can open an existing file. | ||||
|  */ | ||||
| function test_open_existing_file() { | ||||
|   info("Starting test_open_existing"); | ||||
|   let file = OS.File.open( | ||||
|     "chrome/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js" | ||||
|   ); | ||||
|   file.close(); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test that opening a file that does not exist fails with the right error. | ||||
|  */ | ||||
| function test_open_non_existing_file() { | ||||
|   info("Starting test_open_non_existing"); | ||||
|   let exn; | ||||
|   try { | ||||
|     OS.File.open("/I do not exist"); | ||||
|   } catch (x) { | ||||
|     exn = x; | ||||
|     info("test_open_non_existing_file: Exception detail " + exn); | ||||
|   } | ||||
|   ok(!!exn, "test_open_non_existing_file: Exception was raised "); | ||||
|   ok( | ||||
|     exn instanceof OS.File.Error, | ||||
|     "test_open_non_existing_file: Exception was a OS.File.Error" | ||||
|   ); | ||||
|   ok( | ||||
|     exn.becauseNoSuchFile, | ||||
|     "test_open_non_existing_file: Exception confirms that the file does not exist" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test that to ensure that |foo.flush()| does not | ||||
|  * cause an error, where |foo| is an open file. | ||||
|  */ | ||||
| function test_flush_open_file() { | ||||
|   info("Starting test_flush_open_file"); | ||||
|   let tmp = "test_flush.tmp"; | ||||
|   let file = OS.File.open(tmp, { create: true, write: true }); | ||||
|   file.flush(); | ||||
|   file.close(); | ||||
|   OS.File.remove(tmp); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Utility function for comparing two files (or a prefix of two files). | ||||
|  * | ||||
|  * This function returns nothing but fails of both files (or prefixes) | ||||
|  * are not identical. | ||||
|  * | ||||
|  * @param {string} test The name of the test (used for logging). | ||||
|  * @param {string} sourcePath The name of the first file. | ||||
|  * @param {string} destPath The name of the second file. | ||||
|  * @param {number=} prefix If specified, only compare the |prefix| | ||||
|  * first bytes of |sourcePath| and |destPath|. | ||||
|  */ | ||||
| function compare_files(test, sourcePath, destPath, prefix) { | ||||
|   info(test + ": Comparing " + sourcePath + " and " + destPath); | ||||
|   let source = OS.File.open(sourcePath); | ||||
|   let dest = OS.File.open(destPath); | ||||
|   info("Files are open"); | ||||
|   let sourceResult, destResult; | ||||
|   try { | ||||
|     if (prefix != undefined) { | ||||
|       sourceResult = source.read(prefix); | ||||
|       destResult = dest.read(prefix); | ||||
|     } else { | ||||
|       sourceResult = source.read(); | ||||
|       destResult = dest.read(); | ||||
|     } | ||||
|     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(); | ||||
|   } | ||||
|   info(test + ": Comparison complete"); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test that copying a file using |copy| works. | ||||
|  */ | ||||
| function test_copy_existing_file() { | ||||
|   let src_file_name = OS.Path.join( | ||||
|     "chrome", | ||||
|     "toolkit", | ||||
|     "components", | ||||
|     "osfile", | ||||
|     "tests", | ||||
|     "mochi", | ||||
|     "worker_test_osfile_front.js" | ||||
|   ); | ||||
|   let tmp_file_name = "test_osfile_front.tmp"; | ||||
|   info("Starting test_copy_existing"); | ||||
|   OS.File.copy(src_file_name, tmp_file_name); | ||||
| 
 | ||||
|   info("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 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
 | ||||
|   let exn; | ||||
|   try { | ||||
|     OS.File.copy(src_file_name, tmp_file_name, { noOverwrite: true }); | ||||
|   } catch (x) { | ||||
|     exn = x; | ||||
|   } | ||||
|   ok( | ||||
|     !!exn, | ||||
|     "test_copy_existing: noOverwrite prevents overwriting existing files" | ||||
|   ); | ||||
| 
 | ||||
|   info("test_copy_existing: Cleaning up"); | ||||
|   OS.File.remove(tmp_file_name); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test that moving a file works. | ||||
|  */ | ||||
| function test_move_file() { | ||||
|   info("test_move_file: Starting"); | ||||
|   // 1. Copy file into a temporary file
 | ||||
|   let src_file_name = OS.Path.join( | ||||
|     "chrome", | ||||
|     "toolkit", | ||||
|     "components", | ||||
|     "osfile", | ||||
|     "tests", | ||||
|     "mochi", | ||||
|     "worker_test_osfile_front.js" | ||||
|   ); | ||||
|   let tmp_file_name = "test_osfile_front.tmp"; | ||||
|   let tmp2_file_name = "test_osfile_front.tmp2"; | ||||
|   OS.File.copy(src_file_name, tmp_file_name); | ||||
| 
 | ||||
|   info("test_move_file: Copy complete"); | ||||
| 
 | ||||
|   // 2. Move
 | ||||
|   OS.File.move(tmp_file_name, tmp2_file_name); | ||||
| 
 | ||||
|   info("test_move_file: Move complete"); | ||||
| 
 | ||||
|   // 3. Check that destination exists
 | ||||
|   compare_files("test_move_file", src_file_name, tmp2_file_name); | ||||
| 
 | ||||
|   // 4. Check that original file does not exist anymore
 | ||||
|   let exn; | ||||
|   try { | ||||
|     OS.File.open(tmp_file_name); | ||||
|   } catch (x) { | ||||
|     exn = x; | ||||
|   } | ||||
|   ok(!!exn, "test_move_file: Original file has been removed"); | ||||
| 
 | ||||
|   info("test_move_file: Cleaning up"); | ||||
|   OS.File.remove(tmp2_file_name); | ||||
| } | ||||
| 
 | ||||
| function test_iter_dir() { | ||||
|   info("test_iter_dir: Starting"); | ||||
| 
 | ||||
|   // Create a file, to be sure that it exists
 | ||||
|   let tmp_file_name = "test_osfile_front.tmp"; | ||||
|   let tmp_file = OS.File.open(tmp_file_name, { write: true, trunc: true }); | ||||
|   tmp_file.close(); | ||||
| 
 | ||||
|   let parent = OS.File.getCurrentDirectory(); | ||||
|   info("test_iter_dir: directory " + parent); | ||||
|   let iterator = new OS.File.DirectoryIterator(parent); | ||||
|   info("test_iter_dir: iterator created"); | ||||
|   let encountered_tmp_file = false; | ||||
|   for (let entry of iterator) { | ||||
|     // Checking that |name| can be decoded properly
 | ||||
|     info("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.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) { | ||||
|         success = false; | ||||
|       } | ||||
|     } | ||||
|     if (file) { | ||||
|       file.close(); | ||||
|     } | ||||
|     ok(success, "test_iter_dir: Entry " + entry.path + " exists"); | ||||
| 
 | ||||
|     if (OS.Win) { | ||||
|       // We assume that the files are at least as recent as 2009.
 | ||||
|       // Since this test was written in 2011 and some of our packaging
 | ||||
|       // sets dates arbitrarily to 2010, this should be safe.
 | ||||
|       let year = new Date().getFullYear(); | ||||
| 
 | ||||
|       let lastWrite = entry.winLastWriteDate; | ||||
|       ok( | ||||
|         lastWrite, | ||||
|         "test_iter_dir: Windows lastWrite date exists: " + lastWrite | ||||
|       ); | ||||
|       ok( | ||||
|         lastWrite.getFullYear() >= 2009 && lastWrite.getFullYear() <= year, | ||||
|         "test_iter_dir: consistent lastWrite date" | ||||
|       ); | ||||
| 
 | ||||
|       let lastAccess = entry.winLastAccessDate; | ||||
|       ok( | ||||
|         lastAccess, | ||||
|         "test_iter_dir: Windows lastAccess date exists: " + lastAccess | ||||
|       ); | ||||
|       ok( | ||||
|         lastAccess.getFullYear() >= 2009 && lastAccess.getFullYear() <= year, | ||||
|         "test_iter_dir: consistent lastAccess date" | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|   ok(encountered_tmp_file, "test_iter_dir: We have found the temporary file"); | ||||
| 
 | ||||
|   info("test_iter_dir: Cleaning up"); | ||||
|   iterator.close(); | ||||
| 
 | ||||
|   // Testing nextBatch()
 | ||||
|   iterator = new OS.File.DirectoryIterator(parent); | ||||
|   let allentries = []; | ||||
|   for (let x of iterator) { | ||||
|     allentries.push(x); | ||||
|   } | ||||
|   iterator.close(); | ||||
| 
 | ||||
|   ok( | ||||
|     allentries.length >= 14, | ||||
|     "test_iter_dir: Meta-check: the test directory should contain at least 14 items" | ||||
|   ); | ||||
| 
 | ||||
|   iterator = new OS.File.DirectoryIterator(parent); | ||||
|   let firstten = iterator.nextBatch(10); | ||||
|   is(firstten.length, 10, "test_iter_dir: nextBatch(10) returns 10 items"); | ||||
|   for (let i = 0; i < firstten.length; ++i) { | ||||
|     is( | ||||
|       allentries[i].path, | ||||
|       firstten[i].path, | ||||
|       "test_iter_dir: Checking that batch returns the correct entries" | ||||
|     ); | ||||
|   } | ||||
|   let nextthree = iterator.nextBatch(3); | ||||
|   is(nextthree.length, 3, "test_iter_dir: nextBatch(3) returns 3 items"); | ||||
|   for (let i = 0; i < nextthree.length; ++i) { | ||||
|     is( | ||||
|       allentries[i + firstten.length].path, | ||||
|       nextthree[i].path, | ||||
|       "test_iter_dir: Checking that batch 2 returns the correct entries" | ||||
|     ); | ||||
|   } | ||||
|   let everythingelse = iterator.nextBatch(); | ||||
|   ok( | ||||
|     everythingelse.length >= 1, | ||||
|     "test_iter_dir: nextBatch() returns at least one item" | ||||
|   ); | ||||
|   for (let i = 0; i < everythingelse.length; ++i) { | ||||
|     is( | ||||
|       allentries[i + firstten.length + nextthree.length].path, | ||||
|       everythingelse[i].path, | ||||
|       "test_iter_dir: Checking that batch 3 returns the correct entries" | ||||
|     ); | ||||
|   } | ||||
|   is( | ||||
|     iterator.nextBatch().length, | ||||
|     0, | ||||
|     "test_iter_dir: Once there is nothing left, nextBatch returns an empty array" | ||||
|   ); | ||||
|   iterator.close(); | ||||
| 
 | ||||
|   iterator = new OS.File.DirectoryIterator(parent); | ||||
|   iterator.close(); | ||||
|   is( | ||||
|     iterator.nextBatch().length, | ||||
|     0, | ||||
|     "test_iter_dir: nextBatch on closed iterator returns an empty array" | ||||
|   ); | ||||
| 
 | ||||
|   iterator = new OS.File.DirectoryIterator(parent); | ||||
|   let allentries2 = iterator.nextBatch(); | ||||
|   is( | ||||
|     allentries.length, | ||||
|     allentries2.length, | ||||
|     "test_iter_dir: Checking that getBatch(null) returns the right number of entries" | ||||
|   ); | ||||
|   for (let i = 0; i < allentries.length; ++i) { | ||||
|     is( | ||||
|       allentries[i].path, | ||||
|       allentries2[i].path, | ||||
|       "test_iter_dir: Checking that getBatch(null) returns everything in the right order" | ||||
|     ); | ||||
|   } | ||||
|   iterator.close(); | ||||
| 
 | ||||
|   // Test forEach
 | ||||
|   iterator = new OS.File.DirectoryIterator(parent); | ||||
|   let index = 0; | ||||
|   iterator.forEach(function cb(entry, aIndex, aIterator) { | ||||
|     is(index, aIndex, "test_iter_dir: Checking that forEach index is correct"); | ||||
|     ok( | ||||
|       iterator == aIterator, | ||||
|       "test_iter_dir: Checking that right iterator is passed" | ||||
|     ); | ||||
|     if (index < 10) { | ||||
|       is( | ||||
|         allentries[index].path, | ||||
|         entry.path, | ||||
|         "test_iter_dir: Checking that forEach entry is correct" | ||||
|       ); | ||||
|     } else if (index == 10) { | ||||
|       iterator.close(); | ||||
|     } else { | ||||
|       ok(false, "test_iter_dir: Checking that forEach can be stopped early"); | ||||
|     } | ||||
|     ++index; | ||||
|   }); | ||||
|   iterator.close(); | ||||
| 
 | ||||
|   // test for prototype |OS.File.DirectoryIterator.unixAsFile|
 | ||||
|   if ("unixAsFile" in OS.File.DirectoryIterator.prototype) { | ||||
|     info("testing property unixAsFile"); | ||||
|     let path = OS.Path.join( | ||||
|       "chrome", | ||||
|       "toolkit", | ||||
|       "components", | ||||
|       "osfile", | ||||
|       "tests", | ||||
|       "mochi" | ||||
|     ); | ||||
|     iterator = new OS.File.DirectoryIterator(path); | ||||
| 
 | ||||
|     let dir_file = iterator.unixAsFile(); // return |File|
 | ||||
|     let stat0 = dir_file.stat(); | ||||
|     let stat1 = OS.File.stat(path); | ||||
| 
 | ||||
|     let unix_info_to_string = function unix_info_to_string(info) { | ||||
|       return ( | ||||
|         "| " + | ||||
|         info.unixMode + | ||||
|         " | " + | ||||
|         info.unixOwner + | ||||
|         " | " + | ||||
|         info.unixGroup + | ||||
|         " | " + | ||||
|         info.lastModificationDate + | ||||
|         " | " + | ||||
|         info.lastAccessDate + | ||||
|         " | " + | ||||
|         info.size + | ||||
|         " |" | ||||
|       ); | ||||
|     }; | ||||
| 
 | ||||
|     let s0_string = unix_info_to_string(stat0); | ||||
|     let s1_string = unix_info_to_string(stat1); | ||||
| 
 | ||||
|     ok(stat0.isDir, "unixAsFile returned a directory"); | ||||
|     is(s0_string, s1_string, "unixAsFile returned the correct file"); | ||||
|     dir_file.close(); | ||||
|     iterator.close(); | ||||
|   } | ||||
|   info("test_iter_dir: Complete"); | ||||
| } | ||||
| 
 | ||||
| function test_position() { | ||||
|   info("test_position: Starting"); | ||||
| 
 | ||||
|   ok("POS_START" in OS.File, "test_position: POS_START exists"); | ||||
|   ok("POS_CURRENT" in OS.File, "test_position: POS_CURRENT exists"); | ||||
|   ok("POS_END" in OS.File, "test_position: POS_END exists"); | ||||
| 
 | ||||
|   let ARBITRARY_POSITION = 321; | ||||
|   let src_file_name = OS.Path.join( | ||||
|     "chrome", | ||||
|     "toolkit", | ||||
|     "components", | ||||
|     "osfile", | ||||
|     "tests", | ||||
|     "mochi", | ||||
|     "worker_test_osfile_front.js" | ||||
|   ); | ||||
| 
 | ||||
|   let file = OS.File.open(src_file_name); | ||||
|   is(file.getPosition(), 0, "test_position: Initial position is 0"); | ||||
| 
 | ||||
|   let size = 0 + file.stat().size; // Hack: We can remove this 0 + once 776259 has landed
 | ||||
| 
 | ||||
|   file.setPosition(ARBITRARY_POSITION, OS.File.POS_START); | ||||
|   is( | ||||
|     file.getPosition(), | ||||
|     ARBITRARY_POSITION, | ||||
|     "test_position: Setting position from start" | ||||
|   ); | ||||
| 
 | ||||
|   file.setPosition(0, OS.File.POS_START); | ||||
|   is( | ||||
|     file.getPosition(), | ||||
|     0, | ||||
|     "test_position: Setting position from start back to 0" | ||||
|   ); | ||||
| 
 | ||||
|   file.setPosition(ARBITRARY_POSITION); | ||||
|   is( | ||||
|     file.getPosition(), | ||||
|     ARBITRARY_POSITION, | ||||
|     "test_position: Setting position without argument" | ||||
|   ); | ||||
| 
 | ||||
|   file.setPosition(-ARBITRARY_POSITION, OS.File.POS_END); | ||||
|   is( | ||||
|     file.getPosition(), | ||||
|     size - ARBITRARY_POSITION, | ||||
|     "test_position: Setting position from end" | ||||
|   ); | ||||
| 
 | ||||
|   file.setPosition(ARBITRARY_POSITION, OS.File.POS_CURRENT); | ||||
|   is(file.getPosition(), size, "test_position: Setting position from current"); | ||||
| 
 | ||||
|   file.close(); | ||||
|   info("test_position: Complete"); | ||||
| } | ||||
| 
 | ||||
| function test_info() { | ||||
|   info("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); | ||||
|     info("test_info: Cleaned up previous garbage"); | ||||
|   } catch (x) { | ||||
|     if (!x.becauseNoSuchFile) { | ||||
|       throw x; | ||||
|     } | ||||
|     info("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 stat = OS.File.stat(filename); | ||||
|   ok(!!stat, "test_info: info acquired"); | ||||
|   ok(!stat.isDir, "test_info: file is not a directory"); | ||||
|   is(stat.isSymLink, false, "test_info: file is not a link"); | ||||
|   is(stat.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 (no clear specifications about that, but it seems
 | ||||
|   // that this can be a little over 1 second under ext3 and 2 seconds
 | ||||
|   // under FAT).
 | ||||
|   let SLOPPY_FILE_SYSTEM_ADJUSTMENT = 3000; | ||||
|   let startMs = start.getTime() - SLOPPY_FILE_SYSTEM_ADJUSTMENT; | ||||
|   let stopMs = stop.getTime() + SLOPPY_FILE_SYSTEM_ADJUSTMENT; | ||||
|   info("Testing stat with bounds [ " + startMs + ", " + stopMs + " ]"); | ||||
| 
 | ||||
|   let change = stat.lastModificationDate; | ||||
|   info("Testing lastModificationDate: " + change); | ||||
|   ok( | ||||
|     change.getTime() >= startMs && change.getTime() <= stopMs, | ||||
|     "test_info: lastModificationDate is consistent" | ||||
|   ); | ||||
| 
 | ||||
|   // Test OS.File.prototype.stat on new file
 | ||||
|   file = OS.File.open(filename); | ||||
|   try { | ||||
|     stat = file.stat(); | ||||
|   } finally { | ||||
|     file.close(); | ||||
|   } | ||||
| 
 | ||||
|   ok(!!stat, "test_info: info acquired 2"); | ||||
|   ok(!stat.isDir, "test_info: file is not a directory 2"); | ||||
|   ok(!stat.isSymLink, "test_info: file is not a link 2"); | ||||
|   is(stat.size.toString(), size, "test_info: correct size 2"); | ||||
| 
 | ||||
|   stop = new Date(); | ||||
| 
 | ||||
|   // Round up/down as above
 | ||||
|   startMs = start.getTime() - SLOPPY_FILE_SYSTEM_ADJUSTMENT; | ||||
|   stopMs = stop.getTime() + SLOPPY_FILE_SYSTEM_ADJUSTMENT; | ||||
|   info("Testing stat 2 with bounds [ " + startMs + ", " + stopMs + " ]"); | ||||
| 
 | ||||
|   let access = stat.lastAccessDate; | ||||
|   info("Testing lastAccessDate: " + access); | ||||
|   ok( | ||||
|     access.getTime() >= startMs && access.getTime() <= stopMs, | ||||
|     "test_info: lastAccessDate is consistent" | ||||
|   ); | ||||
| 
 | ||||
|   change = stat.lastModificationDate; | ||||
|   info("Testing lastModificationDate 2: " + change); | ||||
|   ok( | ||||
|     change.getTime() >= startMs && change.getTime() <= stopMs, | ||||
|     "test_info: lastModificationDate 2 is consistent" | ||||
|   ); | ||||
| 
 | ||||
|   // Test OS.File.stat on directory
 | ||||
|   stat = OS.File.stat(OS.File.getCurrentDirectory()); | ||||
|   ok(!!stat, "test_info: info on directory acquired"); | ||||
|   ok(stat.isDir, "test_info: directory is a directory"); | ||||
| 
 | ||||
|   info("test_info: Complete"); | ||||
| } | ||||
| 
 | ||||
| // Note that most of the features of path are tested in
 | ||||
| // worker_test_osfile_{unix, win}.js
 | ||||
| function test_path() { | ||||
|   info("test_path: starting"); | ||||
|   let abcd = OS.Path.join("a", "b", "c", "d"); | ||||
|   is(OS.Path.basename(abcd), "d", "basename of a/b/c/d"); | ||||
| 
 | ||||
|   let abc = OS.Path.join("a", "b", "c"); | ||||
|   is(OS.Path.dirname(abcd), abc, "dirname of a/b/c/d"); | ||||
| 
 | ||||
|   let abdotsc = OS.Path.join("a", "b", "..", "c"); | ||||
|   is(OS.Path.normalize(abdotsc), OS.Path.join("a", "c"), "normalize a/b/../c"); | ||||
| 
 | ||||
|   let adotsdotsdots = OS.Path.join("a", "..", "..", ".."); | ||||
|   is( | ||||
|     OS.Path.normalize(adotsdotsdots), | ||||
|     OS.Path.join("..", ".."), | ||||
|     "normalize a/../../.." | ||||
|   ); | ||||
| 
 | ||||
|   info("test_path: Complete"); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test the file |exists| method. | ||||
|  */ | ||||
| function test_exists_file() { | ||||
|   let file_name = OS.Path.join( | ||||
|     "chrome", | ||||
|     "toolkit", | ||||
|     "components", | ||||
|     "osfile", | ||||
|     "tests", | ||||
|     "mochi", | ||||
|     "test_osfile_front.xhtml" | ||||
|   ); | ||||
|   info("test_exists_file: starting"); | ||||
|   ok( | ||||
|     OS.File.exists(file_name), | ||||
|     "test_exists_file: file exists (OS.File.exists)" | ||||
|   ); | ||||
|   ok( | ||||
|     !OS.File.exists(file_name + ".tmp"), | ||||
|     "test_exists_file: file does not exists (OS.File.exists)" | ||||
|   ); | ||||
| 
 | ||||
|   let dir_name = OS.Path.join( | ||||
|     "chrome", | ||||
|     "toolkit", | ||||
|     "components", | ||||
|     "osfile", | ||||
|     "tests", | ||||
|     "mochi" | ||||
|   ); | ||||
|   ok(OS.File.exists(dir_name), "test_exists_file: directory exists"); | ||||
|   ok( | ||||
|     !OS.File.exists(dir_name) + ".tmp", | ||||
|     "test_exists_file: directory does not exist" | ||||
|   ); | ||||
| 
 | ||||
|   info("test_exists_file: complete"); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test the file |remove| method. | ||||
|  */ | ||||
| function test_remove_file() { | ||||
|   let absent_file_name = "test_osfile_front_absent.tmp"; | ||||
| 
 | ||||
|   // Check that removing absent files is handled correctly
 | ||||
|   let exn = should_throw(function() { | ||||
|     OS.File.remove(absent_file_name, { ignoreAbsent: false }); | ||||
|   }); | ||||
|   ok(!!exn, "test_remove_file: throws if there is no such file"); | ||||
| 
 | ||||
|   exn = should_throw(function() { | ||||
|     OS.File.remove(absent_file_name, { ignoreAbsent: true }); | ||||
|     OS.File.remove(absent_file_name); | ||||
|   }); | ||||
|   ok(!exn, "test_remove_file: ignoreAbsent works"); | ||||
| 
 | ||||
|   if (OS.Win) { | ||||
|     let file_name = "test_osfile_front_file_to_remove.tmp"; | ||||
|     let file = OS.File.open(file_name, { write: true }); | ||||
|     file.close(); | ||||
|     ok(OS.File.exists(file_name), "test_remove_file: test file exists"); | ||||
|     OS.Win.File.SetFileAttributes( | ||||
|       file_name, | ||||
|       OS.Constants.Win.FILE_ATTRIBUTE_READONLY | ||||
|     ); | ||||
|     OS.File.remove(file_name); | ||||
|     ok( | ||||
|       !OS.File.exists(file_name), | ||||
|       "test_remove_file: test file has been removed" | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | @ -1,257 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| /* eslint-env mozilla/chrome-worker, node */ | ||||
| 
 | ||||
| /* import-globals-from /testing/mochitest/tests/SimpleTest/WorkerSimpleTest.js */ | ||||
| importScripts("chrome://mochikit/content/tests/SimpleTest/WorkerSimpleTest.js"); | ||||
| 
 | ||||
| self.onmessage = function(msg) { | ||||
|   log("received message " + JSON.stringify(msg.data)); | ||||
|   self.onmessage = function(msg) { | ||||
|     log("ignored message " + JSON.stringify(msg.data)); | ||||
|   }; | ||||
|   test_init(); | ||||
|   test_getcwd(); | ||||
|   test_open_close(); | ||||
|   test_create_file(); | ||||
|   test_access(); | ||||
|   test_read_write(); | ||||
|   test_passing_undefined(); | ||||
|   finish(); | ||||
| }; | ||||
| 
 | ||||
| function test_init() { | ||||
|   info("Starting test_init"); | ||||
|   /* import-globals-from /toolkit/components/osfile/osfile.jsm */ | ||||
|   importScripts("resource://gre/modules/osfile.jsm"); | ||||
| } | ||||
| 
 | ||||
| function test_open_close() { | ||||
|   info("Starting test_open_close"); | ||||
|   is(typeof OS.Unix.File.open, "function", "OS.Unix.File.open is a function"); | ||||
|   let file = OS.Unix.File.open( | ||||
|     "chrome/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js", | ||||
|     OS.Constants.libc.O_RDONLY | ||||
|   ); | ||||
|   isnot(file, -1, "test_open_close: opening succeeded"); | ||||
|   info("Close: " + OS.Unix.File.close.toSource()); | ||||
|   let result = OS.Unix.File.close(file); | ||||
|   is(result, 0, "test_open_close: close succeeded"); | ||||
| 
 | ||||
|   file = OS.Unix.File.open("/i do not exist", OS.Constants.libc.O_RDONLY); | ||||
|   is(file, -1, "test_open_close: opening of non-existing file failed"); | ||||
|   is( | ||||
|     ctypes.errno, | ||||
|     OS.Constants.libc.ENOENT, | ||||
|     "test_open_close: error is ENOENT" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function test_create_file() { | ||||
|   info("Starting test_create_file"); | ||||
|   let file = OS.Unix.File.open( | ||||
|     "test.tmp", | ||||
|     OS.Constants.libc.O_RDWR | | ||||
|       OS.Constants.libc.O_CREAT | | ||||
|       OS.Constants.libc.O_TRUNC, | ||||
|     ctypes.int(OS.Constants.libc.S_IRWXU) | ||||
|   ); | ||||
|   isnot(file, -1, "test_create_file: file created"); | ||||
|   OS.Unix.File.close(file); | ||||
| } | ||||
| 
 | ||||
| function test_access() { | ||||
|   info("Starting test_access"); | ||||
|   let file = OS.Unix.File.open( | ||||
|     "test1.tmp", | ||||
|     OS.Constants.libc.O_RDWR | | ||||
|       OS.Constants.libc.O_CREAT | | ||||
|       OS.Constants.libc.O_TRUNC, | ||||
|     ctypes.int(OS.Constants.libc.S_IRWXU) | ||||
|   ); | ||||
|   let result = OS.Unix.File.access( | ||||
|     "test1.tmp", | ||||
|     OS.Constants.libc.R_OK | | ||||
|       OS.Constants.libc.W_OK | | ||||
|       OS.Constants.libc.X_OK | | ||||
|       OS.Constants.libc.F_OK | ||||
|   ); | ||||
|   is(result, 0, "first call to access() succeeded"); | ||||
|   OS.Unix.File.close(file); | ||||
| 
 | ||||
|   file = OS.Unix.File.open( | ||||
|     "test1.tmp", | ||||
|     OS.Constants.libc.O_WRONLY | | ||||
|       OS.Constants.libc.O_CREAT | | ||||
|       OS.Constants.libc.O_TRUNC, | ||||
|     ctypes.int(OS.Constants.libc.S_IWUSR) | ||||
|   ); | ||||
| 
 | ||||
|   info("test_access: preparing second call to access()"); | ||||
|   result = OS.Unix.File.access( | ||||
|     "test2.tmp", | ||||
|     OS.Constants.libc.R_OK | | ||||
|       OS.Constants.libc.W_OK | | ||||
|       OS.Constants.libc.X_OK | | ||||
|       OS.Constants.libc.F_OK | ||||
|   ); | ||||
|   is(result, -1, "test_access: second call to access() failed as expected"); | ||||
|   is(ctypes.errno, OS.Constants.libc.ENOENT, "This is the correct error"); | ||||
|   OS.Unix.File.close(file); | ||||
| } | ||||
| 
 | ||||
| function test_getcwd() { | ||||
|   let array = new (ctypes.ArrayType(ctypes.char, 32768))(); | ||||
|   let path = OS.Unix.File.getcwd(array, array.length); | ||||
|   if (ctypes.char.ptr(path).isNull()) { | ||||
|     ok(false, "test_get_cwd: getcwd returned null, errno: " + ctypes.errno); | ||||
|   } | ||||
|   let path2; | ||||
|   if (OS.Unix.File.get_current_dir_name) { | ||||
|     path2 = OS.Unix.File.get_current_dir_name(); | ||||
|   } else { | ||||
|     path2 = OS.Unix.File.getwd_auto(null); | ||||
|   } | ||||
|   if (ctypes.char.ptr(path2).isNull()) { | ||||
|     ok( | ||||
|       false, | ||||
|       "test_get_cwd: getwd_auto/get_current_dir_name returned null, errno: " + | ||||
|         ctypes.errno | ||||
|     ); | ||||
|   } | ||||
|   is( | ||||
|     path.readString(), | ||||
|     path2.readString(), | ||||
|     "test_get_cwd: getcwd and getwd return the same path" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function test_read_write() { | ||||
|   let output_name = "osfile_copy.tmp"; | ||||
|   // Copy file
 | ||||
|   let input = OS.Unix.File.open( | ||||
|     "chrome/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js", | ||||
|     OS.Constants.libc.O_RDONLY | ||||
|   ); | ||||
|   isnot(input, -1, "test_read_write: input file opened"); | ||||
|   let output = OS.Unix.File.open( | ||||
|     "osfile_copy.tmp", | ||||
|     OS.Constants.libc.O_RDWR | | ||||
|       OS.Constants.libc.O_CREAT | | ||||
|       OS.Constants.libc.O_TRUNC, | ||||
|     ctypes.int(OS.Constants.libc.S_IRWXU) | ||||
|   ); | ||||
|   isnot(output, -1, "test_read_write: output file opened"); | ||||
| 
 | ||||
|   let array = new (ctypes.ArrayType(ctypes.char, 4096))(); | ||||
|   let bytes = -1; | ||||
|   let total = 0; | ||||
|   while (true) { | ||||
|     bytes = OS.Unix.File.read(input, array, 4096); | ||||
|     ok(bytes != undefined, "test_read_write: bytes is defined"); | ||||
|     isnot(bytes, -1, "test_read_write: no read error"); | ||||
|     let write_from = 0; | ||||
|     if (bytes == 0) { | ||||
|       break; | ||||
|     } | ||||
|     while (bytes > 0) { | ||||
|       array.addressOfElement(write_from); | ||||
|       // Note: |write| launches an exception in case of error
 | ||||
|       let written = OS.Unix.File.write(output, array, bytes); | ||||
|       isnot(written, -1, "test_read_write: no write error"); | ||||
|       write_from += written; | ||||
|       bytes -= written; | ||||
|     } | ||||
|     total += write_from; | ||||
|   } | ||||
|   info("test_read_write: copy complete " + total); | ||||
| 
 | ||||
|   // Compare files
 | ||||
|   let result; | ||||
|   info("SEEK_SET: " + OS.Constants.libc.SEEK_SET); | ||||
|   info("Input: " + input + "(" + input.toSource() + ")"); | ||||
|   info("Output: " + output + "(" + output.toSource() + ")"); | ||||
|   result = OS.Unix.File.lseek(input, 0, OS.Constants.libc.SEEK_SET); | ||||
|   info("Result of lseek: " + result); | ||||
|   isnot(result, -1, "test_read_write: input seek succeeded " + ctypes.errno); | ||||
|   result = OS.Unix.File.lseek(output, 0, OS.Constants.libc.SEEK_SET); | ||||
|   isnot(result, -1, "test_read_write: output seek succeeded " + ctypes.errno); | ||||
| 
 | ||||
|   let array2 = new (ctypes.ArrayType(ctypes.char, 4096))(); | ||||
|   let bytes2 = -1; | ||||
|   let pos = 0; | ||||
|   while (true) { | ||||
|     bytes = OS.Unix.File.read(input, array, 4096); | ||||
|     isnot(bytes, -1, "test_read_write: input read succeeded"); | ||||
|     bytes2 = OS.Unix.File.read(output, array2, 4096); | ||||
|     isnot(bytes, -1, "test_read_write: output read succeeded"); | ||||
|     is( | ||||
|       bytes > 0, | ||||
|       bytes2 > 0, | ||||
|       "Both files contain data or neither does " + bytes + ", " + bytes2 | ||||
|     ); | ||||
|     if (bytes == 0) { | ||||
|       break; | ||||
|     } | ||||
|     if (bytes != bytes2) { | ||||
|       // This would be surprising, but theoretically possible with a
 | ||||
|       // remote file system, I believe.
 | ||||
|       bytes = Math.min(bytes, bytes2); | ||||
|       pos += bytes; | ||||
|       result = OS.Unix.File.lseek(input, pos, OS.Constants.libc.SEEK_SET); | ||||
|       isnot(result, -1, "test_read_write: input seek succeeded"); | ||||
|       result = OS.Unix.File.lseek(output, pos, OS.Constants.libc.SEEK_SET); | ||||
|       isnot(result, -1, "test_read_write: output seek succeeded"); | ||||
|     } else { | ||||
|       pos += bytes; | ||||
|     } | ||||
|     for (let i = 0; i < bytes; ++i) { | ||||
|       if (array[i] != array2[i]) { | ||||
|         ok( | ||||
|           false, | ||||
|           "Files do not match at position " + | ||||
|             i + | ||||
|             " (" + | ||||
|             array[i] + | ||||
|             "/" + | ||||
|             array2[i] + | ||||
|             ")" | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   info("test_read_write test complete"); | ||||
|   result = OS.Unix.File.close(input); | ||||
|   isnot(result, -1, "test_read_write: input close succeeded"); | ||||
|   result = OS.Unix.File.close(output); | ||||
|   isnot(result, -1, "test_read_write: output close succeeded"); | ||||
|   result = OS.Unix.File.unlink(output_name); | ||||
|   isnot(result, -1, "test_read_write: input remove succeeded"); | ||||
|   info("test_read_write cleanup complete"); | ||||
| } | ||||
| 
 | ||||
| function test_passing_undefined() { | ||||
|   info( | ||||
|     "Testing that an exception gets thrown when an FFI function is passed undefined" | ||||
|   ); | ||||
|   let exceptionRaised = false; | ||||
| 
 | ||||
|   try { | ||||
|     OS.Unix.File.open( | ||||
|       undefined, | ||||
|       OS.Constants.libc.O_RDWR | | ||||
|         OS.Constants.libc.O_CREAT | | ||||
|         OS.Constants.libc.O_TRUNC, | ||||
|       ctypes.int(OS.Constants.libc.S_IRWXU) | ||||
|     ); | ||||
|   } catch (e) { | ||||
|     if (e instanceof TypeError && e.message.indexOf("open") > -1) { | ||||
|       exceptionRaised = true; | ||||
|     } else { | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   ok(exceptionRaised, "test_passing_undefined: exception gets thrown"); | ||||
| } | ||||
|  | @ -1,310 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| /* eslint-env mozilla/chrome-worker, node */ | ||||
| 
 | ||||
| /* import-globals-from /testing/mochitest/tests/SimpleTest/WorkerSimpleTest.js */ | ||||
| importScripts("chrome://mochikit/content/tests/SimpleTest/WorkerSimpleTest.js"); | ||||
| 
 | ||||
| self.onmessage = function(msg) { | ||||
|   self.onmessage = function(msg) { | ||||
|     log("ignored message " + JSON.stringify(msg.data)); | ||||
|   }; | ||||
| 
 | ||||
|   test_init(); | ||||
|   test_GetCurrentDirectory(); | ||||
|   test_OpenClose(); | ||||
|   test_CreateFile(); | ||||
|   test_ReadWrite(); | ||||
|   test_passing_undefined(); | ||||
|   finish(); | ||||
| }; | ||||
| 
 | ||||
| function test_init() { | ||||
|   info("Starting test_init"); | ||||
|   /* import-globals-from /toolkit/components/osfile/osfile.jsm */ | ||||
|   importScripts("resource://gre/modules/osfile.jsm"); | ||||
| } | ||||
| 
 | ||||
| function test_OpenClose() { | ||||
|   info("Starting test_OpenClose"); | ||||
|   is( | ||||
|     typeof OS.Win.File.CreateFile, | ||||
|     "function", | ||||
|     "OS.Win.File.CreateFile is a function" | ||||
|   ); | ||||
|   is( | ||||
|     OS.Win.File.CloseHandle(OS.Constants.Win.INVALID_HANDLE_VALUE), | ||||
|     true, | ||||
|     "CloseHandle returns true given the invalid handle" | ||||
|   ); | ||||
|   is( | ||||
|     OS.Win.File.FindClose(OS.Constants.Win.INVALID_HANDLE_VALUE), | ||||
|     true, | ||||
|     "FindClose returns true given the invalid handle" | ||||
|   ); | ||||
|   isnot(OS.Constants.Win.GENERIC_READ, undefined, "GENERIC_READ exists"); | ||||
|   isnot(OS.Constants.Win.FILE_SHARE_READ, undefined, "FILE_SHARE_READ exists"); | ||||
|   isnot( | ||||
|     OS.Constants.Win.FILE_ATTRIBUTE_NORMAL, | ||||
|     undefined, | ||||
|     "FILE_ATTRIBUTE_NORMAL exists" | ||||
|   ); | ||||
|   let file = OS.Win.File.CreateFile( | ||||
|     "chrome\\toolkit\\components\\osfile\\tests\\mochi\\worker_test_osfile_win.js", | ||||
|     OS.Constants.Win.GENERIC_READ, | ||||
|     0, | ||||
|     null, | ||||
|     OS.Constants.Win.OPEN_EXISTING, | ||||
|     0, | ||||
|     null | ||||
|   ); | ||||
|   info("test_OpenClose: Passed open"); | ||||
|   isnot( | ||||
|     file, | ||||
|     OS.Constants.Win.INVALID_HANDLE_VALUE, | ||||
|     "test_OpenClose: file opened" | ||||
|   ); | ||||
|   let result = OS.Win.File.CloseHandle(file); | ||||
|   isnot(result, 0, "test_OpenClose: close succeeded"); | ||||
| 
 | ||||
|   file = OS.Win.File.CreateFile( | ||||
|     "\\I do not exist", | ||||
|     OS.Constants.Win.GENERIC_READ, | ||||
|     OS.Constants.Win.FILE_SHARE_READ, | ||||
|     null, | ||||
|     OS.Constants.Win.OPEN_EXISTING, | ||||
|     OS.Constants.Win.FILE_ATTRIBUTE_NORMAL, | ||||
|     null | ||||
|   ); | ||||
|   is( | ||||
|     file, | ||||
|     OS.Constants.Win.INVALID_HANDLE_VALUE, | ||||
|     "test_OpenClose: cannot open non-existing file" | ||||
|   ); | ||||
|   is( | ||||
|     ctypes.winLastError, | ||||
|     OS.Constants.Win.ERROR_FILE_NOT_FOUND, | ||||
|     "test_OpenClose: error is ERROR_FILE_NOT_FOUND" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function test_CreateFile() { | ||||
|   info("Starting test_CreateFile"); | ||||
|   let file = OS.Win.File.CreateFile( | ||||
|     "test.tmp", | ||||
|     OS.Constants.Win.GENERIC_READ | OS.Constants.Win.GENERIC_WRITE, | ||||
|     OS.Constants.Win.FILE_SHARE_READ | OS.Constants.FILE_SHARE_WRITE, | ||||
|     null, | ||||
|     OS.Constants.Win.CREATE_ALWAYS, | ||||
|     OS.Constants.Win.FILE_ATTRIBUTE_NORMAL, | ||||
|     null | ||||
|   ); | ||||
|   isnot( | ||||
|     file, | ||||
|     OS.Constants.Win.INVALID_HANDLE_VALUE, | ||||
|     "test_CreateFile: opening succeeded" | ||||
|   ); | ||||
|   let result = OS.Win.File.CloseHandle(file); | ||||
|   isnot(result, 0, "test_CreateFile: close succeeded"); | ||||
| } | ||||
| 
 | ||||
| function test_GetCurrentDirectory() { | ||||
|   let array = new (ctypes.ArrayType(ctypes.char16_t, 4096))(); | ||||
|   let result = OS.Win.File.GetCurrentDirectory(4096, array); | ||||
|   ok(result < array.length, "test_GetCurrentDirectory: length sufficient"); | ||||
|   ok(result > 0, "test_GetCurrentDirectory: length != 0"); | ||||
| } | ||||
| 
 | ||||
| function test_ReadWrite() { | ||||
|   info("Starting test_ReadWrite"); | ||||
|   let output_name = "osfile_copy.tmp"; | ||||
|   // Copy file
 | ||||
|   let input = OS.Win.File.CreateFile( | ||||
|     "chrome\\toolkit\\components\\osfile\\tests\\mochi\\worker_test_osfile_win.js", | ||||
|     OS.Constants.Win.GENERIC_READ, | ||||
|     0, | ||||
|     null, | ||||
|     OS.Constants.Win.OPEN_EXISTING, | ||||
|     0, | ||||
|     null | ||||
|   ); | ||||
|   isnot( | ||||
|     input, | ||||
|     OS.Constants.Win.INVALID_HANDLE_VALUE, | ||||
|     "test_ReadWrite: input file opened" | ||||
|   ); | ||||
|   let output = OS.Win.File.CreateFile( | ||||
|     "osfile_copy.tmp", | ||||
|     OS.Constants.Win.GENERIC_READ | OS.Constants.Win.GENERIC_WRITE, | ||||
|     0, | ||||
|     null, | ||||
|     OS.Constants.Win.CREATE_ALWAYS, | ||||
|     OS.Constants.Win.FILE_ATTRIBUTE_NORMAL, | ||||
|     null | ||||
|   ); | ||||
|   isnot( | ||||
|     output, | ||||
|     OS.Constants.Win.INVALID_HANDLE_VALUE, | ||||
|     "test_ReadWrite: output file opened" | ||||
|   ); | ||||
|   let array = new (ctypes.ArrayType(ctypes.char, 4096))(); | ||||
|   let bytes_read = new ctypes.uint32_t(0); | ||||
|   let bytes_read_ptr = bytes_read.address(); | ||||
|   log("We have a pointer for bytes read: " + bytes_read_ptr); | ||||
|   let bytes_written = new ctypes.uint32_t(0); | ||||
|   let bytes_written_ptr = bytes_written.address(); | ||||
|   log("We have a pointer for bytes written: " + bytes_written_ptr); | ||||
|   log("test_ReadWrite: buffer and pointers ready"); | ||||
|   let result; | ||||
|   while (true) { | ||||
|     log("test_ReadWrite: reading"); | ||||
|     result = OS.Win.File.ReadFile(input, array, 4096, bytes_read_ptr, null); | ||||
|     isnot(result, 0, "test_ReadWrite: read success"); | ||||
|     let write_from = 0; | ||||
|     let bytes_left = bytes_read; | ||||
|     log("test_ReadWrite: read chunk complete " + bytes_left.value); | ||||
|     if (bytes_left.value == 0) { | ||||
|       break; | ||||
|     } | ||||
|     while (bytes_left.value > 0) { | ||||
|       log("test_ReadWrite: writing " + bytes_left.value); | ||||
|       array.addressOfElement(write_from); | ||||
|       // Note: |WriteFile| launches an exception in case of error
 | ||||
|       result = OS.Win.File.WriteFile( | ||||
|         output, | ||||
|         array, | ||||
|         bytes_left, | ||||
|         bytes_written_ptr, | ||||
|         null | ||||
|       ); | ||||
|       isnot(result, 0, "test_ReadWrite: write success"); | ||||
|       write_from += bytes_written; | ||||
|       bytes_left -= bytes_written; | ||||
|     } | ||||
|   } | ||||
|   info("test_ReadWrite: copy complete"); | ||||
| 
 | ||||
|   // Compare files
 | ||||
|   result = OS.Win.File.SetFilePointer( | ||||
|     input, | ||||
|     0, | ||||
|     null, | ||||
|     OS.Constants.Win.FILE_BEGIN | ||||
|   ); | ||||
|   isnot( | ||||
|     result, | ||||
|     OS.Constants.Win.INVALID_SET_FILE_POINTER, | ||||
|     "test_ReadWrite: input reset" | ||||
|   ); | ||||
| 
 | ||||
|   result = OS.Win.File.SetFilePointer( | ||||
|     output, | ||||
|     0, | ||||
|     null, | ||||
|     OS.Constants.Win.FILE_BEGIN | ||||
|   ); | ||||
|   isnot( | ||||
|     result, | ||||
|     OS.Constants.Win.INVALID_SET_FILE_POINTER, | ||||
|     "test_ReadWrite: output reset" | ||||
|   ); | ||||
| 
 | ||||
|   let array2 = new (ctypes.ArrayType(ctypes.char, 4096))(); | ||||
|   let bytes_read2 = new ctypes.uint32_t(0); | ||||
|   let bytes_read2_ptr = bytes_read2.address(); | ||||
|   let pos = 0; | ||||
|   while (true) { | ||||
|     result = OS.Win.File.ReadFile(input, array, 4096, bytes_read_ptr, null); | ||||
|     isnot(result, 0, "test_ReadWrite: input read succeeded"); | ||||
| 
 | ||||
|     result = OS.Win.File.ReadFile(output, array2, 4096, bytes_read2_ptr, null); | ||||
|     isnot(result, 0, "test_ReadWrite: output read succeeded"); | ||||
| 
 | ||||
|     is( | ||||
|       bytes_read.value > 0, | ||||
|       bytes_read2.value > 0, | ||||
|       "Both files contain data or neither does " + | ||||
|         bytes_read.value + | ||||
|         ", " + | ||||
|         bytes_read2.value | ||||
|     ); | ||||
|     if (bytes_read.value == 0) { | ||||
|       break; | ||||
|     } | ||||
|     let bytes; | ||||
|     if (bytes_read.value != bytes_read2.value) { | ||||
|       // This would be surprising, but theoretically possible with a
 | ||||
|       // remote file system, I believe.
 | ||||
|       bytes = Math.min(bytes_read.value, bytes_read2.value); | ||||
|       pos += bytes; | ||||
|       result = OS.Win.File.SetFilePointer( | ||||
|         input, | ||||
|         pos, | ||||
|         null, | ||||
|         OS.Constants.Win.FILE_BEGIN | ||||
|       ); | ||||
|       isnot(result, 0, "test_ReadWrite: input seek succeeded"); | ||||
| 
 | ||||
|       result = OS.Win.File.SetFilePointer( | ||||
|         output, | ||||
|         pos, | ||||
|         null, | ||||
|         OS.Constants.Win.FILE_BEGIN | ||||
|       ); | ||||
|       isnot(result, 0, "test_ReadWrite: output seek succeeded"); | ||||
|     } else { | ||||
|       bytes = bytes_read.value; | ||||
|       pos += bytes; | ||||
|     } | ||||
|     for (let i = 0; i < bytes; ++i) { | ||||
|       if (array[i] != array2[i]) { | ||||
|         ok( | ||||
|           false, | ||||
|           "Files do not match at position " + | ||||
|             i + | ||||
|             " (" + | ||||
|             array[i] + | ||||
|             "/" + | ||||
|             array2[i] + | ||||
|             ")" | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   info("test_ReadWrite test complete"); | ||||
|   result = OS.Win.File.CloseHandle(input); | ||||
|   isnot(result, 0, "test_ReadWrite: inpout close succeeded"); | ||||
|   result = OS.Win.File.CloseHandle(output); | ||||
|   isnot(result, 0, "test_ReadWrite: outpout close succeeded"); | ||||
|   result = OS.Win.File.DeleteFile(output_name); | ||||
|   isnot(result, 0, "test_ReadWrite: output remove succeeded"); | ||||
|   info("test_ReadWrite cleanup complete"); | ||||
| } | ||||
| 
 | ||||
| function test_passing_undefined() { | ||||
|   info( | ||||
|     "Testing that an exception gets thrown when an FFI function is passed undefined" | ||||
|   ); | ||||
|   let exceptionRaised = false; | ||||
| 
 | ||||
|   try { | ||||
|     OS.Win.File.CreateFile( | ||||
|       undefined, | ||||
|       OS.Constants.Win.GENERIC_READ, | ||||
|       0, | ||||
|       null, | ||||
|       OS.Constants.Win.OPEN_EXISTING, | ||||
|       0, | ||||
|       null | ||||
|     ); | ||||
|   } catch (e) { | ||||
|     if (e instanceof TypeError && e.message.indexOf("CreateFile") > -1) { | ||||
|       exceptionRaised = true; | ||||
|     } else { | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   ok(exceptionRaised, "test_passing_undefined: exception gets thrown"); | ||||
| } | ||||
|  | @ -1,7 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| module.exports = { | ||||
|   rules: { | ||||
|     "no-shadow": "off", | ||||
|   }, | ||||
| }; | ||||
|  | @ -1,109 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var { XPCOMUtils } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/XPCOMUtils.sys.mjs" | ||||
| ); | ||||
| 
 | ||||
| // Bug 1014484 can only be reproduced by loading OS.File first from the
 | ||||
| // CommonJS loader, so we do not want OS.File to be loaded eagerly for
 | ||||
| // all the tests in this directory.
 | ||||
| ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); | ||||
| ChromeUtils.defineESModuleGetters(this, { | ||||
|   FileUtils: "resource://gre/modules/FileUtils.sys.mjs", | ||||
| }); | ||||
| ChromeUtils.defineModuleGetter( | ||||
|   this, | ||||
|   "NetUtil", | ||||
|   "resource://gre/modules/NetUtil.jsm" | ||||
| ); | ||||
| 
 | ||||
| Services.prefs.setBoolPref("toolkit.osfile.log", true); | ||||
| 
 | ||||
| /** | ||||
|  * As add_task, but execute the test both with native operations and | ||||
|  * without. | ||||
|  */ | ||||
| function add_test_pair(generator) { | ||||
|   add_task(async function() { | ||||
|     info("Executing test " + generator.name + " with native operations"); | ||||
|     Services.prefs.setBoolPref("toolkit.osfile.native", true); | ||||
|     return generator(); | ||||
|   }); | ||||
|   add_task(async function() { | ||||
|     info("Executing test " + generator.name + " without native operations"); | ||||
|     Services.prefs.setBoolPref("toolkit.osfile.native", false); | ||||
|     return generator(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Fetch asynchronously the contents of a file using xpcom. | ||||
|  * | ||||
|  * Used for comparing xpcom-based results to os.file-based results. | ||||
|  * | ||||
|  * @param {string} path The _absolute_ path to the file. | ||||
|  * @return {promise} | ||||
|  * @resolves {string} The contents of the file. | ||||
|  */ | ||||
| function reference_fetch_file(path, test) { | ||||
|   info("Fetching file " + path); | ||||
|   return new Promise((resolve, reject) => { | ||||
|     let file = new FileUtils.File(path); | ||||
|     NetUtil.asyncFetch( | ||||
|       { | ||||
|         uri: NetUtil.newURI(file), | ||||
|         loadUsingSystemPrincipal: true, | ||||
|       }, | ||||
|       function(stream, status) { | ||||
|         if (!Components.isSuccessCode(status)) { | ||||
|           reject(status); | ||||
|           return; | ||||
|         } | ||||
|         let result, reject; | ||||
|         try { | ||||
|           result = NetUtil.readInputStreamToString(stream, stream.available()); | ||||
|         } catch (x) { | ||||
|           reject = x; | ||||
|         } | ||||
|         stream.close(); | ||||
|         if (reject) { | ||||
|           reject(reject); | ||||
|         } else { | ||||
|           resolve(result); | ||||
|         } | ||||
|       } | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Compare asynchronously the contents two files using xpcom. | ||||
|  * | ||||
|  * Used for comparing xpcom-based results to os.file-based results. | ||||
|  * | ||||
|  * @param {string} a The _absolute_ path to the first file. | ||||
|  * @param {string} b The _absolute_ path to the second file. | ||||
|  * | ||||
|  * @resolves {null} | ||||
|  */ | ||||
| function reference_compare_files(a, b, test) { | ||||
|   return (async function() { | ||||
|     info("Comparing files " + a + " and " + b); | ||||
|     let a_contents = await reference_fetch_file(a, test); | ||||
|     let b_contents = await reference_fetch_file(b, test); | ||||
|     Assert.equal(a_contents, b_contents); | ||||
|   })(); | ||||
| } | ||||
| 
 | ||||
| async function removeTestFile(filePath, ignoreNoSuchFile = true) { | ||||
|   try { | ||||
|     await OS.File.remove(filePath); | ||||
|   } catch (ex) { | ||||
|     if (!ignoreNoSuchFile || !ex.becauseNoSuchFile) { | ||||
|       do_throw(ex); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1,106 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| function run_test() { | ||||
|   do_test_pending(); | ||||
|   run_next_test(); | ||||
| } | ||||
| 
 | ||||
| add_task(async function test_compress_lz4() { | ||||
|   let path = OS.Path.join(OS.Constants.Path.tmpDir, "compression.lz"); | ||||
|   let length = 1024; | ||||
|   let array = new Uint8Array(length); | ||||
|   for (let i = 0; i < array.byteLength; ++i) { | ||||
|     array[i] = i; | ||||
|   } | ||||
|   let arrayAsString = Array.prototype.join.call(array); | ||||
| 
 | ||||
|   info("Writing data with lz4 compression"); | ||||
|   let bytes = await OS.File.writeAtomic(path, array, { compression: "lz4" }); | ||||
|   info("Compressed " + length + " bytes into " + bytes); | ||||
| 
 | ||||
|   info("Reading back with lz4 decompression"); | ||||
|   let decompressed = await OS.File.read(path, { compression: "lz4" }); | ||||
|   info("Decompressed into " + decompressed.byteLength + " bytes"); | ||||
|   Assert.equal(arrayAsString, Array.prototype.join.call(decompressed)); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function test_uncompressed() { | ||||
|   info("Writing data without compression"); | ||||
|   let path = OS.Path.join(OS.Constants.Path.tmpDir, "no_compression.tmp"); | ||||
|   let array = new Uint8Array(1024); | ||||
|   for (let i = 0; i < array.byteLength; ++i) { | ||||
|     array[i] = i; | ||||
|   } | ||||
|   await OS.File.writeAtomic(path, array); // No compression
 | ||||
| 
 | ||||
|   let exn; | ||||
|   // Force decompression, reading should fail
 | ||||
|   try { | ||||
|     await OS.File.read(path, { compression: "lz4" }); | ||||
|   } catch (ex) { | ||||
|     exn = ex; | ||||
|   } | ||||
|   Assert.ok(!!exn); | ||||
|   // Check the exception message (and that it contains the file name)
 | ||||
|   Assert.ok( | ||||
|     exn.message.includes(`Invalid header (no magic number) - Data: ${path}`) | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function test_no_header() { | ||||
|   let path = OS.Path.join(OS.Constants.Path.tmpDir, "no_header.tmp"); | ||||
|   let array = new Uint8Array(8).fill(0, 0); // Small array with no header
 | ||||
| 
 | ||||
|   info("Writing data with no header"); | ||||
| 
 | ||||
|   await OS.File.writeAtomic(path, array); // No compression
 | ||||
|   let exn; | ||||
|   // Force decompression, reading should fail
 | ||||
|   try { | ||||
|     await OS.File.read(path, { compression: "lz4" }); | ||||
|   } catch (ex) { | ||||
|     exn = ex; | ||||
|   } | ||||
|   Assert.ok(!!exn); | ||||
|   // Check the exception message (and that it contains the file name)
 | ||||
|   Assert.ok( | ||||
|     exn.message.includes(`Buffer is too short (no header) - Data: ${path}`) | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function test_invalid_content() { | ||||
|   let path = OS.Path.join(OS.Constants.Path.tmpDir, "invalid_content.tmp"); | ||||
|   let arr1 = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]); | ||||
|   let arr2 = new Uint8Array(248).fill(1, 0); | ||||
| 
 | ||||
|   let array = new Uint8Array(arr1.length + arr2.length); | ||||
|   array.set(arr1); | ||||
|   array.set(arr2, arr1.length); | ||||
| 
 | ||||
|   info("Writing invalid data (with a valid header and only ones after that)"); | ||||
| 
 | ||||
|   await OS.File.writeAtomic(path, array); // No compression
 | ||||
|   let exn; | ||||
|   // Force decompression, reading should fail
 | ||||
|   try { | ||||
|     await OS.File.read(path, { compression: "lz4" }); | ||||
|   } catch (ex) { | ||||
|     exn = ex; | ||||
|   } | ||||
|   Assert.ok(!!exn); | ||||
|   // Check the exception message (and that it contains the file name)
 | ||||
|   Assert.ok( | ||||
|     exn.message.includes( | ||||
|       `Invalid content: Decompression stopped at 0 - Data: ${path}` | ||||
|     ) | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| add_task(function() { | ||||
|   do_test_finished(); | ||||
| }); | ||||
|  | @ -1,20 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| // Test that OS.Constants is defined correctly.
 | ||||
| add_task(async function check_definition() { | ||||
|   Assert.ok(OS.Constants != null); | ||||
|   Assert.ok(!!OS.Constants.Win || !!OS.Constants.libc); | ||||
|   Assert.ok(OS.Constants.Path != null); | ||||
|   Assert.ok(OS.Constants.Sys != null); | ||||
|   // check system name
 | ||||
|   Assert.equal(Services.appinfo.OS, OS.Constants.Sys.Name); | ||||
| 
 | ||||
|   // check if using DEBUG build
 | ||||
|   if (Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2).isDebugBuild) { | ||||
|     Assert.ok(OS.Constants.Sys.DEBUG); | ||||
|   } else { | ||||
|     Assert.ok(typeof OS.Constants.Sys.DEBUG == "undefined"); | ||||
|   } | ||||
| }); | ||||
|  | @ -1,127 +0,0 @@ | |||
| var { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| /** | ||||
|  * Test optional duration reporting that can be used for telemetry. | ||||
|  */ | ||||
| add_task(async function duration() { | ||||
|   const availableDurations = [ | ||||
|     "outSerializationDuration", | ||||
|     "outExecutionDuration", | ||||
|   ]; | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", true); | ||||
|   // Options structure passed to a OS.File copy method.
 | ||||
|   let copyOptions = { | ||||
|     // These fields should be overwritten with the actual duration
 | ||||
|     // measurements.
 | ||||
|     outSerializationDuration: null, | ||||
|     outExecutionDuration: null, | ||||
|   }; | ||||
|   let currentDir = await OS.File.getCurrentDirectory(); | ||||
|   let pathSource = OS.Path.join(currentDir, "test_duration.js"); | ||||
|   let copyFile = pathSource + ".bak"; | ||||
|   function testOptions(options, name, durations = availableDurations) { | ||||
|     for (let duration of durations) { | ||||
|       info(`Checking ${duration} for operation: ${name}`); | ||||
|       info(`${name}: Gathered method duration time: ${options[duration]} ms`); | ||||
|       // Making sure that duration was updated.
 | ||||
|       Assert.equal(typeof options[duration], "number"); | ||||
|       Assert.ok(options[duration] >= 0); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function testOptionIncrements( | ||||
|     options, | ||||
|     name, | ||||
|     backupDuration, | ||||
|     durations = availableDurations | ||||
|   ) { | ||||
|     for (let duration of durations) { | ||||
|       info(`Checking ${duration} increment for operation: ${name}`); | ||||
|       info(`${name}: Gathered method duration time: ${options[duration]} ms`); | ||||
|       info(`${name}: Previous duration: ${backupDuration[duration]} ms`); | ||||
|       // Making sure that duration was incremented.
 | ||||
|       Assert.ok(options[duration] >= backupDuration[duration]); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Testing duration of OS.File.copy.
 | ||||
|   await OS.File.copy(pathSource, copyFile, copyOptions); | ||||
|   testOptions(copyOptions, "OS.File.copy"); | ||||
|   await OS.File.remove(copyFile); | ||||
| 
 | ||||
|   // Trying an operation where options are cloned.
 | ||||
|   let pathDest = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "osfile async test read writeAtomic.tmp" | ||||
|   ); | ||||
|   let tmpPath = pathDest + ".tmp"; | ||||
|   let readOptions = { | ||||
|     // We do not check for |outSerializationDuration| since |Scheduler.post|
 | ||||
|     // may not be called whenever |read| is called.
 | ||||
|     outExecutionDuration: null, | ||||
|   }; | ||||
|   let contents = await OS.File.read(pathSource, undefined, readOptions); | ||||
|   testOptions(readOptions, "OS.File.read", ["outExecutionDuration"]); | ||||
|   // Options structure passed to a OS.File writeAtomic method.
 | ||||
|   let writeAtomicOptions = { | ||||
|     // This field should be first initialized with the actual
 | ||||
|     // duration measurement then progressively incremented.
 | ||||
|     outExecutionDuration: null, | ||||
|     tmpPath, | ||||
|   }; | ||||
|   // Note that |contents| cannot be reused after this call since it is detached.
 | ||||
|   await OS.File.writeAtomic(pathDest, contents, writeAtomicOptions); | ||||
|   testOptions(writeAtomicOptions, "OS.File.writeAtomic", [ | ||||
|     "outExecutionDuration", | ||||
|   ]); | ||||
|   await OS.File.remove(pathDest); | ||||
| 
 | ||||
|   info( | ||||
|     `Ensuring that we can use ${availableDurations.join( | ||||
|       ", " | ||||
|     )} to accumulate durations` | ||||
|   ); | ||||
| 
 | ||||
|   let ARBITRARY_BASE_DURATION = 5; | ||||
|   copyOptions = { | ||||
|     // This field should now be incremented with the actual duration
 | ||||
|     // measurement.
 | ||||
|     outSerializationDuration: ARBITRARY_BASE_DURATION, | ||||
|     outExecutionDuration: ARBITRARY_BASE_DURATION, | ||||
|   }; | ||||
| 
 | ||||
|   // We need to copy the object, since having a reference would make this pointless.
 | ||||
|   let backupDuration = Object.assign({}, copyOptions); | ||||
| 
 | ||||
|   // Testing duration of OS.File.copy.
 | ||||
|   await OS.File.copy(pathSource, copyFile, copyOptions); | ||||
|   testOptionIncrements(copyOptions, "copy", backupDuration); | ||||
| 
 | ||||
|   backupDuration = Object.assign({}, copyOptions); | ||||
|   await OS.File.remove(copyFile, copyOptions); | ||||
|   testOptionIncrements(copyOptions, "remove", backupDuration); | ||||
| 
 | ||||
|   // Trying an operation where options are cloned.
 | ||||
|   // Options structure passed to a OS.File writeAtomic method.
 | ||||
|   writeAtomicOptions = { | ||||
|     // We do not check for |outSerializationDuration| since |Scheduler.post|
 | ||||
|     // may not be called whenever |writeAtomic| is called.
 | ||||
|     outExecutionDuration: ARBITRARY_BASE_DURATION, | ||||
|   }; | ||||
|   writeAtomicOptions.tmpPath = tmpPath; | ||||
|   backupDuration = Object.assign({}, writeAtomicOptions); | ||||
|   contents = await OS.File.read(pathSource, undefined, readOptions); | ||||
|   await OS.File.writeAtomic(pathDest, contents, writeAtomicOptions); | ||||
|   testOptionIncrements( | ||||
|     writeAtomicOptions, | ||||
|     "writeAtomicOptions", | ||||
|     backupDuration, | ||||
|     ["outExecutionDuration"] | ||||
|   ); | ||||
|   OS.File.remove(pathDest); | ||||
| 
 | ||||
|   // Testing an operation that doesn't take arguments at all
 | ||||
|   let file = await OS.File.open(pathSource); | ||||
|   await file.stat(); | ||||
|   await file.close(); | ||||
| }); | ||||
|  | @ -1,108 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| /** | ||||
|  * Test that functions throw the appropriate exceptions. | ||||
|  */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var EXISTING_FILE = do_get_file("xpcshell.ini").path; | ||||
| 
 | ||||
| // Tests on |open|
 | ||||
| 
 | ||||
| add_test_pair(async function test_typeerror() { | ||||
|   let exn; | ||||
|   try { | ||||
|     let fd = await OS.File.open("/tmp", { no_such_key: 1 }); | ||||
|     info("Fd: " + fd); | ||||
|   } catch (ex) { | ||||
|     exn = ex; | ||||
|   } | ||||
|   info("Exception: " + exn); | ||||
|   Assert.ok(exn.constructor.name == "TypeError"); | ||||
| }); | ||||
| 
 | ||||
| // Tests on |read|
 | ||||
| 
 | ||||
| add_test_pair(async function test_bad_encoding() { | ||||
|   info("Testing with a wrong encoding"); | ||||
|   try { | ||||
|     await OS.File.read(EXISTING_FILE, { encoding: "baby-speak-encoded" }); | ||||
|     do_throw("Should have thrown with an ex.becauseInvalidArgument"); | ||||
|   } catch (ex) { | ||||
|     if (ex.becauseInvalidArgument) { | ||||
|       info("Wrong encoding caused the correct exception"); | ||||
|     } else { | ||||
|       throw ex; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   try { | ||||
|     await OS.File.read(EXISTING_FILE, { encoding: 4 }); | ||||
|     do_throw("Should have thrown a TypeError"); | ||||
|   } catch (ex) { | ||||
|     if (ex.constructor.name == "TypeError") { | ||||
|       // Note that TypeError doesn't carry across compartments
 | ||||
|       info("Non-string encoding caused the correct exception"); | ||||
|     } else { | ||||
|       throw ex; | ||||
|     } | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| add_test_pair(async function test_bad_compression() { | ||||
|   info("Testing with a non-existing compression"); | ||||
|   try { | ||||
|     await OS.File.read(EXISTING_FILE, { compression: "mmmh-crunchy" }); | ||||
|     do_throw("Should have thrown with an ex.becauseInvalidArgument"); | ||||
|   } catch (ex) { | ||||
|     if (ex.becauseInvalidArgument) { | ||||
|       info("Wrong encoding caused the correct exception"); | ||||
|     } else { | ||||
|       throw ex; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   info("Testing with a bad type for option compression"); | ||||
|   try { | ||||
|     await OS.File.read(EXISTING_FILE, { compression: 5 }); | ||||
|     do_throw("Should have thrown a TypeError"); | ||||
|   } catch (ex) { | ||||
|     if (ex.constructor.name == "TypeError") { | ||||
|       // Note that TypeError doesn't carry across compartments
 | ||||
|       info("Non-string encoding caused the correct exception"); | ||||
|     } else { | ||||
|       throw ex; | ||||
|     } | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| add_test_pair(async function test_bad_bytes() { | ||||
|   info("Testing with a bad type for option bytes"); | ||||
|   try { | ||||
|     await OS.File.read(EXISTING_FILE, { bytes: "five" }); | ||||
|     do_throw("Should have thrown a TypeError"); | ||||
|   } catch (ex) { | ||||
|     if (ex.constructor.name == "TypeError") { | ||||
|       // Note that TypeError doesn't carry across compartments
 | ||||
|       info("Non-number bytes caused the correct exception"); | ||||
|     } else { | ||||
|       throw ex; | ||||
|     } | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| add_test_pair(async function read_non_existent() { | ||||
|   info("Testing with a non-existent file"); | ||||
|   try { | ||||
|     await OS.File.read("I/do/not/exist"); | ||||
|     do_throw("Should have thrown with an ex.becauseNoSuchFile"); | ||||
|   } catch (ex) { | ||||
|     if (ex.becauseNoSuchFile) { | ||||
|       info("Correct exceptions"); | ||||
|     } else { | ||||
|       throw ex; | ||||
|     } | ||||
|   } | ||||
| }); | ||||
|  | @ -1,119 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| function run_test() { | ||||
|   const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
|   const { FileUtils } = ChromeUtils.importESModule( | ||||
|     "resource://gre/modules/FileUtils.sys.mjs" | ||||
|   ); | ||||
| 
 | ||||
|   let isWindows = "@mozilla.org/windows-registry-key;1" in Cc; | ||||
| 
 | ||||
|   // Test cases for filePathToURI
 | ||||
|   let paths = isWindows | ||||
|     ? [ | ||||
|         "C:\\", | ||||
|         "C:\\test", | ||||
|         "C:\\test\\", | ||||
|         "C:\\test%2f", | ||||
|         "C:\\test\\test\\test", | ||||
|         "C:\\test;+%", | ||||
|         "C:\\test?action=index\\", | ||||
|         "C:\\test test", | ||||
|         "\\\\C:\\a\\b\\c", | ||||
|         "\\\\Server\\a\\b\\c", | ||||
| 
 | ||||
|         // note that per http://support.microsoft.com/kb/177506 (under more info),
 | ||||
|         // the following characters are allowed on Windows:
 | ||||
|         "C:\\char^", | ||||
|         "C:\\char&", | ||||
|         "C:\\char'", | ||||
|         "C:\\char@", | ||||
|         "C:\\char{", | ||||
|         "C:\\char}", | ||||
|         "C:\\char[", | ||||
|         "C:\\char]", | ||||
|         "C:\\char,", | ||||
|         "C:\\char$", | ||||
|         "C:\\char=", | ||||
|         "C:\\char!", | ||||
|         "C:\\char-", | ||||
|         "C:\\char#", | ||||
|         "C:\\char(", | ||||
|         "C:\\char)", | ||||
|         "C:\\char%", | ||||
|         "C:\\char.", | ||||
|         "C:\\char+", | ||||
|         "C:\\char~", | ||||
|         "C:\\char_", | ||||
|       ] | ||||
|     : [ | ||||
|         "/", | ||||
|         "/test", | ||||
|         "/test/", | ||||
|         "/test%2f", | ||||
|         "/test/test/test", | ||||
|         "/test;+%", | ||||
|         "/test?action=index/", | ||||
|         "/test test", | ||||
|         "/punctuation/;,/?:@&=+$-_.!~*'()[]\"#", | ||||
|         "/CasePreserving", | ||||
|       ]; | ||||
| 
 | ||||
|   // some additional URIs to test, beyond those generated from paths
 | ||||
|   let uris = isWindows | ||||
|     ? [ | ||||
|         "file:///C:/test/", | ||||
|         "file://localhost/C:/test", | ||||
|         "file:///c:/test/test.txt", | ||||
|         // 'file:///C:/foo%2f', // trailing, encoded slash
 | ||||
|         "file:///C:/%3f%3F", | ||||
|         "file:///C:/%3b%3B", | ||||
|         "file:///C:/%3c%3C", // not one of the special-cased ? or ;
 | ||||
|         "file:///C:/%78", // 'x', not usually uri encoded
 | ||||
|         "file:///C:/test#frag", // a fragment identifier
 | ||||
|         "file:///C:/test?action=index", // an actual query component
 | ||||
|       ] | ||||
|     : [ | ||||
|         "file:///test/", | ||||
|         "file://localhost/test", | ||||
|         "file:///test/test.txt", | ||||
|         "file:///foo%2f", // trailing, encoded slash
 | ||||
|         "file:///%3f%3F", | ||||
|         "file:///%3b%3B", | ||||
|         "file:///%3c%3C", // not one of the special-cased ? or ;
 | ||||
|         "file:///%78", // 'x', not usually uri encoded
 | ||||
|         "file:///test#frag", // a fragment identifier
 | ||||
|         "file:///test?action=index", // an actual query component
 | ||||
|       ]; | ||||
| 
 | ||||
|   for (let path of paths) { | ||||
|     // convert that to a uri using FileUtils and Services, which toFileURI is trying to model
 | ||||
|     let file = FileUtils.File(path); | ||||
|     let uri = Services.io.newFileURI(file).spec; | ||||
|     Assert.equal(uri, OS.Path.toFileURI(path)); | ||||
| 
 | ||||
|     // keep the resulting URI to try the reverse, except for "C:\" for which the
 | ||||
|     // behavior of nsIFileURL and OS.File is inconsistent
 | ||||
|     if (path != "C:\\") { | ||||
|       uris.push(uri); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   for (let uri of uris) { | ||||
|     // convert URIs to paths with nsIFileURI, which fromFileURI is trying to model
 | ||||
|     let path = Services.io.newURI(uri).QueryInterface(Ci.nsIFileURL).file.path; | ||||
|     Assert.equal(path, OS.Path.fromFileURI(uri)); | ||||
|   } | ||||
| 
 | ||||
|   // check that non-file URLs aren't allowed
 | ||||
|   let thrown = false; | ||||
|   try { | ||||
|     OS.Path.fromFileURI("http://test.com"); | ||||
|   } catch (e) { | ||||
|     Assert.equal(e.message, "fromFileURI expects a file URI"); | ||||
|     thrown = true; | ||||
|   } | ||||
|   Assert.ok(thrown); | ||||
| } | ||||
|  | @ -1,73 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| /** | ||||
|  * Tests logging by passing OS.Shared.LOG both an object with its own | ||||
|  * toString method, and one with the default. | ||||
|  */ | ||||
| function run_test() { | ||||
|   do_test_pending(); | ||||
|   let messageCount = 0; | ||||
| 
 | ||||
|   info("Test starting"); | ||||
| 
 | ||||
|   // Create a console listener.
 | ||||
|   let consoleListener = { | ||||
|     observe(aMessage) { | ||||
|       // Ignore unexpected messages.
 | ||||
|       if (!(aMessage instanceof Ci.nsIConsoleMessage)) { | ||||
|         return; | ||||
|       } | ||||
|       // This is required, as printing to the |Services.console|
 | ||||
|       // while in the observe function causes an exception.
 | ||||
|       executeSoon(function() { | ||||
|         info("Observing message " + aMessage.message); | ||||
|         if (!aMessage.message.includes("TEST OS")) { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         ++messageCount; | ||||
|         if (messageCount == 1) { | ||||
|           Assert.equal(aMessage.message, 'TEST OS {"name":"test"}\n'); | ||||
|         } | ||||
|         if (messageCount == 2) { | ||||
|           Assert.equal(aMessage.message, "TEST OS name is test\n"); | ||||
|           toggleConsoleListener(false); | ||||
|           do_test_finished(); | ||||
|         } | ||||
|       }); | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   // Set/Unset the console listener.
 | ||||
|   function toggleConsoleListener(pref) { | ||||
|     info("Setting console listener: " + pref); | ||||
|     Services.prefs.setBoolPref("toolkit.osfile.log", pref); | ||||
|     Services.prefs.setBoolPref("toolkit.osfile.log.redirect", pref); | ||||
|     Services.console[pref ? "registerListener" : "unregisterListener"]( | ||||
|       consoleListener | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   toggleConsoleListener(true); | ||||
| 
 | ||||
|   let objectDefault = { name: "test" }; | ||||
|   let CustomToString = function() { | ||||
|     this.name = "test"; | ||||
|   }; | ||||
|   CustomToString.prototype.toString = function() { | ||||
|     return "name is " + this.name; | ||||
|   }; | ||||
|   let objectCustom = new CustomToString(); | ||||
| 
 | ||||
|   info(OS.Shared.LOG.toSource()); | ||||
| 
 | ||||
|   info("Logging 1"); | ||||
|   OS.Shared.LOG(objectDefault); | ||||
| 
 | ||||
|   info("Logging 2"); | ||||
|   OS.Shared.LOG(objectCustom); | ||||
|   // Once both messages are observed OS.Shared.DEBUG, and OS.Shared.TEST
 | ||||
|   // are reset to false.
 | ||||
| } | ||||
|  | @ -1,137 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| var Path = OS.Path; | ||||
| var profileDir; | ||||
| 
 | ||||
| registerCleanupFunction(function() { | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", false); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Test OS.File.makeDir | ||||
|  */ | ||||
| 
 | ||||
| add_task(function init() { | ||||
|   // Set up profile. We create the directory in the profile, because the profile
 | ||||
|   // is removed after every test run.
 | ||||
|   do_get_profile(); | ||||
|   profileDir = OS.Constants.Path.profileDir; | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", true); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Basic use | ||||
|  */ | ||||
| 
 | ||||
| add_task(async function test_basic() { | ||||
|   let dir = Path.join(profileDir, "directory"); | ||||
| 
 | ||||
|   // Sanity checking for the test
 | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| 
 | ||||
|   // Make a directory
 | ||||
|   await OS.File.makeDir(dir); | ||||
| 
 | ||||
|   // check if the directory exists
 | ||||
|   await OS.File.stat(dir); | ||||
| 
 | ||||
|   // Make a directory that already exists, this should succeed
 | ||||
|   await OS.File.makeDir(dir); | ||||
| 
 | ||||
|   // Make a directory with ignoreExisting
 | ||||
|   await OS.File.makeDir(dir, { ignoreExisting: true }); | ||||
| 
 | ||||
|   // Make a directory with ignoreExisting false
 | ||||
|   let exception = null; | ||||
|   try { | ||||
|     await OS.File.makeDir(dir, { ignoreExisting: false }); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
| 
 | ||||
|   Assert.ok(!!exception); | ||||
|   Assert.ok(exception instanceof OS.File.Error); | ||||
|   Assert.ok(exception.becauseExists); | ||||
| }); | ||||
| 
 | ||||
| // Make a root directory that already exists
 | ||||
| add_task(async function test_root() { | ||||
|   if (OS.Constants.Win) { | ||||
|     await OS.File.makeDir("C:"); | ||||
|     await OS.File.makeDir("C:\\"); | ||||
|   } else { | ||||
|     await OS.File.makeDir("/"); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Creating subdirectories | ||||
|  */ | ||||
| add_task(async function test_option_from() { | ||||
|   let dir = Path.join(profileDir, "a", "b", "c"); | ||||
| 
 | ||||
|   // Sanity checking for the test
 | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| 
 | ||||
|   // Make a directory
 | ||||
|   await OS.File.makeDir(dir, { from: profileDir }); | ||||
| 
 | ||||
|   // check if the directory exists
 | ||||
|   await OS.File.stat(dir); | ||||
| 
 | ||||
|   // Make a directory that already exists, this should succeed
 | ||||
|   await OS.File.makeDir(dir); | ||||
| 
 | ||||
|   // Make a directory with ignoreExisting
 | ||||
|   await OS.File.makeDir(dir, { ignoreExisting: true }); | ||||
| 
 | ||||
|   // Make a directory with ignoreExisting false
 | ||||
|   let exception = null; | ||||
|   try { | ||||
|     await OS.File.makeDir(dir, { ignoreExisting: false }); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
| 
 | ||||
|   Assert.ok(!!exception); | ||||
|   Assert.ok(exception instanceof OS.File.Error); | ||||
|   Assert.ok(exception.becauseExists); | ||||
| 
 | ||||
|   // Make a directory without |from| and fail
 | ||||
|   let dir2 = Path.join(profileDir, "g", "h", "i"); | ||||
|   exception = null; | ||||
|   try { | ||||
|     await OS.File.makeDir(dir2); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
| 
 | ||||
|   Assert.ok(!!exception); | ||||
|   Assert.ok(exception instanceof OS.File.Error); | ||||
|   Assert.ok(exception.becauseNoSuchFile); | ||||
| 
 | ||||
|   // Test edge cases on paths
 | ||||
| 
 | ||||
|   let dir3 = Path.join(profileDir, "d", "", "e", "f"); | ||||
|   Assert.equal(false, await OS.File.exists(dir3)); | ||||
|   await OS.File.makeDir(dir3, { from: profileDir }); | ||||
|   Assert.ok(await OS.File.exists(dir3)); | ||||
| 
 | ||||
|   let dir4; | ||||
|   if (OS.Constants.Win) { | ||||
|     // Test that we can create a directory recursively even
 | ||||
|     // if we have too many "\\".
 | ||||
|     dir4 = profileDir + "\\\\g"; | ||||
|   } else { | ||||
|     dir4 = profileDir + "////g"; | ||||
|   } | ||||
|   Assert.equal(false, await OS.File.exists(dir4)); | ||||
|   await OS.File.makeDir(dir4, { from: profileDir }); | ||||
|   Assert.ok(await OS.File.exists(dir4)); | ||||
| }); | ||||
|  | @ -1,75 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| /** | ||||
|  * Test OS.File.open for reading: | ||||
|  * - with an existing file (should succeed); | ||||
|  * - with a non-existing file (should fail); | ||||
|  * - with inconsistent arguments (should fail). | ||||
|  */ | ||||
| add_task(async function() { | ||||
|   // Attempt to open a file that does not exist, ensure that it yields the
 | ||||
|   // appropriate error.
 | ||||
|   try { | ||||
|     await OS.File.open(OS.Path.join(".", "This file does not exist")); | ||||
|     Assert.ok(false, "File opening 1 succeeded (it should fail)"); | ||||
|   } catch (err) { | ||||
|     if (err instanceof OS.File.Error && err.becauseNoSuchFile) { | ||||
|       info("File opening 1 failed " + err); | ||||
|     } else { | ||||
|       throw err; | ||||
|     } | ||||
|   } | ||||
|   // Attempt to open a file with the wrong args, so that it fails before
 | ||||
|   // serialization, ensure that it yields the appropriate error.
 | ||||
|   info("Attempting to open a file with wrong arguments"); | ||||
|   try { | ||||
|     let fd = await OS.File.open(1, 2, 3); | ||||
|     Assert.ok(false, "File opening 2 succeeded (it should fail)" + fd); | ||||
|   } catch (err) { | ||||
|     info("File opening 2 failed " + err); | ||||
|     Assert.equal( | ||||
|       false, | ||||
|       err instanceof OS.File.Error, | ||||
|       "File opening 2 returned something that is not a file error" | ||||
|     ); | ||||
|     Assert.ok( | ||||
|       err.constructor.name == "TypeError", | ||||
|       "File opening 2 returned a TypeError" | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   // Attempt to open a file correctly
 | ||||
|   info("Attempting to open a file correctly"); | ||||
|   let openedFile = await OS.File.open( | ||||
|     OS.Path.join(do_get_cwd().path, "test_open.js") | ||||
|   ); | ||||
|   info("File opened correctly"); | ||||
| 
 | ||||
|   info("Attempting to close a file correctly"); | ||||
|   await openedFile.close(); | ||||
| 
 | ||||
|   info("Attempting to close a file again"); | ||||
|   await openedFile.close(); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Test the error thrown by OS.File.open when attempting to open a directory | ||||
|  * that does not exist. | ||||
|  */ | ||||
| add_task(async function test_error_attributes() { | ||||
|   let dir = OS.Path.join(do_get_profile().path, "test_osfileErrorAttrs"); | ||||
|   let fpath = OS.Path.join(dir, "test_error_attributes.txt"); | ||||
| 
 | ||||
|   try { | ||||
|     await OS.File.open(fpath, { truncate: true }, {}); | ||||
|     Assert.ok(false, "Opening path suceeded (it should fail) " + fpath); | ||||
|   } catch (err) { | ||||
|     Assert.ok(err instanceof OS.File.Error); | ||||
|     Assert.ok(err.becauseNoSuchFile); | ||||
|   } | ||||
| }); | ||||
|  | @ -1,13 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| /** | ||||
|  * A trivial test ensuring that we can call osfile from xpcshell. | ||||
|  * (see bug 808161) | ||||
|  */ | ||||
| 
 | ||||
| function run_test() { | ||||
|   do_test_pending(); | ||||
|   OS.File.getCurrentDirectory().then(do_test_finished, do_test_finished); | ||||
| } | ||||
|  | @ -1,105 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| info("starting tests"); | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| /** | ||||
|  * A test to check that the |append| mode flag is correctly implemented. | ||||
|  * (see bug 925865) | ||||
|  */ | ||||
| 
 | ||||
| function setup_mode(mode) { | ||||
|   // Complete mode.
 | ||||
|   let realMode = { | ||||
|     read: true, | ||||
|     write: true, | ||||
|   }; | ||||
|   for (let k in mode) { | ||||
|     realMode[k] = mode[k]; | ||||
|   } | ||||
|   return realMode; | ||||
| } | ||||
| 
 | ||||
| // Test append mode.
 | ||||
| async function test_append(mode) { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_append.tmp" | ||||
|   ); | ||||
| 
 | ||||
|   // Clear any left-over files from previous runs.
 | ||||
|   await removeTestFile(path); | ||||
| 
 | ||||
|   try { | ||||
|     mode = setup_mode(mode); | ||||
|     mode.append = true; | ||||
|     if (mode.trunc) { | ||||
|       // Pre-fill file with some data to see if |trunc| actually works.
 | ||||
|       await OS.File.writeAtomic(path, new Uint8Array(500)); | ||||
|     } | ||||
|     let file = await OS.File.open(path, mode); | ||||
|     try { | ||||
|       await file.write(new Uint8Array(1000)); | ||||
|       await file.setPosition(0, OS.File.POS_START); | ||||
|       await file.read(100); | ||||
|       // Should be at offset 100, length 1000 now.
 | ||||
|       await file.write(new Uint8Array(100)); | ||||
|       // Should be at offset 1100, length 1100 now.
 | ||||
|       let stat = await file.stat(); | ||||
|       Assert.equal(1100, stat.size); | ||||
|     } finally { | ||||
|       await file.close(); | ||||
|     } | ||||
|   } catch (ex) { | ||||
|     await removeTestFile(path); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Test no-append mode.
 | ||||
| async function test_no_append(mode) { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_noappend.tmp" | ||||
|   ); | ||||
| 
 | ||||
|   // Clear any left-over files from previous runs.
 | ||||
|   await removeTestFile(path); | ||||
| 
 | ||||
|   try { | ||||
|     mode = setup_mode(mode); | ||||
|     mode.append = false; | ||||
|     if (mode.trunc) { | ||||
|       // Pre-fill file with some data to see if |trunc| actually works.
 | ||||
|       await OS.File.writeAtomic(path, new Uint8Array(500)); | ||||
|     } | ||||
|     let file = await OS.File.open(path, mode); | ||||
|     try { | ||||
|       await file.write(new Uint8Array(1000)); | ||||
|       await file.setPosition(0, OS.File.POS_START); | ||||
|       await file.read(100); | ||||
|       // Should be at offset 100, length 1000 now.
 | ||||
|       await file.write(new Uint8Array(100)); | ||||
|       // Should be at offset 200, length 1000 now.
 | ||||
|       let stat = await file.stat(); | ||||
|       Assert.equal(1000, stat.size); | ||||
|     } finally { | ||||
|       await file.close(); | ||||
|     } | ||||
|   } finally { | ||||
|     await removeTestFile(path); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| var test_flags = [{}, { create: true }, { trunc: true }]; | ||||
| function run_test() { | ||||
|   do_test_pending(); | ||||
| 
 | ||||
|   for (let t of test_flags) { | ||||
|     add_task(test_append.bind(null, t)); | ||||
|     add_task(test_no_append.bind(null, t)); | ||||
|   } | ||||
|   add_task(do_test_finished); | ||||
| 
 | ||||
|   run_next_test(); | ||||
| } | ||||
|  | @ -1,40 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| function run_test() { | ||||
|   do_test_pending(); | ||||
|   run_next_test(); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test to ensure that {bytes:} in options to |write| is correctly | ||||
|  * preserved. | ||||
|  */ | ||||
| add_task(async function test_bytes() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_bytes.tmp" | ||||
|   ); | ||||
|   let file = await OS.File.open(path, { trunc: true, read: true, write: true }); | ||||
|   try { | ||||
|     try { | ||||
|       // 1. Test write, by supplying {bytes:} options smaller than the actual
 | ||||
|       // buffer.
 | ||||
|       await file.write(new Uint8Array(2048), { bytes: 1024 }); | ||||
|       Assert.equal((await file.stat()).size, 1024); | ||||
| 
 | ||||
|       // 2. Test that passing nullish values for |options| still works.
 | ||||
|       await file.setPosition(0, OS.File.POS_END); | ||||
|       await file.write(new Uint8Array(1024), null); | ||||
|       await file.write(new Uint8Array(1024), undefined); | ||||
|       Assert.equal((await file.stat()).size, 3072); | ||||
|     } finally { | ||||
|       await file.close(); | ||||
|     } | ||||
|   } finally { | ||||
|     await OS.File.remove(path); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| add_task(do_test_finished); | ||||
|  | @ -1,109 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| const { FileUtils } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/FileUtils.sys.mjs" | ||||
| ); | ||||
| const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); | ||||
| 
 | ||||
| function run_test() { | ||||
|   do_test_pending(); | ||||
|   run_next_test(); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * A file that we know exists and that can be used for reading. | ||||
|  */ | ||||
| var EXISTING_FILE = "test_osfile_async_copy.js"; | ||||
| 
 | ||||
| /** | ||||
|  * Fetch asynchronously the contents of a file using xpcom. | ||||
|  * | ||||
|  * Used for comparing xpcom-based results to os.file-based results. | ||||
|  * | ||||
|  * @param {string} path The _absolute_ path to the file. | ||||
|  * @return {promise} | ||||
|  * @resolves {string} The contents of the file. | ||||
|  */ | ||||
| var reference_fetch_file = function reference_fetch_file(path) { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     let file = new FileUtils.File(path); | ||||
|     NetUtil.asyncFetch( | ||||
|       { | ||||
|         uri: NetUtil.newURI(file), | ||||
|         loadUsingSystemPrincipal: true, | ||||
|       }, | ||||
|       function(stream, status) { | ||||
|         if (!Components.isSuccessCode(status)) { | ||||
|           reject(status); | ||||
|           return; | ||||
|         } | ||||
|         let result, reject; | ||||
|         try { | ||||
|           result = NetUtil.readInputStreamToString(stream, stream.available()); | ||||
|         } catch (x) { | ||||
|           reject = x; | ||||
|         } | ||||
|         stream.close(); | ||||
|         if (reject) { | ||||
|           reject(reject); | ||||
|         } else { | ||||
|           resolve(result); | ||||
|         } | ||||
|       } | ||||
|     ); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Compare asynchronously the contents two files using xpcom. | ||||
|  * | ||||
|  * Used for comparing xpcom-based results to os.file-based results. | ||||
|  * | ||||
|  * @param {string} a The _absolute_ path to the first file. | ||||
|  * @param {string} b The _absolute_ path to the second file. | ||||
|  * | ||||
|  * @resolves {null} | ||||
|  */ | ||||
| var reference_compare_files = async function reference_compare_files(a, b) { | ||||
|   let a_contents = await reference_fetch_file(a); | ||||
|   let b_contents = await reference_fetch_file(b); | ||||
|   // Not using do_check_eq to avoid dumping the whole file to the log.
 | ||||
|   // It is OK to === compare here, as both variables contain a string.
 | ||||
|   Assert.ok(a_contents === b_contents); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Test to ensure that OS.File.copy works. | ||||
|  */ | ||||
| async function test_copymove(options = {}) { | ||||
|   let source = OS.Path.join(await OS.File.getCurrentDirectory(), EXISTING_FILE); | ||||
|   let dest = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_copy_dest.tmp" | ||||
|   ); | ||||
|   let dest2 = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_copy_dest2.tmp" | ||||
|   ); | ||||
|   try { | ||||
|     // 1. Test copy.
 | ||||
|     await OS.File.copy(source, dest, options); | ||||
|     await reference_compare_files(source, dest); | ||||
|     // 2. Test subsequent move.
 | ||||
|     await OS.File.move(dest, dest2); | ||||
|     await reference_compare_files(source, dest2); | ||||
|     // 3. Check that the moved file was really moved.
 | ||||
|     Assert.equal(await OS.File.exists(dest), false); | ||||
|   } finally { | ||||
|     await removeTestFile(dest); | ||||
|     await removeTestFile(dest2); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Regular copy test.
 | ||||
| add_task(test_copymove); | ||||
| // Userland copy test.
 | ||||
| add_task(test_copymove.bind(null, { unixUserland: true })); | ||||
| 
 | ||||
| add_task(do_test_finished); | ||||
|  | @ -1,31 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| function run_test() { | ||||
|   do_test_pending(); | ||||
|   run_next_test(); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test to ensure that |File.prototype.flush| is available in the async API. | ||||
|  */ | ||||
| 
 | ||||
| add_task(async function test_flush() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_flush.tmp" | ||||
|   ); | ||||
|   let file = await OS.File.open(path, { trunc: true, write: true }); | ||||
|   try { | ||||
|     try { | ||||
|       await file.flush(); | ||||
|     } finally { | ||||
|       await file.close(); | ||||
|     } | ||||
|   } finally { | ||||
|     await OS.File.remove(path); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| add_task(do_test_finished); | ||||
|  | @ -1,137 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| const { ctypes } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/ctypes.sys.mjs" | ||||
| ); | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| /** | ||||
|  * A test to check that .getPosition/.setPosition work with large files. | ||||
|  * (see bug 952997) | ||||
|  */ | ||||
| 
 | ||||
| // Test setPosition/getPosition.
 | ||||
| async function test_setPosition(forward, current, backward) { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_largefiles.tmp" | ||||
|   ); | ||||
| 
 | ||||
|   // Clear any left-over files from previous runs.
 | ||||
|   await removeTestFile(path); | ||||
| 
 | ||||
|   try { | ||||
|     let file = await OS.File.open(path, { write: true, append: false }); | ||||
|     try { | ||||
|       let pos = 0; | ||||
| 
 | ||||
|       // 1. seek forward from start
 | ||||
|       info("Moving forward: " + forward); | ||||
|       await file.setPosition(forward, OS.File.POS_START); | ||||
|       pos += forward; | ||||
|       Assert.equal(await file.getPosition(), pos); | ||||
| 
 | ||||
|       // 2. seek forward from current position
 | ||||
|       info("Moving current: " + current); | ||||
|       await file.setPosition(current, OS.File.POS_CURRENT); | ||||
|       pos += current; | ||||
|       Assert.equal(await file.getPosition(), pos); | ||||
| 
 | ||||
|       // 3. seek backward from current position
 | ||||
|       info("Moving current backward: " + backward); | ||||
|       await file.setPosition(-backward, OS.File.POS_CURRENT); | ||||
|       pos -= backward; | ||||
|       Assert.equal(await file.getPosition(), pos); | ||||
|     } finally { | ||||
|       await file.setPosition(0, OS.File.POS_START); | ||||
|       await file.close(); | ||||
|     } | ||||
|   } catch (ex) { | ||||
|     await removeTestFile(path); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Test setPosition/getPosition expected failures.
 | ||||
| async function test_setPosition_failures() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_largefiles.tmp" | ||||
|   ); | ||||
| 
 | ||||
|   // Clear any left-over files from previous runs.
 | ||||
|   await removeTestFile(path); | ||||
| 
 | ||||
|   try { | ||||
|     let file = await OS.File.open(path, { write: true, append: false }); | ||||
|     try { | ||||
|       // 1. Use an invalid position value
 | ||||
|       try { | ||||
|         await file.setPosition(0.5, OS.File.POS_START); | ||||
|         do_throw("Shouldn't have succeeded"); | ||||
|       } catch (ex) { | ||||
|         Assert.ok(ex.toString().includes("can't pass")); | ||||
|       } | ||||
|       // Since setPosition should have bailed, it shouldn't have moved the
 | ||||
|       // file pointer at all.
 | ||||
|       Assert.equal(await file.getPosition(), 0); | ||||
| 
 | ||||
|       // 2. Use an invalid position value
 | ||||
|       try { | ||||
|         await file.setPosition(0xffffffff + 0.5, OS.File.POS_START); | ||||
|         do_throw("Shouldn't have succeeded"); | ||||
|       } catch (ex) { | ||||
|         Assert.ok(ex.toString().includes("can't pass")); | ||||
|       } | ||||
|       // Since setPosition should have bailed, it shouldn't have moved the
 | ||||
|       // file pointer at all.
 | ||||
|       Assert.equal(await file.getPosition(), 0); | ||||
| 
 | ||||
|       // 3. Use a position that cannot be represented as a double
 | ||||
|       try { | ||||
|         // Not all numbers after 9007199254740992 can be represented as a
 | ||||
|         // double. E.g. in js 9007199254740992 + 1 == 9007199254740992
 | ||||
|         await file.setPosition(9007199254740992, OS.File.POS_START); | ||||
|         await file.setPosition(1, OS.File.POS_CURRENT); | ||||
|         do_throw("Shouldn't have succeeded"); | ||||
|       } catch (ex) { | ||||
|         info(ex.toString()); | ||||
|         Assert.ok(!!ex); | ||||
|       } | ||||
|     } finally { | ||||
|       await file.setPosition(0, OS.File.POS_START); | ||||
|       await file.close(); | ||||
|       await removeTestFile(path); | ||||
|     } | ||||
|   } catch (ex) { | ||||
|     do_throw(ex); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function run_test() { | ||||
|   // First verify stuff works for small values.
 | ||||
|   add_task(test_setPosition.bind(null, 0, 100, 50)); | ||||
|   add_task(test_setPosition.bind(null, 1000, 100, 50)); | ||||
|   add_task(test_setPosition.bind(null, 1000, -100, -50)); | ||||
| 
 | ||||
|   if (OS.Constants.Win || ctypes.off_t.size >= 8) { | ||||
|     // Now verify stuff still works for large values.
 | ||||
|     // 1. Multiple small seeks, which add up to > MAXINT32
 | ||||
|     add_task(test_setPosition.bind(null, 0x7fffffff, 0x7fffffff, 0)); | ||||
|     // 2. Plain large seek, that should end up at 0 again.
 | ||||
|     // 0xffffffff also happens to be the INVALID_SET_FILE_POINTER value on
 | ||||
|     // Windows, so this also tests the error handling
 | ||||
|     add_task(test_setPosition.bind(null, 0, 0xffffffff, 0xffffffff)); | ||||
|     // 3. Multiple large seeks that should end up > MAXINT32.
 | ||||
|     add_task(test_setPosition.bind(null, 0xffffffff, 0xffffffff, 0xffffffff)); | ||||
|     // 5. Multiple large seeks with negative offsets.
 | ||||
|     add_task(test_setPosition.bind(null, 0xffffffff, -0x7fffffff, 0x7fffffff)); | ||||
| 
 | ||||
|     // 6. Check failures
 | ||||
|     add_task(test_setPosition_failures); | ||||
|   } | ||||
| 
 | ||||
|   run_next_test(); | ||||
| } | ||||
|  | @ -1,214 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| /* eslint-disable no-lone-blocks */ | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| /** | ||||
|  * A test to ensure that OS.File.setDates and OS.File.prototype.setDates are | ||||
|  * working correctly. | ||||
|  * (see bug 924916) | ||||
|  */ | ||||
| 
 | ||||
| // Non-prototypical tests, operating on path names.
 | ||||
| add_task(async function test_nonproto() { | ||||
|   // First, create a file we can mess with.
 | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_setDates_nonproto.tmp" | ||||
|   ); | ||||
|   await OS.File.writeAtomic(path, new Uint8Array(1)); | ||||
| 
 | ||||
|   try { | ||||
|     // 1. Try to set some well known dates.
 | ||||
|     // We choose multiples of 2000ms, because the time stamp resolution of
 | ||||
|     // the underlying OS might not support something more precise.
 | ||||
|     const accDate = 2000; | ||||
|     const modDate = 4000; | ||||
|     { | ||||
|       await OS.File.setDates(path, accDate, modDate); | ||||
|       let stat = await OS.File.stat(path); | ||||
|       Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|       Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|     } | ||||
| 
 | ||||
|     // 2.1 Try to omit modificationDate (which should then default to
 | ||||
|     // |Date.now()|, expect for resolution differences).
 | ||||
|     { | ||||
|       await OS.File.setDates(path, accDate); | ||||
|       let stat = await OS.File.stat(path); | ||||
|       Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|       Assert.notEqual(modDate, stat.lastModificationDate.getTime()); | ||||
|     } | ||||
| 
 | ||||
|     // 2.2 Try to omit accessDate as well (which should then default to
 | ||||
|     // |Date.now()|, expect for resolution differences).
 | ||||
|     { | ||||
|       await OS.File.setDates(path); | ||||
|       let stat = await OS.File.stat(path); | ||||
|       Assert.notEqual(accDate, stat.lastAccessDate.getTime()); | ||||
|       Assert.notEqual(modDate, stat.lastModificationDate.getTime()); | ||||
|     } | ||||
| 
 | ||||
|     // 3. Repeat 1., but with Date objects this time
 | ||||
|     { | ||||
|       await OS.File.setDates(path, new Date(accDate), new Date(modDate)); | ||||
|       let stat = await OS.File.stat(path); | ||||
|       Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|       Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|     } | ||||
| 
 | ||||
|     // 4. Check that invalid params will cause an exception/rejection.
 | ||||
|     { | ||||
|       for (let p of ["invalid", new Uint8Array(1), NaN]) { | ||||
|         try { | ||||
|           await OS.File.setDates(path, p, modDate); | ||||
|           do_throw("Invalid access date should have thrown for: " + p); | ||||
|         } catch (ex) { | ||||
|           let stat = await OS.File.stat(path); | ||||
|           Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|           Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|         } | ||||
|         try { | ||||
|           await OS.File.setDates(path, accDate, p); | ||||
|           do_throw("Invalid modification date should have thrown for: " + p); | ||||
|         } catch (ex) { | ||||
|           let stat = await OS.File.stat(path); | ||||
|           Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|           Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|         } | ||||
|         try { | ||||
|           await OS.File.setDates(path, p, p); | ||||
|           do_throw("Invalid dates should have thrown for: " + p); | ||||
|         } catch (ex) { | ||||
|           let stat = await OS.File.stat(path); | ||||
|           Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|           Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } finally { | ||||
|     // Remove the temp file again
 | ||||
|     await OS.File.remove(path); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| // Prototypical tests, operating on |File| handles.
 | ||||
| add_task(async function test_proto() { | ||||
|   if (OS.Constants.Sys.Name == "Android") { | ||||
|     info("File.prototype.setDates is not implemented for Android"); | ||||
|     Assert.equal(OS.File.prototype.setDates, undefined); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // First, create a file we can mess with.
 | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_setDates_proto.tmp" | ||||
|   ); | ||||
|   await OS.File.writeAtomic(path, new Uint8Array(1)); | ||||
| 
 | ||||
|   try { | ||||
|     let fd = await OS.File.open(path, { write: true }); | ||||
| 
 | ||||
|     try { | ||||
|       // 1. Try to set some well known dates.
 | ||||
|       // We choose multiples of 2000ms, because the time stamp resolution of
 | ||||
|       // the underlying OS might not support something more precise.
 | ||||
|       const accDate = 2000; | ||||
|       const modDate = 4000; | ||||
|       { | ||||
|         await fd.setDates(accDate, modDate); | ||||
|         let stat = await fd.stat(); | ||||
|         Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|         Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|       } | ||||
| 
 | ||||
|       // 2.1 Try to omit modificationDate (which should then default to
 | ||||
|       // |Date.now()|, expect for resolution differences).
 | ||||
|       { | ||||
|         await fd.setDates(accDate); | ||||
|         let stat = await fd.stat(); | ||||
|         Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|         Assert.notEqual(modDate, stat.lastModificationDate.getTime()); | ||||
|       } | ||||
| 
 | ||||
|       // 2.2 Try to omit accessDate as well (which should then default to
 | ||||
|       // |Date.now()|, expect for resolution differences).
 | ||||
|       { | ||||
|         await fd.setDates(); | ||||
|         let stat = await fd.stat(); | ||||
|         Assert.notEqual(accDate, stat.lastAccessDate.getTime()); | ||||
|         Assert.notEqual(modDate, stat.lastModificationDate.getTime()); | ||||
|       } | ||||
| 
 | ||||
|       // 3. Repeat 1., but with Date objects this time
 | ||||
|       { | ||||
|         await fd.setDates(new Date(accDate), new Date(modDate)); | ||||
|         let stat = await fd.stat(); | ||||
|         Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|         Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|       } | ||||
| 
 | ||||
|       // 4. Check that invalid params will cause an exception/rejection.
 | ||||
|       { | ||||
|         for (let p of ["invalid", new Uint8Array(1), NaN]) { | ||||
|           try { | ||||
|             await fd.setDates(p, modDate); | ||||
|             do_throw("Invalid access date should have thrown for: " + p); | ||||
|           } catch (ex) { | ||||
|             let stat = await fd.stat(); | ||||
|             Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|             Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|           } | ||||
|           try { | ||||
|             await fd.setDates(accDate, p); | ||||
|             do_throw("Invalid modification date should have thrown for: " + p); | ||||
|           } catch (ex) { | ||||
|             let stat = await fd.stat(); | ||||
|             Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|             Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|           } | ||||
|           try { | ||||
|             await fd.setDates(p, p); | ||||
|             do_throw("Invalid dates should have thrown for: " + p); | ||||
|           } catch (ex) { | ||||
|             let stat = await fd.stat(); | ||||
|             Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|             Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } finally { | ||||
|       await fd.close(); | ||||
|     } | ||||
|   } finally { | ||||
|     // Remove the temp file again
 | ||||
|     await OS.File.remove(path); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| // Tests setting dates on directories.
 | ||||
| add_task(async function test_dirs() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_setDates_dir" | ||||
|   ); | ||||
|   await OS.File.makeDir(path); | ||||
| 
 | ||||
|   try { | ||||
|     // 1. Try to set some well known dates.
 | ||||
|     // We choose multiples of 2000ms, because the time stamp resolution of
 | ||||
|     // the underlying OS might not support something more precise.
 | ||||
|     const accDate = 2000; | ||||
|     const modDate = 4000; | ||||
|     { | ||||
|       await OS.File.setDates(path, accDate, modDate); | ||||
|       let stat = await OS.File.stat(path); | ||||
|       Assert.equal(accDate, stat.lastAccessDate.getTime()); | ||||
|       Assert.equal(modDate, stat.lastModificationDate.getTime()); | ||||
|     } | ||||
|   } finally { | ||||
|     await OS.File.removeEmptyDir(path); | ||||
|   } | ||||
| }); | ||||
|  | @ -1,102 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| /** | ||||
|  * A test to ensure that OS.File.setPermissions and | ||||
|  * OS.File.prototype.setPermissions are all working correctly. | ||||
|  * (see bug 1001849) | ||||
|  * These functions are currently Unix-specific.  The manifest skips | ||||
|  * the test on Windows. | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * Helper function for test logging: prints a POSIX file permission mode as an | ||||
|  * octal number, with a leading '0' per C (not JS) convention.  When the | ||||
|  * numeric value is 0o777 or lower, it is padded on the left with zeroes to | ||||
|  * four digits wide. | ||||
|  * Sample outputs:  0022, 0644, 04755. | ||||
|  */ | ||||
| function format_mode(mode) { | ||||
|   if (mode <= 0o777) { | ||||
|     return ("0000" + mode.toString(8)).slice(-4); | ||||
|   } | ||||
|   return "0" + mode.toString(8); | ||||
| } | ||||
| 
 | ||||
| const _umask = OS.Constants.Sys.umask; | ||||
| info("umask: " + format_mode(_umask)); | ||||
| 
 | ||||
| /** | ||||
|  * Compute the mode that a file should have after applying the umask, | ||||
|  * whatever it happens to be. | ||||
|  */ | ||||
| function apply_umask(mode) { | ||||
|   return mode & ~_umask; | ||||
| } | ||||
| 
 | ||||
| // Sequence of setPermission parameters and expected file mode.  The first test
 | ||||
| // checks the permissions when the file is first created.
 | ||||
| var testSequence = [ | ||||
|   [null, apply_umask(0o600)], | ||||
|   [{ unixMode: 0o4777 }, apply_umask(0o4777)], | ||||
|   [{ unixMode: 0o4777, unixHonorUmask: false }, 0o4777], | ||||
|   [{ unixMode: 0o4777, unixHonorUmask: true }, apply_umask(0o4777)], | ||||
|   [undefined, apply_umask(0o600)], | ||||
|   [{ unixMode: 0o666 }, apply_umask(0o666)], | ||||
|   [{ unixMode: 0o600 }, apply_umask(0o600)], | ||||
|   [{ unixMode: 0 }, 0], | ||||
|   [{}, apply_umask(0o600)], | ||||
| ]; | ||||
| 
 | ||||
| // Test application to paths.
 | ||||
| add_task(async function test_path_setPermissions() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_setPermissions_path.tmp" | ||||
|   ); | ||||
|   await OS.File.writeAtomic(path, new Uint8Array(1)); | ||||
| 
 | ||||
|   try { | ||||
|     for (let [options, expectedMode] of testSequence) { | ||||
|       if (options !== null) { | ||||
|         info("Setting permissions to " + JSON.stringify(options)); | ||||
|         await OS.File.setPermissions(path, options); | ||||
|       } | ||||
| 
 | ||||
|       let stat = await OS.File.stat(path); | ||||
|       Assert.equal(format_mode(stat.unixMode), format_mode(expectedMode)); | ||||
|     } | ||||
|   } finally { | ||||
|     await OS.File.remove(path); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| // Test application to open files.
 | ||||
| add_task(async function test_file_setPermissions() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_async_setPermissions_file.tmp" | ||||
|   ); | ||||
|   await OS.File.writeAtomic(path, new Uint8Array(1)); | ||||
| 
 | ||||
|   try { | ||||
|     let fd = await OS.File.open(path, { write: true }); | ||||
|     try { | ||||
|       for (let [options, expectedMode] of testSequence) { | ||||
|         if (options !== null) { | ||||
|           info("Setting permissions to " + JSON.stringify(options)); | ||||
|           await fd.setPermissions(options); | ||||
|         } | ||||
| 
 | ||||
|         let stat = await fd.stat(); | ||||
|         Assert.equal(format_mode(stat.unixMode), format_mode(expectedMode)); | ||||
|       } | ||||
|     } finally { | ||||
|       await fd.close(); | ||||
|     } | ||||
|   } finally { | ||||
|     await OS.File.remove(path); | ||||
|   } | ||||
| }); | ||||
|  | @ -1,46 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| function run_test() { | ||||
|   do_test_pending(); | ||||
|   run_next_test(); | ||||
| } | ||||
| 
 | ||||
| add_task(async function test_closed() { | ||||
|   OS.Shared.DEBUG = true; | ||||
|   let currentDir = await OS.File.getCurrentDirectory(); | ||||
|   info("Open a file, ensure that we can call stat()"); | ||||
|   let path = OS.Path.join(currentDir, "test_osfile_closed.js"); | ||||
|   let file = await OS.File.open(path); | ||||
|   await file.stat(); | ||||
|   Assert.ok(true); | ||||
| 
 | ||||
|   await file.close(); | ||||
| 
 | ||||
|   info("Ensure that we cannot stat() on closed file"); | ||||
|   let exn; | ||||
|   try { | ||||
|     await file.stat(); | ||||
|   } catch (ex) { | ||||
|     exn = ex; | ||||
|   } | ||||
|   info("Ensure that this raises the correct error"); | ||||
|   Assert.ok(!!exn); | ||||
|   Assert.ok(exn instanceof OS.File.Error); | ||||
|   Assert.ok(exn.becauseClosed); | ||||
| 
 | ||||
|   info("Ensure that we cannot read() on closed file"); | ||||
|   exn = null; | ||||
|   try { | ||||
|     await file.read(); | ||||
|   } catch (ex) { | ||||
|     exn = ex; | ||||
|   } | ||||
|   info("Ensure that this raises the correct error"); | ||||
|   Assert.ok(!!exn); | ||||
|   Assert.ok(exn instanceof OS.File.Error); | ||||
|   Assert.ok(exn.becauseClosed); | ||||
| }); | ||||
| 
 | ||||
| add_task(do_test_finished); | ||||
|  | @ -1,56 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var { | ||||
|   OS: { File, Path, Constants }, | ||||
| } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| add_task(async function testFileError_with_writeAtomic() { | ||||
|   let DEFAULT_CONTENTS = "default contents" + Math.random(); | ||||
|   let path = Path.join(Constants.Path.tmpDir, "testFileError.tmp"); | ||||
|   await File.remove(path); | ||||
|   await File.writeAtomic(path, DEFAULT_CONTENTS); | ||||
|   let exception; | ||||
|   try { | ||||
|     await File.writeAtomic(path, DEFAULT_CONTENTS, { noOverwrite: true }); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
|   Assert.ok(exception instanceof File.Error); | ||||
|   Assert.ok(exception.path == path); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function testFileError_with_makeDir() { | ||||
|   let path = Path.join(Constants.Path.tmpDir, "directory"); | ||||
|   await File.removeDir(path); | ||||
|   await File.makeDir(path); | ||||
|   let exception; | ||||
|   try { | ||||
|     await File.makeDir(path, { ignoreExisting: false }); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
|   Assert.ok(exception instanceof File.Error); | ||||
|   Assert.ok(exception.path == path); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function testFileError_with_move() { | ||||
|   let DEFAULT_CONTENTS = "default contents" + Math.random(); | ||||
|   let sourcePath = Path.join(Constants.Path.tmpDir, "src.tmp"); | ||||
|   let destPath = Path.join(Constants.Path.tmpDir, "dest.tmp"); | ||||
|   await File.remove(sourcePath); | ||||
|   await File.remove(destPath); | ||||
|   await File.writeAtomic(sourcePath, DEFAULT_CONTENTS); | ||||
|   await File.writeAtomic(destPath, DEFAULT_CONTENTS); | ||||
|   let exception; | ||||
|   try { | ||||
|     await File.move(sourcePath, destPath, { noOverwrite: true }); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
|   info(exception); | ||||
|   Assert.ok(exception instanceof File.Error); | ||||
|   Assert.ok(exception.path == sourcePath); | ||||
| }); | ||||
|  | @ -1,97 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| // We want the actual global to get at the internals since Scheduler is not
 | ||||
| // exported.
 | ||||
| var { Scheduler } = ChromeUtils.import( | ||||
|   "resource://gre/modules/osfile/osfile_async_front.jsm" | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * Verify that Scheduler.kill() interacts with other OS.File requests correctly, | ||||
|  * and that no requests are lost.  This is relevant because on B2G we | ||||
|  * auto-kill the worker periodically, making it very possible for valid requests | ||||
|  * to be interleaved with the automatic kill(). | ||||
|  * | ||||
|  * This test is being created with the fix for Bug 1125989 where `kill` queue | ||||
|  * management was found to be buggy.  It is a glass-box test that explicitly | ||||
|  * re-creates the observed failure situation; it is not guaranteed to prevent | ||||
|  * all future regressions.  The following is a detailed explanation of the test | ||||
|  * for your benefit if this test ever breaks or you are wondering what was the | ||||
|  * point of all this.  You might want to skim the code below first. | ||||
|  * | ||||
|  * OS.File maintains a `queue` of operations to be performed.  This queue is | ||||
|  * nominally implemented as a chain of promises.  Every time a new job is | ||||
|  * OS.File.push()ed, it effectively becomes the new `queue` promise.  (An | ||||
|  * extra promise is interposed with a rejection handler to avoid the rejection | ||||
|  * cascading, but that does not matter for our purposes.) | ||||
|  * | ||||
|  * The flaw in `kill` was that it would wait for the `queue` to complete before | ||||
|  * replacing `queue`.  As a result, another OS.File operation could use `push` | ||||
|  * (by way of OS.File.post()) to also use .then() on the same `queue` promise. | ||||
|  * Accordingly, assuming that promise was not yet resolved (due to a pending | ||||
|  * OS.File request), when it was resolved, both the task scheduled in `kill` | ||||
|  * and in `post` would be triggered.  Both of those tasks would run until | ||||
|  * encountering a call to worker.post(). | ||||
|  * | ||||
|  * Re-creating this race is not entirely trivial because of the large number of | ||||
|  * promises used by the code causing control flow to repeatedly be deferred.  In | ||||
|  * a slightly simpler world we could run the follwing in the same turn of the | ||||
|  * event loop and trigger the problem. | ||||
|  * - any OS.File request | ||||
|  * - Scheduler.kill() | ||||
|  * - any OS.File request | ||||
|  * | ||||
|  * However, we need the Scheduler.kill task to reach the point where it is | ||||
|  * waiting on the same `queue` that another task has been scheduled against. | ||||
|  * Since the `kill` task yields on the `killQueue` promise prior to yielding | ||||
|  * on `queue`, however, some turns of the event loop are required.  Happily, | ||||
|  * for us, as discussed above, the problem triggers when we have two promises | ||||
|  * scheduled on the `queue`, so we can just wait to schedule the second OS.File | ||||
|  * request on the queue.  (Note that because of the additional then() added to | ||||
|  * eat rejections, there is an important difference between the value of | ||||
|  * `queue` and the value returned by the first OS.File request.) | ||||
|  */ | ||||
| add_task(async function test_kill_race() { | ||||
|   // Ensure the worker has been created and that SET_DEBUG has taken effect.
 | ||||
|   // We have chosen OS.File.exists for our tests because it does not trigger
 | ||||
|   // a rejection and we absolutely do not care what the operation is other
 | ||||
|   // than it does not invoke a native fast-path.
 | ||||
|   await OS.File.exists("foo.foo"); | ||||
| 
 | ||||
|   info("issuing first request"); | ||||
|   let firstRequest = OS.File.exists("foo.bar"); // eslint-disable-line no-unused-vars
 | ||||
|   let secondRequest; | ||||
|   let secondResolved = false; | ||||
| 
 | ||||
|   // As noted in our big block comment, we want to wait to schedule the
 | ||||
|   // second request so that it races `kill`'s call to `worker.post`.  Having
 | ||||
|   // ourselves wait on the same promise, `queue`, and registering ourselves
 | ||||
|   // before we issue the kill request means we will get run before the `kill`
 | ||||
|   // task resumes and allow us to precisely create the desired race.
 | ||||
|   Scheduler.queue.then(function() { | ||||
|     info("issuing second request"); | ||||
|     secondRequest = OS.File.exists("foo.baz"); | ||||
|     secondRequest.then(function() { | ||||
|       secondResolved = true; | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   info("issuing kill request"); | ||||
|   let killRequest = Scheduler.kill({ reset: true, shutdown: false }); | ||||
| 
 | ||||
|   // Wait on the killRequest so that we can schedule a new OS.File request
 | ||||
|   // after it completes...
 | ||||
|   await killRequest; | ||||
|   // ...because our ordering guarantee ensures that there is at most one
 | ||||
|   // worker (and this usage here should not be vulnerable even with the
 | ||||
|   // bug present), so when this completes the secondRequest has either been
 | ||||
|   // resolved or lost.
 | ||||
|   await OS.File.exists("foo.goz"); | ||||
| 
 | ||||
|   ok( | ||||
|     secondResolved, | ||||
|     "The second request was resolved so we avoided the bug. Victory!" | ||||
|   ); | ||||
| }); | ||||
|  | @ -1,135 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| /** | ||||
|  * A test to ensure that OS.File.setPermissions and | ||||
|  * OS.File.prototype.setPermissions are all working correctly. | ||||
|  * (see bug 1022816) | ||||
|  * The manifest tests on Windows. | ||||
|  */ | ||||
| 
 | ||||
| // Sequence of setPermission parameters.
 | ||||
| var testSequence = [ | ||||
|   [ | ||||
|     { winAttributes: { readOnly: true, system: true, hidden: true } }, | ||||
|     { readOnly: true, system: true, hidden: true }, | ||||
|   ], | ||||
|   [ | ||||
|     { winAttributes: { readOnly: false } }, | ||||
|     { readOnly: false, system: true, hidden: true }, | ||||
|   ], | ||||
|   [ | ||||
|     { winAttributes: { system: false } }, | ||||
|     { readOnly: false, system: false, hidden: true }, | ||||
|   ], | ||||
|   [ | ||||
|     { winAttributes: { hidden: false } }, | ||||
|     { readOnly: false, system: false, hidden: false }, | ||||
|   ], | ||||
|   [ | ||||
|     { winAttributes: { readOnly: true, system: false, hidden: false } }, | ||||
|     { readOnly: true, system: false, hidden: false }, | ||||
|   ], | ||||
|   [ | ||||
|     { winAttributes: { readOnly: false, system: true, hidden: false } }, | ||||
|     { readOnly: false, system: true, hidden: false }, | ||||
|   ], | ||||
|   [ | ||||
|     { winAttributes: { readOnly: false, system: false, hidden: true } }, | ||||
|     { readOnly: false, system: false, hidden: true }, | ||||
|   ], | ||||
| ]; | ||||
| 
 | ||||
| // Test application to paths.
 | ||||
| add_task(async function test_path_setPermissions() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_win_async_setPermissions_path.tmp" | ||||
|   ); | ||||
|   await OS.File.writeAtomic(path, new Uint8Array(1)); | ||||
| 
 | ||||
|   try { | ||||
|     for (let [options, attributesExpected] of testSequence) { | ||||
|       if (options !== null) { | ||||
|         info("Setting permissions to " + JSON.stringify(options)); | ||||
|         await OS.File.setPermissions(path, options); | ||||
|       } | ||||
| 
 | ||||
|       let stat = await OS.File.stat(path); | ||||
|       info("Got stat winAttributes: " + JSON.stringify(stat.winAttributes)); | ||||
| 
 | ||||
|       Assert.equal(stat.winAttributes.readOnly, attributesExpected.readOnly); | ||||
|       Assert.equal(stat.winAttributes.system, attributesExpected.system); | ||||
|       Assert.equal(stat.winAttributes.hidden, attributesExpected.hidden); | ||||
|     } | ||||
|   } finally { | ||||
|     await OS.File.remove(path); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| // Test application to open files.
 | ||||
| add_task(async function test_file_setPermissions() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_win_async_setPermissions_file.tmp" | ||||
|   ); | ||||
|   await OS.File.writeAtomic(path, new Uint8Array(1)); | ||||
| 
 | ||||
|   try { | ||||
|     let fd = await OS.File.open(path, { write: true }); | ||||
|     try { | ||||
|       for (let [options, attributesExpected] of testSequence) { | ||||
|         if (options !== null) { | ||||
|           info("Setting permissions to " + JSON.stringify(options)); | ||||
|           await fd.setPermissions(options); | ||||
|         } | ||||
| 
 | ||||
|         let stat = await fd.stat(); | ||||
|         info("Got stat winAttributes: " + JSON.stringify(stat.winAttributes)); | ||||
|         Assert.equal(stat.winAttributes.readOnly, attributesExpected.readOnly); | ||||
|         Assert.equal(stat.winAttributes.system, attributesExpected.system); | ||||
|         Assert.equal(stat.winAttributes.hidden, attributesExpected.hidden); | ||||
|       } | ||||
|     } finally { | ||||
|       await fd.close(); | ||||
|     } | ||||
|   } finally { | ||||
|     await OS.File.remove(path); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| // Test application to Check setPermissions on a non-existant file path.
 | ||||
| add_task(async function test_non_existant_file_path_setPermissions() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_win_async_setPermissions_path.tmp" | ||||
|   ); | ||||
|   await Assert.rejects( | ||||
|     OS.File.setPermissions(path, { winAttributes: { readOnly: true } }), | ||||
|     /The system cannot find the file specified/, | ||||
|     "setPermissions failed as expected on a non-existant file path" | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| // Test application to Check setPermissions on a invalid file handle.
 | ||||
| add_task(async function test_closed_file_handle_setPermissions() { | ||||
|   let path = OS.Path.join( | ||||
|     OS.Constants.Path.tmpDir, | ||||
|     "test_osfile_win_async_setPermissions_path.tmp" | ||||
|   ); | ||||
|   await OS.File.writeAtomic(path, new Uint8Array(1)); | ||||
| 
 | ||||
|   try { | ||||
|     let fd = await OS.File.open(path, { write: true }); | ||||
|     await fd.close(); | ||||
|     await Assert.rejects( | ||||
|       fd.setPermissions(path, { winAttributes: { readOnly: true } }), | ||||
|       /The handle is invalid/, | ||||
|       "setPermissions failed as expected on a invalid file handle" | ||||
|     ); | ||||
|   } finally { | ||||
|     await OS.File.remove(path); | ||||
|   } | ||||
| }); | ||||
|  | @ -1,148 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var { | ||||
|   OS: { File, Path, Constants }, | ||||
| } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| /** | ||||
|  * Remove all temporary files and back up files, including | ||||
|  * test_backupTo_option_with_tmpPath.tmp | ||||
|  * test_backupTo_option_with_tmpPath.tmp.backup | ||||
|  * test_backupTo_option_without_tmpPath.tmp | ||||
|  * test_backupTo_option_without_tmpPath.tmp.backup | ||||
|  * test_non_backupTo_option.tmp | ||||
|  * test_non_backupTo_option.tmp.backup | ||||
|  * test_backupTo_option_without_destination_file.tmp | ||||
|  * test_backupTo_option_without_destination_file.tmp.backup | ||||
|  * test_backupTo_option_with_backup_file.tmp | ||||
|  * test_backupTo_option_with_backup_file.tmp.backup | ||||
|  */ | ||||
| async function clearFiles() { | ||||
|   let files = [ | ||||
|     "test_backupTo_option_with_tmpPath.tmp", | ||||
|     "test_backupTo_option_without_tmpPath.tmp", | ||||
|     "test_non_backupTo_option.tmp", | ||||
|     "test_backupTo_option_without_destination_file.tmp", | ||||
|     "test_backupTo_option_with_backup_file.tmp", | ||||
|   ]; | ||||
|   for (let file of files) { | ||||
|     let path = Path.join(Constants.Path.tmpDir, file); | ||||
|     await File.remove(path); | ||||
|     await File.remove(path + ".backup"); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| add_task(async function init() { | ||||
|   await clearFiles(); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * test when | ||||
|  * |backupTo| specified | ||||
|  * |tmpPath| specified | ||||
|  * destination file exists | ||||
|  * @result destination file will be backed up | ||||
|  */ | ||||
| add_task(async function test_backupTo_option_with_tmpPath() { | ||||
|   let DEFAULT_CONTENTS = "default contents" + Math.random(); | ||||
|   let WRITE_CONTENTS = "abc" + Math.random(); | ||||
|   let path = Path.join( | ||||
|     Constants.Path.tmpDir, | ||||
|     "test_backupTo_option_with_tmpPath.tmp" | ||||
|   ); | ||||
|   await File.writeAtomic(path, DEFAULT_CONTENTS); | ||||
|   await File.writeAtomic(path, WRITE_CONTENTS, { | ||||
|     tmpPath: path + ".tmp", | ||||
|     backupTo: path + ".backup", | ||||
|   }); | ||||
|   Assert.ok(await File.exists(path + ".backup")); | ||||
|   let contents = await File.read(path + ".backup"); | ||||
|   Assert.equal(DEFAULT_CONTENTS, new TextDecoder().decode(contents)); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * test when | ||||
|  * |backupTo| specified | ||||
|  * |tmpPath| not specified | ||||
|  * destination file exists | ||||
|  * @result destination file will be backed up | ||||
|  */ | ||||
| add_task(async function test_backupTo_option_without_tmpPath() { | ||||
|   let DEFAULT_CONTENTS = "default contents" + Math.random(); | ||||
|   let WRITE_CONTENTS = "abc" + Math.random(); | ||||
|   let path = Path.join( | ||||
|     Constants.Path.tmpDir, | ||||
|     "test_backupTo_option_without_tmpPath.tmp" | ||||
|   ); | ||||
|   await File.writeAtomic(path, DEFAULT_CONTENTS); | ||||
|   await File.writeAtomic(path, WRITE_CONTENTS, { backupTo: path + ".backup" }); | ||||
|   Assert.ok(await File.exists(path + ".backup")); | ||||
|   let contents = await File.read(path + ".backup"); | ||||
|   Assert.equal(DEFAULT_CONTENTS, new TextDecoder().decode(contents)); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * test when | ||||
|  * |backupTo| not specified | ||||
|  * |tmpPath| not specified | ||||
|  * destination file exists | ||||
|  * @result destination file will not be backed up | ||||
|  */ | ||||
| add_task(async function test_non_backupTo_option() { | ||||
|   let DEFAULT_CONTENTS = "default contents" + Math.random(); | ||||
|   let WRITE_CONTENTS = "abc" + Math.random(); | ||||
|   let path = Path.join(Constants.Path.tmpDir, "test_non_backupTo_option.tmp"); | ||||
|   await File.writeAtomic(path, DEFAULT_CONTENTS); | ||||
|   await File.writeAtomic(path, WRITE_CONTENTS); | ||||
|   Assert.equal(false, await File.exists(path + ".backup")); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * test when | ||||
|  * |backupTo| specified | ||||
|  * |tmpPath| not specified | ||||
|  * destination file not exists | ||||
|  * @result no back up file exists | ||||
|  */ | ||||
| add_task(async function test_backupTo_option_without_destination_file() { | ||||
|   let WRITE_CONTENTS = "abc" + Math.random(); | ||||
|   let path = Path.join( | ||||
|     Constants.Path.tmpDir, | ||||
|     "test_backupTo_option_without_destination_file.tmp" | ||||
|   ); | ||||
|   await File.remove(path); | ||||
|   await File.writeAtomic(path, WRITE_CONTENTS, { backupTo: path + ".backup" }); | ||||
|   Assert.equal(false, await File.exists(path + ".backup")); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * test when | ||||
|  * |backupTo| specified | ||||
|  * |tmpPath| not specified | ||||
|  * backup file exists | ||||
|  * destination file exists | ||||
|  * @result destination file will be backed up | ||||
|  */ | ||||
| add_task(async function test_backupTo_option_with_backup_file() { | ||||
|   let DEFAULT_CONTENTS = "default contents" + Math.random(); | ||||
|   let WRITE_CONTENTS = "abc" + Math.random(); | ||||
|   let path = Path.join( | ||||
|     Constants.Path.tmpDir, | ||||
|     "test_backupTo_option_with_backup_file.tmp" | ||||
|   ); | ||||
|   await File.writeAtomic(path, DEFAULT_CONTENTS); | ||||
| 
 | ||||
|   await File.writeAtomic(path + ".backup", new Uint8Array(1000)); | ||||
| 
 | ||||
|   await File.writeAtomic(path, WRITE_CONTENTS, { backupTo: path + ".backup" }); | ||||
|   Assert.ok(await File.exists(path + ".backup")); | ||||
|   let contents = await File.read(path + ".backup"); | ||||
|   Assert.equal(DEFAULT_CONTENTS, new TextDecoder().decode(contents)); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function cleanup() { | ||||
|   await clearFiles(); | ||||
| }); | ||||
|  | @ -1,48 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| /** | ||||
|  * This test checks against failures that may occur while creating and/or | ||||
|  * renaming files with Unicode paths on Windows. | ||||
|  * See bug 1063635#c89 for a failure due to a Unicode filename being renamed. | ||||
|  */ | ||||
| 
 | ||||
| "use strict"; | ||||
| var profileDir; | ||||
| 
 | ||||
| async function writeAndCheck(path, tmpPath) { | ||||
|   const encoder = new TextEncoder(); | ||||
|   const content = "tmpContent"; | ||||
|   const outBin = encoder.encode(content); | ||||
|   await OS.File.writeAtomic(path, outBin, { tmpPath }); | ||||
| 
 | ||||
|   const decoder = new TextDecoder(); | ||||
|   const writtenBin = await OS.File.read(path); | ||||
|   const written = decoder.decode(writtenBin); | ||||
| 
 | ||||
|   // Clean up
 | ||||
|   await OS.File.remove(path); | ||||
|   Assert.equal( | ||||
|     written, | ||||
|     content, | ||||
|     `Expected correct write/read for ${path} with tmpPath ${tmpPath}` | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| add_task(async function init() { | ||||
|   do_get_profile(); | ||||
|   profileDir = OS.Constants.Path.profileDir; | ||||
| }); | ||||
| 
 | ||||
| add_test_pair(async function test_osfile_writeAtomic_unicode_filename() { | ||||
|   await writeAndCheck(OS.Path.join(profileDir, "☕") + ".tmp", undefined); | ||||
|   await writeAndCheck(OS.Path.join(profileDir, "☕"), undefined); | ||||
|   await writeAndCheck( | ||||
|     OS.Path.join(profileDir, "☕") + ".tmp", | ||||
|     OS.Path.join(profileDir, "☕") | ||||
|   ); | ||||
|   await writeAndCheck( | ||||
|     OS.Path.join(profileDir, "☕"), | ||||
|     OS.Path.join(profileDir, "☕") + ".tmp" | ||||
|   ); | ||||
| }); | ||||
|  | @ -1,26 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| var SHARED_PATH; | ||||
| 
 | ||||
| add_task(async function init() { | ||||
|   do_get_profile(); | ||||
|   SHARED_PATH = OS.Path.join( | ||||
|     OS.Constants.Path.profileDir, | ||||
|     "test_osfile_write_zerobytes.tmp" | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| add_test_pair(async function test_osfile_writeAtomic_zerobytes() { | ||||
|   let encoder = new TextEncoder(); | ||||
|   let string1 = ""; | ||||
|   let outbin = encoder.encode(string1); | ||||
|   await OS.File.writeAtomic(SHARED_PATH, outbin); | ||||
| 
 | ||||
|   let decoder = new TextDecoder(); | ||||
|   let bin = await OS.File.read(SHARED_PATH); | ||||
|   let string2 = decoder.decode(bin); | ||||
|   // Checking if writeAtomic supports writing encoded zero-byte strings
 | ||||
|   Assert.equal(string2, string1, "Read the expected (empty) string."); | ||||
| }); | ||||
|  | @ -1,187 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| Services.prefs.setBoolPref("toolkit.osfile.test.syslib_necessary", false); | ||||
| // We don't need libc/kernel32.dll for this test
 | ||||
| 
 | ||||
| const Win = ChromeUtils.import("resource://gre/modules/osfile/ospath_win.jsm"); | ||||
| const Unix = ChromeUtils.import( | ||||
|   "resource://gre/modules/osfile/ospath_unix.jsm" | ||||
| ); | ||||
| 
 | ||||
| function do_check_fail(f) { | ||||
|   try { | ||||
|     let result = f(); | ||||
|     info("Failed do_check_fail: " + result); | ||||
|     Assert.ok(false); | ||||
|   } catch (ex) { | ||||
|     Assert.ok(true); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function run_test() { | ||||
|   info("Testing Windows paths"); | ||||
| 
 | ||||
|   info("Backslash-separated, no drive"); | ||||
|   Assert.equal(Win.basename("a\\b"), "b"); | ||||
|   Assert.equal(Win.basename("a\\b\\"), ""); | ||||
|   Assert.equal(Win.basename("abc"), "abc"); | ||||
|   Assert.equal(Win.dirname("a\\b"), "a"); | ||||
|   Assert.equal(Win.dirname("a\\b\\"), "a\\b"); | ||||
|   Assert.equal(Win.dirname("a\\\\\\\\b"), "a"); | ||||
|   Assert.equal(Win.dirname("abc"), "."); | ||||
|   Assert.equal(Win.normalize("\\a\\b\\c"), "\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("\\a\\b\\\\\\\\c"), "\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("\\a\\b\\c\\\\\\"), "\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("\\a\\b\\c\\..\\..\\..\\d\\e\\f"), "\\d\\e\\f"); | ||||
|   Assert.equal(Win.normalize("a\\b\\c\\..\\..\\..\\d\\e\\f"), "d\\e\\f"); | ||||
|   do_check_fail(() => Win.normalize("\\a\\b\\c\\..\\..\\..\\..\\d\\e\\f")); | ||||
| 
 | ||||
|   Assert.equal( | ||||
|     Win.join("\\tmp", "foo", "bar"), | ||||
|     "\\tmp\\foo\\bar", | ||||
|     "join \\tmp,foo,bar" | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     Win.join("\\tmp", "\\foo", "bar"), | ||||
|     "\\foo\\bar", | ||||
|     "join \\tmp,\\foo,bar" | ||||
|   ); | ||||
|   Assert.equal(Win.winGetDrive("\\tmp"), null); | ||||
|   Assert.equal(Win.winGetDrive("\\tmp\\a\\b\\c\\d\\e"), null); | ||||
|   Assert.equal(Win.winGetDrive("\\"), null); | ||||
| 
 | ||||
|   info("Backslash-separated, with a drive"); | ||||
|   Assert.equal(Win.basename("c:a\\b"), "b"); | ||||
|   Assert.equal(Win.basename("c:a\\b\\"), ""); | ||||
|   Assert.equal(Win.basename("c:abc"), "abc"); | ||||
|   Assert.equal(Win.dirname("c:a\\b"), "c:a"); | ||||
|   Assert.equal(Win.dirname("c:a\\b\\"), "c:a\\b"); | ||||
|   Assert.equal(Win.dirname("c:a\\\\\\\\b"), "c:a"); | ||||
|   Assert.equal(Win.dirname("c:abc"), "c:"); | ||||
|   let options = { | ||||
|     winNoDrive: true, | ||||
|   }; | ||||
|   Assert.equal(Win.dirname("c:a\\b", options), "a"); | ||||
|   Assert.equal(Win.dirname("c:a\\b\\", options), "a\\b"); | ||||
|   Assert.equal(Win.dirname("c:a\\\\\\\\b", options), "a"); | ||||
|   Assert.equal(Win.dirname("c:abc", options), "."); | ||||
|   Assert.equal(Win.join("c:", "abc"), "c:\\abc", "join c:,abc"); | ||||
| 
 | ||||
|   Assert.equal(Win.normalize("c:"), "c:\\"); | ||||
|   Assert.equal(Win.normalize("c:\\"), "c:\\"); | ||||
|   Assert.equal(Win.normalize("c:\\a\\b\\c"), "c:\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("c:\\a\\b\\\\\\\\c"), "c:\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("c:\\\\\\\\a\\b\\c"), "c:\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("c:\\a\\b\\c\\\\\\"), "c:\\a\\b\\c"); | ||||
|   Assert.equal( | ||||
|     Win.normalize("c:\\a\\b\\c\\..\\..\\..\\d\\e\\f"), | ||||
|     "c:\\d\\e\\f" | ||||
|   ); | ||||
|   Assert.equal(Win.normalize("c:a\\b\\c\\..\\..\\..\\d\\e\\f"), "c:\\d\\e\\f"); | ||||
|   do_check_fail(() => Win.normalize("c:\\a\\b\\c\\..\\..\\..\\..\\d\\e\\f")); | ||||
| 
 | ||||
|   Assert.equal(Win.join("c:\\", "foo"), "c:\\foo", "join c:,foo"); | ||||
|   Assert.equal( | ||||
|     Win.join("c:\\tmp", "foo", "bar"), | ||||
|     "c:\\tmp\\foo\\bar", | ||||
|     "join c:\\tmp,foo,bar" | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     Win.join("c:\\tmp", "\\foo", "bar"), | ||||
|     "c:\\foo\\bar", | ||||
|     "join c:\\tmp,\\foo,bar" | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     Win.join("c:\\tmp", "c:\\foo", "bar"), | ||||
|     "c:\\foo\\bar", | ||||
|     "join c:\\tmp,c:\\foo,bar" | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     Win.join("c:\\tmp", "c:foo", "bar"), | ||||
|     "c:\\foo\\bar", | ||||
|     "join c:\\tmp,c:foo,bar" | ||||
|   ); | ||||
|   Assert.equal(Win.winGetDrive("c:"), "c:"); | ||||
|   Assert.equal(Win.winGetDrive("c:\\"), "c:"); | ||||
|   Assert.equal(Win.winGetDrive("c:abc"), "c:"); | ||||
|   Assert.equal(Win.winGetDrive("c:abc\\d\\e\\f\\g"), "c:"); | ||||
|   Assert.equal(Win.winGetDrive("c:\\abc"), "c:"); | ||||
|   Assert.equal(Win.winGetDrive("c:\\abc\\d\\e\\f\\g"), "c:"); | ||||
| 
 | ||||
|   info("Forwardslash-separated, no drive"); | ||||
|   Assert.equal(Win.normalize("/a/b/c"), "\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("/a/b////c"), "\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("/a/b/c///"), "\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("/a/b/c/../../../d/e/f"), "\\d\\e\\f"); | ||||
|   Assert.equal(Win.normalize("a/b/c/../../../d/e/f"), "d\\e\\f"); | ||||
| 
 | ||||
|   info("Forwardslash-separated, with a drive"); | ||||
|   Assert.equal(Win.normalize("c:/"), "c:\\"); | ||||
|   Assert.equal(Win.normalize("c:/a/b/c"), "c:\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("c:/a/b////c"), "c:\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("c:////a/b/c"), "c:\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("c:/a/b/c///"), "c:\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("c:/a/b/c/../../../d/e/f"), "c:\\d\\e\\f"); | ||||
|   Assert.equal(Win.normalize("c:a/b/c/../../../d/e/f"), "c:\\d\\e\\f"); | ||||
| 
 | ||||
|   info("Backslash-separated, UNC-style"); | ||||
|   Assert.equal(Win.basename("\\\\a\\b"), "b"); | ||||
|   Assert.equal(Win.basename("\\\\a\\b\\"), ""); | ||||
|   Assert.equal(Win.basename("\\\\abc"), ""); | ||||
|   Assert.equal(Win.dirname("\\\\a\\b"), "\\\\a"); | ||||
|   Assert.equal(Win.dirname("\\\\a\\b\\"), "\\\\a\\b"); | ||||
|   Assert.equal(Win.dirname("\\\\a\\\\\\\\b"), "\\\\a"); | ||||
|   Assert.equal(Win.dirname("\\\\abc"), "\\\\abc"); | ||||
|   Assert.equal(Win.normalize("\\\\a\\b\\c"), "\\\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("\\\\a\\b\\\\\\\\c"), "\\\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("\\\\a\\b\\c\\\\\\"), "\\\\a\\b\\c"); | ||||
|   Assert.equal(Win.normalize("\\\\a\\b\\c\\..\\..\\d\\e\\f"), "\\\\a\\d\\e\\f"); | ||||
|   do_check_fail(() => Win.normalize("\\\\a\\b\\c\\..\\..\\..\\d\\e\\f")); | ||||
| 
 | ||||
|   Assert.equal(Win.join("\\\\a\\tmp", "foo", "bar"), "\\\\a\\tmp\\foo\\bar"); | ||||
|   Assert.equal(Win.join("\\\\a\\tmp", "\\foo", "bar"), "\\\\a\\foo\\bar"); | ||||
|   Assert.equal(Win.join("\\\\a\\tmp", "\\\\foo\\", "bar"), "\\\\foo\\bar"); | ||||
|   Assert.equal(Win.winGetDrive("\\\\"), null); | ||||
|   Assert.equal(Win.winGetDrive("\\\\c"), "\\\\c"); | ||||
|   Assert.equal(Win.winGetDrive("\\\\c\\abc"), "\\\\c"); | ||||
| 
 | ||||
|   info("Testing unix paths"); | ||||
|   Assert.equal(Unix.basename("a/b"), "b"); | ||||
|   Assert.equal(Unix.basename("a/b/"), ""); | ||||
|   Assert.equal(Unix.basename("abc"), "abc"); | ||||
|   Assert.equal(Unix.dirname("a/b"), "a"); | ||||
|   Assert.equal(Unix.dirname("a/b/"), "a/b"); | ||||
|   Assert.equal(Unix.dirname("a////b"), "a"); | ||||
|   Assert.equal(Unix.dirname("abc"), "."); | ||||
|   Assert.equal(Unix.normalize("/a/b/c"), "/a/b/c"); | ||||
|   Assert.equal(Unix.normalize("/a/b////c"), "/a/b/c"); | ||||
|   Assert.equal(Unix.normalize("////a/b/c"), "/a/b/c"); | ||||
|   Assert.equal(Unix.normalize("/a/b/c///"), "/a/b/c"); | ||||
|   Assert.equal(Unix.normalize("/a/b/c/../../../d/e/f"), "/d/e/f"); | ||||
|   Assert.equal(Unix.normalize("a/b/c/../../../d/e/f"), "d/e/f"); | ||||
|   do_check_fail(() => Unix.normalize("/a/b/c/../../../../d/e/f")); | ||||
| 
 | ||||
|   Assert.equal( | ||||
|     Unix.join("/tmp", "foo", "bar"), | ||||
|     "/tmp/foo/bar", | ||||
|     "join /tmp,foo,bar" | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     Unix.join("/tmp", "/foo", "bar"), | ||||
|     "/foo/bar", | ||||
|     "join /tmp,/foo,bar" | ||||
|   ); | ||||
| 
 | ||||
|   info("Testing the presence of ospath.jsm"); | ||||
|   let scope; | ||||
|   try { | ||||
|     scope = ChromeUtils.import("resource://gre/modules/osfile/ospath.jsm"); | ||||
|   } catch (ex) { | ||||
|     // Can't load ospath
 | ||||
|   } | ||||
|   Assert.ok(!!scope.basename); | ||||
| } | ||||
|  | @ -1,83 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| const { AppConstants } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/AppConstants.sys.mjs" | ||||
| ); | ||||
| const { ctypes } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/ctypes.sys.mjs" | ||||
| ); | ||||
| const { makeFakeAppDir } = ChromeUtils.importESModule( | ||||
|   "resource://testing-common/AppData.sys.mjs" | ||||
| ); | ||||
| 
 | ||||
| function compare_paths(ospath, key) { | ||||
|   let file; | ||||
|   try { | ||||
|     file = Services.dirsvc.get(key, Ci.nsIFile); | ||||
|   } catch (ex) {} | ||||
| 
 | ||||
|   if (file) { | ||||
|     Assert.ok(!!ospath); | ||||
|     Assert.equal(ospath, file.path); | ||||
|   } else { | ||||
|     info( | ||||
|       "WARNING: " + key + " is not defined. Test may not be testing anything!" | ||||
|     ); | ||||
|     Assert.ok(!ospath); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Test simple paths
 | ||||
| add_task(async function test_simple_paths() { | ||||
|   Assert.ok(!!OS.Constants.Path.tmpDir); | ||||
|   compare_paths(OS.Constants.Path.tmpDir, "TmpD"); | ||||
| }); | ||||
| 
 | ||||
| // Some path constants aren't set up until the profile is available. This
 | ||||
| // test verifies that behavior.
 | ||||
| add_task(async function test_before_after_profile() { | ||||
|   // On Android the profile is initialized during xpcshell init, so this test
 | ||||
|   // will fail.
 | ||||
|   if (AppConstants.platform != "android") { | ||||
|     Assert.equal(null, OS.Constants.Path.profileDir); | ||||
|     Assert.equal(null, OS.Constants.Path.localProfileDir); | ||||
|     Assert.equal(null, OS.Constants.Path.userApplicationDataDir); | ||||
|   } | ||||
| 
 | ||||
|   do_get_profile(); | ||||
|   Assert.ok(!!OS.Constants.Path.profileDir); | ||||
|   Assert.ok(!!OS.Constants.Path.localProfileDir); | ||||
| 
 | ||||
|   // UAppData is still null because the xpcshell profile doesn't set it up.
 | ||||
|   // This test is mostly here to fail in case behavior of do_get_profile() ever
 | ||||
|   // changes. We want to know if our assumptions no longer hold!
 | ||||
|   Assert.equal(null, OS.Constants.Path.userApplicationDataDir); | ||||
| 
 | ||||
|   await makeFakeAppDir(); | ||||
|   Assert.ok(!!OS.Constants.Path.userApplicationDataDir); | ||||
| 
 | ||||
|   // FUTURE: verify AppData too (bug 964291).
 | ||||
| }); | ||||
| 
 | ||||
| // Test presence of paths that only exist on Desktop platforms
 | ||||
| add_task(async function test_desktop_paths() { | ||||
|   if (OS.Constants.Sys.Name == "Android") { | ||||
|     return; | ||||
|   } | ||||
|   Assert.ok(!!OS.Constants.Path.homeDir); | ||||
| 
 | ||||
|   compare_paths(OS.Constants.Path.homeDir, "Home"); | ||||
|   compare_paths(OS.Constants.Path.userApplicationDataDir, "UAppData"); | ||||
| 
 | ||||
|   compare_paths(OS.Constants.Path.macUserLibDir, "ULibDir"); | ||||
| }); | ||||
| 
 | ||||
| // Open libxul
 | ||||
| add_task(async function test_libxul() { | ||||
|   ctypes.open(OS.Constants.Path.libxul); | ||||
|   info("Linked to libxul"); | ||||
| }); | ||||
|  | @ -1,34 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| // Check if Scheduler.queue returned by OS.File.queue is resolved initially.
 | ||||
| add_task(async function check_init() { | ||||
|   await OS.File.queue; | ||||
|   info("Function resolved"); | ||||
| }); | ||||
| 
 | ||||
| // Check if Scheduler.queue returned by OS.File.queue is resolved
 | ||||
| // after an operation is successful.
 | ||||
| add_task(async function check_success() { | ||||
|   info("Attempting to open a file correctly"); | ||||
|   await OS.File.open(OS.Path.join(do_get_cwd().path, "test_queue.js")); | ||||
|   info("File opened correctly"); | ||||
|   await OS.File.queue; | ||||
|   info("Function resolved"); | ||||
| }); | ||||
| 
 | ||||
| // Check if Scheduler.queue returned by OS.File.queue is resolved
 | ||||
| // after an operation fails.
 | ||||
| add_task(async function check_failure() { | ||||
|   let exception; | ||||
|   try { | ||||
|     info("Attempting to open a non existing file"); | ||||
|     await OS.File.open(OS.Path.join(".", "Bigfoot")); | ||||
|   } catch (err) { | ||||
|     exception = err; | ||||
|     await OS.File.queue; | ||||
|   } | ||||
|   Assert.ok(exception != null); | ||||
|   info("Function resolved"); | ||||
| }); | ||||
|  | @ -1,119 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| var SHARED_PATH; | ||||
| 
 | ||||
| var EXISTING_FILE = do_get_file("xpcshell.ini").path; | ||||
| 
 | ||||
| add_task(async function init() { | ||||
|   do_get_profile(); | ||||
|   SHARED_PATH = OS.Path.join( | ||||
|     OS.Constants.Path.profileDir, | ||||
|     "test_osfile_read.tmp" | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| // Check that OS.File.read() is executed after the previous operation
 | ||||
| add_test_pair(async function ordering() { | ||||
|   let string1 = "Initial state " + Math.random(); | ||||
|   let string2 = "After writing " + Math.random(); | ||||
|   await OS.File.writeAtomic(SHARED_PATH, string1); | ||||
|   OS.File.writeAtomic(SHARED_PATH, string2); | ||||
|   let string3 = await OS.File.read(SHARED_PATH, { encoding: "utf-8" }); | ||||
|   Assert.equal(string3, string2); | ||||
| }); | ||||
| 
 | ||||
| add_test_pair(async function read_write_all() { | ||||
|   let DEST_PATH = SHARED_PATH + Math.random(); | ||||
|   let TMP_PATH = DEST_PATH + ".tmp"; | ||||
| 
 | ||||
|   let test_with_options = function(options, suffix) { | ||||
|     return (async function() { | ||||
|       info( | ||||
|         "Running test read_write_all with options " + JSON.stringify(options) | ||||
|       ); | ||||
|       let TEST = "read_write_all " + suffix; | ||||
| 
 | ||||
|       let optionsBackup = JSON.parse(JSON.stringify(options)); | ||||
| 
 | ||||
|       // Check that read + writeAtomic performs a correct copy
 | ||||
|       let currentDir = await OS.File.getCurrentDirectory(); | ||||
|       let pathSource = OS.Path.join(currentDir, EXISTING_FILE); | ||||
|       let contents = await OS.File.read(pathSource); | ||||
|       Assert.ok(!!contents); // Content is not empty
 | ||||
|       let bytesRead = contents.byteLength; | ||||
| 
 | ||||
|       let bytesWritten = await OS.File.writeAtomic( | ||||
|         DEST_PATH, | ||||
|         contents, | ||||
|         options | ||||
|       ); | ||||
|       Assert.equal(bytesRead, bytesWritten); // Correct number of bytes written
 | ||||
| 
 | ||||
|       // Check that options are not altered
 | ||||
|       Assert.equal(JSON.stringify(options), JSON.stringify(optionsBackup)); | ||||
|       await reference_compare_files(pathSource, DEST_PATH, TEST); | ||||
| 
 | ||||
|       // Check that temporary file was removed or never created exist
 | ||||
|       Assert.ok(!new FileUtils.File(TMP_PATH).exists()); | ||||
| 
 | ||||
|       // Check that writeAtomic fails if noOverwrite is true and the destination
 | ||||
|       // file already exists!
 | ||||
|       contents = new Uint8Array(300); | ||||
|       let view = new Uint8Array(contents.buffer, 10, 200); | ||||
|       try { | ||||
|         let opt = JSON.parse(JSON.stringify(options)); | ||||
|         opt.noOverwrite = true; | ||||
|         await OS.File.writeAtomic(DEST_PATH, view, opt); | ||||
|         do_throw( | ||||
|           "With noOverwrite, writeAtomic should have refused to overwrite file (" + | ||||
|             suffix + | ||||
|             ")" | ||||
|         ); | ||||
|       } catch (err) { | ||||
|         if (err instanceof OS.File.Error && err.becauseExists) { | ||||
|           info( | ||||
|             "With noOverwrite, writeAtomic correctly failed (" + suffix + ")" | ||||
|           ); | ||||
|         } else { | ||||
|           throw err; | ||||
|         } | ||||
|       } | ||||
|       await reference_compare_files(pathSource, DEST_PATH, TEST); | ||||
| 
 | ||||
|       // Check that temporary file was removed or never created
 | ||||
|       Assert.ok(!new FileUtils.File(TMP_PATH).exists()); | ||||
| 
 | ||||
|       // Now write a subset
 | ||||
|       let START = 10; | ||||
|       let LENGTH = 100; | ||||
|       contents = new Uint8Array(300); | ||||
|       for (let i = 0; i < contents.byteLength; i++) { | ||||
|         contents[i] = i % 256; | ||||
|       } | ||||
|       view = new Uint8Array(contents.buffer, START, LENGTH); | ||||
|       bytesWritten = await OS.File.writeAtomic(DEST_PATH, view, options); | ||||
|       Assert.equal(bytesWritten, LENGTH); | ||||
| 
 | ||||
|       let array2 = await OS.File.read(DEST_PATH); | ||||
|       Assert.equal(LENGTH, array2.length); | ||||
|       for (let j = 0; j < LENGTH; j++) { | ||||
|         Assert.equal(array2[j], (j + START) % 256); | ||||
|       } | ||||
| 
 | ||||
|       // Cleanup.
 | ||||
|       await OS.File.remove(DEST_PATH); | ||||
|       await OS.File.remove(TMP_PATH); | ||||
|     })(); | ||||
|   }; | ||||
| 
 | ||||
|   await test_with_options({ tmpPath: TMP_PATH }, "Renaming, not flushing"); | ||||
|   await test_with_options( | ||||
|     { tmpPath: TMP_PATH, flush: true }, | ||||
|     "Renaming, flushing" | ||||
|   ); | ||||
|   await test_with_options({}, "Not renaming, not flushing"); | ||||
|   await test_with_options({ flush: true }, "Not renaming, flushing"); | ||||
| }); | ||||
|  | @ -1,60 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| registerCleanupFunction(function() { | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", false); | ||||
| }); | ||||
| 
 | ||||
| function run_test() { | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", true); | ||||
|   run_next_test(); | ||||
| } | ||||
| 
 | ||||
| add_task(async function test_ignoreAbsent() { | ||||
|   let absent_file_name = "test_osfile_front_absent.tmp"; | ||||
| 
 | ||||
|   // Removing absent files should throw if "ignoreAbsent" is true.
 | ||||
|   await Assert.rejects( | ||||
|     OS.File.remove(absent_file_name, { ignoreAbsent: false }), | ||||
|     err => err.operation == "remove", | ||||
|     "OS.File.remove throws if there is no such file." | ||||
|   ); | ||||
| 
 | ||||
|   // Removing absent files should not throw if "ignoreAbsent" is true or not
 | ||||
|   // defined.
 | ||||
|   let exception = null; | ||||
|   try { | ||||
|     await OS.File.remove(absent_file_name, { ignoreAbsent: true }); | ||||
|     await OS.File.remove(absent_file_name); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
|   Assert.ok(!exception, "OS.File.remove should not throw when not requested."); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function test_ignoreAbsent_directory_missing() { | ||||
|   let absent_file_name = OS.Path.join("absent_parent", "test.tmp"); | ||||
| 
 | ||||
|   // Removing absent files should throw if "ignoreAbsent" is true.
 | ||||
|   await Assert.rejects( | ||||
|     OS.File.remove(absent_file_name, { ignoreAbsent: false }), | ||||
|     err => err.operation == "remove", | ||||
|     "OS.File.remove throws if there is no such file." | ||||
|   ); | ||||
| 
 | ||||
|   // Removing files from absent directories should not throw if "ignoreAbsent"
 | ||||
|   // is true or not defined.
 | ||||
|   let exception = null; | ||||
|   try { | ||||
|     await OS.File.remove(absent_file_name, { ignoreAbsent: true }); | ||||
|     await OS.File.remove(absent_file_name); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
|   Assert.ok(!exception, "OS.File.remove should not throw when not requested."); | ||||
| }); | ||||
|  | @ -1,177 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| registerCleanupFunction(function() { | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", false); | ||||
| }); | ||||
| 
 | ||||
| function run_test() { | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", true); | ||||
| 
 | ||||
|   run_next_test(); | ||||
| } | ||||
| 
 | ||||
| add_task(async function() { | ||||
|   // Set up profile. We create the directory in the profile, because the profile
 | ||||
|   // is removed after every test run.
 | ||||
|   do_get_profile(); | ||||
| 
 | ||||
|   let file = OS.Path.join(OS.Constants.Path.profileDir, "file"); | ||||
|   let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory"); | ||||
|   let file1 = OS.Path.join(dir, "file1"); | ||||
|   let file2 = OS.Path.join(dir, "file2"); | ||||
|   let subDir = OS.Path.join(dir, "subdir"); | ||||
|   let fileInSubDir = OS.Path.join(subDir, "file"); | ||||
| 
 | ||||
|   // Sanity checking for the test
 | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| 
 | ||||
|   // Remove non-existent directory
 | ||||
|   let exception = null; | ||||
|   try { | ||||
|     await OS.File.removeDir(dir, { ignoreAbsent: false }); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
| 
 | ||||
|   Assert.ok(!!exception); | ||||
|   Assert.ok(exception instanceof OS.File.Error); | ||||
| 
 | ||||
|   // Remove non-existent directory with ignoreAbsent
 | ||||
|   await OS.File.removeDir(dir, { ignoreAbsent: true }); | ||||
|   await OS.File.removeDir(dir); | ||||
| 
 | ||||
|   // Remove file with ignoreAbsent: false
 | ||||
|   await OS.File.writeAtomic(file, "content", { tmpPath: file + ".tmp" }); | ||||
|   exception = null; | ||||
|   try { | ||||
|     await OS.File.removeDir(file, { ignoreAbsent: false }); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
| 
 | ||||
|   Assert.ok(!!exception); | ||||
|   Assert.ok(exception instanceof OS.File.Error); | ||||
| 
 | ||||
|   // Remove empty directory
 | ||||
|   await OS.File.makeDir(dir); | ||||
|   await OS.File.removeDir(dir); | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| 
 | ||||
|   // Remove directory that contains one file
 | ||||
|   await OS.File.makeDir(dir); | ||||
|   await OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" }); | ||||
|   await OS.File.removeDir(dir); | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| 
 | ||||
|   // Remove directory that contains multiple files
 | ||||
|   await OS.File.makeDir(dir); | ||||
|   await OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" }); | ||||
|   await OS.File.writeAtomic(file2, "content", { tmpPath: file2 + ".tmp" }); | ||||
|   await OS.File.removeDir(dir); | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| 
 | ||||
|   // Remove directory that contains a file and a directory
 | ||||
|   await OS.File.makeDir(dir); | ||||
|   await OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" }); | ||||
|   await OS.File.makeDir(subDir); | ||||
|   await OS.File.writeAtomic(fileInSubDir, "content", { | ||||
|     tmpPath: fileInSubDir + ".tmp", | ||||
|   }); | ||||
|   await OS.File.removeDir(dir); | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function test_unix_symlink() { | ||||
|   // Windows does not implement OS.File.unixSymLink()
 | ||||
|   if (OS.Constants.Win) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // Android / B2G file systems typically don't support symlinks.
 | ||||
|   if (OS.Constants.Sys.Name == "Android") { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   let file = OS.Path.join(OS.Constants.Path.profileDir, "file"); | ||||
|   let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory"); | ||||
|   let file1 = OS.Path.join(dir, "file1"); | ||||
| 
 | ||||
|   // This test will create the following directory structure:
 | ||||
|   // <profileDir>/file                             (regular file)
 | ||||
|   // <profileDir>/file.link => file                (symlink)
 | ||||
|   // <profileDir>/directory                        (directory)
 | ||||
|   // <profileDir>/linkdir => directory             (directory)
 | ||||
|   // <profileDir>/directory/file1                  (regular file)
 | ||||
|   // <profileDir>/directory3                       (directory)
 | ||||
|   // <profileDir>/directory3/file3                 (directory)
 | ||||
|   // <profileDir>/directory/link2 => ../directory3 (regular file)
 | ||||
| 
 | ||||
|   // Sanity checking for the test
 | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| 
 | ||||
|   await OS.File.writeAtomic(file, "content", { tmpPath: file + ".tmp" }); | ||||
|   Assert.ok(await OS.File.exists(file)); | ||||
|   let info = await OS.File.stat(file, { unixNoFollowingLinks: true }); | ||||
|   Assert.ok(!info.isDir); | ||||
|   Assert.ok(!info.isSymLink); | ||||
| 
 | ||||
|   await OS.File.unixSymLink(file, file + ".link"); | ||||
|   Assert.ok(await OS.File.exists(file + ".link")); | ||||
|   info = await OS.File.stat(file + ".link", { unixNoFollowingLinks: true }); | ||||
|   Assert.ok(!info.isDir); | ||||
|   Assert.ok(info.isSymLink); | ||||
|   info = await OS.File.stat(file + ".link"); | ||||
|   Assert.ok(!info.isDir); | ||||
|   Assert.ok(!info.isSymLink); | ||||
|   await OS.File.remove(file + ".link"); | ||||
|   Assert.equal(false, await OS.File.exists(file + ".link")); | ||||
| 
 | ||||
|   await OS.File.makeDir(dir); | ||||
|   Assert.ok(await OS.File.exists(dir)); | ||||
|   info = await OS.File.stat(dir, { unixNoFollowingLinks: true }); | ||||
|   Assert.ok(info.isDir); | ||||
|   Assert.ok(!info.isSymLink); | ||||
| 
 | ||||
|   let link = OS.Path.join(OS.Constants.Path.profileDir, "linkdir"); | ||||
| 
 | ||||
|   await OS.File.unixSymLink(dir, link); | ||||
|   Assert.ok(await OS.File.exists(link)); | ||||
|   info = await OS.File.stat(link, { unixNoFollowingLinks: true }); | ||||
|   Assert.ok(!info.isDir); | ||||
|   Assert.ok(info.isSymLink); | ||||
|   info = await OS.File.stat(link); | ||||
|   Assert.ok(info.isDir); | ||||
|   Assert.ok(!info.isSymLink); | ||||
| 
 | ||||
|   let dir3 = OS.Path.join(OS.Constants.Path.profileDir, "directory3"); | ||||
|   let file3 = OS.Path.join(dir3, "file3"); | ||||
|   let link2 = OS.Path.join(dir, "link2"); | ||||
| 
 | ||||
|   await OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" }); | ||||
|   Assert.ok(await OS.File.exists(file1)); | ||||
|   await OS.File.makeDir(dir3); | ||||
|   Assert.ok(await OS.File.exists(dir3)); | ||||
|   await OS.File.writeAtomic(file3, "content", { tmpPath: file3 + ".tmp" }); | ||||
|   Assert.ok(await OS.File.exists(file3)); | ||||
|   await OS.File.unixSymLink("../directory3", link2); | ||||
|   Assert.ok(await OS.File.exists(link2)); | ||||
| 
 | ||||
|   await OS.File.removeDir(link); | ||||
|   Assert.equal(false, await OS.File.exists(link)); | ||||
|   Assert.ok(await OS.File.exists(file1)); | ||||
|   await OS.File.removeDir(dir); | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
|   Assert.ok(await OS.File.exists(file3)); | ||||
|   await OS.File.removeDir(dir3); | ||||
|   Assert.equal(false, await OS.File.exists(dir3)); | ||||
| 
 | ||||
|   // This task will be executed only on Unix-like systems.
 | ||||
|   // Please do not add tests independent to operating systems here
 | ||||
|   // or implement symlink() on Windows.
 | ||||
| }); | ||||
|  | @ -1,54 +0,0 @@ | |||
| /* 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/. */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| registerCleanupFunction(function() { | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", false); | ||||
| }); | ||||
| 
 | ||||
| function run_test() { | ||||
|   Services.prefs.setBoolPref("toolkit.osfile.log", true); | ||||
| 
 | ||||
|   run_next_test(); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Test OS.File.removeEmptyDir | ||||
|  */ | ||||
| add_task(async function() { | ||||
|   // Set up profile. We create the directory in the profile, because the profile
 | ||||
|   // is removed after every test run.
 | ||||
|   do_get_profile(); | ||||
| 
 | ||||
|   let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory"); | ||||
| 
 | ||||
|   // Sanity checking for the test
 | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| 
 | ||||
|   // Remove non-existent directory
 | ||||
|   await OS.File.removeEmptyDir(dir); | ||||
| 
 | ||||
|   // Remove non-existent directory with ignoreAbsent
 | ||||
|   await OS.File.removeEmptyDir(dir, { ignoreAbsent: true }); | ||||
| 
 | ||||
|   // Remove non-existent directory with ignoreAbsent false
 | ||||
|   let exception = null; | ||||
|   try { | ||||
|     await OS.File.removeEmptyDir(dir, { ignoreAbsent: false }); | ||||
|   } catch (ex) { | ||||
|     exception = ex; | ||||
|   } | ||||
| 
 | ||||
|   Assert.ok(!!exception); | ||||
|   Assert.ok(exception instanceof OS.File.Error); | ||||
|   Assert.ok(exception.becauseNoSuchFile); | ||||
| 
 | ||||
|   // Remove empty directory
 | ||||
|   await OS.File.makeDir(dir); | ||||
|   await OS.File.removeEmptyDir(dir); | ||||
|   Assert.equal(false, await OS.File.exists(dir)); | ||||
| }); | ||||
|  | @ -1,102 +0,0 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| var Path = OS.Constants.Path; | ||||
| 
 | ||||
| add_task(async function init() { | ||||
|   do_get_profile(); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function reset_before_launching() { | ||||
|   info("Reset without launching OS.File, it shouldn't break"); | ||||
|   await OS.File.resetWorker(); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function transparent_reset() { | ||||
|   for (let i = 1; i < 3; ++i) { | ||||
|     info( | ||||
|       "Do stome stuff before and after " + | ||||
|         i + | ||||
|         " reset(s), " + | ||||
|         "it shouldn't break" | ||||
|     ); | ||||
|     let CONTENT = "some content " + i; | ||||
|     let path = OS.Path.join(Path.profileDir, "tmp"); | ||||
|     await OS.File.writeAtomic(path, CONTENT); | ||||
|     for (let j = 0; j < i; ++j) { | ||||
|       await OS.File.resetWorker(); | ||||
|     } | ||||
|     let data = await OS.File.read(path); | ||||
|     let string = new TextDecoder().decode(data); | ||||
|     Assert.equal(string, CONTENT); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| add_task(async function file_open_cannot_reset() { | ||||
|   let TEST_FILE = OS.Path.join(Path.profileDir, "tmp-" + Math.random()); | ||||
|   info( | ||||
|     "Leaking file descriptor " + TEST_FILE + ", we shouldn't be able to reset" | ||||
|   ); | ||||
|   let openedFile = await OS.File.open(TEST_FILE, { create: true }); | ||||
|   let thrown = false; | ||||
|   try { | ||||
|     await OS.File.resetWorker(); | ||||
|   } catch (ex) { | ||||
|     if (ex.message.includes(OS.Path.basename(TEST_FILE))) { | ||||
|       thrown = true; | ||||
|     } else { | ||||
|       throw ex; | ||||
|     } | ||||
|   } | ||||
|   Assert.ok(thrown); | ||||
| 
 | ||||
|   info("Closing the file, we should now be able to reset"); | ||||
|   await openedFile.close(); | ||||
|   await OS.File.resetWorker(); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function dir_open_cannot_reset() { | ||||
|   let TEST_DIR = await OS.File.getCurrentDirectory(); | ||||
|   info("Leaking directory " + TEST_DIR + ", we shouldn't be able to reset"); | ||||
|   let iterator = new OS.File.DirectoryIterator(TEST_DIR); | ||||
|   let thrown = false; | ||||
|   try { | ||||
|     await OS.File.resetWorker(); | ||||
|   } catch (ex) { | ||||
|     if (ex.message.includes(OS.Path.basename(TEST_DIR))) { | ||||
|       thrown = true; | ||||
|     } else { | ||||
|       throw ex; | ||||
|     } | ||||
|   } | ||||
|   Assert.ok(thrown); | ||||
| 
 | ||||
|   info("Closing the directory, we should now be able to reset"); | ||||
|   await iterator.close(); | ||||
|   await OS.File.resetWorker(); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function race_against_itself() { | ||||
|   info("Attempt to get resetWorker() to race against itself"); | ||||
|   // Arbitrary operation, just to wake up the worker
 | ||||
|   try { | ||||
|     await OS.File.read("/foo"); | ||||
|   } catch (ex) {} | ||||
| 
 | ||||
|   let all = []; | ||||
|   for (let i = 0; i < 100; ++i) { | ||||
|     all.push(OS.File.resetWorker()); | ||||
|   } | ||||
| 
 | ||||
|   await Promise.all(all); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function finish_with_a_reset() { | ||||
|   info("Reset without waiting for the result"); | ||||
|   // Arbitrary operation, just to wake up the worker
 | ||||
|   try { | ||||
|     await OS.File.read("/foo"); | ||||
|   } catch (ex) {} | ||||
|   // Now reset
 | ||||
|   /* don't yield*/ OS.File.resetWorker(); | ||||
| }); | ||||
|  | @ -1,103 +0,0 @@ | |||
| const { PromiseUtils } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/PromiseUtils.sys.mjs" | ||||
| ); | ||||
| 
 | ||||
| add_task(function init() { | ||||
|   do_get_profile(); | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Test logging of file descriptors leaks. | ||||
|  */ | ||||
| add_task(async function system_shutdown() { | ||||
|   // Test that unclosed files cause warnings
 | ||||
|   // Test that unclosed directories cause warnings
 | ||||
|   // Test that closed files do not cause warnings
 | ||||
|   // Test that closed directories do not cause warnings
 | ||||
|   function testLeaksOf(resource, topic) { | ||||
|     return (async function() { | ||||
|       let deferred = PromiseUtils.defer(); | ||||
| 
 | ||||
|       // Register observer
 | ||||
|       Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true); | ||||
|       Services.prefs.setBoolPref("toolkit.osfile.log", true); | ||||
|       Services.prefs.setBoolPref("toolkit.osfile.log.redirect", true); | ||||
|       Services.prefs.setCharPref( | ||||
|         "toolkit.osfile.test.shutdown.observer", | ||||
|         topic | ||||
|       ); | ||||
| 
 | ||||
|       let observer = function(aMessage) { | ||||
|         try { | ||||
|           info("Got message: " + aMessage); | ||||
|           if (!(aMessage instanceof Ci.nsIConsoleMessage)) { | ||||
|             return; | ||||
|           } | ||||
|           let message = aMessage.message; | ||||
|           info("Got message: " + message); | ||||
|           if (!message.includes("TEST OS Controller WARNING")) { | ||||
|             return; | ||||
|           } | ||||
|           info( | ||||
|             "Got message: " + message + ", looking for resource " + resource | ||||
|           ); | ||||
|           if (!message.includes(resource)) { | ||||
|             return; | ||||
|           } | ||||
|           info("Resource: " + resource + " found"); | ||||
|           executeSoon(deferred.resolve); | ||||
|         } catch (ex) { | ||||
|           executeSoon(function() { | ||||
|             deferred.reject(ex); | ||||
|           }); | ||||
|         } | ||||
|       }; | ||||
|       Services.console.registerListener(observer); | ||||
|       Services.obs.notifyObservers(null, topic); | ||||
|       do_timeout(1000, function() { | ||||
|         info("Timeout while waiting for resource: " + resource); | ||||
|         deferred.reject("timeout"); | ||||
|       }); | ||||
| 
 | ||||
|       let resolved = false; | ||||
|       try { | ||||
|         await deferred.promise; | ||||
|         resolved = true; | ||||
|       } catch (ex) { | ||||
|         if (ex == "timeout") { | ||||
|           resolved = false; | ||||
|         } else { | ||||
|           throw ex; | ||||
|         } | ||||
|       } | ||||
|       Services.console.unregisterListener(observer); | ||||
|       Services.prefs.clearUserPref("toolkit.osfile.log"); | ||||
|       Services.prefs.clearUserPref("toolkit.osfile.log.redirect"); | ||||
|       Services.prefs.clearUserPref("toolkit.osfile.test.shutdown.observer"); | ||||
|       Services.prefs.clearUserPref("toolkit.async_shutdown.testing"); | ||||
| 
 | ||||
|       return resolved; | ||||
|     })(); | ||||
|   } | ||||
| 
 | ||||
|   let TEST_DIR = OS.Path.join(await OS.File.getCurrentDirectory(), ".."); | ||||
|   info("Testing for leaks of directory iterator " + TEST_DIR); | ||||
|   let iterator = new OS.File.DirectoryIterator(TEST_DIR); | ||||
|   info("At this stage, we leak the directory"); | ||||
|   Assert.ok(await testLeaksOf(TEST_DIR, "test.shutdown.dir.leak")); | ||||
|   await iterator.close(); | ||||
|   info("At this stage, we don't leak the directory anymore"); | ||||
|   Assert.equal(false, await testLeaksOf(TEST_DIR, "test.shutdown.dir.noleak")); | ||||
| 
 | ||||
|   let TEST_FILE = OS.Path.join(OS.Constants.Path.profileDir, "test"); | ||||
|   info("Testing for leaks of file descriptor: " + TEST_FILE); | ||||
|   let openedFile = await OS.File.open(TEST_FILE, { create: true }); | ||||
|   info("At this stage, we leak the file"); | ||||
|   Assert.ok(await testLeaksOf(TEST_FILE, "test.shutdown.file.leak")); | ||||
|   await openedFile.close(); | ||||
|   info("At this stage, we don't leak the file anymore"); | ||||
|   Assert.equal( | ||||
|     false, | ||||
|     await testLeaksOf(TEST_FILE, "test.shutdown.file.leak.2") | ||||
|   ); | ||||
| }); | ||||
|  | @ -1,61 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| var { | ||||
|   OS: { File, Path, Constants }, | ||||
| } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| // Ensure that we have a profile but that the OS.File worker is not launched
 | ||||
| add_task(async function init() { | ||||
|   do_get_profile(); | ||||
|   await File.resetWorker(); | ||||
| }); | ||||
| 
 | ||||
| function getCount(histogram) { | ||||
|   if (histogram == null) { | ||||
|     return 0; | ||||
|   } | ||||
| 
 | ||||
|   let total = 0; | ||||
|   for (let i of Object.values(histogram.values)) { | ||||
|     total += i; | ||||
|   } | ||||
|   return total; | ||||
| } | ||||
| 
 | ||||
| // Ensure that launching the OS.File worker adds data to the relevant
 | ||||
| // histograms
 | ||||
| add_task(async function test_startup() { | ||||
|   let LAUNCH = "OSFILE_WORKER_LAUNCH_MS"; | ||||
|   let READY = "OSFILE_WORKER_READY_MS"; | ||||
| 
 | ||||
|   let before = Services.telemetry.getSnapshotForHistograms("main", false) | ||||
|     .parent; | ||||
| 
 | ||||
|   // Launch the OS.File worker
 | ||||
|   await File.getCurrentDirectory(); | ||||
| 
 | ||||
|   let after = Services.telemetry.getSnapshotForHistograms("main", false).parent; | ||||
| 
 | ||||
|   info("Ensuring that we have recorded measures for histograms"); | ||||
|   Assert.equal(getCount(after[LAUNCH]), getCount(before[LAUNCH]) + 1); | ||||
|   Assert.equal(getCount(after[READY]), getCount(before[READY]) + 1); | ||||
| 
 | ||||
|   info("Ensuring that launh <= ready"); | ||||
|   Assert.ok(after[LAUNCH].sum <= after[READY].sum); | ||||
| }); | ||||
| 
 | ||||
| // Ensure that calling writeAtomic adds data to the relevant histograms
 | ||||
| add_task(async function test_writeAtomic() { | ||||
|   let LABEL = "OSFILE_WRITEATOMIC_JANK_MS"; | ||||
| 
 | ||||
|   let before = Services.telemetry.getSnapshotForHistograms("main", false) | ||||
|     .parent; | ||||
| 
 | ||||
|   // Perform a write.
 | ||||
|   let path = Path.join(Constants.Path.profileDir, "test_osfile_telemetry.tmp"); | ||||
|   await File.writeAtomic(path, LABEL, { tmpPath: path + ".tmp" }); | ||||
| 
 | ||||
|   let after = Services.telemetry.getSnapshotForHistograms("main", false).parent; | ||||
| 
 | ||||
|   Assert.equal(getCount(after[LABEL]), getCount(before[LABEL]) + 1); | ||||
| }); | ||||
|  | @ -1,87 +0,0 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); | ||||
| 
 | ||||
| function run_test() { | ||||
|   do_get_profile(); | ||||
|   run_next_test(); | ||||
| } | ||||
| 
 | ||||
| function testFiles(filename) { | ||||
|   return (async function() { | ||||
|     const MAX_TRIES = 10; | ||||
|     let profileDir = OS.Constants.Path.profileDir; | ||||
|     let path = OS.Path.join(profileDir, filename); | ||||
| 
 | ||||
|     // Ensure that openUnique() uses the file name if there is no file with that name already.
 | ||||
|     let openedFile = await OS.File.openUnique(path); | ||||
|     info("\nCreate new file: " + openedFile.path); | ||||
|     await openedFile.file.close(); | ||||
|     let exists = await OS.File.exists(openedFile.path); | ||||
|     Assert.ok(exists); | ||||
|     Assert.equal(path, openedFile.path); | ||||
|     let fileInfo = await OS.File.stat(openedFile.path); | ||||
|     Assert.ok(fileInfo.size == 0); | ||||
| 
 | ||||
|     // Ensure that openUnique() creates a new file name using a HEX number, as the original name is already taken.
 | ||||
|     openedFile = await OS.File.openUnique(path); | ||||
|     info("\nCreate unique HEX file: " + openedFile.path); | ||||
|     await openedFile.file.close(); | ||||
|     exists = await OS.File.exists(openedFile.path); | ||||
|     Assert.ok(exists); | ||||
|     fileInfo = await OS.File.stat(openedFile.path); | ||||
|     Assert.ok(fileInfo.size == 0); | ||||
| 
 | ||||
|     // Ensure that openUnique() generates different file names each time, using the HEX number algorithm
 | ||||
|     let filenames = new Set(); | ||||
|     for (let i = 0; i < MAX_TRIES; i++) { | ||||
|       openedFile = await OS.File.openUnique(path); | ||||
|       await openedFile.file.close(); | ||||
|       filenames.add(openedFile.path); | ||||
|     } | ||||
| 
 | ||||
|     Assert.equal(filenames.size, MAX_TRIES); | ||||
| 
 | ||||
|     // Ensure that openUnique() creates a new human readable file name using, as the original name is already taken.
 | ||||
|     openedFile = await OS.File.openUnique(path, { humanReadable: true }); | ||||
|     info("\nCreate unique Human Readable file: " + openedFile.path); | ||||
|     await openedFile.file.close(); | ||||
|     exists = await OS.File.exists(openedFile.path); | ||||
|     Assert.ok(exists); | ||||
|     fileInfo = await OS.File.stat(openedFile.path); | ||||
|     Assert.ok(fileInfo.size == 0); | ||||
| 
 | ||||
|     // Ensure that openUnique() generates different human readable file names each time
 | ||||
|     filenames = new Set(); | ||||
|     for (let i = 0; i < MAX_TRIES; i++) { | ||||
|       openedFile = await OS.File.openUnique(path, { humanReadable: true }); | ||||
|       await openedFile.file.close(); | ||||
|       filenames.add(openedFile.path); | ||||
|     } | ||||
| 
 | ||||
|     Assert.equal(filenames.size, MAX_TRIES); | ||||
| 
 | ||||
|     let exn; | ||||
|     try { | ||||
|       for (let i = 0; i < 100; i++) { | ||||
|         openedFile = await OS.File.openUnique(path, { humanReadable: true }); | ||||
|         await openedFile.file.close(); | ||||
|       } | ||||
|     } catch (ex) { | ||||
|       exn = ex; | ||||
|     } | ||||
| 
 | ||||
|     info("Ensure that this raises the correct error"); | ||||
|     Assert.ok(!!exn); | ||||
|     Assert.ok(exn instanceof OS.File.Error); | ||||
|     Assert.ok(exn.becauseExists); | ||||
|   })(); | ||||
| } | ||||
| 
 | ||||
| add_task(async function test_unique() { | ||||
|   OS.Shared.DEBUG = true; | ||||
|   // Tests files with extension
 | ||||
|   await testFiles("dummy_unique_file.txt"); | ||||
|   // Tests files with no extension
 | ||||
|   await testFiles("dummy_unique_file_no_ext"); | ||||
| }); | ||||
|  | @ -1,48 +0,0 @@ | |||
| [DEFAULT] | ||||
| head = head.js | ||||
| 
 | ||||
| [test_compression.js] | ||||
| [test_constants.js] | ||||
| [test_duration.js] | ||||
| [test_exception.js] | ||||
| [test_file_URL_conversion.js] | ||||
| [test_logging.js] | ||||
| [test_makeDir.js] | ||||
| [test_open.js] | ||||
| [test_osfile_async.js] | ||||
| [test_osfile_async_append.js] | ||||
| [test_osfile_async_bytes.js] | ||||
| [test_osfile_async_copy.js] | ||||
| [test_osfile_async_flush.js] | ||||
| [test_osfile_async_largefiles.js] | ||||
| [test_osfile_async_setDates.js] | ||||
| # Unimplemented on Windows (bug 1022816). | ||||
| # Spurious failure on Android test farm due to non-POSIX behavior of | ||||
| # filesystem backing /mnt/sdcard (not worth trying to fix). | ||||
| [test_osfile_async_setPermissions.js] | ||||
| skip-if = os == "win" || os == "android" | ||||
| [test_osfile_closed.js] | ||||
| [test_osfile_error.js] | ||||
| [test_osfile_kill.js] | ||||
| # Windows test | ||||
| [test_osfile_win_async_setPermissions.js] | ||||
| skip-if = os != "win" | ||||
| [test_osfile_writeAtomic_backupTo_option.js] | ||||
| [test_osfile_writeAtomic_zerobytes.js] | ||||
| [test_osfile_writeAtomic_unicode_filename.js] | ||||
| [test_path.js] | ||||
| [test_path_constants.js] | ||||
| [test_queue.js] | ||||
| [test_read_write.js] | ||||
| requesttimeoutfactor = 4 | ||||
| [test_remove.js] | ||||
| [test_removeDir.js] | ||||
| requesttimeoutfactor = 4 | ||||
| [test_removeEmptyDir.js] | ||||
| [test_reset.js] | ||||
| [test_shutdown.js] | ||||
| [test_telemetry.js] | ||||
| # On Android, we use OS.File during xpcshell initialization, so the expected | ||||
| # telemetry cannot be observed. | ||||
| skip-if = toolkit == "android" | ||||
| [test_unique.js] | ||||
|  | @ -857,15 +857,6 @@ | |||
|   "resource://gre/modules/nsAsyncShutdown.jsm": "toolkit/components/asyncshutdown/nsAsyncShutdown.jsm", | ||||
|   "resource://gre/modules/nsCrashMonitor.jsm": "toolkit/components/crashmonitor/nsCrashMonitor.jsm", | ||||
|   "resource://gre/modules/nsFormAutoCompleteResult.jsm": "toolkit/components/satchel/nsFormAutoCompleteResult.jsm", | ||||
|   "resource://gre/modules/osfile.jsm": "toolkit/components/osfile/osfile.jsm", | ||||
|   "resource://gre/modules/osfile/osfile_async_front.jsm": "toolkit/components/osfile/modules/osfile_async_front.jsm", | ||||
|   "resource://gre/modules/osfile/osfile_native.jsm": "toolkit/components/osfile/modules/osfile_native.jsm", | ||||
|   "resource://gre/modules/osfile/osfile_shared_allthreads.jsm": "toolkit/components/osfile/modules/osfile_shared_allthreads.jsm", | ||||
|   "resource://gre/modules/osfile/osfile_unix_allthreads.jsm": "toolkit/components/osfile/modules/osfile_unix_allthreads.jsm", | ||||
|   "resource://gre/modules/osfile/osfile_win_allthreads.jsm": "toolkit/components/osfile/modules/osfile_win_allthreads.jsm", | ||||
|   "resource://gre/modules/osfile/ospath.jsm": "toolkit/components/osfile/modules/ospath.jsm", | ||||
|   "resource://gre/modules/osfile/ospath_unix.jsm": "toolkit/components/osfile/modules/ospath_unix.jsm", | ||||
|   "resource://gre/modules/osfile/ospath_win.jsm": "toolkit/components/osfile/modules/ospath_win.jsm", | ||||
|   "resource://gre/modules/pdfjs.js": "toolkit/components/pdfjs/pdfjs.js", | ||||
|   "resource://gre/modules/policies/WindowsGPOParser.jsm": "toolkit/components/enterprisepolicies/WindowsGPOParser.jsm", | ||||
|   "resource://gre/modules/policies/macOSPoliciesParser.jsm": "toolkit/components/enterprisepolicies/macOSPoliciesParser.jsm", | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
		Reference in a new issue
	
	 Barret Rennie
						Barret Rennie