Merge fx-team to m-c

This commit is contained in:
Victor Porof 2012-11-18 11:49:52 +02:00
commit 7b5c67fb5a
21 changed files with 1905 additions and 123 deletions

View File

@ -374,6 +374,8 @@ StackFrames.prototype = {
get activeThread() DebuggerController.activeThread,
autoScopeExpand: false,
currentFrame: null,
syncedWatchExpressions: null,
currentWatchExpressions: null,
currentBreakpointLocation: null,
currentEvaluation: null,
currentException: null,
@ -428,7 +430,7 @@ StackFrames.prototype = {
break;
// If paused by a client evaluation, store the evaluated value.
case "clientEvaluated":
this.currentEvaluation = aPacket.why.frameFinished.return;
this.currentEvaluation = aPacket.why.frameFinished;
break;
// If paused by an exception, store the exception value.
case "exception":
@ -445,6 +447,11 @@ StackFrames.prototype = {
*/
_onResumed: function SF__onResumed() {
DebuggerView.editor.setDebugLocation(-1);
// Prepare the watch expression evaluation string for the next pause.
if (!this._isWatchExpressionsEvaluation) {
this.currentWatchExpressions = this.syncedWatchExpressions;
}
},
/**
@ -455,7 +462,6 @@ StackFrames.prototype = {
if (!this.activeThread.cachedFrames.length) {
return;
}
DebuggerView.StackFrames.empty();
// Conditional breakpoints are { breakpoint, expression } tuples. The
// boolean evaluation of the expression decides if the active thread
@ -468,28 +474,56 @@ StackFrames.prototype = {
// 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.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 })) {
if (VariablesView.isFalsy({ value: this.currentEvaluation.return })) {
this.activeThread.resume();
return;
}
}
// Watch expressions are evaluated in the context of the topmost frame,
// and the results and displayed in the variables view.
if (this.currentWatchExpressions) {
// Evaluation causes the stack frames to be cleared and active thread to
// pause, sending a 'clientEvaluated' packed and adding the frames again.
this.evaluate(this.currentWatchExpressions, 0);
this._isWatchExpressionsEvaluation = true;
return;
}
// Got our evaluation of the current watch expressions.
if (this._isWatchExpressionsEvaluation) {
this._isWatchExpressionsEvaluation = false;
// If an error was thrown during the evaluation of the watch expressions,
// then at least one expression evaluation could not be performed.
if (this.currentEvaluation.throw) {
DebuggerView.WatchExpressions.removeExpression(0);
DebuggerController.StackFrames.syncWatchExpressions();
return;
}
// If the watch expressions were evaluated successfully, attach
// the results to the topmost frame.
let topmostFrame = this.activeThread.cachedFrames[0];
topmostFrame.watchExpressionsEvaluation = this.currentEvaluation.return;
}
// Make sure all the previous stackframes are removed before re-adding them.
DebuggerView.StackFrames.empty();
for (let frame of this.activeThread.cachedFrames) {
this._addFrame(frame);
}
if (!this.currentFrame) {
if (this.currentFrame == null) {
this.selectFrame(0);
}
if (this.activeThread.moreFrames) {
@ -502,6 +536,7 @@ StackFrames.prototype = {
*/
_onFramesCleared: function SF__onFramesCleared() {
this.currentFrame = null;
this.currentWatchExpressions = null;
this.currentBreakpointLocation = null;
this.currentEvaluation = null;
this.currentException = null;
@ -523,6 +558,7 @@ StackFrames.prototype = {
DebuggerView.StackFrames.empty();
DebuggerView.Variables.empty(0);
DebuggerView.Breakpoints.unhighlightBreakpoint();
DebuggerView.WatchExpressions.toggleContents(true);
window.dispatchEvent("Debugger:AfterFramesCleared");
},
@ -538,7 +574,7 @@ StackFrames.prototype = {
if (!frame) {
return;
}
let environment = frame.environment;
let { environment, watchExpressionsEvaluation } = frame;
let { url, line } = frame.where;
// Check if the frame does not represent the evaluation of debuggee code.
@ -552,11 +588,27 @@ StackFrames.prototype = {
DebuggerView.StackFrames.highlightFrame(aDepth);
// Highlight the breakpoint at the specified url and line if it exists.
DebuggerView.Breakpoints.highlightBreakpoint(url, line);
// Don't display the watch expressions textbox inputs in the pane.
DebuggerView.WatchExpressions.toggleContents(false);
// Start recording any added variables or properties in any scope.
DebuggerView.Variables.createHierarchy();
// Clear existing scopes and create each one dynamically.
DebuggerView.Variables.empty();
// If watch expressions evaluation results are available, create a scope
// to contain all the values.
if (watchExpressionsEvaluation) {
let label = L10N.getStr("watchExpressionsScopeLabel");
let arrow = L10N.getStr("watchExpressionsSeparatorLabel");
let scope = DebuggerView.Variables.addScope(label);
scope.separator = arrow;
// The evaluation hasn't thrown, so display the returned results and
// always expand the watch expressions scope by default.
this._fetchWatchExpressions(scope, watchExpressionsEvaluation);
scope.expand();
}
do {
// Create a scope to contain all the inspected variables.
let label = this._getScopeLabel(environment);
@ -624,6 +676,39 @@ StackFrames.prototype = {
aVar.onexpand = callback;
},
/**
* Adds the watch expressions evaluation results to a scope in the view.
*
* @param Scope aScope
* The scope where the watch expressions will be placed into.
* @param object aExp
* The grip of the evaluation results.
*/
_fetchWatchExpressions: function SF__fetchWatchExpressions(aScope, aExp) {
// Retrieve the expressions only once.
if (aScope.fetched) {
return;
}
aScope.fetched = true;
// Add nodes for every watch expression in scope.
this.activeThread.pauseGrip(aExp).getPrototypeAndProperties(function(aResponse) {
let ownProperties = aResponse.ownProperties;
let totalExpressions = DebuggerView.WatchExpressions.totalItems;
for (let i = 0; i < totalExpressions; i++) {
let name = DebuggerView.WatchExpressions.getExpression(i);
let expVal = ownProperties[i].value;
let expRef = aScope.addVar(name, ownProperties[i]);
this._addVarExpander(expRef, expVal);
}
// Signal that watch expressions have been fetched.
window.dispatchEvent("Debugger:FetchedWatchExpressions");
DebuggerView.Variables.commitHierarchy();
}.bind(this));
},
/**
* Adds variables to a scope in the view. Triggered when a scope is
* expanded or is hovered. It does not expand the scope.
@ -760,7 +845,7 @@ StackFrames.prototype = {
}
// Add the variable's __proto__.
if (prototype.type != "null") {
if (prototype && prototype.type != "null") {
aVar.addProperty("__proto__", { value: prototype });
// Expansion handlers must be set after the properties are added.
this._addVarExpander(aVar.get("__proto__"), prototype);
@ -830,6 +915,27 @@ StackFrames.prototype = {
this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE);
},
/**
* Updates a list of watch expressions to evaluate on each pause.
*/
syncWatchExpressions: function SF_syncWatchExpressions() {
let list = DebuggerView.WatchExpressions.getExpressions();
if (list.length) {
this.syncedWatchExpressions =
this.currentWatchExpressions = "[" + list.map(function(str)
"(function() {" +
"try { return eval(\"" + str.replace(/"/g, "\\$&") + "\"); }" +
"catch(e) { return e.name + ': ' + e.message; }" +
"})()"
).join(",") + "]";
} else {
this.syncedWatchExpressions =
this.currentWatchExpressions = null;
}
this._onFrames();
},
/**
* Evaluate an expression in the context of the selected frame. This is used
* for modifying the value of variables or properties in scope.
@ -839,7 +945,7 @@ StackFrames.prototype = {
* @param number aFrame [optional]
* The frame depth used for evaluation.
*/
evaluate: function SF_evaluate(aExpression, aFrame = this.currentFrame) {
evaluate: function SF_evaluate(aExpression, aFrame = this.currentFrame || 0) {
let frame = this.activeThread.cachedFrames[aFrame];
this.activeThread.eval(frame.actor, aExpression);
}
@ -958,6 +1064,10 @@ SourceScripts.prototype = {
_onScriptsAdded: function SS__onScriptsAdded(aResponse) {
// Add all the sources in the debugger view sources container.
for (let script of aResponse.scripts) {
// Ignore scripts generated from 'clientEvaluate' packets.
if (script.url == "debugger eval code") {
continue;
}
this._addSource(script);
}

View File

@ -36,7 +36,7 @@ create({ constructor: StackFramesView, proto: MenuContainer.prototype }, {
*/
destroy: function DVSF_destroy() {
dumpn("Destroying the StackFramesView");
this._container.removeEventListener("click", this._onClick, true);
this._container.removeEventListener("click", this._onClick, false);
this._container.removeEventListener("scroll", this._onScroll, true);
window.removeEventListener("resize", this._onScroll, true);
},
@ -909,7 +909,8 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
},
/**
* Gets an identifier for a breakpoint item for the current cache.
* Gets an identifier for a breakpoint item in the current cache.
* @return string
*/
_key: function DVB__key(aSourceLocation, aLineNumber) {
return aSourceLocation + aLineNumber;
@ -924,6 +925,247 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
_editorContextMenuLineNumber: -1
});
/**
* Functions handling the watch expressions UI.
*/
function WatchExpressionsView() {
dumpn("WatchExpressionsView was instantiated");
MenuContainer.call(this);
this._createItemView = this._createItemView.bind(this);
this._onClick = this._onClick.bind(this);
this._onClose = this._onClose.bind(this);
this._onBlur = this._onBlur.bind(this);
this._onKeyPress = this._onKeyPress.bind(this);
this._onMouseOver = this._onMouseOver.bind(this);
this._onMouseOut = this._onMouseOut.bind(this);
}
create({ constructor: WatchExpressionsView, proto: MenuContainer.prototype }, {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function DVWE_initialize() {
dumpn("Initializing the WatchExpressionsView");
this._container = new StackList(document.getElementById("expressions"));
this._variables = document.getElementById("variables");
this._container.permaText = L10N.getStr("addWatchExpressionText");
this._container.itemFactory = this._createItemView;
this._container.addEventListener("click", this._onClick, false);
this._cache = [];
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function DVWE_destroy() {
dumpn("Destroying the WatchExpressionsView");
this._container.removeEventListener("click", this._onClick, false);
},
/**
* Adds a watch expression in this container.
*
* @param string aExpression [optional]
* An optional initial watch expression text.
*/
addExpression: function DVWE_addExpression(aExpression = "") {
// Watch expressions are UI elements which benefit from visible panes.
DebuggerView.showPanesSoon();
// Append a watch expression item to this container.
let expressionItem = this.push("", aExpression, {
forced: { atIndex: 0 },
unsorted: true,
relaxed: true,
attachment: {
expression: "",
initialExpression: aExpression,
id: this._generateId()
}
});
// Check if watch expression was already appended.
if (!expressionItem) {
return;
}
let element = expressionItem.target;
element.id = "expression-" + expressionItem.attachment.id;
element.className = "dbg-expression list-item";
element.arrowNode.className = "dbg-expression-arrow";
element.inputNode.className = "dbg-expression-input plain";
element.closeNode.className = "dbg-expression-delete plain devtools-closebutton";
// Automatically focus the new watch expression input and
// scroll the variables view to top.
element.inputNode.value = aExpression;
element.inputNode.select();
element.inputNode.focus();
this._variables.scrollTop = 0;
this._cache.splice(0, 0, expressionItem);
},
/**
* Removes the watch expression with the specified index from this container.
*
* @param number aIndex
* The index used to identify the watch expression.
*/
removeExpression: function DVWE_removeExpression(aIndex) {
this.remove(this._cache[aIndex]);
this._cache.splice(aIndex, 1);
},
/**
* Gets the watch expression code string for an item in this container.
*
* @param number aIndex
* The index used to identify the watch expression.
* @return string
* The watch expression code string.
*/
getExpression: function DVWE_getExpression(aIndex) {
return this._cache[aIndex].attachment.expression;
},
/**
* Gets the watch expressions code strings for all items in this container.
*
* @return array
* The watch expressions code strings.
*/
getExpressions: function DVWE_getExpressions() {
return [item.attachment.expression for (item of this._cache)];
},
/**
* Customization function for creating an item's UI.
*
* @param nsIDOMNode aElementNode
* The element associated with the displayed item.
* @param string aExpression
* The initial watch expression text.
*/
_createItemView: function DVWE__createItemView(aElementNode, aExpression) {
let arrowNode = document.createElement("box");
let inputNode = document.createElement("textbox");
let closeNode = document.createElement("toolbarbutton");
inputNode.setAttribute("value", aExpression);
inputNode.setAttribute("flex", "1");
closeNode.addEventListener("click", this._onClose, false);
inputNode.addEventListener("blur", this._onBlur, false);
inputNode.addEventListener("keypress", this._onKeyPress, false);
aElementNode.addEventListener("mouseover", this._onMouseOver, false);
aElementNode.addEventListener("mouseout", this._onMouseOut, false);
aElementNode.appendChild(arrowNode);
aElementNode.appendChild(inputNode);
aElementNode.appendChild(closeNode);
aElementNode.arrowNode = arrowNode;
aElementNode.inputNode = inputNode;
aElementNode.closeNode = closeNode;
},
/**
* The click listener for this container.
*/
_onClick: function DVWE__onClick(e) {
let expressionItem = this.getItemForElement(e.target);
if (!expressionItem) {
// The container is empty or we didn't click on an actual item.
this.addExpression();
}
},
/**
* The click listener for a watch expression's close button.
*/
_onClose: function DVWE__onClose(e) {
let expressionItem = this.getItemForElement(e.target);
this.removeExpression(this._cache.indexOf(expressionItem));
// Synchronize with the controller's watch expressions store.
DebuggerController.StackFrames.syncWatchExpressions();
e.preventDefault();
e.stopPropagation();
},
/**
* The blur listener for a watch expression's textbox.
*/
_onBlur: function DVWE__onBlur({ target: textbox }) {
let expressionItem = this.getItemForElement(textbox);
let oldExpression = expressionItem.attachment.expression;
let newExpression = textbox.value;
// Remove the watch expression if it's empty.
if (!newExpression) {
this.removeExpression(this._cache.indexOf(expressionItem));
}
// Remove the watch expression if it's a duplicate.
else if (!oldExpression && this.getExpressions().indexOf(newExpression) != -1) {
this.removeExpression(this._cache.indexOf(expressionItem));
}
// Expression is eligible.
else {
// Save the watch expression code string.
expressionItem.attachment.expression = newExpression;
// Make sure the close button is hidden when the textbox is unfocused.
expressionItem.target.closeNode.hidden = true;
}
// Synchronize with the controller's watch expressions store.
DebuggerController.StackFrames.syncWatchExpressions();
},
/**
* The keypress listener for a watch expression's textbox.
*/
_onKeyPress: function DVWE__onKeyPress(e) {
switch(e.keyCode) {
case e.DOM_VK_RETURN:
case e.DOM_VK_ENTER:
case e.DOM_VK_ESCAPE:
DebuggerView.editor.focus();
return;
}
},
/**
* The mouse over listener for a watch expression.
*/
_onMouseOver: function DVWE__onMouseOver({ target: element }) {
this.getItemForElement(element).target.closeNode.hidden = false;
},
/**
* The mouse out listener for a watch expression.
*/
_onMouseOut: function DVWE__onMouseOut({ target: element }) {
this.getItemForElement(element).target.closeNode.hidden = true;
},
/**
* Gets an identifier for a new watch expression item in the current cache.
* @return string
*/
_generateId: (function() {
let count = 0;
return function DVWE__generateId() {
return (++count) + "";
};
})(),
_variables: null,
_cache: null
});
/**
* Functions handling the global search UI.
*/
@ -1778,4 +2020,5 @@ LineResults.size = function DVGS_size() {
*/
DebuggerView.StackFrames = new StackFramesView();
DebuggerView.Breakpoints = new BreakpointsView();
DebuggerView.WatchExpressions = new WatchExpressionsView();
DebuggerView.GlobalSearch = new GlobalSearchView();

View File

@ -42,6 +42,7 @@ let DebuggerView = {
this.Filtering.initialize();
this.StackFrames.initialize();
this.Breakpoints.initialize();
this.WatchExpressions.initialize();
this.GlobalSearch.initialize();
this.Variables = new VariablesView(document.getElementById("variables"));
@ -71,6 +72,7 @@ let DebuggerView = {
this.Filtering.destroy();
this.StackFrames.destroy();
this.Breakpoints.destroy();
this.WatchExpressions.destroy();
this.GlobalSearch.destroy();
this._destroyWindow();
@ -122,10 +124,10 @@ let DebuggerView = {
this._togglePanesButton = document.getElementById("toggle-panes");
this._stackframesAndBreakpoints = document.getElementById("stackframes+breakpoints");
this._variables = document.getElementById("variables");
this._variablesAndExpressions = document.getElementById("variables+expressions");
this._stackframesAndBreakpoints.setAttribute("width", Prefs.stackframesWidth);
this._variables.setAttribute("width", Prefs.variablesWidth);
this._variablesAndExpressions.setAttribute("width", Prefs.variablesWidth);
this.togglePanes({
visible: Prefs.panesVisibleOnStartup,
animated: false
@ -139,11 +141,11 @@ let DebuggerView = {
dumpn("Destroying the DebuggerView panes");
Prefs.stackframesWidth = this._stackframesAndBreakpoints.getAttribute("width");
Prefs.variablesWidth = this._variables.getAttribute("width");
Prefs.variablesWidth = this._variablesAndExpressions.getAttribute("width");
this._togglePanesButton = null;
this._stackframesAndBreakpoints = null;
this._variables = null;
this._variablesAndExpressions = null;
},
/**
@ -401,21 +403,21 @@ let DebuggerView = {
if (aFlags.visible) {
this._stackframesAndBreakpoints.style.marginLeft = "0";
this._variables.style.marginRight = "0";
this._variablesAndExpressions.style.marginRight = "0";
this._togglePanesButton.removeAttribute("panesHidden");
this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("collapsePanes"));
} else {
let marginL = ~~(this._stackframesAndBreakpoints.getAttribute("width")) + 1;
let marginR = ~~(this._variables.getAttribute("width")) + 1;
let marginR = ~~(this._variablesAndExpressions.getAttribute("width")) + 1;
this._stackframesAndBreakpoints.style.marginLeft = -marginL + "px";
this._variables.style.marginRight = -marginR + "px";
this._variablesAndExpressions.style.marginRight = -marginR + "px";
this._togglePanesButton.setAttribute("panesHidden", "true");
this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("expandPanes"));
}
if (aFlags.animated) {
this._stackframesAndBreakpoints.setAttribute("animated", "");
this._variables.setAttribute("animated", "");
this._variablesAndExpressions.setAttribute("animated", "");
// Displaying the panes may have the effect of triggering scrollbars to
// appear in the source editor, which would render the currently
@ -429,7 +431,7 @@ let DebuggerView = {
}, false);
} else {
this._stackframesAndBreakpoints.removeAttribute("animated");
this._variables.removeAttribute("animated");
this._variablesAndExpressions.removeAttribute("animated");
aFlags.callback && aFlags.callback();
}
},
@ -487,7 +489,7 @@ let DebuggerView = {
_editorSource: null,
_togglePanesButton: null,
_stackframesAndBreakpoints: null,
_variables: null,
_variablesAndExpressions: null,
_isInitialized: false,
_isDestroyed: false
};
@ -609,8 +611,8 @@ MenuContainer.prototype = {
* The actual internal value of the item.
* @param object aOptions [optional]
* Additional options or flags supported by this operation:
* - forced: true to force the item to be immediately added
* - unsorted: true if the items should not remain sorted
* - forced: true to force the item to be immediately appended
* - unsorted: true if the items should not always remain sorted
* - relaxed: true if this container should allow dupes & degenerates
* - description: an optional description of the item
* - attachment: some attached primitive/object
@ -626,6 +628,10 @@ MenuContainer.prototype = {
if (!aOptions.forced) {
this._stagedItems.push(item);
}
// Immediately insert the item at the specified index.
else if (aOptions.forced && aOptions.forced.atIndex !== undefined) {
return this._insertItemAt(aOptions.forced.atIndex, item, aOptions);
}
// Find the target position in this container and insert the item there.
else if (!aOptions.unsorted) {
return this._insertItemAt(this._findExpectedIndex(aLabel), item, aOptions);
@ -709,6 +715,18 @@ MenuContainer.prototype = {
this._stagedItems = [];
},
/**
* Toggles all the items in this container hidden or visible.
*
* @param boolean aVisibleFlag
* Specifies the intended visibility.
*/
toggleContents: function DVMC_toggleContents(aVisibleFlag) {
for (let [, item] of this._itemsByElement) {
item.target.hidden = !aVisibleFlag;
}
},
/**
* Does not remove any item in this container. Instead, it overrides the
* current label to signal that it is unavailable and removes the tooltip.
@ -840,6 +858,18 @@ MenuContainer.prototype = {
}
},
/**
* Gets the item in the container having the specified index.
*
* @param number aIndex
* The index used to identify the element.
* @return MenuItem
* The matched item, or null if nothing is found.
*/
getItemAtIndex: function DVMC_getItemAtIndex(aIndex) {
return this.getItemForElement(this._container.getItemAtIndex(aIndex));
},
/**
* Gets the item in the container having the specified label.
*
@ -908,6 +938,14 @@ MenuContainer.prototype = {
return values;
},
/**
* Gets the total items in this container.
* @return number
*/
get totalItems() {
return this._itemsByElement.size;
},
/**
* Gets the total visible (non-hidden) items in this container.
* @return number
@ -1097,6 +1135,7 @@ MenuContainer.prototype = {
*
* Custom methods introduced by this view, not necessary for a MenuContainer:
* set emptyText(aValue:string)
* set permaText(aValue:string)
* set itemType(aType:string)
* set itemFactory(aCallback:function)
*
@ -1107,7 +1146,6 @@ MenuContainer.prototype = {
*/
function StackList(aAssociatedNode) {
this._parent = aAssociatedNode;
this._appendEmptyNotice();
// Create an internal list container.
this._list = document.createElement("vbox");
@ -1319,6 +1357,18 @@ StackList.prototype = {
this._parent.removeEventListener(aName, aCallback, aBubbleFlag);
},
/**
* Sets the text displayed permanently in this container's header.
* @param string aValue
*/
set permaText(aValue) {
if (this._permaTextNode) {
this._permaTextNode.setAttribute("value", aValue);
}
this._permaTextValue = aValue;
this._appendPermaNotice();
},
/**
* Sets the text displayed in this container when there are no available items.
* @param string aValue
@ -1328,6 +1378,7 @@ StackList.prototype = {
this._emptyTextNode.setAttribute("value", aValue);
}
this._emptyTextValue = aValue;
this._appendEmptyNotice();
},
/**
@ -1369,11 +1420,27 @@ StackList.prototype = {
aElementNode.valueNode = valueNode;
},
/**
* Creates and appends a label displayed permanently in this container's header.
*/
_appendPermaNotice: function DVSL__appendPermaNotice() {
if (this._permaTextNode || !this._permaTextValue) {
return;
}
let label = document.createElement("label");
label.className = "empty list-item";
label.setAttribute("value", this._permaTextValue);
this._parent.insertBefore(label, this._list);
this._permaTextNode = label;
},
/**
* Creates and appends a label signaling that this container is empty.
*/
_appendEmptyNotice: function DVSL__appendEmptyNotice() {
if (this._emptyTextNode) {
if (this._emptyTextNode || !this._emptyTextValue) {
return;
}
@ -1401,6 +1468,8 @@ StackList.prototype = {
_list: null,
_selectedIndex: -1,
_selectedItem: null,
_permaTextNode: null,
_permaTextValue: "",
_emptyTextNode: null,
_emptyTextValue: ""
};

View File

@ -245,7 +245,11 @@
<splitter class="devtools-side-splitter"/>
<vbox id="editor" flex="1"/>
<splitter class="devtools-side-splitter"/>
<vbox id="variables"/>
<vbox id="variables+expressions">
<vbox id="expressions"/>
<splitter class="devtools-horizontal-splitter"/>
<vbox id="variables" flex="1"/>
</vbox>
</hbox>
</vbox>

View File

@ -74,6 +74,8 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_bug723071_editor-breakpoints-pane.js \
browser_dbg_bug740825_conditional-breakpoints-01.js \
browser_dbg_bug740825_conditional-breakpoints-02.js \
browser_dbg_bug727429_watch-expressions-01.js \
browser_dbg_bug727429_watch-expressions-02.js \
browser_dbg_bug731394_editor-contextmenu.js \
browser_dbg_bug786070_hide_nonenums.js \
browser_dbg_displayName.js \
@ -108,6 +110,7 @@ MOCHITEST_BROWSER_PAGES = \
browser_dbg_pause-exceptions.html \
browser_dbg_breakpoint-new-script.html \
browser_dbg_conditional-breakpoints.html \
browser_dbg_watch-expressions.html \
$(NULL)
MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES

View File

@ -0,0 +1,238 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Bug 727429: test the debugger watch expressions.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_watch-expressions.html";
let gPane = null;
let gTab = null;
let gDebuggee = null;
let gDebugger = null;
let gWatch = null;
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.contentWindow;
gWatch = gDebugger.DebuggerView.WatchExpressions;
gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
executeSoon(function() {
performTest();
});
});
function performTest()
{
is(gWatch.getExpressions().length, 0,
"There should initially be no watch expressions");
addAndCheckExpressions(1, 0, "a");
addAndCheckExpressions(2, 0, "b");
addAndCheckExpressions(3, 0, "c");
removeAndCheckExpression(2, 1, "a");
removeAndCheckExpression(1, 0, "a");
addAndCheckExpressions(2, 0, "", true);
gDebugger.editor.focus();
is(gWatch.getExpressions().length, 1,
"Empty watch expressions are automatically removed");
addAndCheckExpressions(2, 0, "a", true);
gDebugger.editor.focus();
is(gWatch.getExpressions().length, 1,
"Duplicate watch expressions are automatically removed");
addAndCheckCustomExpression(2, 0, "bazΩΩka");
addAndCheckCustomExpression(3, 0, "bambøøcha");
EventUtils.sendMouseEvent({ type: "click" },
gWatch.getItemAtIndex(0).target.closeNode,
gDebugger);
is(gWatch.getExpressions().length, 2,
"Watch expressions are removed when the close button is pressed");
is(gWatch.getExpressions()[0], "bazΩΩka",
"The expression at index " + 0 + " should be correct (1)");
is(gWatch.getExpressions()[1], "a",
"The expression at index " + 1 + " should be correct (2)");
EventUtils.sendMouseEvent({ type: "click" },
gWatch.getItemAtIndex(0).target.closeNode,
gDebugger);
is(gWatch.getExpressions().length, 1,
"Watch expressions are removed when the close button is pressed");
is(gWatch.getExpressions()[0], "a",
"The expression at index " + 0 + " should be correct (3)");
EventUtils.sendMouseEvent({ type: "click" },
gWatch.getItemAtIndex(0).target.closeNode,
gDebugger);
is(gWatch.getExpressions().length, 0,
"Watch expressions are removed when the close button is pressed");
EventUtils.sendMouseEvent({ type: "click" },
gWatch._container._parent,
gDebugger);
is(gWatch.getExpressions().length, 1,
"Watch expressions are added when the view container is pressed");
closeDebuggerAndFinish();
}
function addAndCheckCustomExpression(total, index, string, noBlur) {
addAndCheckExpressions(total, index, "", true);
for (let i = 0; i < string.length; i++) {
EventUtils.sendChar(string[i]);
}
gDebugger.editor.focus();
let id = gWatch.getItemAtIndex(index).attachment.id;
let element = gDebugger.document.getElementById("expression-" + id);
is(gWatch.getItemAtIndex(index).attachment.initialExpression, "",
"The initial expression at index " + index + " should be correct (1)");
is(gWatch.getItemForElement(element).attachment.initialExpression, "",
"The initial expression at index " + index + " should be correct (2)");
is(gWatch.getItemAtIndex(index).attachment.expression, string,
"The expression at index " + index + " should be correct (1)");
is(gWatch.getItemForElement(element).attachment.expression, string,
"The expression at index " + index + " should be correct (2)");
is(gWatch.getExpression(index), string,
"The expression at index " + index + " should be correct (3)");
is(gWatch.getExpressions()[index], string,
"The expression at index " + index + " should be correct (4)");
}
function addAndCheckExpressions(total, index, string, noBlur) {
gWatch.addExpression(string);
is(gWatch.getExpressions().length, total,
"There should be " + total + " watch expressions available (1)");
is(gWatch.totalItems, total,
"There should be " + total + " watch expressions available (2)");
ok(gWatch.getItemAtIndex(index),
"The expression at index " + index + " should be available");
ok(gWatch.getItemAtIndex(index).attachment.id,
"The expression at index " + index + " should have an id");
is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
"The expression at index " + index + " should have an initial expression");
let id = gWatch.getItemAtIndex(index).attachment.id;
let element = gDebugger.document.getElementById("expression-" + id);
ok(element,
"Three should be a new expression item in the view");
ok(gWatch.getItemForElement(element),
"The watch expression item should be accessible");
is(gWatch.getItemForElement(element), gWatch.getItemAtIndex(index),
"The correct watch expression item was accessed");
ok(gWatch.getItemAtIndex(index) instanceof gDebugger.MenuItem,
"The correct watch expression element was accessed (1)");
ok(gWatch._container.getItemAtIndex(index) instanceof XULElement,
"The correct watch expression element was accessed (2)");
is(element, gWatch._container.getItemAtIndex(index),
"The correct watch expression element was accessed (3)");
is(element.arrowNode.hidden, false,
"The arrow node should be visible");
is(element.closeNode.hidden, false,
"The close button should be visible");
is(element.inputNode.getAttribute("focused"), "true",
"The textbox input should be focused");
is(gWatch._variables.scrollTop, 0,
"The variables view should be scrolled to top");
is(gWatch._cache[0], gWatch.getItemAtIndex(index),
"The correct watch expression was added to the cache (1)");
is(gWatch._cache[0], gWatch.getItemForElement(element),
"The correct watch expression was added to the cache (2)");
if (!noBlur) {
gDebugger.editor.focus();
is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
"The initial expression at index " + index + " should be correct (1)");
is(gWatch.getItemForElement(element).attachment.initialExpression, string,
"The initial expression at index " + index + " should be correct (2)");
is(gWatch.getItemAtIndex(index).attachment.expression, string,
"The expression at index " + index + " should be correct (1)");
is(gWatch.getItemForElement(element).attachment.expression, string,
"The expression at index " + index + " should be correct (2)");
is(gWatch.getExpression(index), string,
"The expression at index " + index + " should be correct (3)");
is(gWatch.getExpressions()[index], string,
"The expression at index " + index + " should be correct (4)");
}
}
function removeAndCheckExpression(total, index, string) {
gWatch.removeExpression(index);
is(gWatch.getExpressions().length, total,
"There should be " + total + " watch expressions available (1)");
is(gWatch.totalItems, total,
"There should be " + total + " watch expressions available (2)");
ok(gWatch.getItemAtIndex(index),
"The expression at index " + index + " should still be available");
ok(gWatch.getItemAtIndex(index).attachment.id,
"The expression at index " + index + " should still have an id");
is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
"The expression at index " + index + " should still have an initial expression");
let id = gWatch.getItemAtIndex(index).attachment.id;
let element = gDebugger.document.getElementById("expression-" + id);
is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
"The initial expression at index " + index + " should be correct (1)");
is(gWatch.getItemForElement(element).attachment.initialExpression, string,
"The initial expression at index " + index + " should be correct (2)");
is(gWatch.getItemAtIndex(index).attachment.expression, string,
"The expression at index " + index + " should be correct (1)");
is(gWatch.getItemForElement(element).attachment.expression, string,
"The expression at index " + index + " should be correct (2)");
is(gWatch.getExpression(index), string,
"The expression at index " + index + " should be correct (3)");
is(gWatch.getExpressions()[index], string,
"The expression at index " + index + " should be correct (4)");
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
gWatch = null;
});
}

View File

@ -0,0 +1,275 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Bug 727429: test the debugger watch expressions.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_watch-expressions.html";
let gPane = null;
let gTab = null;
let gDebuggee = null;
let gDebugger = null;
let gWatch = null;
let gVars = null;
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.contentWindow;
gWatch = gDebugger.DebuggerView.WatchExpressions;
gVars = gDebugger.DebuggerView.Variables;
gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
addExpressions();
performTest();
});
function addExpressions()
{
gWatch.addExpression("'a'");
gWatch.addExpression("\"a\"");
gWatch.addExpression("'a\"\"'");
gWatch.addExpression("\"a''\"");
gWatch.addExpression("?");
gWatch.addExpression("a");
gWatch.addExpression("[1, 2, 3]");
gWatch.addExpression("x = [1, 2, 3]");
gWatch.addExpression("y = [1, 2, 3]; y.test = 4");
gWatch.addExpression("z = [1, 2, 3]; z.test = 4; z");
gWatch.addExpression("t = [1, 2, 3]; t.test = 4; !t");
gWatch.addExpression("encodeURI(\"\\\")");
gWatch.addExpression("decodeURI(\"\\\")");
}
function performTest()
{
is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
"There should be 0 hidden nodes in the watch expressions container");
is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 13,
"There should be 13 visible nodes in the watch expressions container");
test1(function() {
test2(function() {
test3(function() {
test4(function() {
test5(function() {
test6(function() {
test7(function() {
test8(function() {
test9(function() {
finishTest();
});
});
});
});
});
});
});
});
});
}
function finishTest()
{
is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
"There should be 0 hidden nodes in the watch expressions container");
is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 12,
"There should be 12 visible nodes in the watch expressions container");
closeDebuggerAndFinish();
}
function test1(callback) {
waitForWatchExpressions(function() {
info("Performing test1");
checkWatchExpressions("ReferenceError: a is not defined");
callback();
});
executeSoon(function() {
gDebuggee.ermahgerd(); // ermahgerd!!
});
}
function test2(callback) {
waitForWatchExpressions(function() {
info("Performing test2");
checkWatchExpressions(undefined);
callback();
});
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.getElementById("resume"),
gDebugger);
}
function test3(callback) {
waitForWatchExpressions(function() {
info("Performing test3");
checkWatchExpressions({ type: "object", class: "Object" });
callback();
});
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.getElementById("resume"),
gDebugger);
}
function test4(callback) {
waitForWatchExpressions(function() {
info("Performing test4");
checkWatchExpressions(5, 12);
callback();
});
executeSoon(function() {
gWatch.addExpression("a = 5");
EventUtils.sendKey("RETURN");
});
}
function test5(callback) {
waitForWatchExpressions(function() {
info("Performing test5");
checkWatchExpressions(5, 12);
callback();
});
executeSoon(function() {
gWatch.addExpression("encodeURI(\"\\\")");
EventUtils.sendKey("RETURN");
});
}
function test6(callback) {
waitForWatchExpressions(function() {
info("Performing test6");
checkWatchExpressions(5, 12);
callback();
})
executeSoon(function() {
gWatch.addExpression("decodeURI(\"\\\")");
EventUtils.sendKey("RETURN");
});
}
function test7(callback) {
waitForWatchExpressions(function() {
info("Performing test7");
checkWatchExpressions(5, 12);
callback();
});
executeSoon(function() {
gWatch.addExpression("?");
EventUtils.sendKey("RETURN");
});
}
function test8(callback) {
waitForWatchExpressions(function() {
info("Performing test8");
checkWatchExpressions(5, 12);
callback();
});
executeSoon(function() {
gWatch.addExpression("a");
EventUtils.sendKey("RETURN");
});
}
function test9(callback) {
waitForAfterFramesCleared(function() {
info("Performing test9");
callback();
});
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.getElementById("resume"),
gDebugger);
}
function waitForAfterFramesCleared(callback) {
gDebugger.addEventListener("Debugger:AfterFramesCleared", function onClear() {
gDebugger.removeEventListener("Debugger:AfterFramesCleared", onClear, false);
executeSoon(callback);
}, false);
}
function waitForWatchExpressions(callback) {
gDebugger.addEventListener("Debugger:FetchedWatchExpressions", function onFetch() {
gDebugger.removeEventListener("Debugger:FetchedWatchExpressions", onFetch, false);
executeSoon(callback);
}, false);
}
function checkWatchExpressions(expected, total = 11) {
is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, total,
"There should be " + total + " hidden nodes in the watch expressions container");
is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
"There should be 0 visible nodes in the watch expressions container");
let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
let scope = gVars._currHierarchy.get(label);
ok(scope, "There should be a wach expressions scope in the variables view");
is(scope._store.size, total, "There should be " + total + " evaluations availalble");
let w1 = scope.get("'a'");
let w2 = scope.get("\"a\"");
let w3 = scope.get("'a\"\"'");
let w4 = scope.get("\"a''\"");
let w5 = scope.get("?");
let w6 = scope.get("a");
let w7 = scope.get("x = [1, 2, 3]");
let w8 = scope.get("y = [1, 2, 3]; y.test = 4");
let w9 = scope.get("z = [1, 2, 3]; z.test = 4; z");
let w10 = scope.get("t = [1, 2, 3]; t.test = 4; !t");
let w11 = scope.get("encodeURI(\"\\\")");
let w12 = scope.get("decodeURI(\"\\\")");
ok(w1, "The first watch expression should be present in the scope");
ok(w2, "The second watch expression should be present in the scope");
ok(w3, "The third watch expression should be present in the scope");
ok(w4, "The fourth watch expression should be present in the scope");
ok(w5, "The fifth watch expression should be present in the scope");
ok(w6, "The sixth watch expression should be present in the scope");
ok(w7, "The seventh watch expression should be present in the scope");
ok(w8, "The eight watch expression should be present in the scope");
ok(w9, "The ninth watch expression should be present in the scope");
ok(w10, "The tenth watch expression should be present in the scope");
ok(!w11, "The eleventh watch expression should not be present in the scope");
ok(!w12, "The twelveth watch expression should not be present in the scope");
is(w1.value, "a", "The first value is correct");
is(w2.value, "a", "The second value is correct");
is(w3.value, "a\"\"", "The third value is correct");
is(w4.value, "a''", "The fourth value is correct");
is(w5.value, "SyntaxError: syntax error", "The fifth value is correct");
if (typeof expected == "object") {
is(w6.value.type, expected.type, "The sixth value type is correct");
is(w6.value.class, expected.class, "The sixth value class is correct");
} else {
is(w6.value, expected, "The sixth value is correct");
}
is(w7.value.type, "object", "The seventh value type is correct");
is(w7.value.class, "Array", "The seventh value class is correct");
is(w8.value, "4", "The eight value is correct");
is(w9.value.type, "object", "The ninth value type is correct");
is(w9.value.class, "Array", "The ninth value class is correct");
is(w10.value, false, "The tenth value is correct");
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
gWatch = null;
gVars = null;
});
}

