From a90a5cba08fdf6c0ceb95101c275108a152a3aed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:35:37 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- testing/modules/MockRegistry.sys.mjs | 347 +++++++++++++++++++++++++++-------- 1 file changed, 269 insertions(+), 78 deletions(-) (limited to 'testing/modules') diff --git a/testing/modules/MockRegistry.sys.mjs b/testing/modules/MockRegistry.sys.mjs index b64d1ce2a8..acb17d790b 100644 --- a/testing/modules/MockRegistry.sys.mjs +++ b/testing/modules/MockRegistry.sys.mjs @@ -4,81 +4,231 @@ import { MockRegistrar } from "resource://testing-common/MockRegistrar.sys.mjs"; -export class MockRegistry { - constructor() { - // Three level structure of Maps pointing to Maps pointing to Maps - // this.roots is the top of the structure and has ROOT_KEY_* values - // as keys. Maps at the second level are the values of the first - // level Map, they have registry keys (also called paths) as keys. - // Third level maps are the values in second level maps, they have - // map registry names to corresponding values (which in this implementation - // are always strings). - this.roots = new Map([ - [Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, new Map()], - [Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, new Map()], - [Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT, new Map()], - ]); +class MockWindowsRegKey { + key = null; - let registry = this; - - /** - * This is a mock nsIWindowsRegistry implementation. It only implements a - * subset of the interface used in tests. In particular, only values - * of type string are supported. - */ - function MockWindowsRegKey() {} - MockWindowsRegKey.prototype = { - values: null, - - // --- Overridden nsISupports interface functions --- - QueryInterface: ChromeUtils.generateQI(["nsIWindowsRegKey"]), - - // --- Overridden nsIWindowsRegKey interface functions --- - open(root, path) { - let rootKey = registry.getRoot(root); - if (!rootKey.has(path)) { - rootKey.set(path, new Map()); - } - this.values = rootKey.get(path); - }, + // --- Overridden nsISupports interface functions --- + QueryInterface = ChromeUtils.generateQI(["nsIWindowsRegKey"]); - close() { - this.values = null; - }, + #assertKey() { + if (this.key) { + return; + } + throw Components.Exception("invalid registry path", Cr.NS_ERROR_FAILURE); + } - get valueCount() { - if (!this.values) { - throw Components.Exception("", Cr.NS_ERROR_FAILURE); - } - return this.values.size; - }, + #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); + } - hasValue(name) { - if (!this.values) { - return false; - } - return this.values.has(name); - }, + 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; + } - getValueType() { - return Ci.nsIWindowsRegKey.TYPE_STRING; - }, + // --- 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); + }); + } - getValueName(index) { - if (!this.values || index >= this.values.size) { - throw Components.Exception("", Cr.NS_ERROR_FAILURE); - } - let names = Array.from(this.values.keys()); - return names[index]; - }, + create(root, path, mode) { + this.#findOrMaybeCreateKey(root, path, mode, (subkeys, part) => + subkeys.set(part, { subkeys: new Map(), values: new Map() }) + ); + } - readStringValue(name) { - if (!this.values) { - throw new Error("invalid registry path"); - } - return this.values.get(name); - }, - }; + 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 @@ -100,7 +250,7 @@ export class MockRegistry { this.cid = MockRegistrar.register( "@mozilla.org/windows-registry-key;1", - MockWindowsRegKey + () => new MockWindowsRegKey() ); } @@ -121,7 +271,7 @@ export class MockRegistry { this.cid = null; } - getRoot(root) { + static getRoot(root) { if (!this.roots.has(root)) { throw new Error(`No such root ${root}`); } @@ -129,17 +279,58 @@ export class MockRegistry { } setValue(root, path, name, value) { - let rootKey = this.getRoot(root); - - if (!rootKey.has(path)) { - rootKey.set(path, new Map()); + 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); } + } - let pathmap = rootKey.get(path); - if (value == null) { - pathmap.delete(name); + /** + * 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 { - pathmap.set(name, value); + 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); + } } } } -- cgit v1.2.3