Bug 1803984 - Add tests for the interaction between speculative preloading and module...
[gecko.git] / dom / filesystem / GetFilesHelper.cpp
bloba7ccc45234b2d69a835dd5d9fccaff3a6a13b6af
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"
17 #include "nsNetCID.h"
18 #include "nsProxyRelease.h"
20 namespace mozilla::dom {
22 namespace {
24 // This class is used in the DTOR of GetFilesHelper to release resources in the
25 // correct thread.
26 class ReleaseRunnable final : public Runnable {
27 public:
28 static void MaybeReleaseOnMainThread(
29 nsTArray<RefPtr<Promise>>&& aPromises,
30 nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks) {
31 if (NS_IsMainThread()) {
32 return;
35 RefPtr<ReleaseRunnable> runnable =
36 new ReleaseRunnable(std::move(aPromises), std::move(aCallbacks));
37 FileSystemUtils::DispatchRunnable(nullptr, runnable.forget());
40 NS_IMETHOD
41 Run() override {
42 MOZ_ASSERT(NS_IsMainThread());
44 mPromises.Clear();
45 mCallbacks.Clear();
47 return NS_OK;
50 private:
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;
61 } // namespace
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);
73 } else {
74 helper = new GetFilesHelperChild(aRecursiveFlag);
77 nsAutoString directoryPath;
79 for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) {
80 const OwningFileOrDirectory& data = aFilesOrDirectory[i];
81 if (data.IsFile()) {
82 if (!helper->mTargetBlobImplArray.AppendElement(data.GetAsFile()->Impl(),
83 fallible)) {
84 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
85 return nullptr;
87 } else {
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())) {
100 return nullptr;
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);
114 helper->Work(aRv);
115 if (NS_WARN_IF(aRv.Failed())) {
116 return nullptr;
119 return helper.forget();
122 GetFilesHelper::GetFilesHelper(bool aRecursiveFlag)
123 : Runnable("GetFilesHelper"),
124 GetFilesHelperBase(aRecursiveFlag),
125 mListingCompleted(false),
126 mErrorResult(NS_OK),
127 mMutex("GetFilesHelper::mMutex"),
128 mCanceled(false) {}
130 GetFilesHelper::~GetFilesHelper() {
131 ReleaseRunnable::MaybeReleaseOnMainThread(std::move(mPromises),
132 std::move(mCallbacks));
135 void GetFilesHelper::AddPromise(Promise* aPromise) {
136 MOZ_ASSERT(aPromise);
138 // Still working.
139 if (!mListingCompleted) {
140 mPromises.AppendElement(aPromise);
141 return;
144 MOZ_ASSERT(mPromises.IsEmpty());
145 ResolveOrRejectPromise(aPromise);
148 void GetFilesHelper::AddCallback(GetFilesCallback* aCallback) {
149 MOZ_ASSERT(aCallback);
151 // Still working.
152 if (!mListingCompleted) {
153 mCallbacks.AppendElement(aCallback);
154 return;
157 MOZ_ASSERT(mCallbacks.IsEmpty());
158 RunCallback(aCallback);
161 void GetFilesHelper::Unlink() {
162 mPromises.Clear();
163 mCallbacks.Clear();
166 MutexAutoLock lock(mMutex);
167 mCanceled = true;
170 Cancel();
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);
181 MOZ_ASSERT(target);
183 aRv = target->Dispatch(this, NS_DISPATCH_NORMAL);
186 NS_IMETHODIMP
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()) {
194 RunIO();
196 // If this operation has been canceled, we don't have to go back to
197 // main-thread.
198 if (IsCanceled()) {
199 return NS_OK;
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.
208 if (IsCanceled()) {
209 return NS_OK;
212 OperationCompleted();
213 return NS_OK;
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))) {
243 return;
246 nsAutoString leafName;
247 mErrorResult = file->GetLeafName(leafName);
248 if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
249 return;
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,
260 nsIFile* aFile) {
261 MOZ_ASSERT(!NS_IsMainThread());
262 MOZ_ASSERT(aFile);
264 // We check if this operation has to be terminated at each recursion.
265 if (IsCanceled()) {
266 return NS_OK;
269 nsresult rv = AddExploredDirectory(aFile);
270 if (NS_WARN_IF(NS_FAILED(rv))) {
271 return rv;
274 nsCOMPtr<nsIDirectoryEnumerator> entries;
275 rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
276 if (NS_WARN_IF(NS_FAILED(rv))) {
277 return rv;
280 for (;;) {
281 nsCOMPtr<nsIFile> currFile;
282 if (NS_WARN_IF(NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile)))) ||
283 !currFile) {
284 break;
286 bool isLink, isSpecial, isFile, isDir;
287 if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
288 NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
289 isSpecial) {
290 continue;
293 if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
294 NS_FAILED(currFile->IsDirectory(&isDir))) ||
295 !(isFile || isDir)) {
296 continue;
299 // We don't want to explore loops of links.
300 if (isDir && isLink && !ShouldFollowSymLink(currFile)) {
301 continue;
304 // The new domPath
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)))) {
313 continue;
315 domPath.Append(leafName);
317 if (isFile) {
318 RefPtr<BlobImpl> blobImpl = new FileBlobImpl(currFile);
319 blobImpl->SetDOMPath(domPath);
321 if (!mTargetBlobImplArray.AppendElement(blobImpl, fallible)) {
322 return NS_ERROR_OUT_OF_MEMORY;
325 continue;
328 MOZ_ASSERT(isDir);
329 if (!mRecursiveFlag) {
330 continue;
333 // Recursive.
334 rv = ExploreDirectory(domPath, currFile);
335 if (NS_WARN_IF(NS_FAILED(rv))) {
336 return rv;
340 return NS_OK;
343 nsresult GetFilesHelperBase::AddExploredDirectory(nsIFile* aDir) {
344 nsresult rv;
346 #ifdef DEBUG
347 bool isDir;
348 rv = aDir->IsDirectory(&isDir);
349 if (NS_WARN_IF(NS_FAILED(rv))) {
350 return rv;
353 MOZ_ASSERT(isDir, "Why are we here?");
354 #endif
356 bool isLink;
357 rv = aDir->IsSymlink(&isLink);
358 if (NS_WARN_IF(NS_FAILED(rv))) {
359 return rv;
362 nsAutoString path;
363 if (!isLink) {
364 rv = aDir->GetPath(path);
365 if (NS_WARN_IF(NS_FAILED(rv))) {
366 return rv;
368 } else {
369 rv = aDir->GetTarget(path);
370 if (NS_WARN_IF(NS_FAILED(rv))) {
371 return rv;
375 mExploredDirectories.Insert(path);
376 return NS_OK;
379 bool GetFilesHelperBase::ShouldFollowSymLink(nsIFile* aDir) {
380 #ifdef DEBUG
381 bool isLink, isDir;
382 if (NS_WARN_IF(NS_FAILED(aDir->IsSymlink(&isLink)) ||
383 NS_FAILED(aDir->IsDirectory(&isDir)))) {
384 return false;
387 MOZ_ASSERT(isLink && isDir, "Why are we here?");
388 #endif
390 nsAutoString targetPath;
391 if (NS_WARN_IF(NS_FAILED(aDir->GetTarget(targetPath)))) {
392 return false;
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;
411 files.Clear();
412 break;
415 if (!files.AppendElement(domFile, fallible)) {
416 mErrorResult = NS_ERROR_OUT_OF_MEMORY;
417 files.Clear();
418 break;
423 // Error propagation.
424 if (NS_FAILED(mErrorResult)) {
425 aPromise->MaybeReject(mErrorResult);
426 return;
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);
447 return;
450 aRv = nsID::GenerateUUIDInPlace(mUUID);
451 if (NS_WARN_IF(aRv.Failed())) {
452 return;
455 mPendingOperation = true;
456 cc->CreateGetFilesRequest(mDirectoryPath, mRecursiveFlag, mUUID, this);
459 void GetFilesHelperChild::Cancel() {
460 if (!mPendingOperation) {
461 return;
464 ContentChild* cc = ContentChild::GetSingleton();
465 if (NS_WARN_IF(!cc)) {
466 return;
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 {
495 public:
496 explicit GetFilesHelperParentCallback(GetFilesHelperParent* aParent)
497 : mParent(aParent) {
498 MOZ_ASSERT(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));
506 return;
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));
519 return;
523 mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
524 success);
527 private:
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,
534 bool aRecursiveFlag)
535 : GetFilesHelper(aRecursiveFlag),
536 mContentParent(aContentParent),
537 mUUID(aUUID) {}
539 GetFilesHelperParent::~GetFilesHelperParent() {
540 NS_ReleaseOnMainThread("GetFilesHelperParent::mContentParent",
541 mContentParent.forget());
544 /* static */
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);
554 helper->Work(aRv);
555 if (NS_WARN_IF(aRv.Failed())) {
556 return nullptr;
559 RefPtr<GetFilesHelperParentCallback> callback =
560 new GetFilesHelperParentCallback(helper);
561 helper->AddCallback(callback);
563 return helper.forget();
566 } // namespace mozilla::dom