firefox: Enable userChrome in about:config

Enable "toolkit.legacyUserProfileCustomizations.stylesheets" in
about:config if userChrome or userContent is not empty.
This commit is contained in:
MrQubo 2024-07-31 00:28:12 +02:00
parent 4fcd54df7c
commit 4205c1fc74
3 changed files with 433 additions and 380 deletions

View file

@ -343,410 +343,421 @@ in {
profiles = mkOption { profiles = mkOption {
inherit visible; inherit visible;
type = types.attrsOf (types.submodule ({ config, name, ... }: { type = types.attrsOf (types.submodule ({ config, name, ... }:
options = { let
name = mkOption {
type = types.str;
default = name;
description = "Profile name.";
};
id = mkOption { profile = config;
type = types.ints.unsigned;
default = 0;
description = ''
Profile ID. This should be set to a unique number per profile.
'';
};
settings = mkOption { in {
type = types.attrsOf (jsonFormat.type // { options = {
description = name = mkOption {
"${name} preference (int, bool, string, and also attrs, list, float as a JSON string)"; type = types.str;
}); default = name;
default = { }; description = "Profile name.";
example = literalExpression '' };
{
"browser.startup.homepage" = "https://nixos.org";
"browser.search.region" = "GB";
"browser.search.isUS" = false;
"distribution.searchplugins.defaultLocale" = "en-GB";
"general.useragent.locale" = "en-GB";
"browser.bookmarks.showMobileBookmarks" = true;
"browser.newtabpage.pinned" = [{
title = "NixOS";
url = "https://nixos.org";
}];
}
'';
description = ''
Attribute set of ${name} preferences.
${name} only supports int, bool, and string types for id = mkOption {
preferences, but home-manager will automatically type = types.ints.unsigned;
convert all other JSON-compatible values into strings. default = 0;
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Extra preferences to add to {file}`user.js`.
'';
};
userChrome = mkOption {
type = types.lines;
default = "";
description = "Custom ${name} user chrome CSS.";
example = ''
/* Hide tab bar in FF Quantum */
@-moz-document url(chrome://browser/content/browser.xul), url(chrome://browser/content/browser.xhtml) {
#TabsToolbar {
visibility: collapse !important;
margin-bottom: 21px !important;
}
#sidebar-box[sidebarcommand="treestyletab_piro_sakura_ne_jp-sidebar-action"] #sidebar-header {
visibility: collapse !important;
}
}
'';
};
userContent = mkOption {
type = types.lines;
default = "";
description = "Custom ${name} user content CSS.";
example = ''
/* Hide scrollbar in FF Quantum */
*{scrollbar-width:none !important}
'';
};
bookmarks = mkOption {
type = let
bookmarkSubmodule = types.submodule ({ config, name, ... }: {
options = {
name = mkOption {
type = types.str;
default = name;
description = "Bookmark name.";
};
tags = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Bookmark tags.";
};
keyword = mkOption {
type = types.nullOr types.str;
default = null;
description = "Bookmark search keyword.";
};
url = mkOption {
type = types.str;
description = "Bookmark url, use %s for search terms.";
};
};
}) // {
description = "bookmark submodule";
};
bookmarkType = types.addCheck bookmarkSubmodule (x: x ? "url");
directoryType = types.submodule ({ config, name, ... }: {
options = {
name = mkOption {
type = types.str;
default = name;
description = "Directory name.";
};
bookmarks = mkOption {
type = types.listOf nodeType;
default = [ ];
description = "Bookmarks within directory.";
};
toolbar = mkOption {
type = types.bool;
default = false;
description = ''
Make this the toolbar directory. Note, this does _not_
mean that this directory will be added to the toolbar,
this directory _is_ the toolbar.
'';
};
};
}) // {
description = "directory submodule";
};
nodeType = types.either bookmarkType directoryType;
in with types;
coercedTo (attrsOf nodeType) attrValues (listOf nodeType);
default = [ ];
example = literalExpression ''
[
{
name = "wikipedia";
tags = [ "wiki" ];
keyword = "wiki";
url = "https://en.wikipedia.org/wiki/Special:Search?search=%s&go=Go";
}
{
name = "kernel.org";
url = "https://www.kernel.org";
}
{
name = "Nix sites";
toolbar = true;
bookmarks = [
{
name = "homepage";
url = "https://nixos.org/";
}
{
name = "wiki";
tags = [ "wiki" "nix" ];
url = "https://wiki.nixos.org/";
}
];
}
]
'';
description = ''
Preloaded bookmarks. Note, this may silently overwrite any
previously existing bookmarks!
'';
};
path = mkOption {
type = types.str;
default = name;
description = "Profile path.";
};
isDefault = mkOption {
type = types.bool;
default = config.id == 0;
defaultText = "true if profile ID is 0";
description = "Whether this is a default profile.";
};
search = {
force = mkOption {
type = with types; bool;
default = false;
description = '' description = ''
Whether to force replace the existing search Profile ID. This should be set to a unique number per profile.
configuration. This is recommended since ${name} will
replace the symlink for the search configuration on every
launch, but note that you'll lose any existing
configuration by enabling this.
''; '';
}; };
default = mkOption { settings = mkOption {
type = with types; nullOr str; type = types.attrsOf (jsonFormat.type // {
default = null; description =
example = "DuckDuckGo"; "${name} preference (int, bool, string, and also attrs, list, float as a JSON string)";
description = '' });
The default search engine used in the address bar and search bar.
'';
};
privateDefault = mkOption {
type = with types; nullOr str;
default = null;
example = "DuckDuckGo";
description = ''
The default search engine used in the Private Browsing.
'';
};
order = mkOption {
type = with types; uniq (listOf str);
default = [ ];
example = [ "DuckDuckGo" "Google" ];
description = ''
The order the search engines are listed in. Any engines
that aren't included in this list will be listed after
these in an unspecified order.
'';
};
engines = mkOption {
type = with types; attrsOf (attrsOf jsonFormat.type);
default = { }; default = { };
example = literalExpression '' example = literalExpression ''
{ {
"Nix Packages" = { "browser.startup.homepage" = "https://nixos.org";
urls = [{ "browser.search.region" = "GB";
template = "https://search.nixos.org/packages"; "browser.search.isUS" = false;
params = [ "distribution.searchplugins.defaultLocale" = "en-GB";
{ name = "type"; value = "packages"; } "general.useragent.locale" = "en-GB";
{ name = "query"; value = "{searchTerms}"; } "browser.bookmarks.showMobileBookmarks" = true;
]; "browser.newtabpage.pinned" = [{
}]; title = "NixOS";
url = "https://nixos.org";
icon = "''${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; }];
definedAliases = [ "@np" ];
};
"NixOS Wiki" = {
urls = [{ template = "https://wiki.nixos.org/index.php?search={searchTerms}"; }];
iconUpdateURL = "https://wiki.nixos.org/favicon.png";
updateInterval = 24 * 60 * 60 * 1000; # every day
definedAliases = [ "@nw" ];
};
"Bing".metaData.hidden = true;
"Google".metaData.alias = "@g"; # builtin engines only support specifying one additional alias
} }
''; '';
description = '' description = ''
Attribute set of search engine configurations. Engines Attribute set of ${name} preferences.
that only have {var}`metaData` specified will
be treated as builtin to ${name}.
See [SearchEngine.jsm](https://searchfox.org/mozilla-central/rev/669329e284f8e8e2bb28090617192ca9b4ef3380/toolkit/components/search/SearchEngine.jsm#1138-1177) ${name} only supports int, bool, and string types for
in Firefox's source for available options. We maintain a preferences, but home-manager will automatically
mapping to let you specify all options in the referenced convert all other JSON-compatible values into strings.
link without underscores, but it may fall out of date with
future options.
Note, {var}`icon` is also a special option
added by Home Manager to make it convenient to specify
absolute icon paths.
''; '';
}; };
};
containersForce = mkOption { extraConfig = mkOption {
type = types.bool; type = types.lines;
default = false; default = "";
description = '' description = ''
Whether to force replace the existing containers configuration. Extra preferences to add to {file}`user.js`.
This is recommended since Firefox will replace the symlink on '';
every launch, but note that you'll lose any existing configuration };
by enabling this.
'';
};
containers = mkOption { userChrome = mkOption {
type = types.attrsOf (types.submodule ({ name, ... }: { type = types.lines;
options = { default = "";
name = mkOption { description = "Custom ${name} user chrome CSS.";
type = types.str; example = ''
default = name; /* Hide tab bar in FF Quantum */
description = "Container name, e.g., shopping."; @-moz-document url(chrome://browser/content/browser.xul), url(chrome://browser/content/browser.xhtml) {
#TabsToolbar {
visibility: collapse !important;
margin-bottom: 21px !important;
}
#sidebar-box[sidebarcommand="treestyletab_piro_sakura_ne_jp-sidebar-action"] #sidebar-header {
visibility: collapse !important;
}
}
'';
};
userContent = mkOption {
type = types.lines;
default = "";
description = "Custom ${name} user content CSS.";
example = ''
/* Hide scrollbar in FF Quantum */
*{scrollbar-width:none !important}
'';
};
bookmarks = mkOption {
type = let
bookmarkSubmodule = types.submodule ({ config, name, ... }: {
options = {
name = mkOption {
type = types.str;
default = name;
description = "Bookmark name.";
};
tags = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Bookmark tags.";
};
keyword = mkOption {
type = types.nullOr types.str;
default = null;
description = "Bookmark search keyword.";
};
url = mkOption {
type = types.str;
description = "Bookmark url, use %s for search terms.";
};
};
}) // {
description = "bookmark submodule";
}; };
id = mkOption { bookmarkType = types.addCheck bookmarkSubmodule (x: x ? "url");
type = types.ints.unsigned;
default = 0; directoryType = types.submodule ({ config, name, ... }: {
description = '' options = {
Container ID. This should be set to a unique number per container in this profile. name = mkOption {
''; type = types.str;
default = name;
description = "Directory name.";
};
bookmarks = mkOption {
type = types.listOf nodeType;
default = [ ];
description = "Bookmarks within directory.";
};
toolbar = mkOption {
type = types.bool;
default = false;
description = ''
Make this the toolbar directory. Note, this does _not_
mean that this directory will be added to the toolbar,
this directory _is_ the toolbar.
'';
};
};
}) // {
description = "directory submodule";
}; };
# List of colors at nodeType = types.either bookmarkType directoryType;
# https://searchfox.org/mozilla-central/rev/5ad226c7379b0564c76dc3b54b44985356f94c5a/toolkit/components/extensions/parent/ext-contextualIdentities.js#32 in with types;
color = mkOption { coercedTo (attrsOf nodeType) attrValues (listOf nodeType);
type = types.enum [ default = [ ];
"blue" example = literalExpression ''
"turquoise" [
"green" {
"yellow" name = "wikipedia";
"orange" tags = [ "wiki" ];
"red" keyword = "wiki";
"pink" url = "https://en.wikipedia.org/wiki/Special:Search?search=%s&go=Go";
"purple" }
"toolbar" {
]; name = "kernel.org";
default = "pink"; url = "https://www.kernel.org";
description = "Container color."; }
}; {
name = "Nix sites";
toolbar = true;
bookmarks = [
{
name = "homepage";
url = "https://nixos.org/";
}
{
name = "wiki";
tags = [ "wiki" "nix" ];
url = "https://wiki.nixos.org/";
}
];
}
]
'';
description = ''
Preloaded bookmarks. Note, this may silently overwrite any
previously existing bookmarks!
'';
};
icon = mkOption { path = mkOption {
type = types.enum [ type = types.str;
"briefcase" default = name;
"cart" description = "Profile path.";
"circle" };
"dollar"
"fence" isDefault = mkOption {
"fingerprint" type = types.bool;
"gift" default = config.id == 0;
"vacation" defaultText = "true if profile ID is 0";
"food" description = "Whether this is a default profile.";
"fruit" };
"pet"
"tree" search = {
"chill" force = mkOption {
]; type = with types; bool;
default = "fruit"; default = false;
description = "Container icon."; description = ''
}; Whether to force replace the existing search
configuration. This is recommended since ${name} will
replace the symlink for the search configuration on every
launch, but note that you'll lose any existing
configuration by enabling this.
'';
}; };
}));
default = { }; default = mkOption {
example = { type = with types; nullOr str;
"shopping" = { default = null;
id = 1; example = "DuckDuckGo";
color = "blue"; description = ''
icon = "cart"; The default search engine used in the address bar and search bar.
'';
}; };
"dangerous" = {
id = 2; privateDefault = mkOption {
color = "red"; type = with types; nullOr str;
icon = "fruit"; default = null;
example = "DuckDuckGo";
description = ''
The default search engine used in the Private Browsing.
'';
};
order = mkOption {
type = with types; uniq (listOf str);
default = [ ];
example = [ "DuckDuckGo" "Google" ];
description = ''
The order the search engines are listed in. Any engines
that aren't included in this list will be listed after
these in an unspecified order.
'';
};
engines = mkOption {
type = with types; attrsOf (attrsOf jsonFormat.type);
default = { };
example = literalExpression ''
{
"Nix Packages" = {
urls = [{
template = "https://search.nixos.org/packages";
params = [
{ name = "type"; value = "packages"; }
{ name = "query"; value = "{searchTerms}"; }
];
}];
icon = "''${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
definedAliases = [ "@np" ];
};
"NixOS Wiki" = {
urls = [{ template = "https://wiki.nixos.org/index.php?search={searchTerms}"; }];
iconUpdateURL = "https://wiki.nixos.org/favicon.png";
updateInterval = 24 * 60 * 60 * 1000; # every day
definedAliases = [ "@nw" ];
};
"Bing".metaData.hidden = true;
"Google".metaData.alias = "@g"; # builtin engines only support specifying one additional alias
}
'';
description = ''
Attribute set of search engine configurations. Engines
that only have {var}`metaData` specified will
be treated as builtin to ${name}.
See [SearchEngine.jsm](https://searchfox.org/mozilla-central/rev/669329e284f8e8e2bb28090617192ca9b4ef3380/toolkit/components/search/SearchEngine.jsm#1138-1177)
in Firefox's source for available options. We maintain a
mapping to let you specify all options in the referenced
link without underscores, but it may fall out of date with
future options.
Note, {var}`icon` is also a special option
added by Home Manager to make it convenient to specify
absolute icon paths.
'';
}; };
}; };
description = ''
Attribute set of container configurations. See containersForce = mkOption {
[Multi-Account type = types.bool;
Containers](https://support.mozilla.org/en-US/kb/containers) default = false;
for more information. description = ''
''; Whether to force replace the existing containers configuration.
This is recommended since Firefox will replace the symlink on
every launch, but note that you'll lose any existing configuration
by enabling this.
'';
};
containers = mkOption {
type = types.attrsOf (types.submodule ({ name, ... }: {
options = {
name = mkOption {
type = types.str;
default = name;
description = "Container name, e.g., shopping.";
};
id = mkOption {
type = types.ints.unsigned;
default = 0;
description = ''
Container ID. This should be set to a unique number per container in this profile.
'';
};
# List of colors at
# https://searchfox.org/mozilla-central/rev/5ad226c7379b0564c76dc3b54b44985356f94c5a/toolkit/components/extensions/parent/ext-contextualIdentities.js#32
color = mkOption {
type = types.enum [
"blue"
"turquoise"
"green"
"yellow"
"orange"
"red"
"pink"
"purple"
"toolbar"
];
default = "pink";
description = "Container color.";
};
icon = mkOption {
type = types.enum [
"briefcase"
"cart"
"circle"
"dollar"
"fence"
"fingerprint"
"gift"
"vacation"
"food"
"fruit"
"pet"
"tree"
"chill"
];
default = "fruit";
description = "Container icon.";
};
};
}));
default = { };
example = {
"shopping" = {
id = 1;
color = "blue";
icon = "cart";
};
"dangerous" = {
id = 2;
color = "red";
icon = "fruit";
};
};
description = ''
Attribute set of container configurations. See
[Multi-Account
Containers](https://support.mozilla.org/en-US/kb/containers)
for more information.
'';
};
extensions = mkOption {
type = types.listOf types.package;
default = [ ];
example = literalExpression ''
with pkgs.nur.repos.rycee.firefox-addons; [
privacy-badger
]
'';
description = ''
List of ${name} add-on packages to install for this profile.
Some pre-packaged add-ons are accessible from the
[Nix User Repository](https://github.com/nix-community/NUR).
Once you have NUR installed run
```console
$ nix-env -f '<nixpkgs>' -qaP -A nur.repos.rycee.firefox-addons
```
to list the available ${name} add-ons.
Note that it is necessary to manually enable these extensions
inside ${name} after the first installation.
To automatically enable extensions add
`"extensions.autoDisableScopes" = 0;`
to
[{option}`${moduleName}.profiles.<profile>.settings`](#opt-${moduleName}.profiles._name_.settings)
'';
};
}; };
extensions = mkOption { config = {
type = types.listOf types.package; settings."toolkit.legacyUserProfileCustomizations.stylesheets" =
default = [ ]; mkIf (profile.userChrome != "" || profile.userContent != "") true;
example = literalExpression ''
with pkgs.nur.repos.rycee.firefox-addons; [
privacy-badger
]
'';
description = ''
List of ${name} add-on packages to install for this profile.
Some pre-packaged add-ons are accessible from the
[Nix User Repository](https://github.com/nix-community/NUR).
Once you have NUR installed run
```console
$ nix-env -f '<nixpkgs>' -qaP -A nur.repos.rycee.firefox-addons
```
to list the available ${name} add-ons.
Note that it is necessary to manually enable these extensions
inside ${name} after the first installation.
To automatically enable extensions add
`"extensions.autoDisableScopes" = 0;`
to
[{option}`${moduleName}.profiles.<profile>.settings`](#opt-${moduleName}.profiles._name_.settings)
'';
}; };
}; }));
}));
default = { }; default = { };
description = "Attribute set of ${name} profiles."; description = "Attribute set of ${name} profiles.";
}; };
@ -801,7 +812,28 @@ in {
} }
(mkNoDuplicateAssertion cfg.profiles "profile") (mkNoDuplicateAssertion cfg.profiles "profile")
] ++ (mapAttrsToList ] ++ (
# Assert "toolkit.legacyUserProfileCustomizations.stylesheets" is enabled if userChrome/userContent is used.
let
assertProfile = userChromeAttr: profile:
profile.${userChromeAttr} != ""
-> (profile.settings."toolkit.legacyUserProfileCustomizations.stylesheets"
!= false);
mkAssertion = userChromeAttr: {
assertion =
all (assertProfile userChromeAttr) (attrValues cfg.profiles);
message = ''
{option}`${userChromeAttr}` won't work with
{option}`settings."toolkit.legacyUserProfileCustomizations.stylesheets"` set to false.
'';
};
in [ (mkAssertion "userChrome") (mkAssertion "userContent") ]
) ++ (mapAttrsToList
(_: profile: mkNoDuplicateAssertion profile.containers "container") (_: profile: mkNoDuplicateAssertion profile.containers "container")
cfg.profiles); cfg.profiles);

