Bug 889984 - Don't leak objects which "inherit" from DOMRequestIpcHelper the associated window is closed. r=fabrice

This commit is contained in:
Justin Lebar 2013-07-08 17:55:42 -04:00
parent 4c7ef0f136
commit 60dc213200
11 changed files with 152 additions and 69 deletions

View File

@ -35,7 +35,9 @@ AlarmsManager.prototype = {
classID : ALARMSMANAGER_CID,
QueryInterface : XPCOMUtils.generateQI([nsIDOMMozAlarmsManager, Ci.nsIDOMGlobalPropertyInitializer]),
QueryInterface : XPCOMUtils.generateQI([nsIDOMMozAlarmsManager,
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
classInfo : XPCOMUtils.generateCI({ classID: ALARMSMANAGER_CID,
contractID: ALARMSMANAGER_CONTRACTID,
@ -162,7 +164,7 @@ AlarmsManager.prototype = {
this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
// Add the valid messages to be listened.
this.initHelper(aWindow, ["AlarmsManager:Add:Return:OK", "AlarmsManager:Add:Return:KO",
this.initDOMRequestHelper(aWindow, ["AlarmsManager:Add:Return:OK", "AlarmsManager:Add:Return:KO",
"AlarmsManager:GetAll:Return:OK", "AlarmsManager:GetAll:Return:KO"]);
// Get the manifest URL if this is an installed app

View File

@ -242,7 +242,7 @@ WebappsRegistry.prototype = {
// nsIDOMGlobalPropertyInitializer implementation
init: function(aWindow) {
this.initHelper(aWindow, ["Webapps:Install:Return:OK", "Webapps:Install:Return:KO",
this.initDOMRequestHelper(aWindow, ["Webapps:Install:Return:OK", "Webapps:Install:Return:KO",
"Webapps:GetInstalled:Return:OK",
"Webapps:GetSelf:Return:OK",
"Webapps:CheckInstalled:Return:OK" ]);
@ -264,7 +264,8 @@ WebappsRegistry.prototype = {
classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationRegistry,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
Ci.mozIDOMApplicationRegistry,
#ifdef MOZ_B2G
Ci.mozIDOMApplicationRegistry2,
#elifdef MOZ_WIDGET_ANDROID
@ -364,7 +365,7 @@ WebappsApplication.prototype = {
this._downloadError = null;
this.initHelper(aWindow, ["Webapps:OfflineCache",
this.initDOMRequestHelper(aWindow, ["Webapps:OfflineCache",
"Webapps:CheckForUpdate:Return:OK",
"Webapps:CheckForUpdate:Return:KO",
"Webapps:Launch:Return:OK",
@ -636,7 +637,8 @@ WebappsApplication.prototype = {
classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplication]),
QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplication,
Ci.nsISupportsWeakReference]),
classInfo: XPCOMUtils.generateCI({classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
contractID: "@mozilla.org/webapps/application;1",
@ -649,7 +651,7 @@ WebappsApplication.prototype = {
* mozIDOMApplicationMgmt object
*/
function WebappsApplicationMgmt(aWindow) {
this.initHelper(aWindow, ["Webapps:GetAll:Return:OK",
this.initDOMRequestHelper(aWindow, ["Webapps:GetAll:Return:OK",
"Webapps:GetAll:Return:KO",
"Webapps:Uninstall:Return:OK",
"Webapps:Uninstall:Broadcast:Return:OK",
@ -787,7 +789,7 @@ WebappsApplicationMgmt.prototype = {
classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"),
QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationMgmt]),
QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationMgmt, Ci.nsISupportsWeakReference]),
classInfo: XPCOMUtils.generateCI({classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"),
contractID: "@mozilla.org/webapps/application-mgmt;1",

View File

@ -3,10 +3,10 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* helper object for APIs that deal with DOMRequest and need to release them properly
* when the window goes out of scope
*/
const Cu = Components.utils;
* Helper object for APIs that deal with DOMRequests and need to release them
* when the window goes out of scope.
*/
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
@ -19,10 +19,109 @@ 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.addMessageListener(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;
}
}
DOMRequestIpcHelperMessageListener.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe: function(aSubject, aTopic, aData) {
if (aTopic !== "inner-window-destroyed") {
return;
}
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
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() {
Services.obs.removeObserver(this, "inner-window-destroyed");
this._messages.forEach(function(msgName) {
cpmm.removeMessageListener(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;
}
},
getRequestId: function(aRequest) {
let id = "id" + this._getRandomId();
this._requests[id] = aRequest;
@ -51,48 +150,22 @@ DOMRequestIpcHelper.prototype = {
return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
},
observe: function(aSubject, aTopic, aData) {
if (aTopic !== "inner-window-destroyed") {
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;
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (wId == this.innerWindowID) {
Services.obs.removeObserver(this, "inner-window-destroyed");
this._requests = [];
this._window = null;
this.removeMessageListener();
if(this.uninit)
this.uninit();
}
},
initRequests: function initRequests() {
this._DOMRequestIpcHelperMessageListener.destroy();
this._requests = [];
},
this._window = null;
initMessageListener: function initMessageListener(aMessages) {
this._messages = aMessages;
this._messages.forEach(function(msgName) {
cpmm.addMessageListener(msgName, this);
}, this);
},
initHelper: function(aWindow, aMessages) {
this.initMessageListener(aMessages);
this.initRequests();
this._id = this._getRandomId();
Services.obs.addObserver(this, "inner-window-destroyed", false);
this._window = aWindow;
let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
this.innerWindowID = util.currentInnerWindowID;
},
removeMessageListener: function removeMessageListener() {
this._messages.forEach(function(msgName) {
cpmm.removeMessageListener(msgName, this);
}, this);
this._messages = null;
if(this.uninit) {
this.uninit();
}
},
createRequest: function() {

View File

@ -911,7 +911,7 @@ ContactManager.prototype = {
},
init: function(aWindow) {
this.initHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
this.initDOMRequestHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
"Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO",
"Contact:Save:Return:OK", "Contact:Save:Return:KO",
"Contact:Remove:Return:OK", "Contact:Remove:Return:KO",
@ -929,7 +929,9 @@ ContactManager.prototype = {
},
classID : CONTACTMANAGER_CID,
QueryInterface : XPCOMUtils.generateQI([nsIDOMContactManager, Ci.nsIDOMGlobalPropertyInitializer]),
QueryInterface : XPCOMUtils.generateQI([nsIDOMContactManager,
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
classInfo : XPCOMUtils.generateCI({classID: CONTACTMANAGER_CID,
contractID: CONTACTMANAGER_CONTRACTID,

View File

@ -42,7 +42,8 @@ DOMFMRadioChild.prototype = {
}),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMFMRadio,
Ci.nsIDOMGlobalPropertyInitializer]),
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
// nsIDOMGlobalPropertyInitializer implementation
init: function(aWindow) {
@ -72,7 +73,7 @@ DOMFMRadioChild.prototype = {
"DOMFMRadio:frequencyChange",
"DOMFMRadio:powerStateChange",
"DOMFMRadio:antennaChange"];
this.initHelper(aWindow, messages);
this.initDOMRequestHelper(aWindow, messages);
let els = Cc["@mozilla.org/eventlistenerservice;1"]
.getService(Ci.nsIEventListenerService);

View File

@ -248,7 +248,7 @@ SystemMessageManager.prototype = {
// nsIDOMGlobalPropertyInitializer implementation.
init: function sysMessMgr_init(aWindow) {
debug("init");
this.initHelper(aWindow, ["SystemMessageManager:Message",
this.initDOMRequestHelper(aWindow, ["SystemMessageManager:Message",
"SystemMessageManager:GetPendingMessages:Return"]);
let principal = aWindow.document.nodePrincipal;
@ -307,7 +307,8 @@ SystemMessageManager.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMNavigatorSystemMessages,
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsIObserver]),
Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
classInfo: XPCOMUtils.generateCI({
classID: Components.ID("{bc076ea0-609b-4d8f-83d7-5af7cbdc3bb2}"),

View File

@ -219,7 +219,7 @@ NetworkStatsManager.prototype = {
return null;
}
this.initHelper(aWindow, ["NetworkStats:Get:Return",
this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
"NetworkStats:Clear:Return"]);
},
@ -232,7 +232,8 @@ NetworkStatsManager.prototype = {
classID : NETWORKSTATSMANAGER_CID,
QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsManager,
Ci.nsIDOMGlobalPropertyInitializer]),
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSMANAGER_CID,
contractID: NETWORKSTATSMANAGER_CONTRACTID,

View File

@ -31,7 +31,8 @@ PaymentContentHelper.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavigatorPayment,
Ci.nsIDOMGlobalPropertyInitializer]),
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
classID: PAYMENTCONTENTHELPER_CID,
classInfo: XPCOMUtils.generateCI({
classID: PAYMENTCONTENTHELPER_CID,
@ -78,7 +79,7 @@ PaymentContentHelper.prototype = {
init: function(aWindow) {
this._window = aWindow;
this.initHelper(aWindow, PAYMENT_IPC_MSG_NAMES);
this.initDOMRequestHelper(aWindow, PAYMENT_IPC_MSG_NAMES);
return this.pay.bind(this);
},

View File

@ -34,7 +34,8 @@ Push.prototype = {
classID : PUSH_CID,
QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer]),
QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
init: function(aWindow) {
debug("init()");
@ -58,9 +59,7 @@ Push.prototype = {
if (perm != Ci.nsIPermissionManager.ALLOW_ACTION)
return null;
this.initHelper(aWindow, []);
this.initMessageListener([
this.initDOMRequestHelper(aWindow, [
"PushService:Register:OK",
"PushService:Register:KO",
"PushService:Unregister:OK",

View File

@ -375,8 +375,7 @@ function RILContentHelper() {
};
this.voicemailInfo = new VoicemailInfo();
this.initRequests();
this.initMessageListener(RIL_IPC_MSG_NAMES);
this.initDOMRequestHelper(/* aWindow */ null, RIL_IPC_MSG_NAMES);
this._windowsMap = [];
Services.obs.addObserver(this, "xpcom-shutdown", false);
}
@ -389,7 +388,8 @@ RILContentHelper.prototype = {
Ci.nsIVoicemailProvider,
Ci.nsITelephonyProvider,
Ci.nsIIccProvider,
Ci.nsIObserver]),
Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
classID: RILCONTENTHELPER_CID,
classInfo: XPCOMUtils.generateCI({classID: RILCONTENTHELPER_CID,
classDescription: "RILContentHelper",
@ -1236,7 +1236,7 @@ RILContentHelper.prototype = {
observe: function observe(subject, topic, data) {
if (topic == "xpcom-shutdown") {
this.removeMessageListener();
this.destroyDOMRequestHelper();
Services.obs.removeObserver(this, "xpcom-shutdown");
}
},

View File

@ -56,7 +56,8 @@ DOMWifiManager.prototype = {
flags: Ci.nsIClassInfo.DOM_OBJECT}),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMWifiManager,
Ci.nsIDOMGlobalPropertyInitializer]),
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
// nsIDOMGlobalPropertyInitializer implementation
init: function(aWindow) {
@ -93,7 +94,7 @@ DOMWifiManager.prototype = {
"WifiManager:onwpstimeout", "WifiManager:onwpsfail",
"WifiManager:onwpsoverlap", "WifiManager:connectionInfoUpdate",
"WifiManager:onconnectingfailed"];
this.initHelper(aWindow, messages);
this.initDOMRequestHelper(aWindow, messages);
this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
var state = this._mm.sendSyncMessage("WifiManager:getState")[0];