Bug 740825 - Implement conditional breakpoints, r=past

This commit is contained in:
Victor Porof 2012-11-16 08:42:52 +02:00
parent a81099530e
commit ba651862ac
14 changed files with 1421 additions and 76 deletions

View File

@ -374,6 +374,8 @@ StackFrames.prototype = {
get activeThread() DebuggerController.activeThread,
autoScopeExpand: false,
currentFrame: null,
currentBreakpointLocation: null,
currentEvaluation: null,
currentException: null,
/**
@ -419,9 +421,19 @@ StackFrames.prototype = {
* The response packet.
*/
_onPaused: function SF__onPaused(aEvent, aPacket) {
// In case the pause was caused by an exception, store the exception value.
if (aPacket.why.type == "exception") {
this.currentException = aPacket.why.exception;
switch (aPacket.why.type) {
// If paused by a breakpoint, store the breakpoint location.
case "breakpoint":
this.currentBreakpointLocation = aPacket.frame.where;
break;
// If paused by a client evaluation, store the evaluated value.
case "clientEvaluated":
this.currentEvaluation = aPacket.why.frameFinished.return;
break;
// If paused by an exception, store the exception value.
case "exception":
this.currentException = aPacket.why.exception;
break;
}
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
@ -445,6 +457,35 @@ StackFrames.prototype = {
}
DebuggerView.StackFrames.empty();
// Conditional breakpoints are { breakpoint, expression } tuples. The
// boolean evaluation of the expression decides if the active thread
// automatically resumes execution or not.
if (this.currentBreakpointLocation) {
let { url, line } = this.currentBreakpointLocation;
let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
let conditionalExpression = breakpointClient.conditionalExpression;
if (conditionalExpression) {
// Evaluating the current breakpoint's conditional expression will
// cause the stack frames to be cleared and active thread to pause,
// sending a 'clientEvaluated' packed and adding the frames again.
this.evaluate("(" + conditionalExpression + ")", 0);
this._isConditionalBreakpointEvaluation = true;
return;
}
}
// Got our evaluation of the current breakpoint's conditional expression.
if (this._isConditionalBreakpointEvaluation) {
this._isConditionalBreakpointEvaluation = false;
// If the breakpoint's conditional expression evaluation is falsy,
// automatically resume execution.
if (VariablesView.isFalsy({ value: this.currentEvaluation })) {
this.activeThread.resume();
return;
}
}
for (let frame of this.activeThread.cachedFrames) {
this._addFrame(frame);
}
@ -461,6 +502,8 @@ StackFrames.prototype = {
*/
_onFramesCleared: function SF__onFramesCleared() {
this.currentFrame = null;
this.currentBreakpointLocation = null;
this.currentEvaluation = null;
this.currentException = null;
// After each frame step (in, over, out), framescleared is fired, which
// forces the UI to be emptied and rebuilt on framesadded. Most of the times
@ -654,7 +697,6 @@ StackFrames.prototype = {
if (!aArguments) {
return;
}
for (let argument of aArguments) {
let name = Object.getOwnPropertyNames(argument)[0];
let argRef = aScope.addVar(name, argument[name]);
@ -675,7 +717,6 @@ StackFrames.prototype = {
if (!aVariables) {
return;
}
let variableNames = Object.keys(aVariables);
// Sort all of the variables before adding them if preferred.
@ -778,10 +819,7 @@ StackFrames.prototype = {
let startText = StackFrameUtils.getFrameTitle(aFrame);
let endText = SourceUtils.getSourceLabel(url) + ":" + line;
DebuggerView.StackFrames.addFrame(startText, endText, depth, {
attachment: aFrame
});
DebuggerView.StackFrames.addFrame(startText, endText, depth);
},
/**
@ -798,9 +836,11 @@ StackFrames.prototype = {
*
* @param string aExpression
* The expression to evaluate.
* @param number aFrame [optional]
* The frame depth used for evaluation.
*/
evaluate: function SF_evaluate(aExpression) {
let frame = this.activeThread.cachedFrames[this.currentFrame];
evaluate: function SF_evaluate(aExpression, aFrame = this.currentFrame) {
let frame = this.activeThread.cachedFrames[aFrame];
this.activeThread.eval(frame.actor, aExpression);
}
};
@ -1107,7 +1147,10 @@ Breakpoints.prototype = {
updateEditorBreakpoints: function BP_updateEditorBreakpoints() {
for each (let breakpointClient in this.store) {
if (DebuggerView.Sources.selectedValue == breakpointClient.location.url) {
this._showBreakpoint(breakpointClient, { noPaneUpdate: true });
this._showBreakpoint(breakpointClient, {
noPaneUpdate: true,
noPaneHighlight: true
});
}
}
},
@ -1120,7 +1163,10 @@ Breakpoints.prototype = {
updatePaneBreakpoints: function BP_updatePaneBreakpoints() {
for each (let breakpointClient in this.store) {
if (DebuggerView.Sources.containsValue(breakpointClient.location.url)) {
this._showBreakpoint(breakpointClient, { noEditorUpdate: true });
this._showBreakpoint(breakpointClient, {
noEditorUpdate: true,
noPaneHighlight: true
});
}
}
},
@ -1140,8 +1186,11 @@ Breakpoints.prototype = {
* - aResponseError: if there was any error
* @param object aFlags [optional]
* An object containing some of the following boolean properties:
* - conditionalExpression: tells this breakpoint's conditional expression
* - openPopup: tells if the expression popup should be shown
* - noEditorUpdate: tells if you want to skip editor updates
* - noPaneUpdate: tells if you want to skip breakpoint pane updates
* - noPaneHighlight: tells if you don't want to highlight the breakpoint
*/
addBreakpoint:
function BP_addBreakpoint(aLocation, aCallback, aFlags = {}) {
@ -1182,6 +1231,9 @@ Breakpoints.prototype = {
// Remember the breakpoint client in the store.
this.store[aBreakpointClient.actor] = aBreakpointClient;
// Attach any specified conditional expression to the breakpoint client.
aBreakpointClient.conditionalExpression = aFlags.conditionalExpression;
// Preserve some information about the breakpoint's source url and line
// to display in the breakpoints pane.
aBreakpointClient.lineText = DebuggerView.getEditorLine(line - 1);
@ -1205,9 +1257,7 @@ Breakpoints.prototype = {
* callback is invoked with one argument
* - aBreakpointClient: the breakpoint location (url and line)
* @param object aFlags [optional]
* An object containing some of the following boolean properties:
* - noEditorUpdate: tells if you want to skip editor updates
* - noPaneUpdate: tells if you want to skip breakpoint pane updates
* @see DebuggerController.Breakpoints.addBreakpoint
*/
removeBreakpoint:
function BP_removeBreakpoint(aBreakpointClient, aCallback, aFlags = {}) {
@ -1237,14 +1287,13 @@ Breakpoints.prototype = {
* @param object aBreakpointClient
* The BreakpointActor client object to show.
* @param object aFlags [optional]
* An object containing some of the following boolean properties:
* - noEditorUpdate: tells if you want to skip editor updates
* - noPaneUpdate: tells if you want to skip breakpoint pane updates
* @see DebuggerController.Breakpoints.addBreakpoint
*/
_showBreakpoint: function BP__showBreakpoint(aBreakpointClient, aFlags = {}) {
let currentSourceUrl = DebuggerView.Sources.selectedValue;
let { url, line } = aBreakpointClient.location;
// Update the editor if required.
if (!aFlags.noEditorUpdate) {
if (url == currentSourceUrl) {
this._skipEditorBreakpointCallbacks = true;
@ -1252,10 +1301,18 @@ Breakpoints.prototype = {
this._skipEditorBreakpointCallbacks = false;
}
}
// Update the breakpoints pane if required.
if (!aFlags.noPaneUpdate) {
let { lineText, lineInfo } = aBreakpointClient;
let actor = aBreakpointClient.actor;
DebuggerView.Breakpoints.addBreakpoint(lineInfo, lineText, url, line, actor);
let { lineText, lineInfo, actor } = aBreakpointClient;
let conditionalFlag = aBreakpointClient.conditionalExpression !== undefined;
let openPopupFlag = aFlags.openPopup;
DebuggerView.Breakpoints.addBreakpoint(
url, line, actor, lineInfo, lineText, conditionalFlag, openPopupFlag);
}
// Highlight the breakpoint in the pane if required.
if (!aFlags.noPaneHighlight) {
DebuggerView.Breakpoints.highlightBreakpoint(url, line);
}
},
@ -1265,14 +1322,13 @@ Breakpoints.prototype = {
* @param object aBreakpointClient
* The BreakpointActor client object to hide.
* @param object aFlags [optional]
* An object containing some of the following boolean properties:
* - noEditorUpdate: tells if you want to skip editor updates
* - noPaneUpdate: tells if you want to skip breakpoint pane updates
* @see DebuggerController.Breakpoints.addBreakpoint
*/
_hideBreakpoint: function BP__hideBreakpoint(aBreakpointClient, aFlags = {}) {
let currentSourceUrl = DebuggerView.Sources.selectedValue;
let { url, line } = aBreakpointClient.location;
// Update the editor if required.
if (!aFlags.noEditorUpdate) {
if (url == currentSourceUrl) {
this._skipEditorBreakpointCallbacks = true;
@ -1280,6 +1336,7 @@ Breakpoints.prototype = {
this._skipEditorBreakpointCallbacks = false;
}
}
// Update the breakpoints pane if required.
if (!aFlags.noPaneUpdate) {
DebuggerView.Breakpoints.removeBreakpoint(url, line);
}

View File

@ -50,12 +50,9 @@ create({ constructor: StackFramesView, proto: MenuContainer.prototype }, {
* Details to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
* @param object aOptions [optional]
* Additional options or flags supported by this operation:
* - attachment: any kind of primitive/object to attach
*/
addFrame:
function DVSF_addFrame(aFrameName, aFrameDetails, aDepth, aOptions = {}) {
function DVSF_addFrame(aFrameName, aFrameDetails, aDepth) {
// Stackframes are UI elements which benefit from visible panes.
DebuggerView.showPanesSoon();
@ -64,7 +61,9 @@ create({ constructor: StackFramesView, proto: MenuContainer.prototype }, {
forced: true,
unsorted: true,
relaxed: true,
attachment: aOptions.attachment
attachment: {
depth: aDepth
}
});
// Check if stackframe was already appended.
@ -154,8 +153,16 @@ function BreakpointsView() {
MenuContainer.call(this);
this._createItemView = this._createItemView.bind(this);
this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
this._onClick = this._onClick.bind(this);
this._onEditorLoad = this._onEditorLoad.bind(this);
this._onEditorUnload = this._onEditorUnload.bind(this);
this._onEditorSelection = this._onEditorSelection.bind(this);
this._onEditorContextMenu = this._onEditorContextMenu.bind(this);
this._onBreakpointClick = this._onBreakpointClick.bind(this);
this._onCheckboxClick = this._onCheckboxClick.bind(this);
this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
this._onConditionalPopupShown = this._onConditionalPopupShown.bind(this);
this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
}
create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
@ -165,12 +172,22 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
initialize: function DVB_initialize() {
dumpn("Initializing the BreakpointsView");
this._container = new StackList(document.getElementById("breakpoints"));
this._commandset = document.getElementById("debuggerCommands");
this._popupset = document.getElementById("debuggerPopupset");
this._cbPanel = document.getElementById("conditional-breakpoint-panel");
this._cbTextbox = document.getElementById("conditional-breakpoint-textbox");
this._container.emptyText = L10N.getStr("emptyBreakpointsText");
this._container.itemFactory = this._createItemView;
this._container.uniquenessQualifier = 2;
this._container.addEventListener("click", this._onClick, false);
window.addEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
this._container.addEventListener("click", this._onBreakpointClick, false);
this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false)
this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false)
this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false)
this._cbTextbox.addEventListener("keypress", this._onConditionalTextboxKeyPress, false);
this._cache = new Map();
},
@ -180,43 +197,55 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
*/
destroy: function DVB_destroy() {
dumpn("Destroying the BreakpointsView");
this._container.removeEventListener("click", this._onClick, false);
window.removeEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
window.removeEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
this._container.removeEventListener("click", this._onBreakpointClick, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.removeEventListener("popupshown", this._onConditionalPopupShown, false);
this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false)
this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
},
/**
* Adds a breakpoint in this breakpoints container.
*
* @param string aLineInfo
* Line information (parent source etc.) to be displayed in the list.
* @param string aLineText
* Line text to be displayed in the list.
* @param string aSourceLocation
* The breakpoint source location specified by the debugger controller.
* @param number aLineNumber
* The breakpoint line number specified by the debugger controller.
* @parm string aId
* A breakpoint identifier specified by the debugger controller.
* @param string aActor
* A breakpoint identifier specified by the debugger controller.
* @param string aLineInfo
* Line information (parent source etc.) to be displayed in the list.
* @param string aLineText
* Line text to be displayed in the list.
* @param boolean aConditionalFlag [optional]
* A flag specifying if this is a conditional breakpoint.
* @param boolean aOpenPopupFlag [optional]
* A flag specifying if the expression popup should be shown.
*/
addBreakpoint:
function DVB_addBreakpoint(aLineInfo, aLineText, aSourceLocation, aLineNumber, aId) {
addBreakpoint: function DVB_addBreakpoint(aSourceLocation, aLineNumber,
aActor, aLineInfo, aLineText,
aConditionalFlag, aOpenPopupFlag) {
// Append a breakpoint item to this container.
let breakpointItem = this.push(aLineInfo.trim(), aLineText.trim(), {
forced: true,
attachment: {
enabled: true,
sourceLocation: aSourceLocation,
lineNumber: aLineNumber
lineNumber: aLineNumber,
isConditional: aConditionalFlag
}
});
// Check if breakpoint was already appended.
if (!breakpointItem) {
this.enableBreakpoint(aSourceLocation, aLineNumber, { id: aId });
this.enableBreakpoint(aSourceLocation, aLineNumber, { id: aActor });
return;
}
let element = breakpointItem.target;
element.id = "breakpoint-" + aId;
element.id = "breakpoint-" + aActor;
element.className = "dbg-breakpoint list-item";
element.infoNode.className = "dbg-breakpoint-info plain";
element.textNode.className = "dbg-breakpoint-text plain";
@ -224,6 +253,12 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
breakpointItem.finalize = this._onBreakpointRemoved;
this._cache.set(this._key(aSourceLocation, aLineNumber), breakpointItem);
// If this is a conditional breakpoint, display the panes and a panel
// to input the corresponding conditional expression.
if (aConditionalFlag && aOpenPopupFlag) {
this.highlightBreakpoint(aSourceLocation, aLineNumber, { openPopup: true });
}
},
/**
@ -267,6 +302,7 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
if (aOptions.id) {
breakpointItem.target.id = "breakpoint-" + aOptions.id;
}
// Update the checkbox state if necessary.
if (!aOptions.silent) {
breakpointItem.target.checkbox.setAttribute("checked", "true");
@ -330,13 +366,47 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
* The breakpoint source location.
* @param number aLineNumber
* The breakpoint line number.
* @param object aFlags [optional]
* An object containing some of the following boolean properties:
* - updateEditor: true if editor updates should be allowed
* - openPopup: true if the expression popup should be shown
*/
highlightBreakpoint: function DVB_highlightBreakpoint(aSourceLocation, aLineNumber) {
highlightBreakpoint:
function DVB_highlightBreakpoint(aSourceLocation, aLineNumber, aFlags = {}) {
let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
if (breakpointItem) {
// Update the editor source location and line number if necessary.
if (aFlags.updateEditor) {
DebuggerView.updateEditor(aSourceLocation, aLineNumber, { noDebug: true });
}
// If the breakpoint requires a new conditional expression, display
// the panes and the panel to input the corresponding expression.
if (aFlags.openPopup && breakpointItem.attachment.isConditional) {
let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
// The conditional expression popup can only be shown with visible panes.
DebuggerView.showPanesSoon(function() {
// Verify if the breakpoint wasn't removed before the panes were shown.
if (this.getBreakpoint(aSourceLocation, aLineNumber)) {
this._cbTextbox.value = breakpointClient.conditionalExpression || "";
this._cbPanel.openPopup(breakpointItem.target,
BREAKPOINT_CONDITIONAL_POPUP_POSITION,
BREAKPOINT_CONDITIONAL_POPUP_OFFSET);
}
}.bind(this));
} else {
this._cbPanel.hidePopup();
}
// Breakpoint is now highlighted.
this._container.selectedItem = breakpointItem.target;
} else {
}
// Can't find a breakpoint at the requested source location and line number.
else {
this._container.selectedIndex = -1;
this._cbPanel.hidePopup();
}
},
@ -363,6 +433,19 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
return this._cache.get(this._key(aSourceLocation, aLineNumber));
},
/**
* Gets the currently selected breakpoint client.
* @return object
*/
get selectedClient() {
let selectedItem = this.selectedItem;
if (selectedItem) {
let { sourceLocation: url, lineNumber: line } = selectedItem.attachment;
return DebuggerController.Breakpoints.getBreakpoint(url, line);
}
return null;
},
/**
* Customization function for creating an item's UI.
*
@ -392,6 +475,7 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
let state = document.createElement("vbox");
state.className = "state";
state.setAttribute("pack", "center");
state.appendChild(checkbox);
let content = document.createElement("vbox");
@ -435,12 +519,14 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
createMenuItem.call(this, "disableOthers");
createMenuItem.call(this, "deleteOthers");
createMenuSeparator();
createMenuItem.call(this, "setConditional");
createMenuSeparator();
createMenuItem.call(this, "enableSelf", true);
createMenuItem.call(this, "disableSelf");
createMenuItem.call(this, "deleteSelf");
this._popupset.appendChild(menupopup);
document.documentElement.appendChild(commandset);
this._commandset.appendChild(commandset);
aElementNode.commandset = commandset;
aElementNode.menupopup = menupopup;
@ -510,19 +596,151 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
this._destroyContextMenu(aItem.target);
},
/**
* The load listener for the source editor.
*/
_onEditorLoad: function DVB__onEditorLoad({ detail: editor }) {
editor.addEventListener("Selection", this._onEditorSelection, false);
editor.addEventListener("ContextMenu", this._onEditorContextMenu, false);
},
/**
* The unload listener for the source editor.
*/
_onEditorUnload: function DVB__onEditorUnload({ detail: editor }) {
editor.removeEventListener("Selection", this._onEditorSelection, false);
editor.removeEventListener("ContextMenu", this._onEditorContextMenu, false);
},
/**
* The selection listener for the source editor.
*/
_onEditorSelection: function DVB__onEditorSelection(e) {
let { start, end } = e.newValue;
let sourceLocation = DebuggerView.Sources.selectedValue;
let lineStart = DebuggerView.editor.getLineAtOffset(start) + 1;
let lineEnd = DebuggerView.editor.getLineAtOffset(end) + 1;
if (this.getBreakpoint(sourceLocation, lineStart) && lineStart == lineEnd) {
this.highlightBreakpoint(sourceLocation, lineStart);
} else {
this.unhighlightBreakpoint();
}
},
/**
* The context menu listener for the source editor.
*/
_onEditorContextMenu: function DVB__onEditorContextMenu({ x, y }) {
let offset = DebuggerView.editor.getOffsetAtLocation(x, y);
let line = DebuggerView.editor.getLineAtOffset(offset);
this._editorContextMenuLineNumber = line;
},
/**
* Called when the add breakpoint key sequence was pressed.
*/
_onCmdAddBreakpoint: function BP__onCmdAddBreakpoint() {
// If this command was executed via the context menu, add the breakpoint
// on the currently hovered line in the source editor.
if (this._editorContextMenuLineNumber >= 0) {
DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
}
// Avoid placing breakpoints incorrectly when using key shortcuts.
this._editorContextMenuLineNumber = -1;
let url = DebuggerView.Sources.selectedValue;
let line = DebuggerView.editor.getCaretPosition().line + 1;
let breakpointItem = this.getBreakpoint(url, line);
// If a breakpoint already existed, remove it now.
if (breakpointItem) {
let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line)
DebuggerController.Breakpoints.removeBreakpoint(breakpointClient);
DebuggerView.Breakpoints.unhighlightBreakpoint();
}
// No breakpoint existed at the required location, add one now.
else {
let breakpointLocation = { url: url, line: line };
DebuggerController.Breakpoints.addBreakpoint(breakpointLocation);
}
},
/**
* Called when the add conditional breakpoint key sequence was pressed.
*/
_onCmdAddConditionalBreakpoint: function BP__onCmdAddConditionalBreakpoint() {
// If this command was executed via the context menu, add the breakpoint
// on the currently hovered line in the source editor.
if (this._editorContextMenuLineNumber >= 0) {
DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
}
// Avoid placing breakpoints incorrectly when using key shortcuts.
this._editorContextMenuLineNumber = -1;
let url = DebuggerView.Sources.selectedValue;
let line = DebuggerView.editor.getCaretPosition().line + 1;
let breakpointItem = this.getBreakpoint(url, line);
// If a breakpoint already existed or wasn't a conditional, morph it now.
if (breakpointItem) {
breakpointItem.attachment.isConditional = true;
this.selectedClient.conditionalExpression = "";
this.highlightBreakpoint(url, line, { openPopup: true });
}
// No breakpoint existed at the required location, add one now.
else {
DebuggerController.Breakpoints.addBreakpoint({ url: url, line: line }, null, {
conditionalExpression: "",
openPopup: true
});
}
},
/**
* The popup showing listener for the breakpoints conditional expression panel.
*/
_onConditionalPopupShowing: function DVB__onConditionalPopupShowing() {
this._popupShown = true;
},
/**
* The popup shown listener for the breakpoints conditional expression panel.
*/
_onConditionalPopupShown: function DVB__onConditionalPopupShown() {
this._cbTextbox.focus();
this._cbTextbox.select();
},
/**
* The popup hiding listener for the breakpoints conditional expression panel.
*/
_onConditionalPopupHiding: function DVB__onConditionalPopupHiding() {
this._popupShown = false;
this.selectedClient.conditionalExpression = this._cbTextbox.value;
},
/**
* The keypress listener for the breakpoints conditional expression textbox.
*/
_onConditionalTextboxKeyPress: function DVB__onConditionalTextboxKeyPress(e) {
if (e.keyCode == e.DOM_VK_RETURN || e.keyCode == e.DOM_VK_ENTER) {
this._cbPanel.hidePopup();
}
},
/**
* The click listener for the breakpoints container.
*/
_onClick: function DVB__onClick(e) {
_onBreakpointClick: function DVB__onBreakpointClick(e) {
let breakpointItem = this.getItemForElement(e.target);
if (!breakpointItem) {
// The container is empty or we didn't click on an actual item.
return;
}
let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
DebuggerView.updateEditor(url, line, { noDebug: true });
this.highlightBreakpoint(url, line);
this.highlightBreakpoint(url, line, { updateEditor: true, openPopup: e.button == 0 });
},
/**
@ -535,14 +753,28 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
return;
}
let { sourceLocation: url, lineNumber: line, enabled } = breakpointItem.attachment;
this[enabled ? "disableBreakpoint" : "enableBreakpoint"](url, line, { silent: true });
// Don't update the editor location.
e.preventDefault();
e.stopPropagation();
},
this[enabled
? "disableBreakpoint"
: "enableBreakpoint"](url, line, { silent: true });
/**
* Listener handling the "setConditional" menuitem command.
*
* @param object aTarget
* The corresponding breakpoint element node.
*/
_onSetConditional: function DVB__onSetConditional(aTarget) {
if (!aTarget) {
return;
}
let breakpointItem = this.getItemForElement(aTarget);
let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
breakpointItem.attachment.isConditional = true;
this.highlightBreakpoint(url, line, { openPopup: true });
},
/**
@ -684,7 +916,12 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
},
_popupset: null,
_cache: null
_commandset: null,
_cbPanel: null,
_cbTextbox: null,
_popupShown: false,
_cache: null,
_editorContextMenuLineNumber: -1
});
/**

View File

@ -9,6 +9,8 @@ const SOURCE_URL_MAX_LENGTH = 64; // chars
const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
const PANES_APPEARANCE_DELAY = 50; // ms
const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "after_start";
const BREAKPOINT_CONDITIONAL_POPUP_OFFSET = 50; // px
const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
const GLOBAL_SEARCH_ACTION_DELAY = 150; // ms
@ -177,6 +179,7 @@ let DebuggerView = {
dumpn("Finished loading the DebuggerView editor");
DebuggerController.Breakpoints.initialize();
window.dispatchEvent("Debugger:EditorLoaded", this.editor);
this.editor.focus();
},
@ -188,6 +191,7 @@ let DebuggerView = {
dumpn("Destroying the DebuggerView editor");
DebuggerController.Breakpoints.destroy();
window.dispatchEvent("Debugger:EditorUnloaded", this.editor);
this.editor = null;
},
@ -237,7 +241,7 @@ let DebuggerView = {
* The source object coming from the active thread.
* @param object aOptions [optional]
* Additional options for showing the source. Supported options:
* - targetLine: place the caret position at the given line number
* - caretLine: place the caret position at the given line number
* - debugLine: place the debug location at the given line number
* - callback: function called when the source is shown
*/
@ -264,21 +268,25 @@ let DebuggerView = {
}
// If the source is already loaded, display it immediately.
else {
if (aSource.text.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
this.setEditorMode(aSource.url, aSource.contentType, aSource.text);
} else {
this.editor.setMode(SourceEditor.MODES.TEXT);
if (this._editorSource != aSource) {
// Avoid setting the editor mode for very large files.
if (aSource.text.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
this.setEditorMode(aSource.url, aSource.contentType, aSource.text);
} else {
this.editor.setMode(SourceEditor.MODES.TEXT);
}
this.editor.setText(aSource.text);
this.editor.resetUndo();
}
this.editor.setText(aSource.text);
this.editor.resetUndo();
this._editorSource = aSource;
this.updateEditor();
DebuggerView.Sources.selectedValue = aSource.url;
DebuggerController.Breakpoints.updateEditorBreakpoints();
// Handle any additional options for showing the source.
if (aOptions.targetLine) {
editor.setCaretPosition(aOptions.targetLine - 1);
if (aOptions.caretLine) {
editor.setCaretPosition(aOptions.caretLine - 1);
}
if (aOptions.debugLine) {
editor.setDebugLocation(aOptions.debugLine - 1);
@ -382,10 +390,12 @@ let DebuggerView = {
* An object containing some of the following boolean properties:
* - visible: true if the pane should be shown, false for hidden
* - animated: true to display an animation on toggle
* - callback: a function to invoke when the panes toggle finishes
*/
togglePanes: function DV__togglePanes(aFlags = {}) {
// Avoid useless toggles.
if (aFlags.visible == !this.panesHidden) {
aFlags.callback && aFlags.callback();
return;
}
@ -414,23 +424,29 @@ let DebuggerView = {
window.addEventListener("transitionend", function onEvent() {
window.removeEventListener("transitionend", onEvent, false);
aFlags.callback && aFlags.callback();
self.updateEditor();
}, false);
} else {
this._stackframesAndBreakpoints.removeAttribute("animated");
this._variables.removeAttribute("animated");
aFlags.callback && aFlags.callback();
}
},
/**
* Sets all the panes visible after a short period of time.
*
* @param function aCallback
* A function to invoke when the panes toggle finishes.
*/
showPanesSoon: function DV__showPanesSoon() {
showPanesSoon: function DV__showPanesSoon(aCallback) {
// Try to keep animations as smooth as possible, so wait a few cycles.
window.setTimeout(function() {
DebuggerView.togglePanes({
visible: true,
animated: true
animated: true,
callback: aCallback
});
}, PANES_APPEARANCE_DELAY);
},
@ -448,11 +464,13 @@ let DebuggerView = {
this.GlobalSearch.clearCache();
this.StackFrames.empty();
this.Breakpoints.empty();
this.Breakpoints.unhighlightBreakpoint();
this.Variables.empty();
SourceUtils.clearLabelsCache();
if (this.editor) {
this.editor.setText("");
this._editorSource = null;
}
},
@ -466,6 +484,7 @@ let DebuggerView = {
GlobalSearch: null,
Variables: null,
_editor: null,
_editorSource: null,
_togglePanesButton: null,
_stackframesAndBreakpoints: null,
_variables: null,

View File

@ -43,6 +43,10 @@
oncommand="DebuggerView.Filtering._doGlobalSearch()"/>
<command id="variableSearchCommand"
oncommand="DebuggerView.Filtering._doVariableSearch()"/>
<command id="addBreakpointCommand"
oncommand="DebuggerView.Breakpoints._onCmdAddBreakpoint()"/>
<command id="addConditionalBreakpointCommand"
oncommand="DebuggerView.Breakpoints._onCmdAddConditionalBreakpoint()"/>
<command id="togglePauseOnExceptions"
oncommand="DebuggerView.Options._togglePauseOnExceptions()"/>
<command id="toggleShowPanesOnStartup"
@ -56,6 +60,15 @@
<popupset id="debuggerPopupset">
<menupopup id="sourceEditorContextMenu"
onpopupshowing="goUpdateSourceEditorMenuItems()">
<menuitem id="se-dbg-cMenu-addBreakpoint"
label="&debuggerUI.seMenuBreak;"
key="addBreakpointKey"
command="addBreakpointCommand"/>
<menuitem id="se-dbg-cMenu-addConditionalBreakpoint"
label="&debuggerUI.seMenuCondBreak;"
key="addConditionalBreakpointKey"
command="addConditionalBreakpointCommand"/>
<menuseparator/>
<menuitem id="se-cMenu-copy"/>
<menuseparator/>
<menuitem id="se-cMenu-selectAll"/>
@ -128,6 +141,14 @@
key="V"
modifiers="control shift"
command="variableSearchCommand"/>
<key id="addBreakpointKey"
key="B"
modifiers="accel"
command="addBreakpointCommand"/>
<key id="addConditionalBreakpointKey"
key="B"
modifiers="accel shift"
command="addConditionalBreakpointCommand"/>
</keyset>
<vbox id="body" flex="1">
@ -201,6 +222,16 @@
</vbox>
</panel>
<panel id="conditional-breakpoint-panel"
type="arrow"
noautofocus="true"
position="after_start">
<vbox>
<label class="description" value="&debuggerUI.condBreakPanelTitle;"/>
<textbox id="conditional-breakpoint-textbox"/>
</vbox>
</panel>
<vbox id="dbg-content" flex="1">
<vbox id="globalsearch" hidden="true"/>
<splitter id="globalsearch-splitter"

View File

@ -72,6 +72,8 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_clean-exit.js \
browser_dbg_bug723069_editor-breakpoints.js \
browser_dbg_bug723071_editor-breakpoints-pane.js \
browser_dbg_bug740825_conditional-breakpoints-01.js \
browser_dbg_bug740825_conditional-breakpoints-02.js \
browser_dbg_bug731394_editor-contextmenu.js \
browser_dbg_bug786070_hide_nonenums.js \
browser_dbg_displayName.js \
@ -105,6 +107,7 @@ MOCHITEST_BROWSER_PAGES = \
browser_dbg_with-frame.html \
browser_dbg_pause-exceptions.html \
browser_dbg_breakpoint-new-script.html \
browser_dbg_conditional-breakpoints.html \
$(NULL)
MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES

View File

@ -0,0 +1,381 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Bug 740825: test the debugger conditional breakpoints.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_conditional-breakpoints.html";
let gPane = null;
let gTab = null;
let gDebuggee = null;
let gDebugger = null;
let gScripts = null;
let gEditor = null;
let gBreakpoints = null;
let gBreakpointsPane = null;
requestLongerTimeout(2);
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;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
gBreakpointsPane = gDebugger.DebuggerView.Breakpoints;
resumed = true;
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
framesAdded = true;
executeSoon(startTest);
});
executeSoon(function() {
gDebuggee.ermahgerd(); // ermahgerd!!
});
});
function onScriptShown(aEvent)
{
scriptShown = aEvent.detail.url.indexOf("conditional-breakpoints") != -1;
executeSoon(startTest);
}
window.addEventListener("Debugger:SourceShown", onScriptShown);
function startTest()
{
if (scriptShown && framesAdded && resumed && !testStarted) {
window.removeEventListener("Debugger:SourceShown", onScriptShown);
testStarted = true;
Services.tm.currentThread.dispatch({ run: addBreakpoints }, 0);
}
}
function performTest()
{
gScripts = gDebugger.DebuggerView.Sources;
is(gDebugger.DebuggerController.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
is(gScripts._container.itemCount, 1, "Found the expected number of scripts.");
gEditor = gDebugger.editor;
isnot(gEditor.getText().indexOf("ermahgerd"), -1,
"The correct script was loaded initially.");
is(gScripts.selectedValue, gScripts.values[0],
"The correct script is selected");
gBreakpoints = gPane.breakpoints;
is(Object.keys(gBreakpoints).length, 13, "thirteen breakpoints");
ok(!gPane.getBreakpoint("foo", 3), "getBreakpoint('foo', 3) returns falsey");
is(gEditor.getBreakpoints().length, 13, "thirteen breakpoints in the editor");
gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
executeSoon(test1);
}
function test1(callback)
{
resumeAndTestBreakpoint(gScripts.selectedValue, 14, test2);
}
function test2(callback)
{
resumeAndTestBreakpoint(gScripts.selectedValue, 15, test3);
}
function test3(callback)
{
resumeAndTestBreakpoint(gScripts.selectedValue, 16, test4);
}
function test4(callback)
{
resumeAndTestBreakpoint(gScripts.selectedValue, 17, test5);
}
function test5(callback)
{
resumeAndTestBreakpoint(gScripts.selectedValue, 18, test6);
}
function test6(callback)
{
resumeAndTestBreakpoint(gScripts.selectedValue, 19, test7);
}
function test7(callback)
{
resumeAndTestBreakpoint(gScripts.selectedValue, 21, test8);
}
function test8(callback)
{
resumeAndTestBreakpoint(gScripts.selectedValue, 22, test9);
}
function test9(callback)
{
resumeAndTestBreakpoint(gScripts.selectedValue, 23, test10);
}
function test10(callback)
{
resume(null, function() {
is(gBreakpointsPane.selectedItem, null,
"There should be no selected breakpoint in the breakpoints pane.")
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should not be shown.");
is(gDebugger.DebuggerController.activeThread.state, "attached",
"Should have finished going through all the breakpoints.");
is(gDebugger.DebuggerView.StackFrames.visibleItems, 0,
"There should be no visible stackframes.");
is(gDebugger.DebuggerView.Breakpoints.visibleItems, 13,
"There should be thirteen visible breakpoints.");
testReload();
});
}
function resumeAndTestBreakpoint(url, line, callback)
{
resume(line, function() {
waitForCaretPos(line - 1, function() {
testBreakpoint(gBreakpointsPane.selectedItem, gBreakpointsPane.selectedClient, url, line, true);
callback();
});
});
}
function testBreakpoint(aBreakpointItem, aBreakpointClient, url, line, editor)
{
is(aBreakpointItem.attachment.sourceLocation, gScripts.selectedValue,
"The breakpoint on line " + line + " wasn't added on the correct source.");
is(aBreakpointItem.attachment.lineNumber, line,
"The breakpoint on line " + line + " wasn't found.");
is(aBreakpointItem.attachment.enabled, true,
"The breakpoint on line " + line + " should be enabled.");
is(aBreakpointItem.attachment.isConditional, true,
"The breakpoint on line " + line + " should be conditional.");
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should not be shown.");
is(aBreakpointClient.location.url, url,
"The breakpoint's client url is correct");
is(aBreakpointClient.location.line, line,
"The breakpoint's client line is correct");
isnot(aBreakpointClient.conditionalExpression, undefined,
"The breakpoint on line " + line + " should have a conditional expression.");
if (editor) {
is(gEditor.getCaretPosition().line + 1, line,
"The editor caret position is not situated on the proper line.");
is(gEditor.getCaretPosition().col, 0,
"The editor caret position is not situated on the proper column.");
}
}
function addBreakpoints(callback)
{
let currentUrl = gDebugger.DebuggerView.Sources.selectedValue;
gPane.addBreakpoint({ url: currentUrl, line: 12 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 13 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 14 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 15 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 16 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 17 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 18 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 19 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 20 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 21 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 22 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 23 }, function() {
gPane.addBreakpoint({ url: currentUrl, line: 24 }, function() {
performTest();
}, {
conditionalExpression: "b"
});
}, {
conditionalExpression: "a !== null"
});
}, {
conditionalExpression: "a !== undefined"
});
}, {
conditionalExpression: "a"
});
}, {
conditionalExpression: "(function() { return false; })()"
});
}, {
conditionalExpression: "function() {}"
});
}, {
conditionalExpression: "{}"
});
}, {
conditionalExpression: "/regexp/"
});
}, {
conditionalExpression: "'nasu'"
});
}, {
conditionalExpression: "true"
});
}, {
conditionalExpression: "42"
});
}, {
conditionalExpression: "null"
});
}, {
conditionalExpression: "undefined"
});
}
function testReload()
{
function _get(url, line) {
return [
gDebugger.DebuggerView.Breakpoints.getBreakpoint(url, line),
gDebugger.DebuggerController.Breakpoints.getBreakpoint(url, line),
url, line, false
];
}
waitForBreakpoints(13, function() {
testBreakpoint.apply(this, _get(gScripts.selectedValue, 14));
testBreakpoint.apply(this, _get(gScripts.selectedValue, 15));
testBreakpoint.apply(this, _get(gScripts.selectedValue, 16));
testBreakpoint.apply(this, _get(gScripts.selectedValue, 17));
testBreakpoint.apply(this, _get(gScripts.selectedValue, 18));
testBreakpoint.apply(this, _get(gScripts.selectedValue, 19));
testBreakpoint.apply(this, _get(gScripts.selectedValue, 21));
testBreakpoint.apply(this, _get(gScripts.selectedValue, 22));
testBreakpoint.apply(this, _get(gScripts.selectedValue, 23));
is(gBreakpointsPane.selectedItem, null,
"There should be no selected item in the breakpoints pane.");
is(gBreakpointsPane.selectedClient, null,
"There should be no selected client in the breakpoints pane.");
closeDebuggerAndFinish();
});
finalCheck();
gDebuggee.location.reload();
}
function finalCheck() {
isnot(gEditor.getText().indexOf("ermahgerd"), -1,
"The correct script is still loaded.");
is(gScripts.selectedValue, gScripts.values[0],
"The correct script is still selected");
}
function resume(expected, callback) {
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
Services.tm.currentThread.dispatch({ run: function() {
waitForBreakpoint(expected, callback);
}}, 0);
});
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.getElementById("resume"),
gDebugger);
}
let bogusClient = {
location: {
url: null,
line: null
}
};
function waitForBreakpoint(expected, callback) {
// Poll every few milliseconds until expected breakpoint is hit.
let count = 0;
let intervalID = window.setInterval(function() {
info("count: " + count + " ");
if (++count > 50) {
ok(false, "Timed out while polling for the breakpoint.");
window.clearInterval(intervalID);
return closeDebuggerAndFinish();
}
if ((gBreakpointsPane.selectedClient !== expected) &&
(gBreakpointsPane.selectedClient || bogusClient).location.line !== expected) {
return;
}
// We arrived at the expected line, it's safe to callback.
window.clearInterval(intervalID);
callback();
}, 100);
}
function waitForBreakpoints(total, callback)
{
// Poll every few milliseconds until the breakpoints are retrieved.
let count = 0;
let intervalID = window.setInterval(function() {
info("count: " + count + " ");
if (++count > 50) {
ok(false, "Timed out while polling for the breakpoints.");
window.clearInterval(intervalID);
return closeDebuggerAndFinish();
}
if (gBreakpointsPane.visibleItems != total) {
return;
}
// We got all the breakpoints, it's safe to callback.
window.clearInterval(intervalID);
callback();
}, 100);
}
function waitForCaretPos(number, callback)
{
// Poll every few milliseconds until the source editor line is active.
let count = 0;
let intervalID = window.setInterval(function() {
info("count: " + count + " ");
if (++count > 50) {
ok(false, "Timed out while polling for the line.");
window.clearInterval(intervalID);
return closeDebuggerAndFinish();
}
if (gEditor.getCaretPosition().line != number) {
return;
}
// We got the source editor at the expected line, it's safe to callback.
window.clearInterval(intervalID);
callback();
}, 100);
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
gScripts = null;
gEditor = null;
gBreakpoints = null;
gBreakpointsPane = null;
});
}

