summaryrefslogtreecommitdiffstats
path: root/dom/system/PathUtils.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/system/PathUtils.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/system/PathUtils.cpp')
-rw-r--r--dom/system/PathUtils.cpp639
1 files changed, 639 insertions, 0 deletions
diff --git a/dom/system/PathUtils.cpp b/dom/system/PathUtils.cpp
new file mode 100644
index 0000000000..d43171c4e9
--- /dev/null
+++ b/dom/system/PathUtils.cpp
@@ -0,0 +1,639 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "PathUtils.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Span.h"
+#include "mozilla/dom/DOMParser.h"
+#include "mozilla/dom/PathUtilsBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsCOMPtr.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsIGlobalObject.h"
+#include "nsLocalFile.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+static constexpr auto ERROR_EMPTY_PATH =
+ "PathUtils does not support empty paths"_ns;
+static constexpr auto ERROR_INITIALIZE_PATH = "Could not initialize path"_ns;
+static constexpr auto ERROR_GET_PARENT = "Could not get parent path"_ns;
+static constexpr auto ERROR_JOIN = "Could not append to path"_ns;
+
+static constexpr auto COLON = ": "_ns;
+
+static void ThrowError(ErrorResult& aErr, const nsresult aResult,
+ const nsCString& aMessage) {
+ nsAutoCStringN<32> errName;
+ GetErrorName(aResult, errName);
+
+ nsAutoCStringN<256> formattedMsg;
+ formattedMsg.Append(aMessage);
+ formattedMsg.Append(COLON);
+ formattedMsg.Append(errName);
+
+ switch (aResult) {
+ case NS_ERROR_FILE_UNRECOGNIZED_PATH:
+ aErr.ThrowOperationError(formattedMsg);
+ break;
+
+ case NS_ERROR_FILE_ACCESS_DENIED:
+ aErr.ThrowInvalidAccessError(formattedMsg);
+ break;
+
+ case NS_ERROR_FAILURE:
+ default:
+ aErr.ThrowUnknownError(formattedMsg);
+ break;
+ }
+}
+
+static bool DoWindowsPathCheck() {
+#ifdef XP_WIN
+# ifdef DEBUG
+ return true;
+# else // DEBUG
+ return xpc::IsInAutomation();
+# endif // DEBUG
+#else // XP_WIN
+ return false;
+#endif // XP_WIN
+}
+
+/* static */
+nsresult PathUtils::InitFileWithPath(nsIFile* aFile, const nsAString& aPath) {
+ if (DoWindowsPathCheck()) {
+ MOZ_RELEASE_ASSERT(!aPath.Contains(u'/'),
+ "Windows paths cannot include forward slashes");
+ }
+
+ MOZ_ASSERT(aFile);
+ return aFile->InitWithPath(aPath);
+}
+
+StaticDataMutex<Maybe<PathUtils::DirectoryCache>> PathUtils::sDirCache{
+ "sDirCache"};
+
+/**
+ * Return the leaf name, including leading path separators in the case of
+ * Windows UNC drive paths.
+ *
+ * @param aFile The file whose leaf name is to be returned.
+ * @param aResult The string to hold the resulting leaf name.
+ * @param aParent The pre-computed parent of |aFile|. If not provided, it will
+ * be computed.
+ */
+static nsresult GetLeafNamePreservingRoot(nsIFile* aFile, nsString& aResult,
+ nsIFile* aParent = nullptr) {
+ MOZ_ASSERT(aFile);
+
+ nsCOMPtr<nsIFile> parent = aParent;
+ if (!parent) {
+ MOZ_TRY(aFile->GetParent(getter_AddRefs(parent)));
+ }
+
+ if (parent) {
+ return aFile->GetLeafName(aResult);
+ }
+
+ // We have reached the root path. On Windows, the leafname for a UNC path
+ // will not have the leading backslashes, so we need to use the entire path
+ // here:
+ //
+ // * for a UNIX root path (/) this will be /;
+ // * for a Windows drive path (e.g., C:), this will be the drive path (C:);
+ // and
+ // * for a Windows UNC server path (e.g., \\\\server), this will be the full
+ // server path (\\\\server).
+ return aFile->GetPath(aResult);
+}
+
+void PathUtils::Filename(const GlobalObject&, const nsAString& aPath,
+ nsString& aResult, ErrorResult& aErr) {
+ if (aPath.IsEmpty()) {
+ aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> path = new nsLocalFile();
+ if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
+ return;
+ }
+
+ if (nsresult rv = GetLeafNamePreservingRoot(path, aResult); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, "Could not get leaf name of path"_ns);
+ return;
+ }
+}
+
+void PathUtils::Parent(const GlobalObject&, const nsAString& aPath,
+ const int32_t aDepth, nsString& aResult,
+ ErrorResult& aErr) {
+ if (aPath.IsEmpty()) {
+ aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> path = new nsLocalFile();
+ if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
+ return;
+ }
+
+ if (aDepth <= 0) {
+ aErr.ThrowNotSupportedError("A depth of at least 1 is required");
+ return;
+ }
+
+ nsCOMPtr<nsIFile> parent;
+ for (int32_t i = 0; path && i < aDepth; i++) {
+ if (nsresult rv = path->GetParent(getter_AddRefs(parent)); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_GET_PARENT);
+ return;
+ }
+ path = parent;
+ }
+
+ if (parent) {
+ MOZ_ALWAYS_SUCCEEDS(parent->GetPath(aResult));
+ } else {
+ aResult = VoidString();
+ }
+}
+
+void PathUtils::Join(const GlobalObject&, const Sequence<nsString>& aComponents,
+ nsString& aResult, ErrorResult& aErr) {
+ nsCOMPtr<nsIFile> path = Join(Span(aComponents), aErr);
+ if (aErr.Failed()) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
+}
+
+already_AddRefed<nsIFile> PathUtils::Join(
+ const Span<const nsString>& aComponents, ErrorResult& aErr) {
+ if (aComponents.IsEmpty() || aComponents[0].IsEmpty()) {
+ aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFile> path = new nsLocalFile();
+ if (nsresult rv = InitFileWithPath(path, aComponents[0]); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
+ return nullptr;
+ }
+
+ const auto components = Span<const nsString>(aComponents).Subspan(1);
+ for (const auto& component : components) {
+ if (nsresult rv = path->Append(component); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_JOIN);
+ return nullptr;
+ }
+ }
+
+ return path.forget();
+}
+
+void PathUtils::JoinRelative(const GlobalObject&, const nsAString& aBasePath,
+ const nsAString& aRelativePath, nsString& aResult,
+ ErrorResult& aErr) {
+ if (aRelativePath.IsEmpty()) {
+ aResult = aBasePath;
+ return;
+ }
+
+ nsCOMPtr<nsIFile> path = new nsLocalFile();
+ if (nsresult rv = InitFileWithPath(path, aBasePath); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
+ return;
+ }
+
+ if (nsresult rv = path->AppendRelativePath(aRelativePath); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_JOIN);
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
+}
+
+void PathUtils::ToExtendedWindowsPath(const GlobalObject&,
+ const nsAString& aPath, nsString& aResult,
+ ErrorResult& aErr) {
+#ifndef XP_WIN
+ aErr.ThrowNotAllowedError("Operation is windows specific"_ns);
+ return;
+#else
+ if (aPath.IsEmpty()) {
+ aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
+ return;
+ }
+
+ const nsAString& str1 = Substring(aPath, 1, 1);
+ const nsAString& str2 = Substring(aPath, 2, aPath.Length() - 2);
+
+ bool isUNC = aPath.Length() >= 2 &&
+ (aPath.First() == '\\' || aPath.First() == '/') &&
+ (str1.EqualsLiteral("\\") || str1.EqualsLiteral("/"));
+
+ constexpr auto pathPrefix = u"\\\\?\\"_ns;
+ const nsAString& uncPath = pathPrefix + u"UNC\\"_ns + str2;
+ const nsAString& normalPath = pathPrefix + aPath;
+
+ nsCOMPtr<nsIFile> path = new nsLocalFile();
+ if (nsresult rv = InitFileWithPath(path, isUNC ? uncPath : normalPath);
+ NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
+#endif
+}
+
+void PathUtils::Normalize(const GlobalObject&, const nsAString& aPath,
+ nsString& aResult, ErrorResult& aErr) {
+ if (aPath.IsEmpty()) {
+ aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> path = new nsLocalFile();
+ if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
+ return;
+ }
+
+ if (nsresult rv = path->Normalize(); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, "Could not normalize path"_ns);
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
+}
+
+void PathUtils::Split(const GlobalObject&, const nsAString& aPath,
+ nsTArray<nsString>& aResult, ErrorResult& aErr) {
+ if (aPath.IsEmpty()) {
+ aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> path = new nsLocalFile();
+ if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
+ return;
+ }
+
+ while (path) {
+ auto* component = aResult.EmplaceBack(fallible);
+ if (!component) {
+ aErr.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> parent;
+ if (nsresult rv = path->GetParent(getter_AddRefs(parent)); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_GET_PARENT);
+ return;
+ }
+
+ // GetLeafPreservingRoot cannot fail if we pass it a parent path.
+ MOZ_ALWAYS_SUCCEEDS(GetLeafNamePreservingRoot(path, *component, parent));
+
+ path = parent;
+ }
+
+ aResult.Reverse();
+}
+
+void PathUtils::SplitRelative(const GlobalObject& aGlobal,
+ const nsAString& aPath,
+ const SplitRelativeOptions& aOptions,
+ nsTArray<nsString>& aResult, ErrorResult& aErr) {
+ if (aPath.IsEmpty()) {
+ aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
+ return;
+ }
+
+ if (DoWindowsPathCheck()) {
+ MOZ_RELEASE_ASSERT(!aPath.Contains(u'/'),
+ "Windows paths cannot include forward slashes");
+ }
+
+ if (IsAbsolute(aGlobal, aPath)) {
+ aErr.ThrowNotAllowedError(
+ "PathUtils.splitRelative requires a relative path"_ns);
+ return;
+ }
+
+#ifdef XP_WIN
+ constexpr auto SEPARATOR = u'\\';
+#else
+ constexpr auto SEPARATOR = u'/';
+#endif
+
+ constexpr auto PARENT = u".."_ns;
+ constexpr auto CURRENT = u"."_ns;
+
+ for (const nsAString& pathComponent :
+ nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>{aPath,
+ SEPARATOR}
+ .ToRange()) {
+ if (!aOptions.mAllowEmpty && pathComponent.IsEmpty()) {
+ aErr.ThrowNotAllowedError(
+ "PathUtils.splitRelative: Empty directory components (\"\") not "
+ "allowed by options");
+ return;
+ }
+
+ if (!aOptions.mAllowParentDir && pathComponent == PARENT) {
+ aErr.ThrowNotAllowedError(
+ "PathUtils.splitRelative: Parent directory components (\"..\") not "
+ "allowed by options");
+ return;
+ }
+
+ if (!aOptions.mAllowCurrentDir && pathComponent == CURRENT) {
+ aErr.ThrowNotAllowedError(
+ "PathUtils.splitRelative: Current directory components (\".\") not "
+ "allowed by options");
+ return;
+ }
+
+ aResult.AppendElement(pathComponent);
+ }
+}
+
+void PathUtils::ToFileURI(const GlobalObject&, const nsAString& aPath,
+ nsCString& aResult, ErrorResult& aErr) {
+ if (aPath.IsEmpty()) {
+ aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> path = new nsLocalFile();
+ if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (nsresult rv = NS_NewFileURI(getter_AddRefs(uri), path); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, "Could not initialize File URI"_ns);
+ return;
+ }
+
+ if (nsresult rv = uri->GetSpec(aResult); NS_FAILED(rv)) {
+ ThrowError(aErr, rv, "Could not retrieve URI spec"_ns);
+ return;
+ }
+}
+
+bool PathUtils::IsAbsolute(const GlobalObject&, const nsAString& aPath) {
+ nsCOMPtr<nsIFile> path = new nsLocalFile();
+ nsresult rv = InitFileWithPath(path, aPath);
+ return NS_SUCCEEDED(rv);
+}
+
+void PathUtils::GetProfileDirSync(const GlobalObject&, nsString& aResult,
+ ErrorResult& aErr) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto guard = sDirCache.Lock();
+ DirectoryCache::Ensure(guard.ref())
+ .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::Profile);
+}
+void PathUtils::GetLocalProfileDirSync(const GlobalObject&, nsString& aResult,
+ ErrorResult& aErr) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto guard = sDirCache.Lock();
+ DirectoryCache::Ensure(guard.ref())
+ .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::LocalProfile);
+}
+void PathUtils::GetTempDirSync(const GlobalObject&, nsString& aResult,
+ ErrorResult& aErr) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto guard = sDirCache.Lock();
+ DirectoryCache::Ensure(guard.ref())
+ .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::Temp);
+}
+
+void PathUtils::GetXulLibraryPathSync(const GlobalObject&, nsString& aResult,
+ ErrorResult& aErr) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto guard = sDirCache.Lock();
+ DirectoryCache::Ensure(guard.ref())
+ .GetDirectorySync(aResult, aErr, DirectoryCache::Directory::XulLibrary);
+}
+
+already_AddRefed<Promise> PathUtils::GetProfileDirAsync(
+ const GlobalObject& aGlobal, ErrorResult& aErr) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ auto guard = sDirCache.Lock();
+ return DirectoryCache::Ensure(guard.ref())
+ .GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::Profile);
+}
+
+already_AddRefed<Promise> PathUtils::GetLocalProfileDirAsync(
+ const GlobalObject& aGlobal, ErrorResult& aErr) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ auto guard = sDirCache.Lock();
+ return DirectoryCache::Ensure(guard.ref())
+ .GetDirectoryAsync(aGlobal, aErr,
+ DirectoryCache::Directory::LocalProfile);
+}
+
+already_AddRefed<Promise> PathUtils::GetTempDirAsync(
+ const GlobalObject& aGlobal, ErrorResult& aErr) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ auto guard = sDirCache.Lock();
+ return DirectoryCache::Ensure(guard.ref())
+ .GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::Temp);
+}
+
+already_AddRefed<Promise> PathUtils::GetXulLibraryPathAsync(
+ const GlobalObject& aGlobal, ErrorResult& aErr) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ auto guard = sDirCache.Lock();
+ return DirectoryCache::Ensure(guard.ref())
+ .GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::XulLibrary);
+}
+
+PathUtils::DirectoryCache::DirectoryCache() {
+ for (auto& dir : mDirectories) {
+ dir.SetIsVoid(true);
+ }
+}
+
+PathUtils::DirectoryCache& PathUtils::DirectoryCache::Ensure(
+ Maybe<PathUtils::DirectoryCache>& aCache) {
+ if (aCache.isNothing()) {
+ aCache.emplace();
+
+ auto clearAtShutdown = []() {
+ RunOnShutdown([]() {
+ auto cache = PathUtils::sDirCache.Lock();
+ cache->reset();
+ });
+ };
+
+ if (NS_IsMainThread()) {
+ clearAtShutdown();
+ } else {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction(__func__, std::move(clearAtShutdown)));
+ }
+ }
+
+ return aCache.ref();
+}
+
+void PathUtils::DirectoryCache::GetDirectorySync(
+ nsString& aResult, ErrorResult& aErr, const Directory aRequestedDir) {
+ MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
+
+ if (nsresult rv = PopulateDirectoriesImpl(aRequestedDir); NS_FAILED(rv)) {
+ nsAutoCStringN<32> errorName;
+ GetErrorName(rv, errorName);
+
+ nsAutoCStringN<256> msg;
+ msg.Append("Could not retrieve directory "_ns);
+ msg.Append(kDirectoryNames[aRequestedDir]);
+ msg.Append(COLON);
+ msg.Append(errorName);
+
+ aErr.ThrowUnknownError(msg);
+ return;
+ }
+
+ aResult = mDirectories[aRequestedDir];
+}
+
+already_AddRefed<Promise> PathUtils::DirectoryCache::GetDirectoryAsync(
+ const GlobalObject& aGlobal, ErrorResult& aErr,
+ const Directory aRequestedDir) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<Promise> promise = Promise::Create(global, aErr);
+ if (aErr.Failed()) {
+ return nullptr;
+ }
+
+ if (RefPtr<PopulateDirectoriesPromise> p =
+ PopulateDirectories(aRequestedDir)) {
+ p->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, aRequestedDir](const Ok&) {
+ auto cache = PathUtils::sDirCache.Lock();
+ cache.ref()->ResolveWithDirectory(promise, aRequestedDir);
+ },
+ [promise](const nsresult& aRv) { promise->MaybeReject(aRv); });
+ } else {
+ ResolveWithDirectory(promise, aRequestedDir);
+ }
+
+ return promise.forget();
+}
+
+void PathUtils::DirectoryCache::ResolveWithDirectory(
+ Promise* aPromise, const Directory aRequestedDir) {
+ MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
+ MOZ_RELEASE_ASSERT(!mDirectories[aRequestedDir].IsVoid());
+ aPromise->MaybeResolve(mDirectories[aRequestedDir]);
+}
+
+already_AddRefed<PathUtils::DirectoryCache::PopulateDirectoriesPromise>
+PathUtils::DirectoryCache::PopulateDirectories(
+ const PathUtils::DirectoryCache::Directory aRequestedDir) {
+ MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
+
+ // If we have already resolved the requested directory, we can return
+ // immediately.
+ // Otherwise, if we have already fired off a request to populate the entry,
+ // so we can return the corresponding promise immediately. caller will queue
+ // a Thenable onto that promise to resolve/reject the request.
+ if (!mDirectories[aRequestedDir].IsVoid()) {
+ return nullptr;
+ }
+ if (!mPromises[aRequestedDir].IsEmpty()) {
+ return mPromises[aRequestedDir].Ensure(__func__);
+ }
+
+ RefPtr<PopulateDirectoriesPromise> promise =
+ mPromises[aRequestedDir].Ensure(__func__);
+
+ if (NS_IsMainThread()) {
+ nsresult rv = PopulateDirectoriesImpl(aRequestedDir);
+ ResolvePopulateDirectoriesPromise(rv, aRequestedDir);
+ } else {
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction(__func__, [aRequestedDir]() {
+ auto cache = PathUtils::sDirCache.Lock();
+ nsresult rv = cache.ref()->PopulateDirectoriesImpl(aRequestedDir);
+ cache.ref()->ResolvePopulateDirectoriesPromise(rv, aRequestedDir);
+ });
+ NS_DispatchToMainThread(runnable.forget());
+ }
+
+ return promise.forget();
+}
+
+void PathUtils::DirectoryCache::ResolvePopulateDirectoriesPromise(
+ nsresult aRv, const PathUtils::DirectoryCache::Directory aRequestedDir) {
+ MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
+
+ if (NS_SUCCEEDED(aRv)) {
+ mPromises[aRequestedDir].Resolve(Ok{}, __func__);
+ } else {
+ mPromises[aRequestedDir].Reject(aRv, __func__);
+ }
+}
+
+nsresult PathUtils::DirectoryCache::PopulateDirectoriesImpl(
+ const PathUtils::DirectoryCache::Directory aRequestedDir) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
+
+ if (!mDirectories[aRequestedDir].IsVoid()) {
+ // In between when this promise was dispatched to the main thread and now,
+ // the directory cache has had this entry populated (via the
+ // on-main-thread sync method).
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> path;
+
+ MOZ_TRY(NS_GetSpecialDirectory(kDirectoryNames[aRequestedDir],
+ getter_AddRefs(path)));
+ MOZ_TRY(path->GetPath(mDirectories[aRequestedDir]));
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom