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 "GetFilesHelper.h"
8 #include "mozilla/dom/ContentChild.h"
9 #include "mozilla/dom/ContentParent.h"
10 #include "mozilla/dom/Directory.h"
11 #include "mozilla/dom/FileBlobImpl.h"
12 #include "mozilla/dom/Promise.h"
13 #include "mozilla/dom/UnionTypes.h"
14 #include "mozilla/dom/IPCBlobUtils.h"
15 #include "mozilla/ipc/IPCStreamUtils.h"
16 #include "FileSystemUtils.h"
18 #include "nsProxyRelease.h"
20 namespace mozilla::dom
{
24 // This class is used in the DTOR of GetFilesHelper to release resources in the
26 class ReleaseRunnable final
: public Runnable
{
28 static void MaybeReleaseOnMainThread(
29 nsTArray
<RefPtr
<Promise
>>&& aPromises
,
30 nsTArray
<RefPtr
<GetFilesCallback
>>&& aCallbacks
) {
31 if (NS_IsMainThread()) {
35 RefPtr
<ReleaseRunnable
> runnable
=
36 new ReleaseRunnable(std::move(aPromises
), std::move(aCallbacks
));
37 FileSystemUtils::DispatchRunnable(nullptr, runnable
.forget());
42 MOZ_ASSERT(NS_IsMainThread());
51 ReleaseRunnable(nsTArray
<RefPtr
<Promise
>>&& aPromises
,
52 nsTArray
<RefPtr
<GetFilesCallback
>>&& aCallbacks
)
53 : Runnable("dom::ReleaseRunnable"),
54 mPromises(std::move(aPromises
)),
55 mCallbacks(std::move(aCallbacks
)) {}
57 nsTArray
<RefPtr
<Promise
>> mPromises
;
58 nsTArray
<RefPtr
<GetFilesCallback
>> mCallbacks
;
63 ///////////////////////////////////////////////////////////////////////////////
64 // GetFilesHelper Base class
66 already_AddRefed
<GetFilesHelper
> GetFilesHelper::Create(
67 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectory
,
68 bool aRecursiveFlag
, ErrorResult
& aRv
) {
69 RefPtr
<GetFilesHelper
> helper
;
71 if (XRE_IsParentProcess()) {
72 helper
= new GetFilesHelper(aRecursiveFlag
);
74 helper
= new GetFilesHelperChild(aRecursiveFlag
);
77 nsAutoString directoryPath
;
79 for (uint32_t i
= 0; i
< aFilesOrDirectory
.Length(); ++i
) {
80 const OwningFileOrDirectory
& data
= aFilesOrDirectory
[i
];
82 if (!helper
->mTargetBlobImplArray
.AppendElement(data
.GetAsFile()->Impl(),
84 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
88 MOZ_ASSERT(data
.IsDirectory());
90 // We support the upload of only 1 top-level directory from our
91 // directory picker. This means that we cannot have more than 1
92 // Directory object in aFilesOrDirectory array.
93 MOZ_ASSERT(directoryPath
.IsEmpty());
95 RefPtr
<Directory
> directory
= data
.GetAsDirectory();
96 MOZ_ASSERT(directory
);
98 aRv
= directory
->GetFullRealPath(directoryPath
);
99 if (NS_WARN_IF(aRv
.Failed())) {
105 // No directories to explore.
106 if (directoryPath
.IsEmpty()) {
107 helper
->mListingCompleted
= true;
108 return helper
.forget();
111 MOZ_ASSERT(helper
->mTargetBlobImplArray
.IsEmpty());
112 helper
->SetDirectoryPath(directoryPath
);
115 if (NS_WARN_IF(aRv
.Failed())) {
119 return helper
.forget();
122 GetFilesHelper::GetFilesHelper(bool aRecursiveFlag
)
123 : Runnable("GetFilesHelper"),
124 GetFilesHelperBase(aRecursiveFlag
),
125 mListingCompleted(false),
127 mMutex("GetFilesHelper::mMutex"),
130 GetFilesHelper::~GetFilesHelper() {
131 ReleaseRunnable::MaybeReleaseOnMainThread(std::move(mPromises
),
132 std::move(mCallbacks
));
135 void GetFilesHelper::AddPromise(Promise
* aPromise
) {
136 MOZ_ASSERT(aPromise
);
139 if (!mListingCompleted
) {
140 mPromises
.AppendElement(aPromise
);
144 MOZ_ASSERT(mPromises
.IsEmpty());
145 ResolveOrRejectPromise(aPromise
);
148 void GetFilesHelper::AddCallback(GetFilesCallback
* aCallback
) {
149 MOZ_ASSERT(aCallback
);
152 if (!mListingCompleted
) {
153 mCallbacks
.AppendElement(aCallback
);
157 MOZ_ASSERT(mCallbacks
.IsEmpty());
158 RunCallback(aCallback
);
161 void GetFilesHelper::Unlink() {
166 MutexAutoLock
lock(mMutex
);
173 void GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback
& cb
) {
174 GetFilesHelper
* tmp
= this;
175 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises
);
178 void GetFilesHelper::Work(ErrorResult
& aRv
) {
179 nsCOMPtr
<nsIEventTarget
> target
=
180 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
183 aRv
= target
->Dispatch(this, NS_DISPATCH_NORMAL
);
187 GetFilesHelper::Run() {
188 MOZ_ASSERT(!mDirectoryPath
.IsEmpty());
189 MOZ_ASSERT(!mListingCompleted
);
191 // First step is to retrieve the list of file paths.
192 // This happens in the I/O thread.
193 if (!NS_IsMainThread()) {
196 // If this operation has been canceled, we don't have to go back to
202 RefPtr
<Runnable
> runnable
= this;
203 return FileSystemUtils::DispatchRunnable(nullptr, runnable
.forget());
206 // We are here, but we should not do anything on this thread because, in the
207 // meantime, the operation has been canceled.
212 OperationCompleted();
216 void GetFilesHelper::OperationCompleted() {
217 // We mark the operation as completed here.
218 mListingCompleted
= true;
220 // Let's process the pending promises.
221 nsTArray
<RefPtr
<Promise
>> promises
= std::move(mPromises
);
223 for (uint32_t i
= 0; i
< promises
.Length(); ++i
) {
224 ResolveOrRejectPromise(promises
[i
]);
227 // Let's process the pending callbacks.
228 nsTArray
<RefPtr
<GetFilesCallback
>> callbacks
= std::move(mCallbacks
);
230 for (uint32_t i
= 0; i
< callbacks
.Length(); ++i
) {
231 RunCallback(callbacks
[i
]);
235 void GetFilesHelper::RunIO() {
236 MOZ_ASSERT(!NS_IsMainThread());
237 MOZ_ASSERT(!mDirectoryPath
.IsEmpty());
238 MOZ_ASSERT(!mListingCompleted
);
240 nsCOMPtr
<nsIFile
> file
;
241 mErrorResult
= NS_NewLocalFile(mDirectoryPath
, true, getter_AddRefs(file
));
242 if (NS_WARN_IF(NS_FAILED(mErrorResult
))) {
246 nsAutoString leafName
;
247 mErrorResult
= file
->GetLeafName(leafName
);
248 if (NS_WARN_IF(NS_FAILED(mErrorResult
))) {
252 nsAutoString domPath
;
253 domPath
.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL
);
254 domPath
.Append(leafName
);
256 mErrorResult
= ExploreDirectory(domPath
, file
);
259 nsresult
GetFilesHelperBase::ExploreDirectory(const nsAString
& aDOMPath
,
261 MOZ_ASSERT(!NS_IsMainThread());
264 // We check if this operation has to be terminated at each recursion.
269 nsresult rv
= AddExploredDirectory(aFile
);
270 if (NS_WARN_IF(NS_FAILED(rv
))) {
274 nsCOMPtr
<nsIDirectoryEnumerator
> entries
;
275 rv
= aFile
->GetDirectoryEntries(getter_AddRefs(entries
));
276 if (NS_WARN_IF(NS_FAILED(rv
))) {
281 nsCOMPtr
<nsIFile
> currFile
;
282 if (NS_WARN_IF(NS_FAILED(entries
->GetNextFile(getter_AddRefs(currFile
)))) ||
286 bool isLink
, isSpecial
, isFile
, isDir
;
287 if (NS_WARN_IF(NS_FAILED(currFile
->IsSymlink(&isLink
)) ||
288 NS_FAILED(currFile
->IsSpecial(&isSpecial
))) ||
293 if (NS_WARN_IF(NS_FAILED(currFile
->IsFile(&isFile
)) ||
294 NS_FAILED(currFile
->IsDirectory(&isDir
))) ||
295 !(isFile
|| isDir
)) {
299 // We don't want to explore loops of links.
300 if (isDir
&& isLink
&& !ShouldFollowSymLink(currFile
)) {
305 nsAutoString domPath
;
306 domPath
.Assign(aDOMPath
);
307 if (!aDOMPath
.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL
)) {
308 domPath
.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL
);
311 nsAutoString leafName
;
312 if (NS_WARN_IF(NS_FAILED(currFile
->GetLeafName(leafName
)))) {
315 domPath
.Append(leafName
);
318 RefPtr
<BlobImpl
> blobImpl
= new FileBlobImpl(currFile
);
319 blobImpl
->SetDOMPath(domPath
);
321 if (!mTargetBlobImplArray
.AppendElement(blobImpl
, fallible
)) {
322 return NS_ERROR_OUT_OF_MEMORY
;
329 if (!mRecursiveFlag
) {
334 rv
= ExploreDirectory(domPath
, currFile
);
335 if (NS_WARN_IF(NS_FAILED(rv
))) {
343 nsresult
GetFilesHelperBase::AddExploredDirectory(nsIFile
* aDir
) {
348 rv
= aDir
->IsDirectory(&isDir
);
349 if (NS_WARN_IF(NS_FAILED(rv
))) {
353 MOZ_ASSERT(isDir
, "Why are we here?");
357 rv
= aDir
->IsSymlink(&isLink
);
358 if (NS_WARN_IF(NS_FAILED(rv
))) {
364 rv
= aDir
->GetPath(path
);
365 if (NS_WARN_IF(NS_FAILED(rv
))) {
369 rv
= aDir
->GetTarget(path
);
370 if (NS_WARN_IF(NS_FAILED(rv
))) {
375 mExploredDirectories
.Insert(path
);
379 bool GetFilesHelperBase::ShouldFollowSymLink(nsIFile
* aDir
) {
382 if (NS_WARN_IF(NS_FAILED(aDir
->IsSymlink(&isLink
)) ||
383 NS_FAILED(aDir
->IsDirectory(&isDir
)))) {
387 MOZ_ASSERT(isLink
&& isDir
, "Why are we here?");
390 nsAutoString targetPath
;
391 if (NS_WARN_IF(NS_FAILED(aDir
->GetTarget(targetPath
)))) {
395 return !mExploredDirectories
.Contains(targetPath
);
398 void GetFilesHelper::ResolveOrRejectPromise(Promise
* aPromise
) {
399 MOZ_ASSERT(NS_IsMainThread());
400 MOZ_ASSERT(mListingCompleted
);
401 MOZ_ASSERT(aPromise
);
403 Sequence
<RefPtr
<File
>> files
;
405 if (NS_SUCCEEDED(mErrorResult
)) {
406 for (uint32_t i
= 0; i
< mTargetBlobImplArray
.Length(); ++i
) {
407 RefPtr
<File
> domFile
=
408 File::Create(aPromise
->GetParentObject(), mTargetBlobImplArray
[i
]);
409 if (NS_WARN_IF(!domFile
)) {
410 mErrorResult
= NS_ERROR_FAILURE
;
415 if (!files
.AppendElement(domFile
, fallible
)) {
416 mErrorResult
= NS_ERROR_OUT_OF_MEMORY
;
423 // Error propagation.
424 if (NS_FAILED(mErrorResult
)) {
425 aPromise
->MaybeReject(mErrorResult
);
429 aPromise
->MaybeResolve(files
);
432 void GetFilesHelper::RunCallback(GetFilesCallback
* aCallback
) {
433 MOZ_ASSERT(NS_IsMainThread());
434 MOZ_ASSERT(mListingCompleted
);
435 MOZ_ASSERT(aCallback
);
437 aCallback
->Callback(mErrorResult
, mTargetBlobImplArray
);
440 ///////////////////////////////////////////////////////////////////////////////
441 // GetFilesHelperChild class
443 void GetFilesHelperChild::Work(ErrorResult
& aRv
) {
444 ContentChild
* cc
= ContentChild::GetSingleton();
445 if (NS_WARN_IF(!cc
)) {
446 aRv
.Throw(NS_ERROR_FAILURE
);
450 aRv
= nsID::GenerateUUIDInPlace(mUUID
);
451 if (NS_WARN_IF(aRv
.Failed())) {
455 mPendingOperation
= true;
456 cc
->CreateGetFilesRequest(mDirectoryPath
, mRecursiveFlag
, mUUID
, this);
459 void GetFilesHelperChild::Cancel() {
460 if (!mPendingOperation
) {
464 ContentChild
* cc
= ContentChild::GetSingleton();
465 if (NS_WARN_IF(!cc
)) {
469 mPendingOperation
= false;
470 cc
->DeleteGetFilesRequest(mUUID
, this);
473 bool GetFilesHelperChild::AppendBlobImpl(BlobImpl
* aBlobImpl
) {
474 MOZ_ASSERT(mPendingOperation
);
475 MOZ_ASSERT(aBlobImpl
);
476 MOZ_ASSERT(aBlobImpl
->IsFile());
478 return mTargetBlobImplArray
.AppendElement(aBlobImpl
, fallible
);
481 void GetFilesHelperChild::Finished(nsresult aError
) {
482 MOZ_ASSERT(mPendingOperation
);
483 MOZ_ASSERT(NS_SUCCEEDED(mErrorResult
));
485 mPendingOperation
= false;
486 mErrorResult
= aError
;
488 OperationCompleted();
491 ///////////////////////////////////////////////////////////////////////////////
492 // GetFilesHelperParent class
494 class GetFilesHelperParentCallback final
: public GetFilesCallback
{
496 explicit GetFilesHelperParentCallback(GetFilesHelperParent
* aParent
)
501 void Callback(nsresult aStatus
,
502 const FallibleTArray
<RefPtr
<BlobImpl
>>& aBlobImpls
) override
{
503 if (NS_FAILED(aStatus
)) {
504 mParent
->mContentParent
->SendGetFilesResponseAndForget(
505 mParent
->mUUID
, GetFilesResponseFailure(aStatus
));
509 GetFilesResponseSuccess success
;
511 nsTArray
<IPCBlob
>& ipcBlobs
= success
.blobs();
512 ipcBlobs
.SetLength(aBlobImpls
.Length());
514 for (uint32_t i
= 0; i
< aBlobImpls
.Length(); ++i
) {
515 nsresult rv
= IPCBlobUtils::Serialize(aBlobImpls
[i
], ipcBlobs
[i
]);
516 if (NS_WARN_IF(NS_FAILED(rv
))) {
517 mParent
->mContentParent
->SendGetFilesResponseAndForget(
518 mParent
->mUUID
, GetFilesResponseFailure(NS_ERROR_OUT_OF_MEMORY
));
523 mParent
->mContentParent
->SendGetFilesResponseAndForget(mParent
->mUUID
,
528 // Raw pointer because this callback is kept alive by this parent object.
529 GetFilesHelperParent
* mParent
;
532 GetFilesHelperParent::GetFilesHelperParent(const nsID
& aUUID
,
533 ContentParent
* aContentParent
,
535 : GetFilesHelper(aRecursiveFlag
),
536 mContentParent(aContentParent
),
539 GetFilesHelperParent::~GetFilesHelperParent() {
540 NS_ReleaseOnMainThread("GetFilesHelperParent::mContentParent",
541 mContentParent
.forget());
545 already_AddRefed
<GetFilesHelperParent
> GetFilesHelperParent::Create(
546 const nsID
& aUUID
, const nsAString
& aDirectoryPath
, bool aRecursiveFlag
,
547 ContentParent
* aContentParent
, ErrorResult
& aRv
) {
548 MOZ_ASSERT(aContentParent
);
550 RefPtr
<GetFilesHelperParent
> helper
=
551 new GetFilesHelperParent(aUUID
, aContentParent
, aRecursiveFlag
);
552 helper
->SetDirectoryPath(aDirectoryPath
);
555 if (NS_WARN_IF(aRv
.Failed())) {
559 RefPtr
<GetFilesHelperParentCallback
> callback
=
560 new GetFilesHelperParentCallback(helper
);
561 helper
->AddCallback(callback
);
563 return helper
.forget();
566 } // namespace mozilla::dom