mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
Backed out changeset 987d013caf11 (bug 1776480) for causing xpc failures in toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js CLOSED TREE
This commit is contained in:
parent
3a99b7feb9
commit
924826ccad
@ -263,6 +263,7 @@ 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,6 +310,7 @@ 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/**",
|
||||
@ -612,6 +613,13 @@ 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,6 +102,7 @@ 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,6 +71,12 @@ 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,6 +58,7 @@ 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
|
||||
|
@ -0,0 +1,13 @@
|
||||
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,12 +835,97 @@ 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,6 +8,9 @@
|
||||
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]
|
||||
|
||||
[test_constants.xhtml]
|
||||
support-files =
|
||||
worker_constants.js
|
||||
|
||||
[test_constants.xhtml]
|
||||
[test_pathutils.html]
|
||||
[test_pathutils_worker.xhtml]
|
||||
support-files =
|
||||
|
@ -15,6 +15,11 @@
|
||||
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(
|
||||
@ -335,6 +340,31 @@
|
||||
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,6 +15,11 @@
|
||||
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
|
||||
@ -285,6 +290,29 @@
|
||||
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,6 +16,21 @@
|
||||
|
||||
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");
|
||||
@ -43,6 +58,11 @@ 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,
|
||||
@ -59,9 +79,13 @@ 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).
|
||||
@ -99,6 +123,7 @@ 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,9 +15,13 @@ self.onmessage = function(msg) {
|
||||
self.onmessage = function(msgInner) {
|
||||
log("ignored message " + JSON.stringify(msgInner.data));
|
||||
};
|
||||
let { umask } = msg.data;
|
||||
let { isDebugBuild, 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);
|
||||
@ -41,6 +45,20 @@ 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(
|
||||
@ -50,3 +68,28 @@ 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"
|
||||
);
|
||||
}
|
||||
|
60
dom/webidl/NativeOSFileInternals.webidl
Normal file
60
dom/webidl/NativeOSFileInternals.webidl
Normal file
@ -0,0 +1,60 @@
|
||||
/* 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,6 +229,9 @@ 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")
|
||||
|
||||
@ -743,6 +746,7 @@ WEBIDL_FILES = [
|
||||
"MutationEvent.webidl",
|
||||
"MutationObserver.webidl",
|
||||
"NamedNodeMap.webidl",
|
||||
"NativeOSFileInternals.webidl",
|
||||
"NavigationPreloadManager.webidl",
|
||||
"Navigator.webidl",
|
||||
"NetErrorInfo.webidl",
|
||||
|
@ -28,6 +28,7 @@ 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,6 +624,9 @@ 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,6 +1564,7 @@
|
||||
"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,6 +386,7 @@
|
||||
"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,6 +388,7 @@
|
||||
"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,6 +74,12 @@ 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}',
|
||||
|
66
toolkit/components/lz4/lz4.cpp
Normal file
66
toolkit/components/lz4/lz4.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/* 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);
|
||||
}
|
188
toolkit/components/lz4/lz4.js
Normal file
188
toolkit/components/lz4/lz4.js
Normal file
@ -0,0 +1,188 @@
|
||||
/* 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,
|
||||
};
|
||||
}
|
76
toolkit/components/lz4/lz4_internal.js
Normal file
76
toolkit/components/lz4/lz4_internal.js
Normal file
@ -0,0 +1,76 @@
|
||||
/* 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;
|
||||
},
|
||||
};
|
||||
}
|
21
toolkit/components/lz4/moz.build
Normal file
21
toolkit/components/lz4/moz.build
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- 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"
|
@ -0,0 +1 @@
|
||||
content test_lz4 ./
|
BIN
toolkit/components/lz4/tests/xpcshell/data/compression.lz
Normal file
BIN
toolkit/components/lz4/tests/xpcshell/data/compression.lz
Normal file
Binary file not shown.
166
toolkit/components/lz4/tests/xpcshell/data/worker_lz4.js
Normal file
166
toolkit/components/lz4/tests/xpcshell/data/worker_lz4.js
Normal file
@ -0,0 +1,166 @@
|
||||
/* 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();
|
||||
}
|
35
toolkit/components/lz4/tests/xpcshell/test_lz4.js
Normal file
35
toolkit/components/lz4/tests/xpcshell/test_lz4.js
Normal file
@ -0,0 +1,35 @@
|
||||
/* 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");
|
||||
});
|
||||
});
|
41
toolkit/components/lz4/tests/xpcshell/test_lz4_sync.js
Normal file
41
toolkit/components/lz4/tests/xpcshell/test_lz4_sync.js
Normal file
@ -0,0 +1,41 @@
|
||||
/* 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));
|
||||
}
|
||||
});
|
10
toolkit/components/lz4/tests/xpcshell/xpcshell.ini
Normal file
10
toolkit/components/lz4/tests/xpcshell/xpcshell.ini
Normal file
@ -0,0 +1,10 @@
|
||||
[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,9 +46,11 @@ DIRS += [
|
||||
"httpsonlyerror",
|
||||
"jsoncpp/src/lib_json",
|
||||
"kvstore",
|
||||
"lz4",
|
||||
"mediasniffer",
|
||||
"mozintl",
|
||||
"mozprotocol",
|
||||
"osfile",
|
||||
"parentalcontrols",
|
||||
"passwordmgr",
|
||||
"pdfjs",
|
||||
|
1263
toolkit/components/osfile/NativeOSFileInternals.cpp
Normal file
1263
toolkit/components/osfile/NativeOSFileInternals.cpp
Normal file
File diff suppressed because it is too large
Load Diff
29
toolkit/components/osfile/NativeOSFileInternals.h
Normal file
29
toolkit/components/osfile/NativeOSFileInternals.h
Normal file
@ -0,0 +1,29 @@
|
||||
/* -*- 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__
|
29
toolkit/components/osfile/modules/moz.build
Normal file
29
toolkit/components/osfile/modules/moz.build
Normal file
@ -0,0 +1,29 @@
|
||||
# -*- 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",
|
||||
]
|
1570
toolkit/components/osfile/modules/osfile_async_front.jsm
Normal file
1570
toolkit/components/osfile/modules/osfile_async_front.jsm
Normal file
File diff suppressed because it is too large
Load Diff
450
toolkit/components/osfile/modules/osfile_async_worker.js
Normal file
450
toolkit/components/osfile/modules/osfile_async_worker.js
Normal file
@ -0,0 +1,450 @@
|
||||
/* 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);
|
126
toolkit/components/osfile/modules/osfile_native.jsm
Normal file
126
toolkit/components/osfile/modules/osfile_native.jsm
Normal file
@ -0,0 +1,126 @@
|
||||
/* 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));
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
1371
toolkit/components/osfile/modules/osfile_shared_allthreads.jsm
Normal file
1371
toolkit/components/osfile/modules/osfile_shared_allthreads.jsm
Normal file
File diff suppressed because it is too large
Load Diff
607
toolkit/components/osfile/modules/osfile_shared_front.js
Normal file
607
toolkit/components/osfile/modules/osfile_shared_front.js
Normal file
@ -0,0 +1,607 @@
|
||||
/* 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);
|
412
toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
Normal file
412
toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
Normal file
@ -0,0 +1,412 @@
|
||||
/* 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];
|
||||
}
|
||||
}
|
1051
toolkit/components/osfile/modules/osfile_unix_back.js
Normal file
1051
toolkit/components/osfile/modules/osfile_unix_back.js
Normal file
File diff suppressed because it is too large
Load Diff
1243
toolkit/components/osfile/modules/osfile_unix_front.js
Normal file
1243
toolkit/components/osfile/modules/osfile_unix_front.js
Normal file
File diff suppressed because it is too large
Load Diff
444
toolkit/components/osfile/modules/osfile_win_allthreads.jsm
Normal file
444
toolkit/components/osfile/modules/osfile_win_allthreads.jsm
Normal file
@ -0,0 +1,444 @@
|
||||
/* 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];
|
||||
}
|
||||
}
|
542
toolkit/components/osfile/modules/osfile_win_back.js
Normal file
542
toolkit/components/osfile/modules/osfile_win_back.js
Normal file
@ -0,0 +1,542 @@
|
||||
/* 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);
|
||||
}
|
1322
toolkit/components/osfile/modules/osfile_win_front.js
Normal file
1322
toolkit/components/osfile/modules/osfile_win_front.js
Normal file
File diff suppressed because it is too large
Load Diff
50
toolkit/components/osfile/modules/ospath.jsm
Normal file
50
toolkit/components/osfile/modules/ospath.jsm
Normal file
@ -0,0 +1,50 @@
|
||||
/* 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];
|
||||
}
|
||||
}
|
204
toolkit/components/osfile/modules/ospath_unix.jsm
Normal file
204
toolkit/components/osfile/modules/ospath_unix.jsm
Normal file
@ -0,0 +1,204 @@
|
||||
/* 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];
|
||||
}
|
||||
}
|
382
toolkit/components/osfile/modules/ospath_win.jsm
Normal file
382
toolkit/components/osfile/modules/ospath_win.jsm
Normal file
@ -0,0 +1,382 @@
|
||||
/* 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];
|
||||
}
|
||||
}
|
35
toolkit/components/osfile/moz.build
Normal file
35
toolkit/components/osfile/moz.build
Normal file
@ -0,0 +1,35 @@
|
||||
# -*- 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")
|
120
toolkit/components/osfile/nsINativeOSFileInternals.idl
Normal file
120
toolkit/components/osfile/nsINativeOSFileInternals.idl
Normal file
@ -0,0 +1,120 @@
|
||||
/* -*- 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"
|
||||
|
||||
%}
|
46
toolkit/components/osfile/osfile.jsm
Normal file
46
toolkit/components/osfile/osfile.jsm
Normal file
@ -0,0 +1,46 @@
|
||||
/* 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");
|
||||
}
|
15
toolkit/components/osfile/tests/mochi/chrome.ini
Normal file
15
toolkit/components/osfile/tests/mochi/chrome.ini
Normal file
@ -0,0 +1,15 @@
|
||||
[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
|
501
toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
Normal file
501
toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
Normal file
@ -0,0 +1,501 @@
|
||||
"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);
|
||||
})();
|
||||
});
|
@ -0,0 +1,21 @@
|
||||
<?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>
|
44
toolkit/components/osfile/tests/mochi/test_osfile_back.xhtml
Normal file
44
toolkit/components/osfile/tests/mochi/test_osfile_back.xhtml
Normal file
@ -0,0 +1,44 @@
|
||||
<?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>
|
@ -0,0 +1,88 @@
|
||||
<?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>
|
@ -0,0 +1,42 @@
|
||||
<?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>
|
@ -0,0 +1,205 @@
|
||||
/* 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();
|
||||
};
|
@ -0,0 +1,696 @@
|
||||
/* 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"
|
||||
);
|
||||
}
|
||||
}
|
257
toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js
Normal file
257
toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js
Normal file
@ -0,0 +1,257 @@
|
||||
/* 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");
|
||||
}
|
310
toolkit/components/osfile/tests/mochi/worker_test_osfile_win.js
Normal file
310
toolkit/components/osfile/tests/mochi/worker_test_osfile_win.js
Normal file
@ -0,0 +1,310 @@
|
||||
/* 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");
|
||||
}
|
7
toolkit/components/osfile/tests/xpcshell/.eslintrc.js
Normal file
7
toolkit/components/osfile/tests/xpcshell/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
"no-shadow": "off",
|
||||
},
|
||||
};
|
109
toolkit/components/osfile/tests/xpcshell/head.js
Normal file
109
toolkit/components/osfile/tests/xpcshell/head.js
Normal file
@ -0,0 +1,109 @@
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
}
|
106
toolkit/components/osfile/tests/xpcshell/test_compression.js
Normal file
106
toolkit/components/osfile/tests/xpcshell/test_compression.js
Normal file
@ -0,0 +1,106 @@
|
||||
/* 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();
|
||||
});
|
20
toolkit/components/osfile/tests/xpcshell/test_constants.js
Normal file
20
toolkit/components/osfile/tests/xpcshell/test_constants.js
Normal file
@ -0,0 +1,20 @@
|
||||
"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");
|
||||
}
|
||||
});
|
127
toolkit/components/osfile/tests/xpcshell/test_duration.js
Normal file
127
toolkit/components/osfile/tests/xpcshell/test_duration.js
Normal file
@ -0,0 +1,127 @@
|
||||
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();
|
||||
});
|
108
toolkit/components/osfile/tests/xpcshell/test_exception.js
Normal file
108
toolkit/components/osfile/tests/xpcshell/test_exception.js
Normal file
@ -0,0 +1,108 @@
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,119 @@
|
||||
/* 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);
|
||||
}
|
73
toolkit/components/osfile/tests/xpcshell/test_logging.js
Normal file
73
toolkit/components/osfile/tests/xpcshell/test_logging.js
Normal file
@ -0,0 +1,73 @@
|
||||
"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.
|
||||
}
|
137
toolkit/components/osfile/tests/xpcshell/test_makeDir.js
Normal file
137
toolkit/components/osfile/tests/xpcshell/test_makeDir.js
Normal file
@ -0,0 +1,137 @@
|
||||
/* 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));
|
||||
});
|
75
toolkit/components/osfile/tests/xpcshell/test_open.js
Normal file
75
toolkit/components/osfile/tests/xpcshell/test_open.js
Normal file
@ -0,0 +1,75 @@
|
||||
/* 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);
|
||||
}
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
"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);
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
"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();
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
"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);
|
@ -0,0 +1,109 @@
|
||||
"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);
|
@ -0,0 +1,31 @@
|
||||
"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);
|
@ -0,0 +1,137 @@
|
||||
/* 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();
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
"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);
|
||||
}
|
||||
});
|
@ -0,0 +1,102 @@
|
||||
/* 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);
|
||||
}
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
"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);
|
@ -0,0 +1,56 @@
|
||||
/* 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);
|
||||
});
|
97
toolkit/components/osfile/tests/xpcshell/test_osfile_kill.js
Normal file
97
toolkit/components/osfile/tests/xpcshell/test_osfile_kill.js
Normal file
@ -0,0 +1,97 @@
|
||||
"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!"
|
||||
);
|
||||
});
|
@ -0,0 +1,135 @@
|
||||
/* 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);
|
||||
}
|
||||
});
|
@ -0,0 +1,148 @@
|
||||
/* 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();
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
/* 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"
|
||||
);
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
/* 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.");
|
||||
});
|
187
toolkit/components/osfile/tests/xpcshell/test_path.js
Normal file
187
toolkit/components/osfile/tests/xpcshell/test_path.js
Normal file
@ -0,0 +1,187 @@
|
||||
/* 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);
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/* 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");
|
||||
});
|
34
toolkit/components/osfile/tests/xpcshell/test_queue.js
Normal file
34
toolkit/components/osfile/tests/xpcshell/test_queue.js
Normal file
@ -0,0 +1,34 @@
|
||||
"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");
|
||||
});
|
119
toolkit/components/osfile/tests/xpcshell/test_read_write.js
Normal file
119
toolkit/components/osfile/tests/xpcshell/test_read_write.js
Normal file
@ -0,0 +1,119 @@
|
||||
/* 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");
|
||||
});
|
60
toolkit/components/osfile/tests/xpcshell/test_remove.js
Normal file
60
toolkit/components/osfile/tests/xpcshell/test_remove.js
Normal file
@ -0,0 +1,60 @@
|
||||
/* 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.");
|
||||
});
|
177
toolkit/components/osfile/tests/xpcshell/test_removeDir.js
Normal file
177
toolkit/components/osfile/tests/xpcshell/test_removeDir.js
Normal file
@ -0,0 +1,177 @@
|
||||
/* 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.
|
||||
});
|
@ -0,0 +1,54 @@
|
||||
/* 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));
|
||||
});
|
102
toolkit/components/osfile/tests/xpcshell/test_reset.js
Normal file
102
toolkit/components/osfile/tests/xpcshell/test_reset.js
Normal file
@ -0,0 +1,102 @@
|
||||
/* 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();
|
||||
});
|
103
toolkit/components/osfile/tests/xpcshell/test_shutdown.js
Normal file
103
toolkit/components/osfile/tests/xpcshell/test_shutdown.js
Normal file
@ -0,0 +1,103 @@
|
||||
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")
|
||||
);
|
||||
});
|
61
toolkit/components/osfile/tests/xpcshell/test_telemetry.js
Normal file
61
toolkit/components/osfile/tests/xpcshell/test_telemetry.js
Normal file
@ -0,0 +1,61 @@
|
||||
"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);
|
||||
});
|
87
toolkit/components/osfile/tests/xpcshell/test_unique.js
Normal file
87
toolkit/components/osfile/tests/xpcshell/test_unique.js
Normal file
@ -0,0 +1,87 @@
|
||||
"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");
|
||||
});
|
48
toolkit/components/osfile/tests/xpcshell/xpcshell.ini
Normal file
48
toolkit/components/osfile/tests/xpcshell/xpcshell.ini
Normal file
@ -0,0 +1,48 @@
|
||||
[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,6 +857,15 @@
|
||||
"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 New Issue
Block a user