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 {
public:
static void MaybeReleaseOnMainThread(
nsTArray<RefPtr<Promise>>&& aPromises,
nsTArray<GetFilesHelper::MozPromiseAndGlobal>&& aMozPromises,
nsTArray<PromiseAdapter>&& aPromises,
nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks) {
if (NS_IsMainThread()) {
return;
}
RefPtr<ReleaseRunnable> runnable = new ReleaseRunnable(
std::move(aPromises), std::move(aMozPromises), std::move(aCallbacks));
RefPtr<ReleaseRunnable> runnable =
new ReleaseRunnable(std::move(aPromises), std::move(aCallbacks));
FileSystemUtils::DispatchRunnable(nullptr, runnable.forget());
}
NS_IMETHOD
Run() override {
MOZ_ASSERT(NS_IsMainThread());
mPromises.Clear();
mMozPromises.Clear();
mCallbacks.Clear();
return NS_OK;
}
private:
ReleaseRunnable(nsTArray<RefPtr<Promise>>&& aPromises,
nsTArray<GetFilesHelper::MozPromiseAndGlobal>&& aMozPromises,
ReleaseRunnable(nsTArray<PromiseAdapter>&& aPromises,
nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks)
: Runnable("dom::ReleaseRunnable"),
mPromises(std::move(aPromises)),
mMozPromises{std::move(aMozPromises)},
mCallbacks(std::move(aCallbacks)) {}
nsTArray<RefPtr<Promise>> mPromises;
nsTArray<GetFilesHelper::MozPromiseAndGlobal> mMozPromises;
nsTArray<PromiseAdapter> mPromises;
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
@@ -127,36 +168,31 @@ GetFilesHelper::GetFilesHelper(bool aRecursiveFlag)
mCanceled(false) {}
GetFilesHelper::~GetFilesHelper() {
ReleaseRunnable::MaybeReleaseOnMainThread(
std::move(mPromises), std::move(mMozPromises), std::move(mCallbacks));
ReleaseRunnable::MaybeReleaseOnMainThread(std::move(mPromises),
std::move(mCallbacks));
}
void GetFilesHelper::AddPromise(Promise* aPromise) {
MOZ_ASSERT(aPromise);
void GetFilesHelper::AddPromiseInternal(PromiseAdapter&& aPromise) {
// Still working.
if (!mListingCompleted) {
mPromises.AppendElement(aPromise);
mPromises.AppendElement(std::move(aPromise));
return;
}
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,
nsIGlobalObject* aGlobal) {
MOZ_ASSERT(aPromise);
MOZ_ASSERT(aGlobal);
// Still working.
if (!mListingCompleted) {
mMozPromises.AppendElement(MozPromiseAndGlobal{aPromise, aGlobal});
return;
}
MOZ_ASSERT(mMozPromises.IsEmpty());
ResolveOrRejectMozPromise({aPromise, aGlobal});
AddPromiseInternal(PromiseAdapter(MozPromiseAndGlobal{aPromise, aGlobal}));
}
void GetFilesHelper::AddCallback(GetFilesCallback* aCallback) {
@@ -174,7 +210,6 @@ void GetFilesHelper::AddCallback(GetFilesCallback* aCallback) {
void GetFilesHelper::Unlink() {
mPromises.Clear();
mMozPromises.Clear();
mCallbacks.Clear();
{
@@ -185,9 +220,26 @@ void GetFilesHelper::Unlink() {
Cancel();
}
void GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback& cb) {
GetFilesHelper* tmp = this;
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises);
void GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback& aCb) {
for (auto&& promiseAdapter : 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) {
@@ -235,12 +287,7 @@ void GetFilesHelper::OperationCompleted() {
// Let's process the pending promises.
auto promises = std::move(mPromises);
for (uint32_t i = 0; i < promises.Length(); ++i) {
ResolveOrRejectPromise(promises[i]);
}
auto mozPromises = std::move(mMozPromises);
for (uint32_t i = 0; i < mozPromises.Length(); ++i) {
ResolveOrRejectMozPromise(mozPromises[i]);
ResolveOrRejectPromise(std::move(promises[i]));
}
// Let's process the pending callbacks.
@@ -359,53 +406,16 @@ nsresult GetFilesHelperBase::ExploreDirectory(const nsAString& aDOMPath,
return NS_OK;
}
void GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise) {
void GetFilesHelper::ResolveOrRejectPromise(PromiseAdapter&& aPromise) {
MOZ_ASSERT(NS_IsMainThread());
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;
if (NS_SUCCEEDED(mErrorResult)) {
for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) {
RefPtr<File> domFile =
File::Create(aPromise.mGlobal, mTargetBlobImplArray[i]);
File::Create(aPromise.GetGlobalObject(), mTargetBlobImplArray[i]);
if (NS_WARN_IF(!domFile)) {
mErrorResult = NS_ERROR_FAILURE;
files.Clear();
@@ -422,11 +432,11 @@ void GetFilesHelper::ResolveOrRejectMozPromise(
// Error propagation.
if (NS_FAILED(mErrorResult)) {
aPromise.mMozPromise->Reject(mErrorResult, __func__);
aPromise.Reject(mErrorResult);
return;
}
aPromise.mMozPromise->Resolve(std::move(files), __func__);
aPromise.Resolve(std::move(files));
}
void GetFilesHelper::RunCallback(GetFilesCallback* aCallback) {

View File

@@ -106,14 +106,31 @@ class GetFilesHelper : public Runnable, public GetFilesHelperBase {
void OperationCompleted();
void ResolveOrRejectPromise(Promise* aPromise);
struct MozPromiseAndGlobal {
RefPtr<MozPromiseType> mMozPromise;
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);
@@ -123,8 +140,8 @@ class GetFilesHelper : public Runnable, public GetFilesHelperBase {
// Error code to propagate.
nsresult mErrorResult;
nsTArray<RefPtr<Promise>> mPromises;
nsTArray<MozPromiseAndGlobal> mMozPromises;
nsTArray<PromiseAdapter> mPromises;
nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
Mutex mMutex MOZ_UNANNOTATED;

View File

@@ -63,15 +63,8 @@ struct BoolStruct {
class FilesCallback : public GetFilesCallback {
public:
FilesCallback(std::atomic<bool>& aGotResponse,
const nsTArray<nsTArray<const char*>>& aExpectedPathSegments)
: mGotResponse(aGotResponse) {
for (const auto& pathSegments : aExpectedPathSegments) {
auto file = MakeFileFromPathSegments(pathSegments);
nsString expectedPath;
MOZ_ALWAYS_SUCCEEDS(file->GetPath(expectedPath));
mExpectedPaths.AppendElement(expectedPath);
}
}
const nsTArray<nsString>& aExpectedPaths)
: mGotResponse(aGotResponse), mExpectedPaths(aExpectedPaths.Clone()) {}
// -------------------
// GetFilesCallback
@@ -94,13 +87,46 @@ class FilesCallback : public GetFilesCallback {
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(
RefPtr<GetFilesHelper> aHelper,
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 =
MakeRefPtr<FilesCallback>(gotResponse, aPathSegmentsArray);
MakeRefPtr<FilesCallback>(gotCallbackResponse, expectedPaths);
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
// has finished we can safely check that (and don't start accessing stack
// values that don't exist anymore)
@@ -108,17 +134,20 @@ void ExpectGetFilesHelperResponse(
RefPtr<CancelableRunnable> timer =
NS_NewCancelableRunnableFunction("GetFilesHelper timeout", [&] {
if (!gotResponse.load()) {
if (!gotCallbackResponse.load() || !gotMozPromiseResponse.load()) {
timedOut->mValue = true;
}
});
constexpr uint32_t kTimeout = 10000;
NS_DelayedDispatchToCurrentThread(do_AddRef(timer), kTimeout);
mozilla::SpinEventLoopUntil(
"Waiting for GetFilesHelper result"_ns,
[&, timedOut]() { return gotResponse.load() || timedOut->mValue; });
"Waiting for GetFilesHelper result"_ns, [&, timedOut]() {
return (gotCallbackResponse.load() && gotMozPromiseResponse.load()) ||
timedOut->mValue;
});
timer->Cancel();
EXPECT_TRUE(gotResponse);
EXPECT_TRUE(gotCallbackResponse);
EXPECT_TRUE(gotMozPromiseResponse);
EXPECT_FALSE(timedOut->mValue);
}