/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* 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/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
});

var { ExtensionPreferencesManager } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionPreferencesManager.sys.mjs"
);

var { ExtensionError } = ExtensionUtils;
var { getSettingsAPI, getPrimedSettingsListener } = ExtensionPreferencesManager;

const HOMEPAGE_URL_PREF = "browser.startup.homepage";

const PERM_DENY_ACTION = Services.perms.DENY_ACTION;

// Add settings objects for supported APIs to the preferences manager.
ExtensionPreferencesManager.addSetting("allowPopupsForUserEvents", {
  permission: "browserSettings",
  prefNames: ["dom.popup_allowed_events"],

  setCallback(value) {
    let returnObj = {};
    // If the value is true, then reset the pref, otherwise set it to "".
    returnObj[this.prefNames[0]] = value ? undefined : "";
    return returnObj;
  },

  getCallback() {
    return Services.prefs.getCharPref("dom.popup_allowed_events") != "";
  },
});

ExtensionPreferencesManager.addSetting("cacheEnabled", {
  permission: "browserSettings",
  prefNames: ["browser.cache.disk.enable", "browser.cache.memory.enable"],

  setCallback(value) {
    let returnObj = {};
    for (let pref of this.prefNames) {
      returnObj[pref] = value;
    }
    return returnObj;
  },

  getCallback() {
    return (
      Services.prefs.getBoolPref("browser.cache.disk.enable") &&
      Services.prefs.getBoolPref("browser.cache.memory.enable")
    );
  },
});

ExtensionPreferencesManager.addSetting("closeTabsByDoubleClick", {
  permission: "browserSettings",
  prefNames: ["browser.tabs.closeTabByDblclick"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.tabs.closeTabByDblclick");
  },

  validate() {
    if (AppConstants.platform == "android") {
      throw new ExtensionError(
        `android is not a supported platform for the closeTabsByDoubleClick setting.`
      );
    }
  },
});

ExtensionPreferencesManager.addSetting("colorManagement.mode", {
  permission: "browserSettings",
  prefNames: ["gfx.color_management.mode"],

  setCallback(value) {
    switch (value) {
      case "off":
        return { [this.prefNames[0]]: 0 };
      case "full":
        return { [this.prefNames[0]]: 1 };
      case "tagged_only":
        return { [this.prefNames[0]]: 2 };
    }
  },

  getCallback() {
    switch (Services.prefs.getIntPref("gfx.color_management.mode")) {
      case 0:
        return "off";
      case 1:
        return "full";
      case 2:
        return "tagged_only";
    }
  },
});

ExtensionPreferencesManager.addSetting("colorManagement.useNativeSRGB", {
  permission: "browserSettings",
  prefNames: ["gfx.color_management.native_srgb"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("gfx.color_management.native_srgb");
  },
});

ExtensionPreferencesManager.addSetting(
  "colorManagement.useWebRenderCompositor",
  {
    permission: "browserSettings",
    prefNames: ["gfx.webrender.compositor"],

    setCallback(value) {
      return { [this.prefNames[0]]: value };
    },

    getCallback() {
      return Services.prefs.getBoolPref("gfx.webrender.compositor");
    },
  }
);

ExtensionPreferencesManager.addSetting("contextMenuShowEvent", {
  permission: "browserSettings",
  prefNames: ["ui.context_menus.after_mouseup"],

  setCallback(value) {
    return { [this.prefNames[0]]: value === "mouseup" };
  },

  getCallback() {
    if (AppConstants.platform === "win") {
      return "mouseup";
    }
    let prefValue = Services.prefs.getBoolPref(
      "ui.context_menus.after_mouseup",
      null
    );
    return prefValue ? "mouseup" : "mousedown";
  },
});

ExtensionPreferencesManager.addSetting("imageAnimationBehavior", {
  permission: "browserSettings",
  prefNames: ["image.animation_mode"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getCharPref("image.animation_mode");
  },
});

ExtensionPreferencesManager.addSetting("newTabPosition", {
  permission: "browserSettings",
  prefNames: [
    "browser.tabs.insertRelatedAfterCurrent",
    "browser.tabs.insertAfterCurrent",
  ],

  setCallback(value) {
    return {
      "browser.tabs.insertAfterCurrent": value === "afterCurrent",
      "browser.tabs.insertRelatedAfterCurrent": value === "relatedAfterCurrent",
    };
  },

  getCallback() {
    if (Services.prefs.getBoolPref("browser.tabs.insertAfterCurrent")) {
      return "afterCurrent";
    }
    if (Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
      return "relatedAfterCurrent";
    }
    return "atEnd";
  },
});

ExtensionPreferencesManager.addSetting("openBookmarksInNewTabs", {
  permission: "browserSettings",
  prefNames: ["browser.tabs.loadBookmarksInTabs"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.tabs.loadBookmarksInTabs");
  },
});

ExtensionPreferencesManager.addSetting("openSearchResultsInNewTabs", {
  permission: "browserSettings",
  prefNames: ["browser.search.openintab"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.search.openintab");
  },
});

