Bug 1746667 - Make PathUtils path lookups sync on main thread r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D137217
This commit is contained in:
Barret Rennie 2022-02-01 04:06:13 +00:00
parent c1424ed185
commit f7c7ff7d55
7 changed files with 205 additions and 54 deletions

View File

@ -99,22 +99,37 @@ namespace PathUtils {
* @return Whether or not the path is absolute.
*/
boolean isAbsolute(DOMString path);
};
[Exposed=Window]
partial namespace PathUtils {
[Throws, BinaryName="ProfileDirSync"]
readonly attribute DOMString profileDir;
[Throws, BinaryName="LocalProfileDirSync"]
readonly attribute DOMString localProfileDir;
[Throws, BinaryName="TempDirSync"]
readonly attribute DOMString tempDir;
};
[Exposed=(Window, Worker)]
partial namespace PathUtils {
/**
* The profile directory.
*/
[Throws]
[Throws, BinaryName="GetProfileDirAsync"]
Promise<DOMString> getProfileDir();
/**
* The local-specific profile directory.
*/
[Throws]
[Throws, BinaryName="GetProfileDirAsync"]
Promise<DOMString> getLocalProfileDir();
/**
* The temporary directory for the process.
*/
[Throws]
[Throws, BinaryName="GetTempDirAsync"]
Promise<DOMString> getTempDir();
};

View File

