summaryrefslogtreecommitdiffstats
path: root/toolkit/components/osfile/NativeOSFileInternals.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /toolkit/components/osfile/NativeOSFileInternals.cpp
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/osfile/NativeOSFileInternals.cpp')
-rw-r--r--toolkit/components/osfile/NativeOSFileInternals.cpp1263
1 files changed, 1263 insertions, 0 deletions
diff --git a/toolkit/components/osfile/NativeOSFileInternals.cpp b/toolkit/components/osfile/NativeOSFileInternals.cpp
new file mode 100644
index 0000000000..3ff8d46fb9
--- /dev/null
+++ b/toolkit/components/osfile/NativeOSFileInternals.cpp
@@ -0,0 +1,1263 @@
+/* -*- 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/. */
+
+/**
+ * Native implementation of some OS.File operations.
+ */
+
+#include "NativeOSFileInternals.h"
+
+#include "nsString.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsServiceManagerUtils.h"
+#include "nsProxyRelease.h"
+
+#include "mozilla/dom/NativeOSFileInternalsBinding.h"
+
+#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/ArrayBuffer.h" // JS::GetArrayBufferByteLength,IsArrayBufferObject,NewArrayBufferWithContents,StealArrayBufferContents
+#include "js/experimental/TypedData.h" // JS_NewUint8ArrayWithBuffer
+#include "js/Utility.h"
+#include "xpcpublic.h"
+
+#include <algorithm>
+#if defined(XP_UNIX)
+# include <errno.h>
+#endif // defined (XP_UNIX)
+
+#if defined(XP_WIN)
+# include <windows.h>
+#endif // defined (XP_WIN)
+
+namespace mozilla {
+
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
+ PR_Close)
+
+namespace {
+
+// Utilities for safely manipulating ArrayBuffer contents even in the
+// absence of a JSContext.
+
+/**
+ * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate
+ * this instead of a void* buffer, as this lets us transfer data across threads
+ * and into JavaScript without copy.
+ */
+struct ArrayBufferContents {
+ /**
+ * The data of the ArrayBuffer. This is the pointer manipulated to
+ * read/write the contents of the buffer.
+ */
+ uint8_t* data;
+ /**
+ * The number of bytes in the ArrayBuffer.
+ */
+ size_t nbytes;
+};
+
+/**
+ * RAII for ArrayBufferContents.
+ */
+struct ScopedArrayBufferContentsTraits {
+ typedef ArrayBufferContents type;
+ const static type empty() {
+ type result = {0, 0};
+ return result;
+ }
+ static void release(type ptr) {
+ js_free(ptr.data);
+ ptr.data = nullptr;
+ ptr.nbytes = 0;
+ }
+};
+
+struct MOZ_NON_TEMPORARY_CLASS ScopedArrayBufferContents
+ : public Scoped<ScopedArrayBufferContentsTraits> {
+ explicit ScopedArrayBufferContents()
+ : Scoped<ScopedArrayBufferContentsTraits>() {}
+
+ ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) {
+ Scoped<ScopedArrayBufferContentsTraits>::operator=(ptr);
+ return *this;
+ }
+
+ /**
+ * Request memory for this ArrayBufferContent. This memory may later
+ * be used to create an ArrayBuffer object (possibly on another
+ * thread) without copy.
+ *
+ * @return true In case of success, false otherwise.
+ */
+ bool Allocate(uint32_t length) {
+ dispose();
+ ArrayBufferContents& value = rwget();
+ void* ptr = js_calloc(1, length);
+ if (ptr) {
+ value.data = (uint8_t*)ptr;
+ value.nbytes = length;
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) =
+ delete;
+ ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) =
+ delete;
+};
+
+///////// 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
+
+///////// Results of OS.File operations
+
+/**
+ * Base class for results passed to the callbacks.
+ *
+ * This base class implements caching of JS values returned to the client.
+ * We make use of this caching in derived classes e.g. to avoid accidents
+ * when we transfer data allocated on another thread into JS. Note that
+ * this caching can lead to cycles (e.g. if a client adds a back-reference
+ * in the JS value), so we implement all Cycle Collector primitives in
+ * AbstractResult.
+ */
+class AbstractResult : public nsINativeOSFileResult {
+ public:
+ NS_DECL_NSINATIVEOSFILERESULT
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult)
+
+ /**
+ * Construct the result object. Must be called on the main thread
+ * as the AbstractResult is cycle-collected.
+ *
+ * @param aStartDate The instant at which the operation was
+ * requested. Used to collect Telemetry statistics.
+ */
+ explicit AbstractResult(TimeStamp aStartDate) : mStartDate(aStartDate) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::HoldJSObjects(this);
+ }
+
+ /**
+ * Setup the AbstractResult once data is available.
+ *
+ * @param aDispatchDate The instant at which the IO thread received
+ * the operation request. Used to collect Telemetry statistics.
+ * @param aExecutionDuration The duration of the operation on the
+ * IO thread.
+ */
+ void Init(TimeStamp aDispatchDate, TimeDuration aExecutionDuration) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mDispatchDuration = (aDispatchDate - mStartDate);
+ mExecutionDuration = aExecutionDuration;
+ }
+
+ /**
+ * Drop any data that could lead to a cycle.
+ */
+ void DropJSData() { mCachedResult = JS::UndefinedValue(); }
+
+ protected:
+ virtual ~AbstractResult() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::DropJSObjects(this);
+ }
+
+ virtual nsresult GetCacheableResult(JSContext* cx,
+ JS::MutableHandle<JS::Value> aResult) = 0;
+
+ private:
+ TimeStamp mStartDate;
+ TimeDuration mDispatchDuration;
+ TimeDuration mExecutionDuration;
+ JS::Heap<JS::Value> mCachedResult;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult)
+ NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedResult)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult)
+ tmp->DropJSData();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMETHODIMP
+AbstractResult::GetDispatchDurationMS(double* aDispatchDuration) {
+ *aDispatchDuration = mDispatchDuration.ToMilliseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AbstractResult::GetExecutionDurationMS(double* aExecutionDuration) {
+ *aExecutionDuration = mExecutionDuration.ToMilliseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AbstractResult::GetResult(JSContext* cx, JS::MutableHandle<JS::Value> aResult) {
+ if (mCachedResult.isUndefined()) {
+ nsresult rv = GetCacheableResult(cx, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCachedResult = aResult;
+ return NS_OK;
+ }
+ aResult.set(mCachedResult);
+ return NS_OK;
+}
+
+/**
+ * Return a result as a string.
+ *
+ * In this implementation, attribute |result| is a string. Strings are
+ * passed to JS without copy.
+ */
+class StringResult final : public AbstractResult {
+ public:
+ explicit StringResult(TimeStamp aStartDate) : AbstractResult(aStartDate) {}
+
+ /**
+ * Initialize the object once the contents of the result as available.
+ *
+ * @param aContents The string to pass to JavaScript. Ownership of the
+ * string and its contents is passed to StringResult. The string must
+ * be valid UTF-16.
+ */
+ void Init(TimeStamp aDispatchDate, TimeDuration aExecutionDuration,
+ nsString& aContents) {
+ AbstractResult::Init(aDispatchDate, aExecutionDuration);
+ mContents = aContents;
+ }
+
+ protected:
+ nsresult GetCacheableResult(JSContext* cx,
+ JS::MutableHandle<JS::Value> aResult) override;
+
+ private:
+ nsString mContents;
+};
+
+nsresult StringResult::GetCacheableResult(
+ JSContext* cx, JS::MutableHandle<JS::Value> aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mContents.get());
+
+ // Convert mContents to a js string without copy. Note that this
+ // may have the side-effect of stealing the contents of the string
+ // from XPCOM and into JS.
+ if (!xpc::StringToJsval(cx, mContents, aResult)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/**
+ * Return a result as a Uint8Array.
+ *
+ * In this implementation, attribute |result| is a Uint8Array. The array
+ * is passed to JS without memory copy.
+ */
+class TypedArrayResult final : public AbstractResult {
+ public:
+ explicit TypedArrayResult(TimeStamp aStartDate)
+ : AbstractResult(aStartDate) {}
+
+ /**
+ * @param aContents The contents to pass to JS. Calling this method.
+ * transmits ownership of the ArrayBufferContents to the TypedArrayResult.
+ * Do not reuse this value anywhere else.
+ */
+ void Init(TimeStamp aDispatchDate, TimeDuration aExecutionDuration,
+ ArrayBufferContents aContents) {
+ AbstractResult::Init(aDispatchDate, aExecutionDuration);
+ mContents = aContents;
+ }
+
+ protected:
+ nsresult GetCacheableResult(JSContext* cx,
+ JS::MutableHandle<JS::Value> aResult) override;
+
+ private:
+ ScopedArrayBufferContents mContents;
+};
+
+nsresult TypedArrayResult::GetCacheableResult(
+ JSContext* cx, JS::MutableHandle<JS::Value> aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // We cannot simply construct a typed array using contents.data as
+ // this would allow us to have several otherwise unrelated
+ // ArrayBuffers with the same underlying C buffer. As this would be
+ // very unsafe, we need to cache the result once we have it.
+
+ const ArrayBufferContents& contents = mContents.get();
+ MOZ_ASSERT(contents.data);
+
+ // This takes ownership of the buffer and notes the memory allocation.
+ JS::Rooted<JSObject*> arrayBuffer(
+ cx, JS::NewArrayBufferWithContents(cx, contents.nbytes, contents.data));
+ if (!arrayBuffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ JS::Rooted<JSObject*> result(
+ cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer, 0, contents.nbytes));
+ if (!result) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ 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::MutableHandle<JS::Value> aResult) override;
+
+ private:
+ int32_t mContents;
+};
+
+nsresult Int32Result::GetCacheableResult(JSContext* cx,
+ JS::MutableHandle<JS::Value> 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 OSFileErrorEvent final : public Runnable {
+ public:
+ /**
+ * @param aOnSuccess The success callback.
+ * @param aOnError The error callback.
+ * @param aDiscardedResult The discarded result.
+ * @param aOperation The name of the operation, used for error reporting.
+ * @param aOSError The OS error of the operation, as returned by errno/
+ * GetLastError().
+ *
+ * Note that we pass both the success callback and the error
+ * callback, as well as the discarded result to ensure that they are
+ * all released on the main thread, rather than on the IO thread
+ * (which would hopefully segfault). Also, we pass the callbacks as
+ * alread_AddRefed to ensure that we do not manipulate main-thread
+ * only refcounters off the main thread.
+ */
+ OSFileErrorEvent(
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
+ already_AddRefed<AbstractResult>& aDiscardedResult,
+ const nsACString& aOperation, int32_t aOSError)
+ : Runnable("OSFileErrorEvent"),
+ mOnSuccess(aOnSuccess),
+ mOnError(aOnError),
+ mDiscardedResult(aDiscardedResult),
+ mOSError(aOSError),
+ mOperation(aOperation) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ (void)mOnError->Complete(mOperation, mOSError);
+
+ // Ensure that the callbacks are released on the main thread.
+ mOnSuccess = nullptr;
+ mOnError = nullptr;
+ mDiscardedResult = nullptr;
+
+ return NS_OK;
+ }
+
+ private:
+ // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
+ // xpconnect values, which cannot be manipulated with nsCOMPtr off
+ // the main thread. We store both the success callback and the
+ // error callback to ensure that they are safely released on the
+ // main thread.
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
+ RefPtr<AbstractResult> mDiscardedResult;
+ int32_t mOSError;
+ nsCString mOperation;
+};
+
+/**
+ * An event used to notify of a success.
+ */
+class SuccessEvent final : public Runnable {
+ public:
+ /**
+ * @param aOnSuccess The success callback.
+ * @param aOnError The error callback.
+ *
+ * Note that we pass both the success callback and the error
+ * callback to ensure that they are both released on the main
+ * thread, rather than on the IO thread (which would hopefully
+ * segfault). Also, we pass them as alread_AddRefed to ensure that
+ * we do not manipulate xpconnect refcounters off the main thread
+ * (which is illegal).
+ */
+ SuccessEvent(
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
+ already_AddRefed<nsINativeOSFileResult>& aResult)
+ : Runnable("SuccessEvent"),
+ mOnSuccess(aOnSuccess),
+ mOnError(aOnError),
+ mResult(aResult) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ (void)mOnSuccess->Complete(mResult);
+
+ // Ensure that the callbacks are released on the main thread.
+ mOnSuccess = nullptr;
+ mOnError = nullptr;
+ mResult = nullptr;
+
+ return NS_OK;
+ }
+
+ private:
+ // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
+ // xpconnect values, which cannot be manipulated with nsCOMPtr off
+ // the main thread. We store both the success callback and the
+ // error callback to ensure that they are safely released on the
+ // main thread.
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
+ RefPtr<nsINativeOSFileResult> mResult;
+};
+
+//////// Action events
+
+/**
+ * Base class shared by actions.
+ */
+class AbstractDoEvent : public Runnable {
+ public:
+ AbstractDoEvent(
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
+ : Runnable("AbstractDoEvent"),
+ mOnSuccess(aOnSuccess),
+ mOnError(aOnError)
+#if defined(DEBUG)
+ ,
+ mResolved(false)
+#endif // defined(DEBUG)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ /**
+ * Fail, asynchronously.
+ */
+ void Fail(const nsACString& aOperation,
+ already_AddRefed<AbstractResult>&& aDiscardedResult,
+ int32_t aOSError = 0) {
+ Resolve();
+
+ RefPtr<OSFileErrorEvent> event = new OSFileErrorEvent(
+ mOnSuccess, mOnError, aDiscardedResult, aOperation, aOSError);
+ nsresult rv = NS_DispatchToMainThread(event);
+ if (NS_FAILED(rv)) {
+ // Last ditch attempt to release on the main thread - some of
+ // the members of event are not thread-safe, so letting the
+ // pointer go out of scope would cause a crash.
+ NS_ReleaseOnMainThread("AbstractDoEvent::OSFileErrorEvent",
+ event.forget());
+ }
+ }
+
+ /**
+ * Succeed, asynchronously.
+ */
+ void Succeed(already_AddRefed<nsINativeOSFileResult>&& aResult) {
+ Resolve();
+ RefPtr<SuccessEvent> event =
+ new SuccessEvent(mOnSuccess, mOnError, aResult);
+ nsresult rv = NS_DispatchToMainThread(event);
+ if (NS_FAILED(rv)) {
+ // Last ditch attempt to release on the main thread - some of
+ // the members of event are not thread-safe, so letting the
+ // pointer go out of scope would cause a crash.
+ NS_ReleaseOnMainThread("AbstractDoEvent::SuccessEvent", event.forget());
+ }
+ }
+
+ private:
+ /**
+ * Mark the event as complete, for debugging purposes.
+ */
+ void Resolve() {
+#if defined(DEBUG)
+ MOZ_ASSERT(!mResolved);
+ mResolved = true;
+#endif // defined(DEBUG)
+ }
+
+ private:
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
+#if defined(DEBUG)
+ // |true| once the action is complete
+ bool mResolved;
+#endif // defined(DEBUG)
+};
+
+/**
+ * An abstract event implementing reading from a file.
+ *
+ * Concrete subclasses are responsible for handling the
+ * data obtained from the file and possibly post-processing it.
+ */
+class AbstractReadEvent : public AbstractDoEvent {
+ public:
+ /**
+ * @param aPath The path of the file.
+ */
+ AbstractReadEvent(
+ const nsAString& aPath, const uint64_t aBytes,
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
+ : AbstractDoEvent(aOnSuccess, aOnError), mPath(aPath), mBytes(aBytes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ TimeStamp dispatchDate = TimeStamp::Now();
+
+ nsresult rv = BeforeRead();
+ if (NS_FAILED(rv)) {
+ // Error reporting is handled by BeforeRead();
+ return NS_OK;
+ }
+
+ ScopedArrayBufferContents buffer;
+ rv = Read(buffer);
+ if (NS_FAILED(rv)) {
+ // Error reporting is handled by Read();
+ return NS_OK;
+ }
+
+ AfterRead(dispatchDate, buffer);
+ return NS_OK;
+ }
+
+ private:
+ /**
+ * Read synchronously.
+ *
+ * Must be called off the main thread.
+ *
+ * @param aBuffer The destination buffer.
+ */
+ nsresult Read(ScopedArrayBufferContents& aBuffer) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ ScopedPRFileDesc file;
+#if defined(XP_WIN)
+ // On Windows, we can't use PR_OpenFile because it doesn't
+ // handle UTF-16 encoding, which is pretty bad. In addition,
+ // PR_OpenFile opens files without sharing, which is not the
+ // general semantics of OS.File.
+ HANDLE handle =
+ ::CreateFileW(mPath.get(), GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ /*Security attributes*/ nullptr, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+ /*Template file*/ nullptr);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ Fail("open"_ns, nullptr, ::GetLastError());
+ return NS_ERROR_FAILURE;
+ }
+
+ file = PR_ImportFile((PROsfd)handle);
+ if (!file) {
+ // |file| is closed by PR_ImportFile
+ Fail("ImportFile"_ns, nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+
+#else
+ // On other platforms, PR_OpenFile will do.
+ NS_ConvertUTF16toUTF8 path(mPath);
+ file = PR_OpenFile(path.get(), PR_RDONLY, 0);
+ if (!file) {
+ Fail("open"_ns, nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+
+#endif // defined(XP_XIN)
+
+ PRFileInfo64 stat;
+ if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) {
+ Fail("stat"_ns, nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t bytes = std::min((uint64_t)stat.size, mBytes);
+ if (bytes > UINT32_MAX) {
+ Fail("Arithmetics"_ns, nullptr, OS_ERROR_INVAL);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aBuffer.Allocate(bytes)) {
+ Fail("allocate"_ns, nullptr, OS_ERROR_NOMEM);
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t total_read = 0;
+ int32_t just_read = 0;
+ char* dest_chars = reinterpret_cast<char*>(aBuffer.rwget().data);
+ do {
+ just_read = PR_Read(file, dest_chars + total_read,
+ std::min(uint64_t(PR_INT32_MAX), bytes - total_read));
+ if (just_read == -1) {
+ Fail("read"_ns, nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+ total_read += just_read;
+ } while (just_read != 0 && total_read < bytes);
+ if (total_read != bytes) {
+ // We seem to have a race condition here.
+ Fail("read"_ns, nullptr, OS_ERROR_RACE);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ /**
+ * Any steps that need to be taken before reading.
+ *
+ * In case of error, this method should call Fail() and return
+ * a failure code.
+ */
+ virtual nsresult BeforeRead() { return NS_OK; }
+
+ /**
+ * Proceed after reading.
+ */
+ virtual void AfterRead(TimeStamp aDispatchDate,
+ ScopedArrayBufferContents& aBuffer) = 0;
+
+ protected:
+ const nsString mPath;
+ const uint64_t mBytes;
+};
+
+/**
+ * An implementation of a Read event that provides the data
+ * as a TypedArray.
+ */
+class DoReadToTypedArrayEvent final : public AbstractReadEvent {
+ public:
+ DoReadToTypedArrayEvent(
+ const nsAString& aPath, const uint32_t aBytes,
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
+ : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError),
+ mResult(new TypedArrayResult(TimeStamp::Now())) {}
+
+ ~DoReadToTypedArrayEvent() override {
+ // If AbstractReadEvent::Run() has bailed out, we may need to cleanup
+ // mResult, which is main-thread only data
+ if (!mResult) {
+ return;
+ }
+ NS_ReleaseOnMainThread("DoReadToTypedArrayEvent::mResult",
+ mResult.forget());
+ }
+
+ protected:
+ void AfterRead(TimeStamp aDispatchDate,
+ ScopedArrayBufferContents& aBuffer) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate,
+ aBuffer.forget());
+ Succeed(mResult.forget());
+ }
+
+ private:
+ RefPtr<TypedArrayResult> mResult;
+};
+
+/**
+ * An implementation of a Read event that provides the data
+ * as a JavaScript string.
+ */
+class DoReadToStringEvent final : public AbstractReadEvent {
+ public:
+ DoReadToStringEvent(
+ const nsAString& aPath, const nsACString& aEncoding,
+ const uint32_t aBytes,
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
+ : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError),
+ mEncoding(aEncoding),
+ mResult(new StringResult(TimeStamp::Now())) {}
+
+ ~DoReadToStringEvent() override {
+ // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup
+ // mResult, which is main-thread only data
+ if (!mResult) {
+ return;
+ }
+ NS_ReleaseOnMainThread("DoReadToStringEvent::mResult", mResult.forget());
+ }
+
+ protected:
+ nsresult BeforeRead() override {
+ // Obtain the decoder. We do this before reading to avoid doing
+ // any unnecessary I/O in case the name of the encoding is incorrect.
+ MOZ_ASSERT(!NS_IsMainThread());
+ const Encoding* encoding = Encoding::ForLabel(mEncoding);
+ if (!encoding) {
+ Fail("Decode"_ns, mResult.forget(), OS_ERROR_INVAL);
+ return NS_ERROR_FAILURE;
+ }
+ mDecoder = encoding->NewDecoderWithBOMRemoval();
+ if (!mDecoder) {
+ Fail("DecoderForEncoding"_ns, mResult.forget(), OS_ERROR_INVAL);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ void AfterRead(TimeStamp aDispatchDate,
+ ScopedArrayBufferContents& aBuffer) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ auto src = Span(aBuffer.get().data, aBuffer.get().nbytes);
+
+ CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(src.Length());
+ if (!needed.isValid() ||
+ needed.value() > std::numeric_limits<nsAString::size_type>::max()) {
+ Fail("arithmetics"_ns, mResult.forget(), OS_ERROR_TOO_LARGE);
+ return;
+ }
+
+ nsString resultString;
+ auto resultSpan = resultString.GetMutableData(needed.value(), fallible);
+ if (!resultSpan) {
+ Fail("allocation"_ns, mResult.forget(), OS_ERROR_TOO_LARGE);
+ return;
+ }
+
+ // Yoric said on IRC that this method is normally called for the entire
+ // file, but that's not guaranteed. Retaining the bug that EOF in conversion
+ // isn't handled anywhere.
+ uint32_t result;
+ size_t read;
+ size_t written;
+ std::tie(result, read, written, std::ignore) =
+ mDecoder->DecodeToUTF16(src, *resultSpan, false);
+ MOZ_ASSERT(result == kInputEmpty);
+ MOZ_ASSERT(read == src.Length());
+ MOZ_ASSERT(written <= needed.value());
+ bool ok = resultString.SetLength(written, fallible);
+ if (!ok) {
+ Fail("allocation"_ns, mResult.forget(), OS_ERROR_TOO_LARGE);
+ return;
+ }
+
+ mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate,
+ resultString);
+ Succeed(mResult.forget());
+ }
+
+ 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[], JS::FreePolicy> 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(std::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_ReleaseOnMainThread("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("noOverwrite"_ns, 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("delete"_ns, nullptr, ::GetLastError());
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (::MoveFileW(mPath.get(), mBackupTo.get()) == false) {
+ Fail("rename"_ns, 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("delete"_ns, nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (PR_Rename(path.get(), backupTo.get()) == PR_FAILURE) {
+ Fail("rename"_ns, 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("open"_ns, nullptr, ::GetLastError());
+ return NS_ERROR_FAILURE;
+ }
+
+ file = PR_ImportFile((PROsfd)handle);
+ if (!file) {
+ // |file| is closed by PR_ImportFile
+ Fail("ImportFile"_ns, 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("open"_ns, 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("write"_ns, 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("delete"_ns, nullptr, ::GetLastError());
+ return NS_ERROR_FAILURE;
+ }
+#else
+ if (PR_Delete(path.get()) == PR_FAILURE) {
+ Fail("delete"_ns, nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+#endif // defined(XP_WIN)
+ }
+
+#if defined(XP_WIN)
+ if (::MoveFileW(mTmpPath.get(), mPath.get()) == false) {
+ Fail("rename"_ns, nullptr, ::GetLastError());
+ return NS_ERROR_FAILURE;
+ }
+#else
+ if (PR_Rename(tmpPath.get(), path.get()) == PR_FAILURE) {
+ Fail("rename"_ns, nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+#endif // defined(XP_WIN)
+ }
+
+ if (mFlush) {
+ if (PR_Sync(file) == PR_FAILURE) {
+ Fail("sync"_ns, 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[], JS::FreePolicy> 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,
+ JS::Handle<JS::Value> aOptions,
+ nsINativeOSFileSuccessCallback* aOnSuccess,
+ nsINativeOSFileErrorCallback* aOnError,
+ JSContext* cx) {
+ // Extract options
+ nsCString encoding;
+ uint64_t bytes = UINT64_MAX;
+
+ if (aOptions.isObject()) {
+ dom::NativeOSFileReadOptions dict;
+ if (!dict.Init(cx, aOptions)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (dict.mEncoding.WasPassed()) {
+ CopyUTF16toUTF8(dict.mEncoding.Value(), encoding);
+ }
+
+ if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
+ bytes = 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;
+ if (encoding.IsEmpty()) {
+ event = new DoReadToTypedArrayEvent(aPath, bytes, onSuccessHandle,
+ onErrorHandle);
+ } else {
+ event = new DoReadToStringEvent(aPath, encoding, bytes, 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);
+}
+
+// Note: This method steals the contents of `aBuffer`.
+NS_IMETHODIMP
+NativeOSFileInternalsService::WriteAtomic(
+ const nsAString& aPath, JS::Handle<JS::Value> aBuffer,
+ JS::Handle<JS::Value> 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[], JS::FreePolicy> buffer;
+ int32_t bytes;
+
+ // The incoming buffer must be an Object.
+ if (!aBuffer.isObject()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JS::Rooted<JSObject*> bufferObject(cx, nullptr);
+ if (!JS_ValueToObject(cx, aBuffer, &bufferObject)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!JS::IsArrayBufferObject(bufferObject.get())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ {
+ // Throw for large ArrayBuffers to prevent truncation.
+ size_t len = JS::GetArrayBufferByteLength(bufferObject.get());
+ if (len > INT32_MAX) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ bytes = len;
+ }
+ 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, std::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