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