View File

@ -0,0 +1,510 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Bug 740825: test the debugger conditional breakpoints.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_conditional-breakpoints.html";
let gPane = null;
let gTab = null;
let gDebuggee = null;
let gDebugger = null;
let gScripts = null;
let gEditor = null;
let gBreakpoints = null;
let gBreakpointsPane = null;
requestLongerTimeout(2);
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;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.contentWindow;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
gBreakpointsPane = gDebugger.DebuggerView.Breakpoints;
resumed = true;
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
framesAdded = true;
executeSoon(startTest);
});
executeSoon(function() {
gDebuggee.ermahgerd(); // ermahgerd!!
});
});
function onScriptShown(aEvent)
{
scriptShown = aEvent.detail.url.indexOf("conditional-breakpoints") != -1;
executeSoon(startTest);
}
window.addEventListener("Debugger:SourceShown", onScriptShown);
function startTest()
{
if (scriptShown && framesAdded && resumed && !testStarted) {
window.removeEventListener("Debugger:SourceShown", onScriptShown);
testStarted = true;
Services.tm.currentThread.dispatch({ run: performTest }, 0);
}
}
function performTest()
{
gScripts = gDebugger.DebuggerView.Sources;
is(gDebugger.DebuggerController.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
is(gScripts._container.itemCount, 1, "Found the expected number of scripts.");
gEditor = gDebugger.editor;
isnot(gEditor.getText().indexOf("ermahgerd"), -1,
"The correct script was loaded initially.");
is(gScripts.selectedValue, gScripts.values[0],
"The correct script is selected");
gBreakpoints = gPane.breakpoints;
is(Object.keys(gBreakpoints), 0, "no breakpoints");
ok(!gPane.getBreakpoint("foo", 3), "getBreakpoint('foo', 3) returns falsey");
is(gEditor.getBreakpoints().length, 0, "no breakpoints in the editor");
gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
executeSoon(addBreakpoint1);
}
function addBreakpoint1()
{
gPane.addBreakpoint({ url: gScripts.selectedValue, line: 12 });
waitForBreakpoint(12, function() {
waitForCaretPos(10, function() {
waitForPopup(false, function() {
testBreakpoint(gBreakpointsPane.selectedItem,
gBreakpointsPane.selectedClient,
gScripts.selectedValue, 12, false, false, false);
executeSoon(addBreakpoint2);
});
});
});
}
function addBreakpoint2()
{
gBreakpointsPane._editorContextMenuLineNumber = 12;
gBreakpointsPane._onCmdAddBreakpoint();
waitForBreakpoint(13, function() {
waitForCaretPos(12, function() {
waitForPopup(false, function() {
testBreakpoint(gBreakpointsPane.selectedItem,
gBreakpointsPane.selectedClient,
gScripts.selectedValue, 13, false, false, true);
executeSoon(modBreakpoint2);
});
});
});
}
function modBreakpoint2()
{
gBreakpointsPane._editorContextMenuLineNumber = 12;
gBreakpointsPane._onCmdAddConditionalBreakpoint();
waitForBreakpoint(13, function() {
waitForCaretPos(12, function() {
waitForPopup(true, function() {
testBreakpoint(gBreakpointsPane.selectedItem,
gBreakpointsPane.selectedClient,
gScripts.selectedValue, 13, true, true, true);
executeSoon(addBreakpoint3);
});
});
});
}
function addBreakpoint3()
{
gBreakpointsPane._editorContextMenuLineNumber = 13;
gBreakpointsPane._onCmdAddConditionalBreakpoint();
waitForBreakpoint(14, function() {
waitForCaretPos(13, function() {
waitForPopup(true, function() {
testBreakpoint(gBreakpointsPane.selectedItem,
gBreakpointsPane.selectedClient,
gScripts.selectedValue, 14, true, true, true);
executeSoon(modBreakpoint3);
});
});
});
}
function modBreakpoint3()
{
write("bamboocha");
EventUtils.sendKey("RETURN");
waitForBreakpoint(14, function() {
waitForCaretPos(13, function() {
waitForPopup(false, function() {
is(gBreakpointsPane.selectedClient.conditionalExpression, "bamboocha",
"The bamboocha expression wasn't fonud on the conditional breakpoint");
executeSoon(testHighlights1);
});
});
});
}
function testHighlights1()
{
isnot(gBreakpointsPane.selectedItem, null,
"There should be a selected breakpoint in the breakpoints pane.");
is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
"The selected breakpoint should have the correct location.");
is(gBreakpointsPane.selectedItem.attachment.lineNumber, 14,
"The selected breakpoint should have the correct line number.");
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should not be shown.");
is(gEditor.getCaretPosition().line, 13,
"The source editor caret position should be at line 13");
is(gEditor.getCaretPosition().col, 0,
"The source editor caret position should be at column 0");
gEditor.setCaretPosition(12);
waitForCaretPos(12, function() {
waitForPopup(false, function() {
isnot(gBreakpointsPane.selectedItem, null,
"There should be a selected breakpoint in the breakpoints pane.");
is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
"The selected breakpoint should have the correct location.");
is(gBreakpointsPane.selectedItem.attachment.lineNumber, 13,
"The selected breakpoint should have the correct line number.");
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should not be shown.");
is(gEditor.getCaretPosition().line, 12,
"The source editor caret position should be at line 12");
is(gEditor.getCaretPosition().col, 0,
"The source editor caret position should be at column 0");
gEditor.setCaretPosition(11);
waitForCaretPos(11, function() {
waitForPopup(false, function() {
isnot(gBreakpointsPane.selectedItem, null,
"There should be a selected breakpoint in the breakpoints pane.");
is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
"The selected breakpoint should have the correct location.");
is(gBreakpointsPane.selectedItem.attachment.lineNumber, 12,
"The selected breakpoint should have the correct line number.");
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should not be shown.");
is(gEditor.getCaretPosition().line, 11,
"The source editor caret position should be at line 11");
is(gEditor.getCaretPosition().col, 0,
"The source editor caret position should be at column 0");
gEditor.setCaretPosition(10);
waitForCaretPos(10, function() {
waitForPopup(false, function() {
is(gBreakpointsPane.selectedItem, null,
"There should not be a selected breakpoint in the breakpoints pane.");
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should not be shown.");
is(gEditor.getCaretPosition().line, 10,
"The source editor caret position should be at line 10");
is(gEditor.getCaretPosition().col, 0,
"The source editor caret position should be at column 0");
gEditor.setCaretPosition(14);
waitForCaretPos(14, function() {
waitForPopup(false, function() {
is(gBreakpointsPane.selectedItem, null,
"There should not be a selected breakpoint in the breakpoints pane.");
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should not be shown.");
is(gEditor.getCaretPosition().line, 14,
"The source editor caret position should be at line 14");
is(gEditor.getCaretPosition().col, 0,
"The source editor caret position should be at column 0");
executeSoon(testHighlights2);
});
});
});
});
});
});
});
});
}
function testHighlights2()
{
EventUtils.sendMouseEvent({ type: "click" },
gBreakpointsPane._container.getItemAtIndex(2),
gDebugger);
waitForCaretPos(13, function() {
waitForPopup(true, function() {
isnot(gBreakpointsPane.selectedItem, null,
"There should be a selected breakpoint in the breakpoints pane.");
is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
"The selected breakpoint should have the correct location.");
is(gBreakpointsPane.selectedItem.attachment.lineNumber, 14,
"The selected breakpoint should have the correct line number.");
is(gBreakpointsPane._popupShown, true,
"The breakpoint conditional expression popup should be shown.");
is(gEditor.getCaretPosition().line, 13,
"The source editor caret position should be at line 13");
is(gEditor.getCaretPosition().col, 0,
"The source editor caret position should be at column 0");
EventUtils.sendMouseEvent({ type: "click" },
gBreakpointsPane._container.getItemAtIndex(1),
gDebugger);
waitForCaretPos(12, function() {
waitForPopup(true, function() {
isnot(gBreakpointsPane.selectedItem, null,
"There should be a selected breakpoint in the breakpoints pane.");
is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
"The selected breakpoint should have the correct location.");
is(gBreakpointsPane.selectedItem.attachment.lineNumber, 13,
"The selected breakpoint should have the correct line number.");
is(gBreakpointsPane._popupShown, true,
"The breakpoint conditional expression popup should be shown.");
is(gEditor.getCaretPosition().line, 12,
"The source editor caret position should be at line 12");
is(gEditor.getCaretPosition().col, 0,
"The source editor caret position should be at column 0");
EventUtils.sendMouseEvent({ type: "click" },
gBreakpointsPane._container.getItemAtIndex(0),
gDebugger);
waitForCaretPos(11, function() {
waitForPopup(false, function() {
isnot(gBreakpointsPane.selectedItem, null,
"There should be a selected breakpoint in the breakpoints pane.");
is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
"The selected breakpoint should have the correct location.");
is(gBreakpointsPane.selectedItem.attachment.lineNumber, 12,
"The selected breakpoint should have the correct line number.");
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should be shown.");
is(gEditor.getCaretPosition().line, 11,
"The source editor caret position should be at line 11");
is(gEditor.getCaretPosition().col, 0,
"The source editor caret position should be at column 0");
executeSoon(delBreakpoint2);
});
});
});
});
});
});
}
function delBreakpoint2()
{
gBreakpointsPane._editorContextMenuLineNumber = 12;
gBreakpointsPane._onCmdAddBreakpoint();
waitForBreakpoint(null, function() {
waitForPopup(false, function() {
is(gBreakpointsPane.selectedItem, null,
"There should be no selected breakpoint in the breakpoints pane.")
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should not be shown.");
executeSoon(delBreakpoint3);
});
});
}
function delBreakpoint3()
{
gBreakpointsPane._editorContextMenuLineNumber = 13;
gBreakpointsPane._onCmdAddBreakpoint();
waitForBreakpoint(null, function() {
waitForPopup(false, function() {
is(gBreakpointsPane.selectedItem, null,
"There should be no selected breakpoint in the breakpoints pane.")
is(gBreakpointsPane._popupShown, false,
"The breakpoint conditional expression popup should not be shown.");
executeSoon(testBreakpoints);
});
});
}
function testBreakpoints()
{
is(Object.keys(gBreakpoints).length, 1, "one breakpoint");
ok(!gPane.getBreakpoint("foo", 3), "getBreakpoint('foo', 3) returns falsey");
is(gEditor.getBreakpoints().length, 1, "one breakpoint in the editor");
closeDebuggerAndFinish();
}
function testBreakpoint(aBreakpointItem, aBreakpointClient, url, line, conditional, popup, editor)
{
is(aBreakpointItem.attachment.sourceLocation, gScripts.selectedValue,
"The breakpoint on line " + line + " wasn't added on the correct source.");
is(aBreakpointItem.attachment.lineNumber, line,
"The breakpoint on line " + line + " wasn't found.");
is(aBreakpointItem.attachment.enabled, true,
"The breakpoint on line " + line + " should be enabled.");
is(aBreakpointItem.attachment.isConditional, conditional,
"The breakpoint on line " + line + " should " + (conditional ? "" : "not ") + "be conditional.");
is(gBreakpointsPane._popupShown, conditional,
"The breakpoint conditional expression popup should" + (conditional ? "" : "not ") + "be shown.");
is(aBreakpointClient.location.url, url,
"The breakpoint's client url is correct");
is(aBreakpointClient.location.line, line,
"The breakpoint's client line is correct");
if (conditional) {
isnot(aBreakpointClient.conditionalExpression, undefined,
"The breakpoint on line " + line + " should have a conditional expression.");
} else {
is(aBreakpointClient.conditionalExpression, undefined,
"The breakpoint on line " + line + " should not have a conditional expression.");
}
if (editor) {
is(gEditor.getCaretPosition().line + 1, line,
"The editor caret position is not situated on the proper line.");
is(gEditor.getCaretPosition().col, 0,
"The editor caret position is not situated on the proper column.");
}
}
let bogusClient = {
location: {
url: null,
line: null
}
};
function waitForBreakpoint(expected, callback) {
// Poll every few milliseconds until expected breakpoint is hit.
let count = 0;
let intervalID = window.setInterval(function() {
info("count: " + count + " ");
if (++count > 50) {
ok(false, "Timed out while polling for the breakpoint.");
window.clearInterval(intervalID);
return closeDebuggerAndFinish();
}
if ((gBreakpointsPane.selectedClient !== expected) &&
(gBreakpointsPane.selectedClient || bogusClient).location.line !== expected) {
return;
}
// We arrived at the expected line, it's safe to callback.
window.clearInterval(intervalID);
callback();
}, 100);
}
function waitForCaretPos(number, callback)
{
// Poll every few milliseconds until the source editor line is active.
let count = 0;
let intervalID = window.setInterval(function() {
info("count: " + count + " ");
if (++count > 50) {
ok(false, "Timed out while polling for the line.");
window.clearInterval(intervalID);
return closeDebuggerAndFinish();
}
if (gEditor.getCaretPosition().line != number) {
return;
}
// We got the source editor at the expected line, it's safe to callback.
window.clearInterval(intervalID);
callback();
}, 100);
}
function waitForPopup(state, callback)
{
// Poll every few milliseconds until the expression popup is shown.
let count = 0;
let intervalID = window.setInterval(function() {
info("count: " + count + " ");
if (++count > 50) {
ok(false, "Timed out while polling for the popup.");
window.clearInterval(intervalID);
return closeDebuggerAndFinish();
}
if (gBreakpointsPane._popupShown != state) {
return;
}
// We got the expression popup at the expected state, it's safe to callback.
window.clearInterval(intervalID);
callback();
}, 100);
}
function clear() {
gBreakpointsPane._cbTextbox.focus();
gBreakpointsPane._cbTextbox.value = "";
}
function write(text) {
clear();
append(text);
}
function append(text) {
gBreakpointsPane._cbTextbox.focus();
for (let i = 0; i < text.length; i++) {
EventUtils.sendChar(text[i]);
}
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
gScripts = null;
gEditor = null;
gBreakpoints = null;
gBreakpointsPane = null;
});
}

