MockRegistrar.sys.mjs (4666B)
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 const Cm = Components.manager; 6 7 export var MockRegistrar = Object.freeze({ 8 _registeredComponents: new Map(), 9 _originalCIDs: new Map(), 10 get registrar() { 11 return Cm.QueryInterface(Ci.nsIComponentRegistrar); 12 }, 13 14 /** 15 * Register a mock to override target interfaces. 16 * The target interface may be accessed through _genuine property of the mock. 17 * If you register multiple mocks to the same contract ID, you have to call 18 * unregister in reverse order. Otherwise the previous factory will not be 19 * restored. 20 * 21 * @param contractID The contract ID of the interface which is overridden by 22 the mock. 23 * e.g. "@mozilla.org/file/directory_service;1" 24 * @param mock An object which implements interfaces for the contract ID. 25 * @param args An array which is passed in the constructor of mock. 26 * 27 * @return The CID of the mock. 28 */ 29 register(contractID, mock, args) { 30 return this.registerEx( 31 contractID, 32 { shouldCreateInstance: true }, 33 mock, 34 args 35 ); 36 }, 37 38 /** 39 * Register a mock to override target interfaces. 40 * If shouldCreateInstance is true then the target interface may be accessed 41 * through _genuine property of the mock. 42 * If you register multiple mocks to the same contract ID, you have to call 43 * unregister in reverse order. Otherwise the previous factory will not be 44 * restored. 45 * 46 * @param contractID The contract ID of the interface which is overridden by 47 the mock. 48 * e.g. "@mozilla.org/file/directory_service;1" 49 * @param options Options object with any of the following optional 50 * parameters: 51 * * shouldCreateInstance: Adds the _genuine property to 52 * the mock. 53 * @param mock An object which implements interfaces for the contract ID. 54 * @param args An array which is passed in the constructor of mock. 55 * 56 * @return The CID of the mock. 57 */ 58 registerEx(contractID, options, mock, args) { 59 let originalCID; 60 let originalFactory; 61 try { 62 originalCID = this._originalCIDs.get(contractID); 63 if (!originalCID) { 64 originalCID = this.registrar.contractIDToCID(contractID); 65 this._originalCIDs.set(contractID, originalCID); 66 } 67 68 originalFactory = Cm.getClassObject(originalCID, Ci.nsIFactory); 69 } catch (e) { 70 // There's no original factory. Ignore and just register the new 71 // one. 72 } 73 74 let cid = Services.uuid.generateUUID(); 75 76 let factory = { 77 createInstance(iid) { 78 let wrappedMock; 79 if (mock.prototype && mock.prototype.constructor) { 80 wrappedMock = Object.create(mock.prototype); 81 mock.apply(wrappedMock, args); 82 } else if (typeof mock == "function") { 83 wrappedMock = mock(); 84 } else { 85 wrappedMock = mock; 86 } 87 88 if (originalFactory && options.shouldCreateInstance) { 89 try { 90 let genuine = originalFactory.createInstance(iid); 91 wrappedMock._genuine = genuine; 92 } catch (ex) { 93 console.error( 94 "MockRegistrar: Creating original instance failed", 95 ex 96 ); 97 } 98 } 99 100 return wrappedMock.QueryInterface(iid); 101 }, 102 QueryInterface: ChromeUtils.generateQI(["nsIFactory"]), 103 }; 104 105 this.registrar.registerFactory( 106 cid, 107 "A Mock for " + contractID, 108 contractID, 109 factory 110 ); 111 112 this._registeredComponents.set(cid, { 113 contractID, 114 factory, 115 originalCID, 116 }); 117 118 return cid; 119 }, 120 121 /** 122 * Unregister the mock. 123 * 124 * @param cid The CID of the mock. 125 */ 126 unregister(cid) { 127 let component = this._registeredComponents.get(cid); 128 if (!component) { 129 return; 130 } 131 132 this.registrar.unregisterFactory(cid, component.factory); 133 if (component.originalCID) { 134 // Passing `null` for the factory re-maps the contract ID to the 135 // entry for its original CID. 136 this.registrar.registerFactory( 137 component.originalCID, 138 "", 139 component.contractID, 140 null 141 ); 142 } 143 144 this._registeredComponents.delete(cid); 145 }, 146 147 /** 148 * Unregister all registered mocks. 149 */ 150 unregisterAll() { 151 for (let cid of this._registeredComponents.keys()) { 152 this.unregister(cid); 153 } 154 }, 155 });