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 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
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;
49 DOMRequestIpcHelper.prototype = {
51 * An object which "inherits" from DOMRequestIpcHelper and declares its own
52 * queryInterface method MUST implement Ci.nsISupportsWeakReference.
54 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
58 * 'aMessages' is expected to be an array of either:
59 * - objects of this form:
61 * name: "messageName",
64 * where 'name' is the message identifier and 'weakRef' a boolean
65 * indicating if the listener should be a weak referred one or not.
67 * - or only strings containing the message name, in which case the listener
68 * will be added as a strong reference by default.
70 addMessageListeners: function(aMessages) {
75 if (!this._listeners) {
79 if (!Array.isArray(aMessages)) {
80 aMessages = [aMessages];
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++;
93 throw Cr.NS_ERROR_FAILURE;
97 aMsg.weakRef ? cpmm.addWeakMessageListener(name, this)
98 : cpmm.addMessageListener(name, this);
99 this._listeners[name] = {
100 weakRef: !!aMsg.weakRef,
107 * 'aMessages' is expected to be a string or an array of strings containing
108 * the message names of the listeners to be removed.
110 removeMessageListeners: function(aMessages) {
111 if (!this._listeners || !aMessages) {
115 if (!Array.isArray(aMessages)) {
116 aMessages = [aMessages];
119 aMessages.forEach((aName) => {
120 if (this._listeners[aName] == undefined) {
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];
136 * Initialize the helper adding the corresponding listeners to the messages
137 * provided as the second parameter.
139 * 'aMessages' is expected to be an array of either:
141 * - objects of this form:
143 * name: 'messageName',
146 * where 'name' is the message identifier and 'weakRef' a boolean
147 * indicating if the listener should be a weak referred one or not.
149 * - or only strings containing the message name, in which case the listener
150 * will be added as a strong referred one by default.
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);
159 this.addMessageListeners(aMessages);
162 this._id = this._getRandomId();
164 this._window = aWindow;
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;
172 this._destroyed = false;
174 Services.obs.addObserver(this, "inner-window-destroyed",
175 /* weak-ref */ true);
178 destroyDOMRequestHelper: function() {
179 if (this._destroyed) {
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);
194 this._listeners = null;
195 this._requests = null;
197 // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
205 observe: function(aSubject, aTopic, aData) {
206 if (aTopic !== "inner-window-destroyed") {
210 let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
211 if (wId != this.innerWindowID) {
215 this.destroyDOMRequestHelper();
218 getRequestId: function(aRequest) {
219 if (!this._requests) {
223 let id = "id" + this._getRandomId();
224 this._requests[id] = aRequest;
228 getPromiseResolverId: function(aPromiseResolver) {
229 // Delegates to getRequest() since the lookup table is agnostic about
231 return this.getRequestId(aPromiseResolver);
234 getRequest: function(aId) {
235 if (this._requests && this._requests[aId]) {
236 return this._requests[aId];
240 getPromiseResolver: function(aId) {
241 // Delegates to getRequest() since the lookup table is agnostic about
243 return this.getRequest(aId);
246 removeRequest: function(aId) {
247 if (this._requests && this._requests[aId]) {
248 delete this._requests[aId];
252 removePromiseResolver: function(aId) {
253 // Delegates to getRequest() since the lookup table is agnostic about
255 this.removeRequest(aId);
258 takeRequest: function(aId) {
259 if (!this._requests || !this._requests[aId]) {
262 let request = this._requests[aId];
263 delete this._requests[aId];
267 takePromiseResolver: function(aId) {
268 // Delegates to getRequest() since the lookup table is agnostic about
270 return this.takeRequest(aId);
273 _getRandomId: function() {
274 return Cc["@mozilla.org/uuid-generator;1"]
275 .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
278 createRequest: function() {
279 // If we don't have a valid window object, throw.
281 Cu.reportError("DOMRequestHelper trying to create a DOMRequest without a valid window, failing.");
282 throw Cr.NS_ERROR_FAILURE;
284 return Services.DOMRequest.createRequest(this._window);
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.
292 createPromise: function(aPromiseInit) {
293 // If we don't have a valid window object, throw.
295 Cu.reportError("DOMRequestHelper trying to create a Promise without a valid window, failing.");
296 throw Cr.NS_ERROR_FAILURE;
298 return new this._window.Promise(aPromiseInit);
301 forEachRequest: function(aCallback) {
302 if (!this._requests) {
306 Object.keys(this._requests).forEach((aKey) => {
307 if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
313 forEachPromiseResolver: function(aCallback) {
314 if (!this._requests) {
318 Object.keys(this._requests).forEach((aKey) => {
319 if ("resolve" in this.getPromiseResolver(aKey) &&
320 "reject" in this.getPromiseResolver(aKey)) {