Bug 723071 - Add a pane to display the list of breakpoints across all scripts in the debuggee; f=msucan,past r=past

This commit is contained in:
Victor Porof 2012-07-15 09:40:37 +03:00
parent d10808c6ce
commit 0a14cf5b14
14 changed files with 1065 additions and 57 deletions

View File

@ -52,6 +52,7 @@ let DebuggerController = {
DebuggerView.initializePanes();
DebuggerView.initializeEditor();
DebuggerView.StackFrames.initialize();
DebuggerView.Breakpoints.initialize();
DebuggerView.Properties.initialize();
DebuggerView.Scripts.initialize();
DebuggerView.showCloseButton(!this._isRemoteDebugger && !this._isChromeDebugger);
@ -75,6 +76,7 @@ let DebuggerController = {
DebuggerView.destroyEditor();
DebuggerView.Scripts.destroy();
DebuggerView.StackFrames.destroy();
DebuggerView.Breakpoints.destroy();
DebuggerView.Properties.destroy();
DebuggerController.Breakpoints.destroy();
@ -395,8 +397,6 @@ StackFrames.prototype = {
connect: function SF_connect(aCallback) {
window.addEventListener("Debugger:FetchedVariables", this._onFetchedVars, false);
this._onFramesCleared();
this.activeThread.addListener("paused", this._onPaused);
this.activeThread.addListener("resumed", this._onResume);
this.activeThread.addListener("framesadded", this._onFrames);
@ -506,12 +506,47 @@ StackFrames.prototype = {
let line = frame.where.line;
let editor = DebuggerView.editor;
// Move the editor's caret to the proper line.
if (DebuggerView.Scripts.isSelected(url) && line) {
editor.setDebugLocation(line - 1);
} else {
editor.setDebugLocation(-1);
this.updateEditorToLocation(url, line, true);
},
/**
* Update the source editor's current caret and debug location based on
* a specified url and line.
*
* @param string aUrl
* The target source url.
* @param number aLine
* The target line number in the source.
* @param boolean aNoSwitch
* Pass true to not switch to the script if not currently selected.
* @param boolean aNoCaretFlag
* Pass true to not set the caret location at the specified line.
* @param boolean aNoDebugFlag
* Pass true to not set the debug location at the specified line.
*/
updateEditorToLocation:
function SF_updateEditorToLocation(aUrl, aLine, aNoSwitch, aNoCaretFlag, aNoDebugFlag) {
let editor = DebuggerView.editor;
function set() {
if (!aNoCaretFlag) {
editor.setCaretPosition(aLine - 1);
}
if (!aNoDebugFlag) {
editor.setDebugLocation(aLine - 1);
}
}
// Move the editor's caret to the proper url and line.
if (DebuggerView.Scripts.isSelected(aUrl)) {
return set();
}
if (!aNoSwitch && DebuggerView.Scripts.contains(aUrl)) {
DebuggerView.Scripts.selectScript(aUrl);
return set();
}
editor.setCaretPosition(-1);
editor.setDebugLocation(-1);
},
/**
@ -550,20 +585,9 @@ StackFrames.prototype = {
let url = frame.where.url;
let line = frame.where.line;
let editor = DebuggerView.editor;
// Move the editor's caret to the proper line.
if (DebuggerView.Scripts.isSelected(url) && line) {
editor.setCaretPosition(line - 1);
editor.setDebugLocation(line - 1);
}
else if (DebuggerView.Scripts.contains(url)) {
DebuggerView.Scripts.selectScript(url);
editor.setCaretPosition(line - 1);
}
else {
editor.setDebugLocation(-1);
}
this.updateEditorToLocation(url, line);
// Start recording any added variables or properties in any scope.
DebuggerView.Properties.createHierarchyStore();
@ -755,7 +779,7 @@ StackFrames.prototype = {
*/
_addFrame: function SF__addFrame(aFrame) {
let depth = aFrame.depth;
let label = DebuggerController.SourceScripts._getScriptLabel(aFrame.where.url);
let label = DebuggerController.SourceScripts.getScriptLabel(aFrame.where.url);
let startText = this._getFrameTitle(aFrame);
let endText = label + ":" + aFrame.where.line;
@ -882,10 +906,12 @@ SourceScripts.prototype = {
}
this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true);
// If there are any stored breakpoints for this script, display them again.
for each (let bp in DebuggerController.Breakpoints.store) {
if (bp.location.url == aPacket.url) {
DebuggerController.Breakpoints.displayBreakpoint(bp.location);
// If there are any stored breakpoints for this script, display them again,
// both in the editor and the pane.
for each (let breakpoint in DebuggerController.Breakpoints.store) {
if (breakpoint.location.url == aPacket.url) {
DebuggerController.Breakpoints.displayBreakpoint(breakpoint);
}
}
},
@ -898,6 +924,7 @@ SourceScripts.prototype = {
this._addScript(script, false);
}
DebuggerView.Scripts.commitScripts();
DebuggerController.Breakpoints.updatePaneBreakpoints();
},
/**
@ -905,6 +932,7 @@ SourceScripts.prototype = {
*/
_onScriptsCleared: function SS__onScriptsCleared() {
DebuggerView.Scripts.empty();
DebuggerView.Breakpoints.emptyText();
},
/**
@ -967,7 +995,7 @@ SourceScripts.prototype = {
* @return string
* The resulting label at the final step.
*/
_trimURL: function SS__trimURL(aUrl, aLabel, aSeq) {
_trimUrl: function SS__trimUrl(aUrl, aLabel, aSeq) {
if (!(aUrl instanceof Ci.nsIURL)) {
try {
// Use an nsIURL to parse all the url path parts.
@ -1012,7 +1040,7 @@ SourceScripts.prototype = {
if (aSeq === 1) {
let query = aUrl.query;
if (query) {
return this._trimURL(aUrl, aLabel + "?" + query, aSeq + 1);
return this._trimUrl(aUrl, aLabel + "?" + query, aSeq + 1);
}
aSeq++;
}
@ -1020,7 +1048,7 @@ SourceScripts.prototype = {
if (aSeq === 2) {
let ref = aUrl.ref;
if (ref) {
return this._trimURL(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1);
return this._trimUrl(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1);
}
aSeq++;
}
@ -1028,7 +1056,7 @@ SourceScripts.prototype = {
if (aSeq === 3) {
let dir = aUrl.directory;
if (dir) {
return this._trimURL(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1);
return this._trimUrl(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1);
}
aSeq++;
}
@ -1036,13 +1064,13 @@ SourceScripts.prototype = {
if (aSeq === 4) {
let host = aUrl.hostPort;
if (host) {
return this._trimURL(aUrl, host + "/" + aLabel, aSeq + 1);
return this._trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
}
aSeq++;
}
// Use the whole url spec but ignoring the reference.
if (aSeq === 5) {
return this._trimURL(aUrl, aUrl.specIgnoringRef, aSeq + 1);
return this._trimUrl(aUrl, aUrl.specIgnoringRef, aSeq + 1);
}
// Give up.
return aUrl.spec;
@ -1059,8 +1087,8 @@ SourceScripts.prototype = {
* @return string
* The simplified label.
*/
_getScriptLabel: function SS__getScriptLabel(aUrl, aHref) {
return this._labelsCache[aUrl] || (this._labelsCache[aUrl] = this._trimURL(aUrl));
getScriptLabel: function SS_getScriptLabel(aUrl, aHref) {
return this._labelsCache[aUrl] || (this._labelsCache[aUrl] = this._trimUrl(aUrl));
},
/**
@ -1081,7 +1109,7 @@ SourceScripts.prototype = {
*/
_addScript: function SS__addScript(aScript, aForceFlag) {
DebuggerView.Scripts.addScript(
this._getScriptLabel(aScript.url), aScript, aForceFlag);
this.getScriptLabel(aScript.url), aScript, aForceFlag);
},
/**
@ -1233,6 +1261,23 @@ SourceScripts.prototype = {
this.showScript(script, aOptions);
},
/**
* Gets the text in a source editor's specified line.
*
* @param number aLine [optional]
* The line to get the text from.
* If unspecified, it defaults to the current caret position line.
* @return string
* The specified line text
*/
getLineText: function SS_getLineText(aLine) {
let editor = DebuggerView.editor;
let line = aLine || editor.getCaretPosition().line;
let start = editor.getLineStart(line);
let end = editor.getLineEnd(line);
return editor.getText(start, end);
},
/**
* Log an error message in the error console when a script fails to load.
*
@ -1395,6 +1440,25 @@ Breakpoints.prototype = {
this._skipEditorBreakpointChange = false;
},
/**
* Update the breakpoints in the pane view. This function is invoked when the
* scripts are added (typically after a page navigation).
*/
updatePaneBreakpoints: function BP_updatePaneBreakpoints() {
let url = DebuggerView.Scripts.selected;
if (!url) {
return;
}
this._skipEditorBreakpointChange = true;
for each (let breakpoint in this.store) {
if (DebuggerView.Scripts.contains(breakpoint.location.url)) {
this.displayBreakpoint(breakpoint, true);
}
}
this._skipEditorBreakpointChange = false;
},
/**
* Add a breakpoint.
*
@ -1412,9 +1476,11 @@ Breakpoints.prototype = {
* @param boolean [aNoEditorUpdate=false]
* Tells if you want to skip editor updates. Typically the editor is
* updated to visually indicate that a breakpoint has been added.
* @param boolean [aNoPaneUpdate=false]
* Tells if you want to skip any breakpoint pane updates.
*/
addBreakpoint:
function BP_addBreakpoint(aLocation, aCallback, aNoEditorUpdate) {
function BP_addBreakpoint(aLocation, aCallback, aNoEditorUpdate, aNoPaneUpdate) {
let breakpoint = this.getBreakpoint(aLocation.url, aLocation.line);
if (breakpoint) {
aCallback && aCallback(breakpoint);
@ -1423,7 +1489,7 @@ Breakpoints.prototype = {
this.activeThread.setBreakpoint(aLocation, function(aResponse, aBpClient) {
this.store[aBpClient.actor] = aBpClient;
this.displayBreakpoint(aLocation, aNoEditorUpdate);
this.displayBreakpoint(aBpClient, aNoEditorUpdate, aNoPaneUpdate);
aCallback && aCallback(aBpClient, aResponse.error);
}.bind(this));
},
@ -1431,24 +1497,37 @@ Breakpoints.prototype = {
/**
* Update the editor to display the specified breakpoint in the gutter.
*
* @param object aLocation
* The location where you want the breakpoint. This object must have
* two properties:
* - url - the URL of the script.
* - line - the line number (starting from 1).
* @param object aBreakpoint
* The breakpoint you want to display.
* @param boolean [aNoEditorUpdate=false]
* Tells if you want to skip editor updates. Typically the editor is
* updated to visually indicate that a breakpoint has been added.
* @param boolean [aNoPaneUpdate=false]
* Tells if you want to skip any breakpoint pane updates.
*/
displayBreakpoint: function BP_displayBreakpoint(aLocation, aNoEditorUpdate) {
displayBreakpoint:
function BP_displayBreakpoint(aBreakpoint, aNoEditorUpdate, aNoPaneUpdate) {
if (!aNoEditorUpdate) {
let url = DebuggerView.Scripts.selected;
if (url == aLocation.url) {
if (url == aBreakpoint.location.url) {
this._skipEditorBreakpointChange = true;
this.editor.addBreakpoint(aLocation.line - 1);
this.editor.addBreakpoint(aBreakpoint.location.line - 1);
this._skipEditorBreakpointChange = false;
}
}
if (!aNoPaneUpdate) {
let { url: url, line: line } = aBreakpoint.location;
if (!aBreakpoint.lineText || !aBreakpoint.lineInfo) {
let scripts = DebuggerController.SourceScripts;
aBreakpoint.lineText = scripts.getLineText(line - 1);
aBreakpoint.lineInfo = scripts.getScriptLabel(url) + ":" + line;
}
DebuggerView.Breakpoints.addBreakpoint(
aBreakpoint.actor,
aBreakpoint.lineInfo,
aBreakpoint.lineText, url, line);
}
},
/**
@ -1463,9 +1542,11 @@ Breakpoints.prototype = {
* @param boolean [aNoEditorUpdate=false]
* Tells if you want to skip editor updates. Typically the editor is
* updated to visually indicate that a breakpoint has been removed.
* @param boolean [aNoPaneUpdate=false]
* Tells if you want to skip any breakpoint pane updates.
*/
removeBreakpoint:
function BP_removeBreakpoint(aBreakpoint, aCallback, aNoEditorUpdate) {
function BP_removeBreakpoint(aBreakpoint, aCallback, aNoEditorUpdate, aNoPaneUpdate) {
if (!(aBreakpoint.actor in this.store)) {
aCallback && aCallback(aBreakpoint.location);
return;
@ -1482,6 +1563,9 @@ Breakpoints.prototype = {
this._skipEditorBreakpointChange = false;
}
}
if (!aNoPaneUpdate) {
DebuggerView.Breakpoints.removeBreakpoint(aBreakpoint.actor);
}
aCallback && aCallback(aBreakpoint.location);
}.bind(this));

View File

@ -6,6 +6,7 @@
"use strict";
const PROPERTY_VIEW_FLASH_DURATION = 400; // ms
const BREAKPOINT_LINE_TOOLTIP_MAX_SIZE = 1000;
/**
* Object mediating visual changes and event listeners between the debugger and
@ -22,7 +23,7 @@ let DebuggerView = {
* Initializes UI properties for all the displayed panes.
*/
initializePanes: function DV_initializePanes() {
let stackframes = document.getElementById("stackframes");
let stackframes = document.getElementById("stackframes+breakpoints");
stackframes.setAttribute("width", Prefs.stackframesWidth);
let variables = document.getElementById("variables");
@ -51,7 +52,7 @@ let DebuggerView = {
* Removes the displayed panes and saves any necessary state.
*/
destroyPanes: function DV_destroyPanes() {
let stackframes = document.getElementById("stackframes");
let stackframes = document.getElementById("stackframes+breakpoints");
Prefs.stackframesWidth = stackframes.getAttribute("width");
let variables = document.getElementById("variables");
@ -807,6 +808,7 @@ StackFramesView.prototype = {
window.addEventListener("resize", this._onFramesScroll, false);
this._frames = frames;
this.emptyText();
},
/**
@ -837,6 +839,545 @@ StackFramesView.prototype = {
}
};
/**
* Functions handling the breakpoints view.
*/
function BreakpointsView() {
this._onBreakpointClick = this._onBreakpointClick.bind(this);
this._onBreakpointCheckboxChange = this._onBreakpointCheckboxChange.bind(this);
}
BreakpointsView.prototype = {
/**
* Removes all elements from the breakpoints container, leaving it empty.
*/
empty: function DVB_empty() {
let firstChild;
while (firstChild = this._breakpoints.firstChild) {
this._destroyContextMenu(firstChild);
this._breakpoints.removeChild(firstChild);
}
},
/**
* Removes all elements from the breakpoints container, and adds a child node
* with an empty text note attached.
*/
emptyText: function DVB_emptyText() {
// Make sure the container is empty first.
this.empty();
let item = document.createElement("label");
// The empty node should look grayed out to avoid confusion.
item.className = "list-item empty";
item.setAttribute("value", L10N.getStr("emptyBreakpointsText"));
this._breakpoints.appendChild(item);
},
/**
* Checks whether the breakpoint with the specified script URL and line is
* among the breakpoints known to the debugger and shown in the list, and
* returns the matched result or null if nothing is found.
*
* @param string aUrl
* The original breakpoint script url.
* @param number aLine
* The original breakpoint script line.
* @return object | null
* The queried breakpoint
*/
getBreakpoint: function DVB_getBreakpoint(aUrl, aLine) {
return this._breakpoints.getElementsByAttribute("location", aUrl + ":" + aLine)[0];
},
/**
* Removes a breakpoint only from the breakpoints container.
* This doesn't remove the breakpoint from the DebuggerController!
*
* @param string aId
* A breakpoint identifier specified by the debugger.
*/
removeBreakpoint: function DVB_removeBreakpoint(aId) {
let breakpoint = document.getElementById("breakpoint-" + aId);
// Make sure we have something to remove.
if (!breakpoint) {
return;
}
this._destroyContextMenu(breakpoint);
this._breakpoints.removeChild(breakpoint);
if (!this.count) {
this.emptyText();
}
},
/**
* Adds a breakpoint to the breakpoints container.
* If the breakpoint already exists (was previously added), null is returned.
* If it's already added but disabled, it will be enabled and null is returned.
* Otherwise, the newly created element is returned.
*
* @param string aId
* A breakpoint identifier specified by the debugger.
* @param string aLineInfo
* The script line information to be displayed in the list.
* @param string aLineText
* The script line text to be displayed in the list.
* @param string aUrl
* The original breakpoint script url.
* @param number aLine
* The original breakpoint script line.
* @return object
* The newly created html node representing the added breakpoint.
*/
addBreakpoint: function DVB_addBreakpoint(aId, aLineInfo, aLineText, aUrl, aLine) {
// Make sure we don't duplicate anything.
if (document.getElementById("breakpoint-" + aId)) {
return null;
}
// Remove the empty list text if it was there.
if (!this.count) {
this.empty();
}
// If the breakpoint was already added but disabled, enable it now.
let breakpoint = this.getBreakpoint(aUrl, aLine);
if (breakpoint) {
breakpoint.id = "breakpoint-" + aId;
breakpoint.breakpointActor = aId;
breakpoint.getElementsByTagName("checkbox")[0].setAttribute("checked", "true");
return;
}
breakpoint = document.createElement("box");
let bkpCheckbox = document.createElement("checkbox");
let bkpLineInfo = document.createElement("label");
let bkpLineText = document.createElement("label");
// Create a list item to be added to the stackframes container.
breakpoint.id = "breakpoint-" + aId;
breakpoint.className = "dbg-breakpoint list-item";
breakpoint.setAttribute("location", aUrl + ":" + aLine);
breakpoint.breakpointUrl = aUrl;
breakpoint.breakpointLine = aLine;
breakpoint.breakpointActor = aId;
aLineInfo = aLineInfo.trim();
aLineText = aLineText.trim();
// A checkbox specifies if the breakpoint is enabled or not.
bkpCheckbox.setAttribute("checked", "true");
bkpCheckbox.addEventListener("click", this._onBreakpointCheckboxChange, false);
// This list should display the line info and text for the breakpoint.
bkpLineInfo.className = "dbg-breakpoint-info plain";
bkpLineText.className = "dbg-breakpoint-text plain";
bkpLineInfo.setAttribute("value", aLineInfo);
bkpLineText.setAttribute("value", aLineText);
bkpLineInfo.setAttribute("crop", "end");
bkpLineText.setAttribute("crop", "end");
bkpLineText.setAttribute("tooltiptext", aLineText.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_SIZE));
// Create a context menu for the breakpoint.
let menupopupId = this._createContextMenu(breakpoint);
breakpoint.setAttribute("contextmenu", menupopupId);
let state = document.createElement("vbox");
state.className = "state";
state.appendChild(bkpCheckbox);
let content = document.createElement("vbox");
content.className = "content";
content.setAttribute("flex", "1");
content.appendChild(bkpLineInfo);
content.appendChild(bkpLineText);
breakpoint.appendChild(state);
breakpoint.appendChild(content);
this._breakpoints.appendChild(breakpoint);
// Return the element for later use if necessary.
return breakpoint;
},
/**
* Enables a breakpoint.
*
* @param object aBreakpoint
* An element representing a breakpoint.
* @param function aCallback
* Optional function to invoke once the breakpoint is enabled.
* @param boolean aNoCheckboxUpdate
* Pass true to not update the checkbox checked state.
* This is usually necessary when the checked state will be updated
* automatically (e.g: on a checkbox click).
*/
enableBreakpoint:
function DVB_enableBreakpoint(aTarget, aCallback, aNoCheckboxUpdate) {
let { breakpointUrl: url, breakpointLine: line } = aTarget;
let breakpoint = DebuggerController.Breakpoints.getBreakpoint(url, line)
if (!breakpoint) {
if (!aNoCheckboxUpdate) {
aTarget.getElementsByTagName("checkbox")[0].setAttribute("checked", "true");
}
DebuggerController.Breakpoints.
addBreakpoint({ url: url, line: line }, aCallback);
return true;
}
return false;
},
/**
* Disables a breakpoint.
*
* @param object aTarget
* An element representing a breakpoint.
* @param function aCallback
* Optional function to invoke once the breakpoint is disabled.
* @param boolean aNoCheckboxUpdate
* Pass true to not update the checkbox checked state.
* This is usually necessary when the checked state will be updated
* automatically (e.g: on a checkbox click).
*/
disableBreakpoint:
function DVB_disableBreakpoint(aTarget, aCallback, aNoCheckboxUpdate) {
let { breakpointUrl: url, breakpointLine: line } = aTarget;
let breakpoint = DebuggerController.Breakpoints.getBreakpoint(url, line)
if (breakpoint) {
if (!aNoCheckboxUpdate) {
aTarget.getElementsByTagName("checkbox")[0].removeAttribute("checked");
}
DebuggerController.Breakpoints.
removeBreakpoint(breakpoint, aCallback, false, true);
return true;
}
return false;
},
/**
* Gets the current number of added breakpoints.
*/
get count() {
return this._breakpoints.getElementsByClassName("dbg-breakpoint").length;
},
/**
* Iterates through all the added breakpoints.
*
* @param function aCallback
* Function called for each element.
*/
_iterate: function DVB_iterate(aCallback) {
Array.forEach(Array.slice(this._breakpoints.childNodes), aCallback);
},
/**
* Gets the real breakpoint target when an event is handled.
* @return object
*/
_getBreakpointTarget: function DVB__getBreakpointTarget(aEvent) {
let target = aEvent.target;
while (target) {
if (target.breakpointActor) {
return target;
}
target = target.parentNode;
}
},
/**
* Listener handling the breakpoint click event.
*/
_onBreakpointClick: function DVB__onBreakpointClick(aEvent) {
let target = this._getBreakpointTarget(aEvent);
let { breakpointUrl: url, breakpointLine: line } = target;
DebuggerController.StackFrames.updateEditorToLocation(url, line, 0, 0, 1);
},
/**
* Listener handling the breakpoint checkbox change event.
*/
_onBreakpointCheckboxChange: function DVB__onBreakpointCheckboxChange(aEvent) {
aEvent.stopPropagation();
let target = this._getBreakpointTarget(aEvent);
let { breakpointUrl: url, breakpointLine: line } = target;
if (aEvent.target.getAttribute("checked") === "true") {
this.disableBreakpoint(target, null, true);
} else {
this.enableBreakpoint(target, null, true);
}
},
/**
* Listener handling the "enableSelf" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element.
*/
_onEnableSelf: function DVB__onEnableSelf(aTarget) {
if (!aTarget) {
return;
}
if (this.enableBreakpoint(aTarget)) {
aTarget.enableSelf.menuitem.setAttribute("hidden", "true");
aTarget.disableSelf.menuitem.removeAttribute("hidden");
}
},
/**
* Listener handling the "disableSelf" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element.
*/
_onDisableSelf: function DVB__onDisableSelf(aTarget) {
if (!aTarget) {
return;
}
if (this.disableBreakpoint(aTarget)) {
aTarget.enableSelf.menuitem.removeAttribute("hidden");
aTarget.disableSelf.menuitem.setAttribute("hidden", "true");
}
},
/**
* Listener handling the "deleteSelf" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element.
*/
_onDeleteSelf: function DVB__onDeleteSelf(aTarget) {
let { breakpointUrl: url, breakpointLine: line } = aTarget;
let breakpoint = DebuggerController.Breakpoints.getBreakpoint(url, line)
if (aTarget) {
this.removeBreakpoint(aTarget.breakpointActor);
}
if (breakpoint) {
DebuggerController.Breakpoints.removeBreakpoint(breakpoint);
}
},
/**
* Listener handling the "enableOthers" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element.
*/
_onEnableOthers: function DVB__onEnableOthers(aTarget) {
this._iterate(function(element) {
if (element !== aTarget) {
this._onEnableSelf(element);
}
}.bind(this));
},
/**
* Listener handling the "disableOthers" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element.
*/
_onDisableOthers: function DVB__onDisableOthers(aTarget) {
this._iterate(function(element) {
if (element !== aTarget) {
this._onDisableSelf(element);
}
}.bind(this));
},
/**
* Listener handling the "deleteOthers" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element.
*/
_onDeleteOthers: function DVB__onDeleteOthers(aTarget) {
this._iterate(function(element) {
if (element !== aTarget) {
this._onDeleteSelf(element);
}
}.bind(this));
},
/**
* Listener handling the "disableAll" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element.
*/
_onEnableAll: function DVB__onEnableAll(aTarget) {
this._onEnableOthers(aTarget);
this._onEnableSelf(aTarget);
},
/**
* Listener handling the "disableAll" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element.
*/
_onDisableAll: function DVB__onDisableAll(aTarget) {
this._onDisableOthers(aTarget);
this._onDisableSelf(aTarget);
},
/**
* Listener handling the "deleteAll" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element.
*/
_onDeleteAll: function DVB__onDeleteAll(aTarget) {
this._onDeleteOthers(aTarget);
this._onDeleteSelf(aTarget);
},
/**
* The cached breakpoints container.
*/
_breakpoints: null,
/**
* Creates a breakpoint context menu.
*
* @param object aBreakpoint
* An element representing a breakpoint.
* @return string
* The popup id.
*/
_createContextMenu: function DVB_createContextMenu(aBreakpoint) {
let commandsetId = "breakpointMenuCommands-" + aBreakpoint.id;
let menupopupId = "breakpointContextMenu-" + aBreakpoint.id;
let commandsset = document.createElement("commandsset");
commandsset.setAttribute("id", commandsetId);
let menupopup = document.createElement("menupopup");
menupopup.setAttribute("id", menupopupId);
/**
* Creates a menu item specified by a name with the appropriate attributes
* (label and command handler).
*
* @param string aName
* A global identifier for the menu item.
* @param boolean aHiddenFlag
* True if this menuitem should be hidden.
*/
function createMenuItem(aName, aHiddenFlag) {
let menuitem = document.createElement("menuitem");
let command = document.createElement("command");
let func = this["_on" + aName.charAt(0).toUpperCase() + aName.slice(1)];
let label = L10N.getStr("breakpointMenuItem." + aName);
let prefix = "bp-cMenu-";
let commandId = prefix + aName + "-" + aBreakpoint.id + "-command";
let menuitemId = prefix + aName + "-" + aBreakpoint.id + "-menuitem";
command.setAttribute("id", commandId);
command.setAttribute("label", label);
command.addEventListener("command", func.bind(this, aBreakpoint), true);
menuitem.setAttribute("id", menuitemId);
menuitem.setAttribute("command", commandId);
menuitem.setAttribute("hidden", aHiddenFlag);
commandsset.appendChild(command);
menupopup.appendChild(menuitem);
aBreakpoint[aName] = {
menuitem: menuitem,
command: command
};
}
/**
* Creates a simple menu separator element and appends it to the current
* menupopup hierarchy.
*/
function createMenuSeparator() {
let menuseparator = document.createElement("menuseparator");
menupopup.appendChild(menuseparator);
}
createMenuItem.call(this, "enableSelf", true);
createMenuItem.call(this, "disableSelf");
createMenuItem.call(this, "deleteSelf");
createMenuSeparator();
createMenuItem.call(this, "enableOthers");
createMenuItem.call(this, "disableOthers");
createMenuItem.call(this, "deleteOthers");
createMenuSeparator();
createMenuItem.call(this, "enableAll");
createMenuItem.call(this, "disableAll");
createMenuSeparator();
createMenuItem.call(this, "deleteAll");
let popupset = document.getElementById("debugger-popups");
popupset.appendChild(menupopup);
document.documentElement.appendChild(commandsset);
return menupopupId;
},
/**
* Destroys a breakpoint context menu.
*
* @param object aBreakpoint
* An element representing a breakpoint.
*/
_destroyContextMenu: function DVB__destroyContextMenu(aBreakpoint) {
let commandsetId = "breakpointMenuCommands-" + aBreakpoint.id;
let menupopupId = "breakpointContextMenu-" + aBreakpoint.id;
let commandset = document.getElementById(commandsetId);
let menupopup = document.getElementById(menupopupId);
if (commandset) {
commandset.parentNode.removeChild(commandset);
}
if (menupopup) {
menupopup.parentNode.removeChild(menupopup);
}
},
/**
* Initialization function, called when the debugger is initialized.
*/
initialize: function DVB_initialize() {
let breakpoints = document.getElementById("breakpoints");
breakpoints.addEventListener("click", this._onBreakpointClick, false);
this._breakpoints = breakpoints;
this.emptyText();
},
/**
* Destruction function, called when the debugger is shut down.
*/
destroy: function DVB_destroy() {
let breakpoints = this._breakpoints;
breakpoints.removeEventListener("click", this._onBreakpointClick, false);
this._breakpoints = null;
}
};
/**
* Functions handling the properties view.
*/
@ -847,6 +1388,7 @@ function PropertiesView() {
}
PropertiesView.prototype = {
/**
* A monotonically-increasing counter, that guarantees the uniqueness of scope
* IDs.
@ -1923,9 +2465,10 @@ PropertiesView.prototype = {
* Initialization function, called when the debugger is initialized.
*/
initialize: function DVP_initialize() {
this.createHierarchyStore();
this._vars = document.getElementById("variables");
this.emptyText();
this.createHierarchyStore();
},
/**
@ -1943,6 +2486,7 @@ PropertiesView.prototype = {
*/
DebuggerView.Scripts = new ScriptsView();
DebuggerView.StackFrames = new StackFramesView();
DebuggerView.Breakpoints = new BreakpointsView();
DebuggerView.Properties = new PropertiesView();
/**

View File

@ -12,6 +12,20 @@
overflow: auto;
}
/**
* Breakpoints view
*/
#breakpoints {
overflow-x: hidden;
overflow-y: auto;
}
.dbg-breakpoint > .state,
.dbg-breakpoint > .content {
overflow: hidden;
}
/**
* Properties elements
*/

View File

@ -80,10 +80,14 @@
#endif
</toolbar>
<hbox id="dbg-content" flex="1">
<vbox id="stackframes"/>
<splitter id="stack-script-splitter" class="devtools-side-splitter"/>
<vbox id="stackframes+breakpoints">
<vbox id="stackframes" flex="1"/>
<splitter class="devtools-horizontal-splitter"/>
<vbox id="breakpoints"/>
</vbox>
<splitter class="devtools-side-splitter"/>
<vbox id="editor" flex="1"/>
<splitter id="script-properties-splitter" class="devtools-side-splitter"/>
<splitter class="devtools-side-splitter"/>
<vbox id="variables"/>
</hbox>
</vbox>

View File

@ -51,6 +51,7 @@ MOCHITEST_BROWSER_TESTS = \
$(warning browser_dbg_select-line.js temporarily disabled due to oranges, see bug 726609) \
browser_dbg_clean-exit.js \
browser_dbg_bug723069_editor-breakpoints.js \
browser_dbg_bug723071_editor-breakpoints-pane.js \
browser_dbg_bug731394_editor-contextmenu.js \
browser_dbg_displayName.js \
browser_dbg_iframes.js \

View File

@ -21,6 +21,7 @@ function test()
let tempScope = {};
Cu.import("resource:///modules/source-editor.jsm", tempScope);
let SourceEditor = tempScope.SourceEditor;
let scriptShown = false;
let framesAdded = false;
let resumed = false;

View File

@ -0,0 +1,281 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Bug 723071: test adding a pane to display the list of breakpoints across
* all scripts in the debuggee.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
let gPane = null;
let gTab = null;
let gDebuggee = null;
let gDebugger = null;
let gScripts = null;
let gBreakpoints = null;
let gBreakpointsElement = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.contentWindow;
resumed = true;
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
framesAdded = true;
executeSoon(startTest);
});
executeSoon(function() {
gDebuggee.firstCall();
});
});
function onScriptShown(aEvent)
{
scriptShown = aEvent.detail.url.indexOf("-02.js") != -1;
executeSoon(startTest);
}
window.addEventListener("Debugger:ScriptShown", onScriptShown);
function startTest()
{
if (scriptShown && framesAdded && resumed && !testStarted) {
window.removeEventListener("Debugger:ScriptShown", onScriptShown);
testStarted = true;
Services.tm.currentThread.dispatch({ run: performTest }, 0);
}
}
let breakpointsAdded = 0;
let breakpointsDisabled = 0;
let breakpointsRemoved = 0;
function performTest()
{
gScripts = gDebugger.DebuggerView.Scripts;
is(gDebugger.DebuggerController.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
is(gScripts._scripts.itemCount, 2, "Found the expected number of scripts.");
let editor = gDebugger.editor;
isnot(editor.getText().indexOf("debugger"), -1,
"The correct script was loaded initially.");
isnot(gScripts.selected, gScripts.scriptLocations[0],
"the correct script is selected");
gBreakpoints = gPane.breakpoints;
is(Object.keys(gBreakpoints), 0, "no breakpoints");
ok(!gPane.getBreakpoint("chocolate", 3), "getBreakpoint('chocolate', 3) returns falsey");
is(editor.getBreakpoints().length, 0, "no breakpoints in the editor");
gBreakpointsElement = gDebugger.DebuggerView.Breakpoints._breakpoints;
is(gBreakpointsElement.childNodes.length, 1,
"The breakpoints pane should be empty, but showing a " +
"'no breakpoints' information message.");
is(gBreakpointsElement.childNodes.length,
gBreakpointsElement.querySelectorAll(".list-item.empty").length,
"Found junk in the breakpoints container.");
addBreakpoints(function() {
is(breakpointsAdded, 3,
"Should have added 3 breakpoints so far.");
is(breakpointsDisabled, 0,
"Shouldn't have disabled anything so far.");
is(breakpointsRemoved, 0,
"Shouldn't have removed anything so far.");
is(gBreakpointsElement.childNodes.length,
gBreakpointsElement.querySelectorAll(".dbg-breakpoint").length,
"Found junk in the breakpoints container.");
disableBreakpoints(function() {
is(breakpointsAdded, 3,
"Should still have 3 breakpoints added so far.");
is(breakpointsDisabled, 3,
"Should have 3 disabled breakpoints.");
is(breakpointsRemoved, 0,
"Shouldn't have removed anything so far.");
is(gBreakpointsElement.childNodes.length, breakpointsAdded,
"Should have the same number of breakpoints in the pane.");
is(gBreakpointsElement.childNodes.length, breakpointsDisabled,
"Should have the same number of disabled breakpoints.");
addBreakpoints(function() {
is(breakpointsAdded, 3,
"Should still have only 3 breakpoints added so far.");
is(breakpointsDisabled, 3,
"Should still have 3 disabled breakpoints.");
is(breakpointsRemoved, 0,
"Shouldn't have removed anything so far.");
is(gBreakpointsElement.childNodes.length, breakpointsAdded,
"Since half of the breakpoints already existed, but disabled, " +
"only half of the added breakpoints are actually in the pane.");
is(gBreakpointsElement.childNodes.length,
gBreakpointsElement.querySelectorAll(".dbg-breakpoint").length,
"Found junk in the breakpoints container.");
removeBreakpoints(function() {
is(breakpointsRemoved, 3,
"Should have 3 removed breakpoints.");
is(gBreakpointsElement.childNodes.length, 1,
"The breakpoints pane should be empty, but showing a " +
"'no breakpoints' information message.");
is(gBreakpointsElement.childNodes.length,
gBreakpointsElement.querySelectorAll(".list-item.empty").length,
"Found junk in the breakpoints container.");
finish();
});
});
});
}, true);
function addBreakpoints(callback, increment)
{
let line;
executeSoon(function()
{
line = 4;
gPane.addBreakpoint({url: gScripts.selected, line: line},
function(cl, err) {
onBreakpointAdd.call({ increment: increment, line: line }, cl, err);
line = 5;
gPane.addBreakpoint({url: gScripts.selected, line: line},
function(cl, err) {
onBreakpointAdd.call({ increment: increment, line: line }, cl, err);
line = 6;
gPane.addBreakpoint({url: gScripts.selected, line: line},
function(cl, err) {
onBreakpointAdd.call({ increment: increment, line: line }, cl, err);
executeSoon(function() {
callback();
});
});
});
});
});
}
function disableBreakpoints(callback)
{
let nodes = Array.slice(gBreakpointsElement.childNodes);
info("Nodes to disable: " + breakpointsAdded);
is(nodes.length, breakpointsAdded,
"The number of nodes to disable is incorrect.");
Array.forEach(nodes, function(bkp) {
info("Disabling breakpoint: " + bkp.id);
gDebugger.DebuggerView.Breakpoints.disableBreakpoint(bkp, function() {
if (++breakpointsDisabled !== breakpointsAdded) {
return;
}
executeSoon(function() {
callback();
});
});
});
}
function removeBreakpoints(callback)
{
let nodes = Array.slice(gBreakpointsElement.childNodes);
info("Nodes to remove: " + breakpointsAdded);
is(nodes.length, breakpointsAdded,
"The number of nodes to remove is incorrect.");
Array.forEach(nodes, function(bkp) {
info("Removing breakpoint: " + bkp.id);
let [url, line, actor] =
[bkp.breakpointUrl, bkp.breakpointLine, bkp.breakpointActor];
gDebugger.DebuggerView.Breakpoints.removeBreakpoint(actor);
gPane.removeBreakpoint(gPane.getBreakpoint(url, line), function() {
if (++breakpointsRemoved !== breakpointsAdded) {
return;
}
executeSoon(function() {
callback();
});
});
});
}
function onBreakpointAdd(aBreakpointClient, aResponseError)
{
if (this.increment) {
breakpointsAdded++;
}
is(gBreakpointsElement.childNodes.length, breakpointsAdded, this.increment
? "Should have added a breakpoint in the pane."
: "Should have the same number of breakpoints in the pane.");
let id = "breakpoint-" + aBreakpointClient.actor;
let bkp = gDebugger.document.getElementById(id);
let info = bkp.getElementsByClassName("dbg-breakpoint-info")[0];
let text = bkp.getElementsByClassName("dbg-breakpoint-text")[0];
let check = bkp.querySelector("checkbox");
is(bkp.id, id,
"Breakpoint element " + id + " found succesfully.");
is(info.getAttribute("value"), getExpectedBreakpointInfo(this.line),
"The expected information wasn't found in the breakpoint element.");
is(text.getAttribute("value"), getExpectedLineText(this.line).trim(),
"The expected line text wasn't found in the breakpoint element.");
is(check.getAttribute("checked"), "true",
"The breakpoint enable checkbox is checked as expected.");
}
function getExpectedBreakpointInfo(line) {
let url = gDebugger.DebuggerView.Scripts.selected;
let label = gDebugger.DebuggerController.SourceScripts.getScriptLabel(url);
return label + ":" + line;
}
function getExpectedLineText(line) {
return gDebugger.DebuggerController.SourceScripts.getLineText(line - 1);
}
}
registerCleanupFunction(function() {
is(Object.keys(gBreakpoints).length, 0, "no breakpoint in the debugger");
ok(!gPane.getBreakpoint(gScripts.scriptLocations[0], 5),
"getBreakpoint(scriptLocations[0], 5) returns no breakpoint");
is(breakpointsAdded, 3, "correct number of breakpoints have been added");
is(breakpointsDisabled, 3, "correct number of breakpoints have been disabled");
is(breakpointsRemoved, 3, "correct number of breakpoints have been removed");
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
gScripts = null;
gBreakpoints = null;
gBreakpointsElement = null;
});
}

View File

@ -33,7 +33,7 @@ function test() {
ok(content.Prefs.variablesWidth,
"The debugger preferences should have a saved variablesWidth value.");
stackframes = content.document.getElementById("stackframes");
stackframes = content.document.getElementById("stackframes+breakpoints");
variables = content.document.getElementById("variables");
is(content.Prefs.stackframesWidth, stackframes.getAttribute("width"),

View File

@ -68,7 +68,7 @@ function testScriptLabelShortening() {
urls.forEach(function(url) {
executeSoon(function() {
let loc = url.href + url.leaf;
vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true);
vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc }, true);
});
});

View File

@ -67,7 +67,7 @@ function addScriptsAndCheckOrder(method, callback) {
case 1:
urls.forEach(function(url) {
let loc = url.href + url.leaf;
vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc });
vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc });
});
vs.commitScripts();
break;
@ -75,7 +75,7 @@ function addScriptsAndCheckOrder(method, callback) {
case 2:
urls.forEach(function(url) {
let loc = url.href + url.leaf;
vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true);
vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc }, true);
});
break;
@ -84,14 +84,14 @@ function addScriptsAndCheckOrder(method, callback) {
for (; i < urls.length / 2; i++) {
let url = urls[i];
let loc = url.href + url.leaf;
vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc });
vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc });
}
vs.commitScripts();
for (; i < urls.length; i++) {
let url = urls[i];
let loc = url.href + url.leaf;
vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true);
vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc }, true);
}
break;
}

View File

@ -55,6 +55,22 @@ resumeTooltip=Click to resume
# frames list when there are no frames to display.
emptyStackText=No stacks to display.
# LOCALIZATION NOTE (emptyBreakpointsText): The text that is displayed in the
# breakpoints list when there are no breakpoints to display.
emptyBreakpointsText=No breakpoints to display.
# LOCALIZATION NOTE (breakpointMenuItem): The text for all the elements that
# are displayed in the breakpoints menu item popup.
breakpointMenuItem.enableSelf=Enable breakpoint
breakpointMenuItem.disableSelf=Disable breakpoint
breakpointMenuItem.deleteSelf=Remove breakpoint
breakpointMenuItem.enableOthers=Enable others
breakpointMenuItem.disableOthers=Disable others
breakpointMenuItem.deleteOthers=Remove others
breakpointMenuItem.enableAll=Enable all breakpoints
breakpointMenuItem.disableAll=Disable all breakpoints
breakpointMenuItem.deleteAll=Remove all breakpoints
# LOCALIZATION NOTE (loadingText): The text that is displayed in the script
# editor when the laoding process has started but there is no file to display
# yet.

View File

@ -28,6 +28,10 @@
padding: 2px;
}
.list-item:not(.selected):not(.empty):hover {
background: #cddae5;
}
.list-item.selected {
background: Highlight;
color: HighlightText;
@ -35,6 +39,7 @@
.list-item.empty {
color: GrayText;
padding: 4px;
}
/**
@ -55,6 +60,22 @@
font-weight: 600;
}
/**
* Breakpoints view
*/
#breakpoints {
background-color: white;
}
.dbg-breakpoint-info {
font-weight: 600;
}
.dbg-breakpoint-text {
font: 8pt monospace;
}
/**
* Properties view
*/

View File

@ -30,6 +30,10 @@
padding: 2px;
}
.list-item:not(.selected):not(.empty):hover {
background: #cddae5;
}
.list-item.selected {
background: Highlight;
color: HighlightText;
@ -37,6 +41,7 @@
.list-item.empty {
color: GrayText;
padding: 4px;
}
/**
@ -57,6 +62,22 @@
font-weight: 600;
}
/**
* Breakpoints view
*/
#breakpoints {
background-color: white;
}
.dbg-breakpoint-info {
font-weight: 600;
}
.dbg-breakpoint-text {
font: 8pt monospace;
}
/**
* Properties view
*/

View File

@ -36,6 +36,10 @@
padding: 2px;
}
.list-item:not(.selected):not(.empty):hover {
background: #cddae5;
}
.list-item.selected {
background: Highlight;
color: HighlightText;
@ -43,6 +47,7 @@
.list-item.empty {
color: GrayText;
padding: 4px;
}
/**
@ -63,6 +68,22 @@
font-weight: 600;
}
/**
* Breakpoints view
*/
#breakpoints {
background-color: white;
}
.dbg-breakpoint-info {
font-weight: 600;
}
.dbg-breakpoint-text {
font: 8pt monospace;
}
/**
* Properties view
*/