/src/mozilla-central/dom/file/MutableBlobStorage.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "EmptyBlobImpl.h" |
8 | | #include "MutableBlobStorage.h" |
9 | | #include "MemoryBlobImpl.h" |
10 | | #include "mozilla/CheckedInt.h" |
11 | | #include "mozilla/dom/ipc/TemporaryIPCBlobChild.h" |
12 | | #include "mozilla/dom/WorkerPrivate.h" |
13 | | #include "mozilla/ipc/BackgroundChild.h" |
14 | | #include "mozilla/ipc/PBackgroundChild.h" |
15 | | #include "mozilla/Preferences.h" |
16 | | #include "mozilla/TaskQueue.h" |
17 | | #include "File.h" |
18 | | #include "nsAnonymousTemporaryFile.h" |
19 | | #include "nsNetCID.h" |
20 | | #include "nsProxyRelease.h" |
21 | | |
22 | 0 | #define BLOB_MEMORY_TEMPORARY_FILE 1048576 |
23 | | |
24 | | namespace mozilla { |
25 | | namespace dom { |
26 | | |
27 | | namespace { |
28 | | |
29 | | // This class uses the callback to inform when the Blob is created or when the |
30 | | // error must be propagated. |
31 | | class BlobCreationDoneRunnable final : public Runnable |
32 | | { |
33 | | public: |
34 | | BlobCreationDoneRunnable(MutableBlobStorage* aBlobStorage, |
35 | | MutableBlobStorageCallback* aCallback, |
36 | | Blob* aBlob, |
37 | | nsresult aRv) |
38 | | : Runnable("dom::BlobCreationDoneRunnable") |
39 | | , mBlobStorage(aBlobStorage) |
40 | | , mCallback(aCallback) |
41 | | , mBlob(aBlob) |
42 | | , mRv(aRv) |
43 | 0 | { |
44 | 0 | MOZ_ASSERT(aBlobStorage); |
45 | 0 | MOZ_ASSERT(aCallback); |
46 | 0 | MOZ_ASSERT((NS_FAILED(aRv) && !aBlob) || |
47 | 0 | (NS_SUCCEEDED(aRv) && aBlob)); |
48 | 0 | } |
49 | | |
50 | | NS_IMETHOD |
51 | | Run() override |
52 | 0 | { |
53 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
54 | 0 | MOZ_ASSERT(mBlobStorage); |
55 | 0 | mCallback->BlobStoreCompleted(mBlobStorage, mBlob, mRv); |
56 | 0 | mCallback = nullptr; |
57 | 0 | mBlob = nullptr; |
58 | 0 | return NS_OK; |
59 | 0 | } |
60 | | |
61 | | private: |
62 | | ~BlobCreationDoneRunnable() |
63 | 0 | { |
64 | 0 | MOZ_ASSERT(mBlobStorage); |
65 | 0 | // If something when wrong, we still have to release these objects in the |
66 | 0 | // correct thread. |
67 | 0 | NS_ProxyRelease( |
68 | 0 | "BlobCreationDoneRunnable::mCallback", |
69 | 0 | mBlobStorage->EventTarget(), mCallback.forget()); |
70 | 0 | NS_ProxyRelease( |
71 | 0 | "BlobCreationDoneRunnable::mBlob", |
72 | 0 | mBlobStorage->EventTarget(), mBlob.forget()); |
73 | 0 | } |
74 | | |
75 | | RefPtr<MutableBlobStorage> mBlobStorage; |
76 | | RefPtr<MutableBlobStorageCallback> mCallback; |
77 | | RefPtr<Blob> mBlob; |
78 | | nsresult mRv; |
79 | | }; |
80 | | |
81 | | // Simple runnable to propagate the error to the BlobStorage. |
82 | | class ErrorPropagationRunnable final : public Runnable |
83 | | { |
84 | | public: |
85 | | ErrorPropagationRunnable(MutableBlobStorage* aBlobStorage, nsresult aRv) |
86 | | : Runnable("dom::ErrorPropagationRunnable") |
87 | | , mBlobStorage(aBlobStorage) |
88 | | , mRv(aRv) |
89 | 0 | {} |
90 | | |
91 | | NS_IMETHOD |
92 | | Run() override |
93 | 0 | { |
94 | 0 | mBlobStorage->ErrorPropagated(mRv); |
95 | 0 | return NS_OK; |
96 | 0 | } |
97 | | |
98 | | private: |
99 | | RefPtr<MutableBlobStorage> mBlobStorage; |
100 | | nsresult mRv; |
101 | | }; |
102 | | |
103 | | // This runnable moves a buffer to the IO thread and there, it writes it into |
104 | | // the temporary file, if its File Descriptor has not been already closed. |
105 | | class WriteRunnable final : public Runnable |
106 | | { |
107 | | public: |
108 | | static WriteRunnable* |
109 | | CopyBuffer(MutableBlobStorage* aBlobStorage, |
110 | | const void* aData, uint32_t aLength) |
111 | 0 | { |
112 | 0 | MOZ_ASSERT(aBlobStorage); |
113 | 0 | MOZ_ASSERT(aData); |
114 | 0 |
|
115 | 0 | // We have to take a copy of this buffer. |
116 | 0 | void* data = malloc(aLength); |
117 | 0 | if (!data) { |
118 | 0 | return nullptr; |
119 | 0 | } |
120 | 0 | |
121 | 0 | memcpy((char*)data, aData, aLength); |
122 | 0 | return new WriteRunnable(aBlobStorage, data, aLength); |
123 | 0 | } |
124 | | |
125 | | static WriteRunnable* |
126 | | AdoptBuffer(MutableBlobStorage* aBlobStorage, void* aData, uint32_t aLength) |
127 | 0 | { |
128 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
129 | 0 | MOZ_ASSERT(aBlobStorage); |
130 | 0 | MOZ_ASSERT(aData); |
131 | 0 |
|
132 | 0 | return new WriteRunnable(aBlobStorage, aData, aLength); |
133 | 0 | } |
134 | | |
135 | | NS_IMETHOD |
136 | | Run() override |
137 | 0 | { |
138 | 0 | MOZ_ASSERT(!NS_IsMainThread()); |
139 | 0 | MOZ_ASSERT(mBlobStorage); |
140 | 0 |
|
141 | 0 | PRFileDesc* fd = mBlobStorage->GetFD(); |
142 | 0 | if (!fd) { |
143 | 0 | // The file descriptor has been closed in the meantime. |
144 | 0 | return NS_OK; |
145 | 0 | } |
146 | 0 | |
147 | 0 | int32_t written = PR_Write(fd, mData, mLength); |
148 | 0 | if (NS_WARN_IF(written < 0 || uint32_t(written) != mLength)) { |
149 | 0 | mBlobStorage->CloseFD(); |
150 | 0 | return mBlobStorage->EventTarget()->Dispatch( |
151 | 0 | new ErrorPropagationRunnable(mBlobStorage, NS_ERROR_FAILURE), |
152 | 0 | NS_DISPATCH_NORMAL); |
153 | 0 | } |
154 | 0 |
|
155 | 0 | return NS_OK; |
156 | 0 | } |
157 | | |
158 | | private: |
159 | | WriteRunnable(MutableBlobStorage* aBlobStorage, void* aData, uint32_t aLength) |
160 | | : Runnable("dom::WriteRunnable") |
161 | | , mBlobStorage(aBlobStorage) |
162 | | , mData(aData) |
163 | | , mLength(aLength) |
164 | 0 | { |
165 | 0 | MOZ_ASSERT(mBlobStorage); |
166 | 0 | MOZ_ASSERT(aData); |
167 | 0 | } |
168 | | |
169 | | ~WriteRunnable() |
170 | 0 | { |
171 | 0 | free(mData); |
172 | 0 | } |
173 | | |
174 | | RefPtr<MutableBlobStorage> mBlobStorage; |
175 | | void* mData; |
176 | | uint32_t mLength; |
177 | | }; |
178 | | |
179 | | // This runnable closes the FD in case something goes wrong or the temporary |
180 | | // file is not needed anymore. |
181 | | class CloseFileRunnable final : public Runnable |
182 | | { |
183 | | public: |
184 | | explicit CloseFileRunnable(PRFileDesc* aFD) |
185 | | : Runnable("dom::CloseFileRunnable") |
186 | | , mFD(aFD) |
187 | 0 | {} |
188 | | |
189 | | NS_IMETHOD |
190 | | Run() override |
191 | 0 | { |
192 | 0 | MOZ_ASSERT(!NS_IsMainThread()); |
193 | 0 | PR_Close(mFD); |
194 | 0 | mFD = nullptr; |
195 | 0 | return NS_OK; |
196 | 0 | } |
197 | | |
198 | | private: |
199 | | ~CloseFileRunnable() |
200 | 0 | { |
201 | 0 | if (mFD) { |
202 | 0 | PR_Close(mFD); |
203 | 0 | } |
204 | 0 | } |
205 | | |
206 | | PRFileDesc* mFD; |
207 | | }; |
208 | | |
209 | | // This runnable is dispatched to the main-thread from the IO thread and its |
210 | | // task is to create the blob and inform the callback. |
211 | | class CreateBlobRunnable final : public Runnable |
212 | | , public TemporaryIPCBlobChildCallback |
213 | | { |
214 | | public: |
215 | | // We need to always declare refcounting because |
216 | | // TemporaryIPCBlobChildCallback has pure-virtual refcounting. |
217 | | NS_DECL_ISUPPORTS_INHERITED |
218 | | |
219 | | CreateBlobRunnable(MutableBlobStorage* aBlobStorage, |
220 | | already_AddRefed<nsISupports> aParent, |
221 | | const nsACString& aContentType, |
222 | | already_AddRefed<MutableBlobStorageCallback> aCallback) |
223 | | : Runnable("dom::CreateBlobRunnable") |
224 | | , mBlobStorage(aBlobStorage) |
225 | | , mParent(aParent) |
226 | | , mContentType(aContentType) |
227 | | , mCallback(aCallback) |
228 | 0 | { |
229 | 0 | MOZ_ASSERT(!NS_IsMainThread()); |
230 | 0 | MOZ_ASSERT(aBlobStorage); |
231 | 0 | } |
232 | | |
233 | | NS_IMETHOD |
234 | | Run() override |
235 | 0 | { |
236 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
237 | 0 | MOZ_ASSERT(mBlobStorage); |
238 | 0 | mBlobStorage->AskForBlob(this, mContentType); |
239 | 0 | return NS_OK; |
240 | 0 | } |
241 | | |
242 | | void |
243 | | OperationSucceeded(BlobImpl* aBlobImpl) override |
244 | 0 | { |
245 | 0 | nsCOMPtr<nsISupports> parent(std::move(mParent)); |
246 | 0 | RefPtr<MutableBlobStorageCallback> callback(std::move(mCallback)); |
247 | 0 |
|
248 | 0 | RefPtr<Blob> blob = Blob::Create(parent, aBlobImpl); |
249 | 0 | callback->BlobStoreCompleted(mBlobStorage, blob, NS_OK); |
250 | 0 | } |
251 | | |
252 | | void |
253 | | OperationFailed(nsresult aRv) override |
254 | 0 | { |
255 | 0 | RefPtr<MutableBlobStorageCallback> callback(std::move(mCallback)); |
256 | 0 | callback->BlobStoreCompleted(mBlobStorage, nullptr, aRv); |
257 | 0 | } |
258 | | |
259 | | private: |
260 | | ~CreateBlobRunnable() |
261 | 0 | { |
262 | 0 | MOZ_ASSERT(mBlobStorage); |
263 | 0 | // If something when wrong, we still have to release data in the correct |
264 | 0 | // thread. |
265 | 0 | NS_ProxyRelease( |
266 | 0 | "CreateBlobRunnable::mParent", |
267 | 0 | mBlobStorage->EventTarget(), mParent.forget()); |
268 | 0 | NS_ProxyRelease( |
269 | 0 | "CreateBlobRunnable::mCallback", |
270 | 0 | mBlobStorage->EventTarget(), mCallback.forget()); |
271 | 0 | } |
272 | | |
273 | | RefPtr<MutableBlobStorage> mBlobStorage; |
274 | | nsCOMPtr<nsISupports> mParent; |
275 | | nsCString mContentType; |
276 | | RefPtr<MutableBlobStorageCallback> mCallback; |
277 | | }; |
278 | | |
279 | | NS_IMPL_ISUPPORTS_INHERITED0(CreateBlobRunnable, Runnable) |
280 | | |
281 | | // This task is used to know when the writing is completed. From the IO thread |
282 | | // it dispatches a CreateBlobRunnable to the main-thread. |
283 | | class LastRunnable final : public Runnable |
284 | | { |
285 | | public: |
286 | | LastRunnable(MutableBlobStorage* aBlobStorage, |
287 | | nsISupports* aParent, |
288 | | const nsACString& aContentType, |
289 | | MutableBlobStorageCallback* aCallback) |
290 | | : Runnable("dom::LastRunnable") |
291 | | , mBlobStorage(aBlobStorage) |
292 | | , mParent(aParent) |
293 | | , mContentType(aContentType) |
294 | | , mCallback(aCallback) |
295 | 0 | { |
296 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
297 | 0 | MOZ_ASSERT(mBlobStorage); |
298 | 0 | MOZ_ASSERT(aCallback); |
299 | 0 | } |
300 | | |
301 | | NS_IMETHOD |
302 | | Run() override |
303 | 0 | { |
304 | 0 | MOZ_ASSERT(!NS_IsMainThread()); |
305 | 0 |
|
306 | 0 | RefPtr<Runnable> runnable = |
307 | 0 | new CreateBlobRunnable(mBlobStorage, mParent.forget(), |
308 | 0 | mContentType, mCallback.forget()); |
309 | 0 | return mBlobStorage->EventTarget()->Dispatch(runnable, NS_DISPATCH_NORMAL); |
310 | 0 | } |
311 | | |
312 | | private: |
313 | | ~LastRunnable() |
314 | 0 | { |
315 | 0 | MOZ_ASSERT(mBlobStorage); |
316 | 0 | // If something when wrong, we still have to release data in the correct |
317 | 0 | // thread. |
318 | 0 | NS_ProxyRelease( |
319 | 0 | "LastRunnable::mParent", |
320 | 0 | mBlobStorage->EventTarget(), mParent.forget()); |
321 | 0 | NS_ProxyRelease( |
322 | 0 | "LastRunnable::mCallback", |
323 | 0 | mBlobStorage->EventTarget(), mCallback.forget()); |
324 | 0 | } |
325 | | |
326 | | RefPtr<MutableBlobStorage> mBlobStorage; |
327 | | nsCOMPtr<nsISupports> mParent; |
328 | | nsCString mContentType; |
329 | | RefPtr<MutableBlobStorageCallback> mCallback; |
330 | | }; |
331 | | |
332 | | } // anonymous namespace |
333 | | |
334 | | MutableBlobStorage::MutableBlobStorage(MutableBlobStorageType aType, |
335 | | nsIEventTarget* aEventTarget, |
336 | | uint32_t aMaxMemory) |
337 | | : mMutex("MutableBlobStorage::mMutex") |
338 | | , mData(nullptr) |
339 | | , mDataLen(0) |
340 | | , mDataBufferLen(0) |
341 | | , mStorageState(aType == eOnlyInMemory ? eKeepInMemory : eInMemory) |
342 | | , mFD(nullptr) |
343 | | , mErrorResult(NS_OK) |
344 | | , mEventTarget(aEventTarget) |
345 | | , mMaxMemory(aMaxMemory) |
346 | 0 | { |
347 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
348 | 0 |
|
349 | 0 | if (!mEventTarget) { |
350 | 0 | mEventTarget = GetMainThreadEventTarget(); |
351 | 0 | } |
352 | 0 |
|
353 | 0 | if (aMaxMemory == 0 && aType == eCouldBeInTemporaryFile) { |
354 | 0 | mMaxMemory = Preferences::GetUint("dom.blob.memoryToTemporaryFile", |
355 | 0 | BLOB_MEMORY_TEMPORARY_FILE); |
356 | 0 | } |
357 | 0 |
|
358 | 0 | MOZ_ASSERT(mEventTarget); |
359 | 0 | } |
360 | | |
361 | | MutableBlobStorage::~MutableBlobStorage() |
362 | 0 | { |
363 | 0 | free(mData); |
364 | 0 |
|
365 | 0 | if (mFD) { |
366 | 0 | RefPtr<Runnable> runnable = new CloseFileRunnable(mFD); |
367 | 0 | Unused << DispatchToIOThread(runnable.forget()); |
368 | 0 | } |
369 | 0 |
|
370 | 0 | if (mTaskQueue) { |
371 | 0 | mTaskQueue->BeginShutdown(); |
372 | 0 | } |
373 | 0 |
|
374 | 0 | if (mActor) { |
375 | 0 | NS_ProxyRelease("MutableBlobStorage::mActor", |
376 | 0 | EventTarget(), mActor.forget()); |
377 | 0 | } |
378 | 0 | } |
379 | | |
380 | | void |
381 | | MutableBlobStorage::GetBlobWhenReady(nsISupports* aParent, |
382 | | const nsACString& aContentType, |
383 | | MutableBlobStorageCallback* aCallback) |
384 | 0 | { |
385 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
386 | 0 | MOZ_ASSERT(aCallback); |
387 | 0 |
|
388 | 0 | MutexAutoLock lock(mMutex); |
389 | 0 |
|
390 | 0 | // GetBlob can be called just once. |
391 | 0 | MOZ_ASSERT(mStorageState != eClosed); |
392 | 0 | StorageState previousState = mStorageState; |
393 | 0 | mStorageState = eClosed; |
394 | 0 |
|
395 | 0 | if (previousState == eInTemporaryFile) { |
396 | 0 | if (NS_FAILED(mErrorResult)) { |
397 | 0 | MOZ_ASSERT(!mActor); |
398 | 0 |
|
399 | 0 | RefPtr<Runnable> runnable = |
400 | 0 | new BlobCreationDoneRunnable(this, aCallback, nullptr, mErrorResult); |
401 | 0 | EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); |
402 | 0 | return; |
403 | 0 | } |
404 | 0 |
|
405 | 0 | MOZ_ASSERT(mActor); |
406 | 0 |
|
407 | 0 | // We want to wait until all the WriteRunnable are completed. The way we do |
408 | 0 | // this is to go to the I/O thread and then we come back: the runnables are |
409 | 0 | // executed in order and this LastRunnable will be... the last one. |
410 | 0 | // This Runnable will also close the FD on the I/O thread. |
411 | 0 | RefPtr<Runnable> runnable = |
412 | 0 | new LastRunnable(this, aParent, aContentType, aCallback); |
413 | 0 |
|
414 | 0 | // If the dispatching fails, we are shutting down and it's fine to do not |
415 | 0 | // run the callback. |
416 | 0 | Unused << DispatchToIOThread(runnable.forget()); |
417 | 0 | return; |
418 | 0 | } |
419 | 0 | |
420 | 0 | // If we are waiting for the temporary file, it's better to wait... |
421 | 0 | if (previousState == eWaitingForTemporaryFile) { |
422 | 0 | mPendingParent = aParent; |
423 | 0 | mPendingContentType = aContentType; |
424 | 0 | mPendingCallback = aCallback; |
425 | 0 | return; |
426 | 0 | } |
427 | 0 | |
428 | 0 | RefPtr<BlobImpl> blobImpl; |
429 | 0 |
|
430 | 0 | if (mData) { |
431 | 0 | blobImpl = new MemoryBlobImpl(mData, mDataLen, |
432 | 0 | NS_ConvertUTF8toUTF16(aContentType)); |
433 | 0 |
|
434 | 0 | mData = nullptr; // The MemoryBlobImpl takes ownership of the buffer |
435 | 0 | mDataLen = 0; |
436 | 0 | mDataBufferLen = 0; |
437 | 0 | } else { |
438 | 0 | blobImpl = new EmptyBlobImpl(NS_ConvertUTF8toUTF16(aContentType)); |
439 | 0 | } |
440 | 0 |
|
441 | 0 | RefPtr<Blob> blob = Blob::Create(aParent, blobImpl); |
442 | 0 | RefPtr<BlobCreationDoneRunnable> runnable = |
443 | 0 | new BlobCreationDoneRunnable(this, aCallback, blob, NS_OK); |
444 | 0 |
|
445 | 0 | nsresult error = EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); |
446 | 0 | if (NS_WARN_IF(NS_FAILED(error))) { |
447 | 0 | return; |
448 | 0 | } |
449 | 0 | } |
450 | | |
451 | | nsresult |
452 | | MutableBlobStorage::Append(const void* aData, uint32_t aLength) |
453 | 0 | { |
454 | 0 | // This method can be called on any thread. |
455 | 0 |
|
456 | 0 | MutexAutoLock lock(mMutex); |
457 | 0 | MOZ_ASSERT(mStorageState != eClosed); |
458 | 0 | NS_ENSURE_ARG_POINTER(aData); |
459 | 0 |
|
460 | 0 | if (!aLength) { |
461 | 0 | return NS_OK; |
462 | 0 | } |
463 | 0 | |
464 | 0 | // If eInMemory is the current Storage state, we could maybe migrate to |
465 | 0 | // a temporary file. |
466 | 0 | if (mStorageState == eInMemory && ShouldBeTemporaryStorage(lock, aLength) && |
467 | 0 | !MaybeCreateTemporaryFile(lock)) { |
468 | 0 | return NS_ERROR_FAILURE; |
469 | 0 | } |
470 | 0 | |
471 | 0 | // If we are already in the temporaryFile mode, we have to dispatch a |
472 | 0 | // runnable. |
473 | 0 | if (mStorageState == eInTemporaryFile) { |
474 | 0 | // If a previous operation failed, let's return that error now. |
475 | 0 | if (NS_FAILED(mErrorResult)) { |
476 | 0 | return mErrorResult; |
477 | 0 | } |
478 | 0 | |
479 | 0 | RefPtr<WriteRunnable> runnable = |
480 | 0 | WriteRunnable::CopyBuffer(this, aData, aLength); |
481 | 0 | if (NS_WARN_IF(!runnable)) { |
482 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
483 | 0 | } |
484 | 0 | |
485 | 0 | nsresult rv = DispatchToIOThread(runnable.forget()); |
486 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
487 | 0 | return rv; |
488 | 0 | } |
489 | 0 | |
490 | 0 | mDataLen += aLength; |
491 | 0 | return NS_OK; |
492 | 0 | } |
493 | 0 | |
494 | 0 | // By default, we store in memory. |
495 | 0 | |
496 | 0 | uint64_t offset = mDataLen; |
497 | 0 |
|
498 | 0 | if (!ExpandBufferSize(lock, aLength)) { |
499 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
500 | 0 | } |
501 | 0 | |
502 | 0 | memcpy((char*)mData + offset, aData, aLength); |
503 | 0 | return NS_OK; |
504 | 0 | } |
505 | | |
506 | | bool |
507 | | MutableBlobStorage::ExpandBufferSize(const MutexAutoLock& aProofOfLock, |
508 | | uint64_t aSize) |
509 | 0 | { |
510 | 0 | MOZ_ASSERT(mStorageState < eInTemporaryFile); |
511 | 0 |
|
512 | 0 | if (mDataBufferLen >= mDataLen + aSize) { |
513 | 0 | mDataLen += aSize; |
514 | 0 | return true; |
515 | 0 | } |
516 | 0 | |
517 | 0 | // Start at 1 or we'll loop forever. |
518 | 0 | CheckedUint32 bufferLen = |
519 | 0 | std::max<uint32_t>(static_cast<uint32_t>(mDataBufferLen), 1); |
520 | 0 | while (bufferLen.isValid() && bufferLen.value() < mDataLen + aSize) { |
521 | 0 | bufferLen *= 2; |
522 | 0 | } |
523 | 0 |
|
524 | 0 | if (!bufferLen.isValid()) { |
525 | 0 | return false; |
526 | 0 | } |
527 | 0 | |
528 | 0 | void* data = realloc(mData, bufferLen.value()); |
529 | 0 | if (!data) { |
530 | 0 | return false; |
531 | 0 | } |
532 | 0 | |
533 | 0 | mData = data; |
534 | 0 | mDataBufferLen = bufferLen.value(); |
535 | 0 | mDataLen += aSize; |
536 | 0 | return true; |
537 | 0 | } |
538 | | |
539 | | bool |
540 | | MutableBlobStorage::ShouldBeTemporaryStorage(const MutexAutoLock& aProofOfLock, |
541 | | uint64_t aSize) const |
542 | 0 | { |
543 | 0 | MOZ_ASSERT(mStorageState == eInMemory); |
544 | 0 |
|
545 | 0 | CheckedUint32 bufferSize = mDataLen; |
546 | 0 | bufferSize += aSize; |
547 | 0 |
|
548 | 0 | if (!bufferSize.isValid()) { |
549 | 0 | return false; |
550 | 0 | } |
551 | 0 | |
552 | 0 | return bufferSize.value() >= mMaxMemory; |
553 | 0 | } |
554 | | |
555 | | bool |
556 | | MutableBlobStorage::MaybeCreateTemporaryFile(const MutexAutoLock& aProofOfLock) |
557 | 0 | { |
558 | 0 | mStorageState = eWaitingForTemporaryFile; |
559 | 0 |
|
560 | 0 | if (!NS_IsMainThread()) { |
561 | 0 | RefPtr<MutableBlobStorage> self = this; |
562 | 0 | nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( |
563 | 0 | "MutableBlobStorage::MaybeCreateTemporaryFile", |
564 | 0 | [self]() { self->MaybeCreateTemporaryFileOnMainThread(); }); |
565 | 0 | EventTarget()->Dispatch(r.forget(), NS_DISPATCH_SYNC); |
566 | 0 | return !!mActor; |
567 | 0 | } |
568 | 0 |
|
569 | 0 | MaybeCreateTemporaryFileOnMainThread(); |
570 | 0 | return !!mActor; |
571 | 0 | } |
572 | | |
573 | | void |
574 | | MutableBlobStorage::MaybeCreateTemporaryFileOnMainThread() |
575 | 0 | { |
576 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
577 | 0 | MOZ_ASSERT(!mActor); |
578 | 0 |
|
579 | 0 | mozilla::ipc::PBackgroundChild* actorChild = |
580 | 0 | mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(); |
581 | 0 | if (NS_WARN_IF(!actorChild)) { |
582 | 0 | return; |
583 | 0 | } |
584 | 0 | |
585 | 0 | mActor = new TemporaryIPCBlobChild(this); |
586 | 0 | actorChild->SendPTemporaryIPCBlobConstructor(mActor); |
587 | 0 |
|
588 | 0 | // We need manually to increase the reference for this actor because the |
589 | 0 | // IPC allocator method is not triggered. The Release() is called by IPDL |
590 | 0 | // when the actor is deleted. |
591 | 0 | mActor.get()->AddRef(); |
592 | 0 |
|
593 | 0 | // The actor will call us when the FileDescriptor is received. |
594 | 0 | } |
595 | | |
596 | | void |
597 | | MutableBlobStorage::TemporaryFileCreated(PRFileDesc* aFD) |
598 | 0 | { |
599 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
600 | 0 |
|
601 | 0 | MutexAutoLock lock(mMutex); |
602 | 0 | MOZ_ASSERT(mStorageState == eWaitingForTemporaryFile || |
603 | 0 | mStorageState == eClosed); |
604 | 0 | MOZ_ASSERT_IF(mPendingCallback, mStorageState == eClosed); |
605 | 0 | MOZ_ASSERT(mActor); |
606 | 0 | MOZ_ASSERT(aFD); |
607 | 0 |
|
608 | 0 | // If the object has been already closed and we don't need to execute a |
609 | 0 | // callback, we need just to close the file descriptor in the correct thread. |
610 | 0 | if (mStorageState == eClosed && !mPendingCallback) { |
611 | 0 | RefPtr<Runnable> runnable = new CloseFileRunnable(aFD); |
612 | 0 |
|
613 | 0 | // If this dispatching fails, CloseFileRunnable will close the FD in the |
614 | 0 | // DTOR on the current thread. |
615 | 0 | Unused << DispatchToIOThread(runnable.forget()); |
616 | 0 |
|
617 | 0 | // Let's inform the parent that we have nothing else to do. |
618 | 0 | mActor->SendOperationFailed(); |
619 | 0 | mActor = nullptr; |
620 | 0 | return; |
621 | 0 | } |
622 | 0 | |
623 | 0 | // If we still receiving data, we can proceed in temporary-file mode. |
624 | 0 | if (mStorageState == eWaitingForTemporaryFile) { |
625 | 0 | mStorageState = eInTemporaryFile; |
626 | 0 | } |
627 | 0 |
|
628 | 0 | mFD = aFD; |
629 | 0 | MOZ_ASSERT(NS_SUCCEEDED(mErrorResult)); |
630 | 0 |
|
631 | 0 | // This runnable takes the ownership of mData and it will write this buffer |
632 | 0 | // into the temporary file. |
633 | 0 | RefPtr<WriteRunnable> runnable = |
634 | 0 | WriteRunnable::AdoptBuffer(this, mData, mDataLen); |
635 | 0 | MOZ_ASSERT(runnable); |
636 | 0 |
|
637 | 0 | mData = nullptr; |
638 | 0 |
|
639 | 0 | nsresult rv = DispatchToIOThread(runnable.forget()); |
640 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
641 | 0 | // Shutting down, we cannot continue. |
642 | 0 | return; |
643 | 0 | } |
644 | 0 | |
645 | 0 | // If we are closed, it means that GetBlobWhenReady() has been called when we |
646 | 0 | // were already waiting for a temporary file-descriptor. Finally we are here, |
647 | 0 | // AdoptBuffer runnable is going to write the current buffer into this file. |
648 | 0 | // After that, there is nothing else to write, and we dispatch LastRunnable |
649 | 0 | // which ends up calling mPendingCallback via CreateBlobRunnable. |
650 | 0 | if (mStorageState == eClosed) { |
651 | 0 | MOZ_ASSERT(mPendingCallback); |
652 | 0 |
|
653 | 0 | RefPtr<Runnable> runnable = |
654 | 0 | new LastRunnable(this, mPendingParent, mPendingContentType, |
655 | 0 | mPendingCallback); |
656 | 0 | Unused << DispatchToIOThread(runnable.forget()); |
657 | 0 |
|
658 | 0 | mPendingParent = nullptr; |
659 | 0 | mPendingCallback = nullptr; |
660 | 0 | } |
661 | 0 | } |
662 | | |
663 | | void |
664 | | MutableBlobStorage::AskForBlob(TemporaryIPCBlobChildCallback* aCallback, |
665 | | const nsACString& aContentType) |
666 | 0 | { |
667 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
668 | 0 |
|
669 | 0 | MutexAutoLock lock(mMutex); |
670 | 0 | MOZ_ASSERT(mStorageState == eClosed); |
671 | 0 | MOZ_ASSERT(mFD); |
672 | 0 | MOZ_ASSERT(mActor); |
673 | 0 | MOZ_ASSERT(aCallback); |
674 | 0 |
|
675 | 0 | // Let's pass the FileDescriptor to the parent actor in order to keep the file |
676 | 0 | // locked on windows. |
677 | 0 | mActor->AskForBlob(aCallback, aContentType, mFD); |
678 | 0 |
|
679 | 0 | // The previous operation has duplicated the file descriptor. Now we can close |
680 | 0 | // mFD. The parent will take care of closing the duplicated file descriptor on |
681 | 0 | // its side. |
682 | 0 | RefPtr<Runnable> runnable = new CloseFileRunnable(mFD); |
683 | 0 | Unused << DispatchToIOThread(runnable.forget()); |
684 | 0 |
|
685 | 0 | mFD = nullptr; |
686 | 0 | mActor = nullptr; |
687 | 0 | } |
688 | | |
689 | | void |
690 | | MutableBlobStorage::ErrorPropagated(nsresult aRv) |
691 | 0 | { |
692 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
693 | 0 |
|
694 | 0 | MutexAutoLock lock(mMutex); |
695 | 0 | mErrorResult = aRv; |
696 | 0 |
|
697 | 0 | if (mActor) { |
698 | 0 | mActor->SendOperationFailed(); |
699 | 0 | mActor = nullptr; |
700 | 0 | } |
701 | 0 | } |
702 | | |
703 | | nsresult |
704 | | MutableBlobStorage::DispatchToIOThread(already_AddRefed<nsIRunnable> aRunnable) |
705 | 0 | { |
706 | 0 | if (!mTaskQueue) { |
707 | 0 | nsCOMPtr<nsIEventTarget> target |
708 | 0 | = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); |
709 | 0 | MOZ_ASSERT(target); |
710 | 0 |
|
711 | 0 | mTaskQueue = new TaskQueue(target.forget()); |
712 | 0 | } |
713 | 0 |
|
714 | 0 | nsCOMPtr<nsIRunnable> runnable(aRunnable); |
715 | 0 | nsresult rv = mTaskQueue->Dispatch(runnable.forget()); |
716 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
717 | 0 | return rv; |
718 | 0 | } |
719 | 0 | |
720 | 0 | return NS_OK; |
721 | 0 | } |
722 | | |
723 | | size_t |
724 | | MutableBlobStorage::SizeOfCurrentMemoryBuffer() |
725 | 0 | { |
726 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
727 | 0 | MutexAutoLock lock(mMutex); |
728 | 0 | return mStorageState < eInTemporaryFile ? mDataLen : 0; |
729 | 0 | } |
730 | | |
731 | | PRFileDesc* |
732 | | MutableBlobStorage::GetFD() |
733 | 0 | { |
734 | 0 | MOZ_ASSERT(!NS_IsMainThread()); |
735 | 0 | MutexAutoLock lock(mMutex); |
736 | 0 | return mFD; |
737 | 0 | } |
738 | | |
739 | | void |
740 | | MutableBlobStorage::CloseFD() |
741 | 0 | { |
742 | 0 | MOZ_ASSERT(!NS_IsMainThread()); |
743 | 0 | MutexAutoLock lock(mMutex); |
744 | 0 | MOZ_ASSERT(mFD); |
745 | 0 |
|
746 | 0 | PR_Close(mFD); |
747 | 0 | mFD = nullptr; |
748 | 0 | } |
749 | | |
750 | | } // dom namespace |
751 | | } // mozilla namespace |