2012-03-06 23:50:58 +04:00
|
|
|
/* 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/. */
|
|
|
|
|
|
|
|
/**
|
2013-10-05 01:59:48 +04:00
|
|
|
* Helper object for APIs that deal with DOMRequests and Promises.
|
|
|
|
* It allows objects inheriting from it to create and keep track of DOMRequests
|
|
|
|
* and Promises objects in the common scenario where requests are created in
|
|
|
|
* the child, handed out to content and delivered to the parent within an async
|
|
|
|
* message (containing the identifiers of these requests). The parent may send
|
|
|
|
* messages back as answers to different requests and the child will use this
|
|
|
|
* helper to get the right request object. This helper also takes care of
|
|
|
|
* releasing the requests objects when the window goes out of scope.
|
|
|
|
*
|
|
|
|
* DOMRequestIPCHelper also deals with message listeners, allowing to add them
|
|
|
|
* to the child side of frame and process message manager and removing them
|
|
|
|
* when needed.
|
2013-07-09 01:55:42 +04:00
|
|
|
*/
|
|
|
|
const Cu = Components.utils;
|
2012-03-06 23:50:58 +04:00
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
2013-10-05 01:59:48 +04:00
|
|
|
const Cr = Components.results;
|
2012-03-06 23:50:58 +04:00
|
|
|
|
2012-10-31 20:13:28 +04:00
|
|
|
this.EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];
|
2012-03-06 23:50:58 +04:00
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
|
2012-08-27 18:13:02 +04:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
|
|
|
"@mozilla.org/childprocessmessagemanager;1",
|
|
|
|
"nsIMessageListenerManager");
|
2012-03-06 23:50:58 +04:00
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
this.DOMRequestIpcHelper = function DOMRequestIpcHelper() {
|
|
|
|
// _listeners keeps a list of messages for which we added a listener and the
|
|
|
|
// kind of listener that we added (strong or weak). It's an object of this
|
|
|
|
// form:
|
|
|
|
// {
|
|
|
|
// "message1": true,
|
|
|
|
// "messagen": false
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// where each property is the name of the message and its value is a boolean
|
|
|
|
// that indicates if the listener is strong or not.
|
|
|
|
this._listeners = null;
|
|
|
|
this._requests = null;
|
|
|
|
this._window = null;
|
2013-07-09 01:55:42 +04:00
|
|
|
}
|
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
DOMRequestIpcHelper.prototype = {
|
|
|
|
/**
|
|
|
|
* An object which "inherits" from DOMRequestIpcHelper, declares its own
|
|
|
|
* queryInterface method and adds at least one weak listener to the Message
|
|
|
|
* Manager MUST implement Ci.nsISupportsWeakReference.
|
|
|
|
*/
|
2013-10-18 02:52:51 +04:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),
|
2013-07-09 01:55:42 +04:00
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
/**
|
|
|
|
* 'aMessages' is expected to be an array of either:
|
|
|
|
* - objects of this form:
|
|
|
|
* {
|
|
|
|
* name: "messageName",
|
|
|
|
* strongRef: false
|
|
|
|
* }
|
|
|
|
* where 'name' is the message identifier and 'strongRef' a boolean
|
|
|
|
* indicating if the listener should be a strong referred one or not.
|
|
|
|
*
|
|
|
|
* - or only strings containing the message name, in which case the listener
|
|
|
|
* will be added as a weak reference by default.
|
|
|
|
*/
|
|
|
|
addMessageListeners: function(aMessages) {
|
|
|
|
if (!aMessages) {
|
2013-07-09 01:55:42 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
if (!this._listeners) {
|
|
|
|
this._listeners = {};
|
2013-07-09 01:55:42 +04:00
|
|
|
}
|
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
if (!Array.isArray(aMessages)) {
|
|
|
|
aMessages = [aMessages];
|
2013-07-09 01:55:42 +04:00
|
|
|
}
|
2013-10-05 01:59:48 +04:00
|
|
|
|
|
|
|
aMessages.forEach((aMsg) => {
|
|
|
|
let name = aMsg.name || aMsg;
|
|
|
|
// If the listener is already set and it is of the same type we just
|
|
|
|
// bail out. If it is not of the same type, we throw an exception.
|
|
|
|
if (this._listeners[name] != undefined) {
|
|
|
|
if (!!aMsg.strongRef == this._listeners[name]) {
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
throw Cr.NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
aMsg.strongRef ? cpmm.addMessageListener(name, this)
|
|
|
|
: cpmm.addWeakMessageListener(name, this);
|
|
|
|
this._listeners[name] = !!aMsg.strongRef;
|
|
|
|
});
|
2013-07-09 01:55:42 +04:00
|
|
|
},
|
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
/**
|
|
|
|
* 'aMessages' is expected to be a string or an array of strings containing
|
|
|
|
* the message names of the listeners to be removed.
|
|
|
|
*/
|
|
|
|
removeMessageListeners: function(aMessages) {
|
|
|
|
if (!this._listeners || !aMessages) {
|
2013-07-12 01:13:42 +04:00
|
|
|
return;
|
|
|
|
}
|
2013-07-09 01:55:42 +04:00
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
if (!Array.isArray(aMessages)) {
|
|
|
|
aMessages = [aMessages];
|
2013-07-09 01:55:42 +04:00
|
|
|
}
|
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
aMessages.forEach((aName) => {
|
|
|
|
if (this._listeners[aName] == undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._listeners[aName] ? cpmm.removeMessageListener(aName, this)
|
|
|
|
: cpmm.removeWeakMessageListener(aName, this);
|
|
|
|
delete this._listeners[aName];
|
|
|
|
});
|
|
|
|
},
|
2012-03-06 23:50:58 +04:00
|
|
|
|
2013-07-09 01:55:42 +04:00
|
|
|
/**
|
2013-10-05 01:59:48 +04:00
|
|
|
* Initialize the helper adding the corresponding listeners to the messages
|
|
|
|
* provided as the second parameter.
|
|
|
|
*
|
|
|
|
* 'aMessages' is expected to be an array of either:
|
|
|
|
*
|
|
|
|
* - objects of this form:
|
|
|
|
* {
|
|
|
|
* name: 'messageName',
|
|
|
|
* strongRef: false
|
|
|
|
* }
|
|
|
|
* where 'name' is the message identifier and 'strongRef' a boolean
|
|
|
|
* indicating if the listener should be a strong referred one or not.
|
|
|
|
*
|
|
|
|
* - or only strings containing the message name, in which case the listener
|
|
|
|
* will be added as a weak referred one by default.
|
2013-07-09 01:55:42 +04:00
|
|
|
*/
|
|
|
|
initDOMRequestHelper: function(aWindow, aMessages) {
|
2013-10-05 01:59:48 +04:00
|
|
|
if (aMessages) {
|
|
|
|
this.addMessageListeners(aMessages);
|
|
|
|
}
|
2013-07-09 01:55:42 +04:00
|
|
|
|
|
|
|
this._id = this._getRandomId();
|
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
this._window = aWindow;
|
2013-07-09 01:55:42 +04:00
|
|
|
if (this._window) {
|
|
|
|
// We don't use this.innerWindowID, but other classes rely on it.
|
|
|
|
let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
this.innerWindowID = util.currentInnerWindowID;
|
|
|
|
}
|
2013-10-05 01:59:48 +04:00
|
|
|
|
2013-10-18 14:58:24 +04:00
|
|
|
this._destroyed = false;
|
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
Services.obs.addObserver(this, "inner-window-destroyed", false);
|
|
|
|
},
|
|
|
|
|
|
|
|
destroyDOMRequestHelper: function() {
|
2013-10-18 14:58:24 +04:00
|
|
|
if (this._destroyed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._destroyed = true;
|
|
|
|
|
2013-10-05 01:59:48 +04:00
|
|
|
Services.obs.removeObserver(this, "inner-window-destroyed");
|
|
|
|
|
|
|
|
if (this._listeners) {
|
|
|
|
Object.keys(this._listeners).forEach((aName) => {
|
|
|
|
this._listeners[aName] ? cpmm.removeMessageListener(aName, this)
|
|
|
|
: cpmm.removeWeakMessageListener(aName, this);
|
|
|
|
delete this._listeners[aName];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this._listeners = null;
|
|
|
|
this._requests = null;
|
|
|
|
this._window = null;
|
2013-10-18 14:58:24 +04:00
|
|
|
|
|
|
|
// Objects inheriting from DOMRequestIPCHelper may have an uninit function.
|
|
|
|
if (this.uninit) {
|
|
|
|
this.uninit();
|
|
|
|
}
|
2013-10-05 01:59:48 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
observe: function(aSubject, aTopic, aData) {
|
|
|
|
if (aTopic !== "inner-window-destroyed") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
|
|
|
if (wId != this.innerWindowID) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.destroyDOMRequestHelper();
|
2013-07-09 01:55:42 +04:00
|
|
|
},
|
|
|
|
|
2012-03-06 23:50:58 +04:00
|
|
|
getRequestId: function(aRequest) {
|
2013-10-05 01:59:48 +04:00
|
|
|
if (!this._requests) {
|
|
|
|
this._requests = {};
|
|
|
|
}
|
|
|
|
|
2012-03-06 23:50:58 +04:00
|
|
|
let id = "id" + this._getRandomId();
|
|
|
|
this._requests[id] = aRequest;
|
|
|
|
return id;
|
|
|
|
},
|
|
|
|
|
2013-08-10 05:11:11 +04:00
|
|
|
getPromiseResolverId: function(aPromiseResolver) {
|
|
|
|
// Delegates to getRequest() since the lookup table is agnostic about
|
|
|
|
// storage.
|
|
|
|
return this.getRequestId(aPromiseResolver);
|
|
|
|
},
|
|
|
|
|
2012-03-06 23:50:58 +04:00
|
|
|
getRequest: function(aId) {
|
2013-10-05 01:59:48 +04:00
|
|
|
if (this._requests && this._requests[aId]) {
|
2012-03-06 23:50:58 +04:00
|
|
|
return this._requests[aId];
|
2013-10-05 01:59:48 +04:00
|
|
|
}
|
2012-03-06 23:50:58 +04:00
|
|
|
},
|
|
|
|
|
2013-08-10 05:11:11 +04:00
|
|
|
getPromiseResolver: function(aId) {
|
|
|
|
// Delegates to getRequest() since the lookup table is agnostic about
|
|
|
|
// storage.
|
|
|
|
return this.getRequest(aId);
|
|
|
|
},
|
|
|
|
|
2012-03-06 23:50:58 +04:00
|
|
|
removeRequest: function(aId) {
|
2013-10-05 01:59:48 +04:00
|
|
|
if (this._requests && this._requests[aId]) {
|
2012-03-06 23:50:58 +04:00
|
|
|
delete this._requests[aId];
|
2013-10-05 01:59:48 +04:00
|
|
|
}
|
2012-03-06 23:50:58 +04:00
|
|
|
},
|
|
|
|
|
2013-08-10 05:11:11 +04:00
|
|
|
removePromiseResolver: function(aId) {
|
|
|
|
// Delegates to getRequest() since the lookup table is agnostic about
|
|
|
|
// storage.
|
|
|
|
this.removeRequest(aId);
|
|
|
|
},
|
|
|
|
|
2012-03-20 19:59:38 +04:00
|
|
|
takeRequest: function(aId) {
|
2013-10-05 01:59:48 +04:00
|
|
|
if (!this._requests || !this._requests[aId]) {
|
2012-03-20 19:59:38 +04:00
|
|
|
return null;
|
2013-10-05 01:59:48 +04:00
|
|
|
}
|
2012-03-20 19:59:38 +04:00
|
|
|
let request = this._requests[aId];
|
|
|
|
delete this._requests[aId];
|
|
|
|
return request;
|
|
|
|
},
|
|
|
|
|
2013-08-10 05:11:11 +04:00
|
|
|
takePromiseResolver: function(aId) {
|
|
|
|
// Delegates to getRequest() since the lookup table is agnostic about
|
|
|
|
// storage.
|
|
|
|
return this.takeRequest(aId);
|
|
|
|
},
|
|
|
|
|
2012-03-06 23:50:58 +04:00
|
|
|
_getRandomId: function() {
|
2013-10-05 01:59:48 +04:00
|
|
|
return Cc["@mozilla.org/uuid-generator;1"]
|
|
|
|
.getService(Ci.nsIUUIDGenerator).generateUUID().toString();
|
2012-03-06 23:50:58 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
createRequest: function() {
|
2012-04-11 09:55:54 +04:00
|
|
|
return Services.DOMRequest.createRequest(this._window);
|
2013-08-10 05:11:11 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* createPromise() creates a new Promise, with `aPromiseInit` as the
|
|
|
|
* PromiseInit callback. The promise constructor is obtained from the
|
|
|
|
* reference to window owned by this DOMRequestIPCHelper.
|
|
|
|
*/
|
|
|
|
createPromise: function(aPromiseInit) {
|
|
|
|
return new this._window.Promise(aPromiseInit);
|
2013-08-13 09:07:03 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
forEachRequest: function(aCallback) {
|
2013-10-05 01:59:48 +04:00
|
|
|
if (!this._requests) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.keys(this._requests).forEach((aKey) => {
|
|
|
|
if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
|
|
|
|
aCallback(aKey);
|
2013-08-13 09:07:03 +04:00
|
|
|
}
|
2013-10-05 01:59:48 +04:00
|
|
|
});
|
2013-08-13 09:07:03 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
forEachPromiseResolver: function(aCallback) {
|
2013-10-05 01:59:48 +04:00
|
|
|
if (!this._requests) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.keys(this._requests).forEach((aKey) => {
|
|
|
|
if ("resolve" in this.getPromiseResolver(aKey) &&
|
|
|
|
"reject" in this.getPromiseResolver(aKey)) {
|
|
|
|
aCallback(aKey);
|
2013-08-13 09:07:03 +04:00
|
|
|
}
|
2013-10-05 01:59:48 +04:00
|
|
|
});
|
2013-08-13 09:07:03 +04:00
|
|
|
},
|
2012-03-06 23:50:58 +04:00
|
|
|
}
|