Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2012-11-17 13:36:17 -05:00
commit 2cf42692b6
73 changed files with 4955 additions and 657 deletions

View File

@ -52,6 +52,7 @@ let DebuggerController = {
DebuggerView.initialize(function() {
DebuggerView._isInitialized = true;
window.dispatchEvent("Debugger:Loaded");
this._connect();
}.bind(this));
@ -371,7 +372,10 @@ function StackFrames() {
StackFrames.prototype = {
get activeThread() DebuggerController.activeThread,
autoScopeExpand: false,
currentFrame: null,
currentBreakpointLocation: null,
currentEvaluation: null,
currentException: null,
/**
@ -417,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);
@ -443,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);
}
@ -459,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
@ -493,11 +538,11 @@ StackFrames.prototype = {
if (!frame) {
return;
}
let env = frame.environment;
let environment = frame.environment;
let { url, line } = frame.where;
// Check if the frame does not represent the evaluation of debuggee code.
if (!env) {
if (!environment) {
return;
}
@ -512,78 +557,24 @@ StackFrames.prototype = {
// Clear existing scopes and create each one dynamically.
DebuggerView.Variables.empty();
let self = this;
let name = "";
do {
// Name the outermost scope Global.
if (!env.parent) {
name = L10N.getStr("globalScopeLabel");
}
// Otherwise construct the scope name.
else {
name = env.type.charAt(0).toUpperCase() + env.type.slice(1);
}
let label = L10N.getFormatStr("scopeLabel", [name]);
switch (env.type) {
case "with":
case "object":
label += " [" + env.object.class + "]";
break;
case "function":
label += " [" + env.functionName + "]";
break;
}
// Create a scope to contain all the inspected variables.
let label = this._getScopeLabel(environment);
let scope = DebuggerView.Variables.addScope(label);
// Special additions to the innermost scope.
if (env == frame.environment) {
// Add any thrown exception.
if (aDepth == 0 && this.currentException) {
let excVar = scope.addVar("<exception>", { value: this.currentException });
this._addExpander(excVar, this.currentException);
}
// Add "this".
if (frame.this) {
let thisVar = scope.addVar("this", { value: frame.this });
this._addExpander(thisVar, frame.this);
}
// Expand the innermost scope by default.
scope.expand(true);
if (environment == frame.environment) {
this._insertScopeFrameReferences(scope, frame);
this._fetchScopeVariables(scope, environment);
// Always expand the innermost scope by default.
scope.expand();
}
switch (env.type) {
case "with":
case "object":
// Add nodes for all variables in the environment object scope.
this.activeThread.pauseGrip(env.object).getPrototypeAndProperties(function(aResponse) {
self._addScopeVariables(aResponse.ownProperties, scope);
// Signal that variables have been fetched.
window.dispatchEvent("Debugger:FetchedVariables");
DebuggerView.Variables.commitHierarchy();
});
break;
case "block":
case "function":
// Add nodes for every argument.
for (let variable of env.bindings.arguments) {
let name = Object.getOwnPropertyNames(variable)[0];
let paramVar = scope.addVar(name, variable[name]);
let paramVal = variable[name].value;
this._addExpander(paramVar, paramVal);
}
// Add nodes for every other variable in scope.
this._addScopeVariables(env.bindings.variables, scope);
break;
default:
Cu.reportError("Unknown Debugger.Environment type: " + env.type);
break;
// Lazily add nodes for every other environment scope.
else {
this._addScopeExpander(scope, environment);
this.autoScopeExpand && scope.expand();
}
} while (env = env.parent);
} while (environment = environment.parent);
// Signal that variables have been fetched.
window.dispatchEvent("Debugger:FetchedVariables");
@ -591,30 +582,21 @@ StackFrames.prototype = {
},
/**
* Add nodes for every variable in scope.
* Adds an 'onexpand' callback for a scope, lazily handling
* the addition of new variables.
*
* @param object aVariables
* The map of names to variables, as specified in the Remote
* Debugging Protocol.
* @param Scope aScope
* The scope where the nodes will be placed into.
* The scope where the variables will be placed into.
* @param object aEnv
* The scope's environment.
*/
_addScopeVariables: function SF_addScopeVariables(aVariables, aScope) {
if (!aVariables) {
return;
}
let variableNames = Object.keys(aVariables);
_addScopeExpander: function SF__addScopeExpander(aScope, aEnv) {
let callback = this._fetchScopeVariables.bind(this, aScope, aEnv);
// Sort all of the variables before adding them if preferred.
if (Prefs.variablesSortingEnabled) {
variableNames.sort();
}
// Add the sorted variables to the specified scope.
for (let name of variableNames) {
let paramVar = aScope.addVar(name, aVariables[name]);
let paramVal = aVariables[name].value;
this._addExpander(paramVar, paramVal);
}
// It's a good idea to be prepared in case of an expansion.
aScope.onmouseover = callback;
// Make sure that variables are always available on expansion.
aScope.onexpand = callback;
},
/**
@ -626,28 +608,144 @@ StackFrames.prototype = {
* @param any aGrip
* The grip of the variable.
*/
_addExpander: function SF__addExpander(aVar, aGrip) {
_addVarExpander: function SF__addVarExpander(aVar, aGrip) {
// No need for expansion for primitive values.
if (VariablesView.isPrimitive({ value: aGrip })) {
return;
}
aVar.onexpand = this._addVarProperties.bind(this, aVar, aGrip);
let callback = this._fetchVarProperties.bind(this, aVar, aGrip);
// Some variables are likely to contain a very large number of properties.
// It's a good idea to be prepared in case of an expansion.
if (aVar.name == "window" || aVar.name == "this") {
aVar.onmouseover = callback;
}
// Make sure that properties are always available on expansion.
aVar.onexpand = callback;
},
/**
* Adds variables to a scope in the view. Triggered when a scope is
* expanded or is hovered. It does not expand the scope.
*
* @param Scope aScope
* The scope where the variables will be placed into.
* @param object aEnv
* The scope's environment.
*/
_fetchScopeVariables: function SF__fetchScopeVariables(aScope, aEnv) {
// Retrieve the variables only once.
if (aScope.fetched) {
return;
}
aScope.fetched = true;
switch (aEnv.type) {
case "with":
case "object":
// Add nodes for every variable in scope.
this.activeThread.pauseGrip(aEnv.object).getPrototypeAndProperties(function(aResponse) {
this._insertScopeVariables(aResponse.ownProperties, aScope);
// Signal that variables have been fetched.
window.dispatchEvent("Debugger:FetchedVariables");
DebuggerView.Variables.commitHierarchy();
}.bind(this));
break;
case "block":
case "function":
// Add nodes for every argument and every other variable in scope.
this._insertScopeArguments(aEnv.bindings.arguments, aScope);
this._insertScopeVariables(aEnv.bindings.variables, aScope);
break;
default:
Cu.reportError("Unknown Debugger.Environment type: " + aEnv.type);
break;
}
},
/**
* Add nodes for special frame references in the innermost scope.
*
* @param Scope aScope
* The scope where the references will be placed into.
* @param object aFrame
* The frame to get some references from.
*/
_insertScopeFrameReferences: function SF__insertScopeFrameReferences(aScope, aFrame) {
// Add any thrown exception.
if (this.currentException) {
let excRef = aScope.addVar("<exception>", { value: this.currentException });
this._addVarExpander(excRef, this.currentException);
}
// Add "this".
if (aFrame.this) {
let thisRef = aScope.addVar("this", { value: aFrame.this });
this._addVarExpander(thisRef, aFrame.this);
}
},
/**
* Add nodes for every argument in scope.
*
* @param object aArguments
* The map of names to arguments, as specified in the protocol.
* @param Scope aScope
* The scope where the nodes will be placed into.
*/
_insertScopeArguments: function SF__insertScopeArguments(aArguments, aScope) {
if (!aArguments) {
return;
}
for (let argument of aArguments) {
let name = Object.getOwnPropertyNames(argument)[0];
let argRef = aScope.addVar(name, argument[name]);
let argVal = argument[name].value;
this._addVarExpander(argRef, argVal);
}
},
/**
* Add nodes for every variable in scope.
*
* @param object aVariables
* The map of names to variables, as specified in the protocol.
* @param Scope aScope
* The scope where the nodes will be placed into.
*/
_insertScopeVariables: function SF__insertScopeVariables(aVariables, aScope) {
if (!aVariables) {
return;
}
let variableNames = Object.keys(aVariables);
// Sort all of the variables before adding them if preferred.
if (Prefs.variablesSortingEnabled) {
variableNames.sort();
}
// Add the sorted variables to the specified scope.
for (let name of variableNames) {
let varRef = aScope.addVar(name, aVariables[name]);
let varVal = aVariables[name].value;
this._addVarExpander(varRef, varVal);
}
},
/**
* Adds properties to a variable in the view. Triggered when a variable is
* expanded.
* expanded or certain variables are hovered. It does not expand the variable.
*
* @param Variable aVar
* The variable where the properties will be placed into.
* @param any aGrip
* The grip of the variable.
*/
_addVarProperties: function SF__addVarProperties(aVar, aGrip) {
_fetchVarProperties: function SF__fetchVarProperties(aVar, aGrip) {
// Retrieve the properties only once.
if (aVar.fetched) {
return;
}
aVar.fetched = true;
this.activeThread.pauseGrip(aGrip).getPrototypeAndProperties(function(aResponse) {
let { ownProperties, prototype } = aResponse;
@ -657,7 +755,7 @@ StackFrames.prototype = {
aVar.addProperties(ownProperties);
// Expansion handlers must be set after the properties are added.
for (let name in ownProperties) {
this._addExpander(aVar.get(name), ownProperties[name].value);
this._addVarExpander(aVar.get(name), ownProperties[name].value);
}
}
@ -665,10 +763,10 @@ StackFrames.prototype = {
if (prototype.type != "null") {
aVar.addProperty("__proto__", { value: prototype });
// Expansion handlers must be set after the properties are added.
this._addExpander(aVar.get("__proto__"), prototype);
this._addVarExpander(aVar.get("__proto__"), prototype);
}
aVar.fetched = true;
aVar._retrieved = true;
// Signal that properties have been fetched.
window.dispatchEvent("Debugger:FetchedProperties");
@ -676,6 +774,39 @@ StackFrames.prototype = {
}.bind(this));
},
/**
* Constructs a scope label based on its environment.
*
* @param object aEnv
* The scope's environment.
* @return string
* The scope's label.
*/
_getScopeLabel: function SV__getScopeLabel(aEnv) {
let name = "";
// Name the outermost scope Global.
if (!aEnv.parent) {
name = L10N.getStr("globalScopeLabel");
}
// Otherwise construct the scope name.
else {
name = aEnv.type.charAt(0).toUpperCase() + aEnv.type.slice(1);
}
let label = L10N.getFormatStr("scopeLabel", [name]);
switch (aEnv.type) {
case "with":
case "object":
label += " [" + aEnv.object.class + "]";
break;
case "function":
label += " [" + aEnv.functionName + "]";
break;
}
return label;
},
/**
* Adds the specified stack frame to the list.
*
@ -688,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);
},
/**
@ -708,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);
}
};
@ -1017,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
});
}
}
},
@ -1030,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
});
}
}
},
@ -1041,8 +1177,8 @@ Breakpoints.prototype = {
* @param object aLocation
* The location where you want the breakpoint. This object must have
* two properties:
* - url - the url of the source.
* - line - the line number (starting from 1).
* - url: the url of the source.
* - line: the line number (starting from 1).
* @param function aCallback [optional]
* Optional function to invoke once the breakpoint is added. The
* callback is invoked with two arguments:
@ -1050,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 = {}) {
@ -1092,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);
@ -1115,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 = {}) {
@ -1147,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;
@ -1162,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);
}
},
@ -1175,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;
@ -1190,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
});
/**
@ -1044,7 +1281,7 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
let sourceResultsItem = SourceResults.getItemForElement(target);
let lineResultsItem = LineResults.getItemForElement(target);
sourceResultsItem.instance.expand(true);
sourceResultsItem.instance.expand();
this._currentlyFocusedMatch = LineResults.indexOfElement(target);
this._scrollMatchIntoViewIfNeeded(target);
this._bounceMatch(target);
@ -1084,7 +1321,7 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
let { clientHeight } = this._container._parent;
if (top - height <= clientHeight || this._forceExpandResults) {
sourceResultsItem.instance.expand(true);
sourceResultsItem.instance.expand();
}
},
@ -1303,7 +1540,7 @@ SourceResults.prototype = {
aElementNode.resultsContainer = resultsContainer;
if (aExpandFlag && aMatchCount < GLOBAL_SEARCH_EXPAND_MAX_RESULTS) {
this.expand(true);
this.expand();
}
let resultsBox = document.createElement("vbox");
@ -1528,7 +1765,7 @@ LineResults.indexOfElement = function DVGS_indexOFElement(aElement) {
SourceResults.size =
LineResults.size = function DVGS_size() {
let count = 0;
for (let [_, item] of this._itemsByElement) {
for (let [, item] of this._itemsByElement) {
if (!item.nonenumerable) {
count++;
}

View File

@ -884,6 +884,7 @@ FilterView.prototype = {
// Perform a variable search based on the specified operator.
if (isVariable) {
DebuggerView.Variables.performSearch(token);
DebuggerView.Variables.expandFirstSearchResults();
return;
}
@ -913,7 +914,6 @@ FilterView.prototype = {
_doSearch: function DVF__doSearch(aOperator = "") {
this._searchbox.focus();
this._searchbox.value = aOperator;
DebuggerView.GlobalSearch.clearView();
},
/**
@ -952,6 +952,7 @@ FilterView.prototype = {
* Called when the variable search filter key sequence was pressed.
*/
_doVariableSearch: function DVF__doVariableSearch() {
DebuggerView.Variables.performSearch("");
this._doSearch(SEARCH_VARIABLE_FLAG);
this._searchboxPanel.hidePopup();
},

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,
@ -680,7 +699,7 @@ MenuContainer.prototype = {
this._container.removeAttribute("tooltiptext");
this._container.removeAllItems();
for (let [_, item] of this._itemsByElement) {
for (let [, item] of this._itemsByElement) {
this._untangleItem(item);
}
@ -1057,7 +1076,7 @@ MenuContainer.prototype = {
* A generator-iterator over all the items in this container.
*/
__iterator__: function DVMC_iterator() {
for (let [_, item] of this._itemsByElement) {
for (let [, item] of this._itemsByElement) {
yield item;
}
},

View File

@ -13,14 +13,10 @@
overflow-y: auto;
}
.dbg-results-container {
.dbg-results-container:not([open]) {
display: none;
}
.dbg-results-container[open] {
display: -moz-box;
}
/**
* Stack frames
*/
@ -48,21 +44,18 @@
*/
#variables {
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
}
/**
* Scope, variable and property elements
*/
#variables .details {
#variables .details:not([open]) {
display: none;
}
#variables .details[open] {
display: -moz-box;
}
.scope[non-header] > .title,
.variable[non-header] > .title,
.property[non-header] > .title {

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

@ -39,6 +39,9 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_propertyview-filter-03.js \
browser_dbg_propertyview-filter-04.js \
browser_dbg_propertyview-filter-05.js \
browser_dbg_propertyview-filter-06.js \
browser_dbg_propertyview-filter-07.js \
browser_dbg_propertyview-filter-08.js \
browser_dbg_propertyview-reexpand.js \
browser_dbg_reload-same-script.js \
browser_dbg_pane-collapse.js \
@ -69,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 \
@ -102,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

@ -173,7 +173,7 @@ function test()
function(cl, err) {
onBreakpointAdd.call({ increment: increment, line: line }, cl, err);
line = 8;
line = 9;
gPane.addBreakpoint({url: gScripts.selectedValue, line: line},
function(cl, err) {
onBreakpointAdd.call({ increment: increment, line: line }, cl, err);

View File

@ -0,0 +1,384 @@
/* 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;
gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
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");
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)
{
gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
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.DebuggerView.StackFrames.visibleItems, 0,
"There should be no visible stackframes.");
is(gDebugger.DebuggerView.Breakpoints.visibleItems, 13,
"There should be thirteen visible breakpoints.");
testReload();
}, true);
gDebugger.DebuggerController.activeThread.resume();
}
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,511 @@
/* 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;
gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
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");
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

@ -36,68 +36,82 @@ function testNonEnumProperties() {
}
});
// Expand the variable.
testScope.expand();
testVar.expand();
let details = testVar._enum;
let nonenum = testVar._nonenum;
executeSoon(function() {
let details = testVar._enum;
let nonenum = testVar._nonenum;
is(details.childNodes.length, 1,
"There should be just one property in the .details container.");
is(details.childNodes.length, 1,
"There should be just one property in the .details container.");
ok(details.hasAttribute("open"),
".details container should be visible.");
ok(details.hasAttribute("open"),
".details container should be visible.");
is(nonenum.childNodes.length, 1,
"There should be just one property in the .nonenum container.");
ok(nonenum.hasAttribute("open"),
".nonenum container should be visible.");
ok(nonenum.hasAttribute("open"),
".nonenum container should be visible.");
is(nonenum.childNodes.length, 1,
"There should be just one property in the .nonenum container.");
// Uncheck 'show hidden properties'.
gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "false");
gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
// Uncheck 'show hidden properties'.
gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "false");
gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
ok(details.hasAttribute("open"),
".details container should stay visible.");
executeSoon(function() {
ok(details.hasAttribute("open"),
".details container should stay visible.");
ok(!nonenum.hasAttribute("open"),
".nonenum container should become hidden.");
ok(!nonenum.hasAttribute("open"),
".nonenum container should become hidden.");
// Check 'show hidden properties'.
gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "true");
gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
// Check 'show hidden properties'.
gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "true");
gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
ok(details.hasAttribute("open"),
".details container should stay visible.");
executeSoon(function() {
ok(details.hasAttribute("open"),
".details container should stay visible.");
ok(nonenum.hasAttribute("open"),
".nonenum container should become visible.");
ok(nonenum.hasAttribute("open"),
".nonenum container should become visible.");
testVar.collapse();
// Collapse the variable.
testVar.collapse();
ok(!details.hasAttribute("open"),
".details container should be hidden.");
executeSoon(function() {
ok(!details.hasAttribute("open"),
".details container should be hidden.");
ok(!nonenum.hasAttribute("open"),
".nonenum container should be hidden.");
ok(!nonenum.hasAttribute("open"),
".nonenum container should be hidden.");
// Uncheck 'show hidden properties'.
gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "false");
gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
// Uncheck 'show hidden properties'.
gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "false");
gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
ok(!details.hasAttribute("open"),
".details container should stay hidden.");
executeSoon(function() {
ok(!details.hasAttribute("open"),
".details container should stay hidden.");
ok(!nonenum.hasAttribute("open"),
".nonenum container should stay hidden.");
ok(!nonenum.hasAttribute("open"),
".nonenum container should stay hidden.");
// Check 'show hidden properties'.
gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "true");
gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
// Check 'show hidden properties'.
gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "true");
gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
gDebugger.DebuggerController.activeThread.resume(function() {
closeDebuggerAndFinish();
executeSoon(function() {
gDebugger.DebuggerController.activeThread.resume(function() {
closeDebuggerAndFinish();
});
});
});
});
});
});
});
}}, 0);
});

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

@ -13,6 +13,8 @@ var gTab = null;
var gDebugger = null;
var gCount = 0;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
@ -20,6 +22,8 @@ function test()
gPane = aPane;
gDebugger = gPane.contentWindow;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.nonEnumVisible = false;
testWithFrame();
});
}

View File

@ -92,9 +92,9 @@ function testFrameParameters()
window.clearInterval(intervalID);
return resumeAndFinish();
}
if (!thisNode.fetched ||
!argumentsNode.fetched ||
!cNode.fetched) {
if (!thisNode._retrieved ||
!argumentsNode._retrieved ||
!cNode._retrieved) {
return;
}
window.clearInterval(intervalID);

View File

@ -12,6 +12,8 @@ var gPane = null;
var gTab = null;
var gDebugger = null;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
@ -19,6 +21,8 @@ function test()
gPane = aPane;
gDebugger = gPane.contentWindow;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.nonEnumVisible = false;
testFrameParameters();
});
}

View File

@ -12,6 +12,8 @@ var gPane = null;
var gTab = null;
var gDebugger = null;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
@ -19,6 +21,8 @@ function test()
gPane = aPane;
gDebugger = gPane.contentWindow;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.nonEnumVisible = false;
testWithFrame();
});
}

View File

@ -3,12 +3,15 @@
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
requestLongerTimeout(2);
function test() {
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
@ -17,6 +20,8 @@ function test() {
gPane = aPane;
gDebugger = gPane.contentWindow;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.nonEnumVisible = false;
testFrameEval();
});
}

View File

@ -24,6 +24,7 @@ function test()
gDebugger = gPane.contentWindow;
gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
testSearchbox();
prepareVariables(testVariablesFiltering);
});
@ -121,6 +122,8 @@ function testVariablesFiltering()
"The local scope 'this.window.document.location' should be expanded");
ignoreExtraMatchedProperties();
locationItem.toggle();
locationItem.toggle();
is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the inner scope");
@ -207,6 +210,8 @@ function testVariablesFiltering()
"The local scope 'this.window.document.location' should be expanded");
ignoreExtraMatchedProperties();
locationItem.toggle();
locationItem.toggle();
is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the inner scope");
@ -317,6 +322,22 @@ function prepareVariables(aCallback)
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, true,
"The mathScope expanded getter should return true");
is(testScopeItem.expanded, true,
"The testScope expanded getter should return true");
is(loadScopeItem.expanded, true,
"The loadScope expanded getter should return true");
is(globalScopeItem.expanded, true,
"The globalScope expanded getter should return true");
mathScopeItem.collapse();
testScopeItem.collapse();
loadScopeItem.collapse();
globalScopeItem.collapse();
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, false,
@ -443,7 +464,7 @@ function prepareVariables(aCallback)
function ignoreExtraMatchedProperties()
{
for (let [_, item] of gDebugger.DebuggerView.Variables._currHierarchy) {
for (let [, item] of gDebugger.DebuggerView.Variables._currHierarchy) {
let name = item.name.toLowerCase();
let value = item._valueString || "";

View File

@ -24,6 +24,7 @@ function test()
gDebugger = gPane.contentWindow;
gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
testSearchbox();
prepareVariables(testVariablesFiltering);
});
@ -69,6 +70,11 @@ function testVariablesFiltering()
is(locationItem.expanded, true,
"The local scope 'this.window.document.location' should be expanded");
locationItem.toggle();
locationItem.toggle();
documentItem.toggle();
documentItem.toggle();
is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the inner scope");
is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
@ -150,6 +156,19 @@ function testVariablesFiltering()
write("htmldocument");
is(thisItem.expanded, true,
"The local scope 'this' should be expanded");
is(windowItem.expanded, true,
"The local scope 'this.window' should be expanded");
is(documentItem.expanded, true,
"The local scope 'this.window.document' should be expanded");
is(locationItem.expanded, false,
"The local scope 'this.window.document.location' should not be expanded");
documentItem.toggle();
documentItem.toggle();
locationItem.toggle();
is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the inner scope");
is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
@ -265,6 +284,22 @@ function prepareVariables(aCallback)
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, true,
"The mathScope expanded getter should return true");
is(testScopeItem.expanded, true,
"The testScope expanded getter should return true");
is(loadScopeItem.expanded, true,
"The loadScope expanded getter should return true");
is(globalScopeItem.expanded, true,
"The globalScope expanded getter should return true");
mathScopeItem.collapse();
testScopeItem.collapse();
loadScopeItem.collapse();
globalScopeItem.collapse();
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, false,

View File

@ -3,7 +3,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view correctly filters nodes by value.
* Make sure that the property view correctly filters nodes.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
@ -14,6 +14,8 @@ var gDebugger = null;
var gDebuggee = null;
var gSearchBox = null;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
@ -22,6 +24,7 @@ function test()
gDebugger = gPane.contentWindow;
gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
prepareVariables(testVariablesFiltering);
});
}
@ -258,7 +261,7 @@ function prepareVariables(aCallback)
function ignoreExtraMatchedProperties()
{
for (let [_, item] of gDebugger.DebuggerView.Variables._currHierarchy) {
for (let [, item] of gDebugger.DebuggerView.Variables._currHierarchy) {
let name = item.name.toLowerCase();
let value = item._valueString || "";

View File

@ -0,0 +1,248 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view correctly filters nodes.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
var gPane = null;
var gTab = null;
var gDebugger = null;
var gDebuggee = null;
var gSearchBox = null;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gPane = aPane;
gDebugger = gPane.contentWindow;
gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = false;
prepareVariables(testVariablesFiltering);
});
}
function testVariablesFiltering()
{
let f = {
test1: function()
{
assertExpansion(1, [true, false, false, false, false]);
clear();
},
test2: function()
{
assertExpansion(2, [true, false, false, false, false]);
EventUtils.sendKey("RETURN");
},
test3: function()
{
assertExpansion(3, [true, false, false, false, false]);
gDebugger.editor.focus();
},
test4: function()
{
assertExpansion(4, [true, false, false, false, false]);
write("*");
},
test5: function() {
assertExpansion(5, [true, true, true, true, true]);
EventUtils.sendKey("RETURN");
},
test6: function() {
assertExpansion(6, [true, true, true, true, true]);
gDebugger.editor.focus();
},
test7: function() {
assertExpansion(7, [true, true, true, true, true]);
backspace(1);
},
test8: function() {
assertExpansion(8, [true, true, true, true, true]);
EventUtils.sendKey("RETURN");
},
test9: function() {
assertExpansion(9, [true, true, true, true, true]);
gDebugger.editor.focus();
},
test10: function() {
assertExpansion(10, [true, true, true, true, true]);
innerScopeItem.collapse();
mathScopeItem.collapse();
testScopeItem.collapse();
loadScopeItem.collapse();
globalScopeItem.collapse();
},
test11: function() {
assertExpansion(11, [false, false, false, false, false]);
clear();
},
test12: function() {
assertExpansion(12, [false, false, false, false, false]);
EventUtils.sendKey("RETURN");
},
test13: function() {
assertExpansion(13, [false, false, false, false, false]);
gDebugger.editor.focus();
},
test14: function() {
assertExpansion(14, [false, false, false, false, false]);
write("*");
},
test15: function() {
assertExpansion(15, [true, true, true, true, true]);
EventUtils.sendKey("RETURN");
},
test16: function() {
assertExpansion(16, [true, true, true, true, true]);
gDebugger.editor.focus();
},
test17: function() {
assertExpansion(17, [true, true, true, true, true]);
backspace(1);
},
test18: function() {
assertExpansion(18, [true, true, true, true, true]);
EventUtils.sendKey("RETURN");
},
test19: function() {
assertExpansion(19, [true, true, true, true, true]);
gDebugger.editor.focus();
},
test20: function() {
assertExpansion(20, [true, true, true, true, true]);
}
};
function assertExpansion(n, array) {
is(innerScopeItem.expanded, array[0],
"The innerScope should " + (array[0] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(mathScopeItem.expanded, array[1],
"The mathScope should " + (array[1] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(testScopeItem.expanded, array[2],
"The testScope should " + (array[2] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(loadScopeItem.expanded, array[3],
"The loadScope should " + (array[3] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(globalScopeItem.expanded, array[4],
"The globalScope should " + (array[4] ? "" : "not ") +
"be expanded at this point (" + n + ")");
}
var scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
executeSoon(function() {
for (let i = 1; i <= Object.keys(f).length; i++) {
f["test" + i]();
}
closeDebuggerAndFinish();
});
}
function prepareVariables(aCallback)
{
let count = 0;
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
// We expect 2 Debugger:FetchedVariables events, one from the inner object
// scope and the regular one.
if (++count < 2) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._container._list,
scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
executeSoon(function() {
aCallback();
});
}}, 0);
}, false);
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee.window);
}
function clear() {
gSearchBox.focus();
gSearchBox.value = "";
}
function write(text) {
clear();
append(text);
}
function backspace(times) {
for (let i = 0; i < times; i++) {
EventUtils.sendKey("BACK_SPACE")
}
}
function append(text) {
gSearchBox.focus();
for (let i = 0; i < text.length; i++) {
EventUtils.sendChar(text[i]);
}
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebugger = null;
gDebuggee = null;
gSearchBox = null;
});

View File

@ -0,0 +1,253 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view correctly filters nodes.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
var gPane = null;
var gTab = null;
var gDebugger = null;
var gDebuggee = null;
var gSearchBox = null;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gPane = aPane;
gDebugger = gPane.contentWindow;
gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
prepareVariables(testVariablesFiltering);
});
}
function testVariablesFiltering()
{
let f = {
test1: function()
{
assertExpansion(1, [true, false, false, false, false]);
clear();
},
test2: function()
{
assertExpansion(2, [true, false, false, false, false]);
EventUtils.sendKey("RETURN");
},
test3: function()
{
assertExpansion(3, [true, false, false, false, false]);
gDebugger.editor.focus();
},
test4: function()
{
assertExpansion(4, [true, false, false, false, false]);
write("*");
},
test5: function() {
assertExpansion(5, [true, true, true, true, true]);
EventUtils.sendKey("RETURN");
},
test6: function() {
assertExpansion(6, [true, true, true, true, true]);
gDebugger.editor.focus();
},
test7: function() {
assertExpansion(7, [true, true, true, true, true]);
backspace(1);
},
test8: function() {
assertExpansion(8, [true, true, true, true, true]);
EventUtils.sendKey("RETURN");
},
test9: function() {
assertExpansion(9, [true, true, true, true, true]);
gDebugger.editor.focus();
},
test10: function() {
assertExpansion(10, [true, true, true, true, true]);
innerScopeItem.collapse();
mathScopeItem.collapse();
testScopeItem.collapse();
loadScopeItem.collapse();
globalScopeItem.collapse();
},
test11: function() {
assertExpansion(11, [false, false, false, false, false]);
clear();
},
test12: function() {
assertExpansion(12, [false, false, false, false, false]);
EventUtils.sendKey("RETURN");
},
test13: function() {
assertExpansion(13, [false, false, false, false, false]);
gDebugger.editor.focus();
},
test14: function() {
assertExpansion(14, [false, false, false, false, false]);
write("*");
},
test15: function() {
assertExpansion(15, [true, true, true, true, true]);
EventUtils.sendKey("RETURN");
},
test16: function() {
assertExpansion(16, [true, true, true, true, true]);
gDebugger.editor.focus();
},
test17: function() {
assertExpansion(17, [true, true, true, true, true]);
backspace(1);
},
test18: function() {
assertExpansion(18, [true, true, true, true, true]);
EventUtils.sendKey("RETURN");
},
test19: function() {
assertExpansion(19, [true, true, true, true, true]);
gDebugger.editor.focus();
},
test20: function() {
assertExpansion(20, [true, true, true, true, true]);
}
};
function assertExpansion(n, array) {
is(innerScopeItem.expanded, array[0],
"The innerScope should " + (array[0] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(mathScopeItem.expanded, array[1],
"The mathScope should " + (array[1] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(testScopeItem.expanded, array[2],
"The testScope should " + (array[2] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(loadScopeItem.expanded, array[3],
"The loadScope should " + (array[3] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(globalScopeItem.expanded, array[4],
"The globalScope should " + (array[4] ? "" : "not ") +
"be expanded at this point (" + n + ")");
}
var scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
executeSoon(function() {
for (let i = 1; i <= Object.keys(f).length; i++) {
f["test" + i]();
}
closeDebuggerAndFinish();
});
}
function prepareVariables(aCallback)
{
let count = 0;
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
// We expect 2 Debugger:FetchedVariables events, one from the inner object
// scope and the regular one.
if (++count < 2) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._container._list,
scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
EventUtils.sendMouseEvent({ type: "mousedown" }, mathScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, testScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, loadScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, globalScope.querySelector(".arrow"), gDebuggee);
executeSoon(function() {
aCallback();
});
}}, 0);
}, false);
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee.window);
}
function clear() {
gSearchBox.focus();
gSearchBox.value = "";
}
function write(text) {
clear();
append(text);
}
function backspace(times) {
for (let i = 0; i < times; i++) {
EventUtils.sendKey("BACK_SPACE")
}
}
function append(text) {
gSearchBox.focus();
for (let i = 0; i < text.length; i++) {
EventUtils.sendChar(text[i]);
}
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebugger = null;
gDebuggee = null;
gSearchBox = null;
});

View File

@ -0,0 +1,323 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view correctly filters nodes.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
var gPane = null;
var gTab = null;
var gDebugger = null;
var gDebuggee = null;
var gSearchBox = null;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gPane = aPane;
gDebugger = gPane.contentWindow;
gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
prepareVariables(testVariablesFiltering);
});
}
function testVariablesFiltering()
{
let f = {
test1: function(aCallback)
{
assertExpansion(1, [true, false, false, false, false]);
write("*arguments");
aCallback();
},
test2: function(aCallback)
{
is(testScopeItem.get("arguments").expanded, false,
"The arguments pseudoarray in the testScope should not be expanded");
is(loadScopeItem.get("arguments").expanded, false,
"The arguments pseudoarray in the testScope should not be expanded");
assertExpansion(1, [true, true, true, true, true]);
EventUtils.sendKey("RETURN");
aCallback();
},
test3: function(aCallback)
{
is(testScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
is(loadScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
waitForFetchedProperties(2, function() {
is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the testScope should have 4 visible properties");
is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the loadScope should have 4 visible properties");
assertExpansion(2, [true, true, true, true, true]);
backspace(1);
aCallback();
});
},
test4: function(aCallback)
{
is(testScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
is(loadScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
waitForFetchedProperties(0, function() {
is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the testScope should have 4 visible properties");
is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the loadScope should have 4 visible properties");
assertExpansion(3, [true, true, true, true, true]);
backspace(8);
aCallback();
});
},
test5: function(aCallback)
{
is(testScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
is(loadScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
waitForFetchedProperties(0, function() {
is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the testScope should have 4 visible properties");
is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the loadScope should have 4 visible properties");
assertExpansion(4, [true, true, true, true, true]);
backspace(1);
aCallback();
});
},
test6: function(aCallback)
{
is(testScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
is(loadScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
waitForFetchedProperties(0, function() {
is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the testScope should have 4 visible properties");
is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the loadScope should have 4 visible properties");
assertExpansion(5, [true, true, true, true, true]);
write("*");
aCallback();
});
},
test7: function(aCallback)
{
is(testScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
is(loadScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
waitForFetchedProperties(0, function() {
is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the testScope should have 4 visible properties");
is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
"The arguments in the loadScope should have 4 visible properties");
assertExpansion(5, [true, true, true, true, true]);
append("arguments");
aCallback();
});
},
test8: function(aCallback)
{
is(testScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
is(loadScopeItem.get("arguments").expanded, true,
"The arguments pseudoarray in the testScope should now be expanded");
waitForFetchedProperties(0, function() {
is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 0,
"The arguments in the testScope should have 0 visible properties");
is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 0,
"The arguments in the loadScope should have 0 visible properties");
assertExpansion(5, [true, true, true, true, true]);
aCallback();
});
},
};
function assertExpansion(n, array) {
is(innerScopeItem.expanded, array[0],
"The innerScope should " + (array[0] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(mathScopeItem.expanded, array[1],
"The mathScope should " + (array[1] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(testScopeItem.expanded, array[2],
"The testScope should " + (array[2] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(loadScopeItem.expanded, array[3],
"The loadScope should " + (array[3] ? "" : "not ") +
"be expanded at this point (" + n + ")");
is(globalScopeItem.expanded, array[4],
"The globalScope should " + (array[4] ? "" : "not ") +
"be expanded at this point (" + n + ")");
}
function waitForFetchedProperties(n, aCallback) {
if (n == 0) {
aCallback();
return;
}
let count = 0;
gDebugger.addEventListener("Debugger:FetchedProperties", function test() {
// We expect n Debugger:FetchedProperties events.
if (++count < n) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedProperties", test, false);
Services.tm.currentThread.dispatch({ run: function() {
executeSoon(aCallback);
}}, 0);
}, false);
}
var scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
executeSoon(function() {
f.test1(function() {
f.test2(function() {
f.test3(function() {
f.test4(function() {
f.test5(function() {
f.test6(function() {
f.test7(function() {
f.test8(function() {
closeDebuggerAndFinish();
});
});
});
});
});
});
});
});
});
}
function prepareVariables(aCallback)
{
let count = 0;
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
// We expect 2 Debugger:FetchedVariables events, one from the inner object
// scope and the regular one.
if (++count < 2) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._container._list,
scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
EventUtils.sendMouseEvent({ type: "mousedown" }, mathScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, testScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, loadScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, globalScope.querySelector(".arrow"), gDebuggee);
executeSoon(function() {
aCallback();
});
}}, 0);
}, false);
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee.window);
}
function clear() {
gSearchBox.focus();
gSearchBox.value = "";
}
function write(text) {
clear();
append(text);
}
function backspace(times) {
for (let i = 0; i < times; i++) {
EventUtils.sendKey("BACK_SPACE")
}
}
function append(text) {
gSearchBox.focus();
for (let i = 0; i < text.length; i++) {
EventUtils.sendChar(text[i]);
}
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebugger = null;
gDebuggee = null;
gSearchBox = null;
});

View File

@ -35,7 +35,9 @@ function addBreakpoint()
}, function() {
// Wait for the resume...
gDebugger.gClient.addOneTimeListener("resumed", function() {
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.nonEnumVisible = false;
gDebugger.DebuggerView.Variables.commitHierarchyIgnoredItems = Object.create(null);
testVariablesExpand();
});
});
@ -73,6 +75,44 @@ function testVariablesExpand()
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
is(innerScope.querySelector(".arrow").hasAttribute("open"), true,
"The innerScope arrow should initially be expanded");
is(mathScope.querySelector(".arrow").hasAttribute("open"), true,
"The mathScope arrow should initially be expanded");
is(testScope.querySelector(".arrow").hasAttribute("open"), true,
"The testScope arrow should initially be expanded");
is(loadScope.querySelector(".arrow").hasAttribute("open"), true,
"The loadScope arrow should initially be expanded");
is(globalScope.querySelector(".arrow").hasAttribute("open"), true,
"The globalScope arrow should initially be expanded");
is(innerScope.querySelector(".details").hasAttribute("open"), true,
"The innerScope enumerables should initially be expanded");
is(mathScope.querySelector(".details").hasAttribute("open"), true,
"The mathScope enumerables should initially be expanded");
is(testScope.querySelector(".details").hasAttribute("open"), true,
"The testScope enumerables should initially be expanded");
is(loadScope.querySelector(".details").hasAttribute("open"), true,
"The loadScope enumerables should initially be expanded");
is(globalScope.querySelector(".details").hasAttribute("open"), true,
"The globalScope enumerables should initially be expanded");
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, true,
"The mathScope expanded getter should return true");
is(testScopeItem.expanded, true,
"The testScope expanded getter should return true");
is(loadScopeItem.expanded, true,
"The loadScope expanded getter should return true");
is(globalScopeItem.expanded, true,
"The globalScope expanded getter should return true");
mathScopeItem.collapse();
testScopeItem.collapse();
loadScopeItem.collapse();
globalScopeItem.collapse();
is(innerScope.querySelector(".arrow").hasAttribute("open"), true,
"The innerScope arrow should initially be expanded");
is(mathScope.querySelector(".arrow").hasAttribute("open"), false,

View File

@ -139,7 +139,7 @@ VariablesView.prototype = {
set enumVisible(aFlag) {
this._enumVisible = aFlag;
for (let [_, scope] in this) {
for (let [, scope] in this) {
scope._nonEnumVisible = aFlag;
}
},
@ -151,7 +151,7 @@ VariablesView.prototype = {
set nonEnumVisible(aFlag) {
this._nonEnumVisible = aFlag;
for (let [_, scope] in this) {
for (let [, scope] in this) {
scope._nonEnumVisible = aFlag;
}
},
@ -216,13 +216,17 @@ VariablesView.prototype = {
* The variable or property to search for.
*/
performSearch: function VV_performSerch(aQuery) {
if (!aQuery) {
for (let [_, item] of this._currHierarchy) {
item._match = true;
}
} else {
for (let [_, scope] in this) {
scope._performSearch(aQuery.toLowerCase());
for (let [, scope] in this) {
switch (aQuery) {
case "":
scope.expand();
// fall through
case null:
scope._performSearch("");
break;
default:
scope._performSearch(aQuery.toLowerCase());
break;
}
}
},
@ -231,8 +235,8 @@ VariablesView.prototype = {
* Expands the first search results in this container.
*/
expandFirstSearchResults: function VV_expandFirstSearchResults() {
for (let [_, scope] in this) {
for (let [_, variable] in scope) {
for (let [, scope] in this) {
for (let [, variable] in scope) {
if (variable._isMatch) {
variable.expand();
break;
@ -366,6 +370,8 @@ function Scope(aView, aName, aFlags = {}) {
this.expand = this.expand.bind(this);
this.collapse = this.collapse.bind(this);
this.toggle = this.toggle.bind(this);
this._openEnum = this._openEnum.bind(this);
this._openNonEnum = this._openNonEnum.bind(this);
this.ownerView = aView;
this.eval = aView.eval;
@ -443,24 +449,16 @@ Scope.prototype = {
/**
* Expands the scope, showing all the added details.
*
* @param boolean aSkipAnimationFlag
* Pass true to not show an opening animation.
*/
expand: function S_expand(aSkipAnimationFlag) {
expand: function S_expand() {
if (this._isExpanded || this._locked) {
return;
}
if (this._variablesView._enumVisible) {
this._arrow.setAttribute("open", "");
this._enum.setAttribute("open", "");
this._openEnum();
}
if (this._variablesView._nonEnumVisible) {
this._nonenum.setAttribute("open", "");
}
if (!aSkipAnimationFlag) {
this._enum.setAttribute("animated", "");
this._nonenum.setAttribute("animated", "");
Services.tm.currentThread.dispatch({ run: this._openNonEnum }, 0);
}
this._isExpanded = true;
@ -479,8 +477,6 @@ Scope.prototype = {
this._arrow.removeAttribute("open");
this._enum.removeAttribute("open");
this._nonenum.removeAttribute("open");
this._enum.removeAttribute("animated");
this._nonenum.removeAttribute("animated");
this._isExpanded = false;
if (this.oncollapse) {
@ -495,6 +491,11 @@ Scope.prototype = {
this._wasToggled = true;
this.expanded ^= 1;
// Make sure the scope and its contents are visibile.
for (let [, variable] in this) {
variable.header = true;
variable._match = true;
}
if (this.ontoggle) {
this.ontoggle(this);
}
@ -504,6 +505,9 @@ Scope.prototype = {
* Shows the scope's title header.
*/
showHeader: function S_showHeader() {
if (this._isHeaderVisible) {
return;
}
this._target.removeAttribute("non-header");
this._isHeaderVisible = true;
},
@ -513,6 +517,9 @@ Scope.prototype = {
* This action will automatically expand the scope.
*/
hideHeader: function S_hideHeader() {
if (!this._isHeaderVisible) {
return;
}
this.expand();
this._target.setAttribute("non-header", "");
this._isHeaderVisible = false;
@ -522,6 +529,9 @@ Scope.prototype = {
* Shows the scope's expand/collapse arrow.
*/
showArrow: function S_showArrow() {
if (this._isArrowVisible) {
return;
}
this._arrow.removeAttribute("invisible");
this._isArrowVisible = true;
},
@ -530,6 +540,9 @@ Scope.prototype = {
* Hides the scope's expand/collapse arrow.
*/
hideArrow: function S_hideArrow() {
if (!this._isArrowVisible) {
return;
}
this._arrow.setAttribute("invisible", "");
this._isArrowVisible = false;
},
@ -663,12 +676,35 @@ Scope.prototype = {
this._title.addEventListener("mousedown", this.toggle, false);
},
/**
* Adds an event listener for the mouse over event on the title element.
* @param function aCallback
*/
set onmouseover(aCallback) {
this._title.addEventListener("mouseover", aCallback, false);
},
/**
* Opens the enumerable items container.
*/
_openEnum: function S__openEnum() {
this._arrow.setAttribute("open", "");
this._enum.setAttribute("open", "");
},
/**
* Opens the non-enumerable items container.
*/
_openNonEnum: function S__openNonEnum() {
this._nonenum.setAttribute("open", "");
},
/**
* Specifies if enumerable properties and variables should be displayed.
* @param boolean aFlag
*/
set _enumVisible(aFlag) {
for (let [_, variable] in this) {
for (let [, variable] in this) {
variable._enumVisible = aFlag;
if (!this.expanded) {
@ -687,7 +723,7 @@ Scope.prototype = {
* @param boolean aFlag
*/
set _nonEnumVisible(aFlag) {
for (let [_, variable] in this) {
for (let [, variable] in this) {
variable._nonEnumVisible = aFlag;
if (!this.expanded) {
@ -709,7 +745,7 @@ Scope.prototype = {
* The lowercased name of the variable or property to search for.
*/
_performSearch: function S__performSearch(aLowerCaseQuery) {
for (let [_, variable] in this) {
for (let [, variable] in this) {
let currentObject = variable;
let lowerCaseName = variable._nameString.toLowerCase();
let lowerCaseValue = variable._valueString.toLowerCase();
@ -727,8 +763,11 @@ Scope.prototype = {
// contain some matched properties, so make sure they're visible
// ("expand downwards").
if (variable._wasToggled) {
variable.expand(true);
if (variable._wasToggled && aLowerCaseQuery) {
variable.expand();
}
if (variable._isExpanded && !aLowerCaseQuery) {
variable._wasToggled = true;
}
// If the variable is contained in another scope (variable or property),
@ -742,12 +781,14 @@ Scope.prototype = {
// Show and expand the parent, as it is certainly accessible.
variable._match = true;
variable.expand(true);
aLowerCaseQuery && variable.expand();
}
}
// Proceed with the search recursively inside this variable or property.
if (variable._wasToggled || variable.expanded || variable.getter || variable.setter) {
if (currentObject._wasToggled ||
currentObject.getter ||
currentObject.setter) {
currentObject._performSearch(aLowerCaseQuery);
}
}
@ -836,6 +877,7 @@ Scope.prototype = {
* The variable's descriptor.
*/
function Variable(aScope, aName, aDescriptor) {
this._displayTooltip = this._displayTooltip.bind(this);
this._activateInput = this._activateInput.bind(this);
this._deactivateInput = this._deactivateInput.bind(this);
this._saveInput = this._saveInput.bind(this);
@ -918,6 +960,7 @@ create({ constructor: Variable, proto: Scope.prototype }, {
if (this.fetched) {
return;
}
this.fetched = true;
// Sort all of the properties before adding them.
let sortedPropertyNames = Object.getOwnPropertyNames(aObject).sort();
@ -936,8 +979,6 @@ create({ constructor: Variable, proto: Scope.prototype }, {
if (prototype) {
this._addRawValueProperty("__proto__", {}, prototype);
}
this.fetched = true;
},
/**
@ -1056,7 +1097,7 @@ create({ constructor: Variable, proto: Scope.prototype }, {
this._idString = generateId(this._nameString = aName);
this._createScope(aName, "variable");
this._displayVariable(aDescriptor);
this._displayTooltip();
this._prepareTooltip();
this._setAttributes(aName, aDescriptor);
this._addEventListeners();
@ -1092,16 +1133,24 @@ create({ constructor: Variable, proto: Scope.prototype }, {
if (aDescriptor.get || aDescriptor.set) {
this.addProperty("get", { value: aDescriptor.get });
this.addProperty("set", { value: aDescriptor.set });
this.expand(true);
this.expand();
separatorLabel.hidden = true;
valueLabel.hidden = true;
}
},
/**
* Prepares a tooltip for this variable.
*/
_prepareTooltip: function V__prepareTooltip() {
this._target.addEventListener("mouseover", this._displayTooltip, false);
},
/**
* Creates a tooltip for this variable.
*/
_displayTooltip: function V__displayTooltip() {
this._target.removeEventListener("mouseover", this._displayTooltip, false);
let document = this.document;
let tooltip = document.createElement("tooltip");
@ -1173,6 +1222,8 @@ create({ constructor: Variable, proto: Scope.prototype }, {
if (!this.eval) {
return;
}
let window = this.window;
let document = this.document;
let title = this._title;
let valueLabel = this._valueLabel;
@ -1182,8 +1233,8 @@ create({ constructor: Variable, proto: Scope.prototype }, {
// element's value location.
let input = this.document.createElement("textbox");
input.setAttribute("value", initialString);
input.className = "element-input";
input.width = valueLabel.clientWidth + 1;
input.className = "plain element-input";
input.width = this._target.clientWidth;
title.removeChild(valueLabel);
title.appendChild(input);
@ -1238,9 +1289,11 @@ create({ constructor: Variable, proto: Scope.prototype }, {
this._deactivateInput(e);
if (initialString != currentString) {
this._arrow.setAttribute("invisible", "");
this._separatorLabel.hidden = true;
this._valueLabel.hidden = true;
this.collapse();
this._enum.hidden = true;
this._nonenum.hidden = true;
this.eval("(" + this._symbolicName + "=" + currentString + ")");
}
},
@ -1304,7 +1357,7 @@ create({ constructor: Property, proto: Variable.prototype }, {
this._idString = generateId(this._nameString = aName);
this._createScope(aName, "property");
this._displayVariable(aDescriptor);
this._displayTooltip();
this._prepareTooltip();
this._setAttributes(aName, aDescriptor);
this._addEventListeners();
@ -1350,29 +1403,34 @@ VariablesView.prototype.commitHierarchy = function VV_commitHierarchy() {
if (currVariable._committed) {
continue;
}
// Avoid performing expensive operations.
if (this.commitHierarchyIgnoredItems[currVariable._nameString]) {
continue;
}
// Try to get the previous instance of the inspected variable to
// determine the difference in state.
let prevVariable = prevHierarchy.get(absoluteName);
let expanded = false;
let changed = false;
// If the inspected variable existed in a previous hierarchy, check if
// the displayed value (a representation of the grip) has changed.
// the displayed value (a representation of the grip) has changed and if
// it was previously expanded.
if (prevVariable) {
let prevString = prevVariable._valueString;
let currString = currVariable._valueString;
changed = prevString != currString;
// Re-expand the variable if not previously collapsed.
if (prevVariable.expanded) {
currVariable.expand(true);
}
expanded = prevVariable._isExpanded;
changed = prevVariable._valueString != currVariable._valueString;
}
// Make sure this variable is not handled in ulteror commits for the
// same hierarchy.
currVariable._committed = true;
// Re-expand the variable if not previously collapsed.
if (expanded) {
currVariable._wasToggled = prevVariable._wasToggled;
currVariable.expand();
}
// This variable was either not changed or removed, no need to continue.
if (!changed) {
continue;
@ -1392,6 +1450,13 @@ VariablesView.prototype.commitHierarchy = function VV_commitHierarchy() {
}
};
// Some variables are likely to contain a very large number of properties.
// It would be a bad idea to re-expand them or perform expensive operations.
VariablesView.prototype.commitHierarchyIgnoredItems = Object.create(null, {
"window": { value: true },
"this": { value: true }
});
/**
* Returns true if the descriptor represents an undefined, null or
* primitive value.
@ -1421,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

@ -2010,6 +2010,66 @@ SourceEditor.prototype = {
return breakpoints;
},
/**
* Convert the given rectangle from one coordinate reference to another.
*
* Known coordinate references:
* - "document" - gives the coordinates relative to the entire document.
* - "view" - gives the coordinates relative to the editor viewport.
*
* @param object aRect
* The rectangle to convert. Object properties: x, y, width and height.
* @param string aFrom
* The source coordinate reference.
* @param string aTo
* The destination coordinate reference.
* @return object aRect
* Returns the rectangle with changed coordinates.
*/
convertCoordinates: function SE_convertCoordinates(aRect, aFrom, aTo)
{
return this._view.convert(aRect, aFrom, aTo);
},
/**
* Get the character offset nearest to the given pixel location.
*
* @param number aX
* @param number aY
* @return number
* Returns the character offset at the given location.
*/
getOffsetAtLocation: function SE_getOffsetAtLocation(aX, aY)
{
return this._view.getOffsetAtLocation(aX, aY);
},
/**
* Get the pixel location, relative to the document, at the given character
* offset.
*
* @param number aOffset
* @return object
* The pixel location relative to the document being edited. Two
* properties are included: x and y.
*/
getLocationAtOffset: function SE_getLocationAtOffset(aOffset)
{
return this._view.getLocationAtOffset(aOffset);
},
/**
* Get the line location for a given character offset.
*
* @param number aOffset
* @return number
* The line location relative to the give character offset.
*/
getLineAtOffset: function SE_getLineAtOffset(aOffset)
{
return this._model.getLineAtOffset(aOffset);
},
/**
* Destroy/uninitialize the editor.
*/

View File

@ -31,6 +31,7 @@ MOCHITEST_BROWSER_FILES = \
browser_bug731721_debugger_stepping.js \
browser_bug729960_block_bracket_jump.js \
browser_bug744021_next_prev_bracket_jump.js \
browser_bug725392_mouse_coords_char_offset.js \
head.js \
include $(topsrcdir)/config/rules.mk

View File

@ -71,6 +71,15 @@ function editorLoaded()
ok(pos.line == 2 && pos.col == 0, "setCaretPosition(line) works, again");
}
let offsetLine = editor.getLineAtOffset(0);
is(offsetLine, 0, "getLineAtOffset() is correct for offset 0");
let offsetLine = editor.getLineAtOffset(6);
is(offsetLine, 1, "getLineAtOffset() is correct for offset 6");
let offsetLine = editor.getLineAtOffset(12);
is(offsetLine, 2, "getLineAtOffset() is correct for offset 12");
editor.destroy();
testWin.close();

View File

@ -0,0 +1,160 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function test()
{
let testWin;
let editor;
let mousePos = { x: 36, y: 4 };
let expectedOffset = 5;
let maxDiff = 10;
waitForExplicitFinish();
function editorLoaded(aEditor, aWindow)
{
editor = aEditor;
testWin = aWindow;
let text = fillEditor(editor, 3);
editor.setText(text);
editor.setCaretOffset(0);
doMouseMove(testPage1);
}
function doMouseMove(aCallback)
{
function mouseEventHandler(aEvent)
{
editor.removeEventListener(editor.EVENTS.MOUSE_OUT, mouseEventHandler);
editor.removeEventListener(editor.EVENTS.MOUSE_OVER, mouseEventHandler);
editor.removeEventListener(editor.EVENTS.MOUSE_MOVE, mouseEventHandler);
executeSoon(aCallback.bind(null, aEvent));
}
editor.addEventListener(editor.EVENTS.MOUSE_MOVE, mouseEventHandler);
editor.addEventListener(editor.EVENTS.MOUSE_OUT, mouseEventHandler);
editor.addEventListener(editor.EVENTS.MOUSE_OVER, mouseEventHandler);
let target = editor.editorElement;
let targetWin = testWin;
EventUtils.synthesizeMouse(target, mousePos.x, mousePos.y,
{type: "mousemove"}, targetWin);
EventUtils.synthesizeMouse(target, mousePos.x, mousePos.y,
{type: "mouseout"}, targetWin);
EventUtils.synthesizeMouse(target, mousePos.x, mousePos.y,
{type: "mouseover"}, targetWin);
}
function checkValue(aValue, aExpectedValue)
{
let result = Math.abs(aValue - aExpectedValue) <= maxDiff;
if (!result) {
info("checkValue() given " + aValue + " expected " + aExpectedValue);
}
return result;
}
function testPage1(aEvent)
{
let {event: { clientX: clientX, clientY: clientY }, x: x, y: y} = aEvent;
info("testPage1 " + aEvent.type +
" clientX " + clientX + " clientY " + clientY +
" x " + x + " y " + y);
// x and y are in document coordinates.
// clientX and clientY are in view coordinates.
// since we are scrolled at the top, both are expected to be approximately
// the same.
ok(checkValue(x, mousePos.x), "x is in range");
ok(checkValue(y, mousePos.y), "y is in range");
ok(checkValue(clientX, mousePos.x), "clientX is in range");
ok(checkValue(clientY, mousePos.y), "clientY is in range");
// we give document-relative coordinates here.
let offset = editor.getOffsetAtLocation(x, y);
ok(checkValue(offset, expectedOffset), "character offset is correct");
let rect = {x: x, y: y};
let viewCoords = editor.convertCoordinates(rect, "document", "view");
ok(checkValue(viewCoords.x, clientX), "viewCoords.x is in range");
ok(checkValue(viewCoords.y, clientY), "viewCoords.y is in range");
rect = {x: clientX, y: clientY};
let docCoords = editor.convertCoordinates(rect, "view", "document");
ok(checkValue(docCoords.x, x), "docCoords.x is in range");
ok(checkValue(docCoords.y, y), "docCoords.y is in range");
// we are given document-relative coordinates.
let offsetPos = editor.getLocationAtOffset(expectedOffset);
ok(checkValue(offsetPos.x, x), "offsetPos.x is in range");
ok(checkValue(offsetPos.y, y), "offsetPos.y is in range");
// Scroll the view and test again.
let topIndex = Math.round(editor.getLineCount() / 2);
editor.setTopIndex(topIndex);
expectedOffset += editor.getLineStart(topIndex);
executeSoon(doMouseMove.bind(null, testPage2));
}
function testPage2(aEvent)
{
let {event: { clientX: clientX, clientY: clientY }, x: x, y: y} = aEvent;
info("testPage2 " + aEvent.type +
" clientX " + clientX + " clientY " + clientY +
" x " + x + " y " + y);
// after page scroll document coordinates need to be different from view
// coordinates.
ok(checkValue(x, mousePos.x), "x is not different from clientX");
ok(!checkValue(y, mousePos.y), "y is different from clientY");
ok(checkValue(clientX, mousePos.x), "clientX is in range");
ok(checkValue(clientY, mousePos.y), "clientY is in range");
// we give document-relative coordinates here.
let offset = editor.getOffsetAtLocation(x, y);
ok(checkValue(offset, expectedOffset), "character offset is correct");
let rect = {x: x, y: y};
let viewCoords = editor.convertCoordinates(rect, "document", "view");
ok(checkValue(viewCoords.x, clientX), "viewCoords.x is in range");
ok(checkValue(viewCoords.y, clientY), "viewCoords.y is in range");
rect = {x: clientX, y: clientY};
let docCoords = editor.convertCoordinates(rect, "view", "document");
ok(checkValue(docCoords.x, x), "docCoords.x is in range");
ok(checkValue(docCoords.y, y), "docCoords.y is in range");
// we are given document-relative coordinates.
let offsetPos = editor.getLocationAtOffset(expectedOffset);
ok(checkValue(offsetPos.x, x), "offsetPos.x is in range");
ok(checkValue(offsetPos.y, y), "offsetPos.y is in range");
executeSoon(testEnd);
}
function testEnd()
{
if (editor) {
editor.destroy();
}
if (testWin) {
testWin.close();
}
waitForFocus(finish, window);
}
openSourceEditorWindow(editorLoaded);
}

View File

@ -113,3 +113,70 @@ waitForSelection.__defineGetter__("_monotonicCounter", function () {
return waitForSelection.__monotonicCounter++;
});
/**
* Open a new window with a source editor inside.
*
* @param function aCallback
* The function you want invoked once the editor is loaded. The function
* is given two arguments: editor instance and the window object.
* @param object [aOptions]
* The options object to pass to the SourceEditor.init() method.
*/
function openSourceEditorWindow(aCallback, aOptions) {
const windowUrl = "data:text/xml;charset=UTF-8,<?xml version='1.0'?>" +
"<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
" title='Test for Source Editor' width='600' height='500'><box flex='1'/></window>";
const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
let editor = null;
let testWin = Services.ww.openWindow(null, windowUrl, "_blank",
windowFeatures, null);
testWin.addEventListener("load", function onWindowLoad() {
testWin.removeEventListener("load", onWindowLoad, false);
waitForFocus(initEditor, testWin);
}, false);
function initEditor()
{
let tempScope = {};
Cu.import("resource:///modules/source-editor.jsm", tempScope);
let box = testWin.document.querySelector("box");
editor = new tempScope.SourceEditor();
editor.init(box, aOptions || {}, editorLoaded);
}
function editorLoaded()
{
editor.focus();
waitForFocus(aCallback.bind(null, editor, testWin), testWin);
}
}
/**
* Get text needed to fill the editor view.
*
* @param object aEditor
* The SourceEditor instance you work with.
* @param number aPages
* The number of pages you want filled with lines.
* @return string
* The string you can insert into the editor so you fill the desired
* number of pages.
*/
function fillEditor(aEditor, aPages) {
let view = aEditor._view;
let model = aEditor._model;
let lineHeight = view.getLineHeight();
let editorHeight = view.getClientArea().height;
let linesPerPage = Math.floor(editorHeight / lineHeight);
let totalLines = aPages * linesPerPage;
let text = "";
for (let i = 0; i < totalLines; i++) {
text += "l" + i + " lorem ipsum dolor sit amet. lipsum foobaris bazbaz,\n";
}
return text;
}

View File

@ -32,15 +32,24 @@ this.EXPORTED_SYMBOLS = ["NetworkPanel"];
/**
* Creates a new NetworkPanel.
*
* @constructor
* @param nsIDOMNode aParent
* Parent node to append the created panel to.
* @param object aHttpActivity
* HttpActivity to display in the panel.
* @param object aWebConsoleFrame
* The parent WebConsoleFrame object that owns this network panel
* instance.
*/
this.NetworkPanel = function NetworkPanel(aParent, aHttpActivity)
this.NetworkPanel =
function NetworkPanel(aParent, aHttpActivity, aWebConsoleFrame)
{
let doc = aParent.ownerDocument;
this.httpActivity = aHttpActivity;
this.webconsole = aWebConsoleFrame;
this._longStringClick = this._longStringClick.bind(this);
this._responseBodyFetch = this._responseBodyFetch.bind(this);
this._requestBodyFetch = this._requestBodyFetch.bind(this);
// Create the underlaying panel
this.panel = createElement(doc, "panel", {
@ -67,6 +76,7 @@ this.NetworkPanel = function NetworkPanel(aParent, aHttpActivity)
self.panel = null;
self.iframe = null;
self.httpActivity = null;
self.webconsole = null;
if (self.linkNode) {
self.linkNode._panelOpen = false;
@ -217,28 +227,8 @@ NetworkPanel.prototype =
*/
get _isResponseBodyTextData()
{
let contentType = this.contentType;
if (!contentType)
return false;
if (contentType.indexOf("text/") == 0) {
return true;
}
switch (NetworkHelper.mimeCategoryMap[contentType]) {
case "txt":
case "js":
case "json":
case "css":
case "html":
case "svg":
case "xml":
return true;
default:
return false;
}
return this.contentType ?
NetworkHelper.isTextMimeType(this.contentType) : false;
},
/**
@ -262,20 +252,27 @@ NetworkPanel.prototype =
get _isRequestBodyFormData()
{
let requestBody = this.httpActivity.request.postData.text;
if (typeof requestBody == "object" && requestBody.type == "longString") {
requestBody = requestBody.initial;
}
return this._fromDataRegExp.test(requestBody);
},
/**
* Appends the node with id=aId by the text aValue.
*
* @private
* @param string aId
* @param string aValue
* @returns void
* @return nsIDOMElement
* The DOM element with id=aId.
*/
_appendTextNode: function NP_appendTextNode(aId, aValue)
_appendTextNode: function NP__appendTextNode(aId, aValue)
{
let textNode = this.document.createTextNode(aValue);
this.document.getElementById(aId).appendChild(textNode);
let elem = this.document.getElementById(aId);
elem.appendChild(textNode);
return elem;
},
/**
@ -302,11 +299,17 @@ NetworkPanel.prototype =
aList.forEach(function(aItem) {
let name = aItem.name;
let value = aItem.value;
if (aIgnoreCookie && name == "Cookie") {
if (aIgnoreCookie && (name == "Cookie" || name == "Set-Cookie")) {
return;
}
let value = aItem.value;
let longString = null;
if (typeof value == "object" && value.type == "longString") {
value = value.initial;
longString = true;
}
/**
* The following code creates the HTML:
* <tr>
@ -327,21 +330,66 @@ NetworkPanel.prototype =
let td = doc.createElement("td");
td.setAttribute("class", "property-value");
td.appendChild(textNode);
if (longString) {
let a = doc.createElement("a");
a.href = "#";
a.className = "longStringEllipsis";
a.addEventListener("mousedown", this._longStringClick.bind(this, aItem));
a.textContent = l10n.getStr("longStringEllipsis");
td.appendChild(a);
}
row.appendChild(td);
parent.appendChild(row);
});
}.bind(this));
},
/**
* The click event handler for the ellipsis which allows the user to retrieve
* the full header value.
*
* @private
* @param object aHeader
* The header object with the |name| and |value| properties.
* @param nsIDOMEvent aEvent
* The DOM click event object.
*/
_longStringClick: function NP__longStringClick(aHeader, aEvent)
{
aEvent.preventDefault();
let longString = this.webconsole.webConsoleClient.longString(aHeader.value);
longString.substring(longString.initial.length, longString.length,
function NP__onLongStringSubstring(aResponse)
{
if (aResponse.error) {
Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
return;
}
aHeader.value = aHeader.value.initial + aResponse.substring;
let textNode = aEvent.target.previousSibling;
textNode.textContent += aResponse.substring;
textNode.parentNode.removeChild(aEvent.target);
});
},
/**
* Displays the node with id=aId.
*
* @private
* @param string aId
* @returns void
* @return nsIDOMElement
* The element with id=aId.
*/
_displayNode: function NP_displayNode(aId)
_displayNode: function NP__displayNode(aId)
{
this.document.getElementById(aId).style.display = "block";
let elem = this.document.getElementById(aId);
elem.style.display = "block";
},
/**
@ -453,7 +501,12 @@ NetworkPanel.prototype =
this._format("durationMS", [deltaDuration]));
this._displayNode("responseContainer");
this._appendList("responseHeadersContent", response.headers);
this._appendList("responseHeadersContent", response.headers, true);
if (response.cookies.length > 0) {
this._displayNode("responseCookie");
this._appendList("responseCookieContent", response.cookies);
}
},
/**
@ -469,6 +522,7 @@ NetworkPanel.prototype =
let self = this;
let timing = this.httpActivity.timings;
let request = this.httpActivity.request;
let response = this.httpActivity.response;
let cached = "";
if (this._isResponseCached) {
@ -477,7 +531,15 @@ NetworkPanel.prototype =
let imageNode = this.document.getElementById("responseImage" +
cached + "Node");
imageNode.setAttribute("src", request.url);
let text = response.content.text;
if (typeof text == "object" && text.type == "longString") {
this._showResponseBodyFetchLink();
}
else {
imageNode.setAttribute("src",
"data:" + this.contentType + ";base64," + text);
}
// This function is called to set the imageInfo.
function setImageInfo() {
@ -520,8 +582,68 @@ NetworkPanel.prototype =
this._format("durationMS", [timing.receive]));
this._displayNode("responseBody" + cached);
this._appendTextNode("responseBody" + cached + "Content",
response.content.text);
let text = response.content.text;
if (typeof text == "object") {
text = text.initial;
this._showResponseBodyFetchLink();
}
this._appendTextNode("responseBody" + cached + "Content", text);
},
/**
* Show the "fetch response body" link.
* @private
*/
_showResponseBodyFetchLink: function NP__showResponseBodyFetchLink()
{
let content = this.httpActivity.response.content;
let elem = this._appendTextNode("responseBodyFetchLink",
this._format("fetchRemainingResponseContentLink",
[content.text.length - content.text.initial.length]));
elem.style.display = "block";
elem.addEventListener("mousedown", this._responseBodyFetch);
},
/**
* Click event handler for the link that allows users to fetch the remaining
* response body.
*
* @private
* @param nsIDOMEvent aEvent
*/
_responseBodyFetch: function NP__responseBodyFetch(aEvent)
{
aEvent.target.style.display = "none";
aEvent.target.removeEventListener("mousedown", this._responseBodyFetch);
let content = this.httpActivity.response.content;
let longString = this.webconsole.webConsoleClient.longString(content.text);
longString.substring(longString.initial.length, longString.length,
function NP__onLongStringSubstring(aResponse)
{
if (aResponse.error) {
Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
return;
}
content.text = content.text.initial + aResponse.substring;
let cached = this._isResponseCached ? "Cached" : "";
if (this._responseIsImage) {
let imageNode = this.document.getElementById("responseImage" +
cached + "Node");
imageNode.src =
"data:" + this.contentType + ";base64," + content.text;
}
else {
this._appendTextNode("responseBody" + cached + "Content",
aResponse.substring);
}
}.bind(this));
},
/**
@ -584,13 +706,7 @@ NetworkPanel.prototype =
case this._DISPLAYED_REQUEST_HEADER:
// Process the request body if there is one.
if (!this.httpActivity.discardRequestBody && request.postData.text) {
// Check if we send some form data. If so, display the form data special.
if (this._isRequestBodyFormData) {
this._displayRequestForm();
}
else {
this._displayRequestBody();
}
this._updateRequestBody();
this._state = this._DISPLAYED_REQUEST_BODY;
}
// FALL THROUGH
@ -632,8 +748,62 @@ NetworkPanel.prototype =
if (this._onUpdate) {
this._onUpdate();
}
}
}
},
/**
* Update the panel to hold the current information we have about the request
* body.
* @private
*/
_updateRequestBody: function NP__updateRequestBody()
{
let postData = this.httpActivity.request.postData;
if (typeof postData.text == "object" && postData.text.type == "longString") {
let elem = this._appendTextNode("requestBodyFetchLink",
this._format("fetchRemainingRequestContentLink",
[postData.text.length - postData.text.initial.length]));
elem.style.display = "block";
elem.addEventListener("mousedown", this._requestBodyFetch);
return;
}
// Check if we send some form data. If so, display the form data special.
if (this._isRequestBodyFormData) {
this._displayRequestForm();
}
else {
this._displayRequestBody();
}
},
/**
* Click event handler for the link that allows users to fetch the remaining
* request body.
*
* @private
* @param nsIDOMEvent aEvent
*/
_requestBodyFetch: function NP__requestBodyFetch(aEvent)
{
aEvent.target.style.display = "none";
aEvent.target.removeEventListener("mousedown", this._responseBodyFetch);
let postData = this.httpActivity.request.postData;
let longString = this.webconsole.webConsoleClient.longString(postData.text);
longString.substring(longString.initial.length, longString.length,
function NP__onLongStringSubstring(aResponse)
{
if (aResponse.error) {
Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
return;
}
postData.text = postData.text.initial + aResponse.substring;
this._updateRequestBody();
}.bind(this));
},
};
/**
* Creates a DOMNode and sets all the attributes of aAttributes on the created

View File

@ -20,19 +20,19 @@
<table id="header">
<tr>
<th class="property-name"
scope="row">&networkPanel.requestURL;:</th>
scope="row">&networkPanel.requestURLColon;</th>
<td class="property-value"
id="headUrl"></td>
</tr>
<tr>
<th class="property-name"
scope="row">&networkPanel.requestMethod;:</th>
scope="row">&networkPanel.requestMethodColon;</th>
<td class="property-value"
id="headMethod"></td>
</tr>
<tr>
<th class="property-name"
scope="row">&networkPanel.statusCode;:</th>
scope="row">&networkPanel.statusCodeColon;</th>
<td class="property-value"
id="headStatus"></td>
</tr>
@ -58,6 +58,7 @@
<h1>&networkPanel.requestFormData;</h1>
<table class="property-table" id="requestFormDataContent"></table>
</div>
<p id="requestBodyFetchLink" style="display:none"></p>
</div>
<div class="group" id="responseContainer" style="display:none">
@ -67,6 +68,11 @@
</h1>
<table class="property-table" id="responseHeadersContent"></table>
<div id="responseCookie" style="display:none">
<h1>&networkPanel.responseCookie;</h1>
<table class="property-table" id="responseCookieContent"></table>
</div>
<div id="responseBody" style="display:none">
<h1>
&networkPanel.responseBody;
@ -112,6 +118,7 @@
<img id="responseImageCachedNode" />
</div>
</div>
<p id="responseBodyFetchLink" style="display:none"></p>
</div>
</body>
</html>

View File

@ -147,6 +147,10 @@ PropertyTreeView.prototype = {
let val = aItem[aProp];
if (val && val.actor) {
this._objectActors.push(val.actor);
if (typeof val.displayString == "object" &&
val.displayString.type == "longString") {
this._objectActors.push(val.displayString.actor);
}
}
}, this);
}

View File

@ -118,6 +118,8 @@ MOCHITEST_BROWSER_FILES = \
browser_output_breaks_after_console_dir_uninspectable.js \
browser_console_log_inspectable_object.js \
browser_bug_638949_copy_link_location.js \
browser_output_longstring_expand.js \
browser_netpanel_longstring_expand.js \
head.js \
$(NULL)
@ -136,6 +138,7 @@ MOCHITEST_BROWSER_FILES += \
test-filter.html \
test-observe-http-ajax.html \
test-data.json \
test-data.json^headers^ \
test-property-provider.html \
test-error.html \
test-duplicate-error.html \

View File

@ -0,0 +1,309 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Tests that the network panel works with LongStringActors.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
const TEST_IMG_BASE64 =
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAVRJREFU" +
"OI2lk7FLw0AUxr+YpC1CBqcMWfsvCCLdXFzqEJCgDl1EQRGxg9AhSBEJONhFhG52UCuFDjq5dxD8" +
"FwoO0qGDOBQkl7vLOeWa2EQDffDBvTu+373Hu1OEEJgntGgxGD6J+7fLXKbt5VNUyhsKAChRBQcP" +
"FVFeWskFGH694mZroCQqCLlAwPxcgJBP254CmAD5B7C7dgHLMLF3uzoL4DQEod+Z5sP1FizDxGgy" +
"BqfhLID9AahX29J89bwPFgMsSEAQglAf9WobhPpScbPXr4FQHyzIADTsDizDRMPuIOC+zEeTMZo9" +
"BwH3EfAMACccbtfGaDKGZZg423yUZrdrg3EqxQlPr0BTdTR7joREN2uqnlBmCwW1hIJagtev4f3z" +
"A16/JvfiigMSYyzqJXlw/XKUyOORMUaBor6YavgdjKa8xGOnidadmwtwsnMu18q83/kHSou+bFND" +
"Dr4AAAAASUVORK5CYII=";
let testDriver;
function test() {
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testNetworkPanel);
}, true);
}
function testNetworkPanel() {
testDriver = testGen();
testDriver.next();
}
function checkIsVisible(aPanel, aList) {
for (let id in aList) {
let node = aPanel.document.getElementById(id);
let isVisible = aList[id];
is(node.style.display, (isVisible ? "block" : "none"), id + " isVisible=" + isVisible);
}
}
function checkNodeContent(aPanel, aId, aContent) {
let node = aPanel.document.getElementById(aId);
if (node == null) {
ok(false, "Tried to access node " + aId + " that doesn't exist!");
}
else if (node.textContent.indexOf(aContent) != -1) {
ok(true, "checking content of " + aId);
}
else {
ok(false, "Got false value for " + aId + ": " + node.textContent + " doesn't have " + aContent);
}
}
function checkNodeKeyValue(aPanel, aId, aKey, aValue) {
let node = aPanel.document.getElementById(aId);
let headers = node.querySelectorAll("th");
for (let i = 0; i < headers.length; i++) {
if (headers[i].textContent == (aKey + ":")) {
is(headers[i].nextElementSibling.textContent, aValue,
"checking content of " + aId + " for key " + aKey);
return;
}
}
ok(false, "content check failed for " + aId + ", key " + aKey);
}
function testGen() {
let hud = HUDService.getHudByWindow(content);
let filterBox = hud.ui.filterBox;
let headerValue = (new Array(456)).join("fooz bar");
let headerValueGrip = {
type: "longString",
initial: headerValue.substr(0, 123),
length: headerValue.length,
actor: "faktor",
_fullString: headerValue,
};
let imageContentGrip = {
type: "longString",
initial: TEST_IMG_BASE64.substr(0, 143),
length: TEST_IMG_BASE64.length,
actor: "faktor2",
_fullString: TEST_IMG_BASE64,
};
let postDataValue = (new Array(123)).join("post me");
let postDataGrip = {
type: "longString",
initial: postDataValue.substr(0, 172),
length: postDataValue.length,
actor: "faktor3",
_fullString: postDataValue,
};
let httpActivity = {
updates: ["responseContent", "eventTimings"],
discardRequestBody: false,
discardResponseBody: false,
startedDateTime: (new Date()).toISOString(),
request: {
url: TEST_IMG,
method: "GET",
cookies: [],
headers: [
{ name: "foo", value: "bar" },
{ name: "loongstring", value: headerValueGrip },
],
postData: { text: postDataGrip },
},
response: {
httpVersion: "HTTP/3.14",
status: 2012,
statusText: "ddahl likes tacos :)",
headers: [
{ name: "Content-Type", value: "image/png" },
],
content: { mimeType: "image/png", text: imageContentGrip },
cookies: [],
},
timings: { wait: 15, receive: 23 },
};
let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
is(filterBox._netPanel, networkPanel,
"Network panel stored on the anchor object");
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
info("test 1: check if a header value is expandable");
checkIsVisible(networkPanel, {
requestCookie: false,
requestFormData: false,
requestBody: false,
requestBodyFetchLink: true,
responseContainer: true,
responseBody: false,
responseNoBody: false,
responseImage: true,
responseImageCached: false,
responseBodyFetchLink: true,
});
checkNodeKeyValue(networkPanel, "requestHeadersContent", "foo", "bar");
checkNodeKeyValue(networkPanel, "requestHeadersContent", "loongstring",
headerValueGrip.initial + "[\u2026]");
let webConsoleClient = networkPanel.webconsole.webConsoleClient;
let longStringFn = webConsoleClient.longString;
let expectedGrip = headerValueGrip;
function longStringClientProvider(aLongString)
{
is(aLongString, expectedGrip,
"longString grip is correct");
return {
initial: expectedGrip.initial,
length: expectedGrip.length,
substring: function(aStart, aEnd, aCallback) {
is(aStart, expectedGrip.initial.length,
"substring start is correct");
is(aEnd, expectedGrip.length,
"substring end is correct");
executeSoon(function() {
aCallback({
substring: expectedGrip._fullString.substring(aStart, aEnd),
});
executeSoon(function() {
testDriver.next();
});
});
},
};
}
webConsoleClient.longString = longStringClientProvider;
let clickable = networkPanel.document
.querySelector("#requestHeadersContent .longStringEllipsis");
ok(clickable, "long string ellipsis is shown");
EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
networkPanel.document.defaultView);
yield;
clickable = networkPanel.document
.querySelector("#requestHeadersContent .longStringEllipsis");
ok(!clickable, "long string ellipsis is not shown");
checkNodeKeyValue(networkPanel, "requestHeadersContent", "loongstring",
expectedGrip._fullString);
info("test 2: check that response body image fetching works");
expectedGrip = imageContentGrip;
let imgNode = networkPanel.document.getElementById("responseImageNode");
ok(!imgNode.getAttribute("src"), "no image is displayed");
clickable = networkPanel.document.querySelector("#responseBodyFetchLink");
EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
networkPanel.document.defaultView);
yield;
imgNode = networkPanel.document.getElementById("responseImageNode");
is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64,
"displayed image is correct");
is(clickable.style.display, "none", "#responseBodyFetchLink is not visible");
info("test 3: expand the request body");
expectedGrip = postDataGrip;
clickable = networkPanel.document.querySelector("#requestBodyFetchLink");
EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
networkPanel.document.defaultView);
yield;
is(clickable.style.display, "none", "#requestBodyFetchLink is not visible");
checkIsVisible(networkPanel, {
requestBody: true,
requestBodyFetchLink: false,
});
checkNodeContent(networkPanel, "requestBodyContent", expectedGrip._fullString);
webConsoleClient.longString = longStringFn;
networkPanel.panel.hidePopup();
info("test 4: reponse body long text");
httpActivity.response.content.mimeType = "text/plain";
httpActivity.response.headers[0].value = "text/plain";
expectedGrip = imageContentGrip;
// Reset response.content.text to avoid caching of the full string.
httpActivity.response.content.text = expectedGrip;
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
is(filterBox._netPanel, networkPanel,
"Network panel stored on httpActivity object");
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
checkIsVisible(networkPanel, {
requestCookie: false,
requestFormData: false,
requestBody: true,
requestBodyFetchLink: false,
responseContainer: true,
responseBody: true,
responseNoBody: false,
responseImage: false,
responseImageCached: false,
responseBodyFetchLink: true,
});
checkNodeContent(networkPanel, "responseBodyContent", expectedGrip.initial);
webConsoleClient.longString = longStringClientProvider;
clickable = networkPanel.document.querySelector("#responseBodyFetchLink");
EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
networkPanel.document.defaultView);
yield;
webConsoleClient.longString = longStringFn;
is(clickable.style.display, "none", "#responseBodyFetchLink is not visible");
checkNodeContent(networkPanel, "responseBodyContent", expectedGrip._fullString);
networkPanel.panel.hidePopup();
// All done!
testDriver = null;
executeSoon(finishTest);
yield;
}

View File

@ -0,0 +1,153 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that long strings can be expanded in the console output.
function test()
{
waitForExplicitFinish();
let tempScope = {};
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
let DebuggerServer = tempScope.DebuggerServer;
let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a") +
"foobar";
let initialString =
longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
addTab("data:text/html;charset=utf8,test for bug 787981 - check that long strings can be expanded in the output.");
let hud = null;
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
openConsole(null, performTest);
}, true);
function performTest(aHud)
{
hud = aHud;
hud.jsterm.clearOutput(true);
hud.jsterm.execute("console.log('bazbaz', '" + longString +"', 'boom')");
waitForSuccess(waitForConsoleLog);
}
let waitForConsoleLog = {
name: "console.log output shown",
validatorFn: function()
{
return hud.outputNode.querySelector(".webconsole-msg-console");
},
successFn: function()
{
let msg = hud.outputNode.querySelector(".webconsole-msg-console");
is(msg.textContent.indexOf("foobar"), -1,
"foobar is not shown");
isnot(msg.textContent.indexOf("bazbaz"), -1,
"bazbaz is shown");
isnot(msg.textContent.indexOf("boom"), -1,
"boom is shown");
isnot(msg.textContent.indexOf(initialString), -1,
"initial string is shown");
let clickable = msg.querySelector(".longStringEllipsis");
ok(clickable, "long string ellipsis is shown");
scrollToVisible(clickable);
executeSoon(function() {
EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
waitForSuccess(waitForFullString);
});
},
failureFn: finishTest,
};
let waitForFullString = {
name: "full string shown",
validatorFn: function()
{
let msg = hud.outputNode.querySelector(".webconsole-msg-log");
return msg.textContent.indexOf(longString) > -1;
},
successFn: function()
{
let msg = hud.outputNode.querySelector(".webconsole-msg-log");
isnot(msg.textContent.indexOf("bazbaz"), -1,
"bazbaz is shown");
isnot(msg.textContent.indexOf("boom"), -1,
"boom is shown");
let clickable = msg.querySelector(".longStringEllipsis");
ok(!clickable, "long string ellipsis is not shown");
executeSoon(function() {
hud.jsterm.clearOutput(true);
hud.jsterm.execute("'" + longString +"'");
waitForSuccess(waitForExecute);
});
},
failureFn: finishTest,
};
let waitForExecute = {
name: "execute() output shown",
validatorFn: function()
{
return hud.outputNode.querySelector(".webconsole-msg-output");
},
successFn: function()
{
let msg = hud.outputNode.querySelector(".webconsole-msg-output");
isnot(msg.textContent.indexOf(initialString), -1,
"initial string is shown");
is(msg.textContent.indexOf(longString), -1,
"full string is not shown");
let clickable = msg.querySelector(".longStringEllipsis");
ok(clickable, "long string ellipsis is shown");
scrollToVisible(clickable);
executeSoon(function() {
EventUtils.synthesizeMouse(clickable, 3, 4, {}, hud.iframeWindow);
waitForSuccess(waitForFullStringAfterExecute);
});
},
failureFn: finishTest,
};
let waitForFullStringAfterExecute = {
name: "full string shown again",
validatorFn: function()
{
let msg = hud.outputNode.querySelector(".webconsole-msg-output");
return msg.textContent.indexOf(longString) > -1;
},
successFn: function()
{
let msg = hud.outputNode.querySelector(".webconsole-msg-output");
let clickable = msg.querySelector(".longStringEllipsis");
ok(!clickable, "long string ellipsis is not shown");
executeSoon(finishTest);
},
failureFn: finishTest,
};
function scrollToVisible(aNode)
{
let richListBoxNode = aNode.parentNode;
while (richListBoxNode.tagName != "richlistbox") {
richListBoxNode = richListBoxNode.parentNode;
}
let boxObject = richListBoxNode.scrollBoxObject;
let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject);
nsIScrollBoxObject.ensureElementIsVisible(aNode);
}
}

View File

@ -15,6 +15,13 @@ let pos = -1;
let dateNow = Date.now();
let tempScope = {};
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
let longString = (new Array(tempScope.DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
let initialString = longString.substring(0,
tempScope.DebuggerServer.LONG_STRING_INITIAL_LENGTH);
let inputValues = [
// [showsPropertyPanel?, input value, expected output format,
// print() output, console output, optional console API test]
@ -87,8 +94,16 @@ let inputValues = [
[true, "({a:'b', c:'d', e:1, f:'2'})", '({a:"b", c:"d", e:1, f:"2"})',
"[object Object",
'({a:"b", c:"d", e:1, f:"2"})'],
// 18
[false, "'" + longString + "'",
'"' + initialString + "\"[\u2026]", initialString],
];
longString = null;
initialString = null;
tempScope = null;
let eventHandlers = [];
let popupShown = [];
let HUD;

View File

@ -9,6 +9,16 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
const TEST_ENCODING_ISO_8859_1 = "http://example.com/browser/browser/devtools/webconsole/test/test-encoding-ISO-8859-1.html";
const TEST_IMG_BASE64 =
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAVRJREFU" +
"OI2lk7FLw0AUxr+YpC1CBqcMWfsvCCLdXFzqEJCgDl1EQRGxg9AhSBEJONhFhG52UCuFDjq5dxD8" +
"FwoO0qGDOBQkl7vLOeWa2EQDffDBvTu+373Hu1OEEJgntGgxGD6J+7fLXKbt5VNUyhsKAChRBQcP" +
"FVFeWskFGH694mZroCQqCLlAwPxcgJBP254CmAD5B7C7dgHLMLF3uzoL4DQEod+Z5sP1FizDxGgy" +
"BqfhLID9AahX29J89bwPFgMsSEAQglAf9WobhPpScbPXr4FQHyzIADTsDizDRMPuIOC+zEeTMZo9" +
"BwH3EfAMACccbtfGaDKGZZg423yUZrdrg3EqxQlPr0BTdTR7joREN2uqnlBmCwW1hIJagtev4f3z" +
"A16/JvfiigMSYyzqJXlw/XKUyOORMUaBor6YavgdjKa8xGOnidadmwtwsnMu18q83/kHSou+bFND" +
"Dr4AAAAASUVORK5CYII=";
let testDriver;
function test() {
@ -80,6 +90,7 @@ function testGen() {
response: {
headers: [],
content: {},
cookies: [],
},
timings: {},
};
@ -223,6 +234,7 @@ function testGen() {
requestFormData: false,
requestCookie: true,
responseContainer: true,
responseCookie: false,
responseBody: true,
responseNoBody: false,
responseImage: false,
@ -236,10 +248,49 @@ function testGen() {
networkPanel.panel.hidePopup();
// Third run: Test for response cookies.
info("test 6b: response cookies");
httpActivity.response.cookies.push(
{ name: "foobar", value: "boom" },
{ name: "foobaz", value: "omg" }
);
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
is(filterBox._netPanel, networkPanel,
"Network panel stored on httpActivity object");
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
checkIsVisible(networkPanel, {
requestBody: true,
requestFormData: false,
requestCookie: true,
responseContainer: true,
responseCookie: true,
responseBody: true,
responseNoBody: false,
responseImage: false,
responseImageCached: false,
responseBodyFetchLink: false,
});
checkNodeKeyValue(networkPanel, "responseCookieContent", "foobar", "boom");
checkNodeKeyValue(networkPanel, "responseCookieContent", "foobaz", "omg");
networkPanel.panel.hidePopup();
// Check image request.
info("test 7: image request");
httpActivity.response.headers[1].value = "image/png";
httpActivity.response.content.mimeType = "image/png";
httpActivity.response.content.text = TEST_IMG_BASE64;
httpActivity.request.url = TEST_IMG;
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
@ -260,11 +311,13 @@ function testGen() {
responseBody: false,
responseNoBody: false,
responseImage: true,
responseImageCached: false
responseImageCached: false,
responseBodyFetchLink: false,
});
let imgNode = networkPanel.document.getElementById("responseImageNode");
is(imgNode.getAttribute("src"), TEST_IMG, "Displayed image is correct");
is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64,
"Displayed image is correct");
function checkImageResponseInfo() {
checkNodeContent(networkPanel, "responseImageInfo", "2ms");
@ -272,20 +325,13 @@ function testGen() {
}
// Check if the image is loaded already.
if (imgNode.width == 0) {
imgNode.addEventListener("load", function onLoad() {
imgNode.removeEventListener("load", onLoad, false);
checkImageResponseInfo();
networkPanel.panel.hidePopup();
testDriver.next();
}, false);
// Wait until the image is loaded.
yield;
}
else {
imgNode.addEventListener("load", function onLoad() {
imgNode.removeEventListener("load", onLoad, false);
checkImageResponseInfo();
networkPanel.panel.hidePopup();
}
testDriver.next();
}, false);
yield;
// Check cached image request.
info("test 8: cached image request");
@ -315,7 +361,8 @@ function testGen() {
});
let imgNode = networkPanel.document.getElementById("responseImageCachedNode");
is(imgNode.getAttribute("src"), TEST_IMG, "Displayed image is correct");
is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64,
"Displayed image is correct");
networkPanel.panel.hidePopup();

View File

@ -0,0 +1 @@
Content-Type: application/json

View File

@ -1073,6 +1073,14 @@ WebConsoleFrame.prototype = {
clipboardArray.push(WebConsoleUtils.objectActorGripToString(aValue));
if (aValue && typeof aValue == "object" && aValue.actor) {
objectActors.push(aValue.actor);
let displayStringIsLong = typeof aValue.displayString == "object" &&
aValue.displayString.type == "longString";
if (aValue.type == "longString" || displayStringIsLong) {
clipboardArray.push(l10n.getStr("longStringEllipsis"));
}
if (displayStringIsLong) {
objectActors.push(aValue.displayString.actor);
}
}
}, this);
clipboardText = clipboardArray.join(" ");
@ -1711,7 +1719,7 @@ WebConsoleFrame.prototype = {
aNode._panelOpen = true;
}.bind(this);
let netPanel = new NetworkPanel(this.popupset, aHttpActivity);
let netPanel = new NetworkPanel(this.popupset, aHttpActivity, this);
netPanel.linkNode = aNode;
if (!actor) {
@ -2360,6 +2368,28 @@ WebConsoleFrame.prototype = {
if (aItem && typeof aItem != "object" || !aItem.inspectable) {
aContainer.appendChild(this.document.createTextNode(text));
let longString = null;
if (aItem.type == "longString") {
longString = aItem;
}
else if (!aItem.inspectable &&
typeof aItem.displayString == "object" &&
aItem.displayString.type == "longString") {
longString = aItem.displayString;
}
if (longString) {
let ellipsis = this.document.createElement("description");
ellipsis.classList.add("hud-clickable");
ellipsis.classList.add("longStringEllipsis");
ellipsis.textContent = l10n.getStr("longStringEllipsis");
this._addMessageLinkCallback(ellipsis,
this._longStringClick.bind(this, aMessage, longString, null));
aContainer.appendChild(ellipsis);
}
return;
}
@ -2376,6 +2406,53 @@ WebConsoleFrame.prototype = {
}, this);
},
/**
* Click event handler for the ellipsis shown immediately after a long string.
* This method retrieves the full string and updates the console output to
* show it.
*
* @private
* @param nsIDOMElement aMessage
* The message element.
* @param object aActor
* The LongStringActor instance we work with.
* @param [function] aFormatter
* Optional function you can use to format the string received from the
* server, before being displayed in the console.
* @param nsIDOMElement aEllipsis
* The DOM element the user can click on to expand the string.
* @param nsIDOMEvent aEvent
* The DOM click event triggered by the user.
*/
_longStringClick:
function WCF__longStringClick(aMessage, aActor, aFormatter, aEllipsis, aEvent)
{
aEvent.preventDefault();
if (!aFormatter) {
aFormatter = function(s) s;
}
let longString = this.webConsoleClient.longString(aActor);
longString.substring(longString.initial.length, longString.length,
function WCF__onSubstring(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF__longStringClick substring failure: " +
aResponse.error);
return;
}
let node = aEllipsis.previousSibling;
node.textContent = aFormatter(longString.initial + aResponse.substring);
aEllipsis.parentNode.removeChild(aEllipsis);
if (aMessage.category == CATEGORY_WEBDEV ||
aMessage.category == CATEGORY_OUTPUT) {
aMessage.clipboardText = aMessage.textContent;
}
});
},
/**
* Creates the XUL label that displays the textual location of an incoming
* message.
@ -2832,6 +2909,41 @@ JSTerm.prototype = {
if (result && typeof result == "object" && result.actor) {
node._objectActors = [result.actor];
if (typeof result.displayString == "object" &&
result.displayString.type == "longString") {
node._objectActors.push(result.displayString.actor);
}
// Add an ellipsis to expand the short string if the object is not
// inspectable.
let longString = null;
let formatter = null;
if (result.type == "longString") {
longString = result;
if (!helperHasRawOutput) {
formatter = WebConsoleUtils.formatResultString.bind(WebConsoleUtils);
}
}
else if (!inspectable && !errorMessage &&
typeof result.displayString == "object" &&
result.displayString.type == "longString") {
longString = result.displayString;
}
if (longString) {
let body = node.querySelector(".webconsole-msg-body");
let ellipsis = this.hud.document.createElement("description");
ellipsis.classList.add("hud-clickable");
ellipsis.classList.add("longStringEllipsis");
ellipsis.textContent = l10n.getStr("longStringEllipsis");
this.hud._addMessageLinkCallback(ellipsis,
this.hud._longStringClick.bind(this.hud, node, longString, formatter));
body.appendChild(ellipsis);
node.clipboardText += " " + ellipsis.textContent;
}
}
},
@ -2849,7 +2961,8 @@ JSTerm.prototype = {
// attempt to execute the content of the inputNode
aExecuteString = aExecuteString || this.inputNode.value;
if (!aExecuteString) {
this.writeOutput("no value to execute", CATEGORY_OUTPUT, SEVERITY_LOG);
this.writeOutput(l10n.getStr("executeEmptyInput"), CATEGORY_OUTPUT,
SEVERITY_LOG);
return;
}
@ -3483,48 +3596,6 @@ JSTerm.prototype = {
this.completeNode.value = prefix + aSuffix;
},
/**
* Clear the object cache from the Web Console content instance.
*
* @param string aCacheId
* The cache ID you want to clear. Multiple objects are cached into one
* group which is given an ID.
*/
clearObjectCache: function JST_clearObjectCache(aCacheId)
{
if (this.hud) {
this.hud.owner.sendMessageToContent("JSTerm:ClearObjectCache",
{ cacheId: aCacheId });
}
},
/**
* The remote object provider allows you to retrieve a given object from
* a specific cache and have your callback invoked when the desired object is
* received from the Web Console content instance.
*
* @param string aCacheId
* Retrieve the desired object from this cache ID.
* @param string aObjectId
* The ID of the object you want.
* @param string aResultCacheId
* The ID of the cache where you want any object references to be
* stored into.
* @param function aCallback
* The function you want invoked when the desired object is retrieved.
*/
remoteObjectProvider:
function JST_remoteObjectProvider(aCacheId, aObjectId, aResultCacheId,
aCallback) {
let message = {
cacheId: aCacheId,
objectId: aObjectId,
resultCacheId: aResultCacheId,
};
this.hud.owner.sendMessageToContent("JSTerm:GetEvalObject", message, aCallback);
},
/**
* The JSTerm InspectObject remote message handler. This allows the remote
* process to open the Property Panel for a given object.

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

@ -10,9 +10,9 @@
<!ENTITY window.title "Web Console">
<!ENTITY networkPanel.requestURL "Request URL">
<!ENTITY networkPanel.requestMethod "Request Method">
<!ENTITY networkPanel.statusCode "Status Code">
<!ENTITY networkPanel.requestURLColon "Request URL:">
<!ENTITY networkPanel.requestMethodColon "Request Method:">
<!ENTITY networkPanel.statusCodeColon "Status Code:">
<!ENTITY networkPanel.requestHeaders "Request Headers">
<!ENTITY networkPanel.requestCookie "Sent Cookie">
@ -20,6 +20,7 @@
<!ENTITY networkPanel.requestFormData "Sent Form Data">
<!ENTITY networkPanel.responseHeaders "Response Headers">
<!ENTITY networkPanel.responseCookie "Received Cookie">
<!ENTITY networkPanel.responseBody "Response Body">
<!ENTITY networkPanel.responseBodyCached "Cached Data">
<!ENTITY networkPanel.responseBodyUnknownType "Unknown Content Type">

View File

@ -165,3 +165,22 @@ remoteWebConsoleSelectTabMessage=Select one of the tabs you want to attach to, o
# LOCALIZATION NOTE (listTabs.globalConsoleActor): The string displayed for the
# global console in the tabs selection.
listTabs.globalConsoleActor=*Global Console*
# LOCALIZATION NOTE (longStringEllipsis): The string displayed after a long
# string. This string is clickable such that the rest of the string is retrieved
# from the server.
longStringEllipsis=[…]
# LOCALIZATION NOTE (executeEmptyInput): This is displayed when the user tries
# to execute code, but the input is empty.
executeEmptyInput=No value to execute.
# LOCALIZATION NOTE (NetworkPanel.fetchRemainingResponseContentLink): This is
# displayed in the network panel when the response body is only partially
# available.
NetworkPanel.fetchRemainingResponseContentLink=Fetch the remaining %1$S bytes
# LOCALIZATION NOTE (NetworkPanel.fetchRemainingRequestContentLink): This is
# displayed in the network panel when the request body is only partially
# available.
NetworkPanel.fetchRemainingRequestContentLink=Fetch the request body (%1$S bytes)

View File

@ -136,7 +136,7 @@
#stackframes {
background-color: white;
min-height: 30px;
min-height: 10px;
}
.dbg-stackframe {
@ -145,7 +145,6 @@
}
.dbg-stackframe-name {
-moz-padding-end: 4px;
font-weight: 600;
}
@ -155,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;
}
@ -171,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
*/
@ -209,22 +220,14 @@
.variable {
-moz-margin-start: 1px;
-moz-margin-end: 1px;
transition: background 1s ease-in-out;
border-bottom: 1px solid #eee;
background: #fff;
}
.variable:not(:last-child) {
border-bottom: 1px dotted #ddd;
border-radius: 8px;
}
.variable:last-child {
margin-bottom: 2px;
transition: background 1s ease-in-out;
}
.variable[changed] {
transition-duration: 0.4s;
background: rgba(255, 255, 0, 0.65);
transition-duration: 0.4s;
}
.variable > .title > .name {
@ -245,14 +248,13 @@
*/
.property {
transition: background 1s ease-in-out;
background: #fff;
border-radius: 8px;
transition: background 1s ease-in-out;
}
.property[changed] {
transition-duration: 0.4s;
background: rgba(255, 255, 0, 0.65);
transition-duration: 0.4s;
}
.property > .title > .name {
@ -326,6 +328,14 @@
text-decoration: line-through;
}
/**
* Variables and properties editing
*/
#variables .element-input {
-moz-margin-start: 5px !important;
}
/**
* Variables and properties searching
*/
@ -386,30 +396,6 @@
visibility: hidden;
}
/**
* Animations
*/
#variables .details[open][animated],
#globalsearch .dbg-results-container[open][animated] {
animation-duration: 0.25s;
animation-name: showblock;
}
@keyframes showblock {
from {
opacity: 0;
transform-origin: top;
transform: scaleY(0);
}
to {
opacity: 1;
transform-origin: top;
transform: scaleY(1);
}
}
/**
* Toolbar Controls
*/

View File

@ -84,3 +84,16 @@ img#responseImageNode {
#responseImageNodeDiv {
padding: 5px;
}
#responseBodyFetchLink, #requestBodyFetchLink {
padding: 5px;
margin: 0;
cursor: pointer;
font-weight: bold;
font-size: 1.1em;
text-decoration: underline;
}
.longStringEllipsis {
margin-left: 0.6em;
}

View File

@ -138,7 +138,7 @@
#stackframes {
background-color: white;
min-height: 30px;
min-height: 10px;
}
.dbg-stackframe {
@ -147,7 +147,6 @@
}
.dbg-stackframe-name {
-moz-padding-end: 4px;
font-weight: 600;
}
@ -157,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;
}
@ -173,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
*/
@ -211,22 +222,14 @@
.variable {
-moz-margin-start: 1px;
-moz-margin-end: 1px;
transition: background 1s ease-in-out;
border-bottom: 1px solid #eee;
background: #fff;
}
.variable:not(:last-child) {
border-bottom: 1px dotted #ddd;
border-radius: 8px;
}
.variable:last-child {
margin-bottom: 2px;
transition: background 1s ease-in-out;
}
.variable[changed] {
transition-duration: 0.4s;
background: rgba(255, 255, 0, 0.65);
transition-duration: 0.4s;
}
.variable > .title > .name {
@ -247,14 +250,13 @@
*/
.property {
transition: background 1s ease-in-out;
background: #fff;
border-radius: 8px;
transition: background 1s ease-in-out;
}
.property[changed] {
transition-duration: 0.4s;
background: rgba(255, 255, 0, 0.65);
transition-duration: 0.4s;
}
.property > .title > .name {
@ -328,6 +330,14 @@
text-decoration: line-through;
}
/**
* Variables and properties editing
*/
#variables .element-input {
-moz-margin-start: 5px !important;
}
/**
* Variables and properties searching
*/
@ -386,30 +396,6 @@
visibility: hidden;
}
/**
* Animations
*/
#variables .details[open][animated],
#globalsearch .dbg-results-container[open][animated] {
animation-duration: 0.25s;
animation-name: showblock;
}
@keyframes showblock {
from {
opacity: 0;
transform-origin: top;
transform: scaleY(0);
}
to {
opacity: 1;
transform-origin: top;
transform: scaleY(1);
}
}
/**
* Toolbar Controls
*/

View File

@ -85,3 +85,16 @@ img#responseImageNode {
#responseImageNodeDiv {
padding: 5px;
}
#responseBodyFetchLink, #requestBodyFetchLink {
padding: 5px;
margin: 0;
cursor: pointer;
font-weight: bold;
font-size: 1.1em;
text-decoration: underline;
}
.longStringEllipsis {
margin-left: 0.6em;
}

View File

@ -144,7 +144,7 @@
#stackframes {
background-color: white;
min-height: 30px;
min-height: 10px;
}
.dbg-stackframe {
@ -153,7 +153,6 @@
}
.dbg-stackframe-name {
-moz-padding-end: 4px;
font-weight: 600;
}
@ -163,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;
}
@ -179,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
*/
@ -217,22 +228,14 @@
.variable {
-moz-margin-start: 1px;
-moz-margin-end: 1px;
transition: background 1s ease-in-out;
border-bottom: 1px solid #eee;
background: #fff;
}
.variable:not(:last-child) {
border-bottom: 1px dotted #ddd;
border-radius: 8px;
}
.variable:last-child {
margin-bottom: 2px;
transition: background 1s ease-in-out;
}
.variable[changed] {
transition-duration: 0.4s;
background: rgba(255, 255, 0, 0.65);
transition-duration: 0.4s;
}
.variable > .title > .name {
@ -253,14 +256,13 @@
*/
.property {
transition: background 1s ease-in-out;
background: #fff;
border-radius: 8px;
transition: background 1s ease-in-out;
}
.property[changed] {
transition-duration: 0.4s;
background: rgba(255, 255, 0, 0.65);
transition-duration: 0.4s;
}
.property > .title > .name {
@ -334,6 +336,14 @@
text-decoration: line-through;
}
/**
* Variables and properties editing
*/
#variables .element-input {
-moz-margin-start: 5px !important;
}
/**
* Variables and properties searching
*/
@ -397,30 +407,6 @@
visibility: hidden;
}
/**
* Animations
*/
#variables .details[open][animated],
#globalsearch .dbg-results-container[open][animated] {
animation-duration: 0.25s;
animation-name: showblock;
}
@keyframes showblock {
from {
opacity: 0;
transform-origin: top;
transform: scaleY(0);
}
to {
opacity: 1;
transform-origin: top;
transform: scaleY(1);
}
}
/**
* Toolbar Controls
*/

View File

@ -85,3 +85,16 @@ img#responseImageNode {
#responseImageNodeDiv {
padding: 5px;
}
#responseBodyFetchLink, #requestBodyFetchLink {
padding: 5px;
margin: 0;
cursor: pointer;
font-weight: bold;
font-size: 1.1em;
text-decoration: underline;
}
.longStringEllipsis {
margin-left: 0.6em;
}

View File

@ -12,7 +12,8 @@ const Cr = Components.results;
this.EXPORTED_SYMBOLS = ["DebuggerTransport",
"DebuggerClient",
"debuggerSocketConnect"];
"debuggerSocketConnect",
"LongStringClient"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
@ -1184,6 +1185,7 @@ function LongStringClient(aClient, aGrip) {
LongStringClient.prototype = {
get actor() { return this._grip.actor; },
get length() { return this._grip.length; },
get initial() { return this._grip.initial; },
valid: true,

View File

@ -32,7 +32,27 @@ function ThreadActor(aHooks, aGlobal)
this._hooks = {};
this._hooks = aHooks;
this.global = aGlobal;
/**
* A script cache that maps script URLs to arrays of different Debugger.Script
* instances that have the same URL. For example, when an inline <script> tag
* in a web page contains a function declaration, the JS engine creates two
* Debugger.Script objects, one for the function and one for the script tag
* as a whole. The two objects will usually have different startLine and/or
* lineCount properties. For the edge case where two scripts are contained in
* the same line we need column support.
*
* The sparse array that is mapped to each URL serves as an additional mapping
* from startLine numbers to Debugger.Script objects, facilitating retrieval
* of the scripts that contain a particular line number. For example, if a
* cache holds two scripts with the URL http://foo.com/ starting at lines 4
* and 10, then the corresponding cache will be:
* this._scripts: {
* 'http://foo.com/': [,,,,[Debugger.Script],,,,,,[Debugger.Script]]
* }
*/
this._scripts = {};
this.findGlobals = this.globalManager.findGlobals.bind(this);
this.onNewGlobal = this.globalManager.onNewGlobal.bind(this);
}
@ -489,13 +509,15 @@ ThreadActor.prototype = {
_setBreakpoint: function TA__setBreakpoint(aLocation) {
// Fetch the list of scripts in that url.
let scripts = this._scripts[aLocation.url];
// Fetch the specified script in that list.
// Fetch the outermost script in that list.
let script = null;
for (let i = aLocation.line; i >= 0; i--) {
for (let i = 0; i <= aLocation.line; i++) {
// Stop when the first script that contains this location is found.
if (scripts[i]) {
// If that first script does not contain the line specified, it's no
// good.
// good. Note that |i === scripts[i].startLine| in this case, so the
// following check makes sure we are not considering a script that does
// not include |aLocation.line|.
if (i + scripts[i].lineCount < aLocation.line) {
continue;
}
@ -525,25 +547,35 @@ ThreadActor.prototype = {
return { error: "noScript", actor: bpActor.actorID };
}
script = this._getInnermostContainer(script, aLocation.line);
bpActor.addScript(script, this);
let inner, codeFound = false;
// We need to set the breakpoint in every script that has bytecode in the
// specified line.
for (let s of this._getContainers(script, aLocation.line)) {
// The first result of the iteration is the innermost script.
if (!inner) {
inner = s;
}
let offsets = script.getLineOffsets(aLocation.line);
let codeFound = false;
for (let i = 0; i < offsets.length; i++) {
script.setBreakpoint(offsets[i], bpActor);
codeFound = true;
let offsets = s.getLineOffsets(aLocation.line);
if (offsets.length) {
bpActor.addScript(s, this);
for (let i = 0; i < offsets.length; i++) {
s.setBreakpoint(offsets[i], bpActor);
codeFound = true;
}
}
}
let actualLocation;
if (offsets.length == 0) {
// No code at that line in any script, skipping forward.
let lines = script.getAllOffsets();
if (!codeFound) {
// No code at that line in any script, skipping forward in the innermost
// script.
let lines = inner.getAllOffsets();
let oldLine = aLocation.line;
for (let line = oldLine; line < lines.length; ++line) {
if (lines[line]) {
for (let i = 0; i < lines[line].length; i++) {
script.setBreakpoint(lines[line][i], bpActor);
inner.setBreakpoint(lines[line][i], bpActor);
codeFound = true;
}
actualLocation = {
@ -560,6 +592,7 @@ ThreadActor.prototype = {
}
}
}
if (!codeFound) {
return { error: "noCodeAtLineColumn", actor: bpActor.actorID };
}
@ -568,28 +601,32 @@ ThreadActor.prototype = {
},
/**
* Get the innermost script that contains this line, by looking through child
* scripts of the supplied script.
* A recursive generator function for iterating over the scripts that contain
* the specified line, by looking through child scripts of the supplied
* script. As an example, an inline <script> tag has the top-level functions
* declared in it as its children.
*
* @param aScript Debugger.Script
* The source script.
* @param aLine number
* The line number.
*/
_getInnermostContainer: function TA__getInnermostContainer(aScript, aLine) {
_getContainers: function TA__getContainers(aScript, aLine) {
let children = aScript.getChildScripts();
if (children.length > 0) {
for (let i = 0; i < children.length; i++) {
let child = children[i];
// Stop when the first script that contains this location is found.
// Iterate over the children that contain this location.
if (child.startLine <= aLine &&
child.startLine + child.lineCount > aLine) {
return this._getInnermostContainer(child, aLine);
for (let j of this._getContainers(child, aLine)) {
yield j;
}
}
}
}
// Location not found in children, this is the innermost containing script.
return aScript;
// Include this script in the iteration, too.
yield aScript;
},
/**
@ -1718,12 +1755,26 @@ LongStringActor.prototype = {
"from": this.actorID,
"substring": this.string.substring(aRequest.start, aRequest.end)
};
}
},
/**
* Handle a request to release this LongStringActor instance.
*/
onRelease: function LSA_onRelease() {
// TODO: also check if registeredPool === threadActor.threadLifetimePool
// when the web console moves aray from manually releasing pause-scoped
// actors.
if (this.registeredPool.longStringActors) {
delete this.registeredPool.longStringActors[this.actorID];
}
this.registeredPool.removeActor(this);
return {};
},
};
LongStringActor.prototype.requestTypes = {
"substring": LongStringActor.prototype.onSubstring
"substring": LongStringActor.prototype.onSubstring,
"release": LongStringActor.prototype.onRelease
};
@ -2142,6 +2193,42 @@ function getFunctionName(aFunction) {
return name;
}
/**
* Override the toString method in order to get more meaningful script output
* for debugging the debugger.
*/
Debugger.Script.prototype.toString = function() {
let output = "";
if (this.url) {
output += this.url;
}
if (typeof this.startLine != "undefined") {
output += ":" + this.startLine;
if (this.lineCount && this.lineCount > 1) {
output += "-" + (this.startLine + this.lineCount - 1);
}
}
if (this.strictMode) {
output += ":strict";
}
return output;
};
/**
* Helper property for quickly getting to the line number a stack frame is
* currently paused at.
*/
Object.defineProperty(Debugger.Frame.prototype, "line", {
configurable: true,
get: function() {
if (this.script) {
return this.script.getOffsetLine(this.offset);
} else {
return null;
}
}
});
/**
* Creates an actor for handling chrome debugging. ChromeDebuggerActor is a

View File

@ -679,6 +679,29 @@ DebuggerServerConnection.prototype = {
this._extraPools = null;
DebuggerServer._connectionClosed(this);
},
/*
* Debugging helper for inspecting the state of the actor pools.
*/
_dumpPools: function DSC_dumpPools() {
dumpn("/-------------------- dumping pools:");
if (this._actorPool) {
dumpn("--------------------- actorPool actors: " +
uneval(Object.keys(this._actorPool._actors)));
}
for each (let pool in this._extraPools)
dumpn("--------------------- extraPool actors: " +
uneval(Object.keys(pool._actors)));
},
/*
* Debugging helper for inspecting the state of an actor pool.
*/
_dumpPool: function DSC_dumpPools(aPool) {
dumpn("/-------------------- dumping pool:");
dumpn("--------------------- actorPool actors: " +
uneval(Object.keys(aPool._actors)));
}
};

View File

@ -30,16 +30,13 @@ function test_remove_breakpoint()
let done = false;
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
let path = getFilePath('test_breakpoint-09.js');
let location = { url: path, line: gDebuggee.line0 + 1};
let location = { url: path, line: gDebuggee.line0 + 2};
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
// Check that the breakpoint has properly skipped forward one line.
do_check_eq(aResponse.actualLocation.url, location.url);
do_check_eq(aResponse.actualLocation.line, location.line + 1);
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
// Check the return value.
do_check_eq(aPacket.type, "paused");
do_check_eq(aPacket.frame.where.url, path);
do_check_eq(aPacket.frame.where.line, location.line + 1);
do_check_eq(aPacket.frame.where.line, location.line);
do_check_eq(aPacket.why.type, "breakpoint");
do_check_eq(aPacket.why.actors[0], bpClient.actor);
// Check that the breakpoint worked.

View File

@ -0,0 +1,78 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that setting a breakpoint in a line with bytecodes in multiple
* scripts, sets the breakpoint in all of them (bug 793214).
*/
var gDebuggee;
var gClient;
var gThreadClient;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function () {
attachTestGlobalClientAndResume(gClient,
"test-stack",
function (aResponse, aThreadClient) {
gThreadClient = aThreadClient;
test_child_breakpoint();
});
});
do_test_pending();
}
function test_child_breakpoint()
{
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
let path = getFilePath('test_breakpoint-11.js');
let location = { url: path, line: gDebuggee.line0 + 2};
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
// actualLocation is not returned when breakpoints don't skip forward.
do_check_eq(aResponse.actualLocation, undefined);
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
// Check the return value.
do_check_eq(aPacket.type, "paused");
do_check_eq(aPacket.why.type, "breakpoint");
do_check_eq(aPacket.why.actors[0], bpClient.actor);
// Check that the breakpoint worked.
do_check_eq(gDebuggee.a, undefined);
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
// Check the return value.
do_check_eq(aPacket.type, "paused");
do_check_eq(aPacket.why.type, "breakpoint");
do_check_eq(aPacket.why.actors[0], bpClient.actor);
// Check that the breakpoint worked.
do_check_eq(gDebuggee.a.b, 1);
do_check_eq(gDebuggee.res, undefined);
// Remove the breakpoint.
bpClient.remove(function (aResponse) {
gThreadClient.resume(function () {
finishClient(gClient);
});
});
});
// Continue until the breakpoint is hit again.
gThreadClient.resume();
});
// Continue until the breakpoint is hit.
gThreadClient.resume();
});
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a = { b: 1, f: function() { return 2; } };\n" + // line0+2
"var res = a.f();\n"); // line0 + 3
}

View File

@ -7,6 +7,8 @@
function run_test()
{
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://global/content/devtools/dbg-script-actors.js");

View File

@ -4,6 +4,8 @@
function run_test()
{
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://global/content/devtools/dbg-script-actors.js");

View File

@ -44,6 +44,7 @@ tail =
[test_breakpoint-08.js]
[test_breakpoint-09.js]
[test_breakpoint-10.js]
[test_breakpoint-11.js]
[test_listscripts-01.js]
[test_objectgrips-01.js]
[test_objectgrips-02.js]

View File

@ -419,5 +419,36 @@ this.NetworkHelper =
"application/x-json": "json",
"application/json-rpc": "json",
"application/x-web-app-manifest+json": "json",
}
},
/**
* Check if the given MIME type is a text-only MIME type.
*
* @param string aMimeType
* @return boolean
*/
isTextMimeType: function NH_isTextMimeType(aMimeType)
{
if (aMimeType.indexOf("text/") == 0) {
return true;
}
if (/^application\/[a-z-]+\+xml$/.test(aMimeType)) {
return true;
}
switch (NetworkHelper.mimeCategoryMap[aMimeType]) {
case "txt":
case "js":
case "json":
case "css":
case "html":
case "svg":
case "xml":
return true;
default:
return false;
}
},
}

View File

@ -10,6 +10,11 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LongStringClient",
"resource://gre/modules/devtools/dbg-client.jsm");
this.EXPORTED_SYMBOLS = ["WebConsoleClient"];
/**
@ -25,9 +30,12 @@ this.WebConsoleClient = function WebConsoleClient(aDebuggerClient, aActor)
{
this._actor = aActor;
this._client = aDebuggerClient;
this._longStrings = {};
}
WebConsoleClient.prototype = {
_longStrings: null,
/**
* Retrieve the cached messages from the server.
*
@ -294,6 +302,25 @@ WebConsoleClient.prototype = {
this._client.request(packet, aOnResponse);
},
/**
* Return an instance of LongStringClient for the given long string grip.
*
* @param object aGrip
* The long string grip returned by the protocol.
* @return object
* The LongStringClient for the given long string grip.
*/
longString: function WCC_longString(aGrip)
{
if (aGrip.actor in this._longStrings) {
return this._longStrings[aGrip.actor];
}
let client = new LongStringClient(this._client, aGrip);
this._longStrings[aGrip.actor] = client;
return client;
},
/**
* Close the WebConsoleClient. This stops all the listeners on the server and
* detaches from the console actor.
@ -304,6 +331,7 @@ WebConsoleClient.prototype = {
close: function WCC_close(aOnResponse)
{
this.stopListeners(null, aOnResponse);
this._longStrings = null;
this._client = null;
},
};

View File

@ -589,9 +589,10 @@ this.WebConsoleUtils = {
let type = typeof(aValue);
switch (type) {
case "boolean":
case "string":
case "number":
return aValue;
case "string":
return aObjectWrapper(aValue);
case "object":
case "function":
if (aValue) {
@ -686,13 +687,28 @@ this.WebConsoleUtils = {
{
// Primitives like strings and numbers are not sent as objects.
// But null and undefined are sent as objects with the type property
// telling which type of value we have.
// telling which type of value we have. We also have long strings which are
// sent using the LongStringActor.
let type = typeof(aGrip);
if (type == "string" ||
(aGrip && type == "object" && aGrip.type == "longString")) {
let str = type == "string" ? aGrip : aGrip.initial;
if (aFormatString) {
return this.formatResultString(str);
}
return str;
}
if (aGrip && type == "object") {
if (aGrip.displayString && typeof aGrip.displayString == "object" &&
aGrip.displayString.type == "longString") {
return aGrip.displayString.initial;
}
return aGrip.displayString || aGrip.className || aGrip.type || type;
}
return type == "string" && aFormatString ?
this.formatResultString(aGrip) : aGrip + "";
return aGrip + "";
},
/**
@ -813,6 +829,10 @@ this.WebConsoleUtils = {
return val;
}
if (val.type == "longString") {
return this.formatResultString(val.initial) + "\u2026";
}
if (val.type == "function" && val.functionName) {
return "function " + val.functionName + "(" +
val.functionArguments.join(", ") + ")";
@ -821,6 +841,11 @@ this.WebConsoleUtils = {
return val.className;
}
if (val.displayString && typeof val.displayString == "object" &&
val.displayString.type == "longString") {
return val.displayString.initial;
}
return val.displayString || val.type;
},
};
@ -1915,13 +1940,18 @@ NetworkResponseListener.prototype = {
text: aData || "",
};
// TODO: Bug 787981 - use LongStringActor for strings that are too long.
response.size = response.text.length;
try {
response.mimeType = this.request.contentType;
}
catch (ex) { }
if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) {
response.encoding = "base64";
response.text = btoa(response.text);
}
if (response.mimeType && this.request.contentCharset) {
response.mimeType += "; charset=" + this.request.contentCharset;
}

View File

@ -67,11 +67,8 @@ function WebConsoleActor(aConnection, aParentActor)
this._isGlobalActor = true;
}
this._objectActorsPool = new ActorPool(this.conn);
this.conn.addActorPool(this._objectActorsPool);
this._networkEventActorsPool = new ActorPool(this.conn);
this.conn.addActorPool(this._networkEventActorsPool);
this._actorPool = new ActorPool(this.conn);
this.conn.addActorPool(this._actorPool);
this._prefs = {};
}
@ -86,22 +83,12 @@ WebConsoleActor.prototype =
_isGlobalActor: false,
/**
* Actor pool for all of the object actors for objects we send to the client.
* Actor pool for all of the actors we send to the client.
* @private
* @type object
* @see ActorPool
* @see WebConsoleObjectActor
* @see this.objectGrip()
*/
_objectActorsPool: null,
/**
* Actor pool for all of the network event actors.
* @private
* @type object
* @see NetworkEventActor
*/
_networkEventActorsPool: null,
_actorPool: null,
/**
* Web Console-related preferences.
@ -212,10 +199,8 @@ WebConsoleActor.prototype =
this.consoleProgressListener.destroy();
this.consoleProgressListener = null;
}
this.conn.removeActorPool(this._objectActorsPool);
this.conn.removeActorPool(this._networkEventActorsPool);
this._objectActorsPool = null;
this._networkEventActorsPool = null;
this.conn.removeActorPool(this.actorPool);
this._actorPool = null;
this._sandboxLocation = this.sandbox = null;
this.conn = this._window = null;
},
@ -243,45 +228,58 @@ WebConsoleActor.prototype =
*/
createObjectActor: function WCA_createObjectActor(aObject)
{
if (typeof aObject == "string") {
return this.createStringGrip(aObject);
}
// We need to unwrap the object, otherwise we cannot access the properties
// and methods added by the content scripts.
let obj = WebConsoleUtils.unwrap(aObject);
let actor = new WebConsoleObjectActor(obj, this);
this._objectActorsPool.addActor(actor);
this._actorPool.addActor(actor);
return actor.grip();
},
/**
* Create a grip for the given string. If the given string is a long string,
* then a LongStringActor grip will be used.
*
* @param string aString
* The string you want to create the grip for.
* @return string|object
* The same string, as is, or a LongStringActor object that wraps the
* given string.
*/
createStringGrip: function WCA_createStringGrip(aString)
{
if (aString.length >= DebuggerServer.LONG_STRING_LENGTH) {
let actor = new LongStringActor(aString, this);
this._actorPool.addActor(actor);
return actor.grip();
}
return aString;
},
/**
* Get an object actor by its ID.
*
* @param string aActorID
* @return object
*/
getObjectActorByID: function WCA_getObjectActorByID(aActorID)
getActorByID: function WCA_getActorByID(aActorID)
{
return this._objectActorsPool.get(aActorID);
return this._actorPool.get(aActorID);
},
/**
* Release an object grip for the given object actor.
* Release an actor.
*
* @param object aActor
* The WebConsoleObjectActor instance you want to release.
* The actor instance you want to release.
*/
releaseObject: function WCA_releaseObject(aActor)
releaseActor: function WCA_releaseActor(aActor)
{
this._objectActorsPool.removeActor(aActor.actorID);
},
/**
* Release a network event actor.
*
* @param object aActor
* The NetworkEventActor instance you want to release.
*/
releaseNetworkEvent: function WCA_releaseNetworkEvent(aActor)
{
this._networkEventActorsPool.removeActor(aActor.actorID);
this._actorPool.removeActor(aActor.actorID);
},
//////////////////
@ -707,7 +705,7 @@ WebConsoleActor.prototype =
onNetworkEvent: function WCA_onNetworkEvent(aEvent)
{
let actor = new NetworkEventActor(aEvent, this);
this._networkEventActorsPool.addActor(actor);
this._actorPool.addActor(actor);
let packet = {
from: this.actorID,
@ -808,7 +806,7 @@ WebConsoleActor.prototype =
result.objectProperties = [];
let first = result.arguments[0];
if (typeof first == "object" && first && first.inspectable) {
let actor = this.getObjectActorByID(first.actor);
let actor = this.getActorByID(first.actor);
result.objectProperties = actor.onInspectProperties().properties;
}
}
@ -869,6 +867,7 @@ WebConsoleObjectActor.prototype =
{
let grip = WebConsoleUtils.getObjectGrip(this.obj);
grip.actor = this.actorID;
grip.displayString = this.parent.createStringGrip(grip.displayString);
return grip;
},
@ -877,7 +876,7 @@ WebConsoleObjectActor.prototype =
*/
release: function WCOA_release()
{
this.parent.releaseObject(this);
this.parent.releaseActor(this);
this.parent = this.obj = null;
},
@ -890,7 +889,6 @@ WebConsoleObjectActor.prototype =
*/
onInspectProperties: function WCOA_onInspectProperties()
{
// TODO: Bug 787981 - use LongStringActor for strings that are too long.
let createObjectActor = this.parent.createObjectActor.bind(this.parent);
let props = WebConsoleUtils.inspectObject(this.obj, createObjectActor);
return {
@ -949,6 +947,7 @@ function NetworkEventActor(aNetworkEvent, aWebConsoleActor)
};
this._timings = {};
this._longStringActors = new Set();
this._discardRequestBody = aNetworkEvent.discardRequestBody;
this._discardResponseBody = aNetworkEvent.discardResponseBody;
@ -959,6 +958,7 @@ NetworkEventActor.prototype =
_request: null,
_response: null,
_timings: null,
_longStringActors: null,
actorPrefix: "netEvent",
@ -980,7 +980,14 @@ NetworkEventActor.prototype =
*/
release: function NEA_release()
{
this.parent.releaseNetworkEvent(this);
for (let grip of this._longStringActors) {
let actor = this.parent.getActorByID(grip.actor);
if (actor) {
this.parent.releaseActor(actor);
}
}
this._longStringActors = new Set();
this.parent.releaseActor(this);
},
/**
@ -1108,6 +1115,7 @@ NetworkEventActor.prototype =
addRequestHeaders: function NEA_addRequestHeaders(aHeaders)
{
this._request.headers = aHeaders;
this._prepareHeaders(aHeaders);
let packet = {
from: this.actorID,
@ -1129,6 +1137,7 @@ NetworkEventActor.prototype =
addRequestCookies: function NEA_addRequestCookies(aCookies)
{
this._request.cookies = aCookies;
this._prepareHeaders(aCookies);
let packet = {
from: this.actorID,
@ -1149,6 +1158,10 @@ NetworkEventActor.prototype =
addRequestPostData: function NEA_addRequestPostData(aPostData)
{
this._request.postData = aPostData;
aPostData.text = this.parent.createStringGrip(aPostData.text);
if (typeof aPostData.text == "object") {
this._longStringActors.add(aPostData.text);
}
let packet = {
from: this.actorID,
@ -1194,6 +1207,7 @@ NetworkEventActor.prototype =
addResponseHeaders: function NEA_addResponseHeaders(aHeaders)
{
this._response.headers = aHeaders;
this._prepareHeaders(aHeaders);
let packet = {
from: this.actorID,
@ -1215,6 +1229,7 @@ NetworkEventActor.prototype =
addResponseCookies: function NEA_addResponseCookies(aCookies)
{
this._response.cookies = aCookies;
this._prepareHeaders(aCookies);
let packet = {
from: this.actorID,
@ -1238,6 +1253,10 @@ NetworkEventActor.prototype =
function NEA_addResponseContent(aContent, aDiscardedResponseBody)
{
this._response.content = aContent;
aContent.text = this.parent.createStringGrip(aContent.text);
if (typeof aContent.text == "object") {
this._longStringActors.add(aContent.text);
}
let packet = {
from: this.actorID,
@ -1273,6 +1292,23 @@ NetworkEventActor.prototype =
this.conn.send(packet);
},
/**
* Prepare the headers array to be sent to the client by using the
* LongStringActor for the header values, when needed.
*
* @private
* @param array aHeaders
*/
_prepareHeaders: function NEA__prepareHeaders(aHeaders)
{
for (let header of aHeaders) {
header.value = this.parent.createStringGrip(header.value);
if (typeof header.value == "object") {
this._longStringActors.add(header.value);
}
}
},
};
NetworkEventActor.prototype.requestTypes =

View File

@ -19,9 +19,11 @@ MOCHITEST_CHROME_FILES = \
test_object_actor.html \
test_network_get.html \
test_network_post.html \
test_network_longstring.html \
test_file_uri.html \
network_requests_iframe.html \
data.json \
data.json^headers^ \
common.js \
$(NULL)

View File

@ -100,40 +100,73 @@ function checkObject(aObject, aExpected)
{
let expected = aExpected[name];
let value = aObject[name];
if (value === undefined) {
ok(false, "'" + name + "' is undefined");
}
else if (typeof expected == "string" ||
typeof expected == "number" ||
typeof expected == "boolean") {
is(value, expected, "property '" + name + "'");
}
else if (expected instanceof RegExp) {
ok(expected.test(value), name + ": " + expected);
}
else if (Array.isArray(expected)) {
info("checking array for property '" + name + "'");
checkObject(value, expected);
}
else if (typeof expected == "object") {
info("checking object for property '" + name + "'");
checkObject(value, expected);
}
checkValue(name, value, expected);
}
}
function checkValue(aName, aValue, aExpected)
{
if (aValue === undefined) {
ok(false, "'" + aName + "' is undefined");
}
else if (typeof aExpected == "string" || typeof aExpected == "number" ||
typeof aExpected == "boolean") {
is(aValue, aExpected, "property '" + aName + "'");
}
else if (aExpected instanceof RegExp) {
ok(aExpected.test(aValue), aName + ": " + aExpected + " matched " + aValue);
}
else if (Array.isArray(aExpected)) {
info("checking array for property '" + aName + "'");
checkObject(aValue, aExpected);
}
else if (typeof aExpected == "object") {
info("checking object for property '" + aName + "'");
checkObject(aValue, aExpected);
}
}
function checkHeadersOrCookies(aArray, aExpected)
{
let foundHeaders = {};
for (let elem of aArray) {
if (!(elem.name in aExpected)) {
continue;
}
let expected = aExpected[elem.name];
if (expected instanceof RegExp) {
ok(expected.test(elem.value), elem.name + ": " + expected);
}
else {
is(elem.value, expected, elem.name);
foundHeaders[elem.name] = true;
info("checking value of header " + elem.name);
checkValue(elem.name, elem.value, aExpected[elem.name]);
}
for (let header in aExpected) {
if (!(header in foundHeaders)) {
ok(false, header + " was not found");
}
}
}
var gTestState = {};
function runTests(aTests, aEndCallback)
{
function driver()
{
let lastResult, sendToNext;
for (let i = 0; i < aTests.length; i++) {
gTestState.index = i;
let fn = aTests[i];
info("will run test #" + i + ": " + fn.name);
lastResult = fn(sendToNext, lastResult);
sendToNext = yield lastResult;
}
yield aEndCallback(sendToNext, lastResult);
}
gTestState.driver = driver();
return gTestState.driver.next();
}
function nextTest(aMessage)
{
return gTestState.driver.send(aMessage);
}

View File

@ -1 +1,3 @@
{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }
{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ],
veryLong: "foo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo bar"
}

View File

@ -0,0 +1,3 @@
Content-Type: application/json
x-very-long: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a ipsum massa. Phasellus at elit dictum libero laoreet sagittis. Phasellus condimentum ultricies imperdiet. Nam eu ligula justo, ut tincidunt quam. Etiam sollicitudin, tortor sed egestas blandit, sapien sem tincidunt nulla, eu luctus libero odio quis leo. Nam elit massa, mattis quis blandit ac, facilisis vitae arcu. Donec vitae dictum neque. Proin ornare nisl at lectus commodo iaculis eget eget est. Quisque scelerisque vestibulum quam sed interdum.
x-very-short: hello world

View File

@ -24,7 +24,8 @@
}
function testXhrPost(aCallback) {
makeXhr('post', 'data.json', "Hello world!", aCallback);
var body = "Hello world! " + (new Array(50)).join("foobaz barr");
makeXhr('post', 'data.json', body, aCallback);
}
document.cookie = "foobar=fooval";

View File

@ -18,12 +18,15 @@ let expectedConsoleCalls = [];
function doConsoleCalls(aState)
{
let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 2)).join("a");
console.log("foobarBaz-log", undefined);
console.info("foobarBaz-info", null);
console.warn("foobarBaz-warn", document.body);
console.debug(null);
console.trace();
console.dir(document, window);
console.log("foo", longString);
expectedConsoleCalls = [
{
@ -98,6 +101,22 @@ function doConsoleCalls(aState)
}, // ...
],
},
{
level: "log",
filename: /test_consoleapi/,
functionName: "doConsoleCalls",
timeStamp: /^\d+$/,
arguments: [
"foo",
{
type: "longString",
initial: longString.substring(0,
DebuggerServer.LONG_STRING_INITIAL_LENGTH),
length: longString.length,
actor: /[a-z]/,
},
],
},
];
}

View File

@ -14,6 +14,8 @@
<script class="testbody" type="text/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();
let gState;
function startTest()
{
removeEventListener("load", startTest);
@ -29,13 +31,24 @@ function onAttach(aState, aResponse)
top.foobarObject.foobaz = 3;
top.foobarObject.omg = 4;
top.foobarObject.omgfoo = 5;
top.foobarObject.strfoo = "foobarz";
top.foobarObject.omgstr = "foobarz" +
(new Array(DebuggerServer.LONG_STRING_LENGTH * 2)).join("abb");
info("test autocomplete for 'window.foo'");
onAutocomplete1 = onAutocomplete1.bind(null, aState);
aState.client.autocomplete("window.foo", 0, onAutocomplete1);
gState = aState;
let tests = [doAutocomplete1, doAutocomplete2, doSimpleEval, doWindowEval,
doEvalWithException, doEvalWithHelper, doEvalString, doEvalLongString];
runTests(tests, testEnd);
}
function onAutocomplete1(aState, aResponse)
function doAutocomplete1()
{
info("test autocomplete for 'window.foo'");
gState.client.autocomplete("window.foo", 0, onAutocomplete1);
}
function onAutocomplete1(aResponse)
{
let matches = aResponse.matches;
@ -43,30 +56,37 @@ function onAutocomplete1(aState, aResponse)
is(matches.length, 1, "matches.length");
is(matches[0], "foobarObject", "matches[0]");
info("test autocomplete for 'window.foobarObject.'");
onAutocomplete2 = onAutocomplete2.bind(null, aState);
aState.client.autocomplete("window.foobarObject.", 0, onAutocomplete2);
nextTest();
}
function onAutocomplete2(aState, aResponse)
function doAutocomplete2()
{
info("test autocomplete for 'window.foobarObject.'");
gState.client.autocomplete("window.foobarObject.", 0, onAutocomplete2);
}
function onAutocomplete2(aResponse)
{
let matches = aResponse.matches;
ok(!aResponse.matchProp, "matchProp");
is(matches.length, 5, "matches.length");
checkObject(matches, ["foo", "foobar", "foobaz", "omg", "omgfoo"]);
is(matches.length, 7, "matches.length");
checkObject(matches,
["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
info("test eval '2+2'");
onEval1 = onEval1.bind(null, aState);
aState.client.evaluateJS("2+2", onEval1);
nextTest();
}
function onEval1(aState, aResponse)
function doSimpleEval()
{
info("test eval '2+2'");
gState.client.evaluateJS("2+2", onSimpleEval);
}
function onSimpleEval(aResponse)
{
checkObject(aResponse, {
from: aState.actor,
from: gState.actor,
input: "2+2",
result: 4,
});
@ -74,15 +94,19 @@ function onEval1(aState, aResponse)
ok(!aResponse.error, "no js error");
ok(!aResponse.helperResult, "no helper result");
info("test eval 'window'");
onEval2 = onEval2.bind(null, aState);
aState.client.evaluateJS("window", onEval2);
nextTest();
}
function onEval2(aState, aResponse)
function doWindowEval()
{
info("test eval 'window'");
gState.client.evaluateJS("window", onWindowEval);
}
function onWindowEval(aResponse)
{
checkObject(aResponse, {
from: aState.actor,
from: gState.actor,
input: "window",
result: {
type: "object",
@ -94,17 +118,19 @@ function onEval2(aState, aResponse)
ok(!aResponse.error, "no js error");
ok(!aResponse.helperResult, "no helper result");
info("test eval with exception");
onEvalWithException = onEvalWithException.bind(null, aState);
aState.client.evaluateJS("window.doTheImpossible()",
onEvalWithException);
nextTest();
}
function onEvalWithException(aState, aResponse)
function doEvalWithException()
{
info("test eval with exception");
gState.client.evaluateJS("window.doTheImpossible()", onEvalWithException);
}
function onEvalWithException(aResponse)
{
checkObject(aResponse, {
from: aState.actor,
from: gState.actor,
input: "window.doTheImpossible()",
result: {
type: "undefined",
@ -115,16 +141,19 @@ function onEvalWithException(aState, aResponse)
ok(aResponse.error, "js error object");
ok(!aResponse.helperResult, "no helper result");
info("test eval with helper");
onEvalWithHelper = onEvalWithHelper.bind(null, aState);
aState.client.evaluateJS("clear()", onEvalWithHelper);
nextTest();
}
function onEvalWithHelper(aState, aResponse)
function doEvalWithHelper()
{
info("test eval with helper");
gState.client.evaluateJS("clear()", onEvalWithHelper);
}
function onEvalWithHelper(aResponse)
{
checkObject(aResponse, {
from: aState.actor,
from: gState.actor,
input: "clear()",
result: {
type: "undefined",
@ -134,7 +163,52 @@ function onEvalWithHelper(aState, aResponse)
ok(!aResponse.error, "no js error");
closeDebugger(aState, function() {
nextTest();
}
function doEvalString()
{
gState.client.evaluateJS("window.foobarObject.strfoo", onEvalString);
}
function onEvalString(aResponse)
{
checkObject(aResponse, {
from: gState.actor,
input: "window.foobarObject.strfoo",
result: "foobarz",
});
nextTest();
}
function doEvalLongString()
{
gState.client.evaluateJS("window.foobarObject.omgstr", onEvalLongString);
}
function onEvalLongString(aResponse)
{
let str = top.foobarObject.omgstr;
let initial = str.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
checkObject(aResponse, {
from: gState.actor,
input: "window.foobarObject.omgstr",
result: {
type: "longString",
initial: initial,
length: str.length,
},
});
nextTest();
}
function testEnd()
{
closeDebugger(gState, function() {
gState = null;
SimpleTest.finish();
});
}

View File

@ -98,7 +98,7 @@ function onNetworkEventUpdate(aState, aType, aPacket)
break;
case "responseContent":
expectedPacket = {
mimeType: /^application\/(json|octet-stream)$/,
mimeType: "application/json",
contentSize: 0,
discardResponseBody: true,
};

View File

@ -0,0 +1,299 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf8">
<title>Test that the network actor uses the LongStringActor</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript;version=1.8" src="common.js"></script>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>Test that the network actor uses the LongStringActor</p>
<iframe src="http://example.com/chrome/toolkit/devtools/webconsole/test/network_requests_iframe.html"></iframe>
<script class="testbody" type="text/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();
function startTest()
{
removeEventListener("load", startTest);
attachConsole(["NetworkActivity"], onAttach, true);
}
function onAttach(aState, aResponse)
{
info("enable network request and response body logging");
window.ORIGINAL_LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
window.ORIGINAL_LONG_STRING_INITIAL_LENGTH =
DebuggerServer.LONG_STRING_INITIAL_LENGTH;
DebuggerServer.LONG_STRING_LENGTH = 400;
DebuggerServer.LONG_STRING_INITIAL_LENGTH = 400;
onSetPreferences = onSetPreferences.bind(null, aState);
aState.client.setPreferences({
"NetworkMonitor.saveRequestAndResponseBodies": true,
}, onSetPreferences);
}
function onSetPreferences(aState, aResponse)
{
is(aResponse.updated.length, 1, "updated prefs length");
is(aResponse.updated[0], "NetworkMonitor.saveRequestAndResponseBodies",
"updated prefs length");
info("test network POST request");
onNetworkEvent = onNetworkEvent.bind(null, aState);
aState.dbgClient.addListener("networkEvent", onNetworkEvent);
onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
let iframe = document.querySelector("iframe").contentWindow;
iframe.wrappedJSObject.testXhrPost();
}
function onNetworkEvent(aState, aType, aPacket)
{
is(aPacket.from, aState.actor, "network event actor");
info("checking the network event packet");
let netActor = aPacket.eventActor;
checkObject(netActor, {
actor: /[a-z]/,
startedDateTime: /^\d+\-\d+\-\d+T.+$/,
url: /data\.json/,
method: "POST",
});
aState.netActor = netActor.actor;
aState.dbgClient.removeListener("networkEvent", onNetworkEvent);
}
let updates = [];
function onNetworkEventUpdate(aState, aType, aPacket)
{
info("received networkEventUpdate " + aPacket.updateType);
is(aPacket.from, aState.netActor, "networkEventUpdate actor");
updates.push(aPacket.updateType);
let expectedPacket = null;
switch (aPacket.updateType) {
case "requestHeaders":
case "responseHeaders":
ok(aPacket.headers > 0, "headers > 0");
ok(aPacket.headersSize > 0, "headersSize > 0");
break;
case "requestCookies":
expectedPacket = {
cookies: 2,
};
break;
case "requestPostData":
ok(aPacket.dataSize > 0, "dataSize > 0");
ok(!aPacket.discardRequestBody, "discardRequestBody");
break;
case "responseStart":
expectedPacket = {
response: {
httpVersion: /^HTTP\/\d\.\d$/,
status: 200,
statusText: "OK",
headersSize: /^\d+$/,
discardResponseBody: false,
},
};
break;
case "responseCookies":
expectedPacket = {
cookies: 0,
};
break;
case "responseContent":
expectedPacket = {
mimeType: "application/json",
contentSize: /^\d+$/,
discardResponseBody: false,
};
break;
case "eventTimings":
expectedPacket = {
totalTime: /^\d+$/,
};
break;
default:
ok(false, "unknown network event update type: " +
aPacket.updateType);
return;
}
if (expectedPacket) {
info("checking the packet content");
checkObject(aPacket, expectedPacket);
}
if (updates.indexOf("responseContent") > -1 &&
updates.indexOf("eventTimings") > -1) {
aState.dbgClient.removeListener("networkEventUpdate",
onNetworkEvent);
onRequestHeaders = onRequestHeaders.bind(null, aState);
aState.client.getRequestHeaders(aState.netActor,
onRequestHeaders);
}
}
function onRequestHeaders(aState, aResponse)
{
info("checking request headers");
ok(aResponse.headers.length > 0, "request headers > 0");
ok(aResponse.headersSize > 0, "request headersSize > 0");
checkHeadersOrCookies(aResponse.headers, {
Referer: /network_requests_iframe\.html/,
Cookie: /bug768096/,
});
onRequestCookies = onRequestCookies.bind(null, aState);
aState.client.getRequestCookies(aState.netActor,
onRequestCookies);
}
function onRequestCookies(aState, aResponse)
{
info("checking request cookies");
is(aResponse.cookies.length, 2, "request cookies length");
checkHeadersOrCookies(aResponse.cookies, {
foobar: "fooval",
omgfoo: "bug768096",
});
onRequestPostData = onRequestPostData.bind(null, aState);
aState.client.getRequestPostData(aState.netActor,
onRequestPostData);
}
function onRequestPostData(aState, aResponse)
{
info("checking request POST data");
checkObject(aResponse, {
postData: {
text: {
type: "longString",
initial: /^Hello world! foobaz barr.+foobaz barrfo$/,
length: 552,
actor: /[a-z]/,
},
},
postDataDiscarded: false,
});
is(aResponse.postData.text.initial.length,
DebuggerServer.LONG_STRING_INITIAL_LENGTH, "postData text initial length");
onResponseHeaders = onResponseHeaders.bind(null, aState);
aState.client.getResponseHeaders(aState.netActor,
onResponseHeaders);
}
function onResponseHeaders(aState, aResponse)
{
info("checking response headers");
ok(aResponse.headers.length > 0, "response headers > 0");
ok(aResponse.headersSize > 0, "response headersSize > 0");
checkHeadersOrCookies(aResponse.headers, {
"Content-Type": /^application\/(json|octet-stream)$/,
"Content-Length": /^\d+$/,
"x-very-short": "hello world",
"x-very-long": {
"type": "longString",
"length": 521,
"initial": /^Lorem ipsum.+\. Donec vitae d$/,
"actor": /[a-z]/,
},
});
onResponseCookies = onResponseCookies.bind(null, aState);
aState.client.getResponseCookies(aState.netActor,
onResponseCookies);
}
function onResponseCookies(aState, aResponse)
{
info("checking response cookies");
is(aResponse.cookies.length, 0, "response cookies length");
onResponseContent = onResponseContent.bind(null, aState);
aState.client.getResponseContent(aState.netActor,
onResponseContent);
}
function onResponseContent(aState, aResponse)
{
info("checking response content");
checkObject(aResponse, {
content: {
text: {
type: "longString",
initial: /^\{ id: "test JSON data"(.|\r|\n)+ barfoo ba$/g,
length: 1070,
actor: /[a-z]/,
},
},
contentDiscarded: false,
});
is(aResponse.content.text.initial.length,
DebuggerServer.LONG_STRING_INITIAL_LENGTH, "content initial length");
onEventTimings = onEventTimings.bind(null, aState);
aState.client.getEventTimings(aState.netActor,
onEventTimings);
}
function onEventTimings(aState, aResponse)
{
info("checking event timings");
checkObject(aResponse, {
timings: {
blocked: /^-1|\d+$/,
dns: /^-1|\d+$/,
connect: /^-1|\d+$/,
send: /^-1|\d+$/,
wait: /^-1|\d+$/,
receive: /^-1|\d+$/,
},
totalTime: /^\d+$/,
});
closeDebugger(aState, function() {
DebuggerServer.LONG_STRING_LENGTH = ORIGINAL_LONG_STRING_LENGTH;
DebuggerServer.LONG_STRING_INITIAL_LENGTH = ORIGINAL_LONG_STRING_INITIAL_LENGTH;
SimpleTest.finish();
});
}
addEventListener("load", startTest);
</script>
</body>
</html>

View File

@ -114,7 +114,7 @@ function onNetworkEventUpdate(aState, aType, aPacket)
break;
case "responseContent":
expectedPacket = {
mimeType: /^application\/(json|octet-stream)$/,
mimeType: "application/json",
contentSize: /^\d+$/,
discardResponseBody: false,
};
@ -185,11 +185,13 @@ function onRequestPostData(aState, aResponse)
checkObject(aResponse, {
postData: {
text: "Hello world!",
text: /^Hello world! foobaz barr.+foobaz barr$/,
},
postDataDiscarded: false,
});
is(aResponse.postData.text.length, 552, "postData text length");
onResponseHeaders = onResponseHeaders.bind(null, aState);
aState.client.getResponseHeaders(aState.netActor,
onResponseHeaders);

View File

@ -28,7 +28,10 @@ function onAttach(aState, aResponse)
onConsoleCall = onConsoleCall.bind(null, aState);
aState.dbgClient.addListener("consoleAPICall", onConsoleCall);
let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 3)).join("\u0629");
window.foobarObject = Object.create(null);
foobarObject.tamarbuta = longString;
foobarObject.foo = 1;
foobarObject.foobar = "hello";
foobarObject.foobaz = document;
@ -46,6 +49,12 @@ function onAttach(aState, aResponse)
set: function fooSet() { 1+2 },
});
foobarObject.longStringObj = {
toSource: function() longString,
toString: function() longString,
boom: "explode",
};
console.log("hello", foobarObject);
expectedProps = [
@ -96,6 +105,21 @@ function onAttach(aState, aResponse)
inspectable: false,
},
},
{
name: "longStringObj",
value: {
type: "object",
className: "Object",
actor: /[a-z]/,
inspectable: true,
displayString: {
type: "longString",
initial: longString.substring(0,
DebuggerServer.LONG_STRING_INITIAL_LENGTH),
length: longString.length,
},
},
},
{
name: "notInspectable",
value: {
@ -119,6 +143,15 @@ function onAttach(aState, aResponse)
inspectable: false,
},
},
{
name: "tamarbuta",
value: {
type: "longString",
initial: longString.substring(0,
DebuggerServer.LONG_STRING_INITIAL_LENGTH),
length: longString.length,
},
},
{
name: "testfoo",
value: false,