View File

@ -226,10 +226,10 @@ function test()
conditionalExpression: "(function() { return false; })()"
});
}, {
conditionalExpression: "function() {}"
conditionalExpression: "(function() {})"
});
}, {
conditionalExpression: "{}"
conditionalExpression: "({})"
});
}, {
conditionalExpression: "/regexp/"

View File

@ -91,20 +91,20 @@ function testPaneCollapse1() {
}
function testPaneCollapse2() {
let variables =
gDebugger.document.getElementById("variables");
let variablesAndExpressions =
gDebugger.document.getElementById("variables+expressions");
let togglePanesButton =
gDebugger.document.getElementById("toggle-panes");
let width = parseInt(variables.getAttribute("width"));
let width = parseInt(variablesAndExpressions.getAttribute("width"));
is(width, gDebugger.Prefs.variablesWidth,
"The variables pane has an incorrect width.");
is(variables.style.marginRight, "0px",
"The variables pane has an incorrect right margin.");
ok(!variables.hasAttribute("animated"),
"The variables pane has an incorrect animated attribute.");
"The variables and expressions pane has an incorrect width.");
is(variablesAndExpressions.style.marginRight, "0px",
"The variables and expressions pane has an incorrect right margin.");
ok(!variablesAndExpressions.hasAttribute("animated"),
"The variables and expressions pane has an incorrect animated attribute.");
ok(!togglePanesButton.getAttribute("panesHidden"),
"The variables pane should at this point be visible.");
"The variables and expressions pane should at this point be visible.");
gView.togglePanes({ visible: false, animated: true });
@ -115,13 +115,13 @@ function testPaneCollapse2() {
let margin = -(width + 1) + "px";
is(width, gDebugger.Prefs.variablesWidth,
"The variables pane has an incorrect width after collapsing.");
is(variables.style.marginRight, margin,
"The variables pane has an incorrect right margin after collapsing.");
ok(variables.hasAttribute("animated"),
"The variables pane has an incorrect attribute after an animated collapsing.");
"The variables and expressions pane has an incorrect width after collapsing.");
is(variablesAndExpressions.style.marginRight, margin,
"The variables and expressions pane has an incorrect right margin after collapsing.");
ok(variablesAndExpressions.hasAttribute("animated"),
"The variables and expressions pane has an incorrect attribute after an animated collapsing.");
ok(togglePanesButton.hasAttribute("panesHidden"),
"The variables pane should not be visible after collapsing.");
"The variables and expressions pane should not be visible after collapsing.");
gView.togglePanes({ visible: true, animated: false });
@ -131,20 +131,20 @@ function testPaneCollapse2() {
"The options menu item should still not be checked.");
is(width, gDebugger.Prefs.variablesWidth,
"The variables pane has an incorrect width after uncollapsing.");
is(variables.style.marginRight, "0px",
"The variables pane has an incorrect right margin after uncollapsing.");
ok(!variables.hasAttribute("animated"),
"The variables pane has an incorrect attribute after an unanimated uncollapsing.");
"The variables and expressions pane has an incorrect width after uncollapsing.");
is(variablesAndExpressions.style.marginRight, "0px",
"The variables and expressions pane has an incorrect right margin after uncollapsing.");
ok(!variablesAndExpressions.hasAttribute("animated"),
"The variables and expressions pane has an incorrect attribute after an unanimated uncollapsing.");
ok(!togglePanesButton.getAttribute("panesHidden"),
"The variables pane should be visible again after uncollapsing.");
"The variables and expressions pane should be visible again after uncollapsing.");
}
function testPanesStartupPref(aCallback) {
let stackframesAndBrekpoints =
gDebugger.document.getElementById("stackframes+breakpoints");
let variables =
gDebugger.document.getElementById("variables");
let variablesAndExpressions =
gDebugger.document.getElementById("variables+expressions");
let togglePanesButton =
gDebugger.document.getElementById("toggle-panes");

View File

@ -43,7 +43,7 @@ function test() {
"The debugger preferences should have a saved variablesWidth value.");
stackframes = content.document.getElementById("stackframes+breakpoints");
variables = content.document.getElementById("variables");
variables = content.document.getElementById("variables+expressions");
is(content.Prefs.stackframesWidth, stackframes.getAttribute("width"),
"The stackframes pane width should be the same as the preferred value.");

View File

@ -0,0 +1,22 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<title>Browser Debugger Watch Expressions Test</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript">
function ermahgerd() {
debugger;
(function() {
var a = undefined;
debugger;
var a = {};
debugger;
}());
}
</script>
</head>
<body>
</body>
</html>

View File

@ -446,7 +446,15 @@ ResponsiveUI.prototype = {
let title = this.strings.GetStringFromName("responsiveUI.customNamePromptTitle");
let message = this.strings.formatStringFromName("responsiveUI.customNamePromptMsg", [w, h], 2);
Services.prompt.prompt(null, title, message, newName, null, {});
let promptOk = Services.prompt.prompt(null, title, message, newName, null, {});
if (!promptOk) {
// Prompt has been cancelled
let menuitem = this.customMenuitem;
this.menulist.selectedItem = menuitem;
this.currentPresetKey = this.customPreset.key;
return;
}
let newPreset = {
key: w + "x" + h,

View File

@ -18,8 +18,11 @@ function test() {
// Mocking prompt
oldPrompt = Services.prompt;
Services.prompt = {
value: "",
returnBool: true,
prompt: function(aParent, aDialogTitle, aText, aValue, aCheckMsg, aCheckState) {
aValue.value = "Testing preset";
aValue.value = this.value;
return this.returnBool;
}
};
@ -42,10 +45,23 @@ function test() {
}
function testAddCustomPreset() {
// Tries to add a custom preset and cancel the prompt
let idx = instance.menulist.selectedIndex;
let presetCount = instance.presets.length;
Services.prompt.value = "";
Services.prompt.returnBool = false;
instance.addbutton.doCommand();
is(idx, instance.menulist.selectedIndex, "selected item didn't change after add preset and cancel");
is(presetCount, instance.presets.length, "number of presets didn't change after add preset and cancel");
let customHeight = 123, customWidth = 456;
instance.setSize(customWidth, customHeight);
// Adds the custom preset with "Testing preset" as label (look at mock upper)
// Adds the custom preset with "Testing preset"
Services.prompt.value = "Testing preset";
Services.prompt.returnBool = true;
instance.addbutton.doCommand();
instance.menulist.selectedIndex = 1;

View File

@ -5,9 +5,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
const LAZY_EMPTY_DELAY = 150; // ms
Components.utils.import('resource://gre/modules/Services.jsm');
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
this.EXPORTED_SYMBOLS = ["VariablesView", "create"];
@ -140,7 +142,7 @@ VariablesView.prototype = {
this._enumVisible = aFlag;
for (let [, scope] in this) {
scope._nonEnumVisible = aFlag;
scope._enumVisible = aFlag;
}
},
@ -1119,7 +1121,7 @@ create({ constructor: Variable, proto: Scope.prototype }, {
let separatorLabel = this._separatorLabel = document.createElement("label");
separatorLabel.className = "plain";
separatorLabel.setAttribute("value", ":");
separatorLabel.setAttribute("value", this.ownerView.separator);
let valueLabel = this._valueLabel = document.createElement("label");
valueLabel.className = "value plain";
@ -1127,10 +1129,13 @@ create({ constructor: Variable, proto: Scope.prototype }, {
this._title.appendChild(separatorLabel);
this._title.appendChild(valueLabel);
if (VariablesView.isPrimitive(aDescriptor)) {
let isPrimitive = VariablesView.isPrimitive(aDescriptor);
let isUndefined = VariablesView.isUndefined(aDescriptor);
if (isPrimitive || isUndefined) {
this.hideArrow();
}
if (aDescriptor.get || aDescriptor.set) {
if (!isUndefined && (aDescriptor.get || aDescriptor.set)) {
this.addProperty("get", { value: aDescriptor.get });
this.addProperty("set", { value: aDescriptor.set });
this.expand();
@ -1493,6 +1498,33 @@ VariablesView.isPrimitive = function VV_isPrimitive(aDescriptor) {
return false;
};
/**
* Returns true if the descriptor represents an undefined value.
*
* @param object aDescriptor
* The variable's descriptor.
*/
VariablesView.isUndefined = function VV_isUndefined(aDescriptor) {
// For accessor property descriptors, the getter and setter need to be
// contained in 'get' and 'set' properties.
let getter = aDescriptor.get;
let setter = aDescriptor.set;
if (typeof getter == "object" && getter.type == "undefined" &&
typeof setter == "object" && setter.type == "undefined") {
return true;
}
// As described in the remote debugger protocol, the value grip
// must be contained in a 'value' property.
// For convenience, undefined is considered a type.
let grip = aDescriptor.value;
if (grip && grip.type == "undefined") {
return true;
}
return false;
};
/**
* Returns true if the descriptor represents a falsy value.
*
@ -1609,6 +1641,30 @@ VariablesView.getClass = function VV_getClass(aGrip) {
return "token-other";
};
/**
* Localization convenience methods.
*/
let L10N = {
/**
* L10N shortcut function.
*
* @param string aName
* @return string
*/
getStr: function L10N_getStr(aName) {
return this.stringBundle.GetStringFromName(aName);
}
};
XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
return Services.strings.createBundle(DBG_STRINGS_URI);
});
/**
* The separator label between the variables or properties name and value.
*/
Scope.prototype.separator = L10N.getStr("variablesSeparatorLabel");
/**
* A monotonically-increasing counter, that guarantees the uniqueness of scope,
* variables and properties ids.
@ -1620,9 +1676,9 @@ VariablesView.getClass = function VV_getClass(aGrip) {
*/
let generateId = (function() {
let count = 0;
return function(aName = "") {
return function VV_generateId(aName = "") {
return aName.toLowerCase().trim().replace(/\s+/g, "-") + (++count);
}
};
})();
/**

View File

@ -59,9 +59,9 @@ this.EXPORTED_SYMBOLS = ["CssRuleView",
/**
* ElementStyle maintains a list of Rule objects for a given element.
*
* @param Element aElement
* @param {Element} aElement
* The element whose style we are viewing.
* @param object aStore
* @param {object} aStore
* The ElementStyle can use this object to store metadata
* that might outlast the rule view, particularly the current
* set of disabled properties.
@ -189,7 +189,7 @@ ElementStyle.prototype = {
* @param {object} aOptions
* Options for creating the Rule, see the Rule constructor.
*
* @return true if we added the rule.
* @return {bool} true if we added the rule.
*/
_maybeAddRule: function ElementStyle_maybeAddRule(aOptions)
{
@ -305,7 +305,7 @@ ElementStyle.prototype = {
* @param {TextProperty} aProp
* The text property to update.
*
* @return True if the TextProperty's overridden state (or any of its
* @return {bool} true if the TextProperty's overridden state (or any of its
* computed properties overridden state) changed.
*/
_updatePropertyOverridden: function ElementStyle_updatePropertyOverridden(aProp)
@ -420,9 +420,8 @@ Rule.prototype = {
* Returns true if the rule matches the creation options
* specified.
*
* @param object aOptions
* Creation options. See the Rule constructor for
* documentation.
* @param {object} aOptions
* Creation options. See the Rule constructor for documentation.
*/
matches: function Rule_matches(aOptions)
{
@ -673,12 +672,12 @@ Rule.prototype = {
*
* If no existing properties match the property, nothing happens.
*
* @param TextProperty aNewProp
* @param {TextProperty} aNewProp
* The current version of the property, as parsed from the
* cssText in Rule._getTextProperties().
*
* @returns true if a property was updated, false if no properties
* were updated.
* @return {bool} true if a property was updated, false if no properties
* were updated.
*/
_updateTextProperty: function Rule__updateTextProperty(aNewProp) {
let match = { rank: 0, prop: null };
@ -801,7 +800,7 @@ TextProperty.prototype = {
* Set all the values from another TextProperty instance into
* this TextProperty instance.
*
* @param TextProperty aOther
* @param {TextProperty} aOther
* The other TextProperty instance.
*/
set: function TextProperty_set(aOther)
@ -866,9 +865,9 @@ TextProperty.prototype = {
* apply to a given element. After construction, the 'element'
* property will be available with the user interface.
*
* @param Document aDoc
* @param {Document} aDoc
* The document that will contain the rule view.
* @param object aStore
* @param {object} aStore
* The CSS rule view can use this object to store metadata
* that might outlast the rule view, particularly the current
* set of disabled properties.
@ -895,7 +894,7 @@ CssRuleView.prototype = {
_viewedElement: null,
/**
* Returns true if the rule view currently has an input editor visible.
* Return {bool} true if the rule view currently has an input editor visible.
*/
get isEditing() {
return this.element.querySelectorAll(".styleinspector-propertyeditor").length > 0;
@ -1120,7 +1119,8 @@ CssRuleView.prototype = {
* Update the rule view's context menu by disabling irrelevant menuitems and
* enabling relevant ones.
*
* @param aEvent The event object
* @param {Event} aEvent
* The event object.
*/
_onMenuUpdate: function CssRuleView_onMenuUpdate(aEvent)
{
@ -1159,7 +1159,8 @@ CssRuleView.prototype = {
/**
* Copy selected text from the rule view.
*
* @param aEvent The event object
* @param {Event} aEvent
* The event object.
*/
_onCopy: function CssRuleView_onCopy(aEvent)
{
@ -1194,7 +1195,8 @@ CssRuleView.prototype = {
/**
* Copy a rule from the rule view.
*
* @param aEvent The event object
* @param {Event} aEvent
* The event object.
*/
_onCopyRule: function CssRuleView_onCopyRule(aEvent)
{
@ -1247,7 +1249,8 @@ CssRuleView.prototype = {
/**
* Copy a declaration from the rule view.
*
* @param aEvent The event object
* @param {Event} aEvent
* The event object.
*/
_onCopyDeclaration: function CssRuleView_onCopyDeclaration(aEvent)
{
@ -1286,7 +1289,8 @@ CssRuleView.prototype = {
/**
* Copy a property name from the rule view.
*
* @param aEvent The event object
* @param {Event} aEvent
* The event object.
*/
_onCopyProperty: function CssRuleView_onCopyProperty(aEvent)
{
@ -1308,7 +1312,8 @@ CssRuleView.prototype = {
/**
* Copy a property value from the rule view.
*
* @param aEvent The event object
* @param {Event} aEvent
* The event object.
*/
_onCopyPropertyValue: function CssRuleView_onCopyPropertyValue(aEvent)
{
@ -1330,9 +1335,9 @@ CssRuleView.prototype = {
/**
* Create a RuleEditor.
*
* @param CssRuleView aRuleView
* @param {CssRuleView} aRuleView
* The CssRuleView containg the document holding this rule editor.
* @param Rule aRule
* @param {Rule} aRule
* The Rule object we're editing.
* @constructor
*/
@ -1451,7 +1456,8 @@ RuleEditor.prototype = {
textContent: ", "
});
}
let cls = element.mozMatchesSelector(selector) ? "ruleview-selector-matched" : "ruleview-selector-unmatched";
let cls = element.mozMatchesSelector(selector) ? "ruleview-selector-matched" :
"ruleview-selector-unmatched";
createChild(this.selectorText, "span", {
class: cls,
textContent: selector
@ -1472,11 +1478,11 @@ RuleEditor.prototype = {
/**
* Programatically add a new property to the rule.
*
* @param string aName
* @param {string} aName
* Property name.
* @param string aValue
* @param {string} aValue
* Property value.
* @param string aPriority
* @param {string} aPriority
* Property priority.
*/
addProperty: function RuleEditor_addProperty(aName, aValue, aPriority)
@ -1524,9 +1530,9 @@ RuleEditor.prototype = {
* Called when the new property input has been dismissed.
* Will create a new TextProperty if necessary.
*
* @param string aValue
* @param {string} aValue
* The value in the editor.
* @param bool aCommit
* @param {bool} aCommit
* True if the value should be committed.
*/
_onNewProperty: function RuleEditor__onNewProperty(aValue, aCommit)
@ -1836,7 +1842,7 @@ TextPropertyEditor.prototype = {
*
* @param {string} aValue
* The value from the text editor.
* @return an object with 'value' and 'priority' properties.
* @return {object} an object with 'value' and 'priority' properties.
*/
_parseValue: function TextPropertyEditor_parseValue(aValue)
{
@ -1853,7 +1859,7 @@ TextPropertyEditor.prototype = {
*
* @param {string} aValue
* The value contained in the editor.
* @param {boolean} aCommit
* @param {bool} aCommit
* True if the change should be applied.
*/
_onValueDone: function PropertyEditor_onValueDone(aValue, aCommit)
@ -1871,12 +1877,11 @@ TextPropertyEditor.prototype = {
/**
* Validate this property.
*
* @param {String} [aValue]
* @param {string} [aValue]
* Override the actual property value used for validation without
* applying property values e.g. validate as you type.
*
* @returns {Boolean}
* True if the property value is valid, false otherwise.
* @return {bool} true if the property value is valid, false otherwise.
*/
_validate: function TextPropertyEditor_validate(aValue)
{
@ -1945,12 +1950,12 @@ function editableField(aOptions)
* clicks and sit in the editing tab order, and call
* a callback when it is activated.
*
* @param object aOptions
* @param {object} aOptions
* The options for this editor, including:
* {Element} element: The DOM element.
* {string} trigger: The DOM event that should trigger editing,
* defaults to "click"
* @param function aCallback
* @param {function} aCallback
* Called when the editor is activated.
*/
this.editableItem = function editableItem(aOptions, aCallback)
@ -2159,6 +2164,355 @@ InplaceEditor.prototype = {
this.input.style.width = width + "px";
},
/**
* Increment property values in rule view.
*
* @param {number} increment
* The amount to increase/decrease the property value.
* @return {bool} true if value has been incremented.
*/
_incrementValue: function InplaceEditor_incrementValue(increment)
{
let value = this.input.value;
let selectionStart = this.input.selectionStart;
let selectionEnd = this.input.selectionEnd;
let newValue = this._incrementCSSValue(value, increment, selectionStart, selectionEnd);
if (!newValue) {
return false;
}
this.input.value = newValue.value;
this.input.setSelectionRange(newValue.start, newValue.end);
return true;
},
/**
* Increment the property value based on the property type.
*
* @param {string} value
* Property value.
* @param {number} increment
* Amount to increase/decrease the property value.
* @param {number} selStart
* Starting index of the value.
* @param {number} selEnd
* Ending index of the value.
* @return {object} object with properties 'value', 'start', and 'end'.
*/
_incrementCSSValue: function InplaceEditor_incrementCSSValue(value, increment, selStart,
selEnd)
{
let range = this._parseCSSValue(value, selStart);
let type = (range && range.type) || "";
let rawValue = (range ? value.substring(range.start, range.end) : "");
let incrementedValue = null, selection;
if (type === "num") {
let newValue = this._incrementRawValue(rawValue, increment);
if (newValue !== null) {
incrementedValue = newValue;
selection = [0, incrementedValue.length];
}
} else if (type === "hex") {
let exprOffset = selStart - range.start;
let exprOffsetEnd = selEnd - range.start;
let newValue = this._incHexColor(rawValue, increment, exprOffset, exprOffsetEnd);
if (newValue) {
incrementedValue = newValue.value;
selection = newValue.selection;
}
} else {
let info;
if (type === "rgb" || type === "hsl") {
info = {};
let part = value.substring(range.start, selStart).split(",").length - 1;
if (part === 3) { // alpha
info.minValue = 0;
info.maxValue = 1;
} else if (type === "rgb") {
info.minValue = 0;
info.maxValue = 255;
} else if (part !== 0) { // hsl percentage
info.minValue = 0;
info.maxValue = 100;
// select the previous number if the selection is at the end of a percentage sign
if (value.charAt(selStart - 1) === "%") {
--selStart;
}
}
}
return this._incrementGenericValue(value, increment, selStart, selEnd, info);
}
if (incrementedValue === null) {
return;
}
let preRawValue = value.substr(0, range.start);
let postRawValue = value.substr(range.end);
return {
value: preRawValue + incrementedValue + postRawValue,
start: range.start + selection[0],
end: range.start + selection[1]
};
},
/**
* Parses the property value and type.
*
* @param {string} value
* Property value.
* @param {number} offset
* Starting index of value.
* @return {object} object with properties 'value', 'start', 'end', and 'type'.
*/
_parseCSSValue: function InplaceEditor_parseCSSValue(value, offset)
{
const reSplitCSS = /(url\("?[^"\)]+"?\)?)|(rgba?\([^)]*\)?)|(hsla?\([^)]*\)?)|(#[\dA-Fa-f]+)|(-?\d+(\.\d+)?(%|[a-z]{1,4})?)|"([^"]*)"?|'([^']*)'?|([^,\s\/!\(\)]+)|(!(.*)?)/;
let start = 0;
let m;
// retreive values from left to right until we find the one at our offset
while ((m = reSplitCSS.exec(value)) &&
(m.index + m[0].length < offset)) {
value = value.substr(m.index + m[0].length);
start += m.index + m[0].length;
offset -= m.index + m[0].length;
}
if (!m) {
return;
}
let type;
if (m[1]) {
type = "url";
} else if (m[2]) {
type = "rgb";
} else if (m[3]) {
type = "hsl";
} else if (m[4]) {
type = "hex";
} else if (m[5]) {
type = "num";
}
return {
value: m[0],
start: start + m.index,
end: start + m.index + m[0].length,
type: type
};
},
/**
* Increment the property value for types other than
* number or hex, such as rgb, hsl, and file names.
*
* @param {string} value
* Property value.
* @param {number} increment
* Amount to increment/decrement.
* @param {number} offset
* Starting index of the property value.
* @param {number} offsetEnd
* Ending index of the property value.
* @param {object} info
* Object with details about the property value.
* @return {object} object with properties 'value', 'start', and 'end'.
*/
_incrementGenericValue: function InplaceEditor_incrementGenericValue(value, increment, offset,
offsetEnd, info)
{
// Try to find a number around the cursor to increment.
let start, end;
// Check if we are incrementing in a non-number context (such as a URL)
if (/^-?[0-9.]/.test(value.substring(offset, offsetEnd)) &&
!(/\d/.test(value.charAt(offset - 1) + value.charAt(offsetEnd)))) {
// We have a number selected, possibly with a suffix, and we are not in
// the disallowed case of just part of a known number being selected.
// Use that number.
start = offset;
end = offsetEnd;
} else {
// Parse periods as belonging to the number only if we are in a known number
// context. (This makes incrementing the 1 in 'image1.gif' work.)
let pattern = "[" + (info ? "0-9." : "0-9") + "]*";
let before = new RegExp(pattern + "$").exec(value.substr(0, offset))[0].length;
let after = new RegExp("^" + pattern).exec(value.substr(offset))[0].length;
start = offset - before;
end = offset + after;
// Expand the number to contain an initial minus sign if it seems
// free-standing.
if (value.charAt(start - 1) === "-" &&
(start - 1 === 0 || /[ (:,='"]/.test(value.charAt(start - 2)))) {
--start;
}
}
if (start !== end)
{
// Include percentages as part of the incremented number (they are
// common enough).
if (value.charAt(end) === "%") {
++end;
}
let first = value.substr(0, start);
let mid = value.substring(start, end);
let last = value.substr(end);
mid = this._incrementRawValue(mid, increment, info);
if (mid !== null) {
return {
value: first + mid + last,
start: start,
end: start + mid.length
};
}
}
},
/**
* Increment the property value for numbers.
*
* @param {string} rawValue
* Raw value to increment.
* @param {number} increment
* Amount to increase/decrease the raw value.
* @param {object} info
* Object with info about the property value.
* @return {string} the incremented value.
*/
_incrementRawValue: function InplaceEditor_incrementRawValue(rawValue, increment, info)
{
let num = parseFloat(rawValue);
if (isNaN(num)) {
return null;
}
let number = /\d+(\.\d+)?/.exec(rawValue);
let units = rawValue.substr(number.index + number[0].length);
// avoid rounding errors
let newValue = Math.round((num + increment) * 1000) / 1000;
if (info && "minValue" in info) {
newValue = Math.max(newValue, info.minValue);
}
if (info && "maxValue" in info) {
newValue = Math.min(newValue, info.maxValue);
}
newValue = newValue.toString();
return newValue + units;
},
/**
* Increment the property value for hex.
*
* @param {string} value
* Property value.
* @param {number} increment
* Amount to increase/decrease the property value.
* @param {number} offset
* Starting index of the property value.
* @param {number} offsetEnd
* Ending index of the property value.
* @return {object} object with properties 'value' and 'selection'.
*/
_incHexColor: function InplaceEditor_incHexColor(rawValue, increment, offset, offsetEnd)
{
// Return early if no part of the rawValue is selected.
if (offsetEnd > rawValue.length && offset >= rawValue.length) {
return;
}
if (offset < 1 && offsetEnd <= 1) {
return;
}
// Ignore the leading #.
rawValue = rawValue.substr(1);
--offset;
--offsetEnd;
// Clamp the selection to within the actual value.
offset = Math.max(offset, 0);
offsetEnd = Math.min(offsetEnd, rawValue.length);
offsetEnd = Math.max(offsetEnd, offset);
// Normalize #ABC -> #AABBCC.
if (rawValue.length === 3) {
rawValue = rawValue.charAt(0) + rawValue.charAt(0) +
rawValue.charAt(1) + rawValue.charAt(1) +
rawValue.charAt(2) + rawValue.charAt(2);
offset *= 2;
offsetEnd *= 2;
}
if (rawValue.length !== 6) {
return;
}
// If no selection, increment an adjacent color, preferably one to the left.
if (offset === offsetEnd) {
if (offset === 0) {
offsetEnd = 1;
} else {
offset = offsetEnd - 1;
}
}
// Make the selection cover entire parts.
offset -= offset % 2;
offsetEnd += offsetEnd % 2;
// Remap the increments from [0.1, 1, 10] to [1, 1, 16].
if (-1 < increment && increment < 1) {
increment = (increment < 0 ? -1 : 1);
}
if (Math.abs(increment) === 10) {
increment = (increment < 0 ? -16 : 16);
}
let isUpper = (rawValue.toUpperCase() === rawValue);
for (let pos = offset; pos < offsetEnd; pos += 2) {
// Increment the part in [pos, pos+2).
let mid = rawValue.substr(pos, 2);
let value = parseInt(mid, 16);
if (isNaN(value)) {
return;
}
mid = Math.min(Math.max(value + increment, 0), 255).toString(16);
while (mid.length < 2) {
mid = "0" + mid;
}
if (isUpper) {
mid = mid.toUpperCase();
}
rawValue = rawValue.substr(0, pos) + mid + rawValue.substr(pos + 2);
}
return {
value: "#" + rawValue,
selection: [offset + 1, offsetEnd + 1]
};
},
/**
* Call the client's done handler and clear out.
*/
@ -2174,6 +2528,7 @@ InplaceEditor.prototype = {
let val = this.input.value.trim();
return this.done(this.cancelled ? this.initial : val, !this.cancelled);
}
return null;
},
@ -2188,9 +2543,43 @@ InplaceEditor.prototype = {
}
},
/**
* Handle the input field's keypress event.
*/
_onKeyPress: function InplaceEditor_onKeyPress(aEvent)
{
let prevent = false;
const largeIncrement = 100;
const mediumIncrement = 10;
const smallIncrement = 0.1;
let increment = 0;
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
increment = 1;
} else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
increment = -1;
}
if (aEvent.shiftKey && !aEvent.altKey) {
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
increment *= largeIncrement;
} else {
increment *= mediumIncrement;
}
} else if (aEvent.altKey && !aEvent.shiftKey) {
increment *= smallIncrement;
}
if (increment && this._incrementValue(increment) ) {
this._updateSize();
prevent = true;
}
if (this.multiline &&
aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN &&
aEvent.shiftKey) {
@ -2258,7 +2647,7 @@ InplaceEditor.prototype = {
},
/**
* Handle changes the input text.
* Handle changes to the input text.
*/
_onInput: function InplaceEditor_onInput(aEvent)
{
@ -2285,7 +2674,10 @@ InplaceEditor.prototype = {
* own compartment, those expandos live on Xray wrappers that are only visible
* within this JSM. So we provide a little workaround here.
*/
this._getInplaceEditorForSpan = function _getInplaceEditorForSpan(aSpan) { return aSpan.inplaceEditor; };
this._getInplaceEditorForSpan = function _getInplaceEditorForSpan(aSpan)
{
return aSpan.inplaceEditor;
};
/**
* Store of CSSStyleDeclarations mapped to properties that have been changed by
@ -2304,13 +2696,13 @@ UserProperties.prototype = {
*
* @param {CSSStyleDeclaration} aStyle
* The CSSStyleDeclaration against which the property is mapped.
* @param {String} aName
* @param {string} aName
* The name of the property to get.
* @param {String} aComputedValue
* @param {string} aComputedValue
* The computed value of the property. The user value will only be
* returned if the computed value hasn't changed since, and this will
* be returned as the default if no user value is available.
* @returns {String}
* @return {string}
* The property value if it has previously been set by the user, null
* otherwise.
*/

View File

@ -36,6 +36,7 @@ MOCHITEST_BROWSER_FILES = \
browser_computedview_bug_703643_context_menu_copy.js \
browser_ruleview_734259_style_editor_link.js \
browser_computedview_734259_style_editor_link.js \
browser_bug722691_rule_view_increment.js \
head.js \
$(NULL)

View File

@ -0,0 +1,205 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that increasing/decreasing values in rule view using
// arrow keys works correctly.
let tempScope = {};
Cu.import("resource:///modules/devtools/CssRuleView.jsm", tempScope);
let CssRuleView = tempScope.CssRuleView;
let _ElementStyle = tempScope._ElementStyle;
let _editableField = tempScope._editableField;
let inplaceEditor = tempScope._getInplaceEditorForSpan;
let doc;
let ruleDialog;
let ruleView;
function setUpTests()
{
doc.body.innerHTML = '<div id="test" style="' +
'margin-top:0px;' +
'padding-top: 0px;' +
'color:#000000;' +
'background-color: #000000; >"'+
'</div>';
let testElement = doc.getElementById("test");
ruleDialog = openDialog("chrome://browser/content/devtools/cssruleview.xul",
"cssruleviewtest",
"width=350,height=350");
ruleDialog.addEventListener("load", function onLoad(evt) {
ruleDialog.removeEventListener("load", onLoad, true);
let doc = ruleDialog.document;
ruleView = new CssRuleView(doc);
doc.documentElement.appendChild(ruleView.element);
ruleView.highlight(testElement);
waitForFocus(runTests, ruleDialog);
}, true);
}
function runTests()
{
let idRuleEditor = ruleView.element.children[0]._ruleEditor;
let marginPropEditor = idRuleEditor.rule.textProps[0].editor;
let paddingPropEditor = idRuleEditor.rule.textProps[1].editor;
let hexColorPropEditor = idRuleEditor.rule.textProps[2].editor;
let rgbColorPropEditor = idRuleEditor.rule.textProps[3].editor;
(function() {
info("INCREMENTS");
newTest( marginPropEditor, {
1: { alt: true, start: "0px", end: "0.1px", selectAll: true },
2: { start: "0px", end: "1px", selectAll: true },
3: { shift: true, start: "0px", end: "10px", selectAll: true },
4: { down: true, alt: true, start: "0.1px", end: "0px", selectAll: true },
5: { down: true, start: "0px", end: "-1px", selectAll: true },
6: { down: true, shift: true, start: "0px", end: "-10px", selectAll: true },
7: { pageUp: true, shift: true, start: "0px", end: "100px", selectAll: true },
8: { pageDown: true, shift: true, start: "0px", end: "-100px", selectAll: true,
nextTest: test2 }
});
EventUtils.synthesizeMouse(marginPropEditor.valueSpan, 1, 1, {}, ruleDialog);
})();
function test2() {
info("UNITS");
newTest( paddingPropEditor, {
1: { start: "0px", end: "1px", selectAll: true },
2: { start: "0pt", end: "1pt", selectAll: true },
3: { start: "0pc", end: "1pc", selectAll: true },
4: { start: "0em", end: "1em", selectAll: true },
5: { start: "0%", end: "1%", selectAll: true },
6: { start: "0in", end: "1in", selectAll: true },
7: { start: "0cm", end: "1cm", selectAll: true },
8: { start: "0mm", end: "1mm", selectAll: true },
9: { start: "0ex", end: "1ex", selectAll: true,
nextTest: test3 }
});
EventUtils.synthesizeMouse(paddingPropEditor.valueSpan, 1, 1, {}, ruleDialog);
};
function test3() {
info("HEX COLORS");
newTest( hexColorPropEditor, {
1: { start: "#CCCCCC", end: "#CDCDCD", selectAll: true},
2: { shift: true, start: "#CCCCCC", end: "#DCDCDC", selectAll: true },
3: { start: "#CCCCCC", end: "#CDCCCC", selection: [1,3] },
4: { shift: true, start: "#CCCCCC", end: "#DCCCCC", selection: [1,3] },
5: { start: "#FFFFFF", end: "#FFFFFF", selectAll: true },
6: { down: true, shift: true, start: "#000000", end: "#000000", selectAll: true,
nextTest: test4 }
});
EventUtils.synthesizeMouse(hexColorPropEditor.valueSpan, 1, 1, {}, ruleDialog);
};
function test4() {
info("RGB COLORS");
newTest( rgbColorPropEditor, {
1: { start: "rgb(0,0,0)", end: "rgb(0,1,0)", selection: [6,7] },
2: { shift: true, start: "rgb(0,0,0)", end: "rgb(0,10,0)", selection: [6,7] },
3: { start: "rgb(0,255,0)", end: "rgb(0,255,0)", selection: [6,9] },
4: { shift: true, start: "rgb(0,250,0)", end: "rgb(0,255,0)", selection: [6,9] },
5: { down: true, start: "rgb(0,0,0)", end: "rgb(0,0,0)", selection: [6,7] },
6: { down: true, shift: true, start: "rgb(0,5,0)", end: "rgb(0,0,0)", selection: [6,7],
nextTest: test5 }
});
EventUtils.synthesizeMouse(rgbColorPropEditor.valueSpan, 1, 1, {}, ruleDialog);
};
function test5() {
info("SHORTHAND");
newTest( paddingPropEditor, {
1: { start: "0px 0px 0px 0px", end: "0px 1px 0px 0px", selection: [4,7] },
2: { shift: true, start: "0px 0px 0px 0px", end: "0px 10px 0px 0px", selection: [4,7] },
3: { start: "0px 0px 0px 0px", end: "1px 0px 0px 0px", selectAll: true },
4: { shift: true, start: "0px 0px 0px 0px", end: "10px 0px 0px 0px", selectAll: true },
5: { down: true, start: "0px 0px 0px 0px", end: "0px 0px -1px 0px", selection: [8,11] },
6: { down: true, shift: true, start: "0px 0px 0px 0px", end: "-10px 0px 0px 0px", selectAll: true,
nextTest: test6 }
});
EventUtils.synthesizeMouse(paddingPropEditor.valueSpan, 1, 1, {}, ruleDialog);
};
function test6() {
info("ODD CASES");
newTest( marginPropEditor, {
1: { start: "98.7%", end: "99.7%", selection: [3,3] },
2: { alt: true, start: "98.7%", end: "98.8%", selection: [3,3] },
3: { start: "0", end: "1" },
4: { down: true, start: "0", end: "-1" },
5: { start: "'a=-1'", end: "'a=0'", selection: [4,4] },
6: { start: "0 -1px", end: "0 0px", selection: [2,2] },
7: { start: "url(-1)", end: "url(-1)", selection: [4,4] },
8: { start: "url('test1.1.png')", end: "url('test1.2.png')", selection: [11,11] },
9: { start: "url('test1.png')", end: "url('test2.png')", selection: [9,9] },
10: { shift: true, start: "url('test1.1.png')", end: "url('test11.1.png')", selection: [9,9] },
11: { down: true, start: "url('test-1.png')", end: "url('test-2.png')", selection: [9,11] },
12: { start: "url('test1.1.png')", end: "url('test1.2.png')", selection: [11,12] },
13: { down: true, alt: true, start: "url('test-0.png')", end: "url('test--0.1.png')", selection: [10,11] },
14: { alt: true, start: "url('test--0.1.png')", end: "url('test-0.png')", selection: [10,14],
endTest: true }
});
EventUtils.synthesizeMouse(marginPropEditor.valueSpan, 1, 1, {}, ruleDialog);
};
}
function newTest( propEditor, tests )
{
waitForEditorFocus(propEditor.element, function onElementFocus(aEditor) {
for( test in tests) {
testIncrement( aEditor, tests[test] );
}
}, false);
}
function testIncrement( aEditor, aOptions )
{
aEditor.input.value = aOptions.start;
let input = aEditor.input;
if ( aOptions.selectAll ) {
input.select();
} else if ( aOptions.selection ) {
input.setSelectionRange(aOptions.selection[0], aOptions.selection[1]);
}
is(input.value, aOptions.start, "Value initialized at " + aOptions.start);
input.addEventListener("keyup", function onIncrementUp() {
input.removeEventListener("keyup", onIncrementUp, false);
input = aEditor.input;
is(input.value, aOptions.end, "Value changed to " + aOptions.end);
if( aOptions.nextTest) {
aOptions.nextTest();
}
else if( aOptions.endTest ) {
finishTest();
}
}, false);
let key;
key = ( aOptions.down ) ? "VK_DOWN" : "VK_UP";
key = ( aOptions.pageDown ) ? "VK_PAGE_DOWN" : ( aOptions.pageUp ) ? "VK_PAGE_UP" : key;
EventUtils.synthesizeKey(key,
{altKey: aOptions.alt, shiftKey: aOptions.shift},
ruleDialog);
}
function finishTest()
{
ruleView.clear();
ruleDialog.close();
ruleDialog = ruleView = null;
doc = null;
gBrowser.removeCurrentTab();
finish();
}
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, onload, true);
doc = content.document;
waitForFocus(setUpTests, content);
}, true);
content.location = "data:text/html,sample document for bug 722691";
}

View File

@ -77,11 +77,11 @@ stepOutTooltip=Step Out (%S)
# LOCALIZATION NOTE (emptyStackText): The text that is displayed in the stack
# frames list when there are no frames to display.
emptyStackText=No stacks to display.
emptyStackText=No stacks to display
# LOCALIZATION NOTE (emptyBreakpointsText): The text that is displayed in the
# breakpoints list when there are no breakpoints to display.
emptyBreakpointsText=No breakpoints to display.
emptyBreakpointsText=No breakpoints to display
# LOCALIZATION NOTE (emptyGlobalsText): The text to display in the menulist
# when there are no chrome globals available.
@ -150,16 +150,33 @@ breakpointMenuItem.deleteAll=Remove all breakpoints
# yet.
loadingText=Loading\u2026
# LOCALIZATION NOTE (emptyStackText): The text that is displayed in the watch
# expressions list to add a new item.
addWatchExpressionText=Add watch expression
# LOCALIZATION NOTE (emptyVariablesText): The text that is displayed in the
# variables pane when there are no variables to display.
emptyVariablesText=No variables to display.
emptyVariablesText=No variables to display
# LOCALIZATION NOTE (scopeLabel): The text that is displayed in the variables
# pane as a header for each variable scope (e.g. "Global scope, "With scope",
# etc.).
scopeLabel=%S scope
# LOCALIZATION NOTE (watchExpressionsScopeLabel): The name of the watch
# expressions scope. This text is displayed in the variables pane as a header for
# the watch expressions scope.
watchExpressionsScopeLabel=Watch expressions
# LOCALIZATION NOTE (globalScopeLabel): The name of the global scope. This text
# is added to scopeLabel and displayed in the variables pane as a header for
# the global scope.
globalScopeLabel=Global
# LOCALIZATION NOTE (variablesSeparatorLabel): The text that is displayed
# in the variables list as a separator between the name and value.
variablesSeparatorLabel=:
# LOCALIZATION NOTE (watchExpressionsSeparatorLabel): The text that is displayed
# in the watch expressions list as a separator between the code and evaluation.
watchExpressionsSeparatorLabel=\ →

View File

@ -35,7 +35,7 @@
.list-item.empty {
color: GrayText;
padding: 4px;
padding: 2px;
}
/**
@ -130,6 +130,19 @@
transition: margin 0.25s ease-in-out;
}
/**
* Variables and watch expressions pane
*/
#variables\+expressions {
background-color: white;
min-width: 50px;
}
#variables\+expressions[animated] {
transition: margin 0.25s ease-in-out;
}
/**
* Stack frames view
*/
@ -182,17 +195,45 @@
margin: 0 0 -2px 0;
}
/**
* Watch expressions view
*/
#expressions {
background-color: white;
min-height: 10px;
}
.dbg-expression {
height: 20px;
-moz-padding-start: 8px;
}
.dbg-expression:last-child {
margin-bottom: 4px;
}
.dbg-expression-arrow {
width: 10px;
height: auto;
background: url("chrome://browser/skin/devtools/commandline.png") 0px 4px no-repeat;
}
.dbg-expression-input {
font: 9pt monospace;
}
.dbg-expression-delete {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
/**
* Variables view
*/
#variables {
background-color: white;
min-width: 50px;
}
#variables[animated] {
transition: margin 0.25s ease-in-out;
min-height: 10px;
}
/**

View File

@ -37,7 +37,7 @@
.list-item.empty {
color: GrayText;
padding: 4px;
padding: 2px;
}
/**
@ -132,6 +132,19 @@
transition: margin 0.25s ease-in-out;
}
/**
* Variables and watch expressions pane
*/
#variables\+expressions {
background-color: white;
min-width: 50px;
}
#variables\+expressions[animated] {
transition: margin 0.25s ease-in-out;
}
/**
* Stack frames view
*/
@ -184,17 +197,45 @@
margin: 0 0 -2px 0;
}
/**
* Watch expressions view
*/
#expressions {
background-color: white;
min-height: 10px;
}
.dbg-expression {
height: 20px;
-moz-padding-start: 8px;
}
.dbg-expression:last-child {
margin-bottom: 4px;
}
.dbg-expression-arrow {
width: 10px;
height: auto;
background: url("chrome://browser/skin/devtools/commandline.png") 0px 4px no-repeat;
}
.dbg-expression-input {
font: 9pt monospace;
}
.dbg-expression-delete {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
/**
* Variables view
*/
#variables {
background-color: white;
min-width: 50px;
}
#variables[animated] {
transition: margin 0.25s ease-in-out;
min-height: 10px;
}
/**

View File

@ -43,7 +43,7 @@
.list-item.empty {
color: GrayText;
padding: 4px;
padding: 2px;
}
/**
@ -138,6 +138,19 @@
transition: margin 0.25s ease-in-out;
}
/**
* Variables and watch expressions pane
*/
#variables\+expressions {
background-color: white;
min-width: 50px;
}
#variables\+expressions[animated] {
transition: margin 0.25s ease-in-out;
}
/**
* Stack frames view
*/
@ -190,17 +203,45 @@
margin: 0 0 -2px 0;
}
/**
* Watch expressions view
*/
#expressions {
background-color: white;
min-height: 10px;
}
.dbg-expression {
height: 20px;
-moz-padding-start: 8px;
}
.dbg-expression:last-child {
margin-bottom: 4px;
}
.dbg-expression-arrow {
width: 10px;
height: auto;
background: url("chrome://browser/skin/devtools/commandline.png") 0px 4px no-repeat;
}
.dbg-expression-input {
font: 9pt monospace;
}
.dbg-expression-delete {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
/**
* Variables view
*/
#variables {
background-color: white;
min-width: 50px;
}
#variables[animated] {
transition: margin 0.25s ease-in-out;
min-height: 10px;
}
/**