mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 915598 - Allow strong references to DOMRequestIPCHelper message listeners. Part 1: DOMRequestHelper. r=fabrice
This commit is contained in:
parent
b284d9f9b7
commit
3d1216244e
@ -3,12 +3,23 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Helper object for APIs that deal with DOMRequests and Promises and need to
|
||||
* release them when the window goes out of scope.
|
||||
* 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.
|
||||
*/
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];
|
||||
|
||||
@ -19,45 +30,148 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
||||
"@mozilla.org/childprocessmessagemanager;1",
|
||||
"nsIMessageListenerManager");
|
||||
|
||||
/**
|
||||
* We use DOMRequestIpcHelperMessageListener to avoid leaking objects which
|
||||
* "inherit" from DOMRequestIpcHelper.
|
||||
*
|
||||
* The issue is that the message manager will hold a strong ref to the message
|
||||
* listener we register with it. But we don't want to hold a strong ref to the
|
||||
* DOMRequestIpcHelper object, because that object may be arbitrarily large.
|
||||
*
|
||||
* So instead the message manager holds a strong ref to the
|
||||
* DOMRequestIpcHelperMessageListener, and that holds a /weak/ ref to its
|
||||
* DOMRequestIpcHelper.
|
||||
*
|
||||
* Additionally, we want to unhook all of these message listeners when the
|
||||
* appropriate window is destroyed. We use DOMRequestIpcHelperMessageListener
|
||||
* for this, too.
|
||||
*/
|
||||
this.DOMRequestIpcHelperMessageListener = function(aHelper, aWindow, aMessages) {
|
||||
this._weakHelper = Cu.getWeakReference(aHelper);
|
||||
|
||||
this._messages = aMessages;
|
||||
this._messages.forEach(function(msgName) {
|
||||
cpmm.addWeakMessageListener(msgName, this);
|
||||
}, this);
|
||||
|
||||
Services.obs.addObserver(this, "inner-window-destroyed", /* weakRef */ true);
|
||||
|
||||
// aWindow may be null; in that case, the DOMRequestIpcHelperMessageListener
|
||||
// is not tied to a particular window and lives forever.
|
||||
if (aWindow) {
|
||||
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
this._innerWindowID = util.currentInnerWindowID;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
DOMRequestIpcHelperMessageListener.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
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.
|
||||
*/
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),
|
||||
|
||||
/**
|
||||
* '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) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._listeners) {
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
if (!Array.isArray(aMessages)) {
|
||||
aMessages = [aMessages];
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* '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) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(aMessages)) {
|
||||
aMessages = [aMessages];
|
||||
}
|
||||
|
||||
aMessages.forEach((aName) => {
|
||||
if (this._listeners[aName] == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._listeners[aName] ? cpmm.removeMessageListener(aName, this)
|
||||
: cpmm.removeWeakMessageListener(aName, this);
|
||||
delete this._listeners[aName];
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
initDOMRequestHelper: function(aWindow, aMessages) {
|
||||
if (aMessages) {
|
||||
this.addMessageListeners(aMessages);
|
||||
}
|
||||
|
||||
this._id = this._getRandomId();
|
||||
|
||||
this._window = aWindow;
|
||||
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;
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, "inner-window-destroyed", false);
|
||||
},
|
||||
|
||||
destroyDOMRequestHelper: function() {
|
||||
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;
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic !== "inner-window-destroyed") {
|
||||
@ -65,70 +179,18 @@ DOMRequestIpcHelperMessageListener.prototype = {
|
||||
}
|
||||
|
||||
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
if (wId != this._innerWindowID) {
|
||||
if (wId != this.innerWindowID) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
receiveMessage: function(aMsg) {
|
||||
let helper = this._weakHelper.get();
|
||||
if (helper) {
|
||||
helper.receiveMessage(aMsg);
|
||||
} else {
|
||||
this.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
// DOMRequestIpcHelper.destroy() calls back into this function.
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
this._destroyed = true;
|
||||
|
||||
Services.obs.removeObserver(this, "inner-window-destroyed");
|
||||
|
||||
this._messages.forEach(function(msgName) {
|
||||
cpmm.removeWeakMessageListener(msgName, this);
|
||||
}, this);
|
||||
this._messages = null;
|
||||
|
||||
let helper = this._weakHelper.get();
|
||||
if (helper) {
|
||||
helper.destroyDOMRequestHelper();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.DOMRequestIpcHelper = function DOMRequestIpcHelper() {
|
||||
}
|
||||
|
||||
DOMRequestIpcHelper.prototype = {
|
||||
/**
|
||||
* An object which "inherits" from DOMRequestIpcHelper and declares its own
|
||||
* queryInterface method MUST implement Ci.nsISupportsWeakReference.
|
||||
*/
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),
|
||||
|
||||
initDOMRequestHelper: function(aWindow, aMessages) {
|
||||
this._DOMRequestIpcHelperMessageListener =
|
||||
new DOMRequestIpcHelperMessageListener(this, aWindow, aMessages);
|
||||
|
||||
this._window = aWindow;
|
||||
this._requests = {};
|
||||
this._id = this._getRandomId();
|
||||
|
||||
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;
|
||||
}
|
||||
this.destroyDOMRequestHelper();
|
||||
},
|
||||
|
||||
getRequestId: function(aRequest) {
|
||||
if (!this._requests) {
|
||||
this._requests = {};
|
||||
}
|
||||
|
||||
let id = "id" + this._getRandomId();
|
||||
this._requests[id] = aRequest;
|
||||
return id;
|
||||
@ -141,8 +203,9 @@ DOMRequestIpcHelper.prototype = {
|
||||
},
|
||||
|
||||
getRequest: function(aId) {
|
||||
if (this._requests[aId])
|
||||
if (this._requests && this._requests[aId]) {
|
||||
return this._requests[aId];
|
||||
}
|
||||
},
|
||||
|
||||
getPromiseResolver: function(aId) {
|
||||
@ -152,8 +215,9 @@ DOMRequestIpcHelper.prototype = {
|
||||
},
|
||||
|
||||
removeRequest: function(aId) {
|
||||
if (this._requests[aId])
|
||||
if (this._requests && this._requests[aId]) {
|
||||
delete this._requests[aId];
|
||||
}
|
||||
},
|
||||
|
||||
removePromiseResolver: function(aId) {
|
||||
@ -163,8 +227,9 @@ DOMRequestIpcHelper.prototype = {
|
||||
},
|
||||
|
||||
takeRequest: function(aId) {
|
||||
if (!this._requests[aId])
|
||||
if (!this._requests || !this._requests[aId]) {
|
||||
return null;
|
||||
}
|
||||
let request = this._requests[aId];
|
||||
delete this._requests[aId];
|
||||
return request;
|
||||
@ -177,25 +242,8 @@ DOMRequestIpcHelper.prototype = {
|
||||
},
|
||||
|
||||
_getRandomId: function() {
|
||||
return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
|
||||
},
|
||||
|
||||
destroyDOMRequestHelper: function() {
|
||||
// This function is re-entrant --
|
||||
// DOMRequestIpcHelperMessageListener.destroy() calls back into this
|
||||
// function, and this.uninit() may also call it.
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
this._destroyed = true;
|
||||
|
||||
this._DOMRequestIpcHelperMessageListener.destroy();
|
||||
this._requests = {};
|
||||
this._window = null;
|
||||
|
||||
if(this.uninit) {
|
||||
this.uninit();
|
||||
}
|
||||
return Cc["@mozilla.org/uuid-generator;1"]
|
||||
.getService(Ci.nsIUUIDGenerator).generateUUID().toString();
|
||||
},
|
||||
|
||||
createRequest: function() {
|
||||
@ -212,19 +260,27 @@ DOMRequestIpcHelper.prototype = {
|
||||
},
|
||||
|
||||
forEachRequest: function(aCallback) {
|
||||
Object.keys(this._requests).forEach(function(k) {
|
||||
if (this.getRequest(k) instanceof this._window.DOMRequest) {
|
||||
aCallback(k);
|
||||
if (!this._requests) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(this._requests).forEach((aKey) => {
|
||||
if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
|
||||
aCallback(aKey);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
forEachPromiseResolver: function(aCallback) {
|
||||
Object.keys(this._requests).forEach(function(k) {
|
||||
if ("resolve" in this.getPromiseResolver(k) &&
|
||||
"reject" in this.getPromiseResolver(k)) {
|
||||
aCallback(k);
|
||||
if (!this._requests) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(this._requests).forEach((aKey) => {
|
||||
if ("resolve" in this.getPromiseResolver(aKey) &&
|
||||
"reject" in this.getPromiseResolver(aKey)) {
|
||||
aCallback(aKey);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user