MockRegistry.sys.mjs (9745B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { MockRegistrar } from "resource://testing-common/MockRegistrar.sys.mjs"; 6 7 class MockWindowsRegKey { 8 key = null; 9 10 // --- Overridden nsISupports interface functions --- 11 QueryInterface = ChromeUtils.generateQI(["nsIWindowsRegKey"]); 12 13 #assertKey() { 14 if (this.key) { 15 return; 16 } 17 throw Components.Exception("invalid registry path", Cr.NS_ERROR_FAILURE); 18 } 19 20 #findOrMaybeCreateKey(root, path, mode, maybeCreateCallback) { 21 let rootKey = MockRegistry.getRoot(root); 22 let parts = path.split("\\"); 23 if (parts.some(part => !part.length)) { 24 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 25 } 26 27 let key = rootKey; 28 for (let part of parts) { 29 if (!key.subkeys.has(part)) { 30 maybeCreateCallback(key.subkeys, part); 31 } 32 key = key.subkeys.get(part); 33 } 34 this.key = key; 35 } 36 37 // --- Overridden nsIWindowsRegKey interface functions --- 38 open(root, path, mode) { 39 // eslint-disable-next-line no-unused-vars 40 this.#findOrMaybeCreateKey(root, path, mode, (subkeys, part) => { 41 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 42 }); 43 } 44 45 create(root, path, mode) { 46 this.#findOrMaybeCreateKey(root, path, mode, (subkeys, part) => 47 subkeys.set(part, { subkeys: new Map(), values: new Map() }) 48 ); 49 } 50 51 close() { 52 this.key = null; 53 } 54 55 get valueCount() { 56 this.#assertKey(); 57 return this.key.values.size; 58 } 59 60 hasValue(name) { 61 this.#assertKey(); 62 return this.key.values.has(name); 63 } 64 65 #getValuePair(name, expectedType = null) { 66 this.#assertKey(); 67 if (!this.key.values.has(name)) { 68 throw Components.Exception("invalid value name", Cr.NS_ERROR_FAILURE); 69 } 70 let [value, type] = this.key.values.get(name); 71 if (expectedType && type !== expectedType) { 72 throw Components.Exception("unexpected value type", Cr.NS_ERROR_FAILURE); 73 } 74 return [value, type]; 75 } 76 77 getValueType(name) { 78 let [, type] = this.#getValuePair(name); 79 return type; 80 } 81 82 getValueName(index) { 83 if (!this.key || index >= this.key.values.size) { 84 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 85 } 86 let names = Array.from(this.key.values.keys()); 87 return names[index]; 88 } 89 90 readStringValue(name) { 91 let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_STRING); 92 return value; 93 } 94 95 readIntValue(name) { 96 let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_INT); 97 return value; 98 } 99 100 readInt64Value(name) { 101 let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_INT64); 102 return value; 103 } 104 105 readBinaryValue(name) { 106 let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_BINARY); 107 return value; 108 } 109 110 #writeValuePair(name, value, type) { 111 this.#assertKey(); 112 this.key.values.set(name, [value, type]); 113 } 114 115 writeStringValue(name, value) { 116 this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_STRING); 117 } 118 119 writeIntValue(name, value) { 120 this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_INT); 121 } 122 123 writeInt64Value(name, value) { 124 this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_INT64); 125 } 126 127 writeBinaryValue(name, value) { 128 this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_BINARY); 129 } 130 131 removeValue(name) { 132 this.#assertKey(); 133 this.key.values.delete(name); 134 } 135 136 get childCount() { 137 this.#assertKey(); 138 return this.key.subkeys.size; 139 } 140 141 getChildName(index) { 142 if (!this.key || index >= this.key.values.size) { 143 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 144 } 145 let names = Array.from(this.key.subkeys.keys()); 146 return names[index]; 147 } 148 149 hasChild(name) { 150 this.#assertKey(); 151 return this.key.subkeys.has(name); 152 } 153 154 removeChild(name) { 155 this.#assertKey(); 156 let child = this.key.subkeys.get(name); 157 158 if (!child) { 159 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 160 } 161 if (child.subkeys.size > 0) { 162 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 163 } 164 165 this.key.subkeys.delete(name); 166 } 167 168 #findOrMaybeCreateChild(name, mode, maybeCreateCallback) { 169 this.#assertKey(); 170 if (name.split("\\").length > 1) { 171 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 172 } 173 if (!this.key.subkeys.has(name)) { 174 maybeCreateCallback(this.key.subkeys, name); 175 } 176 // This won't wrap in the same way as `Cc["@mozilla.org/windows-registry-key;1"].createInstance(nsIWindowsRegKey);`. 177 let subKey = new MockWindowsRegKey(); 178 subKey.key = this.key.subkeys.get(name); 179 return subKey; 180 } 181 182 openChild(name, mode) { 183 // eslint-disable-next-line no-unused-vars 184 return this.#findOrMaybeCreateChild(name, mode, (subkeys, part) => { 185 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 186 }); 187 } 188 189 createChild(name, mode) { 190 return this.#findOrMaybeCreateChild(name, mode, (subkeys, part) => 191 subkeys.set(part, { subkeys: new Map(), values: new Map() }) 192 ); 193 } 194 } 195 196 export class MockRegistry { 197 // All instances of `MockRegistry` share a single data-store; this is 198 // conceptually parallel to the Windows registry, which is a shared global 199 // resource. It would be possible to have separate data-stores for separate 200 // instances with a little adjustment to `MockWindowsRegKey`. 201 // 202 // Top-level map is indexed by roots. A "key" is an object that has 203 // `subkeys` and `values`, both maps indexed by strings. Subkey items are 204 // again "key" objects. Value items are `[value, type]` pairs. 205 // 206 // In pseudo-code: 207 // 208 // {Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER: 209 // {subkeys: 210 // {child: {subkeys: {}, values: {key: ["string_value", Ci.nsIWindowsRegKey.TYPE_STRING]}}}, 211 // values: {} 212 // }, 213 // ... 214 // } 215 static roots; 216 217 constructor() { 218 MockRegistry.roots = new Map([ 219 [ 220 Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, 221 { subkeys: new Map(), values: new Map() }, 222 ], 223 [ 224 Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, 225 { subkeys: new Map(), values: new Map() }, 226 ], 227 [ 228 Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT, 229 { subkeys: new Map(), values: new Map() }, 230 ], 231 ]); 232 233 // See bug 1688838 - nsNotifyAddrListener::CheckAdaptersAddresses might 234 // attempt to use the registry off the main thread, so we disable that 235 // feature while the mock registry is active. 236 this.oldSuffixListPref = Services.prefs.getBoolPref( 237 "network.notify.dnsSuffixList" 238 ); 239 Services.prefs.setBoolPref("network.notify.dnsSuffixList", false); 240 241 this.oldCheckForProxiesPref = Services.prefs.getBoolPref( 242 "network.notify.checkForProxies" 243 ); 244 Services.prefs.setBoolPref("network.notify.checkForProxies", false); 245 246 this.oldCheckForNRPTPref = Services.prefs.getBoolPref( 247 "network.notify.checkForNRPT" 248 ); 249 Services.prefs.setBoolPref("network.notify.checkForNRPT", false); 250 251 this.cid = MockRegistrar.register( 252 "@mozilla.org/windows-registry-key;1", 253 () => new MockWindowsRegKey() 254 ); 255 } 256 257 shutdown() { 258 MockRegistrar.unregister(this.cid); 259 Services.prefs.setBoolPref( 260 "network.notify.dnsSuffixList", 261 this.oldSuffixListPref 262 ); 263 Services.prefs.setBoolPref( 264 "network.notify.checkForProxies", 265 this.oldCheckForProxiesPref 266 ); 267 Services.prefs.setBoolPref( 268 "network.notify.checkForNRPT", 269 this.oldCheckForNRPTPref 270 ); 271 this.cid = null; 272 } 273 274 static getRoot(root) { 275 if (!this.roots.has(root)) { 276 throw new Error(`No such root ${root}`); 277 } 278 return this.roots.get(root); 279 } 280 281 setValue(root, path, name, value, type = Ci.nsIWindowsRegKey.TYPE_STRING) { 282 let key = new MockWindowsRegKey(); 283 key.create(root, path, Ci.nsIWindowsRegKey.ACCESS_ALL); 284 if (value == null) { 285 try { 286 key.removeValue(name); 287 } catch (e) { 288 if ( 289 !(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE) 290 ) { 291 throw e; 292 } 293 } 294 } else { 295 switch (type) { 296 case Ci.nsIWindowsRegKey.TYPE_STRING: 297 key.writeStringValue(name, value); 298 break; 299 case Ci.nsIWindowsRegKey.TYPE_BINARY: 300 key.writeBinaryValue(name, value); 301 break; 302 case Ci.nsIWindowsRegKey.TYPE_INT: 303 key.writeIntValue(name, value); 304 break; 305 case Ci.nsIWindowsRegKey.TYPE_INT64: 306 key.writeInt64Value(name, value); 307 break; 308 } 309 } 310 } 311 312 /** 313 * Dump given `key` (or, if not given, all roots), and all its value and its 314 * subkeys recursively, using the given function to `printOneLine`. 315 */ 316 static dump(key = null, indent = "", printOneLine = console.log) { 317 let types = new Map([ 318 [1, "REG_SZ"], 319 [3, "REG_BINARY"], 320 [4, "REG_DWORD"], 321 [11, "REG_QWORD"], 322 ]); 323 324 if (!key) { 325 let roots = [ 326 "ROOT_KEY_LOCAL_MACHINE", 327 "ROOT_KEY_CURRENT_USER", 328 "ROOT_KEY_CLASSES_ROOT", 329 ]; 330 for (let root of roots) { 331 printOneLine(indent + root); 332 this.dump( 333 this.roots.get(Ci.nsIWindowsRegKey[root]), 334 " " + indent, 335 printOneLine 336 ); 337 } 338 } else { 339 for (let [k, v] of key.values.entries()) { 340 let [value, type] = v; 341 printOneLine(`${indent}${k}: ${value} (${types.get(type)})`); 342 } 343 for (let [k, child] of key.subkeys.entries()) { 344 printOneLine(indent + k); 345 this.dump(child, " " + indent, printOneLine); 346 } 347 } 348 } 349 }