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"
28 using namespace mozilla::dom::indexedDB
;
29 using namespace mozilla::ipc
;
33 RefPtr
<IDBFileRequest
> GenerateFileRequest(IDBFileHandle
* aFileHandle
) {
34 MOZ_ASSERT(aFileHandle
);
35 aFileHandle
->AssertIsOnOwningThread();
37 return IDBFileRequest::Create(aFileHandle
, /* aWrapAsDOMRequest */ false);
42 IDBFileHandle::IDBFileHandle(IDBMutableFile
* aMutableFile
, FileMode aMode
)
43 : DOMEventTargetHelper(aMutableFile
),
44 mMutableFile(aMutableFile
),
45 mBackgroundActor(nullptr),
47 mPendingRequestCount(0),
54 mSentFinishOrAbort(false),
55 mFiredCompleteOrAbort(false)
58 MOZ_ASSERT(aMutableFile
);
59 aMutableFile
->AssertIsOnOwningThread();
62 IDBFileHandle::~IDBFileHandle() {
63 AssertIsOnOwningThread();
64 MOZ_ASSERT(!mPendingRequestCount
);
65 MOZ_ASSERT(!mCreating
);
66 MOZ_ASSERT(mSentFinishOrAbort
);
67 MOZ_ASSERT_IF(mBackgroundActor
, mFiredCompleteOrAbort
);
69 mMutableFile
->UnregisterFileHandle(this);
71 if (mBackgroundActor
) {
72 mBackgroundActor
->SendDeleteMeInternal();
74 MOZ_ASSERT(!mBackgroundActor
, "SendDeleteMeInternal should have cleared!");
79 RefPtr
<IDBFileHandle
> IDBFileHandle::Create(IDBMutableFile
* aMutableFile
,
81 MOZ_ASSERT(aMutableFile
);
82 aMutableFile
->AssertIsOnOwningThread();
83 MOZ_ASSERT(aMode
== FileMode::Readonly
|| aMode
== FileMode::Readwrite
);
85 RefPtr
<IDBFileHandle
> fileHandle
= new IDBFileHandle(aMutableFile
, aMode
);
88 MOZ_ASSERT(NS_IsMainThread(), "This won't work on non-main threads!");
90 nsCOMPtr
<nsIRunnable
> runnable
= do_QueryObject(fileHandle
);
91 nsContentUtils::AddPendingIDBTransaction(runnable
.forget());
93 fileHandle
->mCreating
= true;
95 aMutableFile
->RegisterFileHandle(fileHandle
);
101 IDBFileHandle
* IDBFileHandle::GetCurrent() {
102 MOZ_ASSERT(BackgroundChild::GetForCurrentThread());
104 BackgroundChildImpl::ThreadLocal
* threadLocal
=
105 BackgroundChildImpl::GetThreadLocalForCurrentThread();
106 MOZ_ASSERT(threadLocal
);
108 return threadLocal
->mCurrentFileHandle
;
113 void IDBFileHandle::AssertIsOnOwningThread() const {
114 MOZ_ASSERT(mMutableFile
);
115 mMutableFile
->AssertIsOnOwningThread();
120 void IDBFileHandle::SetBackgroundActor(BackgroundFileHandleChild
* aActor
) {
121 AssertIsOnOwningThread();
123 MOZ_ASSERT(!mBackgroundActor
);
125 mBackgroundActor
= aActor
;
128 void IDBFileHandle::StartRequest(IDBFileRequest
* aFileRequest
,
129 const FileRequestParams
& aParams
) {
130 AssertIsOnOwningThread();
131 MOZ_ASSERT(aFileRequest
);
132 MOZ_ASSERT(aParams
.type() != FileRequestParams::T__None
);
134 BackgroundFileRequestChild
* actor
=
135 new BackgroundFileRequestChild(aFileRequest
);
137 mBackgroundActor
->SendPBackgroundFileRequestConstructor(actor
, aParams
);
139 // Balanced in BackgroundFileRequestChild::Recv__delete__().
143 void IDBFileHandle::OnNewRequest() {
144 AssertIsOnOwningThread();
146 if (!mPendingRequestCount
) {
147 MOZ_ASSERT(mReadyState
== INITIAL
);
148 mReadyState
= LOADING
;
151 ++mPendingRequestCount
;
154 void IDBFileHandle::OnRequestFinished(bool aActorDestroyedNormally
) {
155 AssertIsOnOwningThread();
156 MOZ_ASSERT(mPendingRequestCount
);
158 --mPendingRequestCount
;
160 if (!mPendingRequestCount
&& !mMutableFile
->IsInvalidated()) {
161 mReadyState
= FINISHING
;
163 if (aActorDestroyedNormally
) {
170 // Don't try to send any more messages to the parent if the request actor
173 MOZ_ASSERT(!mSentFinishOrAbort
);
174 mSentFinishOrAbort
= true;
180 void IDBFileHandle::FireCompleteOrAbortEvents(bool aAborted
) {
181 AssertIsOnOwningThread();
182 MOZ_ASSERT(!mFiredCompleteOrAbort
);
187 mFiredCompleteOrAbort
= true;
190 // TODO: Why is it necessary to create the Event on the heap at all?
191 const auto event
= CreateGenericEvent(
193 aAborted
? nsDependentString(kAbortEventType
)
194 : nsDependentString(kCompleteEventType
),
195 aAborted
? eDoesBubble
: eDoesNotBubble
, eNotCancelable
);
198 IgnoredErrorResult rv
;
199 DispatchEvent(*event
, rv
);
201 NS_WARNING("DispatchEvent failed!");
205 bool IDBFileHandle::IsOpen() const {
206 AssertIsOnOwningThread();
208 // If we haven't started anything then we're open.
209 if (mReadyState
== INITIAL
) {
213 // If we've already started then we need to check to see if we still have the
214 // mCreating flag set. If we do (i.e. we haven't returned to the event loop
215 // from the time we were created) then we are open. Otherwise check the
216 // currently running file handles to see if it's the same. We only allow other
217 // requests to be made if this file handle is currently running.
218 if (mReadyState
== LOADING
&& (mCreating
|| GetCurrent() == this)) {
225 void IDBFileHandle::Abort() {
226 AssertIsOnOwningThread();
228 if (IsFinishingOrDone()) {
229 // Already started (and maybe finished) the finish or abort so there is
230 // nothing to do here.
234 const bool isInvalidated
= mMutableFile
->IsInvalidated();
235 bool needToSendAbort
= mReadyState
== INITIAL
&& !isInvalidated
;
239 mSentFinishOrAbort
= true;
246 // Fire the abort event if there are no outstanding requests. Otherwise the
247 // abort event will be fired when all outstanding requests finish.
248 if (needToSendAbort
) {
253 RefPtr
<IDBFileRequest
> IDBFileHandle::GetMetadata(
254 const IDBFileMetadataParameters
& aParameters
, ErrorResult
& aRv
) {
255 AssertIsOnOwningThread();
257 // Common state checking
258 if (!CheckState(aRv
)) {
262 // Argument checking for get metadata.
263 if (!aParameters
.mSize
&& !aParameters
.mLastModified
) {
264 aRv
.ThrowTypeError("Either size or lastModified should be true.");
268 // Do nothing if the window is closed
269 if (!CheckWindow()) {
273 FileRequestGetMetadataParams params
;
274 params
.size() = aParameters
.mSize
;
275 params
.lastModified() = aParameters
.mLastModified
;
277 auto fileRequest
= GenerateFileRequest(this);
279 StartRequest(fileRequest
, params
);
284 RefPtr
<IDBFileRequest
> IDBFileHandle::Truncate(const Optional
<uint64_t>& aSize
,
286 AssertIsOnOwningThread();
288 // State checking for write
289 if (!CheckStateForWrite(aRv
)) {
293 // Getting location and additional state checking for truncate
295 if (aSize
.WasPassed()) {
296 // Cannot use UINT64_MAX as the truncation size, as this is used as a
297 // special value for the location to mark append mode. This is not really of
298 // practical relevance, as a file cannot actually have a size that large.
300 // XXX: Remove this check when removing the use of UINT64_MAX as a special
301 // value for the location to mark append mode?
302 if (aSize
.Value() == UINT64_MAX
) {
303 aRv
.ThrowTypeError("UINT64_MAX is not a valid size");
306 location
= aSize
.Value();
308 // Fail if we are in append mode.
310 // XXX: Is it really ok that truncate with a size parameter works when in
311 // append mode, but one without a size parameter does not?
312 if (mLocation
== UINT64_MAX
) {
313 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR
);
316 location
= mLocation
;
319 // Do nothing if the window is closed
320 if (!CheckWindow()) {
324 FileRequestTruncateParams params
;
325 params
.offset() = location
;
327 auto fileRequest
= GenerateFileRequest(this);
329 StartRequest(fileRequest
, params
);
331 if (aSize
.WasPassed()) {
332 mLocation
= aSize
.Value();
338 RefPtr
<IDBFileRequest
> IDBFileHandle::Flush(ErrorResult
& aRv
) {
339 AssertIsOnOwningThread();
341 // State checking for write
342 if (!CheckStateForWrite(aRv
)) {
346 // Do nothing if the window is closed
347 if (!CheckWindow()) {
351 FileRequestFlushParams params
;
353 auto fileRequest
= GenerateFileRequest(this);
355 StartRequest(fileRequest
, params
);
360 void IDBFileHandle::Abort(ErrorResult
& aRv
) {
361 AssertIsOnOwningThread();
363 // This method is special enough for not using generic state checking methods.
365 if (IsFinishingOrDone()) {
366 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR
);
373 bool IDBFileHandle::CheckState(ErrorResult
& aRv
) {
375 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR
);
382 bool IDBFileHandle::CheckStateAndArgumentsForRead(uint64_t aSize
,
384 // Common state checking
385 if (!CheckState(aRv
)) {
389 // Additional state checking for read
390 if (mLocation
== UINT64_MAX
) {
391 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR
);
395 // Argument checking for read
397 aRv
.ThrowTypeError("0 (Zero) is not a valid read size.");
401 if (aSize
> UINT32_MAX
) {
402 aRv
.ThrowTypeError("Data size for read is too large.");
409 bool IDBFileHandle::CheckStateForWrite(ErrorResult
& aRv
) {
410 // Common state checking
411 if (!CheckState(aRv
)) {
415 // Additional state checking for write
416 if (mMode
!= FileMode::Readwrite
) {
417 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR
);
424 bool IDBFileHandle::CheckStateForWriteOrAppend(bool aAppend
, ErrorResult
& aRv
) {
425 // State checking for write
426 if (!CheckStateForWrite(aRv
)) {
430 // Additional state checking for write
431 if (!aAppend
&& mLocation
== UINT64_MAX
) {
432 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR
);
439 bool IDBFileHandle::CheckWindow() {
440 AssertIsOnOwningThread();
445 RefPtr
<IDBFileRequest
> IDBFileHandle::Read(uint64_t aSize
, bool aHasEncoding
,
446 const nsAString
& aEncoding
,
448 AssertIsOnOwningThread();
450 // State and argument checking for read
451 if (!CheckStateAndArgumentsForRead(aSize
, aRv
)) {
455 // Do nothing if the window is closed
456 if (!CheckWindow()) {
460 FileRequestReadParams params
;
461 params
.offset() = mLocation
;
462 params
.size() = aSize
;
464 auto fileRequest
= GenerateFileRequest(this);
466 fileRequest
->SetEncoding(aEncoding
);
469 StartRequest(fileRequest
, params
);
476 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(
477 const StringOrArrayBufferOrArrayBufferViewOrBlob
& aValue
, bool aAppend
,
479 AssertIsOnOwningThread();
481 if (aValue
.IsString()) {
482 return WriteOrAppend(aValue
.GetAsString(), aAppend
, aRv
);
485 if (aValue
.IsArrayBuffer()) {
486 return WriteOrAppend(aValue
.GetAsArrayBuffer(), aAppend
, aRv
);
489 if (aValue
.IsArrayBufferView()) {
490 return WriteOrAppend(aValue
.GetAsArrayBufferView(), aAppend
, aRv
);
493 MOZ_ASSERT(aValue
.IsBlob());
494 return WriteOrAppend(aValue
.GetAsBlob(), aAppend
, aRv
);
497 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(const nsAString
& aValue
,
500 AssertIsOnOwningThread();
502 // State checking for write or append
503 if (!CheckStateForWriteOrAppend(aAppend
, aRv
)) {
507 NS_ConvertUTF16toUTF8
cstr(aValue
);
509 uint64_t dataLength
= cstr
.Length();
515 FileRequestStringData
stringData(cstr
);
517 // Do nothing if the window is closed
518 if (!CheckWindow()) {
522 return WriteInternal(stringData
, dataLength
, aAppend
, aRv
);
525 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(const ArrayBuffer
& aValue
,
528 AssertIsOnOwningThread();
530 // State checking for write or append
531 if (!CheckStateForWriteOrAppend(aAppend
, aRv
)) {
535 aValue
.ComputeState();
537 uint64_t dataLength
= aValue
.Length();
543 const char* data
= reinterpret_cast<const char*>(aValue
.Data());
545 FileRequestStringData stringData
;
547 !stringData
.string().Assign(data
, aValue
.Length(), fallible_t()))) {
548 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR
);
552 // Do nothing if the window is closed
553 if (!CheckWindow()) {
557 return WriteInternal(stringData
, dataLength
, aAppend
, aRv
);
560 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(
561 const ArrayBufferView
& aValue
, bool aAppend
, ErrorResult
& aRv
) {
562 AssertIsOnOwningThread();
564 // State checking for write or append
565 if (!CheckStateForWriteOrAppend(aAppend
, aRv
)) {
569 aValue
.ComputeState();
571 uint64_t dataLength
= aValue
.Length();
577 const char* data
= reinterpret_cast<const char*>(aValue
.Data());
579 FileRequestStringData stringData
;
581 !stringData
.string().Assign(data
, aValue
.Length(), fallible_t()))) {
582 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR
);
586 // Do nothing if the window is closed
587 if (!CheckWindow()) {
591 return WriteInternal(stringData
, dataLength
, aAppend
, aRv
);
594 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteOrAppend(Blob
& aValue
, bool aAppend
,
596 AssertIsOnOwningThread();
598 // State checking for write or append
599 if (!CheckStateForWriteOrAppend(aAppend
, aRv
)) {
604 uint64_t dataLength
= aValue
.GetSize(error
);
605 if (NS_WARN_IF(error
.Failed())) {
606 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR
);
614 PBackgroundChild
* backgroundActor
= BackgroundChild::GetForCurrentThread();
615 MOZ_ASSERT(backgroundActor
);
619 IPCBlobUtils::Serialize(aValue
.Impl(), backgroundActor
, ipcBlob
);
620 if (NS_WARN_IF(NS_FAILED(rv
))) {
621 aRv
.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR
);
625 FileRequestBlobData blobData
;
626 blobData
.blob() = ipcBlob
;
628 // Do nothing if the window is closed
629 if (!CheckWindow()) {
633 return WriteInternal(blobData
, dataLength
, aAppend
, aRv
);
636 RefPtr
<IDBFileRequest
> IDBFileHandle::WriteInternal(
637 const FileRequestData
& aData
, uint64_t aDataLength
, bool aAppend
,
639 AssertIsOnOwningThread();
641 DebugOnly
<ErrorResult
> error
;
642 MOZ_ASSERT(CheckStateForWrite(error
));
643 MOZ_ASSERT_IF(!aAppend
, mLocation
!= UINT64_MAX
);
644 MOZ_ASSERT(aDataLength
);
645 MOZ_ASSERT(CheckWindow());
647 FileRequestWriteParams params
;
648 params
.offset() = aAppend
? UINT64_MAX
: mLocation
;
649 params
.data() = aData
;
650 params
.dataLength() = aDataLength
;
652 auto fileRequest
= GenerateFileRequest(this);
653 MOZ_ASSERT(fileRequest
);
655 StartRequest(fileRequest
, params
);
658 mLocation
= UINT64_MAX
;
660 mLocation
+= aDataLength
;
666 void IDBFileHandle::SendFinish() {
667 AssertIsOnOwningThread();
668 MOZ_ASSERT(!mAborted
);
669 MOZ_ASSERT(IsFinishingOrDone());
670 MOZ_ASSERT(!mSentFinishOrAbort
);
671 MOZ_ASSERT(!mPendingRequestCount
);
673 MOZ_ASSERT(mBackgroundActor
);
674 mBackgroundActor
->SendFinish();
677 mSentFinishOrAbort
= true;
681 void IDBFileHandle::SendAbort() {
682 AssertIsOnOwningThread();
683 MOZ_ASSERT(mAborted
);
684 MOZ_ASSERT(IsFinishingOrDone());
685 MOZ_ASSERT(!mSentFinishOrAbort
);
687 MOZ_ASSERT(mBackgroundActor
);
688 mBackgroundActor
->SendAbort();
691 mSentFinishOrAbort
= true;
695 NS_IMPL_ADDREF_INHERITED(IDBFileHandle
, DOMEventTargetHelper
)
696 NS_IMPL_RELEASE_INHERITED(IDBFileHandle
, DOMEventTargetHelper
)
698 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBFileHandle
)
699 NS_INTERFACE_MAP_ENTRY(nsIRunnable
)
700 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
701 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
703 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBFileHandle
)
705 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBFileHandle
,
706 DOMEventTargetHelper
)
707 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMutableFile
)
708 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
710 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBFileHandle
,
711 DOMEventTargetHelper
)
712 // Don't unlink mMutableFile!
713 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
714 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
717 IDBFileHandle::Run() {
718 AssertIsOnOwningThread();
720 // We're back at the event loop, no longer newborn.
723 // Maybe finish if there were no requests generated.
724 if (mReadyState
== INITIAL
) {
733 void IDBFileHandle::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
734 AssertIsOnOwningThread();
736 aVisitor
.mCanHandle
= true;
737 aVisitor
.SetParentTarget(mMutableFile
, false);
741 JSObject
* IDBFileHandle::WrapObject(JSContext
* aCx
,
742 JS::Handle
<JSObject
*> aGivenProto
) {
743 AssertIsOnOwningThread();
745 return IDBFileHandle_Binding::Wrap(aCx
, this, aGivenProto
);
749 } // namespace mozilla