merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sun, 15 Oct 2017 11:40:42 +0200
changeset 437036 0bd9b61304e208cdd5cc489aef45fdff04a006f2
parent 437030 7d88e2fbd2cf5b3f7dfa695e4d561c260ab8c02c (current diff)
parent 437035 09157e44e113b4fdb403623a8b9c78c75f188f26 (diff)
child 437037 503047e4dcafd84f38706fd7eccf21d1b4334d22
child 437052 1559e0d7e0b6bc6c75b497cd6fa2032a784a1a7d
push id8114
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 16:33:21 +0000
treeherdermozilla-beta@73e0d89a540f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
0bd9b61304e2 / 58.0a1 / 20171015100127 / files
nightly linux64
0bd9b61304e2 / 58.0a1 / 20171015100127 / files
nightly mac
0bd9b61304e2 / 58.0a1 / 20171015100127 / files
nightly win32
0bd9b61304e2 / 58.0a1 / 20171015100127 / files
nightly win64
0bd9b61304e2 / 58.0a1 / 20171015100127 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central. r=merge a=merge MozReview-Commit-ID: AWcJtsdyruX
--- a/build/moz.configure/rust.configure
+++ b/build/moz.configure/rust.configure
@@ -53,17 +53,17 @@ def rust_compiler(rustc_info, cargo_info
         die(dedent('''\
         Rust compiler not found.
         To compile rust language sources, you must have 'rustc' in your path.
         See https://www.rust-lang.org/ for more information.
 
         You can install rust by running './mach bootstrap'
         or by directly running the installer from https://rustup.rs/
         '''))
-    rustc_min_version = Version('1.19.0')
+    rustc_min_version = Version('1.20.0')
     cargo_min_version = Version('0.{}'.format(rustc_min_version.minor + 1))
 
     version = rustc_info.version
     if version < rustc_min_version:
         die(dedent('''\
         Rust compiler {} is too old.
 
         To compile Rust language sources please install at least
--- a/dom/webidl/NativeOSFileInternals.webidl
+++ b/dom/webidl/NativeOSFileInternals.webidl
@@ -14,8 +14,45 @@ dictionary NativeOSFileReadOptions
    */
   DOMString? encoding;
 
   /**
    * If specified, limit the number of bytes to read.
    */
   unsigned long long? bytes;
 };
+
+/**
+ * Options for nsINativeOSFileInternals::WriteAtomic
+ */
+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;
+};
--- a/servo/python/tidy/servo_tidy/tidy.py
+++ b/servo/python/tidy/servo_tidy/tidy.py
@@ -61,17 +61,16 @@ WEBIDL_STANDARDS = [
     "//www.khronos.org/registry/webgl/extensions",
     "//www.khronos.org/registry/webgl/specs",
     "//developer.mozilla.org/en-US/docs/Web/API",
     "//dev.w3.org/2006/webapi",
     "//dev.w3.org/csswg",
     "//dev.w3.org/fxtf",
     "//dvcs.w3.org/hg",
     "//dom.spec.whatwg.org",
-    "//domparsing.spec.whatwg.org",
     "//drafts.csswg.org",
     "//drafts.css-houdini.org",
     "//drafts.fxtf.org",
     "//encoding.spec.whatwg.org",
     "//fetch.spec.whatwg.org",
     "//html.spec.whatwg.org",
     "//url.spec.whatwg.org",
     "//xhr.spec.whatwg.org",
--- a/taskcluster/ci/build/linux.yml
+++ b/taskcluster/ci/build/linux.yml
@@ -175,17 +175,17 @@ linux64-base-toolchains/opt:
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         tooltool-downloads: public
         need-xvfb: true
     toolchains:
         - linux64-clang-3.9
         - linux64-gcc-4.9
-        - linux64-rust-1.19
+        - linux64-rust-1.20
         - linux64-sccache
 
 linux64-base-toolchains/debug:
     description: "Linux64 base toolchains Debug"
     index:
         product: firefox
         job-name: linux64-base-toolchains-debug
     treeherder:
@@ -206,17 +206,17 @@ linux64-base-toolchains/debug:
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: debug
         tooltool-downloads: public
         need-xvfb: true
     toolchains:
         - linux64-clang-3.9
         - linux64-gcc-4.9
-        - linux64-rust-1.19
+        - linux64-rust-1.20
         - linux64-sccache
 
 linux/opt:
     description: "Linux32 Opt"
     index:
         product: firefox
         job-name: linux-opt
     treeherder:
--- a/toolkit/components/osfile/NativeOSFileInternals.cpp
+++ b/toolkit/components/osfile/NativeOSFileInternals.cpp
@@ -21,23 +21,25 @@
 
 #include "mozilla/Encoding.h"
 #include "nsIEventTarget.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/HoldDropJSObjects.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
 
 #include "prio.h"
 #include "prerror.h"
 #include "private/pprio.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
+#include "js/Conversions.h"
 #include "js/Utility.h"
 #include "xpcpublic.h"
 
 #include <algorithm>
 #if defined(XP_UNIX)
 #include <unistd.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -125,21 +127,23 @@ private:
 };
 
 ///////// Cross-platform issues
 
 // Platform specific constants. As OS.File always uses OS-level
 // errors, we need to map a few high-level errors to OS-level
 // constants.
 #if defined(XP_UNIX)
+#define OS_ERROR_FILE_EXISTS EEXIST
 #define OS_ERROR_NOMEM ENOMEM
 #define OS_ERROR_INVAL EINVAL
 #define OS_ERROR_TOO_LARGE EFBIG
 #define OS_ERROR_RACE EIO
 #elif defined(XP_WIN)
+#define OS_ERROR_FILE_EXISTS ERROR_ALREADY_EXISTS
 #define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY
 #define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS
 #define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE
 #define OS_ERROR_RACE ERROR_SHARING_VIOLATION
 #else
 #error "We do not have platform-specific constants for this platform"
 #endif
 
@@ -376,16 +380,56 @@ TypedArrayResult::GetCacheableResult(JSC
   // have a context, attach the memory to where it belongs.
   JS_updateMallocCounter(cx, contents.nbytes);
   mContents.forget();
 
   aResult.setObject(*result);
   return NS_OK;
 }
 
+/**
+ * Return a result as an int32_t.
+ *
+ * In this implementation, attribute |result| is an int32_t.
+ */
+class Int32Result final: public AbstractResult
+{
+public:
+  explicit Int32Result(TimeStamp aStartDate)
+    : AbstractResult(aStartDate)
+    , mContents(0)
+  {
+  }
+
+  /**
+   * Initialize the object once the contents of the result are available.
+   *
+   * @param aContents The contents to pass to JS. This is an int32_t.
+   */
+  void Init(TimeStamp aDispatchDate,
+            TimeDuration aExecutionDuration,
+            int32_t aContents) {
+    AbstractResult::Init(aDispatchDate, aExecutionDuration);
+    mContents = aContents;
+  }
+
+protected:
+  nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) override;
+private:
+  int32_t mContents;
+};
+
+nsresult
+Int32Result::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  aResult.set(JS::NumberValue(mContents));
+  return NS_OK;
+}
+
 //////// Callback events
 
 /**
  * An event used to notify asynchronously of an error.
  */
 class ErrorEvent final : public Runnable {
 public:
   /**
@@ -856,16 +900,273 @@ protected:
   }
 
  private:
   nsCString mEncoding;
   mozilla::UniquePtr<mozilla::Decoder> mDecoder;
   RefPtr<StringResult> mResult;
 };
 
+/**
+ * An event implenting writing atomically to a file.
+ */
+class DoWriteAtomicEvent: public AbstractDoEvent {
+public:
+  /**
+   * @param aPath The path of the file.
+   */
+  DoWriteAtomicEvent(const nsAString& aPath,
+                     UniquePtr<char> aBuffer,
+                     const uint64_t aBytes,
+                     const nsAString& aTmpPath,
+                     const nsAString& aBackupTo,
+                     const bool aFlush,
+                     const bool aNoOverwrite,
+                     nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+                     nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
+    : AbstractDoEvent(aOnSuccess, aOnError)
+    , mPath(aPath)
+    , mBuffer(Move(aBuffer))
+    , mBytes(aBytes)
+    , mTmpPath(aTmpPath)
+    , mBackupTo(aBackupTo)
+    , mFlush(aFlush)
+    , mNoOverwrite(aNoOverwrite)
+    , mResult(new Int32Result(TimeStamp::Now()))
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  ~DoWriteAtomicEvent() override {
+    // If Run() has bailed out, we may need to cleanup
+    // mResult, which is main-thread only data
+    if (!mResult) {
+      return;
+    }
+    NS_ReleaseOnMainThreadSystemGroup("DoWriteAtomicEvent::mResult",
+                                      mResult.forget());
+  }
+
+  NS_IMETHODIMP Run() override {
+    MOZ_ASSERT(!NS_IsMainThread());
+    TimeStamp dispatchDate = TimeStamp::Now();
+    int32_t bytesWritten;
+
+    nsresult rv = WriteAtomic(&bytesWritten);
+    if (NS_FAILED(rv)) {
+      return NS_OK;
+    }
+
+    AfterWriteAtomic(dispatchDate, bytesWritten);
+    return NS_OK;
+  }
+
+private:
+  /**
+   * Write atomically to a file.
+   * Must be called off the main thread.
+   * @param aBytesWritten will contain the total bytes written.
+   * This does not support compression in this implementation.
+   */
+  nsresult WriteAtomic(int32_t* aBytesWritten)
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    // Note: In Windows, many NSPR File I/O functions which act on pathnames
+    // do not handle UTF-16 encoding. Thus, we use the following functions
+    // to overcome this.
+    // PR_Access : GetFileAttributesW
+    // PR_Delete : DeleteFileW
+    // PR_OpenFile : CreateFileW followed by PR_ImportFile
+    // PR_Rename : MoveFileW
+
+    ScopedPRFileDesc file;
+    NS_ConvertUTF16toUTF8 path(mPath);
+    NS_ConvertUTF16toUTF8 tmpPath(mTmpPath);
+    NS_ConvertUTF16toUTF8 backupTo(mBackupTo);
+    bool fileExists = false;
+
+    if (!mTmpPath.IsVoid() || !mBackupTo.IsVoid() || mNoOverwrite) {
+      // fileExists needs to be computed in the case of tmpPath, since
+      // the rename behaves differently depending on whether the
+      // file already exists. It's also computed for backupTo since the
+      // backup can be skipped if the file does not exist in the first place.
+#if defined(XP_WIN)
+      fileExists = ::GetFileAttributesW(mPath.get()) != INVALID_FILE_ATTRIBUTES;
+#else
+      fileExists = PR_Access(path.get(), PR_ACCESS_EXISTS) == PR_SUCCESS;
+#endif // defined(XP_WIN)
+    }
+
+    // Check noOverwrite.
+    if (mNoOverwrite && fileExists) {
+      Fail(NS_LITERAL_CSTRING("noOverwrite"), nullptr, OS_ERROR_FILE_EXISTS);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Backup the original file if it exists.
+    if (!mBackupTo.IsVoid() && fileExists) {
+#if defined(XP_WIN)
+      if (::GetFileAttributesW(mBackupTo.get()) != INVALID_FILE_ATTRIBUTES) {
+        // The file specified by mBackupTo exists, so we need to delete it first.
+        if (::DeleteFileW(mBackupTo.get()) == false) {
+          Fail(NS_LITERAL_CSTRING("delete"), nullptr, ::GetLastError());
+          return NS_ERROR_FAILURE;
+        }
+      }
+
+      if (::MoveFileW(mPath.get(), mBackupTo.get()) == false) {
+        Fail(NS_LITERAL_CSTRING("rename"), nullptr, ::GetLastError());
+        return NS_ERROR_FAILURE;
+      }
+#else
+      if (PR_Access(backupTo.get(), PR_ACCESS_EXISTS) == PR_SUCCESS) {
+        // The file specified by mBackupTo exists, so we need to delete it first.
+        if (PR_Delete(backupTo.get()) == PR_FAILURE) {
+          Fail(NS_LITERAL_CSTRING("delete"), nullptr, PR_GetOSError());
+          return NS_ERROR_FAILURE;
+         }
+       }
+
+       if (PR_Rename(path.get(), backupTo.get()) == PR_FAILURE) {
+        Fail(NS_LITERAL_CSTRING("rename"), nullptr, PR_GetOSError());
+        return NS_ERROR_FAILURE;
+      }
+#endif // defined(XP_WIN)
+    }
+
+#if defined(XP_WIN)
+    // In addition to not handling UTF-16 encoding in file paths,
+    // PR_OpenFile opens files without sharing, which is not the
+    // general semantics of OS.File.
+    HANDLE handle;
+    // if we're dealing with a tmpFile, we need to write there.
+    if (!mTmpPath.IsVoid()) {
+      handle =
+        ::CreateFileW(mTmpPath.get(),
+                      GENERIC_WRITE,
+                      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                      /*Security attributes*/nullptr,
+                      // CREATE_ALWAYS is used since since we need to create the temporary file,
+                      // which we don't care about overwriting.
+                      CREATE_ALWAYS,
+                      FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
+                      /*Template file*/ nullptr);
+    } else {
+      handle =
+        ::CreateFileW(mPath.get(),
+                      GENERIC_WRITE,
+                      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                      /*Security attributes*/nullptr,
+                      // CREATE_ALWAYS is used since since have already checked the noOverwrite
+                      // condition, and thus can overwrite safely.
+                      CREATE_ALWAYS,
+                      FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
+                      /*Template file*/ nullptr);
+    }
+
+    if (handle == INVALID_HANDLE_VALUE) {
+      Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError());
+      return NS_ERROR_FAILURE;
+    }
+
+    file = PR_ImportFile((PROsfd)handle);
+    if (!file) {
+      // |file| is closed by PR_ImportFile
+      Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError());
+      return NS_ERROR_FAILURE;
+    }
+
+#else
+    // if we're dealing with a tmpFile, we need to write there.
+    if (!mTmpPath.IsVoid()) {
+      file = PR_OpenFile(tmpPath.get(),
+                         PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+                         PR_IRUSR | PR_IWUSR);
+    } else {
+      file = PR_OpenFile(path.get(),
+                         PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+                         PR_IRUSR | PR_IWUSR);
+    }
+
+    if (!file) {
+      Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError());
+      return NS_ERROR_FAILURE;
+    }
+#endif // defined(XP_WIN)
+
+    int32_t bytesWrittenSuccess = PR_Write(file, (void* )(mBuffer.get()), mBytes);
+
+    if (bytesWrittenSuccess == -1) {
+      Fail(NS_LITERAL_CSTRING("write"), nullptr, PR_GetOSError());
+      return NS_ERROR_FAILURE;
+    }
+
+    // Apply any tmpPath renames.
+    if (!mTmpPath.IsVoid()) {
+      if (mBackupTo.IsVoid() && fileExists) {
+        // We need to delete the old file first, if it exists and we haven't already
+        // renamed it as a part of backing it up.
+#if defined(XP_WIN)
+        if (::DeleteFileW(mPath.get()) == false) {
+          Fail(NS_LITERAL_CSTRING("delete"), nullptr, ::GetLastError());
+          return NS_ERROR_FAILURE;
+        }
+#else
+        if (PR_Delete(path.get()) == PR_FAILURE) {
+          Fail(NS_LITERAL_CSTRING("delete"), nullptr, PR_GetOSError());
+          return NS_ERROR_FAILURE;
+        }
+#endif // defined(XP_WIN)
+      }
+
+#if defined(XP_WIN)
+      if (::MoveFileW(mTmpPath.get(), mPath.get()) == false) {
+        Fail(NS_LITERAL_CSTRING("rename"), nullptr, ::GetLastError());
+        return NS_ERROR_FAILURE;
+      }
+#else
+      if(PR_Rename(tmpPath.get(), path.get()) == PR_FAILURE) {
+        Fail(NS_LITERAL_CSTRING("rename"), nullptr, PR_GetOSError());
+        return NS_ERROR_FAILURE;
+      }
+#endif // defined(XP_WIN)
+    }
+
+    if (mFlush) {
+      if (PR_Sync(file) == PR_FAILURE) {
+        Fail(NS_LITERAL_CSTRING("sync"), nullptr, PR_GetOSError());
+        return NS_ERROR_FAILURE;
+      }
+    }
+
+    *aBytesWritten = bytesWrittenSuccess;
+    return NS_OK;
+  }
+
+protected:
+  nsresult AfterWriteAtomic(TimeStamp aDispatchDate, int32_t aBytesWritten) {
+    MOZ_ASSERT(!NS_IsMainThread());
+    mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBytesWritten);
+    Succeed(mResult.forget());
+    return NS_OK;
+  }
+
+  const nsString mPath;
+  const UniquePtr<char> mBuffer;
+  const int32_t mBytes;
+  const nsString mTmpPath;
+  const nsString mBackupTo;
+  const bool mFlush;
+  const bool mNoOverwrite;
+
+private:
+  RefPtr<Int32Result> mResult;
+};
+
 } // namespace
 
 // The OS.File service
 
 NS_IMPL_ISUPPORTS(NativeOSFileInternalsService, nsINativeOSFileInternalsService);
 
 NS_IMETHODIMP
 NativeOSFileInternalsService::Read(const nsAString& aPath,
@@ -918,9 +1219,97 @@ NativeOSFileInternalsService::Read(const
   nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
 
   if (NS_FAILED(rv)) {
     return rv;
   }
   return target->Dispatch(event, NS_DISPATCH_NORMAL);
 }
 
+// Note: This method steals the contents of `aBuffer`.
+NS_IMETHODIMP
+NativeOSFileInternalsService::WriteAtomic(const nsAString& aPath,
+                                          JS::HandleValue aBuffer,
+                                          JS::HandleValue aOptions,
+                                          nsINativeOSFileSuccessCallback *aOnSuccess,
+                                          nsINativeOSFileErrorCallback *aOnError,
+                                          JSContext* cx)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // Extract typed-array/string into buffer. We also need to store the length
+  // of the buffer as that may be required if not provided in `aOptions`.
+  UniquePtr<char> buffer;
+  int32_t bytes;
+
+  // The incoming buffer must be an Object.
+  if (!aBuffer.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JS::RootedObject bufferObject(cx, nullptr);
+  if (!JS_ValueToObject(cx, aBuffer, &bufferObject)) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!JS_IsArrayBufferObject(bufferObject.get())) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  bytes = JS_GetArrayBufferByteLength(bufferObject.get());
+  buffer.reset(static_cast<char*>(
+                 JS_StealArrayBufferContents(cx, bufferObject)));
+
+  if (!buffer) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Extract options.
+  dom::NativeOSFileWriteAtomicOptions dict;
+
+  if (aOptions.isObject()) {
+    if (!dict.Init(cx, aOptions)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+  } else {
+    // If an options object is not provided, initializing with a `null`
+    // value, which will give a set of defaults defined in the WebIDL binding.
+    if (!dict.Init(cx, JS::NullHandleValue)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
+    // We need to check size and cast because NSPR and WebIDL have different types.
+    if (dict.mBytes.Value().Value() > PR_INT32_MAX) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    bytes = (int32_t) (dict.mBytes.Value().Value());
+  }
+
+  // Prepare the off main thread event and dispatch it
+  nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess);
+  nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> onSuccessHandle(
+    new nsMainThreadPtrHolder<nsINativeOSFileSuccessCallback>(
+      "nsINativeOSFileSuccessCallback", onSuccess));
+  nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError);
+  nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> onErrorHandle(
+    new nsMainThreadPtrHolder<nsINativeOSFileErrorCallback>(
+      "nsINativeOSFileErrorCallback", onError));
+
+  RefPtr<AbstractDoEvent> event = new DoWriteAtomicEvent(aPath,
+                                                         Move(buffer),
+                                                         bytes,
+                                                         dict.mTmpPath,
+                                                         dict.mBackupTo,
+                                                         dict.mFlush,
+                                                         dict.mNoOverwrite,
+                                                         onSuccessHandle,
+                                                         onErrorHandle);
+  nsresult rv;
+  nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
 } // namespace mozilla
--- 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));
+      }
+    );
+  });
+};
--- a/toolkit/components/osfile/nsINativeOSFileInternals.idl
+++ b/toolkit/components/osfile/nsINativeOSFileInternals.idl
@@ -77,16 +77,43 @@ interface nsINativeOSFileInternalsServic
    * - {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"
 
--- a/toolkit/components/osfile/tests/xpcshell/test_duration.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_duration.js
@@ -52,22 +52,22 @@ add_task(async function duration() {
     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.
-    outSerializationDuration: null,
     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");
+  testOptions(writeAtomicOptions, "OS.File.writeAtomic", ["outExecutionDuration"]);
   await OS.File.remove(pathDest);
 
   do_print(`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.
@@ -83,20 +83,25 @@ add_task(async function duration() {
   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 = copyOptions;
+  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({}, copyOptions);
+  backupDuration = Object.assign({}, writeAtomicOptions);
+  contents = await OS.File.read(pathSource, undefined, readOptions);
   await OS.File.writeAtomic(pathDest, contents, writeAtomicOptions);
-  testOptionIncrements(writeAtomicOptions, "writeAtomicOptions", backupDuration);
+  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();
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_writeAtomic_unicode_filename.js
@@ -0,0 +1,40 @@
+/* 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");
+});
--- a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
@@ -25,16 +25,17 @@ 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