diff --git a/modules/programs/firefox.nix b/modules/programs/firefox.nix index 2a2667c35..853c3385b 100644 --- a/modules/programs/firefox.nix +++ b/modules/programs/firefox.nix @@ -1,15 +1,11 @@ { lib, ... }: - with lib; - let - modulePath = [ "programs" "firefox" ]; moduleName = concatStringsSep "." modulePath; mkFirefoxModule = import ./firefox/mkFirefoxModule.nix; - in { meta.maintainers = [ maintainers.rycee hm.maintainers.bricked ]; @@ -32,14 +28,13 @@ in { }) (mkRemovedOptionModule (modulePath ++ [ "extensions" ]) '' - Extensions are now managed per-profile. That is, change from ${moduleName}.extensions = [ foo bar ]; to - ${moduleName}.profiles.myprofile.extensions = [ foo bar ];'') + ${moduleName}.profiles.myprofile.extensions.packages = [ foo bar ];'') (mkRemovedOptionModule (modulePath ++ [ "enableAdobeFlash" ]) "Support for this option has been removed.") (mkRemovedOptionModule (modulePath ++ [ "enableGoogleTalk" ]) diff --git a/modules/programs/firefox/mkFirefoxModule.nix b/modules/programs/firefox/mkFirefoxModule.nix index 76954dd9c..115af89d9 100644 --- a/modules/programs/firefox/mkFirefoxModule.nix +++ b/modules/programs/firefox/mkFirefoxModule.nix @@ -1,13 +1,9 @@ { modulePath, name, description ? null, wrappedPackageName ? null , unwrappedPackageName ? null, platforms, visible ? false -, enableBookmarks ? true }: - +, enableBookmarks ? true, }: { config, lib, pkgs, ... }: - with lib; - let - inherit (pkgs.stdenv.hostPlatform) isDarwin; appName = name; @@ -77,11 +73,13 @@ let else builtins.toJSON pref); - mkUserJs = prePrefs: prefs: extraPrefs: bookmarks: + mkUserJs = prePrefs: prefs: extraPrefs: bookmarks: extensions: let prefs' = lib.optionalAttrs ([ ] != bookmarks) { "browser.bookmarks.file" = toString (browserBookmarksFile bookmarks); "browser.places.importBookmarksHTML" = true; + } // lib.optionalAttrs (extensions != { }) { + "extensions.webextensions.ExtensionStorageIDB.enabled" = false; } // prefs; in '' // Generated by Home Manager. @@ -218,7 +216,6 @@ let # The configuration expected by the Firefox wrapper builder. bcfg = setAttrByPath [ browserName ] fcfg; - in if package == null then null else if isDarwin then @@ -230,7 +227,6 @@ let }) else (pkgs.wrapFirefox.override { config = bcfg; }) package { }; - in { options = setAttrByPath modulePath { enable = mkOption { @@ -242,7 +238,7 @@ in { optionalString (description != null) " ${description}" } ${optionalString (!visible) - "See `programs.firefox` for more configuration options."} + "See `${moduleName}` for more configuration options."} ''; }; @@ -667,37 +663,114 @@ in { for more information. ''; }; - extensions = mkOption { - type = types.listOf types.package; - default = [ ]; - example = literalExpression '' - with pkgs.nur.repos.rycee.firefox-addons; [ - privacy-badger - ] - ''; + type = types.coercedTo (types.listOf types.package) (packages: { + packages = mkIf (builtins.length packages > 0) (warn '' + In order to support declarative extension configuration, + extension installation has been moved from + ${moduleName}.profiles..extensions + to + ${moduleName}.profiles..extensions.packages + '' packages); + }) (types.submodule { + options = { + packages = 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. + Once you have NUR installed run + + ```console + $ nix-env -f '' -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..settings`](#opt-${moduleName}.profiles._name_.settings) + ''; + }; + + settings = mkOption { + default = { }; + example = literalExpression '' + { + # Example with uBlock origin's extensionID + "uBlock0@raymondhill.net".settings = { + selectedFilterLists = [ + "ublock-filters" + "ublock-badware" + "ublock-privacy" + "ublock-unbreak" + "ublock-quick-fixes" + ]; + }; + + # Example with Stylus' UUID-form extensionID + "{7a7a4a92-a2a0-41d1-9fd7-1e92480d612d}".settings = { + dbInChromeStorage = true; # required for Stylus + } + } + ''; + description = '' + Attribute set of options for each extension. + The keys of the attribute set consist of the ID of the extension + or its UUID wrapped in curly braces. + ''; + type = types.attrsOf (types.submodule { + options = { + settings = mkOption { + type = types.attrsOf jsonFormat.type; + description = + "Json formatted options for the specified extensionID"; + }; + force = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Forcibly override any existing configuration for + this extension. + ''; + }; + }; + }); + }; + }; + }); + default = { }; description = '' - List of ${appName} 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 '' -qaP -A nur.repos.rycee.firefox-addons - ``` - - to list the available ${appName} add-ons. - - Note that it is necessary to manually enable these extensions - inside ${appName} after the first installation. - - To automatically enable extensions add - `"extensions.autoDisableScopes" = 0;` - to - [{option}`${moduleName}.profiles..settings`](#opt-${moduleName}.profiles._name_.settings) + Submodule for installing and configuring extensions. + ''; + example = literalExpression '' + { + packages = with pkgs.nur.repos.rycee.firefox-addons; [ + ublock-origin + ]; + settings."uBlock0@raymondhill.net".settings = { + selectedFilterLists = [ + "ublock-filters" + "ublock-badware" + "ublock-privacy" + "ublock-unbreak" + "ublock-quick-fixes" + ]; + }; + } ''; }; - }; })); default = { }; @@ -748,7 +821,7 @@ in { { assertion = cfg.languagePacks == [ ] || cfg.package != null; message = '' - 'programs.firefox.languagePacks' requires 'programs.firefox.package' + '${moduleName}.languagePacks' requires '${moduleName}.package' to be set to a non-null value. ''; } @@ -776,47 +849,59 @@ in { "${nativeMessagingHostsJoined}/lib/mozilla/native-messaging-hosts"; recursive = true; }; - } ++ flip mapAttrsToList cfg.profiles (_: profile: { - "${profilesPath}/${profile.path}/.keep".text = ""; + } ++ flip mapAttrsToList cfg.profiles (_: profile: + # Merge the regular profile settings with extension settings + mkMerge ([{ + "${profilesPath}/${profile.path}/.keep".text = ""; - "${profilesPath}/${profile.path}/chrome/userChrome.css" = - mkIf (profile.userChrome != "") { text = profile.userChrome; }; + "${profilesPath}/${profile.path}/chrome/userChrome.css" = + mkIf (profile.userChrome != "") { text = profile.userChrome; }; - "${profilesPath}/${profile.path}/chrome/userContent.css" = - mkIf (profile.userContent != "") { text = profile.userContent; }; + "${profilesPath}/${profile.path}/chrome/userContent.css" = + mkIf (profile.userContent != "") { text = profile.userContent; }; - "${profilesPath}/${profile.path}/user.js" = mkIf (profile.preConfig != "" - || profile.settings != { } || profile.extraConfig != "" - || profile.bookmarks != [ ]) { - text = mkUserJs profile.preConfig profile.settings profile.extraConfig - profile.bookmarks; - }; + "${profilesPath}/${profile.path}/user.js" = mkIf (profile.preConfig + != "" || profile.settings != { } || profile.extraConfig != "" + || profile.bookmarks != [ ]) { + text = + mkUserJs profile.preConfig profile.settings profile.extraConfig + profile.bookmarks profile.extensions.settings; + }; - "${profilesPath}/${profile.path}/containers.json" = - mkIf (profile.containers != { }) { - text = mkContainersJson profile.containers; - force = profile.containersForce; - }; + "${profilesPath}/${profile.path}/containers.json" = + mkIf (profile.containers != { }) { + text = mkContainersJson profile.containers; + force = profile.containersForce; + }; - "${profilesPath}/${profile.path}/search.json.mozlz4" = - mkIf (profile.search.enable) { - enable = profile.search.enable; - force = profile.search.force; - source = profile.search.file; - }; + "${profilesPath}/${profile.path}/search.json.mozlz4" = + mkIf (profile.search.enable) { + enable = profile.search.enable; + force = profile.search.force; + source = profile.search.file; + }; - "${profilesPath}/${profile.path}/extensions" = - mkIf (profile.extensions != [ ]) { - source = let - extensionsEnvPkg = pkgs.buildEnv { - name = "hm-firefox-extensions"; - paths = profile.extensions; - }; - in "${extensionsEnvPkg}/share/mozilla/${extensionPath}"; - recursive = true; - force = true; - }; - })); + "${profilesPath}/${profile.path}/extensions" = + mkIf (profile.extensions.packages != [ ]) { + source = let + extensionsEnvPkg = pkgs.buildEnv { + name = "hm-firefox-extensions"; + paths = profile.extensions.packages; + }; + in "${extensionsEnvPkg}/share/mozilla/${extensionPath}"; + recursive = true; + force = true; + }; + }] ++ + # Add extension settings as separate attributes + optional (profile.extensions.settings != { }) (mkMerge (mapAttrsToList + (name: settingConfig: { + "${profilesPath}/${profile.path}/browser-extension-data/${name}/storage.js" = + { + force = settingConfig.force; + text = generators.toJSON { } settingConfig.settings; + }; + }) profile.extensions.settings))))); } // setAttrByPath modulePath { finalPackage = wrapPackage cfg.package; diff --git a/tests/modules/programs/firefox/profiles/extensions/default.nix b/tests/modules/programs/firefox/profiles/extensions/default.nix new file mode 100644 index 000000000..577518390 --- /dev/null +++ b/tests/modules/programs/firefox/profiles/extensions/default.nix @@ -0,0 +1,31 @@ +modulePath: +{ config, lib, pkgs, ... }: +with lib; +let + cfg = getAttrFromPath modulePath config; + + firefoxMockOverlay = import ../../setup-firefox-mock-overlay.nix modulePath; +in { + imports = [ firefoxMockOverlay ]; + + config = mkIf config.test.enableBig (setAttrByPath modulePath { + enable = true; + profiles.extensions = { + extensions.settings."uBlock0@raymondhill.net".settings = { + selectedFilterLists = [ + "ublock-filters" + "ublock-badware" + "ublock-privacy" + "ublock-unbreak" + "ublock-quick-fixes" + ]; + }; + }; + } // { + nmt.script = '' + assertFileContent \ + home-files/${cfg.configPath}/extensions/uBlock0@raymondhill.net/storage.js \ + ${./expected-storage.js} + ''; + }); +} diff --git a/tests/modules/programs/firefox/profiles/extensions/expected-storage.js b/tests/modules/programs/firefox/profiles/extensions/expected-storage.js new file mode 100644 index 000000000..66c2a8d0f --- /dev/null +++ b/tests/modules/programs/firefox/profiles/extensions/expected-storage.js @@ -0,0 +1 @@ +{"selectedFilterLists":["ublock-filters","ublock-badware","ublock-privacy","ublock-unbreak","ublock-quick-fixes"]}