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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "IDBFileHandle.h"
9 #include "ActorsChild.h"
10 #include "BackgroundChildImpl.h"
11 #include "IDBEvents.h"
12 #include "IDBMutableFile.h"
13 #include "mozilla/Assertions.h"
14 #include "mozilla/dom/File.h"
15 #include "mozilla/dom/IDBFileHandleBinding.h"
16 #include "mozilla/dom/IPCBlobUtils.h"
17 #include "mozilla/dom/PBackgroundFileHandle.h"
18 #include "mozilla/EventDispatcher.h"
19 #include "mozilla/ipc/BackgroundChild.h"
20 #include "nsContentUtils.h"
21 #include "nsQueryObject.h"
22 #include "nsServiceManagerUtils.h"
23 #include "nsWidgetsCID.h"
25 namespace mozilla::dom
{
27 using namespace mozilla::dom::indexedDB
;
28 using namespace mozilla::ipc
;
32 RefPtr
<IDBFileRequest
> GenerateFileRequest(IDBFileHandle
* aFileHandle
) {
33 MOZ_ASSERT(aFileHandle
);
34 aFileHandle
->AssertIsOnOwningThread();
36 return IDBFileRequest::Create(aFileHandle
, /* aWrapAsDOMRequest */ false);
41 IDBFileHandle::IDBFileHandle(IDBMutableFile
* aMutableFile
, FileMode aMode
)
42 : DOMEventTargetHelper(aMutableFile
),
43 mMutableFile(aMutableFile
),
44 mBackgroundActor(nullptr),
46 mPendingRequestCount(0),
53 mSentFinishOrAbort(false),
54 mFiredCompleteOrAbort(false)
57 MOZ_ASSERT(aMutableFile
);
58 aMutableFile
->AssertIsOnOwningThread();
61 IDBFileHandle::~IDBFileHandle() {
62 AssertIsOnOwningThread();
63 MOZ_ASSERT(!mPendingRequestCount
);
64 MOZ_ASSERT(!mCreating
);
65 MOZ_ASSERT(mSentFinishOrAbort
);
66 MOZ_ASSERT_IF(mBackgroundActor
, mFiredCompleteOrAbort
);
68 mMutableFile
->UnregisterFileHandle(this);
70 if (mBackgroundActor
) {
71 mBackgroundActor
->SendDeleteMeInternal();
73 MOZ_ASSERT(!mBackgroundActor
, "SendDeleteMeInternal should have cleared!");
78 RefPtr
<IDBFileHandle
> IDBFileHandle::Create(IDBMutableFile
* aMutableFile
,
80 MOZ_ASSERT(aMutableFile
);
81 aMutableFile
->AssertIsOnOwningThread();
82 MOZ_ASSERT(aMode
== FileMode::Readonly
|| aMode
== FileMode::Readwrite
);
84 RefPtr
<IDBFileHandle
> fileHandle
= new IDBFileHandle(aMutableFile
, aMode
);
87 MOZ_ASSERT(NS_IsMainThread(), "This won't work on non-main threads!");
89 nsCOMPtr
<nsIRunnable
> runnable
= do_QueryObject(fileHandle
);
90 nsContentUtils::AddPendingIDBTransaction(runnable
.forget());
92 fileHandle
->mCreating
= true;
94 aMutableFile
->RegisterFileHandle(fileHandle
);
100 IDBFileHandle
* IDBFileHandle::GetCurrent() {
101 MOZ_ASSERT(BackgroundChild::GetForCurrentThread());
103 BackgroundChildImpl::ThreadLocal
* threadLocal
=
104 BackgroundChildImpl::GetThreadLocalForCurrentThread();
105 MOZ_ASSERT(threadLocal
);
107 return threadLocal
->mCurrentFileHandle
;
112 void IDBFileHandle::AssertIsOnOwningThread() const {
113 MOZ_ASSERT(mMutableFile
);
114 mMutableFile
->AssertIsOnOwningThread();
119 void IDBFileHandle::SetBackgroundActor(BackgroundFileHandleChild
* aActor
) {
120 AssertIsOnOwningThread();
122 MOZ_ASSERT(!mBackgroundActor
);
124 mBackgroundActor
= aActor
;
127 void IDBFileHandle::StartRequest(IDBFileRequest
* aFileRequest
,
128 const FileRequestParams
& aParams
) {
129 AssertIsOnOwningThread();
130 MOZ_ASSERT(aFileRequest
);
131 MOZ_ASSERT(aParams
.type() != FileRequestParams::T__None
);
133 BackgroundFileRequestChild
* actor
=
134 new BackgroundFileRequestChild(aFileRequest
);
136 mBackgroundActor
->SendPBackgroundFileRequestConstructor(actor
, aParams
);
138 // Balanced in BackgroundFileRequestChild::Recv__delete__().
142 void IDBFileHandle::OnNewRequest() {
143 AssertIsOnOwningThread();
145 if (!mPendingRequestCount
) {
146 MOZ_ASSERT(mReadyState
== INITIAL
);
147 mReadyState
= LOADING
;
150 ++mPendingRequestCount
;
153 void IDBFileHandle::OnRequestFinished(bool aActorDestroyedNormally
) {
154 AssertIsOnOwningThread();
155 MOZ_ASSERT(mPendingRequestCount
);
157 --mPendingRequestCount
;
159 if (!mPendingRequestCount
&& !mMutableFile
->IsInvalidated()) {
160 mReadyState
= FINISHING
;
162 if (aActorDestroyedNormally
) {
169 // Don't try to send any more messages to the parent if the request actor
172 MOZ_ASSERT(!mSentFinishOrAbort
);
173 mSentFinishOrAbort
= true;
179 void IDBFileHandle::FireCompleteOrAbortEvents(bool aAborted
) {
180 AssertIsOnOwningThread();
181 MOZ_ASSERT(!mFiredCompleteOrAbort
);
186 mFiredCompleteOrAbort
= true;
189 // TODO: Why is it necessary to create the Event on the heap at all?
190 const auto event
= CreateGenericEvent(
192 aAborted
? nsDependentString(kAbortEventType
)
193 : nsDependentString(kCompleteEventType
),
194 aAborted
? eDoesBubble
: eDoesNotBubble
, eNotCancelable
);
197 IgnoredErrorResult rv
;
198 DispatchEvent(*event
, rv
);
200 NS_WARNING("DispatchEvent failed!");
204 bool IDBFileHandle::IsOpen() const {
205 AssertIsOnOwningThread();
207 // If we haven't started anything then we're open.
208 if (mReadyState
== INITIAL
) {
212 // If we've already started then we need to check to see if we still have the
213 // mCreating flag set. If we do (i.e. we haven't returned to the event loop
214 // from the time we were created) then we are open. Otherwise check the
215 // currently running file handles to see if it's the same. We only allow other
216 // requests to be made if this file handle is currently running.
217 if (mReadyState
== LOADING
&& (mCreating
|| GetCurrent() == this)) {
224 void IDBFileHandle::Abort() {
225 AssertIsOnOwningThread();
227 if (IsFinishingOrDone()) {
228 // Already started (and maybe finished) the finish or abort so there is
229 // nothing to do here.
233 const bool isInvalidated
= mMutableFile
->IsInvalidated();
234 bool needToSendAbort
= mReadyState
== INITIAL
&& !isInvalidated
;
238 mSentFinishOrAbort
= true;
245 // Fire the abort event if there are no outstanding requests. Otherwise the
246 // abort event will be fired when all outstanding requests finish.
247 if (needToSendAbort
) {
252 RefPtr
<IDBFileRequest
> IDBFileHandle::GetMetadata(
253 const IDBFileMetadataParameters
& aParameters
, ErrorResult
& aRv
) {
254 AssertIsOnOwningThread();
256 // Common state checking
257 if (!CheckState(aRv
)) {
261 // Argument checking for get metadata.
262 if (!aParameters
.mSize
&& !aParameters
.mLastModified
) {
263 aRv
.ThrowTypeError("Either size or lastModified should be true.");
267 // Do nothing if the window is closed
268 if (!CheckWindow()) {
272 FileRequestGetMetadataParams params
;
273 params
.size() = aParameters
.mSize
;
274 params
.lastModified() = aParameters
.mLastModified
;
276 auto fileRequest
= GenerateFileRequest(this);
278 StartRequest(fileRequest
, params
);
283 RefPtr
<IDBFileRequest
> IDBFileHandle::Truncate(const Optional
<uint64_t>& aSize
,
285 AssertIsOnOwningThread();
287 // State checking for write
288 if (!CheckStateForWrite(aRv
)) {
292 // Getting location and additional state checking for truncate
294 if (aSize
.WasPassed()) {
295 // Cannot use UINT64_MAX as the truncation size, as this is used as a
296 // special value for the location to mark append mode. This is not really of
297 // practical relevance, as a file cannot actually have a size that large.
299 // XXX: Remove this check when removing the use of UINT64_MAX as a special
300 // value for the location to mark append mode?
301 if (aSize
.Value() == UINT64_MAX
) {
302 aRv
.ThrowTypeError("UINT64_MAX is not a valid size");
305 location
= aSize
.Value();
307 // Fail if we are in append mode.
309 // XXX: Is it really ok that truncate with a size parameter works when in
310 // append mode, but one without a size parameter does not?
311 if (mLocation
== UINT64_MAX
) {
312 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR
);
315 location
= mLocation
;
318 // Do nothing if the window is closed
319 if (!CheckWindow()) {
323 FileRequestTruncateParams params
;
324 params
.offset() = location
;
326 auto fileRequest
= GenerateFileRequest(this);
328 StartRequest(fileRequest
, params
);
330 if (aSize
.WasPassed()) {
331 mLocation
= aSize
.Value();
337 RefPtr
<IDBFileRequest
> IDBFileHandle::Flush(ErrorResult
& aRv
) {
338 AssertIsOnOwningThread();
340 // State checking for write
341 if (!CheckStateForWrite(aRv
)) {
345 // Do nothing if the window is closed
346 if (!CheckWindow()) {
350 FileRequestFlushParams params
;
352 auto fileRequest
= GenerateFileRequest(this);
354 StartRequest(fileRequest
, params
);
359 void IDBFileHandle::Abort(ErrorResult
& aRv
) {
360 AssertIsOnOwningThread();
362 // This method is special enough for not using generic state checking methods.
364 if (IsFinishingOrDone()) {
365 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR
);
372 bool IDBFileHandle::CheckState(ErrorResult
& aRv
) {
374 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR
);
381 bool IDBFileHandle::CheckStateAndArgumentsForRead(uint64_t aSize
,
383 // Common state checking
384 if (!CheckState(aRv
)) {
388 // Additional state checking for read
389 if (mLocation
== UINT64_MAX
) {
390 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR
);
394 // Argument checking for read
396 aRv
.ThrowTypeError("0 (Zero) is not a valid read size.");
400 if (aSize
> UINT32_MAX
) {
401 aRv
.ThrowTypeError("Data size for read is too large.");
408 bool IDBFileHandle::CheckStateForWrite(ErrorResult
& aRv
) {
409 // Common state checking
410 if (!CheckState(aRv
)) {
414 // Additional state checking for write
415 if (mMode
!= FileMode::Readwrite
) {
416 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR
);
423 bool IDBFileHandle::CheckStateForWriteOrAppend(bool aAppend
, ErrorResult
& aRv
) {
424 // State checking for write
425 if (!CheckStateForWrite(aRv
)) {
429 // Additional state checking for write
430 if (!aAppend
&& mLocation
== UINT64_MAX
) {
431 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR
);
438 bool IDBFileHandle::CheckWindow() {
439 AssertIsOnOwningThread();
444 RefPtr
<IDBFileRequest
> IDBFileHandle::Read(uint64_t aSize
, bool aHasEncoding
,
445 const nsAString
& aEncoding
,
447 AssertIsOnOwningThread();
449 // State and argument checking for read
450 if (!CheckStateAndArgumentsForRead(aSize
, aRv
)) {
454 // Do nothing if the window is closed
455 if (!CheckWindow()) {
459 FileRequestReadParams params
;
460 params
.offset() = mLocation
;
461 params
.size() = aSize
;
463 auto fileRequest
= GenerateFileRequest(this);
465 fileRequest
->SetEncoding(aEncoding
);
468 StartRequest(fileRequest
, params
);
475 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(
476 const StringOrArrayBufferOrArrayBufferViewOrBlob
& aValue
, bool aAppend
,
478 AssertIsOnOwningThread();
480 if (aValue
.IsString()) {
481 return WriteOrAppend(aValue
.GetAsString(), aAppend
, aRv
);
484 if (aValue
.IsArrayBuffer()) {
485 return WriteOrAppend(aValue
.GetAsArrayBuffer(), aAppend
, aRv
);
488 if (aValue
.IsArrayBufferView()) {
489 return WriteOrAppend(aValue
.GetAsArrayBufferView(), aAppend
, aRv
);
492 MOZ_ASSERT(aValue
.IsBlob());
493 return WriteOrAppend(aValue
.GetAsBlob(), aAppend
, aRv
);
496 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(const nsAString
& aValue
,
499 AssertIsOnOwningThread();
501 // State checking for write or append
502 if (!CheckStateForWriteOrAppend(aAppend
, aRv
)) {
506 NS_ConvertUTF16toUTF8
cstr(aValue
);
508 uint64_t dataLength
= cstr
.Length();
514 FileRequestStringData
stringData(cstr
);
516 // Do nothing if the window is closed
517 if (!CheckWindow()) {
521 return WriteInternal(stringData
, dataLength
, aAppend
, aRv
);
524 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(const ArrayBuffer
& aValue
,
527 AssertIsOnOwningThread();
529 // State checking for write or append
530 if (!CheckStateForWriteOrAppend(aAppend
, aRv
)) {
534 aValue
.ComputeState();
536 uint64_t dataLength
= aValue
.Length();
542 const char* data
= reinterpret_cast<const char*>(aValue
.Data());
544 FileRequestStringData stringData
;
546 !stringData
.string().Assign(data
, aValue
.Length(), fallible_t()))) {
547 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR
);
551 // Do nothing if the window is closed
552 if (!CheckWindow()) {
556 return WriteInternal(stringData
, dataLength
, aAppend
, aRv
);
559 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(
560 const ArrayBufferView
& aValue
, bool aAppend
, ErrorResult
& aRv
) {
561 AssertIsOnOwningThread();
563 // State checking for write or append
564 if (!CheckStateForWriteOrAppend(aAppend
, aRv
)) {
568 aValue
.ComputeState();
570 uint64_t dataLength
= aValue
.Length();
576 const char* data
= reinterpret_cast<const char*>(aValue
.Data());
578 FileRequestStringData stringData
;
580 !stringData
.string().Assign(data
, aValue
.Length(), fallible_t()))) {
581 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR
);
585 // Do nothing if the window is closed
586 if (!CheckWindow()) {
590 return WriteInternal(stringData
, dataLength
, aAppend
, aRv
);
593 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(Blob
& aValue
, bool aAppend
,
595 AssertIsOnOwningThread();
597 // State checking for write or append
598 if (!CheckStateForWriteOrAppend(aAppend
, aRv
)) {
603 uint64_t dataLength
= aValue
.GetSize(error
);
604 if (NS_WARN_IF(error
.Failed())) {
605 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR
);
613 PBackgroundChild
* backgroundActor
= BackgroundChild::GetForCurrentThread();
614 MOZ_ASSERT(backgroundActor
);
618 IPCBlobUtils::Serialize(aValue
.Impl(), backgroundActor
, ipcBlob
);
619 if (NS_WARN_IF(NS_FAILED(rv
))) {
620 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR
);
624 FileRequestBlobData blobData
;
625 blobData
.blob() = ipcBlob
;
627 // Do nothing if the window is closed
628 if (!CheckWindow()) {
632 return WriteInternal(blobData
, dataLength
, aAppend
, aRv
);
635 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteInternal(
636 const FileRequestData
& aData
, uint64_t aDataLength
, bool aAppend
,
638 AssertIsOnOwningThread();
640 DebugOnly
<ErrorResult
> error
;
641 MOZ_ASSERT(CheckStateForWrite(error
));
642 MOZ_ASSERT_IF(!aAppend
, mLocation
!= UINT64_MAX
);
643 MOZ_ASSERT(aDataLength
);
644 MOZ_ASSERT(CheckWindow());
646 FileRequestWriteParams params
;
647 params
.offset() = aAppend
? UINT64_MAX
: mLocation
;
648 params
.data() = aData
;
649 params
.dataLength() = aDataLength
;
651 auto fileRequest
= GenerateFileRequest(this);
652 MOZ_ASSERT(fileRequest
);
654 StartRequest(fileRequest
, params
);
657 mLocation
= UINT64_MAX
;
659 mLocation
+= aDataLength
;
665 void IDBFileHandle::SendFinish() {
666 AssertIsOnOwningThread();
667 MOZ_ASSERT(!mAborted
);
668 MOZ_ASSERT(IsFinishingOrDone());
669 MOZ_ASSERT(!mSentFinishOrAbort
);
670 MOZ_ASSERT(!mPendingRequestCount
);
672 MOZ_ASSERT(mBackgroundActor
);
673 mBackgroundActor
->SendFinish();
676 mSentFinishOrAbort
= true;
680 void IDBFileHandle::SendAbort() {
681 AssertIsOnOwningThread();
682 MOZ_ASSERT(mAborted
);
683 MOZ_ASSERT(IsFinishingOrDone());
684 MOZ_ASSERT(!mSentFinishOrAbort
);
686 MOZ_ASSERT(mBackgroundActor
);
687 mBackgroundActor
->SendAbort();
690 mSentFinishOrAbort
= true;
694 NS_IMPL_ADDREF_INHERITED(IDBFileHandle
, DOMEventTargetHelper
)
695 NS_IMPL_RELEASE_INHERITED(IDBFileHandle
, DOMEventTargetHelper
)
697 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBFileHandle
)
698 NS_INTERFACE_MAP_ENTRY(nsIRunnable
)
699 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
700 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
702 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBFileHandle
)
704 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBFileHandle
,
705 DOMEventTargetHelper
)
706 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMutableFile
)
707 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
709 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBFileHandle
,
710 DOMEventTargetHelper
)
711 // Don't unlink mMutableFile!
712 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
713 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
716 IDBFileHandle::Run() {
717 AssertIsOnOwningThread();
719 // We're back at the event loop, no longer newborn.
722 // Maybe finish if there were no requests generated.
723 if (mReadyState
== INITIAL
) {
732 void IDBFileHandle::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
733 AssertIsOnOwningThread();
735 aVisitor
.mCanHandle
= true;
736 aVisitor
.SetParentTarget(mMutableFile
, false);
740 JSObject
* IDBFileHandle::WrapObject(JSContext
* aCx
,
741 JS::Handle
<JSObject
*> aGivenProto
) {
742 AssertIsOnOwningThread();
744 return IDBFileHandle_Binding::Wrap(aCx
, this, aGivenProto
);
747 } // namespace mozilla::dom