Bug 741324 - Make it possible to start a debugger in a new firefox instance; r=past,rcampbell,zpao

This commit is contained in:
Victor Porof 2012-04-12 18:47:56 +03:00
parent f496b1f174
commit 5eed6b0f7e
11 changed files with 378 additions and 19 deletions

View File

@ -1061,9 +1061,14 @@ pref("devtools.layoutview.open", false);
// Enable the Debugger
pref("devtools.debugger.enabled", false);
pref("devtools.debugger.remote-enabled", false);
pref("devtools.debugger.remote-host", "localhost");
pref("devtools.debugger.remote-port", 6000);
// The default Debugger UI height
pref("devtools.debugger.ui.height", 250);
pref("devtools.debugger.ui.remote-win.width", 900);
pref("devtools.debugger.ui.remote-win.height", 400);
// Enable the style inspector
pref("devtools.styleinspector.enabled", true);

View File

@ -201,6 +201,10 @@
label="&debuggerMenu.label;"
key="key_debugger"
command="Tools:Debugger"/>
<menuitem id="appmenu_remoteDebugger"
hidden="true"
label="&remoteDebuggerMenu.label;"
command="Tools:RemoteDebugger"/>
<menuitem id="appmenu_scratchpad"
hidden="true"
label="&scratchpad.label;"

View File

@ -559,6 +559,10 @@
label="&debuggerMenu.label;"
key="key_debugger"
command="Tools:Debugger"/>
<menuitem id="menu_remoteDebugger"
hidden="true"
label="&remoteDebuggerMenu.label;"
command="Tools:RemoteDebugger"/>
<menuitem id="menu_scratchpad"
hidden="true"
label="&scratchpad.label;"

View File

@ -130,6 +130,7 @@
<command id="Tools:WebConsole" oncommand="HUDConsoleUI.toggleHUD();"/>
<command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();" disabled="true"/>
<command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();" disabled="true"/>
<command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();" disabled="true"/>
<command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true"/>
<command id="Tools:StyleEditor" oncommand="StyleEditor.openChrome();" disabled="true"/>
<command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>

View File

@ -1734,6 +1734,16 @@ function delayedStartup(isLoadingBlank, mustLoadSidebar) {
document.getElementById("developer-toolbar-debugger").hidden = false;
}
// Enable Remote Debugger?
let enabled = gPrefService.getBoolPref("devtools.debugger.remote-enabled");
if (enabled) {
document.getElementById("menu_remoteDebugger").hidden = false;
document.getElementById("Tools:RemoteDebugger").removeAttribute("disabled");
#ifdef MENUBAR_CAN_AUTOHIDE
document.getElementById("appmenu_remoteDebugger").hidden = false;
#endif
}
// Enable Error Console?
// XXX Temporarily always-enabled, see bug 601201
let consoleEnabled = true || gPrefService.getBoolPref("devtools.errorconsole.enabled");

View File

