Bug 1063635 Part 2 - Call native writeAtomic code instead of JS backend when applicable. r?Yoric draft
authorMilindL <i.milind.luthra@gmail.com>
Thu, 27 Jul 2017 19:41:00 +0530
changeset 679544 5d5b01c0f8a79d7ef371b25b2c6eb171b26507e6
parent 679543 800d8f5ca4f84482f19807e94d2793456b207ad8
child 679545 03d6788c0ae0cf800f219d567217b4c122a350f4
push id84268
push userbmo:i.milind.luthra@gmail.com
push dateThu, 12 Oct 2017 21:35:29 +0000
reviewersYoric
bugs1063635
milestone58.0a1
Bug 1063635 Part 2 - Call native writeAtomic code instead of JS backend when applicable. r?Yoric MozReview-Commit-ID: fiIS2xPc2r
toolkit/components/osfile/modules/osfile_async_front.jsm
toolkit/components/osfile/modules/osfile_native.jsm
--- 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));
+      }
+    );
+  });
+};