Bug 710258 - Don't allow the debugger to be open in more than one window; r=past

This commit is contained in:
Victor Porof 2012-06-20 15:21:46 +03:00
parent 727e12a234
commit 91b6ae553a
5 changed files with 351 additions and 48 deletions

View File

@ -22,7 +22,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let EXPORTED_SYMBOLS = ["DebuggerUI"];
/**
* Provides a simple mechanism of managing debugger instances per tab.
* Provides a simple mechanism of managing debugger instances.
*
* @param nsIDOMWindow aWindow
* The chrome window for which the DebuggerUI instance is created.
@ -71,7 +71,7 @@ DebuggerUI.prototype = {
* @return DebuggerPane if the debugger is started, null if it's stopped.
*/
toggleDebugger: function DUI_toggleDebugger() {
let scriptDebugger = this.getDebugger();
let scriptDebugger = this.findDebugger();
let selectedTab = this.chromeWindow.gBrowser.selectedTab;
if (scriptDebugger) {
@ -110,34 +110,55 @@ DebuggerUI.prototype = {
chromeDebugger.close();
return null;
}
return new ChromeDebuggerProcess(this.chromeWindow, aOnClose, aOnRun, true);
return new ChromeDebuggerProcess(this, aOnClose, aOnRun);
},
/**
* Gets the script debugger in any open window.
*
* @return DebuggerPane | null
* The script debugger instance if it exists, null otherwise.
*/
findDebugger: function DUI_findDebugger() {
let enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
let chromeWindow = enumerator.getNext().QueryInterface(Ci.nsIDOMWindow);
let scriptDebugger = chromeWindow.DebuggerUI.getDebugger();
if (scriptDebugger) {
return scriptDebugger;
}
}
return null;
},
/**
* Get the current script debugger.
* @return DebuggerPane if a debugger exists for the tab, null otherwise.
*
* @return DebuggerPane | null
* The script debugger instance if it exists, null otherwise.
*/
getDebugger: function DUI_getDebugger() {
let win = this.chromeWindow;
return '_scriptDebugger' in win ? win._scriptDebugger : null;
return '_scriptDebugger' in this ? this._scriptDebugger : null;
},
/**
* Get the remote debugger for the current chrome window.
* @return RemoteDebuggerWindow if a remote debugger exists, null otherwise.
*
* @return RemoteDebuggerWindow | null
* The remote debugger instance if it exists, null otherwise.
*/
getRemoteDebugger: function DUI_getRemoteDebugger() {
let win = this.chromeWindow;
return '_remoteDebugger' in win ? win._remoteDebugger : null;
return '_remoteDebugger' in this ? this._remoteDebugger : null;
},
/**
* Get the chrome debugger for the current firefox instance.
* @return ChromeDebuggerProcess if a chrome debugger exists, null otherwise.
*
* @return ChromeDebuggerProcess | null
* The chrome debugger instance if it exists, null otherwise.
*/
getChromeDebugger: function DUI_getChromeDebugger() {
let win = this.chromeWindow;
return '_chromeDebugger' in win ? win._chromeDebugger : null;
return '_chromeDebugger' in this ? this._chromeDebugger : null;
},
/**
@ -170,14 +191,17 @@ DebuggerUI.prototype = {
label: L10N.getStr("confirmTabSwitch.buttonSwitch"),
accessKey: L10N.getStr("confirmTabSwitch.buttonSwitch.accessKey"),
callback: function DUI_notificationButtonSwitch() {
gBrowser.selectedTab = this.getDebugger().ownerTab;
let scriptDebugger = this.findDebugger();
let targetWindow = scriptDebugger.globalUI.chromeWindow;
targetWindow.gBrowser.selectedTab = scriptDebugger.ownerTab;
targetWindow.focus();
}.bind(this)
}, {
id: "debugger.confirmTabSwitch.buttonOpen",
label: L10N.getStr("confirmTabSwitch.buttonOpen"),
accessKey: L10N.getStr("confirmTabSwitch.buttonOpen.accessKey"),
callback: function DUI_notificationButtonOpen() {
this.getDebugger().close();
this.findDebugger().close();
this.toggleDebugger();
}.bind(this)
}];
@ -204,7 +228,7 @@ DebuggerUI.prototype = {
* The tab in which to create the debugger.
*/
function DebuggerPane(aDebuggerUI, aTab) {
this._globalUI = aDebuggerUI;
this.globalUI = aDebuggerUI;
this._win = aDebuggerUI.chromeWindow;
this._tab = aTab;
@ -229,7 +253,7 @@ DebuggerPane.prototype = {
* Creates and initializes the widgets containing the debugger UI.
*/
_create: function DP__create() {
this._win._scriptDebugger = this;
this.globalUI._scriptDebugger = this;
let gBrowser = this._win.gBrowser;
let ownerDocument = gBrowser.parentNode.ownerDocument;
@ -260,7 +284,7 @@ DebuggerPane.prototype = {
}, true);
this._frame.setAttribute("src", DBG_XUL);
this._globalUI.refreshCommand();
this.globalUI.refreshCommand();
},
/**
@ -271,10 +295,10 @@ DebuggerPane.prototype = {
* the panel successfully closes.
*/
close: function DP_close(aCloseCallback) {
if (!this._win) {
if (!this.globalUI) {
return;
}
delete this._win._scriptDebugger;
delete this.globalUI._scriptDebugger;
this._win = null;
this._tab = null;
@ -299,7 +323,8 @@ DebuggerPane.prototype = {
this._frame = null;
this._nbox = null;
this._globalUI.refreshCommand();
this.globalUI.refreshCommand();
this.globalUI = null;
},
/**
@ -338,7 +363,7 @@ DebuggerPane.prototype = {
* The parent instance creating the new debugger.
*/
function RemoteDebuggerWindow(aDebuggerUI) {
this._globalUI = aDebuggerUI;
this.globalUI = aDebuggerUI;
this._win = aDebuggerUI.chromeWindow;
this._create();
@ -350,9 +375,9 @@ RemoteDebuggerWindow.prototype = {
* Creates and initializes the widgets containing the remote debugger UI.
*/
_create: function DP__create() {
this._win._remoteDebugger = this;
this.globalUI._remoteDebugger = this;
this._dbgwin = this._globalUI.chromeWindow.open(DBG_XUL,
this._dbgwin = this.globalUI.chromeWindow.open(DBG_XUL,
L10N.getStr("remoteDebuggerWindowTitle"),
"width=" + DebuggerPreferences.remoteWinWidth + "," +
"height=" + DebuggerPreferences.remoteWinHeight + "," +
@ -380,10 +405,11 @@ RemoteDebuggerWindow.prototype = {
* Closes the remote debugger, along with the parent window if necessary.
*/
close: function DP_close() {
if (!this._win) {
if (!this.globalUI) {
return;
}
delete this._win._remoteDebugger;
delete this.globalUI._remoteDebugger;
this.globalUI = null;
this._win = null;
this._dbgwin.close();
@ -414,15 +440,16 @@ RemoteDebuggerWindow.prototype = {
/**
* Creates a process that will hold a chrome debugger.
*
* @param DebuggerUI aDebuggerUI
* The parent instance creating the new debugger.
* @param function aOnClose
* Optional, a function called when the process exits.
* @param function aOnRun
* Optional, a function called when the process starts running.
* @param nsIDOMWindow aWindow
* The chrome window for which the debugger instance is created.
*/
function ChromeDebuggerProcess(aWindow, aOnClose, aOnRun) {
this._win = aWindow;
function ChromeDebuggerProcess(aDebuggerUI, aOnClose, aOnRun) {
this.globalUI = aDebuggerUI;
this._win = aDebuggerUI.chromeWindow;
this._closeCallback = aOnClose;
this._runCallback = aOnRun;
@ -494,7 +521,7 @@ ChromeDebuggerProcess.prototype = {
* Creates and initializes the profile & process for the remote debugger.
*/
_create: function RDP__create() {
this._win._chromeDebugger = this;
this.globalUI._chromeDebugger = this;
let file = FileUtils.getFile("CurProcD",
[Services.appinfo.OS == "WINNT" ? "firefox.exe"
@ -521,10 +548,11 @@ ChromeDebuggerProcess.prototype = {
* Closes the remote debugger, removing the profile and killing the process.
*/
close: function RDP_close() {
if (!this._win) {
if (!this.globalUI) {
return;
}
delete this._win._chromeDebugger;
delete this.globalUI._chromeDebugger;
this.globalUI = null;
this._win = null;
if (this._dbgProcess.isRunning) {

View File

@ -15,6 +15,7 @@ _BROWSER_TEST_FILES = \
browser_dbg_createRemote.js \
browser_dbg_createChrome.js \
browser_dbg_debugger-tab-switch.js \
browser_dbg_debugger-tab-switch-window.js \
browser_dbg_debuggerstatement.js \
browser_dbg_listtabs.js \
browser_dbg_tabactor-01.js \

View File

@ -0,0 +1,244 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
let gInitialTab, gTab1, gTab2, gTab3, gTab4;
let gInitialWindow, gSecondWindow;
let gPane1, gPane2;
let gNbox;
/**
* Tests that a debugger instance can't be opened in multiple windows at once,
* and that on such an attempt a notification is shown, which can either switch
* to the old debugger instance, or close the old instance to open a new one.
*/
function test() {
gInitialWindow = window;
gInitialTab = window.gBrowser.selectedTab;
gNbox = gInitialWindow.gBrowser.getNotificationBox(gInitialWindow.gBrowser.selectedBrowser);
testTab1_initialWindow(function() {
testTab2_secondWindow(function() {
testTab3_secondWindow(function() {
testTab4_secondWindow(function() {
lastTest(function() {
cleanup(function() {
finish();
});
});
});
});
});
});
}
function testTab1_initialWindow(callback) {
gTab1 = addTab(TAB1_URL, function() {
gInitialWindow.gBrowser.selectedTab = gTab1;
gNbox = gInitialWindow.gBrowser.getNotificationBox(gInitialWindow.gBrowser.selectedBrowser);
is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
"Shouldn't have a tab switch notification.");
ok(!gInitialWindow.DebuggerUI.getDebugger(),
"Shouldn't have a debugger pane for this tab yet.");
info("Toggling a debugger (1).");
gPane1 = gInitialWindow.DebuggerUI.toggleDebugger();
ok(gPane1, "toggleDebugger() should return a pane.");
is(gPane1.ownerTab, gTab1, "Incorrect tab owner.");
is(gInitialWindow.DebuggerUI.getDebugger(), gPane1,
"getDebugger() should return the same pane as toggleDebugger().");
wait_for_connect_and_resume(function dbgLoaded() {
info("First debugger has finished loading correctly.");
executeSoon(function() {
callback();
});
}, gInitialWindow);
}, gInitialWindow);
}
function testTab2_secondWindow(callback) {
gSecondWindow = addWindow();
gTab2 = addTab(TAB1_URL, function() {
gSecondWindow.gBrowser.selectedTab = gTab2;
gNbox = gSecondWindow.gBrowser.getNotificationBox(gSecondWindow.gBrowser.selectedBrowser);
is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
"Shouldn't have a tab switch notification yet.");
ok(gSecondWindow.DebuggerUI.findDebugger(),
"Should already have a debugger pane for another tab.");
gNbox.addEventListener("AlertActive", function active() {
gNbox.removeEventListener("AlertActive", active, true);
executeSoon(function() {
ok(gPane2, "toggleDebugger() should always return a pane.");
is(gPane2.ownerTab, gTab1, "Incorrect tab owner.");
is(gSecondWindow.DebuggerUI.findDebugger(), gPane1,
"findDebugger() should return the same pane as the first call to toggleDebugger().");
is(gSecondWindow.DebuggerUI.findDebugger(), gPane2,
"findDebugger() should return the same pane as the second call to toggleDebugger().");
info("Second debugger has not loaded.");
let notification = gNbox.getNotificationWithValue("debugger-tab-switch");
ok(gNbox.currentNotification, "Should have a tab switch notification.");
is(gNbox.currentNotification, notification, "Incorrect current notification.");
info("Notification will be simply closed.");
notification.close();
executeSoon(function() {
callback();
});
});
}, true);
info("Toggling a debugger (2).");
gPane2 = gSecondWindow.DebuggerUI.toggleDebugger();
}, gSecondWindow);
}
function testTab3_secondWindow(callback) {
gTab3 = addTab(TAB1_URL, function() {
gSecondWindow.gBrowser.selectedTab = gTab3;
gNbox = gSecondWindow.gBrowser.getNotificationBox(gSecondWindow.gBrowser.selectedBrowser);
is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
"Shouldn't have a tab switch notification.");
ok(gSecondWindow.DebuggerUI.findDebugger(),
"Should already have a debugger pane for another tab.");
gNbox.addEventListener("AlertActive", function active() {
gNbox.removeEventListener("AlertActive", active, true);
executeSoon(function() {
ok(gPane2, "toggleDebugger() should always return a pane.");
is(gPane2.ownerTab, gTab1, "Incorrect tab owner.");
is(gSecondWindow.DebuggerUI.findDebugger(), gPane1,
"findDebugger() should return the same pane as the first call to toggleDebugger().");
is(gSecondWindow.DebuggerUI.findDebugger(), gPane2,
"findDebugger() should return the same pane as the second call to toggleDebugger().");
info("Second debugger has not loaded.");
let notification = gNbox.getNotificationWithValue("debugger-tab-switch");
ok(gNbox.currentNotification, "Should have a tab switch notification.");
is(gNbox.currentNotification, notification, "Incorrect current notification.");
gInitialWindow.gBrowser.selectedTab = gInitialTab;
gInitialWindow.gBrowser.tabContainer.addEventListener("TabSelect", function tabSelect() {
gInitialWindow.gBrowser.tabContainer.removeEventListener("TabSelect", tabSelect, true);
executeSoon(function() {
callback();
});
}, true);
let buttonSwitch = notification.querySelectorAll("button")[0];
buttonSwitch.focus();
EventUtils.sendKey("SPACE", gSecondWindow);
info("The switch button on the notification was pressed.");
});
}, true);
info("Toggling a debugger (3).");
gPane2 = gSecondWindow.DebuggerUI.toggleDebugger();
}, gSecondWindow);
}
function testTab4_secondWindow(callback) {
is(gInitialWindow.gBrowser.selectedTab, gTab1,
"Should've switched to the first debugged tab.");
gTab4 = addTab(TAB1_URL, function() {
gSecondWindow.gBrowser.selectedTab = gTab4;
gNbox = gSecondWindow.gBrowser.getNotificationBox(gSecondWindow.gBrowser.selectedBrowser);
is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
"Shouldn't have a tab switch notification.");
ok(gSecondWindow.DebuggerUI.findDebugger(),
"Should already have a debugger pane for another tab.");
gNbox.addEventListener("AlertActive", function active() {
gNbox.removeEventListener("AlertActive", active, true);
executeSoon(function() {
ok(gPane2, "toggleDebugger() should always return a pane.");
is(gPane2.ownerTab, gTab1, "Incorrect tab owner.");
is(gSecondWindow.DebuggerUI.findDebugger(), gPane1,
"findDebugger() should return the same pane as the first call to toggleDebugger().");
is(gSecondWindow.DebuggerUI.findDebugger(), gPane2,
"findDebugger() should return the same pane as the second call to toggleDebugger().");
info("Second debugger has not loaded.");
let notification = gNbox.getNotificationWithValue("debugger-tab-switch");
ok(gNbox.currentNotification, "Should have a tab switch notification.");
is(gNbox.currentNotification, notification, "Incorrect current notification.");
let buttonOpen = notification.querySelectorAll("button")[1];
buttonOpen.focus();
EventUtils.sendKey("SPACE", gSecondWindow);
info("The open button on the notification was pressed.");
wait_for_connect_and_resume(function() {
callback();
}, gSecondWindow);
});
}, true);
info("Toggling a debugger (4).");
gPane2 = gSecondWindow.DebuggerUI.toggleDebugger();
}, gSecondWindow);
}
function lastTest(callback) {
is(gInitialWindow.gBrowser.selectedTab, gTab1,
"The initial window should continue having selected the first debugged tab.");
is(gSecondWindow.gBrowser.selectedTab, gTab4,
"Should currently be in the fourth tab.");
is(gSecondWindow.DebuggerUI.findDebugger().ownerTab, gTab4,
"The debugger should be open for the fourth tab.");
is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
"Shouldn't have a tab switch notification.");
info("Second debugger has loaded.");
executeSoon(function() {
callback();
});
}
function cleanup(callback)
{
gPane1 = null;
gPane2 = null;
gNbox = null;
closeDebuggerAndFinish(false, function() {
removeTab(gTab1, gInitialWindow);
removeTab(gTab2, gSecondWindow);
removeTab(gTab3, gSecondWindow);
removeTab(gTab4, gSecondWindow);
gSecondWindow.close();
gTab1 = null;
gTab2 = null;
gTab3 = null;
gTab4 = null;
gInitialWindow = null;
gSecondWindow = null;
callback();
}, gSecondWindow);
}

View File

@ -8,6 +8,12 @@ let gTab1, gTab2, gTab3, gTab4;
let gPane1, gPane2;
let gNbox;
/**
* Tests that a debugger instance can't be opened in multiple tabs at once,
* and that on such an attempt a notification is shown, which can either switch
* to the old debugger instance, or close the old instance to open a new one.
*/
function test() {
gNbox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
@ -50,7 +56,7 @@ function testTab1(callback) {
executeSoon(function() {
callback();
});
}, true);
});
});
}
@ -208,8 +214,8 @@ function lastTest(callback) {
});
}
function cleanup(callback) {
function cleanup(callback)
{
gPane1 = null;
gPane2 = null;
gNbox = null;

View File

@ -33,11 +33,26 @@ if (!DebuggerServer.initialized) {
waitForExplicitFinish();
function addTab(aURL, aOnload)
function addWindow()
{
gBrowser.selectedTab = gBrowser.addTab(aURL);
let windowReference = window.open();
let chromeWindow = windowReference
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
let tab = gBrowser.selectedTab;
return chromeWindow;
}
function addTab(aURL, aOnload, aWindow)
{
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
targetWindow.focus();
targetBrowser.selectedTab = targetBrowser.addTab(aURL);
let tab = targetBrowser.selectedTab;
if (aOnload) {
let handler = function() {
if (tab.linkedBrowser.currentURI.spec == aURL) {
@ -51,11 +66,17 @@ function addTab(aURL, aOnload)
return tab;
}
function removeTab(aTab) {
gBrowser.removeTab(aTab);
function removeTab(aTab, aWindow) {
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
targetBrowser.removeTab(aTab);
}
function closeDebuggerAndFinish(aRemoteFlag, aCallback) {
function closeDebuggerAndFinish(aRemoteFlag, aCallback, aWindow) {
let targetWindow = aWindow || window;
let debuggerUI = targetWindow.DebuggerUI;
let debuggerClosed = false;
let debuggerDisconnected = false;
@ -67,19 +88,19 @@ function closeDebuggerAndFinish(aRemoteFlag, aCallback) {
}
}
DebuggerUI.chromeWindow.addEventListener("Debugger:Shutdown", function cleanup() {
DebuggerUI.chromeWindow.removeEventListener("Debugger:Shutdown", cleanup, false);
debuggerUI.chromeWindow.addEventListener("Debugger:Shutdown", function cleanup() {
debuggerUI.chromeWindow.removeEventListener("Debugger:Shutdown", cleanup, false);
debuggerDisconnected = true;
_maybeFinish();
}, false);
if (!aRemoteFlag) {
DebuggerUI.getDebugger().close(function() {
debuggerUI.getDebugger().close(function() {
debuggerClosed = true;
_maybeFinish();
});
} else {
debuggerClosed = true;
DebuggerUI.getRemoteDebugger().close();
debuggerUI.getRemoteDebugger().close();
}
}
@ -132,10 +153,13 @@ function debug_tab_pane(aURL, aOnDebugging)
});
}
function wait_for_connect_and_resume(aOnDebugging)
function wait_for_connect_and_resume(aOnDebugging, aWindow)
{
window.document.addEventListener("Debugger:Connecting", function dbgConnected(aEvent) {
window.document.removeEventListener("Debugger:Connecting", dbgConnected, true);
let targetWindow = aWindow || window;
let targetDocument = targetWindow.document;
targetDocument.addEventListener("Debugger:Connecting", function dbgConnected(aEvent) {
targetDocument.removeEventListener("Debugger:Connecting", dbgConnected, true);
// Wait for the initial resume...
aEvent.target.ownerDocument.defaultView.gClient.addOneTimeListener("resumed", function() {