Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  AboutPreferences.sys.mjs   Sprache: unbekannt

 
Untersuchungsergebnis.mjs Download desUnknown {[0] [0] [0]}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  actionTypes as at,
  actionCreators as ac,
} from "resource://newtab/common/Actions.mjs";
import {
  WIDGET_REGISTRY,
  isWidgetToggleVisible,
  isWidgetsContainerVisible,
} from "resource://newtab/common/WidgetsRegistry.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
  ExtensionPreferencesManager:
    "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
  ExtensionSettingsStore:
    "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
  HomePage: "resource:///modules/HomePage.sys.mjs",
  Management: "resource://gre/modules/Extension.sys.mjs",
});

const DEFAULT_HOMEPAGE_URL = "about:home";
const BLANK_HOMEPAGE_URL = "chrome://browser/content/blanktab.html";
const HOMEPAGE_OVERRIDE_KEY = "homepage_override";
const URL_OVERRIDES_TYPE = "url_overrides";
const NEW_TAB_KEY = "newTabURL";
const PREF_SETTING_TYPE = "prefs";

export const PREFERENCES_LOADED_EVENT = "home-pane-loaded";
export const PREFERENCES_LOADED_EVENT_SUBPANE = "customHomepage-pane-loaded";

// These "section" objects are formatted in a way to be similar to the ones from
// SectionsManager to construct the preferences view.
// @nova-cleanup(remove-conditional): Remove novaEnabled check; hardcode feed: "widgets.weather.enabled" and shouldHidePref using widgets.system.weather.enabled; convert function body back to arrow returning array literal
const PREFS_FOR_SETTINGS = () => {
  const novaEnabled = Services.prefs.getBoolPref(
    "browser.newtabpage.activity-stream.nova.enabled",
    false
  );
  return [
    {
      id: "web-search",
      pref: {
        feed: "showSearch",
        titleString: "home-prefs-search-header",
      },
    },
    {
      id: "weather",
      pref: {
        feed: novaEnabled ? "widgets.weather.enabled" : "showWeather",
        titleString: "home-prefs-weather-header",
        descString: "home-prefs-weather-description",
        learnMore: {
          link: {
            href: "https://support.mozilla.org/kb/customize-items-on-firefox-new-tab-page",
            id: "home-prefs-weather-learn-more-link",
          },
        },
      },
      eventSource: "WEATHER",
      shouldHidePref: !Services.prefs.getBoolPref(
        novaEnabled
          ? "browser.newtabpage.activity-stream.widgets.system.weather.enabled"
          : "browser.newtabpage.activity-stream.system.showWeather",
        false
      ),
    },
    {
      id: "topsites",
      pref: {
        feed: "feeds.topsites",
        titleString: "home-prefs-shortcuts-header",
        descString: "home-prefs-shortcuts-description",
      },
      maxRows: 4,
      rowsPref: "topSitesRows",
      eventSource: "TOP_SITES",
    },
    {
      id: "topstories",
      pref: {
        feed: "feeds.section.topstories",
        titleString: {
          id: "home-prefs-recommended-by-header-generic",
        },
        descString: {
          id: "home-prefs-recommended-by-description-generic",
        },
      },
      shouldHidePref: !Services.prefs.getBoolPref(
        "browser.newtabpage.activity-stream.feeds.system.topstories",
        true
      ),
      eventSource: "TOP_STORIES",
    },
    {
      id: "support-firefox",
      pref: {
        feed: "showSponsoredCheckboxes",
        titleString: "home-prefs-support-firefox-header",
        nestedPrefs: [
          {
            name: "showSponsoredTopSites",
            titleString: "home-prefs-shortcuts-by-option-sponsored",
            eventSource: "SPONSORED_TOP_SITES",
          },
          {
            name: "showSponsored",
            titleString: "home-prefs-recommended-by-option-sponsored-stories",
            eventSource: "POCKET_SPOCS",
            shouldHidePref: !Services.prefs.getBoolPref(
              "browser.newtabpage.activity-stream.feeds.system.topstories",
              true
            ),
            shouldDisablePref: !Services.prefs.getBoolPref(
              "browser.newtabpage.activity-stream.feeds.section.topstories",
              true
            ),
          },
        ],
      },
    },
  ];
};

/**
 * Queries ExtensionSettingsStore for active extensions of the given type/key
 * and returns dropdown option objects for each.
 *
 * @param {string} type - The setting type (e.g. "prefs" or "url_overrides").
 * @param {string} key - The setting key (e.g. "homepage_override" or "newTabURL").
 * @returns {Promise<Array<{value: string, l10nId: string, l10nArgs: object}>>}
 */
async function getExtensionOptions(type, key) {
  await lazy.ExtensionSettingsStore.initialize();
  let extensionSettings = lazy.ExtensionSettingsStore.getAllSettings(type, key);
  let options = [];
  // Skip extensions that have already been disabled or uninstalled — the
  // store can briefly still list them after the extension has shut down.
  for (let { id } of extensionSettings) {
    let policy = WebExtensionPolicy.getByID(id);
    if (policy) {
      options.push({
        value: policy.id,
        l10nId: "home-prefs-homepage-extension-option",
        l10nArgs: { extension: policy.name },
      });
    }
  }
  return options;
}

function getActiveExtensionForSetting(type, key) {
  try {
    let setting = lazy.ExtensionSettingsStore.getSetting(type, key);
    return setting?.id && WebExtensionPolicy.getByID(setting.id);
  } catch (e) {
    // ExtensionSettingsStore can throw if not yet initialized.
    console.error(e);
    return null;
  }
}

function getHomepageActiveExtension() {
  let ext = getActiveExtensionForSetting(
    PREF_SETTING_TYPE,
    HOMEPAGE_OVERRIDE_KEY
  );
  if (ext) {
    return ext;
  }
  let prefVal = Services.prefs.getStringPref("browser.startup.homepage", "");
  try {
    let uri = Services.io.newURI(prefVal);
    return WebExtensionPolicy.getByURI(uri);
  } catch {
    return null;
  }
}

/**
 * Build an AddonManager listener that runs `refreshFn` for any of the four
 * lifecycle events that affect the dropdown.
 *
 * @param {() => void} refreshFn
 * @returns {object}
 */
function makeAddonListenerForRefresh(refreshFn) {
  return {
    onEnabled: refreshFn,
    onDisabled: refreshFn,
    onInstalled: refreshFn,
    onUninstalled: refreshFn,
  };
}

/**
 * Build a Management "extension-setting-changed" handler that runs `refreshFn`
 * when the changed setting matches the given type and key.
 *
 * @param {string} type
 * @param {string} key
 * @param {() => void} refreshFn
 * @returns {(eventName: string, changedSetting: object) => void}
 */
function makeExtensionSettingChangedListener(type, key, refreshFn) {
  return (_evt, changedSetting) => {
    if (changedSetting.key === key && changedSetting.type === type) {
      refreshFn();
    }
  };
}

/**
 * Force the moz-select value after the DOM has settled. Setting the value
 * in the same tick that the option is added doesn't take effect, so we
 * defer to the next animation frame.
 *
 * @param {Window} window - The preferences window.
 * @param {string} settingId - The setting ID (e.g. "homepageNewWindows").
 * @param {string|null} value - The value to set on the moz-select.
 */
