Source code

Revision control

Copy as Markdown

Other Tools

/* 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 { MockRegistrar } from "resource://testing-common/MockRegistrar.sys.mjs";
class MockWindowsRegKey {
key = null;
// --- Overridden nsISupports interface functions ---
QueryInterface = ChromeUtils.generateQI(["nsIWindowsRegKey"]);
#assertKey() {
if (this.key) {
return;
}
throw Components.Exception("invalid registry path", Cr.NS_ERROR_FAILURE);
}
#findOrMaybeCreateKey(root, path, mode, maybeCreateCallback) {
let rootKey = MockRegistry.getRoot(root);
let parts = path.split("\\");
if (parts.some(part => !part.length)) {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
let key = rootKey;
for (let part of parts) {
if (!key.subkeys.has(part)) {
maybeCreateCallback(key.subkeys, part);
}
key = key.subkeys.get(part);
}
this.key = key;
}
// --- Overridden nsIWindowsRegKey interface functions ---
open(root, path, mode) {
// eslint-disable-next-line no-unused-vars
this.#findOrMaybeCreateKey(root, path, mode, (subkeys, part) => {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
});
}
create(root, path, mode) {
this.#findOrMaybeCreateKey(root, path, mode, (subkeys, part) =>
subkeys.set(part, { subkeys: new Map(), values: new Map() })
);
}
close() {
this.key = null;
}
get valueCount() {
this.#assertKey();
return this.key.values.size;
}
hasValue(name) {
this.#assertKey();
return this.key.values.has(name);
}
#getValuePair(name, expectedType = null) {
this.#assertKey();
if (!this.key.values.has(name)) {
throw Components.Exception("invalid value name", Cr.NS_ERROR_FAILURE);
}
let [value, type] = this.key.values.get(name);
if (expectedType && type !== expectedType) {
throw Components.Exception("unexpected value type", Cr.NS_ERROR_FAILURE);
}
return [value, type];
}
getValueType(name) {
let [, type] = this.#getValuePair(name);
return type;
}
getValueName(index) {
if (!this.key || index >= this.key.values.size) {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
let names = Array.from(this.key.values.keys());
return names[index];
}
readStringValue(name) {
let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_STRING);
return value;
}
readIntValue(name) {
let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_INT);
return value;
}
readInt64Value(name) {
let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_INT64);
return value;
}
readBinaryValue(name) {
let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_BINARY);
return value;
}
#writeValuePair(name, value, type) {
this.#assertKey();
this.key.values.set(name, [value, type]);
}
writeStringValue(name, value) {
this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_STRING);
}
writeIntValue(name, value) {
this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_INT);
}
writeInt64Value(name, value) {
this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_INT64);
}
writeBinaryValue(name, value) {
this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_BINARY);
}
removeValue(name) {
this.#assertKey();
this.key.values.delete(name);
}
get childCount() {
this.#assertKey();
return this.key.subkeys.size;
}
getChildName(index) {
if (!this.key || index >= this.key.values.size) {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
let names = Array.from(this.key.subkeys.keys());
return names[index];
}
hasChild(name) {
this.#assertKey();
return this.key.subkeys.has(name);
}
removeChild(name) {
this.#assertKey();
let child = this.key.subkeys.get(name);
if (!child) {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
if (child.subkeys.size > 0) {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
this.key.subkeys.delete(name);
}
#findOrMaybeCreateChild(name, mode, maybeCreateCallback) {
this.#assertKey();
if (name.split("\\").length > 1) {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
if (!this.key.subkeys.has(name)) {
maybeCreateCallback(this.key.subkeys, name);
}
// This won't wrap in the same way as `Cc["@mozilla.org/windows-registry-key;1"].createInstance(nsIWindowsRegKey);`.
let subKey = new MockWindowsRegKey();
subKey.key = this.key.subkeys.get(name);
return subKey;
}
openChild(name, mode) {
// eslint-disable-next-line no-unused-vars
return this.#findOrMaybeCreateChild(name, mode, (subkeys, part) => {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
});
}
createChild(name, mode) {
return this.#findOrMaybeCreateChild(name, mode, (subkeys, part) =>
subkeys.set(part, { subkeys: new Map(), values: new Map() })
);
}
}
export class MockRegistry {
// All instances of `MockRegistry` share a single data-store; this is
// conceptually parallel to the Windows registry, which is a shared global
// resource. It would be possible to have separate data-stores for separate
// instances with a little adjustment to `MockWindowsRegKey`.
//
// Top-level map is indexed by roots. A "key" is an object that has
// `subkeys` and `values`, both maps indexed by strings. Subkey items are
// again "key" objects. Value items are `[value, type]` pairs.
//
// In pseudo-code:
//
// {Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER:
// {subkeys:
// {child: {subkeys: {}, values: {key: ["string_value", Ci.nsIWindowsRegKey.TYPE_STRING]}}},
// values: {}
// },
// ...
// }
static roots;
constructor() {
MockRegistry.roots = new Map([
[
Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
{ subkeys: new Map(), values: new Map() },
],
[
Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
{ subkeys: new Map(), values: new Map() },
],
[
Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT,
{ subkeys: new Map(), values: new Map() },
],
]);
// See bug 1688838 - nsNotifyAddrListener::CheckAdaptersAddresses might
// attempt to use the registry off the main thread, so we disable that
// feature while the mock registry is active.
this.oldSuffixListPref = Services.prefs.getBoolPref(
"network.notify.dnsSuffixList"
);
Services.prefs.setBoolPref("network.notify.dnsSuffixList", false);
this.oldCheckForProxiesPref = Services.prefs.getBoolPref(
"network.notify.checkForProxies"
);
Services.prefs.setBoolPref("network.notify.checkForProxies", false);
this.oldCheckForNRPTPref = Services.prefs.getBoolPref(
"network.notify.checkForNRPT"
);
Services.prefs.setBoolPref("network.notify.checkForNRPT", false);
this.cid = MockRegistrar.register(
"@mozilla.org/windows-registry-key;1",
() => new MockWindowsRegKey()
);
}
shutdown() {
MockRegistrar.unregister(this.cid);
Services.prefs.setBoolPref(
"network.notify.dnsSuffixList",
this.oldSuffixListPref
);
Services.prefs.setBoolPref(
"network.notify.checkForProxies",
this.oldCheckForProxiesPref
);
Services.prefs.setBoolPref(
"network.notify.checkForNRPT",
this.oldCheckForNRPTPref
);
this.cid = null;
}
static getRoot(root) {
if (!this.roots.has(root)) {
throw new Error(`No such root ${root}`);
}
return this.roots.get(root);
}
setValue(root, path, name, value) {
let key = new MockWindowsRegKey();
key.create(root, path, Ci.nsIWindowsRegKey.ACCESS_ALL);
if (value == null) {
try {
key.removeValue(name);
} catch (e) {
if (
!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)
) {
throw e;
}
}
} else {
key.writeStringValue(name, value);
}
}
/**
* Dump given `key` (or, if not given, all roots), and all its value and its
* subkeys recursively, using the given function to `printOneLine`.
*/
static dump(key = null, indent = "", printOneLine = console.log) {
let types = new Map([
[1, "REG_SZ"],
[3, "REG_BINARY"],
[4, "REG_DWORD"],
[11, "REG_QWORD"],
]);
if (!key) {
let roots = [
"ROOT_KEY_LOCAL_MACHINE",
"ROOT_KEY_CURRENT_USER",
"ROOT_KEY_CLASSES_ROOT",
];
for (let root of roots) {
printOneLine(indent + root);
this.dump(
this.roots.get(Ci.nsIWindowsRegKey[root]),
" " + indent,
printOneLine
);
}
} else {
for (let [k, v] of key.values.entries()) {
let [value, type] = v;
printOneLine(`${indent}${k}: ${value} (${types.get(type)})`);
}
for (let [k, child] of key.subkeys.entries()) {
printOneLine(indent + k);
this.dump(child, " " + indent, printOneLine);
}
}
}
}