fix(browser): resolve snap and Flatpak Chromium profiles on Linux
real_profile_data_dir hard-wired Linux to $XDG_CONFIG_HOME/<name>, and the xdg fragment map only knew the native package names. Ubuntu's default snap Chromium (xdg reports chromium_chromium.desktop, profile under ~/snap/chromium/common/chromium) and Flatpak builds (~/.var/app/<id>/config/…) therefore ended in 'profile directory was not found' for a browser the user runs every day, and Flatpak Chrome (com.google.Chrome.desktop) was reported as 'not a supported Chromium browser'. Try the native, snap and Flatpak locations and return the first that exists; fall back to the native path so the error message still names a concrete directory. Map the Flatpak application ids in the xdg lookup. Tests cover the xdg names for all four browsers in native and Flatpak form, and the directory preference order with a temp HOME.
This commit is contained in:
committed by
Teknium
parent
f5e6028417
commit
7e2c2b1b08
@@ -104,14 +104,30 @@ _WINDOWS_PROGID_MAP = (
|
||||
)
|
||||
|
||||
# Linux xdg default-web-browser .desktop name fragments → canonical key.
|
||||
# Includes the Flatpak application ids (``com.google.Chrome.desktop`` etc.),
|
||||
# which share none of the native package name fragments.
|
||||
_LINUX_DESKTOP_MAP = (
|
||||
("google-chrome", "chrome"),
|
||||
("com.google.chrome", "chrome"),
|
||||
("chromium", "chromium"),
|
||||
("brave", "brave"),
|
||||
("microsoft-edge", "edge"),
|
||||
("com.microsoft.edge", "edge"),
|
||||
("msedge", "edge"),
|
||||
)
|
||||
|
||||
# Where sandboxed Linux packages keep the profile instead of $XDG_CONFIG_HOME.
|
||||
_LINUX_FLATPAK_IDS = {
|
||||
"chrome": "com.google.Chrome",
|
||||
"chromium": "org.chromium.Chromium",
|
||||
"brave": "com.brave.Browser",
|
||||
"edge": "com.microsoft.Edge",
|
||||
}
|
||||
_LINUX_SNAP_PROFILE_PARTS = {
|
||||
"chromium": ("snap", "chromium", "common", "chromium"),
|
||||
"brave": ("snap", "brave", "current", ".config", "BraveSoftware", "Brave-Browser"),
|
||||
}
|
||||
|
||||
# macOS LaunchServices bundle-id fragments → canonical key.
|
||||
_DARWIN_BUNDLE_MAP = (
|
||||
("com.google.chrome", "chrome"),
|
||||
@@ -150,10 +166,12 @@ def _real_profile_relparts(browser: str) -> tuple:
|
||||
def real_profile_data_dir(browser: str, system: str | None = None) -> str | None:
|
||||
"""Return the default user-data-dir for a Chromium ``browser`` on ``system``.
|
||||
|
||||
Returns None for unknown browsers. Does not check existence — callers that
|
||||
need that should stat the result. Paths are built with the TARGET system's
|
||||
separator (posix for Darwin/Linux, backslash for Windows) so an explicit
|
||||
``system`` argument resolves correctly regardless of the host OS.
|
||||
Returns None for unknown browsers. On Linux the native ($XDG_CONFIG_HOME),
|
||||
snap and Flatpak locations are tried and the first existing one wins; the
|
||||
native path is returned when none exists so the caller's error names it.
|
||||
Darwin/Windows paths are not stat'ed. Paths are built with the TARGET
|
||||
system's separator (posix for Darwin/Linux, backslash for Windows) so an
|
||||
explicit ``system`` argument resolves correctly regardless of the host OS.
|
||||
"""
|
||||
if browser not in _CHROMIUM_BROWSERS:
|
||||
return None
|
||||
@@ -167,7 +185,19 @@ def real_profile_data_dir(browser: str, system: str | None = None) -> str | None
|
||||
return ntpath.join(local, *win_parts)
|
||||
# Linux / other POSIX
|
||||
config = os.environ.get("XDG_CONFIG_HOME") or posixpath.join(home, ".config")
|
||||
return posixpath.join(config, *linux_name.split("/"))
|
||||
candidates = [posixpath.join(config, *linux_name.split("/"))]
|
||||
snap_parts = _LINUX_SNAP_PROFILE_PARTS.get(browser)
|
||||
if snap_parts:
|
||||
candidates.append(posixpath.join(home, *snap_parts))
|
||||
flatpak_id = _LINUX_FLATPAK_IDS.get(browser)
|
||||
if flatpak_id:
|
||||
candidates.append(
|
||||
posixpath.join(home, ".var", "app", flatpak_id, "config", *linux_name.split("/"))
|
||||
)
|
||||
for candidate in candidates:
|
||||
if os.path.isdir(candidate):
|
||||
return candidate
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def chromium_executable(browser: str, system: str | None = None) -> str | None:
|
||||
|
||||
@@ -104,3 +104,69 @@ class TestDetectDefaultDarwin:
|
||||
def test_bundle_map(self, bundle, expected):
|
||||
with self._run_with(_ls_dump(_handler("https", bundle))):
|
||||
assert bc._detect_default_darwin() == expected
|
||||
|
||||
|
||||
class TestDetectDefaultLinux:
|
||||
def _run_with(self, output: str):
|
||||
class _Proc:
|
||||
stdout = output
|
||||
|
||||
return patch.object(bc.subprocess, "run", return_value=_Proc())
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"desktop,expected",
|
||||
[
|
||||
("google-chrome.desktop", "chrome"),
|
||||
("com.google.Chrome.desktop", "chrome"),
|
||||
("chromium_chromium.desktop", "chromium"),
|
||||
("org.chromium.Chromium.desktop", "chromium"),
|
||||
("brave-browser.desktop", "brave"),
|
||||
("com.brave.Browser.desktop", "brave"),
|
||||
("microsoft-edge.desktop", "edge"),
|
||||
("com.microsoft.Edge.desktop", "edge"),
|
||||
("firefox.desktop", None),
|
||||
("org.mozilla.firefox.desktop", None),
|
||||
("", None),
|
||||
],
|
||||
)
|
||||
def test_xdg_desktop_names(self, desktop, expected):
|
||||
with self._run_with(desktop + "\n"):
|
||||
assert bc._detect_default_linux() == expected
|
||||
|
||||
def test_missing_xdg_settings_fails_closed(self):
|
||||
with patch.object(bc.subprocess, "run", side_effect=FileNotFoundError("xdg-settings")):
|
||||
assert bc._detect_default_linux() is None
|
||||
|
||||
|
||||
class TestLinuxProfileDir:
|
||||
def _env(self, monkeypatch, home):
|
||||
monkeypatch.setenv("HOME", str(home))
|
||||
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
|
||||
|
||||
def test_native_path_when_nothing_exists(self, tmp_path, monkeypatch):
|
||||
self._env(monkeypatch, tmp_path)
|
||||
assert bc.real_profile_data_dir("chromium", "Linux") == str(tmp_path / ".config" / "chromium")
|
||||
|
||||
def test_snap_chromium_profile_is_found(self, tmp_path, monkeypatch):
|
||||
self._env(monkeypatch, tmp_path)
|
||||
snap = tmp_path / "snap" / "chromium" / "common" / "chromium"
|
||||
snap.mkdir(parents=True)
|
||||
assert bc.real_profile_data_dir("chromium", "Linux") == str(snap)
|
||||
|
||||
def test_flatpak_chrome_profile_is_found(self, tmp_path, monkeypatch):
|
||||
self._env(monkeypatch, tmp_path)
|
||||
flatpak = tmp_path / ".var" / "app" / "com.google.Chrome" / "config" / "google-chrome"
|
||||
flatpak.mkdir(parents=True)
|
||||
assert bc.real_profile_data_dir("chrome", "Linux") == str(flatpak)
|
||||
|
||||
def test_native_profile_wins_when_present(self, tmp_path, monkeypatch):
|
||||
self._env(monkeypatch, tmp_path)
|
||||
native = tmp_path / ".config" / "BraveSoftware" / "Brave-Browser"
|
||||
native.mkdir(parents=True)
|
||||
(tmp_path / ".var" / "app" / "com.brave.Browser" / "config" / "BraveSoftware" / "Brave-Browser").mkdir(parents=True)
|
||||
assert bc.real_profile_data_dir("brave", "Linux") == str(native)
|
||||
|
||||
def test_xdg_config_home_is_honoured(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
monkeypatch.setenv("XDG_CONFIG_HOME", "/home/t/.config")
|
||||
assert bc.real_profile_data_dir("edge", "Linux") == "/home/t/.config/microsoft-edge"
|
||||
|
||||
Reference in New Issue
Block a user