Bug 672470 - Fix memory leaks in Web Console code; r=rcampbell,gavin.sharp

This commit is contained in:
Mihai Sucan 2011-08-03 21:04:30 +03:00
parent 1b9d0af5d1
commit 42502a15ad
4 changed files with 123 additions and 79 deletions

View File

@ -466,7 +466,6 @@ ResponseListener.prototype =
}
});
this.httpActivity.response.isDone = true;
this.httpActivity.response.listener = null;
this.httpActivity = null;
this.receivedData = "";
this.request = null;
@ -1278,6 +1277,37 @@ function HUD_SERVICE()
// bytes).
this.responsePipeSegmentSize =
Services.prefs.getIntPref("network.buffer.cache.size");
/**
* Collection of HUDIds that map to the tabs/windows/contexts
* that a HeadsUpDisplay can be activated for.
*/
this.activatedContexts = [];
/**
* Collection of outer window IDs mapping to HUD IDs.
*/
this.windowIds = {};
/**
* Each HeadsUpDisplay has a set of filter preferences
*/
this.filterPrefs = {};
/**
* Keeps a reference for each HeadsUpDisplay that is created
*/
this.hudReferences = {};
/**
* Requests that haven't finished yet.
*/
this.openRequests = {};
/**
* Response headers for requests that haven't finished yet.
*/
this.openResponseHeaders = {};
};
HUD_SERVICE.prototype =
@ -1313,28 +1343,12 @@ HUD_SERVICE.prototype =
return HeadsUpDisplayUICommands;
},
/**
* Collection of HUDIds that map to the tabs/windows/contexts
* that a HeadsUpDisplay can be activated for.
*/
activatedContexts: [],
/**
* Collection of outer window IDs mapping to HUD IDs.
*/
windowIds: {},
/**
* The sequencer is a generator (after initialization) that returns unique
* integers
*/
sequencer: null,
/**
* Each HeadsUpDisplay has a set of filter preferences
*/
filterPrefs: {},
/**
* Gets the ID of the outer window of this DOM window
*
@ -1681,11 +1695,6 @@ HUD_SERVICE.prototype =
this.regroupOutput(outputNode);
},
/**
* Keeps a reference for each HeadsUpDisplay that is created
*/
hudReferences: {},
/**
* Register a reference of each HeadsUpDisplay that is created
*/
@ -1730,6 +1739,8 @@ HUD_SERVICE.prototype =
// remove the nodes then.
hud.jsterm.clearOutput();
hud.destroy();
// Make sure that the console panel does not try to call
// deactivateHUDForContext() again.
hud.consoleWindowUnregisterOnHide = false;
@ -1826,6 +1837,8 @@ HUD_SERVICE.prototype =
delete this.defaultFilterPrefs;
delete this.defaultGlobalConsolePrefs;
delete this.lastFinishedRequestCallback;
HUDWindowObserver.uninit();
HUDConsoleObserver.uninit();
ConsoleAPIObserver.shutdown();
@ -2124,16 +2137,6 @@ HUD_SERVICE.prototype =
*/
applicationHooks: null,
/**
* Requests that haven't finished yet.
*/
openRequests: {},
/**
* Response headers for requests that haven't finished yet.
*/
openResponseHeaders: {},
/**
* Assign a function to this property to listen for finished httpRequests.
* Used by unit tests.
@ -2239,15 +2242,8 @@ HUD_SERVICE.prototype =
return;
}
// Add listener for the response body.
let newListener = new ResponseListener(httpActivity);
aChannel.QueryInterface(Ci.nsITraceableChannel);
httpActivity.response.listener = newListener;
let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
createInstance(Ci.nsIStreamListenerTee);
// The response will be written into the outputStream of this pipe.
// This allows us to buffer the data we are receiving and read it
// asynchronously.
@ -2259,11 +2255,17 @@ HUD_SERVICE.prototype =
sink.init(false, false, HUDService.responsePipeSegmentSize,
PR_UINT32_MAX, null);
// Add listener for the response body.
let newListener = new ResponseListener(httpActivity);
// Remember the input stream, so it isn't released by GC.
newListener.inputStream = sink.inputStream;
newListener.sink = sink;
let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
createInstance(Ci.nsIStreamListenerTee);
let originalListener = aChannel.setNewListener(tee);
newListener.sink = sink;
tee.init(originalListener, sink.outputStream, newListener);
@ -2307,7 +2309,7 @@ HUD_SERVICE.prototype =
// Iterate over all currently ongoing requests. If aChannel can't
// be found within them, then exit this function.
let httpActivity = null;
for each (var item in self.openRequests) {
for each (let item in self.openRequests) {
if (item.channel !== aChannel) {
continue;
}
@ -2420,7 +2422,8 @@ HUD_SERVICE.prototype =
msgObject.messageNode.clipboardText =
clipboardTextPieces.join(" ");
delete self.openRequests[item.id];
delete httpActivity.messageObject;
delete self.openRequests[httpActivity.id];
updatePanel = true;
break;
}
@ -2657,14 +2660,25 @@ HUD_SERVICE.prototype =
*/
onWindowUnload: function HS_onWindowUnload(aEvent)
{
let gBrowser = aEvent.target.defaultView.gBrowser;
let window = aEvent.target.defaultView;
window.removeEventListener("unload", this.onWindowUnload, false);
let gBrowser = window.gBrowser;
let tabContainer = gBrowser.tabContainer;
tabContainer.removeEventListener("TabClose", this.onTabClose, false);
let tab = tabContainer.firstChild;
while (tab != null) {
this.deactivateHUDForContext(tab, false);
tab = tab.nextSibling;
}
if (window.webConsoleCommandController) {
window.controllers.removeController(window.webConsoleCommandController);
window.webConsoleCommandController = null;
}
},
/**
@ -2690,13 +2704,8 @@ HUD_SERVICE.prototype =
return;
}
xulWindow.addEventListener("unload", this.onWindowUnload, false);
let gBrowser = xulWindow.gBrowser;
let container = gBrowser.tabContainer;
container.addEventListener("TabClose", this.onTabClose, false);
let _browser = gBrowser.
getBrowserForDocument(aContentWindow.top.document);
let nBox = gBrowser.getNotificationBox(_browser);
@ -2716,6 +2725,9 @@ HUD_SERVICE.prototype =
return;
}
xulWindow.addEventListener("unload", this.onWindowUnload, false);
gBrowser.tabContainer.addEventListener("TabClose", this.onTabClose, false);
this.registerDisplay(hudId);
let hudNode;
@ -2775,9 +2787,10 @@ HUD_SERVICE.prototype =
*/
createController: function HUD_createController(aWindow)
{
if (aWindow.commandController == null) {
aWindow.commandController = new CommandController(aWindow);
aWindow.controllers.insertControllerAt(0, aWindow.commandController);
if (aWindow.webConsoleCommandController == null) {
aWindow.webConsoleCommandController = new CommandController(aWindow);
aWindow.controllers.insertControllerAt(0,
aWindow.webConsoleCommandController);
}
},
@ -3611,7 +3624,16 @@ HeadsUpDisplay.prototype = {
*/
createPositionUI: function HUD_createPositionUI()
{
let self = this;
this._positionConsoleAbove = (function HUD_positionAbove() {
this.positionConsole("above");
}).bind(this);
this._positionConsoleBelow = (function HUD_positionBelow() {
this.positionConsole("below");
}).bind(this);
this._positionConsoleWindow = (function HUD_positionWindow() {
this.positionConsole("window");
}).bind(this);
let button = this.makeXULNode("toolbarbutton");
button.setAttribute("type", "menu");
@ -3625,27 +3647,21 @@ HeadsUpDisplay.prototype = {
itemAbove.setAttribute("label", this.getStr("webConsolePositionAbove"));
itemAbove.setAttribute("type", "checkbox");
itemAbove.setAttribute("autocheck", "false");
itemAbove.addEventListener("command", function() {
self.positionConsole("above");
}, false);
itemAbove.addEventListener("command", this._positionConsoleAbove, false);
menuPopup.appendChild(itemAbove);
let itemBelow = this.makeXULNode("menuitem");
itemBelow.setAttribute("label", this.getStr("webConsolePositionBelow"));
itemBelow.setAttribute("type", "checkbox");
itemBelow.setAttribute("autocheck", "false");
itemBelow.addEventListener("command", function() {
self.positionConsole("below");
}, false);
itemBelow.addEventListener("command", this._positionConsoleBelow, false);
menuPopup.appendChild(itemBelow);
let itemWindow = this.makeXULNode("menuitem");
itemWindow.setAttribute("label", this.getStr("webConsolePositionWindow"));
itemWindow.setAttribute("type", "checkbox");
itemWindow.setAttribute("autocheck", "false");
itemWindow.addEventListener("command", function() {
self.positionConsole("window");
}, false);
itemWindow.addEventListener("command", this._positionConsoleWindow, false);
menuPopup.appendChild(itemWindow);
this.positionMenuitems = {
@ -3771,16 +3787,17 @@ HeadsUpDisplay.prototype = {
*/
makeCloseButton: function HUD_makeCloseButton(aToolbar)
{
let onCommand = (function HUD_closeButton_onCommand() {
this.closeButtonOnCommand = (function HUD_closeButton_onCommand() {
HUDService.animate(this.hudId, ANIMATE_OUT, (function() {
HUDService.deactivateHUDForContext(this.tab, true);
}).bind(this));
}).bind(this);
let closeButton = this.makeXULNode("toolbarbutton");
closeButton.classList.add("webconsole-close-button");
closeButton.addEventListener("command", onCommand, false);
aToolbar.appendChild(closeButton);
this.closeButton = this.makeXULNode("toolbarbutton");
this.closeButton.classList.add("webconsole-close-button");
this.closeButton.addEventListener("command",
this.closeButtonOnCommand, false);
aToolbar.appendChild(this.closeButton);
},
/**
@ -3859,7 +3876,26 @@ HeadsUpDisplay.prototype = {
HUD_BOX_DOES_NOT_EXIST: "Heads Up Display does not exist",
TAB_ID_REQUIRED: "Tab DOM ID is required",
PARENTNODE_NOT_FOUND: "parentNode element not found"
}
},
/**
* Destroy the HUD object. Call this method to avoid memory leaks when the Web
* Console is closed.
*/
destroy: function HUD_destroy()
{
this.jsterm.destroy();
this.positionMenuitems.above.removeEventListener("command",
this._positionConsoleAbove, false);
this.positionMenuitems.below.removeEventListener("command",
this._positionConsoleBelow, false);
this.positionMenuitems.window.removeEventListener("command",
this._positionConsoleWindow, false);
this.closeButton.removeEventListener("command",
this.closeButtonOnCommand, false);
},
};
@ -4459,12 +4495,15 @@ JSTerm.prototype = {
this.outputNode = this.mixins.outputNode;
this.completeNode = this.mixins.completeNode;
this._keyPress = this.keyPress.bind(this);
this._inputEventHandler = this.inputEventHandler.bind(this);
this.inputNode.addEventListener("keypress",
this.keyPress.bind(this), false);
this._keyPress, false);
this.inputNode.addEventListener("input",
this.inputEventHandler.bind(this), false);
this._inputEventHandler, false);
this.inputNode.addEventListener("keyup",
this.inputEventHandler.bind(this), false);
this._inputEventHandler, false);
},
get codeInputString()
@ -5245,6 +5284,16 @@ JSTerm.prototype = {
let prefix = aSuffix ? this.inputNode.value.replace(/[\S]/g, " ") : "";
this.completeNode.value = prefix + aSuffix;
},
/**
* Destroy the JSTerm object. Call this method to avoid memory leaks.
*/
destroy: function JST_destroy()
{
this.inputNode.removeEventListener("keypress", this._keyPress, false);
this.inputNode.removeEventListener("input", this._inputEventHandler, false);
this.inputNode.removeEventListener("keyup", this._inputEventHandler, false);
},
};
/**
@ -6399,8 +6448,7 @@ CommandController.prototype = {
supportsCommand: function CommandController_supportsCommand(aCommand)
{
return this.isCommandEnabled(aCommand) &&
this._getFocusedOutputNode() != null;
return this.isCommandEnabled(aCommand);
},
isCommandEnabled: function CommandController_isCommandEnabled(aCommand)

View File

@ -36,7 +36,7 @@ function testSelectionWhenMovingBetweenBoxes() {
// Test that the global Firefox "Select All" functionality (e.g. Edit >
// Select All) works properly in the Web Console.
let commandController = window.commandController;
let commandController = window.webConsoleCommandController;
ok(commandController != null, "the window has a command controller object");
commandController.selectAll(outputNode);

View File

@ -1,6 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let itemsSet;
let itemsSet, HUD;
function test() {
addTab("data:text/html,Web Console test for bug 626484");
@ -30,6 +30,7 @@ function nextTest() {
if (itemsSet.length === 0) {
outputNode.clearSelection();
HUD.jsterm.clearOutput();
HUD = null;
finish();
}
else {

View File

@ -57,8 +57,6 @@ function pprint(aObj)
let tab, browser, hudId, hud, hudBox, filterBox, outputNode, cs;
let win = gBrowser.selectedBrowser;
function addTab(aURL)
{
gBrowser.selectedTab = gBrowser.addTab();
@ -185,6 +183,3 @@ registerCleanupFunction(tearDown);
waitForExplicitFinish();
// removed tests:
// browser_webconsole_bug_580030_errors_after_page_reload.js \
// browser_webconsole_bug_595350_multiple_windows_and_tabs.js \