@ -45,7 +45,13 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const DBG_XUL = "chrome://browser/content/debugger.xul";
const REMOTE_PROFILE_NAME = "_remote-debug";
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let EXPORTED_SYMBOLS = ["DebuggerUI"];
@ -89,6 +95,21 @@ DebuggerUI.prototype = {
return new DebuggerPane(this, tab);
},
/**
* Starts a remote debugger in a new process, or stops it if already started.
* @see DebuggerProcess.constructor
* @return DebuggerProcess if the debugger is started, null if it's stopped.
*/
toggleRemoteDebugger: function DUI_toggleRemoteDebugger(aOnClose, aOnRun) {
let win = this.chromeWindow;
if (win._remoteDebugger) {
win._remoteDebugger.close();
return null;
}
return new DebuggerProcess(win, aOnClose, aOnRun);
},
/**
* Get the debugger for a specified tab.
* @return DebuggerPane if a debugger exists for the tab, null otherwise
@ -102,7 +123,7 @@ DebuggerUI.prototype = {
* @return object
*/
get preferences() {
return DebuggerUIPreferences;
return DebuggerPreferences;
}
};
@ -115,11 +136,22 @@ DebuggerUI.prototype = {
function DebuggerPane(aDebuggerUI, aTab) {
this._globalUI = aDebuggerUI;
this._tab = aTab;
this._initServer();
this._create();
}
DebuggerPane.prototype = {
/**
* Initializes the debugger server.
*/
_initServer: function DP__initServer() {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
},
/**
* Creates and initializes the widgets containing the debugger UI.
*/
@ -133,7 +165,7 @@ DebuggerPane.prototype = {
this._splitter.setAttribute("class", "hud-splitter");
this._frame = ownerDocument.createElement("iframe");
this._frame.height = DebuggerUIPreferences.height;
this._frame.height = DebuggerPreferences.height;
this._nbox = gBrowser.getNotificationBox(this._tab.linkedBrowser);
this._nbox.appendChild(this._splitter);
@ -154,7 +186,7 @@ DebuggerPane.prototype = {
self.getBreakpoint = bkp.getBreakpoint;
}, true);
this._frame.setAttribute("src", "chrome://browser/content/debugger.xul");
this._frame.setAttribute("src", DBG_XUL);
this._globalUI.refreshCommand();
},
@ -166,10 +198,10 @@ DebuggerPane.prototype = {
if (!this._tab) {
return;
}
this._tab._scriptDebugger = null;
delete this._tab._scriptDebugger;
this._tab = null;
DebuggerUIPreferences.height = this._frame.height;
DebuggerPreferences.height = this._frame.height;
this._frame.removeEventListener("Debugger:Close", this.close, true);
this._frame.removeEventListener("unload", this.close, true);
@ -205,9 +237,112 @@ DebuggerPane.prototype = {
};
/**
* Various debugger UI preferences (currently just the pane height).
* Creates a process that will hold the remote 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 remote debugger instance is created.
*/
let DebuggerUIPreferences = {
function DebuggerProcess(aWindow, aOnClose, aOnRun) {
this._win = aWindow;
this._closeCallback = aOnClose;
this._runCallback = aOnRun;
this._initProfile();
this._create();
}
DebuggerProcess.prototype = {
/**
* Initializes the debugger server.
*/
_initServer: function RDP__initServer() {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
DebuggerServer.closeListener();
DebuggerServer.openListener(DebuggerPreferences.remotePort, false);
},
/**
* Initializes a profile for the remote debugger process.
*/
_initProfile: function RDP__initProfile() {
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
.createInstance(Ci.nsIToolkitProfileService);
let dbgProfileName;
try {
dbgProfileName = profileService.selectedProfile.name + REMOTE_PROFILE_NAME;
} catch(e) {
dbgProfileName = REMOTE_PROFILE_NAME;
Cu.reportError(e);
}
this._dbgProfile = profileService.createProfile(null, null, dbgProfileName);
profileService.flush();
},
/**
* Creates and initializes the profile & process for the remote debugger.
*/
_create: function RDP__create() {
this._win._remoteDebugger = this;
let file = FileUtils.getFile("CurProcD",
[Services.appinfo.OS == "WINNT" ? "firefox.exe"
: "firefox-bin"]);
let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(file);
let args = [
"-no-remote", "-P", this._dbgProfile.name,
"-chrome", DBG_XUL,
"-width", DebuggerPreferences.remoteWinWidth,
"-height", DebuggerPreferences.remoteWinHeight];
process.runwAsync(args, args.length, { observe: this.close.bind(this) });
this._dbgProcess = process;
if (typeof this._runCallback === "function") {
this._runCallback.call({}, this);
}
},
/**
* Closes the remote debugger, removing the profile and killing the process.
*/
close: function RDP_close() {
if (!this._win) {
return;
}
delete this._win._remoteDebugger;
this._win = null;
if (this._dbgProcess.isRunning) {
this._dbgProcess.kill();
}
if (this._dbgProfile) {
this._dbgProfile.remove(false);
}
if (typeof this._closeCallback === "function") {
this._closeCallback.call({}, this);
}
this._dbgProcess = null;
this._dbgProfile = null;
}
};
/**
* Various debugger preferences.
*/
let DebuggerPreferences = {
/**
* Gets the preferred height of the debugger pane.
@ -229,3 +364,35 @@ let DebuggerUIPreferences = {
this._height = value;
}
};
/**
* Gets the preferred width of the remote debugger window.
* @return number
*/
XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteWinWidth", function() {
return Services.prefs.getIntPref("devtools.debugger.ui.remote-win.width");
});
/**
* Gets the preferred height of the remote debugger window.
* @return number
*/
XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteWinHeight", function() {
return Services.prefs.getIntPref("devtools.debugger.ui.remote-win.height");
});
/**
* Gets the preferred default remote debugging host.
* @return string
*/
XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteHost", function() {
return Services.prefs.getCharPref("devtools.debugger.remote-host");
});
/**
* Gets the preferred default remote debugging port.
* @return number
*/
XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remotePort", function() {
return Services.prefs.getIntPref("devtools.debugger.remote-port");
});

