/* 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 "nsString.h" #include "nsNetCID.h" #include "nsThreadUtils.h" #include "nsXPCOMCID.h" #include "nsCycleCollectionParticipant.h" #include "nsServiceManagerUtils.h" #include "nsProxyRelease.h" #include "nsINativeOSFileInternals.h" #include "NativeOSFileInternals.h" #include "mozilla/dom/NativeOSFileInternalsBinding.h" #include "nsIUnicodeDecoder.h" #include "nsIEventTarget.h" #include "mozilla/dom/EncodingUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/Scoped.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/TimeStamp.h" #include "prio.h" #include "prerror.h" #include "private/pprio.h" #include "jsapi.h" #include "jsfriendapi.h" #include "js/Utility.h" #include "xpcpublic.h" #include #if defined(XP_UNIX) #include #include #include #include #include #endif // defined (XP_UNIX) #if defined(XP_WIN) #include #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 { explicit ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM): Scoped(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT) { } explicit ScopedArrayBufferContents(const ArrayBufferContents& v MOZ_GUARD_OBJECT_NOTIFIER_PARAM): Scoped(v MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) { } ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) { Scoped::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_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_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()); DropJSData(); mozilla::DropJSObjects(this); } virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0; private: TimeStamp mStartDate; TimeDuration mDispatchDuration; TimeDuration mExecutionDuration; JS::Heap 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::MutableHandleValue 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::MutableHandleValue aResult) override; private: nsString mContents; }; nsresult StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue 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::MutableHandleValue aResult) override; private: ScopedArrayBufferContents mContents; }; nsresult TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle 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); JS::Rooted arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.nbytes, contents.data)); if (!arrayBuffer) { return NS_ERROR_OUT_OF_MEMORY; } JS::Rooted result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer, 0, contents.nbytes)); if (!result) { return NS_ERROR_OUT_OF_MEMORY; } // The memory of contents has been allocated on a thread that // doesn't have a JSRuntime, hence without a context. Now that we // have a context, attach the memory to where it belongs. JS_updateMallocCounter(cx, contents.nbytes); mContents.forget(); aResult.setObject(*result); return NS_OK; } //////// Callback events /** * An event used to notify asynchronously of an error. */ class ErrorEvent 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. */ ErrorEvent(nsMainThreadPtrHandle& aOnSuccess, nsMainThreadPtrHandle& aOnError, already_AddRefed& aDiscardedResult, const nsACString& aOperation, int32_t aOSError) : 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 mOnSuccess; nsMainThreadPtrHandle mOnError; RefPtr 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& aOnSuccess, nsMainThreadPtrHandle& aOnError, already_AddRefed& aResult) : 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 mOnSuccess; nsMainThreadPtrHandle mOnError; RefPtr mResult; }; //////// Action events /** * Base class shared by actions. */ class AbstractDoEvent: public Runnable { public: AbstractDoEvent(nsMainThreadPtrHandle& aOnSuccess, nsMainThreadPtrHandle& aOnError) : 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&& aDiscardedResult, int32_t aOSError = 0) { Resolve(); RefPtr event = new ErrorEvent(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(event.forget()); } } /** * Succeed, asynchronously. */ void Succeed(already_AddRefed&& aResult) { Resolve(); RefPtr 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(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 mOnSuccess; nsMainThreadPtrHandle 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& aOnSuccess, nsMainThreadPtrHandle& 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(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 // On other platforms, PR_OpenFile will do. NS_ConvertUTF16toUTF8 path(mPath); file = PR_OpenFile(path.get(), PR_RDONLY, 0); if (!file) { Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError()); return NS_ERROR_FAILURE; } #endif // defined(XP_XIN) PRFileInfo64 stat; if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) { Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError()); return NS_ERROR_FAILURE; } uint64_t bytes = std::min((uint64_t)stat.size, mBytes); if (bytes > UINT32_MAX) { Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL); return NS_ERROR_FAILURE; } if (!aBuffer.Allocate(bytes)) { Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM); return NS_ERROR_FAILURE; } uint64_t total_read = 0; int32_t just_read = 0; char* dest_chars = reinterpret_cast(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(NS_LITERAL_CSTRING("read"), 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(NS_LITERAL_CSTRING("read"), 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& aOnSuccess, nsMainThreadPtrHandle& aOnError) : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError) , mResult(new TypedArrayResult(TimeStamp::Now())) { } ~DoReadToTypedArrayEvent() { // If AbstractReadEvent::Run() has bailed out, we may need to cleanup // mResult, which is main-thread only data if (!mResult) { return; } NS_ReleaseOnMainThread(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 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& aOnSuccess, nsMainThreadPtrHandle& aOnError) : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError) , mEncoding(aEncoding) , mResult(new StringResult(TimeStamp::Now())) { } ~DoReadToStringEvent() { // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup // mResult, which is main-thread only data if (!mResult) { return; } NS_ReleaseOnMainThread(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()); nsAutoCString encodingName; if (!dom::EncodingUtils::FindEncodingForLabel(mEncoding, encodingName)) { Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL); return NS_ERROR_FAILURE; } mDecoder = dom::EncodingUtils::DecoderForEncoding(encodingName); if (!mDecoder) { Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL); return NS_ERROR_FAILURE; } return NS_OK; } void AfterRead(TimeStamp aDispatchDate, ScopedArrayBufferContents& aBuffer) override { MOZ_ASSERT(!NS_IsMainThread()); int32_t maxChars; const char* sourceChars = reinterpret_cast(aBuffer.get().data); int32_t sourceBytes = aBuffer.get().nbytes; if (sourceBytes < 0) { Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); return; } nsresult rv = mDecoder->GetMaxLength(sourceChars, sourceBytes, &maxChars); if (NS_FAILED(rv)) { Fail(NS_LITERAL_CSTRING("GetMaxLength"), mResult.forget(), OS_ERROR_INVAL); return; } if (maxChars < 0) { Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); return; } nsString resultString; resultString.SetLength(maxChars); if (resultString.Length() != (nsString::size_type)maxChars) { Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE); return; } rv = mDecoder->Convert(sourceChars, &sourceBytes, resultString.BeginWriting(), &maxChars); MOZ_ASSERT(NS_SUCCEEDED(rv)); resultString.SetLength(maxChars); mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString); Succeed(mResult.forget()); } private: nsCString mEncoding; nsCOMPtr mDecoder; RefPtr mResult; }; } // namespace // The OS.File service NS_IMPL_ISUPPORTS(NativeOSFileInternalsService, nsINativeOSFileInternalsService); NS_IMETHODIMP NativeOSFileInternalsService::Read(const nsAString& aPath, JS::HandleValue 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 onSuccess(aOnSuccess); nsMainThreadPtrHandle onSuccessHandle( new nsMainThreadPtrHolder(onSuccess)); nsCOMPtr onError(aOnError); nsMainThreadPtrHandle onErrorHandle( new nsMainThreadPtrHolder(onError)); RefPtr event; if (encoding.IsEmpty()) { event = new DoReadToTypedArrayEvent(aPath, bytes, onSuccessHandle, onErrorHandle); } else { event = new DoReadToStringEvent(aPath, encoding, bytes, onSuccessHandle, onErrorHandle); } nsresult rv; nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) { return rv; } return target->Dispatch(event, NS_DISPATCH_NORMAL); } } // namespace mozilla