function forceSelectValue(window, settingId, value) {
  if (!value || window.closed) {
    return;
  }
  window.requestAnimationFrame(() => {
    let control = window.document.getElementById(
      `setting-control-${settingId}`
    );
    // Setting may live on the home pane while we're rendered on the
    // customHomepage subpage — silently skip if the control isn't on the
    // active pane.
    if (!control) {
      return;
    }
    control.controlEl.value = value;
  });
}

export class AboutPreferences {
  init() {
    Services.obs.addObserver(this, PREFERENCES_LOADED_EVENT);
    Services.obs.addObserver(this, PREFERENCES_LOADED_EVENT_SUBPANE);
    // Load the extension-settings modules so that "extension-setting-changed"
    // events fire reliably for the listeners registered in setup().
    lazy.Management.asyncLoadSettingsModules();
  }

  uninit() {
    Services.obs.removeObserver(this, PREFERENCES_LOADED_EVENT);
    Services.obs.removeObserver(this, PREFERENCES_LOADED_EVENT_SUBPANE);
  }

  onAction(action) {
    switch (action.type) {
      case at.INIT:
        this.init();
        break;
      case at.UNINIT:
        this.uninit();
        break;
      case at.SETTINGS_OPEN:
        action._target.window.openPreferences("paneHome");
        break;
      // This is used to open the web extension settings page for an extension
      case at.OPEN_WEBEXT_SETTINGS:
        action._target.window.BrowserAddonUI.openAddonsMgr(
          `addons://detail/${encodeURIComponent(action.data)}`
        );
        break;
    }
  }

  setupUserEvent(element, eventSource) {
    element.addEventListener("command", e => {
      const { checked } = e.target;
      if (typeof checked === "boolean") {
        this.store.dispatch(
          ac.UserEvent({
            event: "PREF_CHANGED",
            source: eventSource,
            value: { status: checked, menu_source: "ABOUT_PREFERENCES" },
          })
        );
      }
    });
  }

  observe(window) {
    if (Services.prefs.getBoolPref("browser.settings-redesign.enabled")) {
      const { SettingGroupManager } = window;

      window.MozXULElement.insertFTLIfNeeded("browser/newtab/newtab.ftl");

      // We observe 2 signals that about:settings is loading - the
      // PREFERENCES_LOADED_EVENT and PREFERENCES_LOADED_EVENT_SUBPANE
      // observer notifications. The first is fired anytime about:settings
      // is loaded directly. The second (and not the first) fires if loading
      // about:preferences#customHomepage. We handle those cases by observing
      // both, and checking to see if the "homepage" settings group was already
      // registered. If so, we take that as a sign that we don't need to
      // re-register and then we bail out.
      try {
        if (SettingGroupManager.get("homepage")) {
          // The homepage group has already been registered for this load of
          // about:settings, so no need to do it again. Bail out.
          return;
        }
      } catch (e) {
        // We didn't find the homepage settings group registered. That's okay,
        // we'll register the group(s) now - that's what we're here for.
      }

      this._registerPreferences(window);

      SettingGroupManager.registerGroups({
        homepage: this._setupHomepageGroup(window),
        customHomepage: this._setupCustomHomepageGroup(window),
        home: this._setupHomeGroup(window),
      });
      return;
    }

    // Legacy settings UI
    const { document, Preferences } = window;

    // Extract just the "Recent activity" pref info from SectionsManager as we have everything else already
    const highlights = this.store
      .getState()
      .Sections.find(el => el.id === "highlights");

    const allSections = [...PREFS_FOR_SETTINGS(), highlights];

    // Render the preferences
    allSections.forEach(pref => {
      this.renderPreferenceSection(pref, document, Preferences);
    });

    // Update the visibility of the Restore Defaults button based on checked prefs
    this.toggleRestoreDefaults(window.gHomePane);
  }

  /** @param {Window} window */
  _registerPreferences(window) {
    const { Preferences } = window;

    Preferences.addAll([
      { id: "browser.newtabpage.activity-stream.showSearch", type: "bool" },
      {
        id: "browser.newtabpage.activity-stream.hideLogo",
        type: "bool",
        inverted: true,
      },
      {
        id: "browser.newtabpage.activity-stream.system.showWeather",
        type: "bool",
      },
      { id: "browser.newtabpage.activity-stream.showWeather", type: "bool" },
      {
        id: "browser.newtabpage.activity-stream.widgets.system.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.system.weather.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.weather.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.system.lists.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.lists.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.system.focusTimer.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.focusTimer.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.system.sportsWidget.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.sportsWidget.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.system.clocks.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.widgets.clocks.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.feeds.topsites",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.topSitesRows",
        type: "int",
      },
      {
        id: "browser.newtabpage.activity-stream.feeds.system.topstories",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.feeds.section.topstories",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.discoverystream.sections.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.discoverystream.topicLabels.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.discoverystream.sections.personalization.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.discoverystream.sections.customizeMenuPanel.enabled",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.showSponsoredCheckboxes",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.showSponsoredTopSites",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.showSponsored",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.feeds.section.highlights",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.section.highlights.rows",
        type: "int",
      },
      {
        id: "browser.newtabpage.activity-stream.section.highlights.includeVisited",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.section.highlights.includeBookmarks",
        type: "bool",
      },
      {
        id: "browser.newtabpage.activity-stream.section.highlights.includeDownloads",
        type: "bool",
      },
    ]);
  }