View File

@ -114,6 +114,7 @@ let DebuggerController = {
this.dispatchEvent("Debugger:Unloaded");
this._disconnect();
this._isRemote && this._quitApp();
},
/**
@ -121,12 +122,10 @@ let DebuggerController = {
* wiring event handlers as necessary.
*/
_connect: function DC__connect() {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
let transport =
this._isRemote ? debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort)
: DebuggerServer.connectPipe();
let transport = DebuggerServer.connectPipe();
let client = this.client = new DebuggerClient(transport);
client.addListener("tabNavigated", this._onTabNavigated);
@ -220,6 +219,31 @@ let DebuggerController = {
}.bind(this));
},
/**
* Returns true if this is a remote debugger instance.
* @return boolean
*/
get _isRemote() {
return !window.parent.content;
},
/**
* Attempts to quit the current process if allowed.
*/
_quitApp: function DC__quitApp() {
let canceled = Cc["@mozilla.org/supports-PRBool;1"]
.createInstance(Ci.nsISupportsPRBool);
Services.obs.notifyObservers(canceled, "quit-application-requested", null);
// Somebody canceled our quit request.
if (canceled.data) {
return;
}
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
},
/**
* Convenience method, dispatching a custom event.
*
@ -767,6 +791,22 @@ SourceScripts.prototype = {
return aUrl;
},
/**
* Gets the prePath for a script URL.
*
* @param string aUrl
* The script url.
* @return string
* The script prePath if the url is valid, null otherwise.
*/
_getScriptPrePath: function SS__getScriptDomain(aUrl) {
try {
return Services.io.newURI(aUrl, null, null).prePath + "/";
} catch (e) {
}
return null;
},
/**
* Gets a unique, simplified label from a script url.
* ex: a). ici://some.address.com/random/subrandom/
@ -783,7 +823,7 @@ SourceScripts.prototype = {
* The script url.
* @param string aHref
* The content location href to be used. If unspecified, it will
* defalult to debugged panrent window location.
* default to the script url prepath.
* @return string
* The simplified label.
*/
@ -794,15 +834,18 @@ SourceScripts.prototype = {
return this._labelsCache[url];
}
let href = aHref || window.parent.content.location.href;
let content = window.parent.content;
let domain = content ? content.location.href : this._getScriptPrePath(aUrl);
let href = aHref || domain;
let pathElements = url.split("/");
let label = pathElements.pop() || (pathElements.pop() + "/");
// if the label as a leaf name is alreay present in the scripts list
// If the label as a leaf name is already present in the scripts list.
if (DebuggerView.Scripts.containsLabel(label)) {
label = url.replace(href.substring(0, href.lastIndexOf("/") + 1), "");
// if the path/to/script is exactly the same, we're in different domains
// If the path/to/script is exactly the same, we're in different domains.
if (DebuggerView.Scripts.containsLabel(label)) {
label = url;
}
@ -897,7 +940,7 @@ SourceScripts.prototype = {
* Handles notifications to load a source script from the cache or from a
* local file.
*
* XXX: Tt may be better to use nsITraceableChannel to get to the sources
* XXX: It may be better to use nsITraceableChannel to get to the sources
* without relying on caching when we can (not for eval, etc.):
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
*/
@ -988,7 +1031,7 @@ SourceScripts.prototype = {
* The failure status code.
*/
_logError: function SS__logError(aUrl, aStatus) {
Components.utils.reportError(L10N.getFormatStr("loadingError", [aUrl, aStatus]));
Cu.reportError(L10N.getFormatStr("loadingError", [aUrl, aStatus]));
},
};
@ -1279,6 +1322,27 @@ XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
return Services.strings.createBundle(DBG_STRINGS_URI);
});
/**
* Shortcuts for accessing various debugger preferences.
*/
let Prefs = {};
/**
* Gets the preferred default remote debugging host.
* @return string
*/
XPCOMUtils.defineLazyGetter(Prefs, "remoteHost", function() {
return Services.prefs.getCharPref("devtools.debugger.remote-host");
});
/**
* Gets the preferred default remote debugging port.
* @return number
*/
XPCOMUtils.defineLazyGetter(Prefs, "remotePort", function() {
return Services.prefs.getIntPref("devtools.debugger.remote-port");
});
/**
* Preliminary setup for the DebuggerController object.
*/

