Bug 1948102: Refactor GetFilesHelper MozPromise behavior r=baku

Adds PromiseAdapter to abstract dom::Promise and MozPromise as one interface.
Also adds a test for the MozPromise behavior.

Differential Revision: https://phabricator.services.mozilla.com/D238819
This commit is contained in:
David P
2025-02-20 18:41:27 +00:00
parent 8dd5945859
commit fe07174949
3 changed files with 155 additions and 99 deletions

View File

@@ -24,43 +24,84 @@ namespace mozilla::dom {
class GetFilesHelper::ReleaseRunnable final : public Runnable { class GetFilesHelper::ReleaseRunnable final : public Runnable {
public: public:
static void MaybeReleaseOnMainThread( static void MaybeReleaseOnMainThread(
nsTArray<RefPtr<Promise>>&& aPromises, nsTArray<PromiseAdapter>&& aPromises,
nsTArray<GetFilesHelper::MozPromiseAndGlobal>&& aMozPromises,
nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks) { nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks) {
if (NS_IsMainThread()) { if (NS_IsMainThread()) {
return; return;
} }
RefPtr<ReleaseRunnable> runnable = new ReleaseRunnable( RefPtr<ReleaseRunnable> runnable =
std::move(aPromises), std::move(aMozPromises), std::move(aCallbacks)); new ReleaseRunnable(std::move(aPromises), std::move(aCallbacks));
FileSystemUtils::DispatchRunnable(nullptr, runnable.forget()); FileSystemUtils::DispatchRunnable(nullptr, runnable.forget());
} }
NS_IMETHOD NS_IMETHOD
Run() override { Run() override {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
mPromises.Clear(); mPromises.Clear();
mMozPromises.Clear();
mCallbacks.Clear(); mCallbacks.Clear();
return NS_OK; return NS_OK;
} }
private: private:
ReleaseRunnable(nsTArray<RefPtr<Promise>>&& aPromises, ReleaseRunnable(nsTArray<PromiseAdapter>&& aPromises,
nsTArray<GetFilesHelper::MozPromiseAndGlobal>&& aMozPromises,
nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks) nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks)
: Runnable("dom::ReleaseRunnable"), : Runnable("dom::ReleaseRunnable"),
mPromises(std::move(aPromises)), mPromises(std::move(aPromises)),
mMozPromises{std::move(aMozPromises)},
mCallbacks(std::move(aCallbacks)) {} mCallbacks(std::move(aCallbacks)) {}
nsTArray<RefPtr<Promise>> mPromises; nsTArray<PromiseAdapter> mPromises;
nsTArray<GetFilesHelper::MozPromiseAndGlobal> mMozPromises;
nsTArray<RefPtr<GetFilesCallback>> mCallbacks; nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
}; };
///////////////////////////////////////////////////////////////////////////////
// PromiseAdapter
GetFilesHelper::PromiseAdapter::PromiseAdapter(
MozPromiseAndGlobal&& aMozPromise)
: mPromise(std::move(aMozPromise)) {}
GetFilesHelper::PromiseAdapter::PromiseAdapter(Promise* aDomPromise)
: mPromise(RefPtr{aDomPromise}) {}
GetFilesHelper::PromiseAdapter::~PromiseAdapter() { Clear(); }
void GetFilesHelper::PromiseAdapter::Clear() {
mPromise = AsVariant(RefPtr<Promise>(nullptr));
}
nsIGlobalObject* GetFilesHelper::PromiseAdapter::GetGlobalObject() {
return mPromise.match(
[](RefPtr<Promise>& aDomPromise) {
return aDomPromise ? aDomPromise->GetGlobalObject() : nullptr;
},
[](MozPromiseAndGlobal& aMozPromiseAndGlobal) {
return aMozPromiseAndGlobal.mGlobal.get();
});
}
void GetFilesHelper::PromiseAdapter::Resolve(nsTArray<RefPtr<File>>&& aFiles) {
mPromise.match(
[&aFiles](RefPtr<Promise>& aDomPromise) {
if (aDomPromise) {
aDomPromise->MaybeResolve(Sequence(std::move(aFiles)));
}
},
[&aFiles](MozPromiseAndGlobal& aMozPromiseAndGlobal) {
aMozPromiseAndGlobal.mMozPromise->Resolve(std::move(aFiles), __func__);
});
}
void GetFilesHelper::PromiseAdapter::Reject(nsresult aError) {
mPromise.match(
[&aError](RefPtr<Promise>& aDomPromise) {
if (aDomPromise) {
aDomPromise->MaybeReject(aError);
}
},
[&aError](MozPromiseAndGlobal& aMozPromiseAndGlobal) {
aMozPromiseAndGlobal.mMozPromise->Reject(aError, __func__);
});
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// GetFilesHelper Base class // GetFilesHelper Base class
@@ -127,36 +168,31 @@ GetFilesHelper::GetFilesHelper(bool aRecursiveFlag)
mCanceled(false) {} mCanceled(false) {}
GetFilesHelper::~GetFilesHelper() { GetFilesHelper::~GetFilesHelper() {
ReleaseRunnable::MaybeReleaseOnMainThread( ReleaseRunnable::MaybeReleaseOnMainThread(std::move(mPromises),
std::move(mPromises), std::move(mMozPromises), std::move(mCallbacks)); std::move(mCallbacks));
} }
void GetFilesHelper::AddPromise(Promise* aPromise) { void GetFilesHelper::AddPromiseInternal(PromiseAdapter&& aPromise) {
MOZ_ASSERT(aPromise);
// Still working. // Still working.
if (!mListingCompleted) { if (!mListingCompleted) {
mPromises.AppendElement(aPromise); mPromises.AppendElement(std::move(aPromise));
return; return;
} }
MOZ_ASSERT(mPromises.IsEmpty()); MOZ_ASSERT(mPromises.IsEmpty());
ResolveOrRejectPromise(aPromise); ResolveOrRejectPromise(std::move(aPromise));
}
void GetFilesHelper::AddPromise(Promise* aPromise) {
MOZ_ASSERT(aPromise);
AddPromiseInternal(PromiseAdapter(aPromise));
} }
void GetFilesHelper::AddMozPromise(MozPromiseType* aPromise, void GetFilesHelper::AddMozPromise(MozPromiseType* aPromise,
nsIGlobalObject* aGlobal) { nsIGlobalObject* aGlobal) {
MOZ_ASSERT(aPromise); MOZ_ASSERT(aPromise);
MOZ_ASSERT(aGlobal); MOZ_ASSERT(aGlobal);
AddPromiseInternal(PromiseAdapter(MozPromiseAndGlobal{aPromise, aGlobal}));
// Still working.
if (!mListingCompleted) {
mMozPromises.AppendElement(MozPromiseAndGlobal{aPromise, aGlobal});
return;
}
MOZ_ASSERT(mMozPromises.IsEmpty());
ResolveOrRejectMozPromise({aPromise, aGlobal});
} }
void GetFilesHelper::AddCallback(GetFilesCallback* aCallback) { void GetFilesHelper::AddCallback(GetFilesCallback* aCallback) {
@@ -174,7 +210,6 @@ void GetFilesHelper::AddCallback(GetFilesCallback* aCallback) {
void GetFilesHelper::Unlink() { void GetFilesHelper::Unlink() {
mPromises.Clear(); mPromises.Clear();
mMozPromises.Clear();
mCallbacks.Clear(); mCallbacks.Clear();
{ {
@@ -185,9 +220,26 @@ void GetFilesHelper::Unlink() {
Cancel(); Cancel();
} }
void GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback& cb) { void GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback& aCb) {
GetFilesHelper* tmp = this; for (auto&& promiseAdapter : mPromises) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises); promiseAdapter.Traverse(aCb);
}
}
void GetFilesHelper::PromiseAdapter::Traverse(
nsCycleCollectionTraversalCallback& aCb) {
mPromise.match(
[&aCb](RefPtr<Promise>& aDomPromise) {
if (aDomPromise) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mDomPromise");
aCb.NoteNativeChild(aDomPromise,
NS_CYCLE_COLLECTION_PARTICIPANT(Promise));
}
},
[&aCb](MozPromiseAndGlobal& aMozPromiseAndGlobal) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mGlobal");
aCb.NoteXPCOMChild(aMozPromiseAndGlobal.mGlobal);
});
} }
void GetFilesHelper::Work(ErrorResult& aRv) { void GetFilesHelper::Work(ErrorResult& aRv) {
@@ -235,12 +287,7 @@ void GetFilesHelper::OperationCompleted() {
// Let's process the pending promises. // Let's process the pending promises.
auto promises = std::move(mPromises); auto promises = std::move(mPromises);
for (uint32_t i = 0; i < promises.Length(); ++i) { for (uint32_t i = 0; i < promises.Length(); ++i) {
ResolveOrRejectPromise(promises[i]); ResolveOrRejectPromise(std::move(promises[i]));
}
auto mozPromises = std::move(mMozPromises);
for (uint32_t i = 0; i < mozPromises.Length(); ++i) {
ResolveOrRejectMozPromise(mozPromises[i]);
} }
// Let's process the pending callbacks. // Let's process the pending callbacks.
@@ -359,53 +406,16 @@ nsresult GetFilesHelperBase::ExploreDirectory(const nsAString& aDOMPath,
return NS_OK; return NS_OK;
} }
void GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise) { void GetFilesHelper::ResolveOrRejectPromise(PromiseAdapter&& aPromise) {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mListingCompleted); MOZ_ASSERT(mListingCompleted);
MOZ_ASSERT(aPromise);
Sequence<RefPtr<File>> files;
if (NS_SUCCEEDED(mErrorResult)) {
for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) {
RefPtr<File> domFile =
File::Create(aPromise->GetParentObject(), mTargetBlobImplArray[i]);
if (NS_WARN_IF(!domFile)) {
mErrorResult = NS_ERROR_FAILURE;
files.Clear();
break;
}
if (!files.AppendElement(domFile, fallible)) {
mErrorResult = NS_ERROR_OUT_OF_MEMORY;
files.Clear();
break;
}
}
}
// Error propagation.
if (NS_FAILED(mErrorResult)) {
aPromise->MaybeReject(mErrorResult);
return;
}
aPromise->MaybeResolve(files);
}
void GetFilesHelper::ResolveOrRejectMozPromise(
GetFilesHelper::MozPromiseAndGlobal aPromise) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mListingCompleted);
MOZ_ASSERT(aPromise.mMozPromise);
MOZ_ASSERT(aPromise.mGlobal);
nsTArray<RefPtr<File>> files; nsTArray<RefPtr<File>> files;
if (NS_SUCCEEDED(mErrorResult)) { if (NS_SUCCEEDED(mErrorResult)) {
for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) { for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) {
RefPtr<File> domFile = RefPtr<File> domFile =
File::Create(aPromise.mGlobal, mTargetBlobImplArray[i]); File::Create(aPromise.GetGlobalObject(), mTargetBlobImplArray[i]);
if (NS_WARN_IF(!domFile)) { if (NS_WARN_IF(!domFile)) {
mErrorResult = NS_ERROR_FAILURE; mErrorResult = NS_ERROR_FAILURE;
files.Clear(); files.Clear();
@@ -422,11 +432,11 @@ void GetFilesHelper::ResolveOrRejectMozPromise(
// Error propagation. // Error propagation.
if (NS_FAILED(mErrorResult)) { if (NS_FAILED(mErrorResult)) {
aPromise.mMozPromise->Reject(mErrorResult, __func__); aPromise.Reject(mErrorResult);
return; return;
} }
aPromise.mMozPromise->Resolve(std::move(files), __func__); aPromise.Resolve(std::move(files));
} }
void GetFilesHelper::RunCallback(GetFilesCallback* aCallback) { void GetFilesHelper::RunCallback(GetFilesCallback* aCallback) {

View File

@@ -106,14 +106,31 @@ class GetFilesHelper : public Runnable, public GetFilesHelperBase {
void OperationCompleted(); void OperationCompleted();
void ResolveOrRejectPromise(Promise* aPromise);
struct MozPromiseAndGlobal { struct MozPromiseAndGlobal {
RefPtr<MozPromiseType> mMozPromise; RefPtr<MozPromiseType> mMozPromise;
RefPtr<nsIGlobalObject> mGlobal; RefPtr<nsIGlobalObject> mGlobal;
}; };
void ResolveOrRejectMozPromise(MozPromiseAndGlobal aPromise); class PromiseAdapter {
public:
explicit PromiseAdapter(MozPromiseAndGlobal&& aMozPromise);
explicit PromiseAdapter(Promise* aDomPromise);
~PromiseAdapter();
void Clear();
void Traverse(nsCycleCollectionTraversalCallback& cb);
nsIGlobalObject* GetGlobalObject();
void Resolve(nsTArray<RefPtr<File>>&& aFiles);
void Reject(nsresult aError);
private:
using PromiseVariant = Variant<RefPtr<Promise>, MozPromiseAndGlobal>;
PromiseVariant mPromise;
};
void AddPromiseInternal(PromiseAdapter&& aPromise);
void ResolveOrRejectPromise(PromiseAdapter&& aPromise);
void RunCallback(GetFilesCallback* aCallback); void RunCallback(GetFilesCallback* aCallback);
@@ -123,8 +140,8 @@ class GetFilesHelper : public Runnable, public GetFilesHelperBase {
// Error code to propagate. // Error code to propagate.
nsresult mErrorResult; nsresult mErrorResult;
nsTArray<RefPtr<Promise>> mPromises; nsTArray<PromiseAdapter> mPromises;
nsTArray<MozPromiseAndGlobal> mMozPromises;
nsTArray<RefPtr<GetFilesCallback>> mCallbacks; nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
Mutex mMutex MOZ_UNANNOTATED; Mutex mMutex MOZ_UNANNOTATED;

View File

@@ -63,15 +63,8 @@ struct BoolStruct {
class FilesCallback : public GetFilesCallback { class FilesCallback : public GetFilesCallback {
public: public:
FilesCallback(std::atomic<bool>& aGotResponse, FilesCallback(std::atomic<bool>& aGotResponse,
const nsTArray<nsTArray<const char*>>& aExpectedPathSegments) const nsTArray<nsString>& aExpectedPaths)
: mGotResponse(aGotResponse) { : mGotResponse(aGotResponse), mExpectedPaths(aExpectedPaths.Clone()) {}
for (const auto& pathSegments : aExpectedPathSegments) {
auto file = MakeFileFromPathSegments(pathSegments);
nsString expectedPath;
MOZ_ALWAYS_SUCCEEDS(file->GetPath(expectedPath));
mExpectedPaths.AppendElement(expectedPath);
}
}
// ------------------- // -------------------
// GetFilesCallback // GetFilesCallback
@@ -94,13 +87,46 @@ class FilesCallback : public GetFilesCallback {
nsTArray<nsString> mExpectedPaths; nsTArray<nsString> mExpectedPaths;
}; };
nsTArray<nsString> GetExpectedPaths(
const nsTArray<nsTArray<const char*>>& aPathSegmentsArray) {
nsTArray<nsString> expectedPaths(aPathSegmentsArray.Length());
for (const auto& pathSegments : aPathSegmentsArray) {
auto file = MakeFileFromPathSegments(pathSegments);
nsString expectedPath;
MOZ_ALWAYS_SUCCEEDS(file->GetPath(expectedPath));
expectedPaths.AppendElement(expectedPath);
}
return expectedPaths;
}
void ExpectGetFilesHelperResponse( void ExpectGetFilesHelperResponse(
RefPtr<GetFilesHelper> aHelper, RefPtr<GetFilesHelper> aHelper,
const nsTArray<nsTArray<const char*>>& aPathSegmentsArray) { const nsTArray<nsTArray<const char*>>& aPathSegmentsArray) {
std::atomic<bool> gotResponse = false; nsTArray<nsString> expectedPaths = GetExpectedPaths(aPathSegmentsArray);
std::atomic<bool> gotCallbackResponse = false;
std::atomic<bool> gotMozPromiseResponse = false;
RefPtr<FilesCallback> callback = RefPtr<FilesCallback> callback =
MakeRefPtr<FilesCallback>(gotResponse, aPathSegmentsArray); MakeRefPtr<FilesCallback>(gotCallbackResponse, expectedPaths);
aHelper->AddCallback(callback); aHelper->AddCallback(callback);
auto mozPromise = MakeRefPtr<GetFilesHelper::MozPromiseType>(__func__);
aHelper->AddMozPromise(mozPromise,
xpc::NativeGlobal(xpc::PrivilegedJunkScope()));
mozPromise->Then(
GetMainThreadSerialEventTarget(), __func__,
[&gotMozPromiseResponse,
&expectedPaths](const nsTArray<RefPtr<mozilla::dom::File>>& aFiles) {
EXPECT_EQ(aFiles.Length(), expectedPaths.Length());
for (const auto& file : aFiles) {
nsString path;
ErrorResult error;
file->GetMozFullPathInternal(path, error);
ASSERT_EQ(error.StealNSResult(), NS_OK);
ASSERT_TRUE(expectedPaths.Contains(path));
}
gotMozPromiseResponse = true;
},
[]() { FAIL() << "MozPromise got rejected!"; });
// Make timedOut a RefPtr so if we get a response after this function // Make timedOut a RefPtr so if we get a response after this function
// has finished we can safely check that (and don't start accessing stack // has finished we can safely check that (and don't start accessing stack
// values that don't exist anymore) // values that don't exist anymore)
@@ -108,17 +134,20 @@ void ExpectGetFilesHelperResponse(
RefPtr<CancelableRunnable> timer = RefPtr<CancelableRunnable> timer =
NS_NewCancelableRunnableFunction("GetFilesHelper timeout", [&] { NS_NewCancelableRunnableFunction("GetFilesHelper timeout", [&] {
if (!gotResponse.load()) { if (!gotCallbackResponse.load() || !gotMozPromiseResponse.load()) {
timedOut->mValue = true; timedOut->mValue = true;
} }
}); });
constexpr uint32_t kTimeout = 10000; constexpr uint32_t kTimeout = 10000;
NS_DelayedDispatchToCurrentThread(do_AddRef(timer), kTimeout); NS_DelayedDispatchToCurrentThread(do_AddRef(timer), kTimeout);
mozilla::SpinEventLoopUntil( mozilla::SpinEventLoopUntil(
"Waiting for GetFilesHelper result"_ns, "Waiting for GetFilesHelper result"_ns, [&, timedOut]() {
[&, timedOut]() { return gotResponse.load() || timedOut->mValue; }); return (gotCallbackResponse.load() && gotMozPromiseResponse.load()) ||
timedOut->mValue;
});
timer->Cancel(); timer->Cancel();
EXPECT_TRUE(gotResponse); EXPECT_TRUE(gotCallbackResponse);
EXPECT_TRUE(gotMozPromiseResponse);
EXPECT_FALSE(timedOut->mValue); EXPECT_FALSE(timedOut->mValue);
} }