ExtensionPreferencesManager.addSetting("openUrlbarResultsInNewTabs", {
  permission: "browserSettings",
  prefNames: ["browser.urlbar.openintab"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.urlbar.openintab");
  },
});

ExtensionPreferencesManager.addSetting("webNotificationsDisabled", {
  permission: "browserSettings",
  prefNames: ["permissions.default.desktop-notification"],

  setCallback(value) {
    return { [this.prefNames[0]]: value ? PERM_DENY_ACTION : undefined };
  },

  getCallback() {
    let prefValue = Services.prefs.getIntPref(
      "permissions.default.desktop-notification",
      null
    );
    return prefValue === PERM_DENY_ACTION;
  },
});

ExtensionPreferencesManager.addSetting("overrideDocumentColors", {
  permission: "browserSettings",
  prefNames: ["browser.display.document_color_use"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    let prefValue = Services.prefs.getIntPref(
      "browser.display.document_color_use"
    );
    if (prefValue === 1) {
      return "never";
    } else if (prefValue === 2) {
      return "always";
    }
    return "high-contrast-only";
  },
});

ExtensionPreferencesManager.addSetting("overrideContentColorScheme", {
  permission: "browserSettings",
  prefNames: ["layout.css.prefers-color-scheme.content-override"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    let prefValue = Services.prefs.getIntPref(
      "layout.css.prefers-color-scheme.content-override"
    );
    switch (prefValue) {
      case 0:
        return "dark";
      case 1:
        return "light";
      default:
        return "auto";
    }
  },
});

ExtensionPreferencesManager.addSetting("useDocumentFonts", {
  permission: "browserSettings",
  prefNames: ["browser.display.use_document_fonts"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return (
      Services.prefs.getIntPref("browser.display.use_document_fonts") !== 0
    );
  },
});

ExtensionPreferencesManager.addSetting("zoomFullPage", {
  permission: "browserSettings",
  prefNames: ["browser.zoom.full"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.zoom.full");
  },
});

ExtensionPreferencesManager.addSetting("zoomSiteSpecific", {
  permission: "browserSettings",
  prefNames: ["browser.zoom.siteSpecific"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.zoom.siteSpecific");
  },
});

ExtensionPreferencesManager.addSetting("verticalTabs", {
  permission: "browserSettings",
  prefNames: ["sidebar.verticalTabs"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("sidebar.verticalTabs", false);
  },

  validate() {
    if (AppConstants.platform == "android") {
      throw new ExtensionError(
        `android is not a supported platform for verticalTabs.`
      );
    }
  },
});

