|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /** |
|
6 * Native implementation of some OS.File operations. |
|
7 */ |
|
8 |
|
9 #include "nsString.h" |
|
10 #include "nsNetCID.h" |
|
11 #include "nsThreadUtils.h" |
|
12 #include "nsXPCOMCID.h" |
|
13 #include "nsCycleCollectionParticipant.h" |
|
14 #include "nsServiceManagerUtils.h" |
|
15 #include "nsProxyRelease.h" |
|
16 |
|
17 #include "nsINativeOSFileInternals.h" |
|
18 #include "NativeOSFileInternals.h" |
|
19 #include "mozilla/dom/NativeOSFileInternalsBinding.h" |
|
20 |
|
21 #include "nsIUnicodeDecoder.h" |
|
22 #include "nsIEventTarget.h" |
|
23 |
|
24 #include "mozilla/dom/EncodingUtils.h" |
|
25 #include "mozilla/DebugOnly.h" |
|
26 #include "mozilla/Scoped.h" |
|
27 #include "mozilla/HoldDropJSObjects.h" |
|
28 #include "mozilla/TimeStamp.h" |
|
29 |
|
30 #include "prio.h" |
|
31 #include "prerror.h" |
|
32 #include "private/pprio.h" |
|
33 |
|
34 #include "jsapi.h" |
|
35 #include "jsfriendapi.h" |
|
36 #include "js/Utility.h" |
|
37 #include "xpcpublic.h" |
|
38 |
|
39 #include <algorithm> |
|
40 #if defined(XP_UNIX) |
|
41 #include <unistd.h> |
|
42 #include <errno.h> |
|
43 #include <fcntl.h> |
|
44 #include <sys/stat.h> |
|
45 #include <sys/uio.h> |
|
46 #endif // defined (XP_UNIX) |
|
47 |
|
48 #if defined(XP_WIN) |
|
49 #include <windows.h> |
|
50 #endif // defined (XP_WIN) |
|
51 |
|
52 namespace mozilla { |
|
53 |
|
54 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close) |
|
55 |
|
56 namespace { |
|
57 |
|
58 // Utilities for safely manipulating ArrayBuffer contents even in the |
|
59 // absence of a JSContext. |
|
60 |
|
61 /** |
|
62 * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate |
|
63 * this instead of a void* buffer, as this lets us transfer data across threads |
|
64 * and into JavaScript without copy. |
|
65 */ |
|
66 struct ArrayBufferContents { |
|
67 /** |
|
68 * The data of the ArrayBuffer. This is the pointer manipulated to |
|
69 * read/write the contents of the buffer. |
|
70 */ |
|
71 uint8_t* data; |
|
72 /** |
|
73 * The number of bytes in the ArrayBuffer. |
|
74 */ |
|
75 size_t nbytes; |
|
76 }; |
|
77 |
|
78 /** |
|
79 * RAII for ArrayBufferContents. |
|
80 */ |
|
81 struct ScopedArrayBufferContentsTraits { |
|
82 typedef ArrayBufferContents type; |
|
83 const static type empty() { |
|
84 type result = {0, 0}; |
|
85 return result; |
|
86 } |
|
87 const static void release(type ptr) { |
|
88 js_free(ptr.data); |
|
89 ptr.data = nullptr; |
|
90 ptr.nbytes = 0; |
|
91 } |
|
92 }; |
|
93 |
|
94 struct ScopedArrayBufferContents: public Scoped<ScopedArrayBufferContentsTraits> { |
|
95 ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM): |
|
96 Scoped<ScopedArrayBufferContentsTraits>(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT) |
|
97 { } |
|
98 ScopedArrayBufferContents(const ArrayBufferContents& v |
|
99 MOZ_GUARD_OBJECT_NOTIFIER_PARAM): |
|
100 Scoped<ScopedArrayBufferContentsTraits>(v MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) |
|
101 { } |
|
102 ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) { |
|
103 Scoped<ScopedArrayBufferContentsTraits>::operator=(ptr); |
|
104 return *this; |
|
105 } |
|
106 |
|
107 /** |
|
108 * Request memory for this ArrayBufferContent. This memory may later |
|
109 * be used to create an ArrayBuffer object (possibly on another |
|
110 * thread) without copy. |
|
111 * |
|
112 * @return true In case of success, false otherwise. |
|
113 */ |
|
114 bool Allocate(uint32_t length) { |
|
115 dispose(); |
|
116 ArrayBufferContents& value = rwget(); |
|
117 void *ptr = JS_AllocateArrayBufferContents(/*no context available*/nullptr, length); |
|
118 if (ptr) { |
|
119 value.data = (uint8_t *) ptr; |
|
120 value.nbytes = length; |
|
121 return true; |
|
122 } |
|
123 return false; |
|
124 } |
|
125 private: |
|
126 explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) MOZ_DELETE; |
|
127 ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) MOZ_DELETE; |
|
128 }; |
|
129 |
|
130 ///////// Cross-platform issues |
|
131 |
|
132 // Platform specific constants. As OS.File always uses OS-level |
|
133 // errors, we need to map a few high-level errors to OS-level |
|
134 // constants. |
|
135 #if defined(XP_UNIX) |
|
136 #define OS_ERROR_NOMEM ENOMEM |
|
137 #define OS_ERROR_INVAL EINVAL |
|
138 #define OS_ERROR_TOO_LARGE EFBIG |
|
139 #define OS_ERROR_RACE EIO |
|
140 #elif defined(XP_WIN) |
|
141 #define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY |
|
142 #define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS |
|
143 #define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE |
|
144 #define OS_ERROR_RACE ERROR_SHARING_VIOLATION |
|
145 #else |
|
146 #error "We do not have platform-specific constants for this platform" |
|
147 #endif |
|
148 |
|
149 ///////// Results of OS.File operations |
|
150 |
|
151 /** |
|
152 * Base class for results passed to the callbacks. |
|
153 * |
|
154 * This base class implements caching of JS values returned to the client. |
|
155 * We make use of this caching in derived classes e.g. to avoid accidents |
|
156 * when we transfer data allocated on another thread into JS. Note that |
|
157 * this caching can lead to cycles (e.g. if a client adds a back-reference |
|
158 * in the JS value), so we implement all Cycle Collector primitives in |
|
159 * AbstractResult. |
|
160 */ |
|
161 class AbstractResult: public nsINativeOSFileResult { |
|
162 public: |
|
163 NS_DECL_NSINATIVEOSFILERESULT |
|
164 NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
|
165 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult) |
|
166 |
|
167 /** |
|
168 * Construct the result object. Must be called on the main thread |
|
169 * as the AbstractResult is cycle-collected. |
|
170 * |
|
171 * @param aStartDate The instant at which the operation was |
|
172 * requested. Used to collect Telemetry statistics. |
|
173 */ |
|
174 AbstractResult(TimeStamp aStartDate) |
|
175 : mStartDate(aStartDate) |
|
176 { |
|
177 MOZ_ASSERT(NS_IsMainThread()); |
|
178 mozilla::HoldJSObjects(this); |
|
179 } |
|
180 virtual ~AbstractResult() { |
|
181 MOZ_ASSERT(NS_IsMainThread()); |
|
182 DropJSData(); |
|
183 mozilla::DropJSObjects(this); |
|
184 } |
|
185 |
|
186 /** |
|
187 * Setup the AbstractResult once data is available. |
|
188 * |
|
189 * @param aDispatchDate The instant at which the IO thread received |
|
190 * the operation request. Used to collect Telemetry statistics. |
|
191 * @param aExecutionDuration The duration of the operation on the |
|
192 * IO thread. |
|
193 */ |
|
194 void Init(TimeStamp aDispatchDate, |
|
195 TimeDuration aExecutionDuration) { |
|
196 MOZ_ASSERT(!NS_IsMainThread()); |
|
197 |
|
198 mDispatchDuration = (aDispatchDate - mStartDate); |
|
199 mExecutionDuration = aExecutionDuration; |
|
200 } |
|
201 |
|
202 /** |
|
203 * Drop any data that could lead to a cycle. |
|
204 */ |
|
205 void DropJSData() { |
|
206 mCachedResult = JS::UndefinedValue(); |
|
207 } |
|
208 |
|
209 protected: |
|
210 virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0; |
|
211 |
|
212 private: |
|
213 TimeStamp mStartDate; |
|
214 TimeDuration mDispatchDuration; |
|
215 TimeDuration mExecutionDuration; |
|
216 JS::Heap<JS::Value> mCachedResult; |
|
217 }; |
|
218 |
|
219 NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult) |
|
220 NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult) |
|
221 |
|
222 NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult) |
|
223 |
|
224 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult) |
|
225 NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult) |
|
226 NS_INTERFACE_MAP_ENTRY(nsISupports) |
|
227 NS_INTERFACE_MAP_END |
|
228 |
|
229 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult) |
|
230 NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedResult) |
|
231 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
232 |
|
233 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult) |
|
234 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
235 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
236 |
|
237 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult) |
|
238 tmp->DropJSData(); |
|
239 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
240 |
|
241 NS_IMETHODIMP |
|
242 AbstractResult::GetDispatchDurationMS(double *aDispatchDuration) |
|
243 { |
|
244 *aDispatchDuration = mDispatchDuration.ToMilliseconds(); |
|
245 return NS_OK; |
|
246 } |
|
247 |
|
248 NS_IMETHODIMP |
|
249 AbstractResult::GetExecutionDurationMS(double *aExecutionDuration) |
|
250 { |
|
251 *aExecutionDuration = mExecutionDuration.ToMilliseconds(); |
|
252 return NS_OK; |
|
253 } |
|
254 |
|
255 NS_IMETHODIMP |
|
256 AbstractResult::GetResult(JSContext *cx, JS::MutableHandleValue aResult) |
|
257 { |
|
258 if (mCachedResult.isUndefined()) { |
|
259 nsresult rv = GetCacheableResult(cx, aResult); |
|
260 if (NS_FAILED(rv)) { |
|
261 return rv; |
|
262 } |
|
263 mCachedResult = aResult; |
|
264 return NS_OK; |
|
265 } |
|
266 aResult.set(mCachedResult); |
|
267 return NS_OK; |
|
268 } |
|
269 |
|
270 /** |
|
271 * Return a result as a string. |
|
272 * |
|
273 * In this implementation, attribute |result| is a string. Strings are |
|
274 * passed to JS without copy. |
|
275 */ |
|
276 class StringResult MOZ_FINAL : public AbstractResult |
|
277 { |
|
278 public: |
|
279 StringResult(TimeStamp aStartDate) |
|
280 : AbstractResult(aStartDate) |
|
281 { |
|
282 } |
|
283 |
|
284 /** |
|
285 * Initialize the object once the contents of the result as available. |
|
286 * |
|
287 * @param aContents The string to pass to JavaScript. Ownership of the |
|
288 * string and its contents is passed to StringResult. The string must |
|
289 * be valid UTF-16. |
|
290 */ |
|
291 void Init(TimeStamp aDispatchDate, |
|
292 TimeDuration aExecutionDuration, |
|
293 nsString& aContents) { |
|
294 AbstractResult::Init(aDispatchDate, aExecutionDuration); |
|
295 mContents = aContents; |
|
296 } |
|
297 |
|
298 protected: |
|
299 nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE; |
|
300 |
|
301 private: |
|
302 nsString mContents; |
|
303 }; |
|
304 |
|
305 nsresult |
|
306 StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) |
|
307 { |
|
308 MOZ_ASSERT(NS_IsMainThread()); |
|
309 MOZ_ASSERT(mContents.get()); |
|
310 |
|
311 // Convert mContents to a js string without copy. Note that this |
|
312 // may have the side-effect of stealing the contents of the string |
|
313 // from XPCOM and into JS. |
|
314 if (!xpc::StringToJsval(cx, mContents, aResult)) { |
|
315 return NS_ERROR_FAILURE; |
|
316 } |
|
317 return NS_OK; |
|
318 } |
|
319 |
|
320 |
|
321 /** |
|
322 * Return a result as a Uint8Array. |
|
323 * |
|
324 * In this implementation, attribute |result| is a Uint8Array. The array |
|
325 * is passed to JS without memory copy. |
|
326 */ |
|
327 class TypedArrayResult MOZ_FINAL : public AbstractResult |
|
328 { |
|
329 public: |
|
330 TypedArrayResult(TimeStamp aStartDate) |
|
331 : AbstractResult(aStartDate) |
|
332 { |
|
333 } |
|
334 |
|
335 /** |
|
336 * @param aContents The contents to pass to JS. Calling this method. |
|
337 * transmits ownership of the ArrayBufferContents to the TypedArrayResult. |
|
338 * Do not reuse this value anywhere else. |
|
339 */ |
|
340 void Init(TimeStamp aDispatchDate, |
|
341 TimeDuration aExecutionDuration, |
|
342 ArrayBufferContents aContents) { |
|
343 AbstractResult::Init(aDispatchDate, aExecutionDuration); |
|
344 mContents = aContents; |
|
345 } |
|
346 |
|
347 protected: |
|
348 nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE; |
|
349 private: |
|
350 ScopedArrayBufferContents mContents; |
|
351 }; |
|
352 |
|
353 nsresult |
|
354 TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle<JS::Value> aResult) |
|
355 { |
|
356 MOZ_ASSERT(NS_IsMainThread()); |
|
357 // We cannot simply construct a typed array using contents.data as |
|
358 // this would allow us to have several otherwise unrelated |
|
359 // ArrayBuffers with the same underlying C buffer. As this would be |
|
360 // very unsafe, we need to cache the result once we have it. |
|
361 |
|
362 const ArrayBufferContents& contents = mContents.get(); |
|
363 MOZ_ASSERT(contents.data); |
|
364 |
|
365 JS::Rooted<JSObject*> |
|
366 arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.nbytes, contents.data)); |
|
367 if (!arrayBuffer) { |
|
368 return NS_ERROR_OUT_OF_MEMORY; |
|
369 } |
|
370 |
|
371 JS::Rooted<JSObject*> |
|
372 result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer, |
|
373 0, contents.nbytes)); |
|
374 if (!result) { |
|
375 return NS_ERROR_OUT_OF_MEMORY; |
|
376 } |
|
377 // The memory of contents has been allocated on a thread that |
|
378 // doesn't have a JSRuntime, hence without a context. Now that we |
|
379 // have a context, attach the memory to where it belongs. |
|
380 JS_updateMallocCounter(cx, contents.nbytes); |
|
381 mContents.forget(); |
|
382 |
|
383 aResult.setObject(*result); |
|
384 return NS_OK; |
|
385 } |
|
386 |
|
387 //////// Callback events |
|
388 |
|
389 /** |
|
390 * An event used to notify asynchronously of an error. |
|
391 */ |
|
392 class ErrorEvent MOZ_FINAL : public nsRunnable { |
|
393 public: |
|
394 /** |
|
395 * @param aOnSuccess The success callback. |
|
396 * @param aOnError The error callback. |
|
397 * @param aDiscardedResult The discarded result. |
|
398 * @param aOperation The name of the operation, used for error reporting. |
|
399 * @param aOSError The OS error of the operation, as returned by errno/ |
|
400 * GetLastError(). |
|
401 * |
|
402 * Note that we pass both the success callback and the error |
|
403 * callback, as well as the discarded result to ensure that they are |
|
404 * all released on the main thread, rather than on the IO thread |
|
405 * (which would hopefully segfault). Also, we pass the callbacks as |
|
406 * alread_AddRefed to ensure that we do not manipulate main-thread |
|
407 * only refcounters off the main thread. |
|
408 */ |
|
409 ErrorEvent(already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess, |
|
410 already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError, |
|
411 already_AddRefed<AbstractResult>& aDiscardedResult, |
|
412 const nsACString& aOperation, |
|
413 int32_t aOSError) |
|
414 : mOnSuccess(aOnSuccess) |
|
415 , mOnError(aOnError) |
|
416 , mDiscardedResult(aDiscardedResult) |
|
417 , mOSError(aOSError) |
|
418 , mOperation(aOperation) |
|
419 { |
|
420 MOZ_ASSERT(!NS_IsMainThread()); |
|
421 } |
|
422 |
|
423 NS_METHOD Run() { |
|
424 MOZ_ASSERT(NS_IsMainThread()); |
|
425 (void)mOnError->Complete(mOperation, mOSError); |
|
426 |
|
427 // Ensure that the callbacks are released on the main thread. |
|
428 mOnSuccess = nullptr; |
|
429 mOnError = nullptr; |
|
430 mDiscardedResult = nullptr; |
|
431 |
|
432 return NS_OK; |
|
433 } |
|
434 private: |
|
435 // The callbacks. Maintained as nsRefPtr as they are generally |
|
436 // xpconnect values, which cannot be manipulated with nsCOMPtr off |
|
437 // the main thread. We store both the success callback and the |
|
438 // error callback to ensure that they are safely released on the |
|
439 // main thread. |
|
440 nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess; |
|
441 nsRefPtr<nsINativeOSFileErrorCallback> mOnError; |
|
442 nsRefPtr<AbstractResult> mDiscardedResult; |
|
443 int32_t mOSError; |
|
444 nsCString mOperation; |
|
445 }; |
|
446 |
|
447 /** |
|
448 * An event used to notify of a success. |
|
449 */ |
|
450 class SuccessEvent MOZ_FINAL : public nsRunnable { |
|
451 public: |
|
452 /** |
|
453 * @param aOnSuccess The success callback. |
|
454 * @param aOnError The error callback. |
|
455 * |
|
456 * Note that we pass both the success callback and the error |
|
457 * callback to ensure that they are both released on the main |
|
458 * thread, rather than on the IO thread (which would hopefully |
|
459 * segfault). Also, we pass them as alread_AddRefed to ensure that |
|
460 * we do not manipulate xpconnect refcounters off the main thread |
|
461 * (which is illegal). |
|
462 */ |
|
463 SuccessEvent(already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess, |
|
464 already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError, |
|
465 already_AddRefed<nsINativeOSFileResult>& aResult) |
|
466 : mOnSuccess(aOnSuccess) |
|
467 , mOnError(aOnError) |
|
468 , mResult(aResult) |
|
469 { |
|
470 MOZ_ASSERT(!NS_IsMainThread()); |
|
471 } |
|
472 |
|
473 NS_METHOD Run() { |
|
474 MOZ_ASSERT(NS_IsMainThread()); |
|
475 (void)mOnSuccess->Complete(mResult); |
|
476 |
|
477 // Ensure that the callbacks are released on the main thread. |
|
478 mOnSuccess = nullptr; |
|
479 mOnError = nullptr; |
|
480 mResult = nullptr; |
|
481 |
|
482 return NS_OK; |
|
483 } |
|
484 private: |
|
485 // The callbacks. Maintained as nsRefPtr as they are generally |
|
486 // xpconnect values, which cannot be manipulated with nsCOMPtr off |
|
487 // the main thread. We store both the success callback and the |
|
488 // error callback to ensure that they are safely released on the |
|
489 // main thread. |
|
490 nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess; |
|
491 nsRefPtr<nsINativeOSFileErrorCallback> mOnError; |
|
492 nsRefPtr<nsINativeOSFileResult> mResult; |
|
493 }; |
|
494 |
|
495 |
|
496 //////// Action events |
|
497 |
|
498 /** |
|
499 * Base class shared by actions. |
|
500 */ |
|
501 class AbstractDoEvent: public nsRunnable { |
|
502 public: |
|
503 AbstractDoEvent(already_AddRefed<nsINativeOSFileSuccessCallback>& aOnSuccess, |
|
504 already_AddRefed<nsINativeOSFileErrorCallback>& aOnError) |
|
505 : mOnSuccess(aOnSuccess) |
|
506 , mOnError(aOnError) |
|
507 #if defined(DEBUG) |
|
508 , mResolved(false) |
|
509 #endif // defined(DEBUG) |
|
510 { |
|
511 MOZ_ASSERT(NS_IsMainThread()); |
|
512 } |
|
513 |
|
514 /** |
|
515 * Fail, asynchronously. |
|
516 */ |
|
517 void Fail(const nsACString& aOperation, |
|
518 already_AddRefed<AbstractResult>&& aDiscardedResult, |
|
519 int32_t aOSError = 0) { |
|
520 Resolve(); |
|
521 nsRefPtr<ErrorEvent> event = new ErrorEvent(mOnSuccess.forget(), |
|
522 mOnError.forget(), |
|
523 aDiscardedResult, |
|
524 aOperation, |
|
525 aOSError); |
|
526 nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); |
|
527 if (NS_FAILED(rv)) { |
|
528 // Last ditch attempt to release on the main thread - some of |
|
529 // the members of event are not thread-safe, so letting the |
|
530 // pointer go out of scope would cause a crash. |
|
531 nsCOMPtr<nsIThread> main = do_GetMainThread(); |
|
532 NS_ProxyRelease(main, event); |
|
533 } |
|
534 } |
|
535 |
|
536 /** |
|
537 * Succeed, asynchronously. |
|
538 */ |
|
539 void Succeed(already_AddRefed<nsINativeOSFileResult>&& aResult) { |
|
540 Resolve(); |
|
541 nsRefPtr<SuccessEvent> event = new SuccessEvent(mOnSuccess.forget(), |
|
542 mOnError.forget(), |
|
543 aResult); |
|
544 nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); |
|
545 if (NS_FAILED(rv)) { |
|
546 // Last ditch attempt to release on the main thread - some of |
|
547 // the members of event are not thread-safe, so letting the |
|
548 // pointer go out of scope would cause a crash. |
|
549 nsCOMPtr<nsIThread> main = do_GetMainThread(); |
|
550 NS_ProxyRelease(main, event); |
|
551 } |
|
552 |
|
553 } |
|
554 |
|
555 private: |
|
556 |
|
557 /** |
|
558 * Mark the event as complete, for debugging purposes. |
|
559 */ |
|
560 void Resolve() { |
|
561 #if defined(DEBUG) |
|
562 MOZ_ASSERT(!mResolved); |
|
563 mResolved = true; |
|
564 #endif // defined(DEBUG) |
|
565 } |
|
566 |
|
567 private: |
|
568 nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess; |
|
569 nsRefPtr<nsINativeOSFileErrorCallback> mOnError; |
|
570 #if defined(DEBUG) |
|
571 // |true| once the action is complete |
|
572 bool mResolved; |
|
573 #endif // defined(DEBUG) |
|
574 }; |
|
575 |
|
576 /** |
|
577 * An abstract event implementing reading from a file. |
|
578 * |
|
579 * Concrete subclasses are responsible for handling the |
|
580 * data obtained from the file and possibly post-processing it. |
|
581 */ |
|
582 class AbstractReadEvent: public AbstractDoEvent { |
|
583 public: |
|
584 /** |
|
585 * @param aPath The path of the file. |
|
586 */ |
|
587 AbstractReadEvent(const nsAString& aPath, |
|
588 const uint64_t aBytes, |
|
589 already_AddRefed<nsINativeOSFileSuccessCallback>& aOnSuccess, |
|
590 already_AddRefed<nsINativeOSFileErrorCallback>& aOnError) |
|
591 : AbstractDoEvent(aOnSuccess, aOnError) |
|
592 , mPath(aPath) |
|
593 , mBytes(aBytes) |
|
594 { |
|
595 MOZ_ASSERT(NS_IsMainThread()); |
|
596 } |
|
597 |
|
598 NS_METHOD Run() MOZ_OVERRIDE { |
|
599 MOZ_ASSERT(!NS_IsMainThread()); |
|
600 TimeStamp dispatchDate = TimeStamp::Now(); |
|
601 |
|
602 nsresult rv = BeforeRead(); |
|
603 if (NS_FAILED(rv)) { |
|
604 // Error reporting is handled by BeforeRead(); |
|
605 return NS_OK; |
|
606 } |
|
607 |
|
608 ScopedArrayBufferContents buffer; |
|
609 rv = Read(buffer); |
|
610 if (NS_FAILED(rv)) { |
|
611 // Error reporting is handled by Read(); |
|
612 return NS_OK; |
|
613 } |
|
614 |
|
615 AfterRead(dispatchDate, buffer); |
|
616 return NS_OK; |
|
617 } |
|
618 |
|
619 private: |
|
620 /** |
|
621 * Read synchronously. |
|
622 * |
|
623 * Must be called off the main thread. |
|
624 * |
|
625 * @param aBuffer The destination buffer. |
|
626 */ |
|
627 nsresult Read(ScopedArrayBufferContents& aBuffer) |
|
628 { |
|
629 MOZ_ASSERT(!NS_IsMainThread()); |
|
630 |
|
631 ScopedPRFileDesc file; |
|
632 #if defined(XP_WIN) |
|
633 // On Windows, we can't use PR_OpenFile because it doesn't |
|
634 // handle UTF-16 encoding, which is pretty bad. In addition, |
|
635 // PR_OpenFile opens files without sharing, which is not the |
|
636 // general semantics of OS.File. |
|
637 HANDLE handle = |
|
638 ::CreateFileW(mPath.get(), |
|
639 GENERIC_READ, |
|
640 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
|
641 /*Security attributes*/nullptr, |
|
642 OPEN_EXISTING, |
|
643 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, |
|
644 /*Template file*/ nullptr); |
|
645 |
|
646 if (handle == INVALID_HANDLE_VALUE) { |
|
647 Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError()); |
|
648 return NS_ERROR_FAILURE; |
|
649 } |
|
650 |
|
651 file = PR_ImportFile((PROsfd)handle); |
|
652 if (!file) { |
|
653 // |file| is closed by PR_ImportFile |
|
654 Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError()); |
|
655 return NS_ERROR_FAILURE; |
|
656 } |
|
657 |
|
658 #else |
|
659 // On other platforms, PR_OpenFile will do. |
|
660 NS_ConvertUTF16toUTF8 path(mPath); |
|
661 file = PR_OpenFile(path.get(), PR_RDONLY, 0); |
|
662 if (!file) { |
|
663 Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError()); |
|
664 return NS_ERROR_FAILURE; |
|
665 } |
|
666 |
|
667 #endif // defined(XP_XIN) |
|
668 |
|
669 PRFileInfo64 stat; |
|
670 if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) { |
|
671 Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError()); |
|
672 return NS_ERROR_FAILURE; |
|
673 } |
|
674 |
|
675 uint64_t bytes = std::min((uint64_t)stat.size, mBytes); |
|
676 if (bytes > UINT32_MAX) { |
|
677 Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL); |
|
678 return NS_ERROR_FAILURE; |
|
679 } |
|
680 |
|
681 if (!aBuffer.Allocate(bytes)) { |
|
682 Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM); |
|
683 return NS_ERROR_FAILURE; |
|
684 } |
|
685 |
|
686 uint64_t total_read = 0; |
|
687 int32_t just_read = 0; |
|
688 char* dest_chars = reinterpret_cast<char*>(aBuffer.rwget().data); |
|
689 do { |
|
690 just_read = PR_Read(file, dest_chars + total_read, |
|
691 std::min(uint64_t(PR_INT32_MAX), bytes - total_read)); |
|
692 if (just_read == -1) { |
|
693 Fail(NS_LITERAL_CSTRING("read"), nullptr, PR_GetOSError()); |
|
694 return NS_ERROR_FAILURE; |
|
695 } |
|
696 total_read += just_read; |
|
697 } while (just_read != 0 && total_read < bytes); |
|
698 if (total_read != bytes) { |
|
699 // We seem to have a race condition here. |
|
700 Fail(NS_LITERAL_CSTRING("read"), nullptr, OS_ERROR_RACE); |
|
701 return NS_ERROR_FAILURE; |
|
702 } |
|
703 |
|
704 return NS_OK; |
|
705 } |
|
706 |
|
707 protected: |
|
708 /** |
|
709 * Any steps that need to be taken before reading. |
|
710 * |
|
711 * In case of error, this method should call Fail() and return |
|
712 * a failure code. |
|
713 */ |
|
714 virtual |
|
715 nsresult BeforeRead() { |
|
716 return NS_OK; |
|
717 } |
|
718 |
|
719 /** |
|
720 * Proceed after reading. |
|
721 */ |
|
722 virtual |
|
723 void AfterRead(TimeStamp aDispatchDate, ScopedArrayBufferContents& aBuffer) = 0; |
|
724 |
|
725 protected: |
|
726 const nsString mPath; |
|
727 const uint64_t mBytes; |
|
728 }; |
|
729 |
|
730 /** |
|
731 * An implementation of a Read event that provides the data |
|
732 * as a TypedArray. |
|
733 */ |
|
734 class DoReadToTypedArrayEvent MOZ_FINAL : public AbstractReadEvent { |
|
735 public: |
|
736 DoReadToTypedArrayEvent(const nsAString& aPath, |
|
737 const uint32_t aBytes, |
|
738 already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess, |
|
739 already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError) |
|
740 : AbstractReadEvent(aPath, aBytes, |
|
741 aOnSuccess, aOnError) |
|
742 , mResult(new TypedArrayResult(TimeStamp::Now())) |
|
743 { } |
|
744 |
|
745 ~DoReadToTypedArrayEvent() { |
|
746 // If AbstractReadEvent::Run() has bailed out, we may need to cleanup |
|
747 // mResult, which is main-thread only data |
|
748 if (!mResult) { |
|
749 return; |
|
750 } |
|
751 nsCOMPtr<nsIThread> main = do_GetMainThread(); |
|
752 (void)NS_ProxyRelease(main, mResult); |
|
753 } |
|
754 |
|
755 protected: |
|
756 void AfterRead(TimeStamp aDispatchDate, |
|
757 ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE { |
|
758 MOZ_ASSERT(!NS_IsMainThread()); |
|
759 mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBuffer.forget()); |
|
760 Succeed(mResult.forget()); |
|
761 } |
|
762 |
|
763 private: |
|
764 nsRefPtr<TypedArrayResult> mResult; |
|
765 }; |
|
766 |
|
767 /** |
|
768 * An implementation of a Read event that provides the data |
|
769 * as a JavaScript string. |
|
770 */ |
|
771 class DoReadToStringEvent MOZ_FINAL : public AbstractReadEvent { |
|
772 public: |
|
773 DoReadToStringEvent(const nsAString& aPath, |
|
774 const nsACString& aEncoding, |
|
775 const uint32_t aBytes, |
|
776 already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess, |
|
777 already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError) |
|
778 : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError) |
|
779 , mEncoding(aEncoding) |
|
780 , mResult(new StringResult(TimeStamp::Now())) |
|
781 { } |
|
782 |
|
783 ~DoReadToStringEvent() { |
|
784 // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup |
|
785 // mResult, which is main-thread only data |
|
786 if (!mResult) { |
|
787 return; |
|
788 } |
|
789 nsCOMPtr<nsIThread> main = do_GetMainThread(); |
|
790 (void)NS_ProxyRelease(main, mResult); |
|
791 } |
|
792 |
|
793 protected: |
|
794 nsresult BeforeRead() MOZ_OVERRIDE { |
|
795 // Obtain the decoder. We do this before reading to avoid doing |
|
796 // any unnecessary I/O in case the name of the encoding is incorrect. |
|
797 MOZ_ASSERT(!NS_IsMainThread()); |
|
798 nsAutoCString encodingName; |
|
799 if (!dom::EncodingUtils::FindEncodingForLabel(mEncoding, encodingName)) { |
|
800 Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL); |
|
801 return NS_ERROR_FAILURE; |
|
802 } |
|
803 mDecoder = dom::EncodingUtils::DecoderForEncoding(encodingName); |
|
804 if (!mDecoder) { |
|
805 Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL); |
|
806 return NS_ERROR_FAILURE; |
|
807 } |
|
808 |
|
809 return NS_OK; |
|
810 } |
|
811 |
|
812 void AfterRead(TimeStamp aDispatchDate, |
|
813 ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE { |
|
814 MOZ_ASSERT(!NS_IsMainThread()); |
|
815 |
|
816 int32_t maxChars; |
|
817 const char* sourceChars = reinterpret_cast<const char*>(aBuffer.get().data); |
|
818 int32_t sourceBytes = aBuffer.get().nbytes; |
|
819 if (sourceBytes < 0) { |
|
820 Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); |
|
821 return; |
|
822 } |
|
823 |
|
824 nsresult rv = mDecoder->GetMaxLength(sourceChars, sourceBytes, &maxChars); |
|
825 if (NS_FAILED(rv)) { |
|
826 Fail(NS_LITERAL_CSTRING("GetMaxLength"), mResult.forget(), OS_ERROR_INVAL); |
|
827 return; |
|
828 } |
|
829 |
|
830 if (maxChars < 0) { |
|
831 Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); |
|
832 return; |
|
833 } |
|
834 |
|
835 nsString resultString; |
|
836 resultString.SetLength(maxChars); |
|
837 if (resultString.Length() != (nsString::size_type)maxChars) { |
|
838 Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE); |
|
839 return; |
|
840 } |
|
841 |
|
842 |
|
843 rv = mDecoder->Convert(sourceChars, &sourceBytes, |
|
844 resultString.BeginWriting(), &maxChars); |
|
845 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
846 resultString.SetLength(maxChars); |
|
847 |
|
848 mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString); |
|
849 Succeed(mResult.forget()); |
|
850 } |
|
851 |
|
852 private: |
|
853 nsCString mEncoding; |
|
854 nsCOMPtr<nsIUnicodeDecoder> mDecoder; |
|
855 nsRefPtr<StringResult> mResult; |
|
856 }; |
|
857 |
|
858 } // osfile |
|
859 |
|
860 // The OS.File service |
|
861 |
|
862 NS_IMPL_ISUPPORTS(NativeOSFileInternalsService, nsINativeOSFileInternalsService); |
|
863 |
|
864 NS_IMETHODIMP |
|
865 NativeOSFileInternalsService::Read(const nsAString& aPath, |
|
866 JS::HandleValue aOptions, |
|
867 nsINativeOSFileSuccessCallback *aOnSuccess, |
|
868 nsINativeOSFileErrorCallback *aOnError, |
|
869 JSContext* cx) |
|
870 { |
|
871 // Extract options |
|
872 nsCString encoding; |
|
873 uint64_t bytes = UINT64_MAX; |
|
874 |
|
875 if (aOptions.isObject()) { |
|
876 dom::NativeOSFileReadOptions dict; |
|
877 if (!dict.Init(cx, aOptions)) { |
|
878 return NS_ERROR_INVALID_ARG; |
|
879 } |
|
880 |
|
881 if (dict.mEncoding.WasPassed()) { |
|
882 CopyUTF16toUTF8(dict.mEncoding.Value(), encoding); |
|
883 } |
|
884 |
|
885 if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) { |
|
886 bytes = dict.mBytes.Value().Value(); |
|
887 } |
|
888 } |
|
889 |
|
890 // Prepare the off main thread event and dispatch it |
|
891 nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess); |
|
892 nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError); |
|
893 |
|
894 nsRefPtr<AbstractDoEvent> event; |
|
895 if (encoding.IsEmpty()) { |
|
896 event = new DoReadToTypedArrayEvent(aPath, bytes, |
|
897 onSuccess.forget(), |
|
898 onError.forget()); |
|
899 } else { |
|
900 event = new DoReadToStringEvent(aPath, encoding, bytes, |
|
901 onSuccess.forget(), |
|
902 onError.forget()); |
|
903 } |
|
904 |
|
905 nsresult rv; |
|
906 nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); |
|
907 |
|
908 if (NS_FAILED(rv)) { |
|
909 return rv; |
|
910 } |
|
911 return target->Dispatch(event, NS_DISPATCH_NORMAL); |
|
912 } |
|
913 |
|
914 } // namespace mozilla |
|
915 |