diff options
Diffstat (limited to 'devtools/client/debugger/views/watch-expressions-view.js')
-rw-r--r-- | devtools/client/debugger/views/watch-expressions-view.js | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/devtools/client/debugger/views/watch-expressions-view.js b/devtools/client/debugger/views/watch-expressions-view.js new file mode 100644 index 000000000..59d3ad5a0 --- /dev/null +++ b/devtools/client/debugger/views/watch-expressions-view.js @@ -0,0 +1,303 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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/. */ +/* import-globals-from ../debugger-controller.js */ +/* import-globals-from ../debugger-view.js */ +/* import-globals-from ../utils.js */ +/* globals document */ +"use strict"; + +const {KeyCodes} = require("devtools/client/shared/keycodes"); + +/** + * Functions handling the watch expressions UI. + */ +function WatchExpressionsView(DebuggerController, DebuggerView) { + dumpn("WatchExpressionsView was instantiated"); + + this.StackFrames = DebuggerController.StackFrames; + this.DebuggerView = DebuggerView; + + this.switchExpression = this.switchExpression.bind(this); + this.deleteExpression = this.deleteExpression.bind(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); +} + +WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, { + /** + * Initialization function, called when the debugger is started. + */ + initialize: function () { + dumpn("Initializing the WatchExpressionsView"); + + this.widget = new SimpleListWidget(document.getElementById("expressions")); + this.widget.setAttribute("context", "debuggerWatchExpressionsContextMenu"); + this.widget.addEventListener("click", this._onClick, false); + + this.headerText = L10N.getStr("addWatchExpressionText"); + this._addCommands(); + }, + + /** + * Destruction function, called when the debugger is closed. + */ + destroy: function () { + dumpn("Destroying the WatchExpressionsView"); + + this.widget.removeEventListener("click", this._onClick, false); + }, + + /** + * Add commands that XUL can fire. + */ + _addCommands: function () { + XULUtils.addCommands(document.getElementById("debuggerCommands"), { + addWatchExpressionCommand: () => this._onCmdAddExpression(), + removeAllWatchExpressionsCommand: () => this._onCmdRemoveAllExpressions() + }); + }, + + /** + * Adds a watch expression in this container. + * + * @param string aExpression [optional] + * An optional initial watch expression text. + * @param boolean aSkipUserInput [optional] + * Pass true to avoid waiting for additional user input + * on the watch expression. + */ + addExpression: function (aExpression = "", aSkipUserInput = false) { + // Watch expressions are UI elements which benefit from visible panes. + this.DebuggerView.showInstrumentsPane(); + + // Create the element node for the watch expression item. + let itemView = this._createItemView(aExpression); + + // Append a watch expression item to this container. + let expressionItem = this.push([itemView.container], { + index: 0, /* specifies on which position should the item be appended */ + attachment: { + view: itemView, + initialExpression: aExpression, + currentExpression: "", + } + }); + + // Automatically focus the new watch expression input + // if additional user input is desired. + if (!aSkipUserInput) { + expressionItem.attachment.view.inputNode.select(); + expressionItem.attachment.view.inputNode.focus(); + this.DebuggerView.Variables.parentNode.scrollTop = 0; + } + // Otherwise, add and evaluate the new watch expression immediately. + else { + this.toggleContents(false); + this._onBlur({ target: expressionItem.attachment.view.inputNode }); + } + }, + + /** + * Changes the watch expression corresponding to the specified variable item. + * This function is called whenever a watch expression's code is edited in + * the variables view container. + * + * @param Variable aVar + * The variable representing the watch expression evaluation. + * @param string aExpression + * The new watch expression text. + */ + switchExpression: function (aVar, aExpression) { + let expressionItem = + [...this].filter(i => i.attachment.currentExpression == aVar.name)[0]; + + // Remove the watch expression if it's going to be empty or a duplicate. + if (!aExpression || this.getAllStrings().indexOf(aExpression) != -1) { + this.deleteExpression(aVar); + return; + } + + // Save the watch expression code string. + expressionItem.attachment.currentExpression = aExpression; + expressionItem.attachment.view.inputNode.value = aExpression; + + // Synchronize with the controller's watch expressions store. + this.StackFrames.syncWatchExpressions(); + }, + + /** + * Removes the watch expression corresponding to the specified variable item. + * This function is called whenever a watch expression's value is edited in + * the variables view container. + * + * @param Variable aVar + * The variable representing the watch expression evaluation. + */ + deleteExpression: function (aVar) { + let expressionItem = + [...this].filter(i => i.attachment.currentExpression == aVar.name)[0]; + + // Remove the watch expression. + this.remove(expressionItem); + + // Synchronize with the controller's watch expressions store. + this.StackFrames.syncWatchExpressions(); + }, + + /** + * 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. + */ + getString: function (aIndex) { + return this.getItemAtIndex(aIndex).attachment.currentExpression; + }, + + /** + * Gets the watch expressions code strings for all items in this container. + * + * @return array + * The watch expressions code strings. + */ + getAllStrings: function () { + return this.items.map(e => e.attachment.currentExpression); + }, + + /** + * Customization function for creating an item's UI. + * + * @param string aExpression + * The watch expression string. + */ + _createItemView: function (aExpression) { + let container = document.createElement("hbox"); + container.className = "list-widget-item dbg-expression"; + container.setAttribute("align", "center"); + + let arrowNode = document.createElement("hbox"); + arrowNode.className = "dbg-expression-arrow"; + + let inputNode = document.createElement("textbox"); + inputNode.className = "plain dbg-expression-input devtools-monospace"; + inputNode.setAttribute("value", aExpression); + inputNode.setAttribute("flex", "1"); + + let closeNode = document.createElement("toolbarbutton"); + closeNode.className = "plain variables-view-delete"; + + closeNode.addEventListener("click", this._onClose, false); + inputNode.addEventListener("blur", this._onBlur, false); + inputNode.addEventListener("keypress", this._onKeyPress, false); + + container.appendChild(arrowNode); + container.appendChild(inputNode); + container.appendChild(closeNode); + + return { + container: container, + arrowNode: arrowNode, + inputNode: inputNode, + closeNode: closeNode + }; + }, + + /** + * Called when the add watch expression key sequence was pressed. + */ + _onCmdAddExpression: function (aText) { + // Only add a new expression if there's no pending input. + if (this.getAllStrings().indexOf("") == -1) { + this.addExpression(aText || this.DebuggerView.editor.getSelection()); + } + }, + + /** + * Called when the remove all watch expressions key sequence was pressed. + */ + _onCmdRemoveAllExpressions: function () { + // Empty the view of all the watch expressions and clear the cache. + this.empty(); + + // Synchronize with the controller's watch expressions store. + this.StackFrames.syncWatchExpressions(); + }, + + /** + * The click listener for this container. + */ + _onClick: function (e) { + if (e.button != 0) { + // Only allow left-click to trigger this event. + return; + } + 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 (e) { + // Remove the watch expression. + this.remove(this.getItemForElement(e.target)); + + // Synchronize with the controller's watch expressions store. + this.StackFrames.syncWatchExpressions(); + + // Prevent clicking the expression element itself. + e.preventDefault(); + e.stopPropagation(); + }, + + /** + * The blur listener for a watch expression's textbox. + */ + _onBlur: function ({ target: textbox }) { + let expressionItem = this.getItemForElement(textbox); + let oldExpression = expressionItem.attachment.currentExpression; + let newExpression = textbox.value.trim(); + + // Remove the watch expression if it's empty. + if (!newExpression) { + this.remove(expressionItem); + } + // Remove the watch expression if it's a duplicate. + else if (!oldExpression && this.getAllStrings().indexOf(newExpression) != -1) { + this.remove(expressionItem); + } + // Expression is eligible. + else { + expressionItem.attachment.currentExpression = newExpression; + } + + // Synchronize with the controller's watch expressions store. + this.StackFrames.syncWatchExpressions(); + }, + + /** + * The keypress listener for a watch expression's textbox. + */ + _onKeyPress: function (e) { + switch (e.keyCode) { + case KeyCodes.DOM_VK_RETURN: + case KeyCodes.DOM_VK_ESCAPE: + e.stopPropagation(); + this.DebuggerView.editor.focus(); + } + } +}); + +DebuggerView.WatchExpressions = new WatchExpressionsView(DebuggerController, + DebuggerView); |