summaryrefslogtreecommitdiffstats
path: root/browser/components/installerprefs/InstallerPrefs.sys.mjs
blob: 2eb9d42fdcb3407f97ac86ccde1dd0e1eb26352b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/* -*- 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";

// 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\".
  ChromeUtils.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;
  },
};