summaryrefslogtreecommitdiffstats
path: root/comm/mail/test/browser/shared-modules/MockObjectHelpers.jsm
blob: 3b4ed91e538c9e6e2e57204f96b40c6dd28eaf4f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const EXPORTED_SYMBOLS = ["MockObjectReplacer", "MockObjectRegisterer"];

var Cm = Components.manager;

function MockObjectRegisterer(aContractID, aCID, aComponent) {
  this._contractID = aContractID;
  this._cid = Components.ID("{" + aCID + "}");
  this._component = aComponent;
}

MockObjectRegisterer.prototype = {
  register() {
    let providedConstructor = this._component;
    this._mockFactory = {
      createInstance(aIid) {
        return new providedConstructor().QueryInterface(aIid);
      },
    };

    let componentRegistrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);

    componentRegistrar.registerFactory(
      this._cid,
      "",
      this._contractID,
      this._mockFactory
    );
  },

  unregister() {
    let componentRegistrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);

    componentRegistrar.unregisterFactory(this._cid, this._mockFactory);
  },
};

/**
 * Allows registering a mock XPCOM component, that temporarily replaces the
 *  original one when an object implementing a given ContractID is requested
 *  using createInstance.
 *
 * @param aContractID
 *        The ContractID of the component to replace, for example
 *        "@mozilla.org/filepicker;1".
 *
 * @param aReplacementCtor
 *        The constructor function for the JavaScript object that will be
 *        created every time createInstance is called. This object must
 *        implement QueryInterface and provide the XPCOM interfaces required by
 *        the specified ContractID (for example
 *        Ci.nsIFilePicker).
 */

function MockObjectReplacer(aContractID, aReplacementCtor) {
  this._contractID = aContractID;
  this._replacementCtor = aReplacementCtor;
  this._cid = null;
}

MockObjectReplacer.prototype = {
  /**
   * Replaces the current factory with one that returns a new mock object.
   *
   * After register() has been called, it is mandatory to call unregister() to
   * restore the original component. Usually, you should use a try-catch block
   * to ensure that unregister() is called.
   */
  register() {
    if (this._cid) {
      throw Error("Invalid object state when calling register()");
    }

    // Define a factory that creates a new object using the given constructor.
    var providedConstructor = this._replacementCtor;
    this._mockFactory = {
      createInstance(aIid) {
        return new providedConstructor().QueryInterface(aIid);
      },
    };

    var retVal = swapFactoryRegistration(
      this._cid,
      this._originalCID,
      this._contractID,
      this._mockFactory
    );
    if ("error" in retVal) {
      throw new Error("ERROR: " + retVal.error);
    } else {
      this._cid = retVal.cid;
      this._originalCID = retVal.originalCID;
    }
  },

  /**
   * Restores the original factory.
   */
  unregister() {
    if (!this._cid) {
      throw Error("Invalid object state when calling unregister()");
    }

    // Free references to the mock factory.
    swapFactoryRegistration(
      this._cid,
      this._originalCID,
      this._contractID,
      this._mockFactory
    );

    // Allow registering a mock factory again later.
    this._cid = null;
    this._originalCID = null;
    this._mockFactory = null;
  },

  // --- Private methods and properties ---

  /**
   * The CID under which the mock contractID was registered.
   */
  _cid: null,

  /**
   * The nsIFactory that was automatically generated by this object.
   */
  _mockFactory: null,
};

/**
 * Swiped from mozilla/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
 */
function swapFactoryRegistration(CID, originalCID, contractID, newFactory) {
  let componentRegistrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);

  if (originalCID == null) {
    if (contractID != null) {
      originalCID = componentRegistrar.contractIDToCID(contractID);
      void Cm.getClassObject(Cc[contractID], Ci.nsIFactory);
    } else {
      return {
        error: "trying to register a new contract ID: Missing contractID",
      };
    }
    CID = Services.uuid.generateUUID();

    componentRegistrar.registerFactory(CID, "", contractID, newFactory);
  } else {
    componentRegistrar.unregisterFactory(CID, newFactory);
    // Restore the original factory.
    componentRegistrar.registerFactory(originalCID, "", contractID, null);
  }

  return { cid: CID, originalCID };
}