1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/osfile/NativeOSFileInternals.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,915 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/** 1.9 + * Native implementation of some OS.File operations. 1.10 + */ 1.11 + 1.12 +#include "nsString.h" 1.13 +#include "nsNetCID.h" 1.14 +#include "nsThreadUtils.h" 1.15 +#include "nsXPCOMCID.h" 1.16 +#include "nsCycleCollectionParticipant.h" 1.17 +#include "nsServiceManagerUtils.h" 1.18 +#include "nsProxyRelease.h" 1.19 + 1.20 +#include "nsINativeOSFileInternals.h" 1.21 +#include "NativeOSFileInternals.h" 1.22 +#include "mozilla/dom/NativeOSFileInternalsBinding.h" 1.23 + 1.24 +#include "nsIUnicodeDecoder.h" 1.25 +#include "nsIEventTarget.h" 1.26 + 1.27 +#include "mozilla/dom/EncodingUtils.h" 1.28 +#include "mozilla/DebugOnly.h" 1.29 +#include "mozilla/Scoped.h" 1.30 +#include "mozilla/HoldDropJSObjects.h" 1.31 +#include "mozilla/TimeStamp.h" 1.32 + 1.33 +#include "prio.h" 1.34 +#include "prerror.h" 1.35 +#include "private/pprio.h" 1.36 + 1.37 +#include "jsapi.h" 1.38 +#include "jsfriendapi.h" 1.39 +#include "js/Utility.h" 1.40 +#include "xpcpublic.h" 1.41 + 1.42 +#include <algorithm> 1.43 +#if defined(XP_UNIX) 1.44 +#include <unistd.h> 1.45 +#include <errno.h> 1.46 +#include <fcntl.h> 1.47 +#include <sys/stat.h> 1.48 +#include <sys/uio.h> 1.49 +#endif // defined (XP_UNIX) 1.50 + 1.51 +#if defined(XP_WIN) 1.52 +#include <windows.h> 1.53 +#endif // defined (XP_WIN) 1.54 + 1.55 +namespace mozilla { 1.56 + 1.57 +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close) 1.58 + 1.59 +namespace { 1.60 + 1.61 +// Utilities for safely manipulating ArrayBuffer contents even in the 1.62 +// absence of a JSContext. 1.63 + 1.64 +/** 1.65 + * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate 1.66 + * this instead of a void* buffer, as this lets us transfer data across threads 1.67 + * and into JavaScript without copy. 1.68 + */ 1.69 +struct ArrayBufferContents { 1.70 + /** 1.71 + * The data of the ArrayBuffer. This is the pointer manipulated to 1.72 + * read/write the contents of the buffer. 1.73 + */ 1.74 + uint8_t* data; 1.75 + /** 1.76 + * The number of bytes in the ArrayBuffer. 1.77 + */ 1.78 + size_t nbytes; 1.79 +}; 1.80 + 1.81 +/** 1.82 + * RAII for ArrayBufferContents. 1.83 + */ 1.84 +struct ScopedArrayBufferContentsTraits { 1.85 + typedef ArrayBufferContents type; 1.86 + const static type empty() { 1.87 + type result = {0, 0}; 1.88 + return result; 1.89 + } 1.90 + const static void release(type ptr) { 1.91 + js_free(ptr.data); 1.92 + ptr.data = nullptr; 1.93 + ptr.nbytes = 0; 1.94 + } 1.95 +}; 1.96 + 1.97 +struct ScopedArrayBufferContents: public Scoped<ScopedArrayBufferContentsTraits> { 1.98 + ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM): 1.99 + Scoped<ScopedArrayBufferContentsTraits>(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT) 1.100 + { } 1.101 + ScopedArrayBufferContents(const ArrayBufferContents& v 1.102 + MOZ_GUARD_OBJECT_NOTIFIER_PARAM): 1.103 + Scoped<ScopedArrayBufferContentsTraits>(v MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) 1.104 + { } 1.105 + ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) { 1.106 + Scoped<ScopedArrayBufferContentsTraits>::operator=(ptr); 1.107 + return *this; 1.108 + } 1.109 + 1.110 + /** 1.111 + * Request memory for this ArrayBufferContent. This memory may later 1.112 + * be used to create an ArrayBuffer object (possibly on another 1.113 + * thread) without copy. 1.114 + * 1.115 + * @return true In case of success, false otherwise. 1.116 + */ 1.117 + bool Allocate(uint32_t length) { 1.118 + dispose(); 1.119 + ArrayBufferContents& value = rwget(); 1.120 + void *ptr = JS_AllocateArrayBufferContents(/*no context available*/nullptr, length); 1.121 + if (ptr) { 1.122 + value.data = (uint8_t *) ptr; 1.123 + value.nbytes = length; 1.124 + return true; 1.125 + } 1.126 + return false; 1.127 + } 1.128 +private: 1.129 + explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) MOZ_DELETE; 1.130 + ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) MOZ_DELETE; 1.131 +}; 1.132 + 1.133 +///////// Cross-platform issues 1.134 + 1.135 +// Platform specific constants. As OS.File always uses OS-level 1.136 +// errors, we need to map a few high-level errors to OS-level 1.137 +// constants. 1.138 +#if defined(XP_UNIX) 1.139 +#define OS_ERROR_NOMEM ENOMEM 1.140 +#define OS_ERROR_INVAL EINVAL 1.141 +#define OS_ERROR_TOO_LARGE EFBIG 1.142 +#define OS_ERROR_RACE EIO 1.143 +#elif defined(XP_WIN) 1.144 +#define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY 1.145 +#define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS 1.146 +#define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE 1.147 +#define OS_ERROR_RACE ERROR_SHARING_VIOLATION 1.148 +#else 1.149 +#error "We do not have platform-specific constants for this platform" 1.150 +#endif 1.151 + 1.152 +///////// Results of OS.File operations 1.153 + 1.154 +/** 1.155 + * Base class for results passed to the callbacks. 1.156 + * 1.157 + * This base class implements caching of JS values returned to the client. 1.158 + * We make use of this caching in derived classes e.g. to avoid accidents 1.159 + * when we transfer data allocated on another thread into JS. Note that 1.160 + * this caching can lead to cycles (e.g. if a client adds a back-reference 1.161 + * in the JS value), so we implement all Cycle Collector primitives in 1.162 + * AbstractResult. 1.163 + */ 1.164 +class AbstractResult: public nsINativeOSFileResult { 1.165 +public: 1.166 + NS_DECL_NSINATIVEOSFILERESULT 1.167 + NS_DECL_CYCLE_COLLECTING_ISUPPORTS 1.168 + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult) 1.169 + 1.170 + /** 1.171 + * Construct the result object. Must be called on the main thread 1.172 + * as the AbstractResult is cycle-collected. 1.173 + * 1.174 + * @param aStartDate The instant at which the operation was 1.175 + * requested. Used to collect Telemetry statistics. 1.176 + */ 1.177 + AbstractResult(TimeStamp aStartDate) 1.178 + : mStartDate(aStartDate) 1.179 + { 1.180 + MOZ_ASSERT(NS_IsMainThread()); 1.181 + mozilla::HoldJSObjects(this); 1.182 + } 1.183 + virtual ~AbstractResult() { 1.184 + MOZ_ASSERT(NS_IsMainThread()); 1.185 + DropJSData(); 1.186 + mozilla::DropJSObjects(this); 1.187 + } 1.188 + 1.189 + /** 1.190 + * Setup the AbstractResult once data is available. 1.191 + * 1.192 + * @param aDispatchDate The instant at which the IO thread received 1.193 + * the operation request. Used to collect Telemetry statistics. 1.194 + * @param aExecutionDuration The duration of the operation on the 1.195 + * IO thread. 1.196 + */ 1.197 + void Init(TimeStamp aDispatchDate, 1.198 + TimeDuration aExecutionDuration) { 1.199 + MOZ_ASSERT(!NS_IsMainThread()); 1.200 + 1.201 + mDispatchDuration = (aDispatchDate - mStartDate); 1.202 + mExecutionDuration = aExecutionDuration; 1.203 + } 1.204 + 1.205 + /** 1.206 + * Drop any data that could lead to a cycle. 1.207 + */ 1.208 + void DropJSData() { 1.209 + mCachedResult = JS::UndefinedValue(); 1.210 + } 1.211 + 1.212 +protected: 1.213 + virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0; 1.214 + 1.215 +private: 1.216 + TimeStamp mStartDate; 1.217 + TimeDuration mDispatchDuration; 1.218 + TimeDuration mExecutionDuration; 1.219 + JS::Heap<JS::Value> mCachedResult; 1.220 +}; 1.221 + 1.222 +NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult) 1.223 +NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult) 1.224 + 1.225 +NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult) 1.226 + 1.227 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult) 1.228 + NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult) 1.229 + NS_INTERFACE_MAP_ENTRY(nsISupports) 1.230 +NS_INTERFACE_MAP_END 1.231 + 1.232 +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult) 1.233 + NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedResult) 1.234 +NS_IMPL_CYCLE_COLLECTION_TRACE_END 1.235 + 1.236 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult) 1.237 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS 1.238 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.239 + 1.240 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult) 1.241 + tmp->DropJSData(); 1.242 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.243 + 1.244 +NS_IMETHODIMP 1.245 +AbstractResult::GetDispatchDurationMS(double *aDispatchDuration) 1.246 +{ 1.247 + *aDispatchDuration = mDispatchDuration.ToMilliseconds(); 1.248 + return NS_OK; 1.249 +} 1.250 + 1.251 +NS_IMETHODIMP 1.252 +AbstractResult::GetExecutionDurationMS(double *aExecutionDuration) 1.253 +{ 1.254 + *aExecutionDuration = mExecutionDuration.ToMilliseconds(); 1.255 + return NS_OK; 1.256 +} 1.257 + 1.258 +NS_IMETHODIMP 1.259 +AbstractResult::GetResult(JSContext *cx, JS::MutableHandleValue aResult) 1.260 +{ 1.261 + if (mCachedResult.isUndefined()) { 1.262 + nsresult rv = GetCacheableResult(cx, aResult); 1.263 + if (NS_FAILED(rv)) { 1.264 + return rv; 1.265 + } 1.266 + mCachedResult = aResult; 1.267 + return NS_OK; 1.268 + } 1.269 + aResult.set(mCachedResult); 1.270 + return NS_OK; 1.271 +} 1.272 + 1.273 +/** 1.274 + * Return a result as a string. 1.275 + * 1.276 + * In this implementation, attribute |result| is a string. Strings are 1.277 + * passed to JS without copy. 1.278 + */ 1.279 +class StringResult MOZ_FINAL : public AbstractResult 1.280 +{ 1.281 +public: 1.282 + StringResult(TimeStamp aStartDate) 1.283 + : AbstractResult(aStartDate) 1.284 + { 1.285 + } 1.286 + 1.287 + /** 1.288 + * Initialize the object once the contents of the result as available. 1.289 + * 1.290 + * @param aContents The string to pass to JavaScript. Ownership of the 1.291 + * string and its contents is passed to StringResult. The string must 1.292 + * be valid UTF-16. 1.293 + */ 1.294 + void Init(TimeStamp aDispatchDate, 1.295 + TimeDuration aExecutionDuration, 1.296 + nsString& aContents) { 1.297 + AbstractResult::Init(aDispatchDate, aExecutionDuration); 1.298 + mContents = aContents; 1.299 + } 1.300 + 1.301 +protected: 1.302 + nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE; 1.303 + 1.304 +private: 1.305 + nsString mContents; 1.306 +}; 1.307 + 1.308 +nsresult 1.309 +StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) 1.310 +{ 1.311 + MOZ_ASSERT(NS_IsMainThread()); 1.312 + MOZ_ASSERT(mContents.get()); 1.313 + 1.314 + // Convert mContents to a js string without copy. Note that this 1.315 + // may have the side-effect of stealing the contents of the string 1.316 + // from XPCOM and into JS. 1.317 + if (!xpc::StringToJsval(cx, mContents, aResult)) { 1.318 + return NS_ERROR_FAILURE; 1.319 + } 1.320 + return NS_OK; 1.321 +} 1.322 + 1.323 + 1.324 +/** 1.325 + * Return a result as a Uint8Array. 1.326 + * 1.327 + * In this implementation, attribute |result| is a Uint8Array. The array 1.328 + * is passed to JS without memory copy. 1.329 + */ 1.330 +class TypedArrayResult MOZ_FINAL : public AbstractResult 1.331 +{ 1.332 +public: 1.333 + TypedArrayResult(TimeStamp aStartDate) 1.334 + : AbstractResult(aStartDate) 1.335 + { 1.336 + } 1.337 + 1.338 + /** 1.339 + * @param aContents The contents to pass to JS. Calling this method. 1.340 + * transmits ownership of the ArrayBufferContents to the TypedArrayResult. 1.341 + * Do not reuse this value anywhere else. 1.342 + */ 1.343 + void Init(TimeStamp aDispatchDate, 1.344 + TimeDuration aExecutionDuration, 1.345 + ArrayBufferContents aContents) { 1.346 + AbstractResult::Init(aDispatchDate, aExecutionDuration); 1.347 + mContents = aContents; 1.348 + } 1.349 + 1.350 +protected: 1.351 + nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE; 1.352 +private: 1.353 + ScopedArrayBufferContents mContents; 1.354 +}; 1.355 + 1.356 +nsresult 1.357 +TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle<JS::Value> aResult) 1.358 +{ 1.359 + MOZ_ASSERT(NS_IsMainThread()); 1.360 + // We cannot simply construct a typed array using contents.data as 1.361 + // this would allow us to have several otherwise unrelated 1.362 + // ArrayBuffers with the same underlying C buffer. As this would be 1.363 + // very unsafe, we need to cache the result once we have it. 1.364 + 1.365 + const ArrayBufferContents& contents = mContents.get(); 1.366 + MOZ_ASSERT(contents.data); 1.367 + 1.368 + JS::Rooted<JSObject*> 1.369 + arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.nbytes, contents.data)); 1.370 + if (!arrayBuffer) { 1.371 + return NS_ERROR_OUT_OF_MEMORY; 1.372 + } 1.373 + 1.374 + JS::Rooted<JSObject*> 1.375 + result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer, 1.376 + 0, contents.nbytes)); 1.377 + if (!result) { 1.378 + return NS_ERROR_OUT_OF_MEMORY; 1.379 + } 1.380 + // The memory of contents has been allocated on a thread that 1.381 + // doesn't have a JSRuntime, hence without a context. Now that we 1.382 + // have a context, attach the memory to where it belongs. 1.383 + JS_updateMallocCounter(cx, contents.nbytes); 1.384 + mContents.forget(); 1.385 + 1.386 + aResult.setObject(*result); 1.387 + return NS_OK; 1.388 +} 1.389 + 1.390 +//////// Callback events 1.391 + 1.392 +/** 1.393 + * An event used to notify asynchronously of an error. 1.394 + */ 1.395 +class ErrorEvent MOZ_FINAL : public nsRunnable { 1.396 +public: 1.397 + /** 1.398 + * @param aOnSuccess The success callback. 1.399 + * @param aOnError The error callback. 1.400 + * @param aDiscardedResult The discarded result. 1.401 + * @param aOperation The name of the operation, used for error reporting. 1.402 + * @param aOSError The OS error of the operation, as returned by errno/ 1.403 + * GetLastError(). 1.404 + * 1.405 + * Note that we pass both the success callback and the error 1.406 + * callback, as well as the discarded result to ensure that they are 1.407 + * all released on the main thread, rather than on the IO thread 1.408 + * (which would hopefully segfault). Also, we pass the callbacks as 1.409 + * alread_AddRefed to ensure that we do not manipulate main-thread 1.410 + * only refcounters off the main thread. 1.411 + */ 1.412 + ErrorEvent(already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess, 1.413 + already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError, 1.414 + already_AddRefed<AbstractResult>& aDiscardedResult, 1.415 + const nsACString& aOperation, 1.416 + int32_t aOSError) 1.417 + : mOnSuccess(aOnSuccess) 1.418 + , mOnError(aOnError) 1.419 + , mDiscardedResult(aDiscardedResult) 1.420 + , mOSError(aOSError) 1.421 + , mOperation(aOperation) 1.422 + { 1.423 + MOZ_ASSERT(!NS_IsMainThread()); 1.424 + } 1.425 + 1.426 + NS_METHOD Run() { 1.427 + MOZ_ASSERT(NS_IsMainThread()); 1.428 + (void)mOnError->Complete(mOperation, mOSError); 1.429 + 1.430 + // Ensure that the callbacks are released on the main thread. 1.431 + mOnSuccess = nullptr; 1.432 + mOnError = nullptr; 1.433 + mDiscardedResult = nullptr; 1.434 + 1.435 + return NS_OK; 1.436 + } 1.437 + private: 1.438 + // The callbacks. Maintained as nsRefPtr as they are generally 1.439 + // xpconnect values, which cannot be manipulated with nsCOMPtr off 1.440 + // the main thread. We store both the success callback and the 1.441 + // error callback to ensure that they are safely released on the 1.442 + // main thread. 1.443 + nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess; 1.444 + nsRefPtr<nsINativeOSFileErrorCallback> mOnError; 1.445 + nsRefPtr<AbstractResult> mDiscardedResult; 1.446 + int32_t mOSError; 1.447 + nsCString mOperation; 1.448 +}; 1.449 + 1.450 +/** 1.451 + * An event used to notify of a success. 1.452 + */ 1.453 +class SuccessEvent MOZ_FINAL : public nsRunnable { 1.454 +public: 1.455 + /** 1.456 + * @param aOnSuccess The success callback. 1.457 + * @param aOnError The error callback. 1.458 + * 1.459 + * Note that we pass both the success callback and the error 1.460 + * callback to ensure that they are both released on the main 1.461 + * thread, rather than on the IO thread (which would hopefully 1.462 + * segfault). Also, we pass them as alread_AddRefed to ensure that 1.463 + * we do not manipulate xpconnect refcounters off the main thread 1.464 + * (which is illegal). 1.465 + */ 1.466 + SuccessEvent(already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess, 1.467 + already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError, 1.468 + already_AddRefed<nsINativeOSFileResult>& aResult) 1.469 + : mOnSuccess(aOnSuccess) 1.470 + , mOnError(aOnError) 1.471 + , mResult(aResult) 1.472 + { 1.473 + MOZ_ASSERT(!NS_IsMainThread()); 1.474 + } 1.475 + 1.476 + NS_METHOD Run() { 1.477 + MOZ_ASSERT(NS_IsMainThread()); 1.478 + (void)mOnSuccess->Complete(mResult); 1.479 + 1.480 + // Ensure that the callbacks are released on the main thread. 1.481 + mOnSuccess = nullptr; 1.482 + mOnError = nullptr; 1.483 + mResult = nullptr; 1.484 + 1.485 + return NS_OK; 1.486 + } 1.487 + private: 1.488 + // The callbacks. Maintained as nsRefPtr as they are generally 1.489 + // xpconnect values, which cannot be manipulated with nsCOMPtr off 1.490 + // the main thread. We store both the success callback and the 1.491 + // error callback to ensure that they are safely released on the 1.492 + // main thread. 1.493 + nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess; 1.494 + nsRefPtr<nsINativeOSFileErrorCallback> mOnError; 1.495 + nsRefPtr<nsINativeOSFileResult> mResult; 1.496 +}; 1.497 + 1.498 + 1.499 +//////// Action events 1.500 + 1.501 +/** 1.502 + * Base class shared by actions. 1.503 + */ 1.504 +class AbstractDoEvent: public nsRunnable { 1.505 +public: 1.506 + AbstractDoEvent(already_AddRefed<nsINativeOSFileSuccessCallback>& aOnSuccess, 1.507 + already_AddRefed<nsINativeOSFileErrorCallback>& aOnError) 1.508 + : mOnSuccess(aOnSuccess) 1.509 + , mOnError(aOnError) 1.510 +#if defined(DEBUG) 1.511 + , mResolved(false) 1.512 +#endif // defined(DEBUG) 1.513 + { 1.514 + MOZ_ASSERT(NS_IsMainThread()); 1.515 + } 1.516 + 1.517 + /** 1.518 + * Fail, asynchronously. 1.519 + */ 1.520 + void Fail(const nsACString& aOperation, 1.521 + already_AddRefed<AbstractResult>&& aDiscardedResult, 1.522 + int32_t aOSError = 0) { 1.523 + Resolve(); 1.524 + nsRefPtr<ErrorEvent> event = new ErrorEvent(mOnSuccess.forget(), 1.525 + mOnError.forget(), 1.526 + aDiscardedResult, 1.527 + aOperation, 1.528 + aOSError); 1.529 + nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 1.530 + if (NS_FAILED(rv)) { 1.531 + // Last ditch attempt to release on the main thread - some of 1.532 + // the members of event are not thread-safe, so letting the 1.533 + // pointer go out of scope would cause a crash. 1.534 + nsCOMPtr<nsIThread> main = do_GetMainThread(); 1.535 + NS_ProxyRelease(main, event); 1.536 + } 1.537 + } 1.538 + 1.539 + /** 1.540 + * Succeed, asynchronously. 1.541 + */ 1.542 + void Succeed(already_AddRefed<nsINativeOSFileResult>&& aResult) { 1.543 + Resolve(); 1.544 + nsRefPtr<SuccessEvent> event = new SuccessEvent(mOnSuccess.forget(), 1.545 + mOnError.forget(), 1.546 + aResult); 1.547 + nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 1.548 + if (NS_FAILED(rv)) { 1.549 + // Last ditch attempt to release on the main thread - some of 1.550 + // the members of event are not thread-safe, so letting the 1.551 + // pointer go out of scope would cause a crash. 1.552 + nsCOMPtr<nsIThread> main = do_GetMainThread(); 1.553 + NS_ProxyRelease(main, event); 1.554 + } 1.555 + 1.556 + } 1.557 + 1.558 +private: 1.559 + 1.560 + /** 1.561 + * Mark the event as complete, for debugging purposes. 1.562 + */ 1.563 + void Resolve() { 1.564 +#if defined(DEBUG) 1.565 + MOZ_ASSERT(!mResolved); 1.566 + mResolved = true; 1.567 +#endif // defined(DEBUG) 1.568 + } 1.569 + 1.570 +private: 1.571 + nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess; 1.572 + nsRefPtr<nsINativeOSFileErrorCallback> mOnError; 1.573 +#if defined(DEBUG) 1.574 + // |true| once the action is complete 1.575 + bool mResolved; 1.576 +#endif // defined(DEBUG) 1.577 +}; 1.578 + 1.579 +/** 1.580 + * An abstract event implementing reading from a file. 1.581 + * 1.582 + * Concrete subclasses are responsible for handling the 1.583 + * data obtained from the file and possibly post-processing it. 1.584 + */ 1.585 +class AbstractReadEvent: public AbstractDoEvent { 1.586 +public: 1.587 + /** 1.588 + * @param aPath The path of the file. 1.589 + */ 1.590 + AbstractReadEvent(const nsAString& aPath, 1.591 + const uint64_t aBytes, 1.592 + already_AddRefed<nsINativeOSFileSuccessCallback>& aOnSuccess, 1.593 + already_AddRefed<nsINativeOSFileErrorCallback>& aOnError) 1.594 + : AbstractDoEvent(aOnSuccess, aOnError) 1.595 + , mPath(aPath) 1.596 + , mBytes(aBytes) 1.597 + { 1.598 + MOZ_ASSERT(NS_IsMainThread()); 1.599 + } 1.600 + 1.601 + NS_METHOD Run() MOZ_OVERRIDE { 1.602 + MOZ_ASSERT(!NS_IsMainThread()); 1.603 + TimeStamp dispatchDate = TimeStamp::Now(); 1.604 + 1.605 + nsresult rv = BeforeRead(); 1.606 + if (NS_FAILED(rv)) { 1.607 + // Error reporting is handled by BeforeRead(); 1.608 + return NS_OK; 1.609 + } 1.610 + 1.611 + ScopedArrayBufferContents buffer; 1.612 + rv = Read(buffer); 1.613 + if (NS_FAILED(rv)) { 1.614 + // Error reporting is handled by Read(); 1.615 + return NS_OK; 1.616 + } 1.617 + 1.618 + AfterRead(dispatchDate, buffer); 1.619 + return NS_OK; 1.620 + } 1.621 + 1.622 + private: 1.623 + /** 1.624 + * Read synchronously. 1.625 + * 1.626 + * Must be called off the main thread. 1.627 + * 1.628 + * @param aBuffer The destination buffer. 1.629 + */ 1.630 + nsresult Read(ScopedArrayBufferContents& aBuffer) 1.631 + { 1.632 + MOZ_ASSERT(!NS_IsMainThread()); 1.633 + 1.634 + ScopedPRFileDesc file; 1.635 +#if defined(XP_WIN) 1.636 + // On Windows, we can't use PR_OpenFile because it doesn't 1.637 + // handle UTF-16 encoding, which is pretty bad. In addition, 1.638 + // PR_OpenFile opens files without sharing, which is not the 1.639 + // general semantics of OS.File. 1.640 + HANDLE handle = 1.641 + ::CreateFileW(mPath.get(), 1.642 + GENERIC_READ, 1.643 + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 1.644 + /*Security attributes*/nullptr, 1.645 + OPEN_EXISTING, 1.646 + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 1.647 + /*Template file*/ nullptr); 1.648 + 1.649 + if (handle == INVALID_HANDLE_VALUE) { 1.650 + Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError()); 1.651 + return NS_ERROR_FAILURE; 1.652 + } 1.653 + 1.654 + file = PR_ImportFile((PROsfd)handle); 1.655 + if (!file) { 1.656 + // |file| is closed by PR_ImportFile 1.657 + Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError()); 1.658 + return NS_ERROR_FAILURE; 1.659 + } 1.660 + 1.661 +#else 1.662 + // On other platforms, PR_OpenFile will do. 1.663 + NS_ConvertUTF16toUTF8 path(mPath); 1.664 + file = PR_OpenFile(path.get(), PR_RDONLY, 0); 1.665 + if (!file) { 1.666 + Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError()); 1.667 + return NS_ERROR_FAILURE; 1.668 + } 1.669 + 1.670 +#endif // defined(XP_XIN) 1.671 + 1.672 + PRFileInfo64 stat; 1.673 + if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) { 1.674 + Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError()); 1.675 + return NS_ERROR_FAILURE; 1.676 + } 1.677 + 1.678 + uint64_t bytes = std::min((uint64_t)stat.size, mBytes); 1.679 + if (bytes > UINT32_MAX) { 1.680 + Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL); 1.681 + return NS_ERROR_FAILURE; 1.682 + } 1.683 + 1.684 + if (!aBuffer.Allocate(bytes)) { 1.685 + Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM); 1.686 + return NS_ERROR_FAILURE; 1.687 + } 1.688 + 1.689 + uint64_t total_read = 0; 1.690 + int32_t just_read = 0; 1.691 + char* dest_chars = reinterpret_cast<char*>(aBuffer.rwget().data); 1.692 + do { 1.693 + just_read = PR_Read(file, dest_chars + total_read, 1.694 + std::min(uint64_t(PR_INT32_MAX), bytes - total_read)); 1.695 + if (just_read == -1) { 1.696 + Fail(NS_LITERAL_CSTRING("read"), nullptr, PR_GetOSError()); 1.697 + return NS_ERROR_FAILURE; 1.698 + } 1.699 + total_read += just_read; 1.700 + } while (just_read != 0 && total_read < bytes); 1.701 + if (total_read != bytes) { 1.702 + // We seem to have a race condition here. 1.703 + Fail(NS_LITERAL_CSTRING("read"), nullptr, OS_ERROR_RACE); 1.704 + return NS_ERROR_FAILURE; 1.705 + } 1.706 + 1.707 + return NS_OK; 1.708 + } 1.709 + 1.710 +protected: 1.711 + /** 1.712 + * Any steps that need to be taken before reading. 1.713 + * 1.714 + * In case of error, this method should call Fail() and return 1.715 + * a failure code. 1.716 + */ 1.717 + virtual 1.718 + nsresult BeforeRead() { 1.719 + return NS_OK; 1.720 + } 1.721 + 1.722 + /** 1.723 + * Proceed after reading. 1.724 + */ 1.725 + virtual 1.726 + void AfterRead(TimeStamp aDispatchDate, ScopedArrayBufferContents& aBuffer) = 0; 1.727 + 1.728 + protected: 1.729 + const nsString mPath; 1.730 + const uint64_t mBytes; 1.731 +}; 1.732 + 1.733 +/** 1.734 + * An implementation of a Read event that provides the data 1.735 + * as a TypedArray. 1.736 + */ 1.737 +class DoReadToTypedArrayEvent MOZ_FINAL : public AbstractReadEvent { 1.738 +public: 1.739 + DoReadToTypedArrayEvent(const nsAString& aPath, 1.740 + const uint32_t aBytes, 1.741 + already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess, 1.742 + already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError) 1.743 + : AbstractReadEvent(aPath, aBytes, 1.744 + aOnSuccess, aOnError) 1.745 + , mResult(new TypedArrayResult(TimeStamp::Now())) 1.746 + { } 1.747 + 1.748 + ~DoReadToTypedArrayEvent() { 1.749 + // If AbstractReadEvent::Run() has bailed out, we may need to cleanup 1.750 + // mResult, which is main-thread only data 1.751 + if (!mResult) { 1.752 + return; 1.753 + } 1.754 + nsCOMPtr<nsIThread> main = do_GetMainThread(); 1.755 + (void)NS_ProxyRelease(main, mResult); 1.756 + } 1.757 + 1.758 +protected: 1.759 + void AfterRead(TimeStamp aDispatchDate, 1.760 + ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE { 1.761 + MOZ_ASSERT(!NS_IsMainThread()); 1.762 + mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBuffer.forget()); 1.763 + Succeed(mResult.forget()); 1.764 + } 1.765 + 1.766 + private: 1.767 + nsRefPtr<TypedArrayResult> mResult; 1.768 +}; 1.769 + 1.770 +/** 1.771 + * An implementation of a Read event that provides the data 1.772 + * as a JavaScript string. 1.773 + */ 1.774 +class DoReadToStringEvent MOZ_FINAL : public AbstractReadEvent { 1.775 +public: 1.776 + DoReadToStringEvent(const nsAString& aPath, 1.777 + const nsACString& aEncoding, 1.778 + const uint32_t aBytes, 1.779 + already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess, 1.780 + already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError) 1.781 + : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError) 1.782 + , mEncoding(aEncoding) 1.783 + , mResult(new StringResult(TimeStamp::Now())) 1.784 + { } 1.785 + 1.786 + ~DoReadToStringEvent() { 1.787 + // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup 1.788 + // mResult, which is main-thread only data 1.789 + if (!mResult) { 1.790 + return; 1.791 + } 1.792 + nsCOMPtr<nsIThread> main = do_GetMainThread(); 1.793 + (void)NS_ProxyRelease(main, mResult); 1.794 + } 1.795 + 1.796 +protected: 1.797 + nsresult BeforeRead() MOZ_OVERRIDE { 1.798 + // Obtain the decoder. We do this before reading to avoid doing 1.799 + // any unnecessary I/O in case the name of the encoding is incorrect. 1.800 + MOZ_ASSERT(!NS_IsMainThread()); 1.801 + nsAutoCString encodingName; 1.802 + if (!dom::EncodingUtils::FindEncodingForLabel(mEncoding, encodingName)) { 1.803 + Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL); 1.804 + return NS_ERROR_FAILURE; 1.805 + } 1.806 + mDecoder = dom::EncodingUtils::DecoderForEncoding(encodingName); 1.807 + if (!mDecoder) { 1.808 + Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL); 1.809 + return NS_ERROR_FAILURE; 1.810 + } 1.811 + 1.812 + return NS_OK; 1.813 + } 1.814 + 1.815 + void AfterRead(TimeStamp aDispatchDate, 1.816 + ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE { 1.817 + MOZ_ASSERT(!NS_IsMainThread()); 1.818 + 1.819 + int32_t maxChars; 1.820 + const char* sourceChars = reinterpret_cast<const char*>(aBuffer.get().data); 1.821 + int32_t sourceBytes = aBuffer.get().nbytes; 1.822 + if (sourceBytes < 0) { 1.823 + Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); 1.824 + return; 1.825 + } 1.826 + 1.827 + nsresult rv = mDecoder->GetMaxLength(sourceChars, sourceBytes, &maxChars); 1.828 + if (NS_FAILED(rv)) { 1.829 + Fail(NS_LITERAL_CSTRING("GetMaxLength"), mResult.forget(), OS_ERROR_INVAL); 1.830 + return; 1.831 + } 1.832 + 1.833 + if (maxChars < 0) { 1.834 + Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); 1.835 + return; 1.836 + } 1.837 + 1.838 + nsString resultString; 1.839 + resultString.SetLength(maxChars); 1.840 + if (resultString.Length() != (nsString::size_type)maxChars) { 1.841 + Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE); 1.842 + return; 1.843 + } 1.844 + 1.845 + 1.846 + rv = mDecoder->Convert(sourceChars, &sourceBytes, 1.847 + resultString.BeginWriting(), &maxChars); 1.848 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.849 + resultString.SetLength(maxChars); 1.850 + 1.851 + mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString); 1.852 + Succeed(mResult.forget()); 1.853 + } 1.854 + 1.855 + private: 1.856 + nsCString mEncoding; 1.857 + nsCOMPtr<nsIUnicodeDecoder> mDecoder; 1.858 + nsRefPtr<StringResult> mResult; 1.859 +}; 1.860 + 1.861 +} // osfile 1.862 + 1.863 +// The OS.File service 1.864 + 1.865 +NS_IMPL_ISUPPORTS(NativeOSFileInternalsService, nsINativeOSFileInternalsService); 1.866 + 1.867 +NS_IMETHODIMP 1.868 +NativeOSFileInternalsService::Read(const nsAString& aPath, 1.869 + JS::HandleValue aOptions, 1.870 + nsINativeOSFileSuccessCallback *aOnSuccess, 1.871 + nsINativeOSFileErrorCallback *aOnError, 1.872 + JSContext* cx) 1.873 +{ 1.874 + // Extract options 1.875 + nsCString encoding; 1.876 + uint64_t bytes = UINT64_MAX; 1.877 + 1.878 + if (aOptions.isObject()) { 1.879 + dom::NativeOSFileReadOptions dict; 1.880 + if (!dict.Init(cx, aOptions)) { 1.881 + return NS_ERROR_INVALID_ARG; 1.882 + } 1.883 + 1.884 + if (dict.mEncoding.WasPassed()) { 1.885 + CopyUTF16toUTF8(dict.mEncoding.Value(), encoding); 1.886 + } 1.887 + 1.888 + if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) { 1.889 + bytes = dict.mBytes.Value().Value(); 1.890 + } 1.891 + } 1.892 + 1.893 + // Prepare the off main thread event and dispatch it 1.894 + nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess); 1.895 + nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError); 1.896 + 1.897 + nsRefPtr<AbstractDoEvent> event; 1.898 + if (encoding.IsEmpty()) { 1.899 + event = new DoReadToTypedArrayEvent(aPath, bytes, 1.900 + onSuccess.forget(), 1.901 + onError.forget()); 1.902 + } else { 1.903 + event = new DoReadToStringEvent(aPath, encoding, bytes, 1.904 + onSuccess.forget(), 1.905 + onError.forget()); 1.906 + } 1.907 + 1.908 + nsresult rv; 1.909 + nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); 1.910 + 1.911 + if (NS_FAILED(rv)) { 1.912 + return rv; 1.913 + } 1.914 + return target->Dispatch(event, NS_DISPATCH_NORMAL); 1.915 +} 1.916 + 1.917 +} // namespace mozilla 1.918 +