@ -39,6 +39,8 @@ 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;
@ -46,7 +48,7 @@ static void ThrowError(ErrorResult& aErr, const nsresult aResult,
nsAutoCStringN<256> formattedMsg;
formattedMsg.Append(aMessage);
formattedMsg.Append(": "_ns);
formattedMsg.Append(COLON);
formattedMsg.Append(errName);
switch (aResult) {
@ -347,25 +349,57 @@ bool PathUtils::IsAbsolute(const GlobalObject&, const nsAString& aPath) {
return NS_SUCCEEDED(rv);
}
already_AddRefed<Promise> PathUtils::GetProfileDir(const GlobalObject& aGlobal,
ErrorResult& aErr) {
void PathUtils::GetProfileDirSync(const GlobalObject&, nsString& aResult,
ErrorResult& aErr) {
MOZ_ASSERT(NS_IsMainThread());
auto guard = sDirCache.Lock();
return DirectoryCache::Ensure(guard.ref())
.GetDirectory(aGlobal, aErr, DirectoryCache::Directory::Profile);
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);
}
already_AddRefed<Promise> PathUtils::GetLocalProfileDir(
already_AddRefed<Promise> PathUtils::GetProfileDirAsync(
const GlobalObject& aGlobal, ErrorResult& aErr) {
// NB: This will eventually be off-main-thread only.
auto guard = sDirCache.Lock();
return DirectoryCache::Ensure(guard.ref())
.GetDirectory(aGlobal, aErr, DirectoryCache::Directory::LocalProfile);
.GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::Profile);
}
already_AddRefed<Promise> PathUtils::GetTempDir(const GlobalObject& aGlobal,
ErrorResult& aErr) {
already_AddRefed<Promise> PathUtils::GetLocalProfileDirAsync(
const GlobalObject& aGlobal, ErrorResult& aErr) {
// NB: This will eventually be off-main-thread only.
auto guard = sDirCache.Lock();
return DirectoryCache::Ensure(guard.ref())
.GetDirectory(aGlobal, aErr, DirectoryCache::Directory::Temp);
.GetDirectoryAsync(aGlobal, aErr,
DirectoryCache::Directory::LocalProfile);
}
already_AddRefed<Promise> PathUtils::GetTempDirAsync(
const GlobalObject& aGlobal, ErrorResult& aErr) {
// NB: This will eventually be off-main-thread only.
auto guard = sDirCache.Lock();
return DirectoryCache::Ensure(guard.ref())
.GetDirectoryAsync(aGlobal, aErr, DirectoryCache::Directory::Temp);
}
PathUtils::DirectoryCache::DirectoryCache() {
@ -397,7 +431,28 @@ PathUtils::DirectoryCache& PathUtils::DirectoryCache::Ensure(
return aCache.ref();
}
already_AddRefed<Promise> PathUtils::DirectoryCache::GetDirectory(
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());
@ -481,10 +536,12 @@ nsresult PathUtils::DirectoryCache::PopulateDirectoriesImpl(
MOZ_RELEASE_ASSERT(NS_IsMainThread());
MOZ_RELEASE_ASSERT(aRequestedDir < Directory::Count);
// We cannot have second request to populate any of these
// directories if the first request succeeded, so assert that the
// corresponding fields are void.
MOZ_RELEASE_ASSERT(mDirectories[aRequestedDir].IsVoid());
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;

View File

@ -67,12 +67,19 @@ class PathUtils final {
static bool IsAbsolute(const GlobalObject&, const nsAString& aPath);
static already_AddRefed<Promise> GetProfileDir(const GlobalObject& aGlobal,
ErrorResult& aErr);
static already_AddRefed<Promise> GetLocalProfileDir(
static void GetProfileDirSync(const GlobalObject&, nsString& aResult,
ErrorResult& aErr);
static void GetLocalProfileDirSync(const GlobalObject&, nsString& aResult,
ErrorResult& aErr);
static void GetTempDirSync(const GlobalObject&, nsString& aResult,
ErrorResult& aErr);
static already_AddRefed<Promise> GetProfileDirAsync(
const GlobalObject& aGlobal, ErrorResult& aErr);
static already_AddRefed<Promise> GetTempDir(const GlobalObject& aGlobal,
ErrorResult& aErr);
static already_AddRefed<Promise> GetLocalProfileDirAsync(
const GlobalObject& aGlobal, ErrorResult& aErr);
static already_AddRefed<Promise> GetTempDirAsync(const GlobalObject& aGlobal,
ErrorResult& aErr);
private:
class DirectoryCache;
@ -87,7 +94,8 @@ class PathUtils final {
class PathUtils::DirectoryCache final {
public:
/**
* A directory that can be requested via |GetDirectory|.
* A directory that can be requested via |GetDirectorySync| or
* |GetDirectoryAsync|.
*/
enum class Directory {
/**
@ -124,6 +132,9 @@ class PathUtils::DirectoryCache final {
*/
static DirectoryCache& Ensure(Maybe<DirectoryCache>& aCache);
void GetDirectorySync(nsString& aResult, ErrorResult& aErr,
const Directory aRequestedDir);
/**
* Request the path of a specific directory.
*
@ -136,9 +147,9 @@ class PathUtils::DirectoryCache final {
*
* @return A promise that resolves to the path of the requested directory.
*/
already_AddRefed<Promise> GetDirectory(const GlobalObject& aGlobalObject,
ErrorResult& aErr,
const Directory aRequestedDir);
already_AddRefed<Promise> GetDirectoryAsync(const GlobalObject& aGlobalObject,
ErrorResult& aErr,
const Directory aRequestedDir);
private:
using PopulateDirectoriesPromise = MozPromise<Ok, nsresult, false>;

View File

@ -4,3 +4,6 @@ support-files =
[test_constants.xhtml]
[test_pathutils.html]
[test_pathutils_worker.xhtml]
support-files =
pathutils_worker.js

View File

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-env mozilla/chrome-worker */
/* global finish */
"use strict";
importScripts("chrome://mochikit/content/tests/SimpleTest/WorkerSimpleTest.js");
self.onmessage = async function(message) {
let expected = message.data;
info("ON message");
info(JSON.stringify(expected));
const profileDir = await PathUtils.getProfileDir();
is(
profileDir,
expected.profileDir,
"PathUtils.profileDir() in a worker should match PathUtils.profileDir on main thread"
);
const localProfileDir = await PathUtils.getLocalProfileDir();
is(
localProfileDir,
expected.localProfileDir,
"PathUtils.getLocalProfileDir() in a worker should match PathUtils.localProfileDir on main thread"
);
const tempDir = await PathUtils.getTempDir();
is(
tempDir,
expected.tempDir,
"PathUtils.getTempDir() in a worker should match PathUtils.tempDir on main thread"
);
finish();
};

View File

@ -8,6 +8,8 @@
<title>PathUtils tests</title>
</head>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/WorkerHandler.js"></script>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script>
"use strict";
@ -471,34 +473,21 @@
});
add_task(async function test_getDirectories() {
const profile = await PathUtils.getProfileDir();
is(
profile,
Services.dirsvc.get("ProfD", Ci.nsIFile).path,
"PathUtils.getProfileDir() should match dirsvc"
);
const localProfile = await PathUtils.getLocalProfileDir();
is(
localProfile,
Services.dirsvc.get("ProfLD", Ci.nsIFile).path,
"PathUtils.getLocalProfileDir() should match dirsvc"
);
// See: nsAppDirectoryServiceDefs.h
const tempDir = await PathUtils.getTempDir();
if (AppConstants.MOZ_SANDBOX) {
is(
tempDir,
Services.dirsvc.get("ContentTmpD", Ci.nsIFile).path,
"PathUtils.getTempDir() should match dirsvc"
);
} else {
is(
tempDir,
Services.dirsvc.get("TmpD", Ci.nsIFile).path,
"PathUtils.getTempDir() should match dirsvc"
);
const tests = [
["profileDir", "getProfileDir", "ProfD"],
["localProfileDir", "getLocalProfileDir", "ProfLD"],
["tempDir", "getTempDir", AppConstants.MOZ_SANDBOX ? "ContentTmpD" : "TmpD"],
];
for (const [attrName, methName, dirConstant] of tests) {
const expected = Services.dirsvc.get(dirConstant, Ci.nsIFile).path;
const attrValue = PathUtils[attrName];
is(attrValue, expected, `PathUtils.${attrName} == Services.dirsvc.get("${dirConstant}", Ci.nsIFile).path`);
const methValue = await PathUtils[methName]();
is(methValue, expected, `PathUtils.${methName}() == Services.dirsvc.get("${dirConstant}", Ci.nsIFile).path`);
}
});
</script>

View File

@ -0,0 +1,39 @@
<?xml version="1.0"?>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<window title="Testing PathUtils on a chrome worker thread"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="test();">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/WorkerHandler.js"></script>
<script type="application/javascript">
<![CDATA[
function test() {
SimpleTest.waitForExplicitFinish();
info("test_pathtuils_worker.xhtml: Starting test");
const worker = new ChromeWorker("pathutils_worker.js");
info("test_pathtuils_worker.xhtml: ChromeWorker created");
listenForTests(worker, { verbose: false });
worker.postMessage({
profileDir: PathUtils.profileDir,
localProfileDir: PathUtils.localProfileDir,
tempDir: PathUtils.tempDir,
});
info("test_pathtuils_worker.xhtml: Test running...");
}
]]>
</script>
<body xmlns="https://www.w3.org/1999/xhtml">
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
</body>
<label id="test-result" />
</window>