Bumping gaia.json for 8 gaia revision(s) a=gaia-bump
[gecko.git] / dom / base / DOMRequestHelper.jsm
blob5c51dce9980ef78a007cd98875518bfbe01f7833
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/. */
5 /**
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.
14  *
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
17  * when needed.
18  */
19 const Cu = Components.utils;
20 const Cc = Components.classes;
21 const Ci = Components.interfaces;
22 const Cr = Components.results;
24 this.EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];
26 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
27 Cu.import("resource://gre/modules/Services.jsm");
29 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
30                                    "@mozilla.org/childprocessmessagemanager;1",
31                                    "nsIMessageListenerManager");
33 this.DOMRequestIpcHelper = function DOMRequestIpcHelper() {
34   // _listeners keeps a list of messages for which we added a listener and the
35   // kind of listener that we added (strong or weak). It's an object of this
36   // form:
37   //  {
38   //    "message1": true,
39   //    "messagen": false
40   //  }
41   //
42   // where each property is the name of the message and its value is a boolean
43   // that indicates if the listener is weak or not.
44   this._listeners = null;
45   this._requests = null;
46   this._window = null;
49 DOMRequestIpcHelper.prototype = {
50   /**
51    * An object which "inherits" from DOMRequestIpcHelper and declares its own
52    * queryInterface method MUST implement Ci.nsISupportsWeakReference.
53    */
54   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
55                                          Ci.nsIObserver]),
57    /**
58    *  'aMessages' is expected to be an array of either:
59    *  - objects of this form:
60    *    {
61    *      name: "messageName",
62    *      weakRef: false
63    *    }
64    *    where 'name' is the message identifier and 'weakRef' a boolean
65    *    indicating if the listener should be a weak referred one or not.
66    *
67    *  - or only strings containing the message name, in which case the listener
68    *    will be added as a strong reference by default.
69    */
70   addMessageListeners: function(aMessages) {
71     if (!aMessages) {
72       return;
73     }
75     if (!this._listeners) {
76       this._listeners = {};
77     }
79     if (!Array.isArray(aMessages)) {
80       aMessages = [aMessages];
81     }
83     aMessages.forEach((aMsg) => {
84       let name = aMsg.name || aMsg;
85       // If the listener is already set and it is of the same type we just
86       // increase the count and bail out. If it is not of the same type,
87       // we throw an exception.
88       if (this._listeners[name] != undefined) {
89         if (!!aMsg.weakRef == this._listeners[name].weakRef) {
90           this._listeners[name].count++;
91           return;
92         } else {
93           throw Cr.NS_ERROR_FAILURE;
94         }
95       }
97       aMsg.weakRef ? cpmm.addWeakMessageListener(name, this)
98                    : cpmm.addMessageListener(name, this);
99       this._listeners[name] = {
100         weakRef: !!aMsg.weakRef,
101         count: 1
102       };
103     });
104   },
106   /**
107    * 'aMessages' is expected to be a string or an array of strings containing
108    * the message names of the listeners to be removed.
109    */
110   removeMessageListeners: function(aMessages) {
111     if (!this._listeners || !aMessages) {
112       return;
113     }
115     if (!Array.isArray(aMessages)) {
116       aMessages = [aMessages];
117     }
119     aMessages.forEach((aName) => {
120       if (this._listeners[aName] == undefined) {
121         return;
122       }
124       // Only remove the listener really when we don't have anybody that could
125       // be waiting on a message.
126       if (!--this._listeners[aName].count) {
127         this._listeners[aName].weakRef ?
128             cpmm.removeWeakMessageListener(aName, this)
129           : cpmm.removeMessageListener(aName, this);
130         delete this._listeners[aName];
131       }
132     });
133   },
135   /**
136    * Initialize the helper adding the corresponding listeners to the messages
137    * provided as the second parameter.
138    *
139    * 'aMessages' is expected to be an array of either:
140    *
141    *  - objects of this form:
142    *    {
143    *      name: 'messageName',
144    *      weakRef: false
145    *    }
146    *    where 'name' is the message identifier and 'weakRef' a boolean
147    *    indicating if the listener should be a weak referred one or not.
148    *
149    *  - or only strings containing the message name, in which case the listener
150    *    will be added as a strong referred one by default.
151    */
152   initDOMRequestHelper: function(aWindow, aMessages) {
153     // Query our required interfaces to force a fast fail if they are not
154     // provided. These calls will throw if the interface is not available.
155     this.QueryInterface(Ci.nsISupportsWeakReference);
156     this.QueryInterface(Ci.nsIObserver);
158     if (aMessages) {
159       this.addMessageListeners(aMessages);
160     }
162     this._id = this._getRandomId();
164     this._window = aWindow;
165     if (this._window) {
166       // We don't use this.innerWindowID, but other classes rely on it.
167       let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
168                              .getInterface(Ci.nsIDOMWindowUtils);
169       this.innerWindowID = util.currentInnerWindowID;
170     }
172     this._destroyed = false;
174     Services.obs.addObserver(this, "inner-window-destroyed",
175                              /* weak-ref */ true);
176   },
178   destroyDOMRequestHelper: function() {
179     if (this._destroyed) {
180       return;
181     }
183     this._destroyed = true;
185     Services.obs.removeObserver(this, "inner-window-destroyed");
187     if (this._listeners) {
188       Object.keys(this._listeners).forEach((aName) => {
189         this._listeners[aName].weakRef ? cpmm.removeWeakMessageListener(aName, this)
190                                        : cpmm.removeMessageListener(aName, this);
191       });
192     }
194     this._listeners = null;
195     this._requests = null;
197     // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
198     if (this.uninit) {
199       this.uninit();
200     }
202     this._window = null;
203   },
205   observe: function(aSubject, aTopic, aData) {
206     if (aTopic !== "inner-window-destroyed") {
207       return;
208     }
210     let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
211     if (wId != this.innerWindowID) {
212       return;
213     }
215     this.destroyDOMRequestHelper();
216   },
218   getRequestId: function(aRequest) {
219     if (!this._requests) {
220       this._requests = {};
221     }
223     let id = "id" + this._getRandomId();
224     this._requests[id] = aRequest;
225     return id;
226   },
228   getPromiseResolverId: function(aPromiseResolver) {
229     // Delegates to getRequest() since the lookup table is agnostic about
230     // storage.
231     return this.getRequestId(aPromiseResolver);
232   },
234   getRequest: function(aId) {
235     if (this._requests && this._requests[aId]) {
236       return this._requests[aId];
237     }
238   },
240   getPromiseResolver: function(aId) {
241     // Delegates to getRequest() since the lookup table is agnostic about
242     // storage.
243     return this.getRequest(aId);
244   },
246   removeRequest: function(aId) {
247     if (this._requests && this._requests[aId]) {
248       delete this._requests[aId];
249     }
250   },
252   removePromiseResolver: function(aId) {
253     // Delegates to getRequest() since the lookup table is agnostic about
254     // storage.
255     this.removeRequest(aId);
256   },
258   takeRequest: function(aId) {
259     if (!this._requests || !this._requests[aId]) {
260       return null;
261     }
262     let request = this._requests[aId];
263     delete this._requests[aId];
264     return request;
265   },
267   takePromiseResolver: function(aId) {
268     // Delegates to getRequest() since the lookup table is agnostic about
269     // storage.
270     return this.takeRequest(aId);
271   },
273   _getRandomId: function() {
274     return Cc["@mozilla.org/uuid-generator;1"]
275              .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
276   },
278   createRequest: function() {
279     // If we don't have a valid window object, throw.
280     if (!this._window) {
281       Cu.reportError("DOMRequestHelper trying to create a DOMRequest without a valid window, failing.");
282       throw Cr.NS_ERROR_FAILURE;
283     }
284     return Services.DOMRequest.createRequest(this._window);
285   },
287   /**
288    * createPromise() creates a new Promise, with `aPromiseInit` as the
289    * PromiseInit callback. The promise constructor is obtained from the
290    * reference to window owned by this DOMRequestIPCHelper.
291    */
292   createPromise: function(aPromiseInit) {
293     // If we don't have a valid window object, throw.
294     if (!this._window) {
295       Cu.reportError("DOMRequestHelper trying to create a Promise without a valid window, failing.");
296       throw Cr.NS_ERROR_FAILURE;
297     }
298     return new this._window.Promise(aPromiseInit);
299   },
301   forEachRequest: function(aCallback) {
302     if (!this._requests) {
303       return;
304     }
306     Object.keys(this._requests).forEach((aKey) => {
307       if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
308         aCallback(aKey);
309       }
310     });
311   },
313   forEachPromiseResolver: function(aCallback) {
314     if (!this._requests) {
315       return;
316     }
318     Object.keys(this._requests).forEach((aKey) => {
319       if ("resolve" in this.getPromiseResolver(aKey) &&
320           "reject" in this.getPromiseResolver(aKey)) {
321         aCallback(aKey);
322       }
323     });
324   },