View File

@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<title>Browser Debugger Conditional Breakpoints Test</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript">
function ermahgerd() {
var a = {};
debugger;
console.log("undefined");
console.log("null");
console.log("42");
console.log("true");
console.log("'nasu'");
console.log("/regexp/");
console.log("{}");
console.log("function() {}");
console.log("(function { return false; })()");
console.log("a");
console.log("a !== undefined");
console.log("a !== null");
console.log("b");
}
</script>
</head>
<body>
</body>
</html>

View File

@ -1486,7 +1486,34 @@ VariablesView.isPrimitive = function VV_isPrimitive(aDescriptor) {
// For convenience, undefined and null are both considered types.
let type = grip.type;
if (["undefined", "null"].indexOf(type + "") != -1) {
if (type == "undefined" || type == "null") {
return true;
}
return false;
};
/**
* Returns true if the descriptor represents a falsy value.
*
* @param object aDescriptor
* The variable's descriptor.
*/
VariablesView.isFalsy = function VV_isFalsy(aDescriptor) {
if (!aDescriptor || typeof aDescriptor != "object") {
return true;
}
// As described in the remote debugger protocol, the value grip
// must be contained in a 'value' property.
let grip = aDescriptor.value;
if (typeof grip != "object") {
return !grip;
}
// For convenience, undefined and null are both considered types.
let type = grip.type;
if (type == "undefined" || type == "null") {
return true;
}

View File

@ -63,3 +63,16 @@
<!-- LOCALIZATION NOTE (debuggerUI.searchPanelTitle): This is the text that
- appears in the filter panel popup as a description. -->
<!ENTITY debuggerUI.searchPanelTitle "Operators">
<!-- LOCALIZATION NOTE (debuggerUI.condBreakPanelTitle): This is the text that
- appears in the conditional breakpoint panel popup as a description. -->
<!ENTITY debuggerUI.condBreakPanelTitle "This breakpoint will stop execution only if the following expression is true">
<!-- LOCALIZATION NOTE (debuggerUI.seMenuBreak): This is the text that
- appears in the source editor context menu for adding a breakpoint. -->
<!ENTITY debuggerUI.seMenuBreak "Add breakpoint">
<!-- LOCALIZATION NOTE (debuggerUI.seMenuCondBreak): This is the text that
- appears in the source editor context menu for adding a conditional
- breakpoint. -->
<!ENTITY debuggerUI.seMenuCondBreak "Add conditional breakpoint">

View File

@ -134,6 +134,7 @@ searchPanelVariable=Filter variables (%S)
# LOCALIZATION NOTE (breakpointMenuItem): The text for all the elements that
# are displayed in the breakpoints menu item popup.
breakpointMenuItem.setConditional=Configure conditional breakpoint
breakpointMenuItem.enableSelf=Enable breakpoint
breakpointMenuItem.disableSelf=Disable breakpoint
breakpointMenuItem.deleteSelf=Remove breakpoint

View File

@ -136,7 +136,7 @@
#stackframes {
background-color: white;
min-height: 30px;
min-height: 10px;
}
.dbg-stackframe {
@ -154,14 +154,18 @@
#breakpoints {
background-color: white;
min-height: 30px;
min-height: 10px;
}
#breakpoints > vbox:not(:empty) {
min-height: 30px;
min-height: 10px;
max-height: 200px;
}
.dbg-breakpoint:not(:last-child) {
border-bottom: 1px solid #eee;
}
.dbg-breakpoint-info {
font-weight: 600;
}
@ -170,6 +174,14 @@
font: 8pt monospace;
}
#conditional-breakpoint-panel .description {
margin: -6px 0 8px 0;
}
#conditional-breakpoint-panel textbox {
margin: 0 0 -2px 0;
}
/**
* Variables view
*/

