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"
18 #include "nsAnonymousTemporaryFile.h"
20 #include "nsProxyRelease.h"
22 #define BLOB_MEMORY_TEMPORARY_FILE 1048576
24 namespace mozilla::dom
{
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
{
32 BlobCreationDoneRunnable(MutableBlobStorage
* aBlobStorage
,
33 MutableBlobStorageCallback
* aCallback
,
34 BlobImpl
* aBlobImpl
, nsresult aRv
)
35 : Runnable("dom::BlobCreationDoneRunnable"),
36 mBlobStorage(aBlobStorage
),
40 MOZ_ASSERT(aBlobStorage
);
41 MOZ_ASSERT(aCallback
);
42 MOZ_ASSERT((NS_FAILED(aRv
) && !aBlobImpl
) ||
43 (NS_SUCCEEDED(aRv
) && aBlobImpl
));
48 MOZ_ASSERT(NS_IsMainThread());
49 MOZ_ASSERT(mBlobStorage
);
50 mCallback
->BlobStoreCompleted(mBlobStorage
, mBlobImpl
, mRv
);
57 ~BlobCreationDoneRunnable() override
{
58 MOZ_ASSERT(mBlobStorage
);
59 // If something when wrong, we still have to release these objects in the
61 NS_ProxyRelease("BlobCreationDoneRunnable::mCallback",
62 mBlobStorage
->EventTarget(), mCallback
.forget());
65 RefPtr
<MutableBlobStorage
> mBlobStorage
;
66 RefPtr
<MutableBlobStorageCallback
> mCallback
;
67 RefPtr
<BlobImpl
> mBlobImpl
;
71 // Simple runnable to propagate the error to the BlobStorage.
72 class ErrorPropagationRunnable final
: public Runnable
{
74 ErrorPropagationRunnable(MutableBlobStorage
* aBlobStorage
, nsresult aRv
)
75 : Runnable("dom::ErrorPropagationRunnable"),
76 mBlobStorage(aBlobStorage
),
81 mBlobStorage
->ErrorPropagated(mRv
);
86 RefPtr
<MutableBlobStorage
> mBlobStorage
;
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
{
94 static WriteRunnable
* CopyBuffer(MutableBlobStorage
* aBlobStorage
,
95 const void* aData
, uint32_t aLength
) {
96 MOZ_ASSERT(aBlobStorage
);
99 // We have to take a copy of this buffer.
100 void* data
= malloc(aLength
);
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
);
115 return new WriteRunnable(aBlobStorage
, aData
, aLength
);
120 MOZ_ASSERT(!NS_IsMainThread());
121 MOZ_ASSERT(mBlobStorage
);
123 PRFileDesc
* fd
= mBlobStorage
->GetFD();
125 // The file descriptor has been closed in the meantime.
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
),
141 WriteRunnable(MutableBlobStorage
* aBlobStorage
, void* aData
, uint32_t aLength
)
142 : Runnable("dom::WriteRunnable"),
143 mBlobStorage(aBlobStorage
),
146 MOZ_ASSERT(mBlobStorage
);
150 ~WriteRunnable() override
{ free(mData
); }
152 RefPtr
<MutableBlobStorage
> mBlobStorage
;
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
{
161 explicit CloseFileRunnable(PRFileDesc
* aFD
)
162 : Runnable("dom::CloseFileRunnable"), mFD(aFD
) {}
166 MOZ_ASSERT(!NS_IsMainThread());
173 ~CloseFileRunnable() override
{
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
{
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
);
204 MOZ_ASSERT(NS_IsMainThread());
205 MOZ_ASSERT(mBlobStorage
);
206 mBlobStorage
->AskForBlob(this, mContentType
);
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
);
221 ~CreateBlobRunnable() override
{
222 MOZ_ASSERT(mBlobStorage
);
223 // If something when wrong, we still have to release data in the correct
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
{
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
);
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
);
261 ~LastRunnable() override
{
262 MOZ_ASSERT(mBlobStorage
);
263 // If something when wrong, we still have to release data in the correct
265 NS_ProxyRelease("LastRunnable::mCallback", mBlobStorage
->EventTarget(),
269 RefPtr
<MutableBlobStorage
> mBlobStorage
;
270 nsCString mContentType
;
271 RefPtr
<MutableBlobStorageCallback
> mCallback
;
274 } // anonymous namespace
276 MutableBlobStorage::MutableBlobStorage(MutableBlobStorageType aType
,
277 nsIEventTarget
* aEventTarget
,
279 : mMutex("MutableBlobStorage::mMutex"),
283 mStorageState(aType
== eOnlyInMemory
? eKeepInMemory
: eInMemory
),
286 mEventTarget(aEventTarget
),
287 mMaxMemory(aMaxMemory
) {
288 MOZ_ASSERT(NS_IsMainThread());
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() {
306 RefPtr
<Runnable
> runnable
= new CloseFileRunnable(mFD
);
307 (void)DispatchToIOThread(runnable
.forget());
311 mTaskQueue
->BeginShutdown();
315 NS_ProxyRelease("MutableBlobStorage::mActor", EventTarget(),
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
)) {
336 RefPtr
<Runnable
> runnable
=
337 new BlobCreationDoneRunnable(this, aCallback
, nullptr, mErrorResult
);
338 EventTarget()->Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
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
352 (void)DispatchToIOThread(runnable
.forget());
356 // If we are waiting for the temporary file, it's better to wait...
357 if (previousState
== eWaitingForTemporaryFile
) {
358 mPendingContentType
= aContentType
;
359 mPendingCallback
= aCallback
;
363 RefPtr
<BlobImpl
> blobImpl
;
366 blobImpl
= new MemoryBlobImpl(mData
, mDataLen
,
367 NS_ConvertUTF8toUTF16(aContentType
));
369 mData
= nullptr; // The MemoryBlobImpl takes ownership of the buffer
373 blobImpl
= new EmptyBlobImpl(NS_ConvertUTF8toUTF16(aContentType
));
376 RefPtr
<BlobCreationDoneRunnable
> runnable
=
377 new BlobCreationDoneRunnable(this, aCallback
, blobImpl
, NS_OK
);
380 EventTarget()->Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
381 if (NS_WARN_IF(NS_FAILED(error
))) {
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
);
397 // If eInMemory is the current Storage state, we could maybe migrate to
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
406 if (mStorageState
== eInTemporaryFile
) {
407 // If a previous operation failed, let's return that error now.
408 if (NS_FAILED(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
))) {
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
);
439 bool MutableBlobStorage::ExpandBufferSize(const MutexAutoLock
& aProofOfLock
,
441 MOZ_ASSERT(mStorageState
< eInTemporaryFile
);
443 if (mDataBufferLen
>= mDataLen
+ aSize
) {
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
) {
455 if (!bufferLen
.isValid()) {
459 void* data
= realloc(mData
, bufferLen
.value());
465 mDataBufferLen
= bufferLen
.value();
470 bool MutableBlobStorage::ShouldBeTemporaryStorage(
471 const MutexAutoLock
& aProofOfLock
, uint64_t aSize
) const {
472 MOZ_ASSERT(mStorageState
== eInMemory
);
474 CheckedUint32 bufferSize
= mDataLen
;
477 if (!bufferSize
.isValid()) {
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
);
495 self
->ErrorPropagated(NS_ERROR_FAILURE
);
498 EventTarget()->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
502 MaybeCreateTemporaryFileOnMainThread(aProofOfLock
);
506 void MutableBlobStorage::MaybeCreateTemporaryFileOnMainThread(
507 const MutexAutoLock
& aProofOfLock
) {
508 MOZ_ASSERT(NS_IsMainThread());
511 mozilla::ipc::PBackgroundChild
* actorChild
=
512 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
513 if (NS_WARN_IF(!actorChild
)) {
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
);
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();
553 // If we still receiving data, we can proceed in temporary-file mode.
554 if (mStorageState
== eWaitingForTemporaryFile
) {
555 mStorageState
= eInTemporaryFile
;
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
);
569 nsresult rv
= DispatchToIOThread(runnable
.forget());
570 if (NS_WARN_IF(NS_FAILED(rv
))) {
571 // Shutting down, we cannot continue.
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
);
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
608 RefPtr
<Runnable
> runnable
= new CloseFileRunnable(mFD
);
609 (void)DispatchToIOThread(runnable
.forget());
615 void MutableBlobStorage::ErrorPropagated(nsresult aRv
) {
616 MOZ_ASSERT(NS_IsMainThread());
618 MutexAutoLock
lock(mMutex
);
622 mActor
->SendOperationFailed();
627 nsresult
MutableBlobStorage::DispatchToIOThread(
628 already_AddRefed
<nsIRunnable
> aRunnable
) {
630 nsCOMPtr
<nsIEventTarget
> target
=
631 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
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
))) {
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
);
658 void MutableBlobStorage::CloseFD() {
659 MOZ_ASSERT(!NS_IsMainThread());
660 MutexAutoLock
lock(mMutex
);
667 } // namespace mozilla::dom