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 file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * Helper object for APIs that deal with DOMRequests and Promises.
7 * It allows objects inheriting from it to create and keep track of DOMRequests
8 * and Promises objects in the common scenario where requests are created in
9 * the child, handed out to content and delivered to the parent within an async
10 * message (containing the identifiers of these requests). The parent may send
11 * messages back as answers to different requests and the child will use this
12 * helper to get the right request object. This helper also takes care of
13 * releasing the requests objects when the window goes out of scope.
15 * DOMRequestIPCHelper also deals with message listeners, allowing to add them
16 * to the child side of frame and process message manager and removing them
19 var EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];
21 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
23 function DOMRequestIpcHelper() {
24 // _listeners keeps a list of messages for which we added a listener and the
25 // kind of listener that we added (strong or weak). It's an object of this
32 // where each property is the name of the message and its value is a boolean
33 // that indicates if the listener is weak or not.
34 this._listeners = null;
35 this._requests = null;
39 DOMRequestIpcHelper.prototype = {
41 * An object which "inherits" from DOMRequestIpcHelper and declares its own
42 * queryInterface method MUST implement Ci.nsISupportsWeakReference.
44 QueryInterface: ChromeUtils.generateQI([
45 "nsISupportsWeakReference",
50 * 'aMessages' is expected to be an array of either:
51 * - objects of this form:
53 * name: "messageName",
56 * where 'name' is the message identifier and 'weakRef' a boolean
57 * indicating if the listener should be a weak referred one or not.
59 * - or only strings containing the message name, in which case the listener
60 * will be added as a strong reference by default.
62 addMessageListeners(aMessages) {
67 if (!this._listeners) {
71 if (!Array.isArray(aMessages)) {
72 aMessages = [aMessages];
75 aMessages.forEach(aMsg => {
76 let name = aMsg.name || aMsg;
77 // If the listener is already set and it is of the same type we just
78 // increase the count and bail out. If it is not of the same type,
79 // we throw an exception.
80 if (this._listeners[name] != undefined) {
81 if (!!aMsg.weakRef == this._listeners[name].weakRef) {
82 this._listeners[name].count++;
85 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
90 ? Services.cpmm.addWeakMessageListener(name, this)
91 : Services.cpmm.addMessageListener(name, this);
92 this._listeners[name] = {
93 weakRef: !!aMsg.weakRef,
100 * 'aMessages' is expected to be a string or an array of strings containing
101 * the message names of the listeners to be removed.
103 removeMessageListeners(aMessages) {
104 if (!this._listeners || !aMessages) {
108 if (!Array.isArray(aMessages)) {
109 aMessages = [aMessages];
112 aMessages.forEach(aName => {
113 if (this._listeners[aName] == undefined) {
117 // Only remove the listener really when we don't have anybody that could
118 // be waiting on a message.
119 if (!--this._listeners[aName].count) {
120 this._listeners[aName].weakRef
121 ? Services.cpmm.removeWeakMessageListener(aName, this)
122 : Services.cpmm.removeMessageListener(aName, this);
123 delete this._listeners[aName];
129 * Initialize the helper adding the corresponding listeners to the messages
130 * provided as the second parameter.
132 * 'aMessages' is expected to be an array of either:
134 * - objects of this form:
136 * name: 'messageName',
139 * where 'name' is the message identifier and 'weakRef' a boolean
140 * indicating if the listener should be a weak referred one or not.
142 * - or only strings containing the message name, in which case the listener
143 * will be added as a strong referred one by default.
145 initDOMRequestHelper(aWindow, aMessages) {
146 // Query our required interfaces to force a fast fail if they are not
147 // provided. These calls will throw if the interface is not available.
148 this.QueryInterface(Ci.nsISupportsWeakReference);
149 this.QueryInterface(Ci.nsIObserver);
152 this.addMessageListeners(aMessages);
155 this._id = this._getRandomId();
157 this._window = aWindow;
159 // We don't use this.innerWindowID, but other classes rely on it.
160 this.innerWindowID = this._window.windowGlobalChild.innerWindowId;
163 this._destroyed = false;
165 Services.obs.addObserver(
167 "inner-window-destroyed",
172 destroyDOMRequestHelper() {
173 if (this._destroyed) {
177 this._destroyed = true;
179 Services.obs.removeObserver(this, "inner-window-destroyed");
181 if (this._listeners) {
182 Object.keys(this._listeners).forEach(aName => {
183 this._listeners[aName].weakRef
184 ? Services.cpmm.removeWeakMessageListener(aName, this)
185 : Services.cpmm.removeMessageListener(aName, this);
189 this._listeners = null;
190 this._requests = null;
192 // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
200 observe(aSubject, aTopic, aData) {
201 if (aTopic !== "inner-window-destroyed") {
205 let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
206 if (wId != this.innerWindowID) {
210 this.destroyDOMRequestHelper();
213 getRequestId(aRequest) {
214 if (!this._requests) {
218 let id = "id" + this._getRandomId();
219 this._requests[id] = aRequest;
223 getPromiseResolverId(aPromiseResolver) {
224 // Delegates to getRequest() since the lookup table is agnostic about
226 return this.getRequestId(aPromiseResolver);
230 if (this._requests && this._requests[aId]) {
231 return this._requests[aId];
235 getPromiseResolver(aId) {
236 // Delegates to getRequest() since the lookup table is agnostic about
238 return this.getRequest(aId);
242 if (this._requests && this._requests[aId]) {
243 delete this._requests[aId];
247 removePromiseResolver(aId) {
248 // Delegates to getRequest() since the lookup table is agnostic about
250 this.removeRequest(aId);
254 if (!this._requests || !this._requests[aId]) {
257 let request = this._requests[aId];
258 delete this._requests[aId];
262 takePromiseResolver(aId) {
263 // Delegates to getRequest() since the lookup table is agnostic about
265 return this.takeRequest(aId);
269 return Cc["@mozilla.org/uuid-generator;1"]
270 .getService(Ci.nsIUUIDGenerator)
276 // If we don't have a valid window object, throw.
279 "DOMRequestHelper trying to create a DOMRequest without a valid window, failing."
281 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
283 return Services.DOMRequest.createRequest(this._window);
287 * createPromise() creates a new Promise, with `aPromiseInit` as the
288 * PromiseInit callback. The promise constructor is obtained from the
289 * reference to window owned by this DOMRequestIPCHelper.
291 createPromise(aPromiseInit) {
292 // If we don't have a valid window object, throw.
295 "DOMRequestHelper trying to create a Promise without a valid window, failing."
297 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
299 return new this._window.Promise(aPromiseInit);
303 * createPromiseWithId() creates a new Promise, accepting a callback
304 * which is immediately called with the generated resolverId.
306 createPromiseWithId(aCallback) {
307 return this.createPromise((aResolve, aReject) => {
308 let resolverId = this.getPromiseResolverId({
312 aCallback(resolverId);
316 forEachRequest(aCallback) {
317 if (!this._requests) {
321 Object.keys(this._requests).forEach(aKey => {
322 if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
328 forEachPromiseResolver(aCallback) {
329 if (!this._requests) {
333 Object.keys(this._requests).forEach(aKey => {
335 "resolve" in this.getPromiseResolver(aKey) &&
336 "reject" in this.getPromiseResolver(aKey)