this.browserSettings = class extends ExtensionAPI {
  homePageOverrideListener(fire) {
    let listener = () => {
      fire.async({
        levelOfControl: "not_controllable",
        value: Services.prefs.getStringPref(HOMEPAGE_URL_PREF),
      });
    };
    Services.prefs.addObserver(HOMEPAGE_URL_PREF, listener);
    return {
      unregister: () => {
        Services.prefs.removeObserver(HOMEPAGE_URL_PREF, listener);
      },
      convert(_fire) {
        fire = _fire;
      },
    };
  }

  newTabOverrideListener(fire) {
    let listener = () => {
      fire.async({
        levelOfControl: "not_controllable",
        value: AboutNewTab.newTabURL,
      });
    };
    Services.obs.addObserver(listener, "newtab-url-changed");
    return {
      unregister: () => {
        Services.obs.removeObserver(listener, "newtab-url-changed");
      },
      convert(_fire) {
        fire = _fire;
      },
    };
  }

  primeListener(event, fire) {
    let { extension } = this;
    if (event == "homepageOverride") {
      return this.homePageOverrideListener(fire);
    }
    if (event == "newTabPageOverride") {
      return this.newTabOverrideListener(fire);
    }
    let listener = getPrimedSettingsListener({
      extension,
      name: event,
    });
    return listener(fire);
  }

  getAPI(context) {
    let self = this;
    let { extension } = context;

    function makeSettingsAPI(name) {
      return getSettingsAPI({
        context,
        module: "browserSettings",
        name,
      });
    }

    const verticalTabsSettingsAPI = makeSettingsAPI("verticalTabs");
    const verticalTabsSet = verticalTabsSettingsAPI.set;

    return {
      browserSettings: {
        allowPopupsForUserEvents: makeSettingsAPI("allowPopupsForUserEvents"),
        cacheEnabled: makeSettingsAPI("cacheEnabled"),
        closeTabsByDoubleClick: makeSettingsAPI("closeTabsByDoubleClick"),
        contextMenuShowEvent: Object.assign(
          makeSettingsAPI("contextMenuShowEvent"),
          {
            set: details => {
              if (!["mouseup", "mousedown"].includes(details.value)) {
                throw new ExtensionError(
                  `${details.value} is not a valid value for contextMenuShowEvent.`
                );
              }
              if (
                AppConstants.platform === "android" ||
                (AppConstants.platform === "win" &&
                  details.value === "mousedown")
              ) {
                return false;
              }
              return ExtensionPreferencesManager.setSetting(
                extension.id,
                "contextMenuShowEvent",
                details.value
              );
            },
          }
        ),
        ftpProtocolEnabled: getSettingsAPI({
          context,
          name: "ftpProtocolEnabled",
          readOnly: true,
          callback() {
            return false;
          },
        }),
        homepageOverride: getSettingsAPI({
          context,
          // Name differs here to preserve this setting properly
          name: "homepage_override",
          callback() {
            return Services.prefs.getStringPref(HOMEPAGE_URL_PREF);
          },
          readOnly: true,
          onChange: new ExtensionCommon.EventManager({
            context,
            module: "browserSettings",
            event: "homepageOverride",
            name: "homepageOverride.onChange",
            register: fire => {
              return self.homePageOverrideListener(fire).unregister;
            },
          }).api(),
        }),
        imageAnimationBehavior: makeSettingsAPI("imageAnimationBehavior"),
        newTabPosition: makeSettingsAPI("newTabPosition"),
        newTabPageOverride: getSettingsAPI({
          context,
          // Name differs here to preserve this setting properly
          name: "newTabURL",
          callback() {
            return AboutNewTab.newTabURL;
          },
          storeType: "url_overrides",
          readOnly: true,
          onChange: new ExtensionCommon.EventManager({
            context,
            module: "browserSettings",
            event: "newTabPageOverride",
            name: "newTabPageOverride.onChange",
            register: fire => {
              return self.newTabOverrideListener(fire).unregister;
            },
          }).api(),
        }),
        openBookmarksInNewTabs: makeSettingsAPI("openBookmarksInNewTabs"),
        openSearchResultsInNewTabs: makeSettingsAPI(
          "openSearchResultsInNewTabs"
        ),
        openUrlbarResultsInNewTabs: makeSettingsAPI(
          "openUrlbarResultsInNewTabs"
        ),
        webNotificationsDisabled: makeSettingsAPI("webNotificationsDisabled"),
        overrideDocumentColors: Object.assign(
          makeSettingsAPI("overrideDocumentColors"),
          {
            set: details => {
              if (
                !["never", "always", "high-contrast-only"].includes(
                  details.value
                )
              ) {
                throw new ExtensionError(
                  `${details.value} is not a valid value for overrideDocumentColors.`
                );
              }
              let prefValue = 0; // initialize to 0 - auto/high-contrast-only
              if (details.value === "never") {
                prefValue = 1;
              } else if (details.value === "always") {
                prefValue = 2;
              }
              return ExtensionPreferencesManager.setSetting(
                extension.id,
                "overrideDocumentColors",
                prefValue
              );
            },
          }
        ),
        overrideContentColorScheme: Object.assign(
          makeSettingsAPI("overrideContentColorScheme"),
          {
            set: details => {
              let value = details.value;
              if (value == "system" || value == "browser") {
                // Map previous values that used to be different but were
                // unified under the "auto" setting. In practice this should
                // almost always behave like the extension author expects.
                extension.logger.warn(
                  `The "${value}" value for overrideContentColorScheme has been deprecated. Use "auto" instead`
                );
                value = "auto";
              }
              let prefValue = ["dark", "light", "auto"].indexOf(value);
              if (prefValue === -1) {
                throw new ExtensionError(
                  `${value} is not a valid value for overrideContentColorScheme.`
                );
              }
              return ExtensionPreferencesManager.setSetting(
                extension.id,
                "overrideContentColorScheme",
                prefValue
              );
            },
          }
        ),
        useDocumentFonts: Object.assign(makeSettingsAPI("useDocumentFonts"), {
          set: details => {
            if (typeof details.value !== "boolean") {
              throw new ExtensionError(
                `${details.value} is not a valid value for useDocumentFonts.`
              );
            }
            return ExtensionPreferencesManager.setSetting(
              extension.id,
              "useDocumentFonts",
              Number(details.value)
            );
          },
        }),
        zoomFullPage: Object.assign(makeSettingsAPI("zoomFullPage"), {
          set: details => {
            if (typeof details.value !== "boolean") {
              throw new ExtensionError(
                `${details.value} is not a valid value for zoomFullPage.`
              );
            }
            return ExtensionPreferencesManager.setSetting(
              extension.id,
              "zoomFullPage",
              details.value
            );
          },
        }),
        zoomSiteSpecific: Object.assign(makeSettingsAPI("zoomSiteSpecific"), {
          set: details => {
            if (typeof details.value !== "boolean") {
              throw new ExtensionError(
                `${details.value} is not a valid value for zoomSiteSpecific.`
              );
            }
            return ExtensionPreferencesManager.setSetting(
              extension.id,
              "zoomSiteSpecific",
              details.value
            );
          },
        }),
        verticalTabs: Object.assign(verticalTabsSettingsAPI, {
          set: details => {
            if (typeof details.value !== "boolean") {
              throw new ExtensionError(
                `${details.value} is not a valid value for verticalTabs.`
              );
            }
            return verticalTabsSet(details);
          },
        }),
        colorManagement: {
          mode: makeSettingsAPI("colorManagement.mode"),
          useNativeSRGB: makeSettingsAPI("colorManagement.useNativeSRGB"),
          useWebRenderCompositor: makeSettingsAPI(
            "colorManagement.useWebRenderCompositor"
          ),
        },
      },
    };
  }
};