  /** @param {Window} window */
  _setupHomepageGroup(window) {
    const { Preferences } = window;

    // Set up `browser.startup.homepage` to show homepage options for Homepage / New Windows
    let homepageExtOptions = [];
    Preferences.addSetting(
      /** @type {{ useCustomHomepage: boolean } & SettingConfig } */ ({
        id: "homepageNewWindows",
        pref: "browser.startup.homepage",
        useCustomHomepage: false,
        setup(onChange) {
          let refreshExtensions = async () => {
            homepageExtOptions = await getExtensionOptions(
              PREF_SETTING_TYPE,
              HOMEPAGE_OVERRIDE_KEY
            );
            if (!window.closed) {
              onChange();
              let ext = getHomepageActiveExtension();
              forceSelectValue(window, "homepageNewWindows", ext?.id);
            }
          };

          refreshExtensions().catch(e =>
            console.error("Failed to refresh homepage extensions", e)
          );

          // Refresh whenever the homepage pref changes — covers third-party
          // writes (enterprise policy, manual edits) that bypass the
          // extension-setting-changed event.
          let homepagePrefObserver = () => {
            onChange();
            let ext = getHomepageActiveExtension();
            forceSelectValue(window, "homepageNewWindows", ext?.id);
          };
          Services.prefs.addObserver(
            "browser.startup.homepage",
            homepagePrefObserver
          );

          let onExtensionChange = makeExtensionSettingChangedListener(
            PREF_SETTING_TYPE,
            HOMEPAGE_OVERRIDE_KEY,
            refreshExtensions
          );
          lazy.Management.on("extension-setting-changed", onExtensionChange);

          let addonListener = makeAddonListenerForRefresh(refreshExtensions);
          lazy.AddonManager.addAddonListener(addonListener);

          return () => {
            lazy.AddonManager.removeAddonListener(addonListener);
            lazy.Management.off("extension-setting-changed", onExtensionChange);
            Services.prefs.removeObserver(
              "browser.startup.homepage",
              homepagePrefObserver
            );
          };
        },
        get(prefVal) {
          if (this.useCustomHomepage) {
            return "custom";
          }
          let ext = getHomepageActiveExtension();
          if (ext) {
            return ext.id;
          }
          switch (prefVal) {
            case DEFAULT_HOMEPAGE_URL:
              return "home";
            case BLANK_HOMEPAGE_URL:
              return "blank";
            // Custom value can be any string so leaving it as default value to catch
            // non-default/blank entries.
            default:
              return "custom";
          }
        },
        set(inputVal, _, setting) {
          let wasCustomHomepage = this.useCustomHomepage;
          this.useCustomHomepage = inputVal === "custom";
          if (wasCustomHomepage !== this.useCustomHomepage) {
            setting.onChange();
          }

          // Deselection uses the low-level ExtensionSettingsStore API
          // because the pref is already being set by the return value.
          if (["home", "blank", "custom"].includes(inputVal)) {
            let currentAddon = getActiveExtensionForSetting(
              PREF_SETTING_TYPE,
              HOMEPAGE_OVERRIDE_KEY
            );
            if (currentAddon) {
              try {
                lazy.ExtensionSettingsStore.select(
                  null,
                  PREF_SETTING_TYPE,
                  HOMEPAGE_OVERRIDE_KEY
                );
              } catch (e) {
                console.error("Failed to deselect homepage extension", e);
              }
            }
          }

          switch (inputVal) {
            case "home":
              return DEFAULT_HOMEPAGE_URL;
            case "blank":
              return BLANK_HOMEPAGE_URL;
            case "custom":
              return setting.pref.value;
            default:
              // Selection uses ExtensionPreferencesManager.selectSetting,
              // which also applies the extension's pref value.
              lazy.ExtensionPreferencesManager.selectSetting(
                inputVal,
                HOMEPAGE_OVERRIDE_KEY
              ).catch(e =>
                console.error("Failed to select homepage extension", e)
              );
              return setting.pref.value;
          }
        },
        getControlConfig(config) {
          // `config` is retained across renders, so filter back to the
          // static builtins before reattaching the current extension entries.
          let builtinValues = new Set(["home", "blank", "custom"]);
          let builtinOptions = config.options.filter(o =>
            builtinValues.has(o.value)
          );
          let extOptions = [...homepageExtOptions];
          // Add an option for extensions that set the homepage pref
          // directly without registering in ExtensionSettingsStore.
          let ext = getHomepageActiveExtension();
          if (ext && !extOptions.some(o => o.value === ext.id)) {
            extOptions.push({
              value: ext.id,
              l10nId: "home-prefs-homepage-extension-option",
              l10nArgs: { extension: ext.name },
            });
          }
          return {
            ...config,
            options: [...builtinOptions, ...extOptions],
          };
        },
      })
    );

    // Set up `browser.startup.homepage` again to update and display its value
    // on the Homepage and Custom Homepage settings panes.
    Preferences.addSetting({
      id: "homepageDisplayPref",
      pref: "browser.startup.homepage",
    });

    Preferences.addSetting({
      id: "disableCurrentPagesButton",
      pref: "pref.browser.homepage.disable_button.current_page",
    });

    Preferences.addSetting({
      id: "disableBookmarkButton",
      pref: "pref.browser.homepage.disable_button.bookmark_page",
    });

    // Homepage / Choose Custom Homepage URL Button
    Preferences.addSetting({
      id: "homepageGoToCustomHomepageUrlPanel",
      deps: ["homepageNewWindows", "homepageDisplayPref"],
      visible: ({ homepageNewWindows }) => {
        return homepageNewWindows.value === "custom";
      },
      onUserClick: () => {
        window.gotoPref("customHomepage");
      },

      getControlConfig(config, { homepageDisplayPref }) {
        let customURLsDescription;

        // Make sure we only show user-provided values for custom URLs rather than
        // values we set in `browser.startup.homepage` for "Firefox Home",
        // "Blank Page", or extension-controlled URLs.
        let prefVal = homepageDisplayPref.value.trim();
        if ([DEFAULT_HOMEPAGE_URL, BLANK_HOMEPAGE_URL].includes(prefVal)) {
          customURLsDescription = null;
        } else {
          // Add a comma-separated list of Custom URLs the user set for their homepage
          // to the description part of the "Choose a specific site" box button.
          customURLsDescription = homepageDisplayPref.value
            .split("|")
            .map(uri => lazy.BrowserUtils.formatURIStringForDisplay(uri))
            .filter(Boolean)
            .join(", ");
        }

        return {
          ...config,
          controlAttrs: {
            ...config.controlAttrs,
            ".description": customURLsDescription,
          },
        };
      },
    });

    // Homepage / New Tabs
    let newTabExtOptions = [];
    Preferences.addSetting({
      id: "homepageNewTabs",
      pref: "browser.newtabpage.enabled",
      setup(onChange) {
        let refreshExtensions = async () => {
          newTabExtOptions = await getExtensionOptions(
            URL_OVERRIDES_TYPE,
            NEW_TAB_KEY
          );
          if (!window.closed) {
            onChange();
            let activeId = getActiveExtensionForSetting(
              URL_OVERRIDES_TYPE,
              NEW_TAB_KEY
            )?.id;
            forceSelectValue(window, "homepageNewTabs", activeId);
          }
        };

        refreshExtensions().catch(e =>
          console.error("Failed to refresh new tab extensions", e)
        );

        let onExtensionChange = makeExtensionSettingChangedListener(
          URL_OVERRIDES_TYPE,
          NEW_TAB_KEY,
          refreshExtensions
        );
        lazy.Management.on("extension-setting-changed", onExtensionChange);

        // Pick up extension installs that set AboutNewTab.newTabURL directly.
        let newTabObserver = () => refreshExtensions();
        Services.obs.addObserver(newTabObserver, "newtab-url-changed");

        let addonListener = makeAddonListenerForRefresh(refreshExtensions);
        lazy.AddonManager.addAddonListener(addonListener);

        return () => {
          lazy.AddonManager.removeAddonListener(addonListener);
          Services.obs.removeObserver(newTabObserver, "newtab-url-changed");
          lazy.Management.off("extension-setting-changed", onExtensionChange);
        };
      },
      get(prefVal) {
        // No URL-based fallback — new tab extensions always register
        // through ExtensionSettingsStore.
        let activeId = getActiveExtensionForSetting(
          URL_OVERRIDES_TYPE,
          NEW_TAB_KEY
        )?.id;
        if (activeId) {
          return activeId;
        }
        return prefVal ? "home" : "blank";
      },
      set(inputVal) {
        if (inputVal === "home" || inputVal === "blank") {
          let currentAddon = getActiveExtensionForSetting(
            URL_OVERRIDES_TYPE,
            NEW_TAB_KEY
          );
          if (currentAddon) {
            try {
              // Deselecting via the low-level API is sufficient here;
              // the url_overrides machinery listens for this and resets
              // AboutNewTab.newTabURL.
              lazy.ExtensionSettingsStore.select(
                null,
                URL_OVERRIDES_TYPE,
                NEW_TAB_KEY
              );
            } catch (e) {
              console.error("Failed to deselect new tab extension", e);
            }
          }
          return inputVal === "home";
        }
        try {
          lazy.ExtensionSettingsStore.select(
            inputVal,
            URL_OVERRIDES_TYPE,
            NEW_TAB_KEY
          );
        } catch (e) {
          console.error("Failed to select new tab extension", e);
        }
        return true;
      },
      getControlConfig(config) {
        // `config` is retained across renders, so filter back to the
        // static builtins before reattaching the current extension entries.
        let builtinValues = new Set(["home", "blank"]);
        let builtinOptions = config.options.filter(o =>
          builtinValues.has(o.value)
        );
        return {
          ...config,
          options: [...builtinOptions, ...newTabExtOptions],
        };
      },
    });

    // Homepage / Restore Defaults button
    Preferences.addSetting({
      id: "homepageRestoreDefaults",
      pref: "pref.browser.homepage.disable_button.restore_default",
      deps: ["homepageNewWindows", "homepageNewTabs"],
      disabled: ({ homepageNewWindows, homepageNewTabs }) => {
        return (
          homepageNewWindows.value === "home" &&
          homepageNewTabs.value === "home"
        );
      },
      onUserClick: (e, { homepageNewWindows, homepageNewTabs }) => {
        e.preventDefault();

        homepageNewWindows.value = "home";
        homepageNewTabs.value = "home";
      },
    });

    return {
      inProgress: true,
      headingLevel: 2,
      iconSrc: "chrome://browser/skin/window-firefox.svg",
      l10nId: "home-homepage-title",
      subcategory: "homepage",
      items: [
        {
          id: "homepageNewWindows",
          subcategory: "homeOverride",
          control: "moz-select",
          l10nId: "home-homepage-new-windows",
          options: [
            {
              value: "home",
              l10nId: "home-mode-choice-default-fx-srd",
            },
            { value: "blank", l10nId: "home-mode-choice-blank-srd" },
            { value: "custom", l10nId: "home-mode-choice-custom-srd" },
          ],
        },
        {
          id: "homepageGoToCustomHomepageUrlPanel",
          control: "moz-box-button",
          l10nId: "home-homepage-custom-homepage-button",
          loadPane: "customHomepage",
        },
        {
          id: "homepageNewTabs",
          subcategory: "newtabOverride",
          control: "moz-select",
          l10nId: "home-homepage-new-tabs",
          options: [
            {
              value: "home",
              l10nId: "home-mode-choice-default-fx-srd",
            },
            { value: "blank", l10nId: "home-mode-choice-blank-srd" },
          ],
        },
        {
          id: "homepageRestoreDefaults",
          control: "moz-button",
          iconSrc: "chrome://global/skin/icons/arrow-counterclockwise-16.svg",
          l10nId: "home-restore-defaults-srd",
          controlAttrs: { id: "restoreDefaultHomePageBtn" },
        },
      ],
    };
  }

