/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */

/**
 * The installer prefs component provides a way to get a specific set of prefs
 * from a profile into a place where the installer can read them. The primary
 * reason for wanting to do this is so we can run things like Shield studies
 * on installer features; normally those are enabled by setting a pref, but
 * the installer runs outside of any profile and so has no access to prefs.
 * So we need to do something else to allow it to read these prefs.
 *
 * The mechanism we use here is to reflect the values of a list of relevant
 * prefs into registry values. One registry value is created for each pref
 * that is set. Each installation of the product gets its own registry key
 * (based on the path hash). This is obviously a somewhat wider scope than a
 * single profile, but it should be close enough in enough cases to suit our
 * purposes here.
 *
 * Currently this module only supports bool prefs. Other types could likely
 * be added if needed, but it doesn't seem necessary for the primary use case.
 */

// All prefs processed through this component must be in this branch.
const INSTALLER_PREFS_BRANCH = "installer.";

// This is the list of prefs that will be reflected to the registry. It should
// be kept up to date so that it reflects the list of prefs that are in
// current use (e.g., currently active experiments).
// Only add prefs to this list which are in INSTALLER_PREFS_BRANCH;
// any others will be ignored.
const INSTALLER_PREFS_LIST = ["installer.taskbarpin.win10.enabled"];

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

// This constructor can take a list of prefs to override the default one,
// but this is really only intended for tests to use. Normal usage should be
// to leave the parameter omitted/undefined.
export function InstallerPrefs(prefsList) {
  this.prefsList = prefsList || INSTALLER_PREFS_LIST;

  // Each pref to be reflected will get a value created under this key, in HKCU.
  // The path will look something like:
  // "Software\Mozilla\Firefox\Installer\71AE18FE3142402B\".
  XPCOMUtils.defineLazyGetter(this, "_registryKeyPath", function () {
    const app = AppConstants.MOZ_APP_NAME;
    const vendor = Services.appinfo.vendor || "Mozilla";
    const xreDirProvider = Cc[
      "@mozilla.org/xre/directory-provider;1"
    ].getService(Ci.nsIXREDirProvider);
    const installHash = xreDirProvider.getInstallHash();
    return `Software\\${vendor}\\${app}\\Installer\\${installHash}`;
  });
}

InstallerPrefs.prototype = {
  classID: Components.ID("{cd8a6995-1f19-4cdd-9ed1-d6263302f594}"),
  contractID: "@mozilla.org/installerprefs;1",

  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),

  observe(subject, topic, data) {
    switch (topic) {
      case "profile-after-change": {
        if (
          AppConstants.platform != "win" ||
          !this.prefsList ||
          !this.prefsList.length
        ) {
          // This module has no work to do.
          break;
        }
        const regKey = this._openRegKey();
        this._reflectPrefsToRegistry(regKey);
        this._registerPrefListeners();
        regKey.close();
        break;
      }
      case "nsPref:changed": {
        const regKey = this._openRegKey();
        if (this.prefsList.includes(data)) {
          this._reflectOnePrefToRegistry(regKey, data);
        }
        regKey.close();
        break;
      }
    }
  },

  _registerPrefListeners() {
    Services.prefs.addObserver(INSTALLER_PREFS_BRANCH, this);
  },

  _cleanRegistryKey(regKey) {
    for (let i = regKey.valueCount - 1; i >= 0; --i) {
      const name = regKey.getValueName(i);
      if (name.startsWith(INSTALLER_PREFS_BRANCH)) {
        regKey.removeValue(name);
      }
    }
  },

  _reflectPrefsToRegistry(regKey) {
    this._cleanRegistryKey(regKey);
    this.prefsList.forEach(pref =>
      this._reflectOnePrefToRegistry(regKey, pref)
    );
  },

  _reflectOnePrefToRegistry(regKey, pref) {
    if (!pref.startsWith(INSTALLER_PREFS_BRANCH)) {
      return;
    }

    const value = Services.prefs.getBoolPref(pref, false);
    if (value) {
      regKey.writeIntValue(pref, 1);
    } else {
      try {
        regKey.removeValue(pref);
      } catch (ex) {
        // This removeValue call is prone to failing because the value we
        // tried to remove didn't exist. Obviously that isn't really an error
        // that we need to handle.
      }
    }
  },

  _openRegKey() {
    const key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
      Ci.nsIWindowsRegKey
    );
    key.create(
      key.ROOT_KEY_CURRENT_USER,
      this._registryKeyPath,
      key.ACCESS_READ | key.ACCESS_WRITE | key.WOW64_64
    );
    return key;
  },
};
