firefox: add handlers.json configuration
Adds support for configuring Firefox's handlers.json file to manage MIME type and URL scheme handlers declaratively (at `programs.firefox.profiles.<profile>.handlers`). Handlers control how Firefox opens files and protocols (e.g., PDF viewers, `mailto:` handlers).
This commit is contained in:
parent
c9507a9aa5
commit
a913ae61bf
6 changed files with 364 additions and 0 deletions
18
modules/misc/news/2025/12/2025-12-10_04-15-59.nix
Normal file
18
modules/misc/news/2025/12/2025-12-10_04-15-59.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
time = "2025-12-10T07:15:59+00:00";
|
||||||
|
condition = config.programs.firefox.enable;
|
||||||
|
message = ''
|
||||||
|
The Firefox module now provides a
|
||||||
|
'programs.firefox.profiles.<name>.handlers' option.
|
||||||
|
|
||||||
|
It allows declarative configuration of MIME type and URL scheme handlers
|
||||||
|
through Firefox's handlers.json file, controlling how Firefox opens files
|
||||||
|
and protocols (e.g., PDF viewers, mailto handlers).
|
||||||
|
|
||||||
|
Configure handlers with:
|
||||||
|
|
||||||
|
programs.firefox.profiles.<name>.handlers.mimeTypes
|
||||||
|
programs.firefox.profiles.<name>.handlers.schemes
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
@ -548,6 +548,25 @@ in
|
||||||
description = "Declarative search engine configuration.";
|
description = "Declarative search engine configuration.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handlers = mkOption {
|
||||||
|
type = types.submodule (
|
||||||
|
args:
|
||||||
|
import ./profiles/handlers.nix {
|
||||||
|
inherit (args) config;
|
||||||
|
inherit lib pkgs appName;
|
||||||
|
package = cfg.finalPackage;
|
||||||
|
modulePath = modulePath ++ [
|
||||||
|
"profiles"
|
||||||
|
name
|
||||||
|
"handlers"
|
||||||
|
];
|
||||||
|
profilePath = config.path;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
description = "Declarative handlers configuration for MIME types and URL schemes.";
|
||||||
|
};
|
||||||
|
|
||||||
containersForce = mkOption {
|
containersForce = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
|
|
@ -1048,6 +1067,11 @@ in
|
||||||
source = profile.search.file;
|
source = profile.search.file;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"${cfg.profilesPath}/${profile.path}/handlers.json" = mkIf (profile.handlers.enable) {
|
||||||
|
source = profile.handlers.configFile;
|
||||||
|
force = profile.handlers.force;
|
||||||
|
};
|
||||||
|
|
||||||
"${cfg.profilesPath}/${profile.path}/extensions" = mkIf (profile.extensions.packages != [ ]) {
|
"${cfg.profilesPath}/${profile.path}/extensions" = mkIf (profile.extensions.packages != [ ]) {
|
||||||
source =
|
source =
|
||||||
let
|
let
|
||||||
|
|
|
||||||
209
modules/programs/firefox/profiles/handlers.nix
Normal file
209
modules/programs/firefox/profiles/handlers.nix
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
appName,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
jsonFormat = pkgs.formats.json { };
|
||||||
|
|
||||||
|
# Process configuration, remove null values and empty handlers arrays.
|
||||||
|
genCfg =
|
||||||
|
cfg:
|
||||||
|
lib.mapAttrs (
|
||||||
|
_: item:
|
||||||
|
(removeAttrs item [ "handlers" ])
|
||||||
|
// (lib.optionalAttrs (item.handlers != [ ]) {
|
||||||
|
handlers = map (handler: lib.filterAttrsRecursive (_: v: v != null) handler) item.handlers;
|
||||||
|
})
|
||||||
|
) cfg;
|
||||||
|
|
||||||
|
# Common options shared between mimeTypes and schemes
|
||||||
|
commonHandlerOptions = {
|
||||||
|
action = lib.mkOption {
|
||||||
|
type = lib.types.enum [
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
];
|
||||||
|
default = 1;
|
||||||
|
description = ''
|
||||||
|
The action to take for this MIME type / URL scheme. Possible values:
|
||||||
|
- 0: Save file
|
||||||
|
- 1: Always ask
|
||||||
|
- 2: Use helper app
|
||||||
|
- 3: Open in ${appName}
|
||||||
|
- 4: Use system default
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ask = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
If true, the user is asked what they want to do with the file.
|
||||||
|
If false, the action is taken without user intervention.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
handlers = lib.mkOption {
|
||||||
|
type = lib.types.listOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Display name of the handler.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
path = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Path to the executable to be used.
|
||||||
|
|
||||||
|
Only one of 'path' or 'uriTemplate' should be set.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
uriTemplate = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
URI for the application handler.
|
||||||
|
|
||||||
|
Only one of 'path' or 'uriTemplate' should be set.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
An array of handlers with the first one being the default.
|
||||||
|
If you don't want to have a default handler, use an empty object for the first handler.
|
||||||
|
Only valid when action is set to 2 (Use helper app).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [ (pkgs.path + "/nixos/modules/misc/meta.nix") ];
|
||||||
|
|
||||||
|
meta.maintainers = with lib.maintainers; [ kugland ];
|
||||||
|
|
||||||
|
options = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = config.schemes != { } || config.mimeTypes != { };
|
||||||
|
internal = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
force = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to force replace the existing handlers configuration.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
mimeTypes = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = commonHandlerOptions // {
|
||||||
|
extensions = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.strMatching "^[^\\.].+$");
|
||||||
|
default = [ ];
|
||||||
|
example = [
|
||||||
|
"jpg"
|
||||||
|
"jpeg"
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
List of file extensions associated with this MIME type.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
"application/pdf" = {
|
||||||
|
action = 2;
|
||||||
|
ask = false;
|
||||||
|
handlers = [
|
||||||
|
{
|
||||||
|
name = "Okular";
|
||||||
|
path = "''${pkgs.okular}/bin/okular";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
extensions = [ "pdf" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Attribute set mapping MIME types to their handler configurations.
|
||||||
|
|
||||||
|
For a configuration example, see [this file on Firefox’s source code](https://github.com/mozilla-firefox/firefox/blob/c3797cdebac1316dd7168e995e3468c5a597e8d1/uriloader/exthandler/tests/unit/handlers.json).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
schemes = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = commonHandlerOptions;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
mailto = {
|
||||||
|
action = 2;
|
||||||
|
ask = false;
|
||||||
|
handlers = [
|
||||||
|
{
|
||||||
|
name = "Gmail";
|
||||||
|
uriTemplate = "https://mail.google.com/mail/?extsrc=mailto&url=%s";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Attribute set mapping URL schemes to their handler configurations.
|
||||||
|
|
||||||
|
For a configuration example, see [this file on Firefox’s source code](https://github.com/mozilla-firefox/firefox/blob/c3797cdebac1316dd7168e995e3468c5a597e8d1/uriloader/exthandler/tests/unit/handlers.json).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
finalSettings = lib.mkOption {
|
||||||
|
type = jsonFormat.type;
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
default = {
|
||||||
|
defaultHandlersVersion = { };
|
||||||
|
isDownloadsImprovementsAlreadyMigrated = false;
|
||||||
|
mimeTypes = genCfg config.mimeTypes;
|
||||||
|
schemes = genCfg config.schemes;
|
||||||
|
};
|
||||||
|
description = ''
|
||||||
|
Resulting handlers.json settings.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
configFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
default = jsonFormat.generate "handlers.json" config.finalSettings;
|
||||||
|
description = ''
|
||||||
|
JSON representation of the handlers configuration.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ builtins.mapAttrs
|
||||||
"${name}-profiles-extensions-assertions" = ./profiles/extensions/assertions.nix;
|
"${name}-profiles-extensions-assertions" = ./profiles/extensions/assertions.nix;
|
||||||
"${name}-profiles-extensions-exhaustive" = ./profiles/extensions/exhaustive.nix;
|
"${name}-profiles-extensions-exhaustive" = ./profiles/extensions/exhaustive.nix;
|
||||||
"${name}-profiles-extensions-exact" = ./profiles/extensions/exact.nix;
|
"${name}-profiles-extensions-exact" = ./profiles/extensions/exact.nix;
|
||||||
|
"${name}-profiles-handlers" = ./profiles/handlers;
|
||||||
"${name}-profiles-overwrite" = ./profiles/overwrite;
|
"${name}-profiles-overwrite" = ./profiles/overwrite;
|
||||||
"${name}-profiles-search" = ./profiles/search;
|
"${name}-profiles-search" = ./profiles/search;
|
||||||
"${name}-profiles-settings" = ./profiles/settings;
|
"${name}-profiles-settings" = ./profiles/settings;
|
||||||
|
|
|
||||||
69
tests/modules/programs/firefox/profiles/handlers/default.nix
Normal file
69
tests/modules/programs/firefox/profiles/handlers/default.nix
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
modulePath:
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = lib.getAttrFromPath modulePath config;
|
||||||
|
firefoxMockOverlay = import ../../setup-firefox-mock-overlay.nix modulePath;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [ firefoxMockOverlay ];
|
||||||
|
|
||||||
|
config = lib.mkIf config.test.enableBig (
|
||||||
|
lib.setAttrByPath modulePath {
|
||||||
|
enable = true;
|
||||||
|
profiles.handlers = {
|
||||||
|
id = 0;
|
||||||
|
handlers = {
|
||||||
|
mimeTypes = {
|
||||||
|
"application/pdf" = {
|
||||||
|
action = 2;
|
||||||
|
ask = false;
|
||||||
|
handlers = [
|
||||||
|
{
|
||||||
|
name = "Hello App";
|
||||||
|
path = "${pkgs.hello}/bin/hello";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
extensions = [ "pdf" ];
|
||||||
|
};
|
||||||
|
"text/html" = {
|
||||||
|
action = 4;
|
||||||
|
ask = true;
|
||||||
|
extensions = [
|
||||||
|
"html"
|
||||||
|
"htm"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
schemes = {
|
||||||
|
mailto = {
|
||||||
|
action = 2;
|
||||||
|
ask = false;
|
||||||
|
handlers = [
|
||||||
|
{
|
||||||
|
name = "Gmail";
|
||||||
|
uriTemplate = "https://mail.google.com/mail/?extsrc=mailto&url=%s";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
http = {
|
||||||
|
action = 3;
|
||||||
|
ask = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
nmt.script = ''
|
||||||
|
assertFileContent \
|
||||||
|
home-files/${cfg.configPath}/handlers/handlers.json \
|
||||||
|
${./expected-handlers.json}
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"defaultHandlersVersion": {},
|
||||||
|
"isDownloadsImprovementsAlreadyMigrated": false,
|
||||||
|
"mimeTypes": {
|
||||||
|
"application/pdf": {
|
||||||
|
"action": 2,
|
||||||
|
"ask": false,
|
||||||
|
"extensions": [
|
||||||
|
"pdf"
|
||||||
|
],
|
||||||
|
"handlers": [
|
||||||
|
{
|
||||||
|
"name": "Hello App",
|
||||||
|
"path": "@hello@/bin/hello"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"text/html": {
|
||||||
|
"action": 4,
|
||||||
|
"ask": true,
|
||||||
|
"extensions": [
|
||||||
|
"html",
|
||||||
|
"htm"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": {
|
||||||
|
"http": {
|
||||||
|
"action": 3,
|
||||||
|
"ask": true
|
||||||
|
},
|
||||||
|
"mailto": {
|
||||||
|
"action": 2,
|
||||||
|
"ask": false,
|
||||||
|
"handlers": [
|
||||||
|
{
|
||||||
|
"name": "Gmail",
|
||||||
|
"uriTemplate": "https://mail.google.com/mail/?extsrc=mailto&url=%s"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue