Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os == 'linux'
- Manifest: browser/components/shell/test/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
ChromeUtils.defineESModuleGetters(this, {
FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs",
ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
});
const gTmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
gTmpDir.append("createLinuxDesktopEntry-" + Date.now());
const gBrowserExe = Services.dirsvc
.get("XREExeF", Ci.nsIFile)
.path // (keep this?) Windows path compat
.replaceAll("\\", "\\\\");
// [the overarching issue here is that XREExeF can't seem to be intercepted,
// and therefore this test could break if characters in it need escaping. any
// way to fix?]
const gDirectoryServiceProvider = {
getFile(prop, persistent) {
persistent.value = false;
// We only expect a narrow range of calls.
let folder;
let type = Ci.nsIFile.DIRECTORY_TYPE;
switch (prop) {
case "Home":
folder = gTmpDir.clone();
folder.append("home");
break;
default:
console.error(`Access to unexpected directory '${prop}'`);
return Cr.NS_ERROR_FAILURE;
}
try {
folder.create(type, 0o755);
} catch (e) {
if (e.result !== Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
throw e;
}
}
return folder;
},
};
/**
* Parses the INI file at the given path.
*
* @param {string} path - The path to the INI file.
* @returns {nsIINIParser} The parsed INI file.
*/
async function parseINI(path) {
let parser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
.getService(Ci.nsIINIParserFactory)
.createINIParser();
// The file should be UTF-8, so use IOUtils to make sure that's the case.
parser.initFromString(await IOUtils.readUTF8(path));
return parser;
}
add_setup(async function setup() {
Services.dirsvc
.QueryInterface(Ci.nsIDirectoryService)
.registerProvider(gDirectoryServiceProvider);
});
registerCleanupFunction(async function cleanupTmp() {
gTmpDir.remove(true);
});
add_setup(function setupEnv() {
Services.env.set("XDG_DATA_HOME", "");
});
add_task(async function test_validateAppId() {
let message = /Desktop entry ID '[^']*' is invalid/;
await Assert.rejects(
ShellService.createLinuxDesktopEntry("", "ignored", [], ""),
message,
"The empty string is not a valid application ID"
);
await Assert.rejects(
ShellService.createLinuxDesktopEntry("a.1b.c", "ignored", [], ""),
message,
"Segment cannot begin with a digit"
);
await Assert.rejects(
ShellService.createLinuxDesktopEntry("a..c", "ignored", [], ""),
message,
"Segment cannot be empty"
);
// Doesn't really care about return value, just that it doesn't throw.
Assert.equal(
await ShellService.createLinuxDesktopEntry("a.b.c", "ignored", [], ""),
undefined,
"Typical segment is allowed"
);
Assert.equal(
await ShellService.createLinuxDesktopEntry(
"a-._b4.c__3",
"ignored",
[],
""
),
undefined,
"Underscores and numbers are allowed"
);
});
add_task(async function test_xdgdir() {
// By default, it should go into ~/.local/share/applications.
let path = PathUtils.join(
gTmpDir.path,
"home",
".local",
"share",
"applications",
"xdgdir.a.b.desktop"
);
await ShellService.createLinuxDesktopEntry("xdgdir.a.b", "ignored", [], "");
Assert.ok(
await IOUtils.exists(path),
"Desktop file was created in the default location if XDG_DATA_HOME is unset"
);
// But $XDG_DATA_HOME/applications should be used instead if available.
Services.env.set("XDG_DATA_HOME", PathUtils.join(gTmpDir.path, "datahome"));
path = PathUtils.join(
gTmpDir.path,
"datahome",
"applications",
"xdgdir.c.d.desktop"
);
await ShellService.createLinuxDesktopEntry("xdgdir.c.d", "ignored", [], "");
Assert.ok(
await IOUtils.exists(path),
"Desktop file was created in XDG_DATA_HOME/applications"
);
await IOUtils.remove(path); // datahome isn't removed in 'cleanup'
// ...unless it's invalid. (Or empty, but XPCOM doesn't differentiate 'empty'
// and 'nonexistent', which is fine.)
Services.env.set("XDG_DATA_HOME", "pineapple!");
path = PathUtils.join(
gTmpDir.path,
"home",
".local",
"share",
"applications",
"xdgdir.e.f.desktop"
);
await ShellService.createLinuxDesktopEntry("xdgdir.e.f", "ignored", [], "");
Assert.ok(
await IOUtils.exists(path),
"Desktop file was created in the default location if XDG_DATA_HOME is invalid"
);
Services.env.set("XDG_DATA_HOME", "");
});
add_task(async function test_standardContent() {
let path = PathUtils.join(
gTmpDir.path,
"home",
".local",
"share",
"applications",
"content.a.desktop"
);
await ShellService.createLinuxDesktopEntry(
"content.a",
"Cool Progr\xe0m!",
[],
"open-menu-symbolic"
);
let ini = await parseINI(path);
Assert.equal(
ini.getSections().getNext(),
"Desktop Entry",
"'Desktop Entry' must be the first section in the file"
);
Assert.equal(
ini.getString("Desktop Entry", "Version"),
"1.5",
"Compliance with version 1.5 of the spec is declared"
);
Assert.equal(
ini.getString("Desktop Entry", "Name"),
"Cool Progr\xe0m!",
"The name is stored, including any non-ASCII characters"
);
Assert.equal(
ini.getString("Desktop Entry", "Exec"),
`"${gBrowserExe}"`,
"XREExeF will be run without any arguments"
);
Assert.equal(
ini.getString("Desktop Entry", "Icon"),
"open-menu-symbolic",
"The icon is exactly the provided text"
);
});
add_task(async function test_exec() {
let path = PathUtils.join(
gTmpDir.path,
"home",
".local",
"share",
"applications",
"content.b.desktop"
);
await ShellService.createLinuxDesktopEntry(
"content.b",
"Exec Test",
["abc", "$d$e$f", "gh\\i", "jk lm", '"nopq"'],
""
);
let ini = await parseINI(path);
Assert.equal(
ini.getString("Desktop Entry", "Exec"),
`"${gBrowserExe}" "abc" "\\$d\\$e\\$f" "gh\\\\i" "jk lm" "\\"nopq\\""`,
"Arguments are escaped as expected"
);
});
add_task(async function test_deletion() {
let path = PathUtils.join(
gTmpDir.path,
"home",
".local",
"share",
"applications",
"deletion.desktop"
);
await ShellService.createLinuxDesktopEntry(
"deletion",
"Deletion Test",
[],
""
);
ok(await IOUtils.exists(path), "The desktop file was created");
await ShellService.deleteLinuxDesktopEntry("deletion");
ok(!(await IOUtils.exists(path)), "The desktop file was deleted");
});