  /** @param {Window} window */
  _setupCustomHomepageGroup(window) {
    const { Preferences } = window;

    Preferences.addSetting(
      /** @type {{ _inputValue: string } & SettingConfig } */ ({
        id: "customHomepageAddUrlInput",
        deps: ["homepageDisplayPref"],
        _inputValue: "",
        get() {
          return this._inputValue;
        },
        set(val, _, setting) {
          this._inputValue = val.trim();
          setting.onChange();
        },
        disabled({ homepageDisplayPref }) {
          return homepageDisplayPref.locked;
        },
      })
    );

    Preferences.addSetting({
      id: "customHomepageAddAddressButton",
      deps: ["homepageDisplayPref", "customHomepageAddUrlInput"],
      onUserClick(e, { homepageDisplayPref, customHomepageAddUrlInput }) {
        // Focus is being stolen by a parent component here (moz-fieldset).
        // Focus on the button to get the input value.
        e.target.focus();

        let inputVal = customHomepageAddUrlInput.value;

        // Don't do anything for empty strings
        if (!inputVal) {
          return;
        }

        let currentVal = homepageDisplayPref.value.trim();
        if (
          [DEFAULT_HOMEPAGE_URL, BLANK_HOMEPAGE_URL].includes(currentVal) ||
          currentVal.startsWith("moz-extension://")
        ) {
          // Replace non-custom homepage values with the new Custom URL.
          homepageDisplayPref.value = inputVal;
        } else {
          // Append this URL to the list of Custom URLs saved in prefs.
          let urls = lazy.HomePage.parseCustomHomepageURLs(
            homepageDisplayPref.value
          );
          urls.push(inputVal);
          homepageDisplayPref.value = urls.join("|");
        }

        // Reset the field to empty string
        customHomepageAddUrlInput.value = "";
      },
      disabled({ homepageDisplayPref }) {
        return homepageDisplayPref.locked;
      },
    });

    Preferences.addSetting({
      id: "customHomepageReplaceWithCurrentPagesButton",
      deps: ["homepageDisplayPref", "disableCurrentPagesButton"],
      // Re-evaluate disabled state on tab open/close (add/remove tabs) and
      // pin/unpin (changes what getTabsForCustomHomepage() captures).
      setup(emitChange) {
        let win = /** @type {any} */ (
          Services.wm.getMostRecentWindow("navigator:browser")
        );
        if (!win) {
          return () => {};
        }
        const { tabContainer } = win.gBrowser;
        // Best-effort filter: skip events from tabs already showing about:preferences.
        // TabOpen fires before the URI is set, so it isn't caught here;
        // the real exclusion happens inside getTabsForCustomHomepage().
        const onTabChange = (/** @type {Event & { target: any }} */ event) => {
          if (
            event.target.linkedBrowser?.currentURI?.spec?.startsWith(
              "about:preferences"
            )
          ) {
            return;
          }
          emitChange();
        };
        tabContainer.addEventListener("TabOpen", onTabChange);
        tabContainer.addEventListener("TabClose", onTabChange);
        tabContainer.addEventListener("TabPinned", onTabChange);
        tabContainer.addEventListener("TabUnpinned", onTabChange);
        return () => {
          tabContainer.removeEventListener("TabOpen", onTabChange);
          tabContainer.removeEventListener("TabClose", onTabChange);
          tabContainer.removeEventListener("TabPinned", onTabChange);
          tabContainer.removeEventListener("TabUnpinned", onTabChange);
        };
      },
      onUserClick(e, { homepageDisplayPref }) {
        let tabs = lazy.HomePage.getTabsForCustomHomepage();

        if (tabs.length) {
          homepageDisplayPref.value = tabs
            .map(t => t.linkedBrowser.currentURI.spec)
            .join("|");
        }
      },
      disabled: ({ disableCurrentPagesButton, homepageDisplayPref }) =>
        // Disable this button if the only open tab is `about:preferences`/`about:settings`
        // or when an enterprise policy sets a special pref to true
        lazy.HomePage.getTabsForCustomHomepage().length < 1 ||
        disableCurrentPagesButton?.value === true ||
        homepageDisplayPref.locked,
    });

    Preferences.addSetting({
      id: "customHomepageReplaceWithBookmarksButton",
      deps: ["homepageDisplayPref", "disableBookmarkButton"],
      onUserClick(e, { homepageDisplayPref }) {
        const rv = { urls: null, names: null };

        // Callback to use when bookmark dialog closes
        const closingCallback = event => {
          if (event.detail.button !== "accept") {
            return;
          }
          if (rv.urls) {
            homepageDisplayPref.value = rv.urls.join("|");
          }
        };

        window.gSubDialog.open(
          "chrome://browser/content/preferences/dialogs/selectBookmark.xhtml",
          {
            features: "resizable=yes, modal=yes",
            closingCallback,
          },
          rv
        );
      },
      disabled: ({ disableBookmarkButton, homepageDisplayPref }) =>
        // Disable this button if an enterprise policy sets a special pref to true
        disableBookmarkButton?.value === true || homepageDisplayPref.locked,
    });

    Preferences.addSetting({
      id: "customHomepageBoxGroup",
      deps: ["homepageDisplayPref"],
      setup(onChange) {
        // Refresh the list when an extension's policy registers or
        // unregisters, so an extension URL in the pref renders as
        // "Extension (Name)" once the policy becomes available (and falls
        // back to the raw URL if it goes away).
        let onExtensionChange = makeExtensionSettingChangedListener(
          PREF_SETTING_TYPE,
          HOMEPAGE_OVERRIDE_KEY,
          onChange
        );
        lazy.Management.on("extension-setting-changed", onExtensionChange);

        let addonListener = makeAddonListenerForRefresh(onChange);
        lazy.AddonManager.addAddonListener(addonListener);

        return () => {
          lazy.AddonManager.removeAddonListener(addonListener);
          lazy.Management.off("extension-setting-changed", onExtensionChange);
        };
      },
      getControlConfig(config, { homepageDisplayPref }) {
        const urls = lazy.HomePage.parseCustomHomepageURLs(
          homepageDisplayPref.value
        );
        let listItems = [];
        let type = "list";

        // Show a reorderable list of Custom URLs if the user has provided any.
        // Make sure to exclude "Firefox Home", "Blank Page", and
        // extension-controlled URLs that are also stored in the homepage pref.
        let currentPrefVal = homepageDisplayPref.value.trim();
        if (
          ![DEFAULT_HOMEPAGE_URL, BLANK_HOMEPAGE_URL].includes(currentPrefVal)
        ) {
          type = homepageDisplayPref.locked ? "list" : "reorderable-list";
          listItems = urls.map((url, index) => ({
            id: `customHomepageUrl-${index}`,
            key: `url-${index}-${url}`,
            control: "moz-box-item",
            controlAttrs: {
              label: lazy.BrowserUtils.formatURIStringForDisplay(url),
              "data-url": url,
            },
            options: homepageDisplayPref.locked
              ? []
              : [
                  {
                    control: "moz-button",
                    iconSrc: "chrome://global/skin/icons/delete.svg",
                    l10nId: "home-custom-homepage-delete-address-button",
                    slot: "actions-start",
                    controlAttrs: {
                      "data-action": "delete",
                      "data-index": index,
                    },
                  },
                ],
          }));
        } else {
          // If no custom URLs have been set, show the "no results" string instead.
          listItems = [
            {
              control: "moz-box-item",
              l10nId: "home-custom-homepage-no-results",
              controlAttrs: {
                class: "description-deemphasized",
              },
            },
          ];
        }

        return {
          ...config,
          controlAttrs: {
            ...config.controlAttrs,
            type,
          },
          options: [
            {
              id: "customHomepageBoxForm",
              control: "moz-box-item",
              slot: "header",
              items: [
                {
                  id: "customHomepageAddUrlInput",
                  l10nId: "home-custom-homepage-address",
                  control: "moz-input-text",
                },
                {
                  id: "customHomepageAddAddressButton",
                  l10nId: "home-custom-homepage-address-button",
                  control: "moz-button",
                  slot: "actions",
                },
              ],
            },
            ...listItems,
            {
              id: "customHomepageBoxActions",
              control: "moz-box-item",
              l10nId: "home-custom-homepage-replace-with-prompt",
              slot: "footer",
              items: [
                {
                  id: "customHomepageReplaceWithCurrentPagesButton",
                  l10nId: "home-custom-homepage-current-pages-button",
                  control: "moz-button",
                  slot: "actions",
                },
                {
                  id: "customHomepageReplaceWithBookmarksButton",
                  l10nId: "home-custom-homepage-bookmarks-button",
                  control: "moz-button",
                  slot: "actions",
                },
              ],
            },
          ],
        };
      },
      onUserReorder(e, { homepageDisplayPref }) {
        let urls = lazy.HomePage.parseCustomHomepageURLs(
          homepageDisplayPref.value
        );
        urls = e.target.reorderArrayFromEvent(urls, e);
        homepageDisplayPref.value = urls.join("|");
      },
      onUserClick(e, { homepageDisplayPref }) {
        let urls = lazy.HomePage.parseCustomHomepageURLs(
          homepageDisplayPref.value
        );

        if (
          e.target.localName === "moz-button" &&
          e.target.getAttribute("data-action") === "delete"
        ) {
          let index = Number(e.target.dataset.index);
          if (Number.isInteger(index) && index >= 0 && index < urls.length) {
            urls.splice(index, 1);
            homepageDisplayPref.value = urls.join("|");
          }
        }
      },
    });

    return {
      inProgress: true,
      headingLevel: 2,
      l10nId: "home-custom-homepage-card-header",
      iconSrc: "chrome://global/skin/icons/link.svg",
      items: [
        {
          id: "customHomepageBoxGroup",
          control: "moz-box-group",
          controlAttrs: {
            type: "list",
          },
        },
      ],
    };
  }

