diff options
Diffstat (limited to 'components/osfile/NativeOSFileInternals.cpp')
-rw-r--r-- | components/osfile/NativeOSFileInternals.cpp | 915 |
1 files changed, 915 insertions, 0 deletions
diff --git a/components/osfile/NativeOSFileInternals.cpp b/components/osfile/NativeOSFileInternals.cpp new file mode 100644 index 000000000..801c8c37d --- /dev/null +++ b/components/osfile/NativeOSFileInternals.cpp @@ -0,0 +1,915 @@ +/* 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 <algorithm> +#if defined(XP_UNIX) +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/uio.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(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM): + Scoped<ScopedArrayBufferContentsTraits>(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT) + { } + explicit ScopedArrayBufferContents(const ArrayBufferContents& v + MOZ_GUARD_OBJECT_NOTIFIER_PARAM): + Scoped<ScopedArrayBufferContentsTraits>(v MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) + { } + 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_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. + */ + 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. + * @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<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::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<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); + + 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; + } + // 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<nsINativeOSFileSuccessCallback>& aOnSuccess, + nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError, + already_AddRefed<AbstractResult>& 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<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) + : 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) + : 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<ErrorEvent> 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<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(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(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<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(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<nsINativeOSFileSuccessCallback>& aOnSuccess, + nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& 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<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() { + // 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<const char*>(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<nsIUnicodeDecoder> mDecoder; + RefPtr<StringResult> 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<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess); + nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> onSuccessHandle( + new nsMainThreadPtrHolder<nsINativeOSFileSuccessCallback>(onSuccess)); + nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError); + nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> onErrorHandle( + new nsMainThreadPtrHolder<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); +} + +} // namespace mozilla + |