summaryrefslogtreecommitdiffstats
path: root/browser/components/installerprefs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/installerprefs/InstallerPrefs.jsm148
-rw-r--r--browser/components/installerprefs/components.conf16
-rw-r--r--browser/components/installerprefs/moz.build18
-rw-r--r--browser/components/installerprefs/test/unit/head.js137
-rw-r--r--browser/components/installerprefs/test/unit/test_empty_prefs_list.js20
-rw-r--r--browser/components/installerprefs/test/unit/test_invalid_name.js20
-rw-r--r--browser/components/installerprefs/test/unit/test_nonbool_pref.js19
-rw-r--r--browser/components/installerprefs/test/unit/test_pref_change.js26
-rw-r--r--browser/components/installerprefs/test/unit/test_pref_values.js19
-rw-r--r--browser/components/installerprefs/test/unit/xpcshell.ini20
10 files changed, 443 insertions, 0 deletions
diff --git a/browser/components/installerprefs/InstallerPrefs.jsm b/browser/components/installerprefs/InstallerPrefs.jsm
new file mode 100644
index 0000000000..668f25c12f
--- /dev/null
+++ b/browser/components/installerprefs/InstallerPrefs.jsm
@@ -0,0 +1,148 @@
+/* -*- 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"];
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "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.
+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;
+ },
+};
+
+var EXPORTED_SYMBOLS = ["InstallerPrefs"];
diff --git a/browser/components/installerprefs/components.conf b/browser/components/installerprefs/components.conf
new file mode 100644
index 0000000000..2bd525f0b0
--- /dev/null
+++ b/browser/components/installerprefs/components.conf
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{cd8a6995-1f19-4cdd-9ed1-d6263302f594}',
+ 'contract_ids': ['@mozilla.org/installerprefs;1'],
+ 'jsm': 'resource:///modules/InstallerPrefs.jsm',
+ 'constructor': 'InstallerPrefs',
+ 'categories': {'profile-after-change': 'InstallerPrefs'},
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+]
diff --git a/browser/components/installerprefs/moz.build b/browser/components/installerprefs/moz.build
new file mode 100644
index 0000000000..853f2d888a
--- /dev/null
+++ b/browser/components/installerprefs/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Installer")
+
+XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini"]
+
+EXTRA_JS_MODULES += [
+ "InstallerPrefs.jsm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
diff --git a/browser/components/installerprefs/test/unit/head.js b/browser/components/installerprefs/test/unit/head.js
new file mode 100644
index 0000000000..3d00a2437f
--- /dev/null
+++ b/browser/components/installerprefs/test/unit/head.js
@@ -0,0 +1,137 @@
+/* -*- 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/. */
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+const { InstallerPrefs } = ChromeUtils.import(
+ "resource:///modules/InstallerPrefs.jsm"
+);
+
+let gRegistryKeyPath = "";
+
+function startModule(prefsList) {
+ // Construct an InstallerPrefs object and simulate a profile-after-change
+ // event on it, so that it performs its full startup procedure.
+ const prefsModule = new InstallerPrefs(prefsList);
+ prefsModule.observe(null, "profile-after-change", "");
+
+ gRegistryKeyPath = prefsModule._registryKeyPath;
+
+ registerCleanupFunction(() => cleanupReflectedPrefs(prefsList));
+}
+
+function getRegistryKey() {
+ const key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ key.open(
+ key.ROOT_KEY_CURRENT_USER,
+ gRegistryKeyPath,
+ key.ACCESS_READ | key.WOW64_64
+ );
+ return key;
+}
+
+function verifyReflectedPrefs(prefsList) {
+ let key;
+ try {
+ key = getRegistryKey();
+ } catch (ex) {
+ Assert.ok(false, `Failed to open registry key: ${ex}`);
+ return;
+ }
+
+ for (const pref of prefsList) {
+ if (pref.startsWith("installer.")) {
+ if (Services.prefs.getPrefType(pref) != Services.prefs.PREF_BOOL) {
+ Assert.ok(
+ !key.hasValue(pref),
+ `Pref ${pref} should not be in the registry because its type is not bool`
+ );
+ } else if (Services.prefs.getBoolPref(pref, false)) {
+ Assert.ok(key.hasValue(pref), `Pref ${pref} should be in the registry`);
+ Assert.equal(
+ key.getValueType(pref),
+ key.TYPE_INT,
+ `Pref ${pref} should be type DWORD`
+ );
+ Assert.equal(
+ key.readIntValue(pref),
+ 1,
+ `Pref ${pref} should have value 1`
+ );
+ } else {
+ Assert.ok(
+ !key.hasValue(pref),
+ `Pref ${pref} should not be in the registry because it is false`
+ );
+ }
+ } else {
+ Assert.ok(
+ !key.hasValue(pref),
+ `Pref ${pref} should not be in the registry because its name is invalid`
+ );
+ }
+ }
+
+ key.close();
+}
+
+function cleanupReflectedPrefs(prefsList) {
+ // Clear out the prefs themselves.
+ prefsList.forEach(pref => Services.prefs.clearUserPref(pref));
+
+ // Get the registry key path without the path hash at the end,
+ // then delete the subkey with the path hash.
+ 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 path = `Software\\${vendor}\\${app}\\Installer`;
+
+ const key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ try {
+ key.open(
+ key.ROOT_KEY_CURRENT_USER,
+ path,
+ key.ACCESS_READ | key.ACCESS_WRITE | key.WOW64_64
+ );
+ const installHash = xreDirProvider.getInstallHash();
+ key.removeChild(installHash);
+ } catch (ex) {
+ // Nothing left to clean up.
+ return;
+ }
+
+ // If the Installer key is now empty, we need to clean it up also, because
+ // that would mean that this test created it.
+ if (key.childCount == 0) {
+ // Unfortunately we can't delete the actual open key, so we'll have to
+ // open its parent and delete the one we're after as a child.
+ key.close();
+ const parentKey = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ try {
+ parentKey.open(
+ parentKey.ROOT_KEY_CURRENT_USER,
+ `Software\\${vendor}\\${app}`,
+ parentKey.ACCESS_READ | parentKey.ACCESS_WRITE | parentKey.WOW64_64
+ );
+ parentKey.removeChild("Installer");
+ parentKey.close();
+ } catch (ex) {
+ // Nothing we can do, and this isn't worth failing the test over.
+ }
+ } else {
+ key.close();
+ }
+}
diff --git a/browser/components/installerprefs/test/unit/test_empty_prefs_list.js b/browser/components/installerprefs/test/unit/test_empty_prefs_list.js
new file mode 100644
index 0000000000..a42e0d50bb
--- /dev/null
+++ b/browser/components/installerprefs/test/unit/test_empty_prefs_list.js
@@ -0,0 +1,20 @@
+/* -*- 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/. */
+
+/**
+ * Test passing an empty list of pref names to the module.
+ */
+
+add_task(function() {
+ const PREFS_LIST = [];
+
+ startModule(PREFS_LIST);
+
+ Assert.throws(
+ getRegistryKey,
+ /NS_ERROR_FAILURE/,
+ "The registry key shouldn't have been created, so opening it should fail"
+ );
+});
diff --git a/browser/components/installerprefs/test/unit/test_invalid_name.js b/browser/components/installerprefs/test/unit/test_invalid_name.js
new file mode 100644
index 0000000000..7de279c338
--- /dev/null
+++ b/browser/components/installerprefs/test/unit/test_invalid_name.js
@@ -0,0 +1,20 @@
+/* -*- 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/. */
+
+/**
+ * Test that using prefs with invalid names is not allowed.
+ */
+
+add_task(function() {
+ const PREFS_LIST = [
+ "the.wrong.branch",
+ "installer.the.right.branch",
+ "not.a.real.pref",
+ ];
+
+ startModule(PREFS_LIST);
+
+ verifyReflectedPrefs(PREFS_LIST);
+});
diff --git a/browser/components/installerprefs/test/unit/test_nonbool_pref.js b/browser/components/installerprefs/test/unit/test_nonbool_pref.js
new file mode 100644
index 0000000000..ad2f0be860
--- /dev/null
+++ b/browser/components/installerprefs/test/unit/test_nonbool_pref.js
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+/**
+ * Test that prefs of types other than bool are not reflected.
+ */
+
+add_task(function() {
+ const PREFS_LIST = ["installer.int", "installer.string"];
+
+ Services.prefs.setIntPref("installer.int", 12);
+ Services.prefs.setStringPref("installer.string", "I'm a string");
+
+ startModule(PREFS_LIST);
+
+ verifyReflectedPrefs(PREFS_LIST);
+});
diff --git a/browser/components/installerprefs/test/unit/test_pref_change.js b/browser/components/installerprefs/test/unit/test_pref_change.js
new file mode 100644
index 0000000000..1e28f6622c
--- /dev/null
+++ b/browser/components/installerprefs/test/unit/test_pref_change.js
@@ -0,0 +1,26 @@
+/* -*- 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/. */
+
+/**
+ * Test that pref values are correctly updated when they change.
+ */
+
+add_task(function() {
+ const PREFS_LIST = ["installer.pref"];
+
+ Services.prefs.setBoolPref("installer.pref", true);
+
+ startModule(PREFS_LIST);
+
+ verifyReflectedPrefs(PREFS_LIST);
+
+ Services.prefs.setBoolPref("installer.pref", false);
+
+ verifyReflectedPrefs(PREFS_LIST);
+
+ Services.prefs.setBoolPref("installer.pref", true);
+
+ verifyReflectedPrefs(PREFS_LIST);
+});
diff --git a/browser/components/installerprefs/test/unit/test_pref_values.js b/browser/components/installerprefs/test/unit/test_pref_values.js
new file mode 100644
index 0000000000..db955da067
--- /dev/null
+++ b/browser/components/installerprefs/test/unit/test_pref_values.js
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+/**
+ * Test that pref values are reflected correctly.
+ */
+
+add_task(function() {
+ const PREFS_LIST = ["installer.true.value", "installer.false.value"];
+
+ Services.prefs.setBoolPref("installer.true.value", true);
+ Services.prefs.setBoolPref("installer.false.value", false);
+
+ startModule(PREFS_LIST);
+
+ verifyReflectedPrefs(PREFS_LIST);
+});
diff --git a/browser/components/installerprefs/test/unit/xpcshell.ini b/browser/components/installerprefs/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..f0ee7e3cea
--- /dev/null
+++ b/browser/components/installerprefs/test/unit/xpcshell.ini
@@ -0,0 +1,20 @@
+[DEFAULT]
+head = head.js
+firefox-appdir = browser
+skip-if = os != 'win'
+
+# These tests must all run sequentially because they use the same registry key.
+# It might be possible to get around this requirement by overriding the install
+# hash so each test uses a different key, and if a lot more tests are added here
+# then it would be worth looking into that.
+[test_empty_prefs_list.js]
+run-sequentially = Uses the Windows registry
+skip-if = os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807932
+[test_invalid_name.js]
+run-sequentially = Uses the Windows registry
+[test_nonbool_pref.js]
+run-sequentially = Uses the Windows registry
+[test_pref_change.js]
+run-sequentially = Uses the Windows registry
+[test_pref_values.js]
+run-sequentially = Uses the Windows registry