View File

@ -46,6 +46,7 @@ include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_BROWSER_TEST_FILES = \
browser_dbg_createRemote.js \
browser_dbg_debuggerstatement.js \
browser_dbg_listtabs.js \
browser_dbg_tabactor-01.js \

View File

@ -0,0 +1,86 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var gProcess = null;
var gTab = null;
var gDebuggee = null;
function test() {
remote_debug_tab_pane(STACK_URL, aOnClosing, function(aTab, aDebuggee, aProcess) {
gTab = aTab;
gDebuggee = aDebuggee;
gProcess = aProcess;
testSimpleCall();
});
}
function testSimpleCall() {
Services.tm.currentThread.dispatch({ run: function() {
ok(gProcess._dbgProcess,
"The remote debugger process wasn't created properly!");
ok(gProcess._dbgProcess.isRunning,
"The remote debugger process isn't running!");
is(typeof gProcess._dbgProcess.pid, "number",
"The remote debugger process doesn't have a pid (?!)");
info("process location: " + gProcess._dbgProcess.location);
info("process pid: " + gProcess._dbgProcess.pid);
info("process name: " + gProcess._dbgProcess.processName);
info("process sig: " + gProcess._dbgProcess.processSignature);
ok(gProcess._dbgProfile,
"The remote debugger profile wasn't created properly!");
ok(gProcess._dbgProfile.localDir,
"The remote debugger profile doesn't have a localDir...");
ok(gProcess._dbgProfile.rootDir,
"The remote debugger profile doesn't have a rootDir...");
ok(gProcess._dbgProfile.name,
"The remote debugger profile doesn't have a name...");
info("profile localDir: " + gProcess._dbgProfile.localDir);
info("profile rootDir: " + gProcess._dbgProfile.rootDir);
info("profile name: " + gProcess._dbgProfile.name);
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
.createInstance(Ci.nsIToolkitProfileService);
let profile = profileService.getProfileByName(gProcess._dbgProfile.name);
ok(profile,
"The remote debugger profile wasn't *actually* created properly!");
is(profile.localDir.path, gProcess._dbgProfile.localDir.path,
"The remote debugger profile doesn't have the correct localDir!");
is(profile.rootDir.path, gProcess._dbgProfile.rootDir.path,
"The remote debugger profile doesn't have the correct rootDir!");
DebuggerUI.toggleRemoteDebugger();
}}, 0);
}
function aOnClosing() {
ok(!gProcess._dbgProcess.isRunning,
"The remote debugger process isn't closed as it should be!");
is(gProcess._dbgProcess.exitValue, (Services.appinfo.OS == "WINNT" ? 0 : 256),
"The remote debugger process didn't die cleanly.");
info("process exit value: " + gProcess._dbgProcess.exitValue);
info("profile localDir: " + gProcess._dbgProfile.localDir.path);
info("profile rootDir: " + gProcess._dbgProfile.rootDir.path);
info("profile name: " + gProcess._dbgProfile.name);
executeSoon(function() {
finish();
});
}
registerCleanupFunction(function() {
removeTab(gTab);
gProcess = null;
gTab = null;
gDebuggee = null;
});

View File

@ -92,7 +92,6 @@ function debug_tab_pane(aURL, aOnDebugging)
{
let tab = addTab(aURL, function() {
gBrowser.selectedTab = gTab;
let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
let pane = DebuggerUI.toggleDebugger();
@ -106,3 +105,17 @@ function debug_tab_pane(aURL, aOnDebugging)
}, true);
});
}
function remote_debug_tab_pane(aURL, aOnClosing, aOnDebugging)
{
let tab = addTab(aURL, function() {
gBrowser.selectedTab = gTab;
let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
DebuggerUI.toggleRemoteDebugger(aOnClosing, function dbgRan(process) {
// Wait for the remote debugging process to start...
aOnDebugging(tab, debuggee, process);
});
});
}

View File

@ -11,6 +11,10 @@
- application menu item that opens the debugger UI. -->
<!ENTITY debuggerMenu.label "Script Debugger">
<!-- LOCALIZATION NOTE (remoteDebuggerMenu.label): This is the label for the
- application menu item that opens the remote debugger UI. -->
<!ENTITY remoteDebuggerMenu.label "Remote Debugger">
<!-- LOCALIZATION NOTE (debuggerMenu.commandkey): This is the command key that
- launches the debugger UI. Do not translate this one! -->
<!ENTITY debuggerMenu.commandkey "S">