View File

@ -138,7 +138,7 @@
#stackframes {
background-color: white;
min-height: 30px;
min-height: 10px;
}
.dbg-stackframe {
@ -156,14 +156,18 @@
#breakpoints {
background-color: white;
min-height: 30px;
min-height: 10px;
}
#breakpoints > vbox:not(:empty) {
min-height: 30px;
min-height: 10px;
max-height: 200px;
}
.dbg-breakpoint:not(:last-child) {
border-bottom: 1px solid #eee;
}
.dbg-breakpoint-info {
font-weight: 600;
}
@ -172,6 +176,14 @@
font: 8pt monospace;
}
#conditional-breakpoint-panel .description {
margin: -6px 0 8px 0;
}
#conditional-breakpoint-panel textbox {
margin: 0 0 -2px 0;
}
/**
* Variables view
*/

View File

@ -144,7 +144,7 @@
#stackframes {
background-color: white;
min-height: 30px;
min-height: 10px;
}
.dbg-stackframe {
@ -162,14 +162,18 @@
#breakpoints {
background-color: white;
min-height: 30px;
min-height: 10px;
}
#breakpoints > vbox:not(:empty) {
min-height: 30px;
min-height: 10px;
max-height: 200px;
}
.dbg-breakpoint:not(:last-child) {
border-bottom: 1px solid #eee;
}
.dbg-breakpoint-info {
font-weight: 600;
}
@ -178,6 +182,14 @@
font: 8pt monospace;
}
#conditional-breakpoint-panel .description {
margin: -6px 0 8px 0;
}
#conditional-breakpoint-panel textbox {
margin: 0 0 -2px 0;
}
/**
* Variables view
*/