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"use strict"; 6 7this.EXPORTED_SYMBOLS = [ 8 "MockRegistrar", 9]; 10 11const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components; 12 13Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 14Cu.import("resource://gre/modules/Log.jsm"); 15var logger = Log.repository.getLogger("MockRegistrar"); 16 17this.MockRegistrar = Object.freeze({ 18 _registeredComponents: new Map(), 19 _originalCIDs: new Map(), 20 get registrar() { 21 return Cm.QueryInterface(Ci.nsIComponentRegistrar); 22 }, 23 24 /** 25 * Register a mock to override target interfaces. 26 * The target interface may be accessed through _genuine property of the mock. 27 * If you register multiple mocks to the same contract ID, you have to call 28 * unregister in reverse order. Otherwise the previous factory will not be 29 * restored. 30 * 31 * @param contractID The contract ID of the interface which is overridden by 32 the mock. 33 * e.g. "@mozilla.org/file/directory_service;1" 34 * @param mock An object which implements interfaces for the contract ID. 35 * @param args An array which is passed in the constructor of mock. 36 * 37 * @return The CID of the mock. 38 */ 39 register(contractID, mock, args) { 40 let originalCID = this._originalCIDs.get(contractID); 41 if (!originalCID) { 42 originalCID = this.registrar.contractIDToCID(contractID); 43 this._originalCIDs.set(contractID, originalCID); 44 } 45 46 let originalFactory = Cm.getClassObject(originalCID, Ci.nsIFactory); 47 48 let factory = { 49 createInstance(outer, iid) { 50 if (outer) { 51 throw Cr.NS_ERROR_NO_AGGREGATION; 52 } 53 54 let wrappedMock; 55 if (mock.prototype && mock.prototype.constructor) { 56 wrappedMock = Object.create(mock.prototype); 57 mock.apply(wrappedMock, args); 58 } else { 59 wrappedMock = mock; 60 } 61 62 try { 63 let genuine = originalFactory.createInstance(outer, iid); 64 wrappedMock._genuine = genuine; 65 } catch(ex) { 66 logger.info("Creating original instance failed", ex); 67 } 68 69 return wrappedMock.QueryInterface(iid); 70 }, 71 lockFactory(lock) { 72 throw Cr.NS_ERROR_NOT_IMPLEMENTED; 73 }, 74 QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) 75 }; 76 77 this.registrar.unregisterFactory(originalCID, originalFactory); 78 this.registrar.registerFactory(originalCID, 79 "A Mock for " + contractID, 80 contractID, 81 factory); 82 83 this._registeredComponents.set(originalCID, { 84 contractID: contractID, 85 factory: factory, 86 originalFactory: originalFactory 87 }); 88 89 return originalCID; 90 }, 91 92 /** 93 * Unregister the mock. 94 * 95 * @param cid The CID of the mock. 96 */ 97 unregister(cid) { 98 let component = this._registeredComponents.get(cid); 99 if (!component) { 100 return; 101 } 102 103 this.registrar.unregisterFactory(cid, component.factory); 104 if (component.originalFactory) { 105 this.registrar.registerFactory(cid, 106 "", 107 component.contractID, 108 component.originalFactory); 109 } 110 111 this._registeredComponents.delete(cid); 112 }, 113 114 /** 115 * Unregister all registered mocks. 116 */ 117 unregisterAll() { 118 for (let cid of this._registeredComponents.keys()) { 119 this.unregister(cid); 120 } 121 } 122 123}); 124