  /** @param {Window} window */
  // eslint-disable-next-line max-statements
  _setupHomeGroup(window) {
    const { Preferences } = window;

    // A widget toggle is shown when its system pref is on OR a trainhop (Nimbus)
    // config enables it. The system-pref half reads the live dep value so the
    // toggle reacts to pref changes without a page refresh; the trainhopConfig
    // half is a snapshot (Nimbus sets it at load, it doesn't change live). The
    // dep id matches the registry trainhopEnabledKey by convention.
    const widgetPrefs = this.store.getState()?.Prefs?.values ?? {};
    const widgetToggleVisible = registryId => {
      const widget = WIDGET_REGISTRY.find(w => w.id === registryId);
      // Resolve via the shared registry helper, but feed the LIVE system-pref
      // value from deps so the toggle still reacts to about:config changes
      // without a page refresh; the trainhop/widgetsSettings terms are a snapshot.
      return deps =>
        isWidgetToggleVisible(widget, {
          ...widgetPrefs,
          [widget.systemEnabledPref]: deps[widget.trainhopEnabledKey]?.value,
        });
    };

    // Build-time snapshot of whether the Widgets container is shown, used only
    // to decide Weather's placement in the items list below. The Widgets group's
    // own visibility is resolved reactively inline.
    const widgetsSystemEnabled = isWidgetsContainerVisible(widgetPrefs);

    // The Firefox Home section should be disabled when neither "New windows"
    // nor "New tabs" is set to Firefox Home.
    const firefoxHomeDeps = ["homepageNewWindows", "homepageNewTabs"];
    const firefoxHomeActive = ({ homepageNewWindows, homepageNewTabs }) =>
      homepageNewWindows.value === "home" || homepageNewTabs.value === "home";

    const HOME_CUSTOMIZE_URL = "about:home#customize";
    const HOME_CUSTOMIZE_TOPICS_URL = "about:home#customize-topics";

    // Open in a new tab if "New tabs" is Firefox Home, else a new window.
    const dispatchForHomeLink = ({ homepageNewTabs }) =>
      homepageNewTabs.value === "home" ? "tab" : "window";

    Preferences.addSetting({
      id: "firefoxHomeDisabledNotice",
      deps: firefoxHomeDeps,
      visible: deps => !firefoxHomeActive(deps),
    });

    // @nova-cleanup(remove-conditional): Remove this lookup and inline `true` at every novaEnabled check below.
    const novaEnabled = Services.prefs.getBoolPref(
      "browser.newtabpage.activity-stream.nova.enabled",
      false
    );

    // hideLogo only affects rendering when Nova is enabled (see Base.jsx),
    // so the toggle is registered only in that branch.
    if (novaEnabled) {
      Preferences.addSetting({
        id: "firefoxLogo",
        pref: "browser.newtabpage.activity-stream.hideLogo",
        deps: firefoxHomeDeps,
        disabled: deps => !firefoxHomeActive(deps),
      });
    }

    // Search
    Preferences.addSetting({
      id: "webSearch",
      pref: "browser.newtabpage.activity-stream.showSearch",
      deps: firefoxHomeDeps,
      disabled: deps => !firefoxHomeActive(deps),
    });

    // Weather
    // @nova-cleanup(remove-conditional): Remove novaEnabled check and else branch; keep only the Nova registration block (weatherEnabled + weather addSetting calls)
    if (novaEnabled) {
      Preferences.addSetting({
        id: "weatherEnabled",
        pref: "browser.newtabpage.activity-stream.widgets.system.weather.enabled",
      });

      Preferences.addSetting({
        id: "weather",
        pref: "browser.newtabpage.activity-stream.widgets.weather.enabled",
        deps: ["weatherEnabled", ...firefoxHomeDeps],
        visible: widgetToggleVisible("weather"),
        disabled: deps => !firefoxHomeActive(deps),
      });
    } else {
      Preferences.addSetting({
        id: "showWeather",
        pref: "browser.newtabpage.activity-stream.system.showWeather",
      });

      Preferences.addSetting({
        id: "weather",
        pref: "browser.newtabpage.activity-stream.showWeather",
        deps: ["showWeather", ...firefoxHomeDeps],
        visible: ({ showWeather }) => showWeather.value,
        disabled: deps => !firefoxHomeActive(deps),
      });
    }

    // Widgets: general
    Preferences.addSetting({
      id: "widgetsEnabled",
      pref: "browser.newtabpage.activity-stream.widgets.system.enabled",
    });

    Preferences.addSetting({
      id: "widgets",
      pref: "browser.newtabpage.activity-stream.widgets.enabled",
      deps: ["widgetsEnabled", ...firefoxHomeDeps],
      visible: ({ widgetsEnabled }) =>
        isWidgetsContainerVisible({
          ...widgetPrefs,
          "widgets.system.enabled": widgetsEnabled.value,
        }),
      disabled: deps => !firefoxHomeActive(deps),
    });

    // Widgets: lists
    Preferences.addSetting({
      id: "listsEnabled",
      pref: "browser.newtabpage.activity-stream.widgets.system.lists.enabled",
    });

    Preferences.addSetting({
      id: "lists",
      pref: "browser.newtabpage.activity-stream.widgets.lists.enabled",
      deps: ["listsEnabled"],
      visible: widgetToggleVisible("lists"),
    });

    // Widgets: timer
    Preferences.addSetting({
      id: "timerEnabled",
      pref: "browser.newtabpage.activity-stream.widgets.system.focusTimer.enabled",
    });

    Preferences.addSetting({
      id: "timer",
      pref: "browser.newtabpage.activity-stream.widgets.focusTimer.enabled",
      deps: ["timerEnabled"],
      visible: widgetToggleVisible("focusTimer"),
    });

    // Widgets: sports
    Preferences.addSetting({
      id: "sportsWidgetEnabled",
      pref: "browser.newtabpage.activity-stream.widgets.system.sportsWidget.enabled",
    });

    Preferences.addSetting({
      id: "sportsWidget",
      pref: "browser.newtabpage.activity-stream.widgets.sportsWidget.enabled",
      deps: ["sportsWidgetEnabled"],
      visible: widgetToggleVisible("sportsWidget"),
    });

    Preferences.addSetting({
      id: "clocksEnabled",
      pref: "browser.newtabpage.activity-stream.widgets.system.clocks.enabled",
    });

    Preferences.addSetting({
      id: "clocks",
      pref: "browser.newtabpage.activity-stream.widgets.clocks.enabled",
      deps: ["clocksEnabled"],
      visible: widgetToggleVisible("clocks"),
    });

    // Shortcuts
    Preferences.addSetting({
      id: "shortcuts",
      pref: "browser.newtabpage.activity-stream.feeds.topsites",
      deps: firefoxHomeDeps,
      disabled: deps => !firefoxHomeActive(deps),
    });
    Preferences.addSetting({
      id: "shortcutsRows",
      pref: "browser.newtabpage.activity-stream.topSitesRows",
    });

    // Dependency prefs for stories & sponsored stories visibility
    Preferences.addSetting({
      id: "systemTopstories",
      pref: "browser.newtabpage.activity-stream.feeds.system.topstories",
    });

    // Stories
    Preferences.addSetting({
      id: "stories",
      pref: "browser.newtabpage.activity-stream.feeds.section.topstories",
      deps: ["systemTopstories", ...firefoxHomeDeps],
      visible: ({ systemTopstories }) => systemTopstories.value,
      disabled: deps => !firefoxHomeActive(deps),
    });

    // Dependencies for "manage topics" checkbox
    Preferences.addSetting({
      id: "sectionsEnabled",
      pref: "browser.newtabpage.activity-stream.discoverystream.sections.enabled",
    });
    Preferences.addSetting({
      id: "topicLabelsEnabled",
      pref: "browser.newtabpage.activity-stream.discoverystream.topicLabels.enabled",
    });
    Preferences.addSetting({
      id: "sectionsPersonalizationEnabled",
      pref: "browser.newtabpage.activity-stream.discoverystream.sections.personalization.enabled",
    });
    Preferences.addSetting({
      id: "sectionsCustomizeMenuPanelEnabled",
      pref: "browser.newtabpage.activity-stream.discoverystream.sections.customizeMenuPanel.enabled",
    });

    Preferences.addSetting({
      id: "manageTopics",
      deps: [
        "sectionsEnabled",
        "sectionsPersonalizationEnabled",
        "sectionsCustomizeMenuPanelEnabled",
        "stories",
        ...firefoxHomeDeps,
      ],
      visible: deps => {
        const {
          sectionsEnabled,
          sectionsPersonalizationEnabled,
          sectionsCustomizeMenuPanelEnabled,
          stories,
        } = deps;
        return (
          firefoxHomeActive(deps) &&
          sectionsEnabled.value &&
          sectionsPersonalizationEnabled.value &&
          sectionsCustomizeMenuPanelEnabled.value &&
          stories.value
        );
      },
      onUserClick: (e, deps) => {
        e.preventDefault();
        window.openTrustedLinkIn(
          HOME_CUSTOMIZE_TOPICS_URL,
          dispatchForHomeLink(deps)
        );
      },
    });

    // Support Firefox: sponsored content
    Preferences.addSetting({
      id: "supportFirefox",
      pref: "browser.newtabpage.activity-stream.showSponsoredCheckboxes",
      deps: ["sponsoredShortcuts", "sponsoredStories", ...firefoxHomeDeps],
      disabled: deps => !firefoxHomeActive(deps),
      onUserChange(value, { sponsoredShortcuts, sponsoredStories }) {
        // When supportFirefox changes, automatically update child preferences to match
        sponsoredShortcuts.value = !!value;
        sponsoredStories.value = !!value;
      },
    });
    Preferences.addSetting({
      id: "topsitesEnabled",
      pref: "browser.newtabpage.activity-stream.feeds.topsites",
    });
    Preferences.addSetting({
      id: "sponsoredShortcuts",
      pref: "browser.newtabpage.activity-stream.showSponsoredTopSites",
      deps: ["topsitesEnabled"],
      disabled: ({ topsitesEnabled }) => !topsitesEnabled.value,
    });
    Preferences.addSetting({
      id: "sponsoredStories",
      pref: "browser.newtabpage.activity-stream.showSponsored",
      deps: ["systemTopstories", "stories"],
      visible: ({ systemTopstories }) => !!systemTopstories.value,
      disabled: ({ stories }) => !stories.value,
    });
    // Not disabled when Firefox Home is off — the promo remains visible
    // regardless of the homepage setting.
    Preferences.addSetting({
      id: "supportFirefoxPromo",
      deps: ["supportFirefox"],
    });

    // Recent activity
    Preferences.addSetting({
      id: "recentActivity",
      pref: "browser.newtabpage.activity-stream.feeds.section.highlights",
      deps: firefoxHomeDeps,
      disabled: deps => !firefoxHomeActive(deps),
    });
    Preferences.addSetting({
      id: "recentActivityRows",
      pref: "browser.newtabpage.activity-stream.section.highlights.rows",
    });
    Preferences.addSetting({
      id: "recentActivityVisited",
      pref: "browser.newtabpage.activity-stream.section.highlights.includeVisited",
    });
    Preferences.addSetting({
      id: "recentActivityBookmarks",
      pref: "browser.newtabpage.activity-stream.section.highlights.includeBookmarks",
    });
    Preferences.addSetting({
      id: "recentActivityDownloads",
      pref: "browser.newtabpage.activity-stream.section.highlights.includeDownloads",
    });

    // Hidden when Firefox Home is off — the wallpaper page only applies when
    // Firefox Home is the active destination for new windows or new tabs.
    Preferences.addSetting({
      id: "chooseWallpaper",
      deps: firefoxHomeDeps,
      visible: deps => firefoxHomeActive(deps),
      onUserClick: (e, deps) => {
        e.preventDefault();
        window.openTrustedLinkIn(HOME_CUSTOMIZE_URL, dispatchForHomeLink(deps));
      },
    });

    // Base shape used when Weather is nested inside the Widgets group, where it
    // matches its sibling widget checkboxes (no explicit control). The
    // standalone row below adds control: "moz-toggle" to render as a top-level
    // toggle like the other Firefox Home rows.
    const weatherItem = {
      id: "weather",
      subcategory: "weather",
      l10nId: "home-prefs-weather-header-srd",
    };

    return {
      inProgress: true,
      headingLevel: 2,
      l10nId: "home-prefs-content-header",
      iconSrc: "chrome://browser/skin/home.svg",
      subcategory: "contents",
      items: [
        {
          id: "firefoxHomeDisabledNotice",
          control: "moz-message-bar",
          l10nId: "home-prefs-firefox-home-disabled-notice",
          controlAttrs: {
            type: "info",
          },
        },
        {
          id: "webSearch",
          subcategory: "web-search",
          l10nId: "home-prefs-search-header2",
          control: "moz-toggle",
        },
        // Weather nests inside the Widgets group only when that group is shown
        // (Nova + the resolved widgets container gate, the same gate the group's
        // visibility uses). When the container is off but weather is
        // independently enabled (the current default), keep Weather as its own
        // row so it stays reachable.
        ...(novaEnabled && widgetsSystemEnabled
          ? []
          : [{ ...weatherItem, control: "moz-toggle" }]),
        {
          id: "widgets",
          l10nId: "home-prefs-widgets-header",
          control: "moz-toggle",
          // Bug 2046503: this hardcoded widget list should be generated
          // dynamically from WIDGET_REGISTRY (WidgetsRegistry.mjs) so new
          // widgets appear here automatically.
          items: [
            {
              id: "lists",
              l10nId: "home-prefs-lists-header",
            },
            {
              id: "timer",
              l10nId: "home-prefs-timer-header",
            },
            {
              id: "sportsWidget",
              l10nId: "home-prefs-sports-widget-header",
            },
            {
              id: "clocks",
              l10nId: "home-prefs-clocks-header",
            },
            ...(novaEnabled && widgetsSystemEnabled ? [weatherItem] : []),
          ],
        },
        {
          id: "shortcuts",
          subcategory: "topsites",
          l10nId: "home-prefs-shortcuts-header-srd",
          control: "moz-toggle",
          items: [
            {
              id: "shortcutsRows",
              l10nId: "home-prefs-shortcuts-select",
              control: "moz-select",
              options: [
                {
                  value: 1,
                  l10nId: "home-prefs-sections-rows-option-srd",
                  l10nArgs: { num: 1 },
                },
                {
                  value: 2,
                  l10nId: "home-prefs-sections-rows-option-srd",
                  l10nArgs: { num: 2 },
                },
                {
                  value: 3,
                  l10nId: "home-prefs-sections-rows-option-srd",
                  l10nArgs: { num: 3 },
                },
                {
                  value: 4,
                  l10nId: "home-prefs-sections-rows-option-srd",
                  l10nArgs: { num: 4 },
                },
              ],
            },
          ],
        },
        {
          id: "stories",
          subcategory: "topstories",
          l10nId: "home-prefs-stories-header2",
          control: "moz-toggle",
          items: [
            {
              id: "manageTopics",
              l10nId: "home-prefs-manage-topics-link2",
              control: "moz-box-link",
              controlAttrs: {
                href: HOME_CUSTOMIZE_TOPICS_URL,
              },
            },
          ],
        },
        {
          id: "supportFirefox",
          subcategory: "support-firefox",
          l10nId: "home-prefs-support-firefox-header-srd",
          control: "moz-toggle",
          items: [
            {
              id: "sponsoredShortcuts",
              l10nId: "home-prefs-shortcuts-by-option-sponsored-srd",
            },
            {
              id: "sponsoredStories",
              l10nId: "home-prefs-recommended-by-option-sponsored-stories-srd",
            },
            {
              id: "supportFirefoxPromo",
              l10nId: "home-prefs-mission-message2",
              control: "moz-promo",
              options: [
                {
                  control: "a",
                  l10nId: "home-prefs-mission-message-learn-more-link-srd",
                  slot: "support-link",
                  controlAttrs: {
                    is: "moz-support-link",
                    "support-page": "sponsor-privacy",
                    "utm-content": "inproduct",
                  },
                },
              ],
            },
          ],
        },
        {
          id: "recentActivity",
          subcategory: "highlights",
          l10nId: "home-prefs-recent-activity-header-srd",
          control: "moz-toggle",
          items: [
            {
              id: "recentActivityRows",
              l10nId: "home-prefs-recent-activity-select",
              control: "moz-select",
              options: [
                {
                  value: 1,
                  l10nId: "home-prefs-sections-rows-option-srd",
                  l10nArgs: { num: 1 },
                },
                {
                  value: 2,
                  l10nId: "home-prefs-sections-rows-option-srd",
                  l10nArgs: { num: 2 },
                },
                {
                  value: 3,
                  l10nId: "home-prefs-sections-rows-option-srd",
                  l10nArgs: { num: 3 },
                },
                {
                  value: 4,
                  l10nId: "home-prefs-sections-rows-option-srd",
                  l10nArgs: { num: 4 },
                },
              ],
            },
            {
              id: "recentActivityVisited",
              l10nId: "home-prefs-highlights-option-visited-pages-srd",
            },
            {
              id: "recentActivityBookmarks",
              l10nId: "home-prefs-highlights-options-bookmarks-srd",
            },
            {
              id: "recentActivityDownloads",
              l10nId: "home-prefs-highlights-option-most-recent-download-srd",
            },
          ],
        },
        {
          id: "chooseWallpaper",
          l10nId: "home-prefs-choose-wallpaper-link2",
          control: "moz-box-link",
          controlAttrs: {
            href: HOME_CUSTOMIZE_URL,
          },
          iconSrc: "chrome://browser/skin/customize.svg",
        },
        ...(novaEnabled
          ? [
              {
                id: "firefoxLogo",
                l10nId: "home-prefs-firefox-logo-header",
                control: "moz-toggle",
              },
            ]
          : []),
      ],
    };
  }

