commit dea79b8a60f1552030dc00825e5dfc75975d29ba
parent 98cb1d5411935ac5a629b7691558925d6ea78e05
Author: Alexandre Lissy <lissyx+mozillians@lissyx.dyndns.org>
Date: Wed, 19 Nov 2025 17:46:17 +0000
Bug 259356 - Add support for the XDG Base Directory Specification r=stransky,glandium,gcp
Differential Revision: https://phabricator.services.mozilla.com/D6995
Diffstat:
9 files changed, 330 insertions(+), 28 deletions(-)
diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
@@ -3536,3 +3536,10 @@ pref("toolkit.rust-components.logging.internal-level", "Warn");
// Settings Redesign 2025 prefs
pref("browser.settings-redesign.enabled", false);
+
+// A preference that will be locked to reflect whether this build has support
+// for XDG Config Home handling. Mostly used to be able to keep tests around
+// in case of a backout of the feature
+#if defined(MOZ_WIDGET_GTK)
+pref("widget.support-xdg-config", true, locked);
+#endif
diff --git a/toolkit/moz.configure b/toolkit/moz.configure
@@ -3901,7 +3901,9 @@ with only_when(compile_environment):
def default_user_appdir(target):
if target.kernel in ("WINNT", "Darwin"):
return "Mozilla"
- return ".mozilla"
+ elif target.os == "Android":
+ return ".mozilla"
+ return "mozilla"
option(
"--with-user-appdir",
@@ -3918,7 +3920,12 @@ with only_when(compile_environment):
die("--with-user-appdir must be a single relative path.")
return '"{}"'.format(appdir[0])
+ @depends(depends("--with-user-appdir")(lambda x: x), when=toolkit_gtk)
+ def forced_or_dotted_user_appdir(value):
+ return value.origin != "default" or value[0].startswith(".")
+
set_define("MOZ_USER_DIR", user_appdir)
+ set_define("MOZ_LEGACY_HOME", True, when=forced_or_dotted_user_appdir)
# Check for sin_len and sin6_len - used by SCTP; only appears in Mac/*BSD generally
diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp
@@ -12,6 +12,7 @@
#include "jsapi.h"
#include "xpcpublic.h"
#include "prprf.h"
+#include "prenv.h"
#include "nsIAppStartup.h"
#include "nsIFile.h"
@@ -45,6 +46,7 @@
#endif
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/Components.h"
+#include "mozilla/DebugOnly.h"
#include "mozilla/Services.h"
#include "mozilla/Omnijar.h"
#include "mozilla/Preferences.h"
@@ -365,7 +367,9 @@ nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent,
else if (!strcmp(aProperty, XRE_SYS_NATIVE_MANIFESTS)) {
rv = ::GetSystemParentDirectory(getter_AddRefs(file));
} else if (!strcmp(aProperty, XRE_USER_NATIVE_MANIFESTS)) {
- rv = GetUserDataDirectoryHome(getter_AddRefs(file), false);
+ // Keep forcing the legacy path for compatibility
+ rv = GetUserDataDirectoryHome(getter_AddRefs(file), /* aLocal */ false,
+ /* aForceLegacy */ true);
NS_ENSURE_SUCCESS(rv, rv);
# if defined(XP_MACOSX)
rv = file->AppendNative("Mozilla"_ns);
@@ -918,7 +922,8 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult,
nsAutoString appDirPath;
if (NS_FAILED(appFile->GetParent(getter_AddRefs(appRootDirFile))) ||
NS_FAILED(appRootDirFile->GetPath(appDirPath)) ||
- NS_FAILED(GetUserDataDirectoryHome(getter_AddRefs(localDir), true))) {
+ NS_FAILED(GetUserDataDirectoryHome(getter_AddRefs(localDir),
+ /* aLocal */ true))) {
return NS_ERROR_FAILURE;
}
@@ -1055,7 +1060,8 @@ nsresult nsXREDirProvider::RestoreUserDataProfileDirectoryFromGTest(
// Return the home directory that will contain user data
nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile,
- bool aLocal) {
+ bool aLocal,
+ bool aForceLegacy) {
// Copied from nsAppFileLocationProvider (more or less)
nsCOMPtr<nsIFile> localDir;
@@ -1110,28 +1116,23 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile,
MOZ_TRY(NS_NewLocalFile(path, getter_AddRefs(localDir)));
#elif defined(XP_UNIX)
- const char* homeDir = getenv("HOME");
+ const char* homeDir = PR_GetEnv("HOME");
if (!homeDir || !*homeDir) return NS_ERROR_FAILURE;
# ifdef ANDROID /* We want (ProfD == ProfLD) on Android. */
- aLocal = false;
-# endif
-
+ MOZ_TRY(NS_NewNativeLocalFile(nsDependentCString(homeDir),
+ getter_AddRefs(localDir)));
+# else
if (aLocal) {
- // If $XDG_CACHE_HOME is defined use it, otherwise use $HOME/.cache.
- const char* cacheHome = getenv("XDG_CACHE_HOME");
- if (cacheHome && *cacheHome) {
- MOZ_TRY(NS_NewNativeLocalFile(nsDependentCString(cacheHome),
- getter_AddRefs(localDir)));
- } else {
- MOZ_TRY(NS_NewNativeLocalFile(nsDependentCString(homeDir),
- getter_AddRefs(localDir)));
- MOZ_TRY(localDir->AppendNative(".cache"_ns));
- }
+ // Not forcing legacy because cache can be lost without consequences, so
+ // there is no real requirement to keep compatibility here
+ MOZ_TRY(nsXREDirProvider::GetLegacyOrXDGCachePath(
+ homeDir, getter_AddRefs(localDir)));
} else {
- MOZ_TRY(NS_NewNativeLocalFile(nsDependentCString(homeDir),
- getter_AddRefs(localDir)));
+ MOZ_TRY(nsXREDirProvider::GetLegacyOrXDGHomePath(
+ homeDir, getter_AddRefs(localDir), aForceLegacy));
}
+# endif // ANDROID
#else
# error "Don't know how to get product dir on your platform"
#endif
@@ -1142,7 +1143,8 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile,
nsresult nsXREDirProvider::GetSysUserExtensionsDirectory(nsIFile** aFile) {
nsCOMPtr<nsIFile> localDir;
- nsresult rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false);
+ nsresult rv = GetUserDataDirectoryHome(
+ getter_AddRefs(localDir), /* aLocal */ false, /* aForceLegacy */ true);
NS_ENSURE_SUCCESS(rv, rv);
rv = AppendSysUserExtensionPath(localDir);
@@ -1254,6 +1256,211 @@ nsresult nsXREDirProvider::AppendSysUserExtensionPath(nsIFile* aFile) {
return NS_OK;
}
+#if defined(MOZ_WIDGET_GTK)
+/*
+ * Return whether MOZ_LEGACY_HOME == 1, via environment or at build time
+ */
+bool nsXREDirProvider::IsForceLegacyHome() {
+# if !defined(MOZ_LEGACY_HOME)
+ const char* legacyhomedir = PR_GetEnv("MOZ_LEGACY_HOME");
+ return legacyhomedir && legacyhomedir[0] == '1';
+# else
+ return true;
+# endif
+}
+
+/* static */
+nsresult nsXREDirProvider::AppendFromAppData(nsIFile* aFile, bool aIsDotted) {
+ // This might happen in xpcshell so assert that it is indeed in a xpcshell
+ // test. This assumes the xpcshell are ran through test harness.
+ if (!gAppData) {
+ mozilla::DebugOnly<const char*> xpcshell =
+ PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR");
+ MOZ_ASSERT(xpcshell, "gAppData can only be nullptr in xpcshell tests");
+ return NS_OK;
+ }
+
+ // Similar to nsXREDirProvider::AppendProfilePath.
+ // TODO: Bug 1990407 - Evaluate if refactoring might be required there in the
+ // future?
+ if (gAppData->profile) {
+ nsAutoCString profile;
+ profile = gAppData->profile;
+ MOZ_TRY(aFile->AppendRelativeNativePath(profile));
+ } else {
+ nsAutoCString vendor;
+ nsAutoCString appName;
+ vendor = gAppData->vendor;
+ appName = gAppData->name;
+ ToLowerCase(vendor);
+ ToLowerCase(appName);
+
+ MOZ_TRY(aFile->AppendRelativeNativePath(aIsDotted ? ("."_ns + vendor)
+ : vendor));
+ MOZ_TRY(aFile->AppendRelativeNativePath(appName));
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Check if legacy directory exists, which can be:
+ * (1) $HOME/.<gAppData->vendor>/<gAppData->appName>
+ * (2) $HOME/<gAppData->profile>
+ * (3) $HOME/<MOZ_USER_DIR>
+ *
+ * The MOZ_USER_DIR will also be defined in case (1), so first check the deeper
+ * directory.
+ */
+bool nsXREDirProvider::LegacyHomeExists(nsIFile** aFile) {
+ bool exists;
+ nsDependentCString homeDir(PR_GetEnv("HOME"));
+ nsCOMPtr<nsIFile> localDir;
+ nsCOMPtr<nsIFile> parentDir;
+
+ // check old config ~/.mozilla
+ nsresult rv = NS_NewNativeLocalFile(homeDir, getter_AddRefs(localDir));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = localDir->Clone(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Handle (1) and (2)
+ rv = AppendFromAppData(localDir, true);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = localDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Give a chance to (3)
+ if (!exists) {
+ nsCOMPtr<nsIFile> userDir;
+ rv = parentDir->Clone(getter_AddRefs(userDir));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString mozUserDir;
+ mozUserDir = nsLiteralCString(MOZ_USER_DIR);
+
+ rv = userDir->AppendRelativeNativePath(mozUserDir);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = userDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ // If required, return the parent dir that may exist.
+ if (aFile) {
+ parentDir.forget(aFile);
+ }
+
+ return exists;
+}
+
+/* static */
+nsresult nsXREDirProvider::GetLegacyOrXDGEnvValue(const char* aHomeDir,
+ const char* aEnvName,
+ nsCString aSubdir,
+ nsIFile** aFile) {
+ nsCOMPtr<nsIFile> localDir;
+ nsresult rv = NS_OK;
+
+ const char* envValue = PR_GetEnv(aEnvName);
+ if (envValue && *envValue) {
+ rv = NS_NewNativeLocalFile(nsDependentCString(envValue),
+ getter_AddRefs(localDir));
+ }
+
+ // Explicitly check for rv failure because in case we get passed an env
+ // value that is an invalid dir by the XDG specification level, it should
+ // be ignored. Per
+ // https://specifications.freedesktop.org/basedir-spec/0.8/:
+ // "If an implementation encounters a relative path in any of
+ // these variables it should consider the path invalid and ignore it."
+ if (NS_FAILED(rv) || !envValue || !*envValue) {
+ MOZ_TRY(NS_NewNativeLocalFile(nsDependentCString(aHomeDir),
+ getter_AddRefs(localDir)));
+ MOZ_TRY(localDir->AppendNative(aSubdir));
+ }
+
+ localDir.forget(aFile);
+ return NS_OK;
+}
+
+/* static */
+nsresult nsXREDirProvider::GetLegacyOrXDGCachePath(const char* aHomeDir,
+ nsIFile** aFile) {
+ return GetLegacyOrXDGEnvValue(aHomeDir, "XDG_CACHE_HOME", ".cache"_ns, aFile);
+}
+
+/*
+ * Check if XDG_CONFIG_HOME is here and use it or default to ${aHomeDir}/.config
+ */
+/* static */
+nsresult nsXREDirProvider::GetLegacyOrXDGConfigHome(const char* aHomeDir,
+ nsIFile** aFile) {
+ return GetLegacyOrXDGEnvValue(aHomeDir, "XDG_CONFIG_HOME", ".config"_ns,
+ aFile);
+}
+
+// Attempt to construct the HOME path depending on XDG or legacy status.
+nsresult nsXREDirProvider::GetLegacyOrXDGHomePath(const char* aHomeDir,
+ nsIFile** aFile,
+ bool aForceLegacy) {
+ nsCOMPtr<nsIFile> parentDir;
+ nsDependentCString homeDir(aHomeDir);
+
+ bool exists = LegacyHomeExists(getter_AddRefs(parentDir));
+ if (exists || IsForceLegacyHome() || aForceLegacy) {
+ parentDir.forget(aFile);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> localDir;
+
+ // If the build was made with --with-user-appdir=.fooProfile it needs to be
+ // applied and considered as a legacy path.
+ nsAutoCString mozUserDir;
+ mozUserDir = nsLiteralCString(MOZ_USER_DIR);
+ if (mozUserDir.get()[0] == '.') {
+ MOZ_TRY(NS_NewNativeLocalFile(nsDependentCString(aHomeDir),
+ getter_AddRefs(localDir)));
+ MOZ_TRY(localDir->AppendRelativeNativePath(mozUserDir));
+ } else {
+ // This might happen in xpcshell so assert that it is indeed in a xpcshell
+ // test
+ if (!gAppData) {
+ mozilla::DebugOnly<const char*> xpcshell =
+ PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR");
+ MOZ_ASSERT(xpcshell, "gAppData can only be nullptr in xpcshell tests");
+ return NS_OK;
+ }
+
+ // If the build was made against a specific profile name, MOZ_APP_PROFILE=
+ // then make sure we respect this and dont move to XDG directory
+ if (gAppData->profile) {
+ MOZ_TRY(NS_NewNativeLocalFile(nsDependentCString(aHomeDir),
+ getter_AddRefs(localDir)));
+ } else {
+ MOZ_TRY(GetLegacyOrXDGConfigHome(aHomeDir, getter_AddRefs(localDir)));
+ MOZ_TRY(localDir->Clone(getter_AddRefs(parentDir)));
+ }
+
+ MOZ_TRY(AppendFromAppData(localDir, false));
+ }
+
+ // If required return the parent directory that matches the profile root
+ // directory.
+ if (aFile) {
+ parentDir.forget(aFile);
+ }
+
+ // The profile root directory needs to exists at that point.
+ MOZ_TRY(EnsureDirectoryExists(localDir));
+
+ return NS_OK;
+}
+#endif // defined(MOZ_WIDGET_GTK)
+
nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) {
NS_ASSERTION(aFile, "Null pointer!");
@@ -1264,6 +1471,9 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) {
return NS_OK;
}
+ // Similar to nsXREDirProvider::AppendFromAppData.
+ // TODO: evaluate if refactoring might be required there in the future?
+
nsAutoCString profile;
nsAutoCString appName;
nsAutoCString vendor;
@@ -1310,7 +1520,13 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) {
nsAutoCString folder;
// Make it hidden (by starting with "."), except when local (the
// profile is already under ~/.cache or XDG_CACHE_HOME).
- if (!aLocal) folder.Assign('.');
+ if (!aLocal
+# if defined(MOZ_WIDGET_GTK)
+ && (IsForceLegacyHome() || LegacyHomeExists(nullptr))
+# endif
+ ) {
+ folder.Assign('.');
+ }
if (!profile.IsEmpty()) {
// Skip any leading path characters
diff --git a/toolkit/xre/nsXREDirProvider.h b/toolkit/xre/nsXREDirProvider.h
@@ -69,6 +69,25 @@ class nsXREDirProvider final : public nsIDirectoryServiceProvider2,
static nsresult GetUserDataDirectory(nsIFile** aFile, bool aLocal);
+#if defined(MOZ_WIDGET_GTK)
+ static nsresult GetLegacyOrXDGEnvValue(const char* aHomeDir,
+ const char* aEnvName,
+ nsCString aSubdir, nsIFile** aFile);
+ static nsresult GetLegacyOrXDGCachePath(const char* aHomeDir,
+ nsIFile** aFile);
+ static nsresult GetLegacyOrXDGHomePath(const char* aHomeDir, nsIFile** aFile,
+ bool aForceLegacy = false);
+ static nsresult AppendFromAppData(nsIFile* aFile, bool aIsDotted);
+
+ static bool IsForceLegacyHome();
+
+ static bool LegacyHomeExists(nsIFile** aFile);
+
+ static nsresult GetLegacyOrXDGConfigHome(const char* aHomeDir,
+ nsIFile** aFile);
+
+#endif // defined(MOZ_WIDGET_GTK)
+
/* make sure you clone it, if you need to do stuff to it */
nsIFile* GetGREDir() { return mGREDir; }
nsIFile* GetGREBinDir() { return mGREBinDir; }
@@ -117,7 +136,13 @@ class nsXREDirProvider final : public nsIDirectoryServiceProvider2,
private:
nsresult GetFilesInternal(const char* aProperty,
nsISimpleEnumerator** aResult);
- static nsresult GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal);
+
+ // aForceLegacy will only act on !aLocal and make sure the path returned
+ // is directly under $HOME. Useful for UserNativeManifests and
+ // SysUserExtensionDir to keep legacy behavior with XDG support active.
+ static nsresult GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal,
+ bool aForceLegacy = false);
+ static nsresult GetNativeUserManifestsDirectory(nsIFile** aFile);
static nsresult GetSysUserExtensionsDirectory(nsIFile** aFile);
#if defined(XP_UNIX) || defined(XP_MACOSX)
static nsresult GetSystemExtensionsDirectory(nsIFile** aFile);
diff --git a/toolkit/xre/test/browser.toml b/toolkit/xre/test/browser.toml
@@ -4,3 +4,6 @@ tags = "os_integration"
["browser_checkdllblockliststate.js"]
run-if = ["os == 'win'"]
skip-if = ["ccov"] # Bug 1531789
+
+["browser_xdg_pref.js"]
+run-if = ["os == 'linux'"]
diff --git a/toolkit/xre/test/browser_xdg_pref.js b/toolkit/xre/test/browser_xdg_pref.js
@@ -0,0 +1,11 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+add_task(async function test_pref_is_true() {
+ Assert.equal(true, Services.prefs.getBoolPref("widget.support-xdg-config"));
+});
+
+add_task(async function test_pref_is_locked() {
+ Services.prefs.setBoolPref("widget.support-xdg-config", false);
+ Assert.equal(true, Services.prefs.getBoolPref("widget.support-xdg-config"));
+});
diff --git a/tools/lint/dot-mozilla-reference.yml b/tools/lint/dot-mozilla-reference.yml
@@ -40,3 +40,4 @@ avoid-dot-mozilla-without-xdg:
- toolkit/moz.configure
- toolkit/xre/nsXREDirProvider.cpp
- toolkit/tests/gtest/TestXREAppDir.cpp
+ - xpcom/io/nsAppFileLocationProvider.cpp
diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build
@@ -165,4 +165,5 @@ if CONFIG["OS_ARCH"] == "Linux" and "lib64" in CONFIG["libdir"]:
LOCAL_INCLUDES += [
"!..",
"../build",
+ "/toolkit/xre",
]
diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp
@@ -13,6 +13,7 @@
#include "nsIFile.h"
#include "nsString.h"
#include "nsSimpleEnumerator.h"
+#include "nsXREDirProvider.h"
#include "prenv.h"
#include "nsCRT.h"
#if defined(MOZ_WIDGET_COCOA)
@@ -168,7 +169,8 @@ nsresult nsAppFileLocationProvider::CloneMozBinDirectory(nsIFile** aLocalFile) {
// GetProductDirectory - Gets the directory which contains the application data
// folder
//
-// UNIX : ~/.mozilla/
+// UNIX : ~/.mozilla/ or ${XDG_CONFIG_HOME:-~/.config}/mozilla
+// if env var MOZ_LEGACY_HOME is set to 1, then ~/.mozilla/ is used
// WIN : <Application Data folder on user's machine>\Mozilla
// Mac : :Documents:Mozilla:
//----------------------------------------------------------------------------------------
@@ -204,21 +206,49 @@ nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
return rv;
}
#elif defined(XP_UNIX)
- rv = NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")),
+ const char* homeDir = PR_GetEnv("HOME");
+ rv = NS_NewNativeLocalFile(nsDependentCString(homeDir),
getter_AddRefs(localDir));
if (NS_FAILED(rv)) {
return rv;
}
+
+# if defined(MOZ_WIDGET_GTK)
+ rv = nsXREDirProvider::GetLegacyOrXDGHomePath(homeDir,
+ getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+# endif
+
#else
# error dont_know_how_to_get_product_dir_on_your_platform
#endif
- rv = localDir->AppendRelativeNativePath(DEFAULT_PRODUCT_DIR);
+#if defined(MOZ_WIDGET_GTK)
+ bool legacyExists = nsXREDirProvider::LegacyHomeExists(nullptr);
+ if (legacyExists || nsXREDirProvider::IsForceLegacyHome()) {
+ nsAutoCString productDir;
+ nsAutoCString mozUserDir;
+ mozUserDir = nsLiteralCString(MOZ_USER_DIR);
+ if (mozUserDir.get()[0] != '.') {
+ productDir = "."_ns + DEFAULT_PRODUCT_DIR;
+ } else {
+ productDir = DEFAULT_PRODUCT_DIR;
+ }
+ rv = localDir->AppendNative(productDir);
+ } else {
+ rv = localDir->AppendNative(DEFAULT_PRODUCT_DIR);
+ }
+#else
+ rv = localDir->AppendNative(DEFAULT_PRODUCT_DIR);
+#endif
+
if (NS_FAILED(rv)) {
return rv;
}
- rv = localDir->Exists(&exists);
+ rv = localDir->Exists(&exists);
if (NS_SUCCEEDED(rv) && !exists) {
rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
}
@@ -236,7 +266,8 @@ nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
// GetDefaultUserProfileRoot - Gets the directory which contains each user
// profile dir
//
-// UNIX : ~/.mozilla/
+// UNIX : ~/.mozilla/ or ${XDG_CONFIG_HOME:-~/.config}/mozilla
+// if env var MOZ_LEGACY_HOME is set to 1, then ~/.mozilla/ is used
// WIN : <Application Data folder on user's machine>\Mozilla\Profiles
// Mac : :Documents:Mozilla:Profiles:
//----------------------------------------------------------------------------------------