View file

@ -1 +1 @@
{"identities":[{"color":"yellow","icon":"circle","name":"shopping","public":true,"userContextId":6},{"accessKey":"","color":"","icon":"","name":"userContextIdInternal.thumbnail","public":false,"userContextId":4294967294},{"accessKey":"","color":"","icon":"","name":"userContextIdInternal.webextStorageLocal","public":false,"userContextId":4294967295}],"lastUserContextId":6,"version":4} {"identities":[{"color":"yellow","icon":"circle","name":"shopping","public":true,"userContextId":0},{"accessKey":"","color":"","icon":"","name":"userContextIdInternal.thumbnail","public":false,"userContextId":4294967294},{"accessKey":"","color":"","icon":"","name":"userContextIdInternal.webextStorageLocal","public":false,"userContextId":4294967295}],"lastUserContextId":0,"version":4}

View file

@ -9,6 +9,9 @@ let
firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath; firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath;
userChromeExample = "#example-user-chrome { display: none; }";
userContentExample = "#example-user-content { display: none; }";
in { in {
imports = [ firefoxMockOverlay ]; imports = [ firefoxMockOverlay ];
@ -154,12 +157,18 @@ in {
id = 5; id = 5;
containers = { containers = {
"shopping" = { "shopping" = {
id = 6;
icon = "circle"; icon = "circle";
color = "yellow"; color = "yellow";
}; };
}; };
}; };
profiles.userChrome = {
id = 6;
userChrome = userChromeExample;
userContent = userContentExample;
};
} // { } // {
nmt.script = '' nmt.script = ''
@ -173,10 +182,6 @@ in {
home-files/${cfg.configPath}/test/user.js \ home-files/${cfg.configPath}/test/user.js \
${./profile-settings-expected-user.js} ${./profile-settings-expected-user.js}
assertFileContent \
home-files/${cfg.configPath}/containers/containers.json \
${./profile-settings-expected-containers.json}
bookmarksUserJs=$(normalizeStorePaths \ bookmarksUserJs=$(normalizeStorePaths \
home-files/${cfg.configPath}/bookmarks/user.js) home-files/${cfg.configPath}/bookmarks/user.js)
@ -210,6 +215,22 @@ in {
assertFirefoxSearchContent \ assertFirefoxSearchContent \
home-files/${cfg.configPath}/searchWithoutDefault/search.json.mozlz4 \ home-files/${cfg.configPath}/searchWithoutDefault/search.json.mozlz4 \
${./profile-settings-expected-search-without-default.json} ${./profile-settings-expected-search-without-default.json}
assertFileContent \
home-files/${cfg.configPath}/containers/containers.json \
${./profile-settings-expected-containers.json}
assertFileContains \
home-files/.mozilla/firefox/userChrome/user.js \
'user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true)'
assertFileContains \
home-files/.mozilla/firefox/userChrome/chrome/userChrome.css \
'${userChromeExample}'
assertFileContains \
home-files/.mozilla/firefox/userChrome/chrome/userContent.css \
'${userContentExample}'
''; '';
}); });
} }