Bug 1063635 Part 2 - Call native writeAtomic code instead of JS backend when applicable. r?Yoric
MozReview-Commit-ID: fiIS2xPc2r
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -1164,32 +1164,41 @@ File.exists = function exists(path) {
* 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 {promise}
* @resolves {number} The number of bytes actually written.
*/
File.writeAtomic = function writeAtomic(path, buffer, options = {}) {
+ const useNativeImplementation = nativeWheneverAvailable &&
+ !options.compression &&
+ !(isTypedArray(buffer) && "byteOffset" in buffer && buffer.byteOffset > 0);
// Copy |options| to avoid modifying the original object but preserve the
// reference to |outExecutionDuration|, |outSerializationDuration| option if it is passed.
options = clone(options, ["outExecutionDuration", "outSerializationDuration"]);
- // As options.tmpPath is a path, we need to encode it as |Type.path| message
- if ("tmpPath" in options) {
+ // As options.tmpPath is a path, we need to encode it as |Type.path| message, but only
+ // if we are not using the native implementation.
+ if ("tmpPath" in options && !useNativeImplementation) {
options.tmpPath = Type.path.toMsg(options.tmpPath);
}
if (isTypedArray(buffer) && (!("bytes" in options))) {
options.bytes = buffer.byteLength;
}
let refObj = {};
+ let promise;
TelemetryStopwatch.start("OSFILE_WRITEATOMIC_JANK_MS", refObj);
- let promise = Scheduler.post("writeAtomic",
+ if (useNativeImplementation) {
+ promise = Scheduler.push(() => Native.writeAtomic(path, buffer, options));
+ } else {
+ promise = Scheduler.post("writeAtomic",
[Type.path.toMsg(path),
Type.void_t.in_ptr.toMsg(buffer),
options], [options, buffer, path]);
+ }
TelemetryStopwatch.finish("OSFILE_WRITEATOMIC_JANK_MS", refObj);
return promise;
};
File.removeDir = function(path, options = {}) {
return Scheduler.post("removeDir",
[Type.path.toMsg(path), options], path);
};
--- a/toolkit/components/osfile/modules/osfile_native.jsm
+++ b/toolkit/components/osfile/modules/osfile_native.jsm
@@ -62,8 +62,55 @@ this.read = function(path, options = {})
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|.
+ */
+this.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
+ let encoding = options.encoding || "utf-8";
+ buffer = new TextEncoder(encoding).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) => {
+ 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));
+ }
+ );
+ });
+};