Bug 1854550 - pt 10. Allow LOG() with zero extra arguments r=glandium
[gecko.git] / dom / file / MutableBlobStorage.cpp
blob75d0dce6de919d9908db3beae3fa2d89b6fca962
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/. */
7 #include "EmptyBlobImpl.h"
8 #include "MutableBlobStorage.h"
9 #include "MemoryBlobImpl.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/dom/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"
22 #define BLOB_MEMORY_TEMPORARY_FILE 1048576
24 namespace mozilla::dom {
26 namespace {
28 // This class uses the callback to inform when the Blob is created or when the
29 // error must be propagated.
30 class BlobCreationDoneRunnable final : public Runnable {
31 public:
32 BlobCreationDoneRunnable(MutableBlobStorage* aBlobStorage,
33 MutableBlobStorageCallback* aCallback,
34 BlobImpl* aBlobImpl, nsresult aRv)
35 : Runnable("dom::BlobCreationDoneRunnable"),
36 mBlobStorage(aBlobStorage),
37 mCallback(aCallback),
38 mBlobImpl(aBlobImpl),
39 mRv(aRv) {
40 MOZ_ASSERT(aBlobStorage);
41 MOZ_ASSERT(aCallback);
42 MOZ_ASSERT((NS_FAILED(aRv) && !aBlobImpl) ||
43 (NS_SUCCEEDED(aRv) && aBlobImpl));
46 NS_IMETHOD
47 Run() override {
48 MOZ_ASSERT(NS_IsMainThread());
49 MOZ_ASSERT(mBlobStorage);
50 mCallback->BlobStoreCompleted(mBlobStorage, mBlobImpl, mRv);
51 mCallback = nullptr;
52 mBlobImpl = nullptr;
53 return NS_OK;
56 private:
57 ~BlobCreationDoneRunnable() override {
58 MOZ_ASSERT(mBlobStorage);
59 // If something when wrong, we still have to release these objects in the
60 // correct thread.
61 NS_ProxyRelease("BlobCreationDoneRunnable::mCallback",
62 mBlobStorage->EventTarget(), mCallback.forget());
65 RefPtr<MutableBlobStorage> mBlobStorage;
66 RefPtr<MutableBlobStorageCallback> mCallback;
67 RefPtr<BlobImpl> mBlobImpl;
68 nsresult mRv;
71 // Simple runnable to propagate the error to the BlobStorage.
72 class ErrorPropagationRunnable final : public Runnable {
73 public:
74 ErrorPropagationRunnable(MutableBlobStorage* aBlobStorage, nsresult aRv)
75 : Runnable("dom::ErrorPropagationRunnable"),
76 mBlobStorage(aBlobStorage),
77 mRv(aRv) {}
79 NS_IMETHOD
80 Run() override {
81 mBlobStorage->ErrorPropagated(mRv);
82 return NS_OK;
85 private:
86 RefPtr<MutableBlobStorage> mBlobStorage;
87 nsresult mRv;
90 // This runnable moves a buffer to the IO thread and there, it writes it into
91 // the temporary file, if its File Descriptor has not been already closed.
92 class WriteRunnable final : public Runnable {
93 public:
94 static WriteRunnable* CopyBuffer(MutableBlobStorage* aBlobStorage,
95 const void* aData, uint32_t aLength) {
96 MOZ_ASSERT(aBlobStorage);
97 MOZ_ASSERT(aData);
99 // We have to take a copy of this buffer.
100 void* data = malloc(aLength);
101 if (!data) {
102 return nullptr;
105 memcpy((char*)data, aData, aLength);
106 return new WriteRunnable(aBlobStorage, data, aLength);
109 static WriteRunnable* AdoptBuffer(MutableBlobStorage* aBlobStorage,
110 void* aData, uint32_t aLength) {
111 MOZ_ASSERT(NS_IsMainThread());
112 MOZ_ASSERT(aBlobStorage);
113 MOZ_ASSERT(aData);
115 return new WriteRunnable(aBlobStorage, aData, aLength);
118 NS_IMETHOD
119 Run() override {
120 MOZ_ASSERT(!NS_IsMainThread());
121 MOZ_ASSERT(mBlobStorage);
123 PRFileDesc* fd = mBlobStorage->GetFD();
124 if (!fd) {
125 // The file descriptor has been closed in the meantime.
126 return NS_OK;
129 int32_t written = PR_Write(fd, mData, mLength);
130 if (NS_WARN_IF(written < 0 || uint32_t(written) != mLength)) {
131 mBlobStorage->CloseFD();
132 return mBlobStorage->EventTarget()->Dispatch(
133 new ErrorPropagationRunnable(mBlobStorage, NS_ERROR_FAILURE),
134 NS_DISPATCH_NORMAL);
137 return NS_OK;
140 private:
141 WriteRunnable(MutableBlobStorage* aBlobStorage, void* aData, uint32_t aLength)
142 : Runnable("dom::WriteRunnable"),
143 mBlobStorage(aBlobStorage),
144 mData(aData),
145 mLength(aLength) {
146 MOZ_ASSERT(mBlobStorage);
147 MOZ_ASSERT(aData);
150 ~WriteRunnable() override { free(mData); }
152 RefPtr<MutableBlobStorage> mBlobStorage;
153 void* mData;
154 uint32_t mLength;
157 // This runnable closes the FD in case something goes wrong or the temporary
158 // file is not needed anymore.
159 class CloseFileRunnable final : public Runnable {
160 public:
161 explicit CloseFileRunnable(PRFileDesc* aFD)
162 : Runnable("dom::CloseFileRunnable"), mFD(aFD) {}
164 NS_IMETHOD
165 Run() override {
166 MOZ_ASSERT(!NS_IsMainThread());
167 PR_Close(mFD);
168 mFD = nullptr;
169 return NS_OK;
172 private:
173 ~CloseFileRunnable() override {
174 if (mFD) {
175 PR_Close(mFD);
179 PRFileDesc* mFD;
182 // This runnable is dispatched to the main-thread from the IO thread and its
183 // task is to create the blob and inform the callback.
184 class CreateBlobRunnable final : public Runnable,
185 public TemporaryIPCBlobChildCallback {
186 public:
187 // We need to always declare refcounting because
188 // TemporaryIPCBlobChildCallback has pure-virtual refcounting.
189 NS_DECL_ISUPPORTS_INHERITED
191 CreateBlobRunnable(MutableBlobStorage* aBlobStorage,
192 const nsACString& aContentType,
193 already_AddRefed<MutableBlobStorageCallback> aCallback)
194 : Runnable("dom::CreateBlobRunnable"),
195 mBlobStorage(aBlobStorage),
196 mContentType(aContentType),
197 mCallback(aCallback) {
198 MOZ_ASSERT(!NS_IsMainThread());
199 MOZ_ASSERT(aBlobStorage);
202 NS_IMETHOD
203 Run() override {
204 MOZ_ASSERT(NS_IsMainThread());
205 MOZ_ASSERT(mBlobStorage);
206 mBlobStorage->AskForBlob(this, mContentType);
207 return NS_OK;
210 void OperationSucceeded(BlobImpl* aBlobImpl) override {
211 RefPtr<MutableBlobStorageCallback> callback(std::move(mCallback));
212 callback->BlobStoreCompleted(mBlobStorage, aBlobImpl, NS_OK);
215 void OperationFailed(nsresult aRv) override {
216 RefPtr<MutableBlobStorageCallback> callback(std::move(mCallback));
217 callback->BlobStoreCompleted(mBlobStorage, nullptr, aRv);
220 private:
221 ~CreateBlobRunnable() override {
222 MOZ_ASSERT(mBlobStorage);
223 // If something when wrong, we still have to release data in the correct
224 // thread.
225 NS_ProxyRelease("CreateBlobRunnable::mCallback",
226 mBlobStorage->EventTarget(), mCallback.forget());
229 RefPtr<MutableBlobStorage> mBlobStorage;
230 nsCString mContentType;
231 RefPtr<MutableBlobStorageCallback> mCallback;
234 NS_IMPL_ISUPPORTS_INHERITED0(CreateBlobRunnable, Runnable)
236 // This task is used to know when the writing is completed. From the IO thread
237 // it dispatches a CreateBlobRunnable to the main-thread.
238 class LastRunnable final : public Runnable {
239 public:
240 LastRunnable(MutableBlobStorage* aBlobStorage, const nsACString& aContentType,
241 MutableBlobStorageCallback* aCallback)
242 : Runnable("dom::LastRunnable"),
243 mBlobStorage(aBlobStorage),
244 mContentType(aContentType),
245 mCallback(aCallback) {
246 MOZ_ASSERT(NS_IsMainThread());
247 MOZ_ASSERT(mBlobStorage);
248 MOZ_ASSERT(aCallback);
251 NS_IMETHOD
252 Run() override {
253 MOZ_ASSERT(!NS_IsMainThread());
255 RefPtr<Runnable> runnable =
256 new CreateBlobRunnable(mBlobStorage, mContentType, mCallback.forget());
257 return mBlobStorage->EventTarget()->Dispatch(runnable, NS_DISPATCH_NORMAL);
260 private:
261 ~LastRunnable() override {
262 MOZ_ASSERT(mBlobStorage);
263 // If something when wrong, we still have to release data in the correct
264 // thread.
265 NS_ProxyRelease("LastRunnable::mCallback", mBlobStorage->EventTarget(),
266 mCallback.forget());
269 RefPtr<MutableBlobStorage> mBlobStorage;
270 nsCString mContentType;
271 RefPtr<MutableBlobStorageCallback> mCallback;
274 } // anonymous namespace
276 MutableBlobStorage::MutableBlobStorage(MutableBlobStorageType aType,
277 nsIEventTarget* aEventTarget,
278 uint32_t aMaxMemory)
279 : mMutex("MutableBlobStorage::mMutex"),
280 mData(nullptr),
281 mDataLen(0),
282 mDataBufferLen(0),
283 mStorageState(aType == eOnlyInMemory ? eKeepInMemory : eInMemory),
284 mFD(nullptr),
285 mErrorResult(NS_OK),
286 mEventTarget(aEventTarget),
287 mMaxMemory(aMaxMemory) {
288 MOZ_ASSERT(NS_IsMainThread());
290 if (!mEventTarget) {
291 mEventTarget = GetMainThreadSerialEventTarget();
294 if (aMaxMemory == 0 && aType == eCouldBeInTemporaryFile) {
295 mMaxMemory = Preferences::GetUint("dom.blob.memoryToTemporaryFile",
296 BLOB_MEMORY_TEMPORARY_FILE);
299 MOZ_ASSERT(mEventTarget);
302 MutableBlobStorage::~MutableBlobStorage() {
303 free(mData);
305 if (mFD) {
306 RefPtr<Runnable> runnable = new CloseFileRunnable(mFD);
307 (void)DispatchToIOThread(runnable.forget());
310 if (mTaskQueue) {
311 mTaskQueue->BeginShutdown();
314 if (mActor) {
315 NS_ProxyRelease("MutableBlobStorage::mActor", EventTarget(),
316 mActor.forget());
320 void MutableBlobStorage::GetBlobImplWhenReady(
321 const nsACString& aContentType, MutableBlobStorageCallback* aCallback) {
322 MOZ_ASSERT(NS_IsMainThread());
323 MOZ_ASSERT(aCallback);
325 MutexAutoLock lock(mMutex);
327 // GetBlob can be called just once.
328 MOZ_ASSERT(mStorageState != eClosed);
329 StorageState previousState = mStorageState;
330 mStorageState = eClosed;
332 if (previousState == eInTemporaryFile) {
333 if (NS_FAILED(mErrorResult)) {
334 MOZ_ASSERT(!mActor);
336 RefPtr<Runnable> runnable =
337 new BlobCreationDoneRunnable(this, aCallback, nullptr, mErrorResult);
338 EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
339 return;
342 MOZ_ASSERT(mActor);
344 // We want to wait until all the WriteRunnable are completed. The way we do
345 // this is to go to the I/O thread and then we come back: the runnables are
346 // executed in order and this LastRunnable will be... the last one.
347 // This Runnable will also close the FD on the I/O thread.
348 RefPtr<Runnable> runnable = new LastRunnable(this, aContentType, aCallback);
350 // If the dispatching fails, we are shutting down and it's fine to do not
351 // run the callback.
352 (void)DispatchToIOThread(runnable.forget());
353 return;
356 // If we are waiting for the temporary file, it's better to wait...
357 if (previousState == eWaitingForTemporaryFile) {
358 mPendingContentType = aContentType;
359 mPendingCallback = aCallback;
360 return;
363 RefPtr<BlobImpl> blobImpl;
365 if (mData) {
366 blobImpl = new MemoryBlobImpl(mData, mDataLen,
367 NS_ConvertUTF8toUTF16(aContentType));
369 mData = nullptr; // The MemoryBlobImpl takes ownership of the buffer
370 mDataLen = 0;
371 mDataBufferLen = 0;
372 } else {
373 blobImpl = new EmptyBlobImpl(NS_ConvertUTF8toUTF16(aContentType));
376 RefPtr<BlobCreationDoneRunnable> runnable =
377 new BlobCreationDoneRunnable(this, aCallback, blobImpl, NS_OK);
379 nsresult error =
380 EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
381 if (NS_WARN_IF(NS_FAILED(error))) {
382 return;
386 nsresult MutableBlobStorage::Append(const void* aData, uint32_t aLength) {
387 // This method can be called on any thread.
389 MutexAutoLock lock(mMutex);
390 MOZ_ASSERT(mStorageState != eClosed);
391 NS_ENSURE_ARG_POINTER(aData);
393 if (!aLength) {
394 return NS_OK;
397 // If eInMemory is the current Storage state, we could maybe migrate to
398 // a temporary file.
399 if (mStorageState == eInMemory && ShouldBeTemporaryStorage(lock, aLength) &&
400 !MaybeCreateTemporaryFile(lock)) {
401 return NS_ERROR_FAILURE;
404 // If we are already in the temporaryFile mode, we have to dispatch a
405 // runnable.
406 if (mStorageState == eInTemporaryFile) {
407 // If a previous operation failed, let's return that error now.
408 if (NS_FAILED(mErrorResult)) {
409 return mErrorResult;
412 RefPtr<WriteRunnable> runnable =
413 WriteRunnable::CopyBuffer(this, aData, aLength);
414 if (NS_WARN_IF(!runnable)) {
415 return NS_ERROR_OUT_OF_MEMORY;
418 nsresult rv = DispatchToIOThread(runnable.forget());
419 if (NS_WARN_IF(NS_FAILED(rv))) {
420 return rv;
423 mDataLen += aLength;
424 return NS_OK;
427 // By default, we store in memory.
429 uint64_t offset = mDataLen;
431 if (!ExpandBufferSize(lock, aLength)) {
432 return NS_ERROR_OUT_OF_MEMORY;
435 memcpy((char*)mData + offset, aData, aLength);
436 return NS_OK;
439 bool MutableBlobStorage::ExpandBufferSize(const MutexAutoLock& aProofOfLock,
440 uint64_t aSize) {
441 MOZ_ASSERT(mStorageState < eInTemporaryFile);
443 if (mDataBufferLen >= mDataLen + aSize) {
444 mDataLen += aSize;
445 return true;
448 // Start at 1 or we'll loop forever.
449 CheckedUint32 bufferLen =
450 std::max<uint32_t>(static_cast<uint32_t>(mDataBufferLen), 1);
451 while (bufferLen.isValid() && bufferLen.value() < mDataLen + aSize) {
452 bufferLen *= 2;
455 if (!bufferLen.isValid()) {
456 return false;
459 void* data = realloc(mData, bufferLen.value());
460 if (!data) {
461 return false;
464 mData = data;
465 mDataBufferLen = bufferLen.value();
466 mDataLen += aSize;
467 return true;
470 bool MutableBlobStorage::ShouldBeTemporaryStorage(
471 const MutexAutoLock& aProofOfLock, uint64_t aSize) const {
472 MOZ_ASSERT(mStorageState == eInMemory);
474 CheckedUint32 bufferSize = mDataLen;
475 bufferSize += aSize;
477 if (!bufferSize.isValid()) {
478 return false;
481 return bufferSize.value() >= mMaxMemory;
484 bool MutableBlobStorage::MaybeCreateTemporaryFile(
485 const MutexAutoLock& aProofOfLock) {
486 mStorageState = eWaitingForTemporaryFile;
488 if (!NS_IsMainThread()) {
489 RefPtr<MutableBlobStorage> self = this;
490 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
491 "MutableBlobStorage::MaybeCreateTemporaryFile", [self]() {
492 MutexAutoLock lock(self->mMutex);
493 self->MaybeCreateTemporaryFileOnMainThread(lock);
494 if (!self->mActor) {
495 self->ErrorPropagated(NS_ERROR_FAILURE);
498 EventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
499 return true;
502 MaybeCreateTemporaryFileOnMainThread(aProofOfLock);
503 return !!mActor;
506 void MutableBlobStorage::MaybeCreateTemporaryFileOnMainThread(
507 const MutexAutoLock& aProofOfLock) {
508 MOZ_ASSERT(NS_IsMainThread());
509 MOZ_ASSERT(!mActor);
511 mozilla::ipc::PBackgroundChild* actorChild =
512 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
513 if (NS_WARN_IF(!actorChild)) {
514 return;
517 mActor = new TemporaryIPCBlobChild(this);
518 actorChild->SendPTemporaryIPCBlobConstructor(mActor);
520 // We need manually to increase the reference for this actor because the
521 // IPC allocator method is not triggered. The Release() is called by IPDL
522 // when the actor is deleted.
523 mActor.get()->AddRef();
525 // The actor will call us when the FileDescriptor is received.
528 void MutableBlobStorage::TemporaryFileCreated(PRFileDesc* aFD) {
529 MOZ_ASSERT(NS_IsMainThread());
531 MutexAutoLock lock(mMutex);
532 MOZ_ASSERT(mStorageState == eWaitingForTemporaryFile ||
533 mStorageState == eClosed);
534 MOZ_ASSERT_IF(mPendingCallback, mStorageState == eClosed);
535 MOZ_ASSERT(mActor);
536 MOZ_ASSERT(aFD);
538 // If the object has been already closed and we don't need to execute a
539 // callback, we need just to close the file descriptor in the correct thread.
540 if (mStorageState == eClosed && !mPendingCallback) {
541 RefPtr<Runnable> runnable = new CloseFileRunnable(aFD);
543 // If this dispatching fails, CloseFileRunnable will close the FD in the
544 // DTOR on the current thread.
545 (void)DispatchToIOThread(runnable.forget());
547 // Let's inform the parent that we have nothing else to do.
548 mActor->SendOperationFailed();
549 mActor = nullptr;
550 return;
553 // If we still receiving data, we can proceed in temporary-file mode.
554 if (mStorageState == eWaitingForTemporaryFile) {
555 mStorageState = eInTemporaryFile;
558 mFD = aFD;
559 MOZ_ASSERT(NS_SUCCEEDED(mErrorResult));
561 // This runnable takes the ownership of mData and it will write this buffer
562 // into the temporary file.
563 RefPtr<WriteRunnable> runnable =
564 WriteRunnable::AdoptBuffer(this, mData, mDataLen);
565 MOZ_ASSERT(runnable);
567 mData = nullptr;
569 nsresult rv = DispatchToIOThread(runnable.forget());
570 if (NS_WARN_IF(NS_FAILED(rv))) {
571 // Shutting down, we cannot continue.
572 return;
575 // If we are closed, it means that GetBlobImplWhenReady() has been called when
576 // we were already waiting for a temporary file-descriptor. Finally we are
577 // here, AdoptBuffer runnable is going to write the current buffer into this
578 // file. After that, there is nothing else to write, and we dispatch
579 // LastRunnable which ends up calling mPendingCallback via CreateBlobRunnable.
580 if (mStorageState == eClosed) {
581 MOZ_ASSERT(mPendingCallback);
583 RefPtr<Runnable> runnable =
584 new LastRunnable(this, mPendingContentType, mPendingCallback);
585 (void)DispatchToIOThread(runnable.forget());
587 mPendingCallback = nullptr;
591 void MutableBlobStorage::AskForBlob(TemporaryIPCBlobChildCallback* aCallback,
592 const nsACString& aContentType) {
593 MOZ_ASSERT(NS_IsMainThread());
595 MutexAutoLock lock(mMutex);
596 MOZ_ASSERT(mStorageState == eClosed);
597 MOZ_ASSERT(mFD);
598 MOZ_ASSERT(mActor);
599 MOZ_ASSERT(aCallback);
601 // Let's pass the FileDescriptor to the parent actor in order to keep the file
602 // locked on windows.
603 mActor->AskForBlob(aCallback, aContentType, mFD);
605 // The previous operation has duplicated the file descriptor. Now we can close
606 // mFD. The parent will take care of closing the duplicated file descriptor on
607 // its side.
608 RefPtr<Runnable> runnable = new CloseFileRunnable(mFD);
609 (void)DispatchToIOThread(runnable.forget());
611 mFD = nullptr;
612 mActor = nullptr;
615 void MutableBlobStorage::ErrorPropagated(nsresult aRv) {
616 MOZ_ASSERT(NS_IsMainThread());
618 MutexAutoLock lock(mMutex);
619 mErrorResult = aRv;
621 if (mActor) {
622 mActor->SendOperationFailed();
623 mActor = nullptr;
627 nsresult MutableBlobStorage::DispatchToIOThread(
628 already_AddRefed<nsIRunnable> aRunnable) {
629 if (!mTaskQueue) {
630 nsCOMPtr<nsIEventTarget> target =
631 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
632 MOZ_ASSERT(target);
634 mTaskQueue = TaskQueue::Create(target.forget(), "BlobStorage");
637 nsCOMPtr<nsIRunnable> runnable(aRunnable);
638 nsresult rv = mTaskQueue->Dispatch(runnable.forget());
639 if (NS_WARN_IF(NS_FAILED(rv))) {
640 return rv;
643 return NS_OK;
646 size_t MutableBlobStorage::SizeOfCurrentMemoryBuffer() {
647 MOZ_ASSERT(NS_IsMainThread());
648 MutexAutoLock lock(mMutex);
649 return mStorageState < eInTemporaryFile ? mDataLen : 0;
652 PRFileDesc* MutableBlobStorage::GetFD() {
653 MOZ_ASSERT(!NS_IsMainThread());
654 MutexAutoLock lock(mMutex);
655 return mFD;
658 void MutableBlobStorage::CloseFD() {
659 MOZ_ASSERT(!NS_IsMainThread());
660 MutexAutoLock lock(mMutex);
661 MOZ_ASSERT(mFD);
663 PR_Close(mFD);
664 mFD = nullptr;
667 } // namespace mozilla::dom