  /**
   * Render a single preference with all the details, e.g. description, links,
   * more granular preferences.
   *
   * @param sectionData
   * @param document
   * @param Preferences
   */

  /**
   * We can remove this eslint exception once the Settings redesign is complete.
   * In fact, we can probably remove this entire method. When removing, also
   * drop the `pref:` blocks on the `highlights` and `topstories` sections in
   * SectionsManager.sys.mjs — they exist only to feed this renderer.
   */
  // eslint-disable-next-line max-statements
  renderPreferenceSection(sectionData, document, Preferences) {
    /* Do not render old-style settings if new settings UI is enabled - this is needed to avoid
     * registering prefs twice and ensuing errors */
    if (Services.prefs.getBoolPref("browser.settings-redesign.enabled")) {
      return;
    }

    const {
      id,
      pref: prefData,
      maxRows,
      rowsPref,
      shouldHidePref,
      eventSource,
    } = sectionData;
    const {
      feed: name,
      titleString = {},
      descString,
      nestedPrefs = [],
    } = prefData || {};

    // Helper to link a UI element to a preference for updating
    const linkPref = (element, prefName, type) => {
      const fullPref = `browser.newtabpage.activity-stream.${prefName}`;
      element.setAttribute("preference", fullPref);
      Preferences.add({ id: fullPref, type });

      // Prevent changing the UI if the preference can't be changed
      element.disabled = Preferences.get(fullPref).locked;
    };

    // Don't show any sections that we don't want to expose in preferences UI
    if (shouldHidePref) {
      return;
    }

    // Add the main preference for turning on/off a section
    const sectionVbox = document.getElementById(id);
    sectionVbox.setAttribute("data-subcategory", id);
    const checkbox = this.createAppend(document, "checkbox", sectionVbox);
    checkbox.classList.add("section-checkbox");
    // Set up a user event if we have an event source for this pref.
    if (eventSource) {
      this.setupUserEvent(checkbox, eventSource);
    }
    document.l10n.setAttributes(
      checkbox,
      this.getString(titleString),
      titleString.values
    );

    linkPref(checkbox, name, "bool");

    // Specially add a link for Weather
    if (id === "weather") {
      const hboxWithLink = this.createAppend(document, "hbox", sectionVbox);
      hboxWithLink.appendChild(checkbox);
      checkbox.classList.add("tail-with-learn-more");

      const link = this.createAppend(document, "label", hboxWithLink, {
        is: "text-link",
      });
      link.setAttribute("href", sectionData.pref.learnMore.link.href);
      document.l10n.setAttributes(link, sectionData.pref.learnMore.link.id);
    }

    // Add more details for the section (e.g., description, more prefs)
    const detailVbox = this.createAppend(document, "vbox", sectionVbox);
    detailVbox.classList.add("indent");
    if (descString) {
      const description = this.createAppend(
        document,
        "description",
        detailVbox
      );
      description.classList.add("text-deemphasized");
      document.l10n.setAttributes(
        description,
        this.getString(descString),
        descString.values
      );

      // Add a rows dropdown if we have a pref to control and a maximum
      if (rowsPref && maxRows) {
        const detailHbox = this.createAppend(document, "hbox", detailVbox);
        detailHbox.setAttribute("align", "center");
        description.setAttribute("flex", 1);
        detailHbox.appendChild(description);

        // Add box so the search tooltip is positioned correctly
        const tooltipBox = this.createAppend(document, "hbox", detailHbox);

        // Add appropriate number of localized entries to the dropdown
        const menulist = this.createAppend(document, "menulist", tooltipBox);
        menulist.setAttribute("crop", "none");
        const menupopup = this.createAppend(document, "menupopup", menulist);
        for (let num = 1; num <= maxRows; num++) {
          const item = this.createAppend(document, "menuitem", menupopup);
          document.l10n.setAttributes(item, "home-prefs-sections-rows-option", {
            num,
          });
          item.setAttribute("value", num);
        }
        linkPref(menulist, rowsPref, "int");
      }
    }

    const subChecks = [];
    const fullName = `browser.newtabpage.activity-stream.${sectionData.pref.feed}`;
    const pref = Preferences.get(fullName);

    // Add a checkbox pref for any nested preferences
    nestedPrefs.forEach(nested => {
      if (nested.shouldHidePref !== true) {
        const subcheck = this.createAppend(document, "checkbox", detailVbox);
        // Set up a user event if we have an event source for this pref.
        if (nested.eventSource) {
          this.setupUserEvent(subcheck, nested.eventSource);
        }
        document.l10n.setAttributes(subcheck, nested.titleString);

        linkPref(subcheck, nested.name, "bool");

        subChecks.push(subcheck);
        subcheck.disabled = !pref._value;
        if (nested.shouldDisablePref) {
          subcheck.disabled = nested.shouldDisablePref;
        }
        subcheck.hidden = nested.hidden;
      }
    });

    // Special cases to like the nested prefs with another pref,
    // so we can disable it real time.
    if (id === "support-firefox") {
      function setupSupportFirefoxSubCheck(triggerPref, subPref) {
        const subCheckFullName = `browser.newtabpage.activity-stream.${triggerPref}`;
        const subCheckPref = Preferences.get(subCheckFullName);

        subCheckPref?.on("change", () => {
          const showSponsoredFullName = `browser.newtabpage.activity-stream.${subPref}`;
          const showSponsoredSubcheck = subChecks.find(
            subcheck =>
              subcheck.getAttribute("preference") === showSponsoredFullName
          );
          if (showSponsoredSubcheck) {
            showSponsoredSubcheck.disabled = !Services.prefs.getBoolPref(
              subCheckFullName,
              true
            );
          }
        });
      }

      setupSupportFirefoxSubCheck("feeds.section.topstories", "showSponsored");
      setupSupportFirefoxSubCheck("feeds.topsites", "showSponsoredTopSites");
    }

    pref.on("change", () => {
      subChecks.forEach(subcheck => {
        // Update child preferences for the "Support Firefox" checkbox group
        // so that they're turned on and off at the same time.
        if (id === "support-firefox") {
          const subPref = Preferences.get(subcheck.getAttribute("preference"));
          subPref.value = pref.value;
        }

        // Disable any nested checkboxes if the parent pref is not enabled.
        subcheck.disabled = !pref._value;
      });
    });
  }

  /**
   * Update the visibility of the Restore Defaults button based on checked prefs.
   *
   * @param gHomePane
   */
  toggleRestoreDefaults(gHomePane) {
    gHomePane.toggleRestoreDefaultsBtn();
  }

  /**
   * A helper function to append XUL elements on the page.
   *
   * @param document
   * @param tag
   * @param parent
   * @param options
   */
  createAppend(document, tag, parent, options = {}) {
    return parent.appendChild(document.createXULElement(tag, options));
  }

  /**
   * Helper to get fluentIDs sometimes encase in an object
   *
   * @param message
   * @returns string
   */
  getString(message) {
    return typeof message !== "object" ? message : message.id;
  }
}

[zur Elbe Produktseite wechseln0.87Quellennavigators]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002