gecko/browser/devtools/debugger/debugger-toolbar.js
Victor Porof 101a25592f Bug 812083 - Implement a SideMenuWidget (add a tree view to the remote debugger's script selector), r=past,rcampbell
--HG--
rename : browser/devtools/debugger/test/browser_dbg_script-switching.js => browser/devtools/debugger/test/browser_dbg_scripts-switching.js
2013-02-21 01:33:36 +02:00

1348 lines
43 KiB
JavaScript

/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
"use strict";
/**
* Functions handling the toolbar view: close button, expand/collapse button,
* pause/resume and stepping buttons etc.
*/
function ToolbarView() {
dumpn("ToolbarView was instantiated");
this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this);
this._onResumePressed = this._onResumePressed.bind(this);
this._onStepOverPressed = this._onStepOverPressed.bind(this);
this._onStepInPressed = this._onStepInPressed.bind(this);
this._onStepOutPressed = this._onStepOutPressed.bind(this);
}
ToolbarView.prototype = {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function DVT_initialize() {
dumpn("Initializing the ToolbarView");
this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
this._resumeButton = document.getElementById("resume");
this._stepOverButton = document.getElementById("step-over");
this._stepInButton = document.getElementById("step-in");
this._stepOutButton = document.getElementById("step-out");
this._chromeGlobals = document.getElementById("chrome-globals");
let resumeKey = LayoutHelpers.prettyKey(document.getElementById("resumeKey"), true);
let stepOverKey = LayoutHelpers.prettyKey(document.getElementById("stepOverKey"), true);
let stepInKey = LayoutHelpers.prettyKey(document.getElementById("stepInKey"), true);
let stepOutKey = LayoutHelpers.prettyKey(document.getElementById("stepOutKey"), true);
this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", [resumeKey]);
this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", [resumeKey]);
this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", [stepOverKey]);
this._stepInTooltip = L10N.getFormatStr("stepInTooltip", [stepInKey]);
this._stepOutTooltip = L10N.getFormatStr("stepOutTooltip", [stepOutKey]);
this._instrumentsPaneToggleButton.addEventListener("mousedown", this._onTogglePanesPressed, false);
this._resumeButton.addEventListener("mousedown", this._onResumePressed, false);
this._stepOverButton.addEventListener("mousedown", this._onStepOverPressed, false);
this._stepInButton.addEventListener("mousedown", this._onStepInPressed, false);
this._stepOutButton.addEventListener("mousedown", this._onStepOutPressed, false);
this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip);
this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip);
this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip);
// TODO: bug 806775
// this.toggleChromeGlobalsContainer(window._isChromeDebugger);
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function DVT_destroy() {
dumpn("Destroying the ToolbarView");
this._instrumentsPaneToggleButton.removeEventListener("mousedown", this._onTogglePanesPressed, false);
this._resumeButton.removeEventListener("mousedown", this._onResumePressed, false);
this._stepOverButton.removeEventListener("mousedown", this._onStepOverPressed, false);
this._stepInButton.removeEventListener("mousedown", this._onStepInPressed, false);
this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false);
},
/**
* Sets the resume button state based on the debugger active thread.
*
* @param string aState
* Either "paused" or "attached".
*/
toggleResumeButtonState: function DVT_toggleResumeButtonState(aState) {
// If we're paused, check and show a resume label on the button.
if (aState == "paused") {
this._resumeButton.setAttribute("checked", "true");
this._resumeButton.setAttribute("tooltiptext", this._resumeTooltip);
}
// If we're attached, do the opposite.
else if (aState == "attached") {
this._resumeButton.removeAttribute("checked");
this._resumeButton.setAttribute("tooltiptext", this._pauseTooltip);
}
},
/**
* Sets the chrome globals container hidden or visible. It's hidden by default.
*
* @param boolean aVisibleFlag
* Specifies the intended visibility.
*/
toggleChromeGlobalsContainer: function DVT_toggleChromeGlobalsContainer(aVisibleFlag) {
this._chromeGlobals.setAttribute("hidden", !aVisibleFlag);
},
/**
* Listener handling the toggle button click event.
*/
_onTogglePanesPressed: function DVT__onTogglePanesPressed() {
DebuggerView.toggleInstrumentsPane({
visible: DebuggerView.instrumentsPaneHidden,
animated: true,
delayed: true
});
},
/**
* Listener handling the pause/resume button click event.
*/
_onResumePressed: function DVT__onResumePressed() {
if (DebuggerController.activeThread.paused) {
DebuggerController.activeThread.resume();
} else {
DebuggerController.activeThread.interrupt();
}
},
/**
* Listener handling the step over button click event.
*/
_onStepOverPressed: function DVT__onStepOverPressed() {
if (DebuggerController.activeThread.paused) {
DebuggerController.activeThread.stepOver();
}
},
/**
* Listener handling the step in button click event.
*/
_onStepInPressed: function DVT__onStepInPressed() {
if (DebuggerController.activeThread.paused) {
DebuggerController.activeThread.stepIn();
}
},
/**
* Listener handling the step out button click event.
*/
_onStepOutPressed: function DVT__onStepOutPressed() {
if (DebuggerController.activeThread.paused) {
DebuggerController.activeThread.stepOut();
}
},
_instrumentsPaneToggleButton: null,
_resumeButton: null,
_stepOverButton: null,
_stepInButton: null,
_stepOutButton: null,
_chromeGlobals: null,
_resumeTooltip: "",
_pauseTooltip: "",
_stepOverTooltip: "",
_stepInTooltip: "",
_stepOutTooltip: ""
};
/**
* Functions handling the options UI.
*/
function OptionsView() {
dumpn("OptionsView was instantiated");
this._togglePauseOnExceptions = this._togglePauseOnExceptions.bind(this);
this._toggleShowPanesOnStartup = this._toggleShowPanesOnStartup.bind(this);
this._toggleShowVariablesOnlyEnum = this._toggleShowVariablesOnlyEnum.bind(this);
this._toggleShowVariablesFilterBox = this._toggleShowVariablesFilterBox.bind(this);
}
OptionsView.prototype = {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function DVO_initialize() {
dumpn("Initializing the OptionsView");
this._button = document.getElementById("debugger-options");
this._pauseOnExceptionsItem = document.getElementById("pause-on-exceptions");
this._showPanesOnStartupItem = document.getElementById("show-panes-on-startup");
this._showVariablesOnlyEnumItem = document.getElementById("show-vars-only-enum");
this._showVariablesFilterBoxItem = document.getElementById("show-vars-filter-box");
this._pauseOnExceptionsItem.setAttribute("checked", Prefs.pauseOnExceptions);
this._showPanesOnStartupItem.setAttribute("checked", Prefs.panesVisibleOnStartup);
this._showVariablesOnlyEnumItem.setAttribute("checked", Prefs.variablesOnlyEnumVisible);
this._showVariablesFilterBoxItem.setAttribute("checked", Prefs.variablesSearchboxVisible);
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function DVO_destroy() {
dumpn("Destroying the OptionsView");
// Nothing to do here yet.
},
/**
* Listener handling the 'gear menu' popup showing event.
*/
_onPopupShowing: function DVO__onPopupShowing() {
this._button.setAttribute("open", "true");
},
/**
* Listener handling the 'gear menu' popup hiding event.
*/
_onPopupHiding: function DVO__onPopupHiding() {
this._button.removeAttribute("open");
},
/**
* Listener handling the 'pause on exceptions' menuitem command.
*/
_togglePauseOnExceptions: function DVO__togglePauseOnExceptions() {
DebuggerController.activeThread.pauseOnExceptions(Prefs.pauseOnExceptions =
this._pauseOnExceptionsItem.getAttribute("checked") == "true");
},
/**
* Listener handling the 'show panes on startup' menuitem command.
*/
_toggleShowPanesOnStartup: function DVO__toggleShowPanesOnStartup() {
Prefs.panesVisibleOnStartup =
this._showPanesOnStartupItem.getAttribute("checked") == "true";
},
/**
* Listener handling the 'show non-enumerables' menuitem command.
*/
_toggleShowVariablesOnlyEnum: function DVO__toggleShowVariablesOnlyEnum() {
DebuggerView.Variables.onlyEnumVisible = Prefs.variablesOnlyEnumVisible =
this._showVariablesOnlyEnumItem.getAttribute("checked") == "true";
},
/**
* Listener handling the 'show variables searchbox' menuitem command.
*/
_toggleShowVariablesFilterBox: function DVO__toggleShowVariablesFilterBox() {
DebuggerView.Variables.searchEnabled = Prefs.variablesSearchboxVisible =
this._showVariablesFilterBoxItem.getAttribute("checked") == "true";
},
_button: null,
_pauseOnExceptionsItem: null,
_showPanesOnStartupItem: null,
_showVariablesOnlyEnumItem: null,
_showVariablesFilterBoxItem: null
};
/**
* Functions handling the chrome globals UI.
*/
function ChromeGlobalsView() {
dumpn("ChromeGlobalsView was instantiated");
this._onSelect = this._onSelect.bind(this);
this._onClick = this._onClick.bind(this);
}
create({ constructor: ChromeGlobalsView, proto: MenuContainer.prototype }, {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function DVCG_initialize() {
dumpn("Initializing the ChromeGlobalsView");
this.node = document.getElementById("chrome-globals");
this.emptyText = L10N.getStr("noGlobalsText");
this.unavailableText = L10N.getStr("noMatchingGlobalsText");
this.node.addEventListener("select", this._onSelect, false);
this.node.addEventListener("click", this._onClick, false);
// Show an empty label by default.
this.empty();
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function DVT_destroy() {
dumpn("Destroying the ChromeGlobalsView");
this.node.removeEventListener("select", this._onSelect, false);
this.node.removeEventListener("click", this._onClick, false);
},
/**
* The select listener for the chrome globals container.
*/
_onSelect: function DVCG__onSelect() {
if (!this.refresh()) {
return;
}
// TODO: bug 806775, do something useful for chrome debugging.
},
/**
* The click listener for the chrome globals container.
*/
_onClick: function DVCG__onClick() {
// Use this container as a filtering target.
DebuggerView.Filtering.target = this;
}
});
/**
* Functions handling the stackframes UI.
*/
function StackFramesView() {
dumpn("StackFramesView was instantiated");
this._framesCache = new Map(); // Can't use a WeakMap because keys are numbers.
this._onStackframeRemoved = this._onStackframeRemoved.bind(this);
this._onClick = this._onClick.bind(this);
this._onScroll = this._onScroll.bind(this);
this._afterScroll = this._afterScroll.bind(this);
this._selectFrame = this._selectFrame.bind(this);
}
create({ constructor: StackFramesView, proto: MenuContainer.prototype }, {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function DVSF_initialize() {
dumpn("Initializing the StackFramesView");
let commandset = this._commandset = document.createElement("commandset");
let menupopup = this._menupopup = document.createElement("menupopup");
commandset.id = "stackframesCommandset";
menupopup.id = "stackframesMenupopup";
document.getElementById("debuggerPopupset").appendChild(menupopup);
document.getElementById("debuggerCommands").appendChild(commandset);
this.node = new BreadcrumbsWidget(document.getElementById("stackframes"));
this.node.addEventListener("mousedown", this._onClick, false);
this.node.addEventListener("scroll", this._onScroll, true);
window.addEventListener("resize", this._onScroll, true);
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function DVSF_destroy() {
dumpn("Destroying the StackFramesView");
this.node.removeEventListener("mousedown", this._onClick, false);
this.node.removeEventListener("scroll", this._onScroll, true);
window.removeEventListener("resize", this._onScroll, true);
},
/**
* Adds a frame in this stackframes container.
*
* @param string aFrameTitle
* The frame title to be displayed in the list.
* @param string aSourceLocation
* The source location to be displayed in the list.
* @param string aLineNumber
* The line number to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
*/
addFrame: function DVSF_addFrame(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
// Create the element node and menu entry for the stack frame item.
let frameView = this._createFrameView.apply(this, arguments);
let menuEntry = this._createMenuEntry.apply(this, arguments);
// Append a stack frame item to this container.
let stackframeItem = this.push(frameView, {
index: 0, /* specifies on which position should the item be appended */
relaxed: true, /* this container should allow dupes & degenerates */
attachment: {
popup: menuEntry,
depth: aDepth
},
attributes: [
["contextmenu", "stackframesMenupopup"],
["tooltiptext", aSourceLocation]
],
// Make sure that when the stack frame item is removed, the corresponding
// menuitem and command are also destroyed.
finalize: this._onStackframeRemoved
});
this._framesCache.set(aDepth, stackframeItem);
},
/**
* Highlights a frame in this stackframes container.
*
* @param number aDepth
* The frame depth specified by the debugger controller.
*/
highlightFrame: function DVSF_highlightFrame(aDepth) {
let selectedItem = this.selectedItem = this._framesCache.get(aDepth);
for (let item in this) {
if (item != selectedItem) {
item.attachment.popup.menuitem.removeAttribute("checked");
} else {
item.attachment.popup.menuitem.setAttribute("checked", "");
}
}
},
/**
* Specifies if the active thread has more frames that need to be loaded.
*/
dirty: false,
/**
* Customization function for creating an item's UI.
*
* @param string aFrameTitle
* The frame title to be displayed in the list.
* @param string aSourceLocation
* The source location to be displayed in the list.
* @param string aLineNumber
* The line number to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
* @return nsIDOMNode
* The stack frame view.
*/
_createFrameView:
function DVSF__createFrameView(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
let frameDetails = SourceUtils.getSourceLabel(aSourceLocation,
STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
STACK_FRAMES_SOURCE_URL_TRIM_SECTION) +
SEARCH_LINE_FLAG + aLineNumber;
let frameTitleNode = document.createElement("label");
frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
frameTitleNode.setAttribute("value", aFrameTitle);
let frameDetailsNode = document.createElement("label");
frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
frameDetailsNode.setAttribute("value", frameDetails);
let container = document.createElement("hbox");
container.id = "stackframe-" + aDepth;
container.className = "dbg-stackframe";
container.appendChild(frameTitleNode);
container.appendChild(frameDetailsNode);
return container;
},
/**
* Customization function for populating an item's context menu.
*
* @param string aFrameTitle
* The frame title to be displayed in the list.
* @param string aSourceLocation
* The source location to be displayed in the list.
* @param string aLineNumber
* The line number to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
* @return object
* An object containing the stack frame command and menu item.
*/
_createMenuEntry:
function DVSF__createMenuEntry(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
let frameDescription = SourceUtils.getSourceLabel(aSourceLocation,
STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH,
STACK_FRAMES_POPUP_SOURCE_URL_TRIM_SECTION) +
SEARCH_LINE_FLAG + aLineNumber;
let prefix = "sf-cMenu-"; // "stackframes context menu"
let commandId = prefix + aDepth + "-" + "-command";
let menuitemId = prefix + aDepth + "-" + "-menuitem";
let command = document.createElement("command");
command.id = commandId;
command.addEventListener("command", this._selectFrame.bind(this, aDepth), false);
let menuitem = document.createElement("menuitem");
menuitem.id = menuitemId;
menuitem.className = "dbg-stackframe-menuitem";
menuitem.setAttribute("type", "checkbox");
menuitem.setAttribute("command", commandId);
menuitem.setAttribute("tooltiptext", aSourceLocation);
let labelNode = document.createElement("label");
labelNode.className = "plain dbg-stackframe-menuitem-title";
labelNode.setAttribute("value", aFrameTitle);
labelNode.setAttribute("flex", "1");
let descriptionNode = document.createElement("label");
descriptionNode.className = "plain dbg-stackframe-menuitem-details";
descriptionNode.setAttribute("value", frameDescription);
menuitem.appendChild(labelNode);
menuitem.appendChild(descriptionNode);
this._commandset.appendChild(command);
this._menupopup.appendChild(menuitem);
return {
command: command,
menuitem: menuitem
};
},
/**
* Destroys a context menu item for a stack frame.
*
* @param object aMenuEntry
* An object containing the stack frame command and menu item.
*/
_destroyMenuEntry: function DVSF__destroyMenuEntry(aMenuEntry) {
dumpn("Destroying context menu: " +
aMenuEntry.command.id + " & " + aMenuEntry.menuitem.id);
let command = aMenuEntry.command;
let menuitem = aMenuEntry.menuitem;
command.parentNode.removeChild(command);
menuitem.parentNode.removeChild(menuitem);
},
/**
* Function called each time a stack frame item is removed.
*
* @param MenuItem aItem
* The corresponding menu item.
*/
_onStackframeRemoved: function DVSF__onStackframeRemoved(aItem) {
dumpn("Finalizing stackframe item: " + aItem);
let { popup, depth } = aItem.attachment;
this._destroyMenuEntry(popup);
this._framesCache.delete(depth);
},
/**
* The click listener for the stackframes container.
*/
_onClick: function DVSF__onClick(e) {
if (e && e.button != 0) {
// Only allow left-click to trigger this event.
return;
}
let item = this.getItemForElement(e.target);
if (item) {
// The container is not empty and we clicked on an actual item.
this._selectFrame(item.attachment.depth);
}
},
/**
* The scroll listener for the stackframes container.
*/
_onScroll: function DVSF__onScroll() {
// Update the stackframes container only if we have to.
if (!this.dirty) {
return;
}
window.clearTimeout(this._scrollTimeout);
this._scrollTimeout = window.setTimeout(this._afterScroll, STACK_FRAMES_SCROLL_DELAY);
},
/**
* Requests the addition of more frames from the controller.
*/
_afterScroll: function DVSF__afterScroll() {
let list = this.node._list;
let scrollPosition = list.scrollPosition;
let scrollWidth = list.scrollWidth;
// If the stackframes container scrolled almost to the end, with only
// 1/10 of a breadcrumb remaining, load more content.
if (scrollPosition - scrollWidth / 10 < 1) {
list.ensureElementIsVisible(this.getItemAtIndex(CALL_STACK_PAGE_SIZE - 1).target);
this.dirty = false;
// Loads more stack frames from the debugger server cache.
DebuggerController.StackFrames.addMoreFrames();
}
},
/**
* Requests selection of a frame from the controller.
*
* @param number aDepth
* The depth of the frame in the stack.
*/
_selectFrame: function DVSF__selectFrame(aDepth) {
DebuggerController.StackFrames.selectFrame(aDepth);
},
_framesCache: null,
_commandset: null,
_menupopup: null,
_scrollTimeout: null
});
/**
* Utility functions for handling stackframes.
*/
let StackFrameUtils = {
/**
* Create a textual representation for the specified stack frame
* to display in the stackframes container.
*
* @param object aFrame
* The stack frame to label.
*/
getFrameTitle: function SFU_getFrameTitle(aFrame) {
if (aFrame.type == "call") {
let c = aFrame.callee;
return (c.name || c.userDisplayName || c.displayName || "(anonymous)");
}
return "(" + aFrame.type + ")";
},
/**
* Constructs a scope label based on its environment.
*
* @param object aEnv
* The scope's environment.
* @return string
* The scope's label.
*/
getScopeLabel: function SFU_getScopeLabel(aEnv) {
let name = "";
// Name the outermost scope Global.
if (!aEnv.parent) {
name = L10N.getStr("globalScopeLabel");
}
// Otherwise construct the scope name.
else {
name = aEnv.type.charAt(0).toUpperCase() + aEnv.type.slice(1);
}
let label = L10N.getFormatStr("scopeLabel", [name]);
switch (aEnv.type) {
case "with":
case "object":
label += " [" + aEnv.object.class + "]";
break;
case "function":
let f = aEnv.function;
label += " [" +
(f.name || f.userDisplayName || f.displayName || "(anonymous)") +
"]";
break;
}
return label;
}
};
/**
* Functions handling the filtering UI.
*/
function FilterView() {
dumpn("FilterView was instantiated");
this._onClick = this._onClick.bind(this);
this._onSearch = this._onSearch.bind(this);
this._onKeyPress = this._onKeyPress.bind(this);
this._onBlur = this._onBlur.bind(this);
}
FilterView.prototype = {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function DVF_initialize() {
dumpn("Initializing the FilterView");
this._searchbox = document.getElementById("searchbox");
this._searchboxHelpPanel = document.getElementById("searchbox-help-panel");
this._globalOperatorButton = document.getElementById("global-operator-button");
this._globalOperatorLabel = document.getElementById("global-operator-label");
this._tokenOperatorButton = document.getElementById("token-operator-button");
this._tokenOperatorLabel = document.getElementById("token-operator-label");
this._lineOperatorButton = document.getElementById("line-operator-button");
this._lineOperatorLabel = document.getElementById("line-operator-label");
this._variableOperatorButton = document.getElementById("variable-operator-button");
this._variableOperatorLabel = document.getElementById("variable-operator-label");
this._fileSearchKey = LayoutHelpers.prettyKey(document.getElementById("fileSearchKey"), true);
this._globalSearchKey = LayoutHelpers.prettyKey(document.getElementById("globalSearchKey"), true);
this._tokenSearchKey = LayoutHelpers.prettyKey(document.getElementById("tokenSearchKey"), true);
this._lineSearchKey = LayoutHelpers.prettyKey(document.getElementById("lineSearchKey"), true);
this._variableSearchKey = LayoutHelpers.prettyKey(document.getElementById("variableSearchKey"), true);
this._searchbox.addEventListener("click", this._onClick, false);
this._searchbox.addEventListener("select", this._onSearch, false);
this._searchbox.addEventListener("input", this._onSearch, false);
this._searchbox.addEventListener("keypress", this._onKeyPress, false);
this._searchbox.addEventListener("blur", this._onBlur, false);
this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG);
this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG);
this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG);
this._variableOperatorButton.setAttribute("label", SEARCH_VARIABLE_FLAG);
this._globalOperatorLabel.setAttribute("value",
L10N.getFormatStr("searchPanelGlobal", [this._globalSearchKey]));
this._tokenOperatorLabel.setAttribute("value",
L10N.getFormatStr("searchPanelToken", [this._tokenSearchKey]));
this._lineOperatorLabel.setAttribute("value",
L10N.getFormatStr("searchPanelLine", [this._lineSearchKey]));
this._variableOperatorLabel.setAttribute("value",
L10N.getFormatStr("searchPanelVariable", [this._variableSearchKey]));
// TODO: bug 806775
// if (window._isChromeDebugger) {
// this.target = DebuggerView.ChromeGlobals;
// } else {
this.target = DebuggerView.Sources;
// }
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function DVF_destroy() {
dumpn("Destroying the FilterView");
this._searchbox.removeEventListener("click", this._onClick, false);
this._searchbox.removeEventListener("select", this._onSearch, false);
this._searchbox.removeEventListener("input", this._onSearch, false);
this._searchbox.removeEventListener("keypress", this._onKeyPress, false);
this._searchbox.removeEventListener("blur", this._onBlur, false);
},
/**
* Sets the target container to be currently filtered.
* @param object aView
*/
set target(aView) {
let placeholder = "";
switch (aView) {
case DebuggerView.ChromeGlobals:
placeholder = L10N.getFormatStr("emptyChromeGlobalsFilterText", [this._fileSearchKey]);
break;
case DebuggerView.Sources:
placeholder = L10N.getFormatStr("emptyFilterText", [this._fileSearchKey]);
break;
}
this._searchbox.setAttribute("placeholder", placeholder);
this._target = aView;
},
/**
* Gets the entered file, line and token entered in the searchbox.
* @return array
*/
get searchboxInfo() {
let file, line, token, isGlobal, isVariable;
let rawValue = this._searchbox.value;
let rawLength = rawValue.length;
let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG);
let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG);
let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG);
let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
// This is not a global or variable search, allow file or line flags.
if (globalFlagIndex != 0 && variableFlagIndex != 0) {
let fileEnd = lineFlagIndex != -1
? lineFlagIndex
: tokenFlagIndex != -1 ? tokenFlagIndex : rawLength;
let lineEnd = tokenFlagIndex != -1
? tokenFlagIndex
: rawLength;
file = rawValue.slice(0, fileEnd);
line = ~~(rawValue.slice(fileEnd + 1, lineEnd)) || 0;
token = rawValue.slice(lineEnd + 1);
isGlobal = false;
isVariable = false;
}
// Global searches dissalow the use of file or line flags.
else if (globalFlagIndex == 0) {
file = "";
line = 0;
token = rawValue.slice(1);
isGlobal = true;
isVariable = false;
}
// Variable searches dissalow the use of file or line flags.
else if (variableFlagIndex == 0) {
file = "";
line = 0;
token = rawValue.slice(1);
isGlobal = false;
isVariable = true;
}
return [file, line, token, isGlobal, isVariable];
},
/**
* Returns the currently searched file.
* @return string
*/
get searchedFile() this.searchboxInfo[0],
/**
* Returns the currently searched line.
* @return number
*/
get searchedLine() this.searchboxInfo[1],
/**
* Returns the currently searched token.
* @return string
*/
get searchedToken() this.searchboxInfo[2],
/**
* Clears the text from the searchbox and resets any changed view.
*/
clearSearch: function DVF_clearSearch() {
this._searchbox.value = "";
this._searchboxHelpPanel.hidePopup();
},
/**
* Performs a file search if necessary.
*
* @param string aFile
* The source location to search for.
*/
_performFileSearch: function DVF__performFileSearch(aFile) {
// Don't search for files if the input hasn't changed.
if (this._prevSearchedFile == aFile) {
return;
}
// This is the target container to be currently filtered. Clicking on a
// container generally means it should become a target.
let view = this._target;
// If we're not searching for a file anymore, unhide all the items.
if (!aFile) {
for (let item in view) {
item.target.hidden = false;
}
view.refresh();
}
// If the searched file string is valid, hide non-matched items.
else {
let found = false;
let lowerCaseFile = aFile.toLowerCase();
for (let item in view) {
let element = item.target;
let lowerCaseLabel = item.label.toLowerCase();
// Search is not case sensitive, and is tied to the label not the value.
if (lowerCaseLabel.match(lowerCaseFile)) {
element.hidden = false;
// Automatically select the first match.
if (!found) {
found = true;
view.selectedItem = item;
view.refresh();
}
}
// Item not matched, hide the corresponding node.
else {
element.hidden = true;
}
}
// If no matches were found, display the appropriate info.
if (!found) {
view.setUnavailable();
}
}
// Synchronize with the view's filtered sources container.
DebuggerView.FilteredSources.syncFileSearch();
// Hide all the groups with no visible children.
view.node.hideEmptyGroups();
// Ensure the currently selected item is visible.
view.node.ensureSelectionIsVisible(true);
// Remember the previously searched file to avoid redundant filtering.
this._prevSearchedFile = aFile;
},
/**
* Performs a line search if necessary.
* (Jump to lines in the currently visible source).
*
* @param number aLine
* The source line number to jump to.
*/
_performLineSearch: function DVF__performLineSearch(aLine) {
// Don't search for lines if the input hasn't changed.
if (this._prevSearchedLine != aLine && aLine) {
DebuggerView.editor.setCaretPosition(aLine - 1);
}
// Can't search for lines and tokens at the same time.
if (this._prevSearchedToken && !aLine) {
this._target.refresh();
}
// Remember the previously searched line to avoid redundant filtering.
this._prevSearchedLine = aLine;
},
/**
* Performs a token search if necessary.
* (Search for tokens in the currently visible source).
*
* @param string aToken
* The source token to find.
*/
_performTokenSearch: function DVF__performTokenSearch(aToken) {
// Don't search for tokens if the input hasn't changed.
if (this._prevSearchedToken != aToken && aToken) {
let editor = DebuggerView.editor;
let offset = editor.find(aToken, { ignoreCase: true });
if (offset > -1) {
editor.setSelection(offset, offset + aToken.length)
}
}
// Can't search for tokens and lines at the same time.
if (this._prevSearchedLine && !aToken) {
this._target.refresh();
}
// Remember the previously searched token to avoid redundant filtering.
this._prevSearchedToken = aToken;
},
/**
* The click listener for the search container.
*/
_onClick: function DVF__onClick() {
this._searchboxHelpPanel.openPopup(this._searchbox);
},
/**
* The search listener for the search container.
*/
_onSearch: function DVF__onScriptsSearch() {
this._searchboxHelpPanel.hidePopup();
let [file, line, token, isGlobal, isVariable] = this.searchboxInfo;
// If this is a global search, schedule it for when the user stops typing,
// or hide the corresponding pane otherwise.
if (isGlobal) {
DebuggerView.GlobalSearch.scheduleSearch(token);
this._prevSearchedToken = token;
return;
}
// If this is a variable search, defer the action to the corresponding
// variables view instance.
if (isVariable) {
DebuggerView.Variables.scheduleSearch(token);
this._prevSearchedToken = token;
return;
}
DebuggerView.GlobalSearch.clearView();
this._performFileSearch(file);
this._performLineSearch(line);
this._performTokenSearch(token);
},
/**
* The key press listener for the search container.
*/
_onKeyPress: function DVF__onScriptsKeyPress(e) {
// This attribute is not implemented in Gecko at this time, see bug 680830.
e.char = String.fromCharCode(e.charCode);
let [file, line, token, isGlobal, isVariable] = this.searchboxInfo;
let isFileSearch, isLineSearch, isDifferentToken, isReturnKey;
let action = -1;
if (file && !line && !token) {
isFileSearch = true;
}
if (line && !token) {
isLineSearch = true;
}
if (this._prevSearchedToken != token) {
isDifferentToken = true;
}
// Meta+G and Ctrl+N focus next matches.
if ((e.char == "g" && e.metaKey) || e.char == "n" && e.ctrlKey) {
action = 0;
}
// Meta+Shift+G and Ctrl+P focus previous matches.
else if ((e.char == "G" && e.metaKey) || e.char == "p" && e.ctrlKey) {
action = 1;
}
// Return, enter, down and up keys focus next or previous matches, while
// the escape key switches focus from the search container.
else switch (e.keyCode) {
case e.DOM_VK_RETURN:
case e.DOM_VK_ENTER:
isReturnKey = true;
// fall through
case e.DOM_VK_DOWN:
action = 0;
break;
case e.DOM_VK_UP:
action = 1;
break;
case e.DOM_VK_ESCAPE:
action = 2;
break;
}
if (action == 2) {
DebuggerView.editor.focus();
return;
}
if (action == -1 || (!file && !line && !token)) {
DebuggerView.FilteredSources.hidden = true;
return;
}
e.preventDefault();
e.stopPropagation();
// Select the next or previous file search entry.
if (isFileSearch) {
if (isReturnKey) {
DebuggerView.FilteredSources.hidden = true;
DebuggerView.editor.focus();
this.clearSearch();
} else {
DebuggerView.FilteredSources[["focusNext", "focusPrev"][action]]();
}
this._prevSearchedFile = file;
return;
}
// Perform a global search based on the specified operator.
if (isGlobal) {
if (isReturnKey && (isDifferentToken || DebuggerView.GlobalSearch.hidden)) {
DebuggerView.GlobalSearch.performSearch(token);
} else {
DebuggerView.GlobalSearch[["focusNextMatch", "focusPrevMatch"][action]]();
}
this._prevSearchedToken = token;
return;
}
// Perform a variable search based on the specified operator.
if (isVariable) {
if (isReturnKey && isDifferentToken) {
DebuggerView.Variables.performSearch(token);
} else {
DebuggerView.Variables.expandFirstSearchResults();
}
this._prevSearchedToken = token;
return;
}
// Increment or decrement the specified line.
if (isLineSearch && !isReturnKey) {
line += action == 0 ? 1 : -1;
let lineCount = DebuggerView.editor.getLineCount();
let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line;
DebuggerView.editor.setCaretPosition(lineTarget - 1);
this._searchbox.value = file + SEARCH_LINE_FLAG + lineTarget;
this._prevSearchedLine = lineTarget;
return;
}
let editor = DebuggerView.editor;
let offset = editor[["findNext", "findPrevious"][action]](true);
if (offset > -1) {
editor.setSelection(offset, offset + token.length)
}
},
/**
* The blur listener for the search container.
*/
_onBlur: function DVF__onBlur() {
DebuggerView.GlobalSearch.clearView();
DebuggerView.Variables.performSearch(null);
this._searchboxHelpPanel.hidePopup();
},
/**
* Called when a filtering key sequence was pressed.
*
* @param string aOperator
* The operator to use for filtering.
*/
_doSearch: function DVF__doSearch(aOperator = "") {
this._searchbox.focus();
this._searchbox.value = aOperator;
},
/**
* Called when the source location filter key sequence was pressed.
*/
_doFileSearch: function DVF__doFileSearch() {
this._doSearch();
this._searchboxHelpPanel.openPopup(this._searchbox);
},
/**
* Called when the global search filter key sequence was pressed.
*/
_doGlobalSearch: function DVF__doGlobalSearch() {
this._doSearch(SEARCH_GLOBAL_FLAG);
this._searchboxHelpPanel.hidePopup();
},
/**
* Called when the source token filter key sequence was pressed.
*/
_doTokenSearch: function DVF__doTokenSearch() {
this._doSearch(SEARCH_TOKEN_FLAG);
this._searchboxHelpPanel.hidePopup();
},
/**
* Called when the source line filter key sequence was pressed.
*/
_doLineSearch: function DVF__doLineSearch() {
this._doSearch(SEARCH_LINE_FLAG);
this._searchboxHelpPanel.hidePopup();
},
/**
* Called when the variable search filter key sequence was pressed.
*/
_doVariableSearch: function DVF__doVariableSearch() {
DebuggerView.Variables.performSearch("");
this._doSearch(SEARCH_VARIABLE_FLAG);
this._searchboxHelpPanel.hidePopup();
},
/**
* Called when the variables focus key sequence was pressed.
*/
_doVariablesFocus: function DVG__doVariablesFocus() {
DebuggerView.showInstrumentsPane();
DebuggerView.Variables.focusFirstVisibleNode();
},
_searchbox: null,
_searchboxHelpPanel: null,
_globalOperatorButton: null,
_globalOperatorLabel: null,
_tokenOperatorButton: null,
_tokenOperatorLabel: null,
_lineOperatorButton: null,
_lineOperatorLabel: null,
_variableOperatorButton: null,
_variableOperatorLabel: null,
_fileSearchKey: "",
_globalSearchKey: "",
_tokenSearchKey: "",
_lineSearchKey: "",
_variableSearchKey: "",
_target: null,
_prevSearchedFile: "",
_prevSearchedLine: 0,
_prevSearchedToken: ""
};
/**
* Functions handling the filtered sources UI.
*/
function FilteredSourcesView() {
dumpn("FilteredSourcesView was instantiated");
this._onClick = this._onClick.bind(this);
}
create({ constructor: FilteredSourcesView, proto: MenuContainer.prototype }, {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function DVFS_initialize() {
dumpn("Initializing the FilteredSourcesView");
this.node = new ListWidget(document.getElementById("filtered-sources-panel"));
this._searchbox = document.getElementById("searchbox");
this.node.itemFactory = this._createItemView;
this.node.itemType = "vbox";
this.node.addEventListener("click", this._onClick, false);
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function DVFS_destroy() {
dumpn("Destroying the FilteredSourcesView");
this.node.removeEventListener("click", this._onClick, false);
},
/**
* Sets the files container hidden or visible. It's hidden by default.
* @param boolean aFlag
*/
set hidden(aFlag) {
if (aFlag) {
this.node._parent.hidePopup();
} else {
this.node._parent.openPopup(this._searchbox);
}
},
/**
* Updates the list of sources displayed in this container.
*/
syncFileSearch: function DVFS_syncFileSearch() {
this.empty();
// If there's no currently searched file, or there are no matches found,
// hide the popup.
if (!DebuggerView.Filtering.searchedFile ||
!DebuggerView.Sources.visibleItems.length) {
this.hidden = true;
return;
}
// Get the currently visible items in the sources container.
let visibleItems = DebuggerView.Sources.visibleItems;
let displayedItems = visibleItems.slice(0, FILTERED_SOURCES_MAX_RESULTS);
for (let item of displayedItems) {
// Append a location item item to this container.
let trimmedLabel = SourceUtils.trimUrlLength(item.label);
let trimmedValue = SourceUtils.trimUrlLength(item.value);
let locationItem = this.push([trimmedLabel, trimmedValue], {
relaxed: true, /* this container should allow dupes & degenerates */
attachment: {
fullLabel: item.label,
fullValue: item.value
}
});
}
this._updateSelection(this.getItemAtIndex(0));
this.hidden = false;
},
/**
* Focuses the next found match in this container.
*/
focusNext: function DVFS_focusNext() {
let nextIndex = this.selectedIndex + 1;
if (nextIndex >= this.itemCount) {
nextIndex = 0;
}
this._updateSelection(this.getItemAtIndex(nextIndex));
},
/**
* Focuses the previously found match in this container.
*/
focusPrev: function DVFS_focusPrev() {
let prevIndex = this.selectedIndex - 1;
if (prevIndex < 0) {
prevIndex = this.itemCount - 1;
}
this._updateSelection(this.getItemAtIndex(prevIndex));
},
/**
* The click listener for this container.
*/
_onClick: function DVFS__onClick(e) {
let locationItem = this.getItemForElement(e.target);
if (locationItem) {
this._updateSelection(locationItem);
DebuggerView.Filtering.clearSearch();
}
},
/**
* Updates the selected item in this container and other views.
*
* @param MenuItem aItem
* The item associated with the element to select.
*/
_updateSelection: function DVFS__updateSelection(aItem) {
this.selectedItem = aItem;
DebuggerView.Filtering._target.selectedValue = aItem.attachment.fullValue;
},
/**
* Customization function for creating an item's UI.
*
* @param nsIDOMNode aElementNode
* The element associated with the displayed item.
* @param any aAttachment
* Some attached primitive/object.
* @param string aLabel
* The item's label.
* @param string aValue
* The item's value.
*/
_createItemView:
function DVFS__createItemView(aElementNode, aAttachment, aLabel, aValue) {
let labelNode = document.createElement("label");
labelNode.className = "plain dbg-source-item-name";
labelNode.setAttribute("value", aLabel);
let valueNode = document.createElement("label");
valueNode.setAttribute("value", aValue);
valueNode.className = "plain dbg-source-item-details";
aElementNode.className = "light dbg-source-item";
aElementNode.appendChild(labelNode);
aElementNode.appendChild(valueNode);
},
_searchbox: null
});
/**
* Preliminary setup for the DebuggerView object.
*/
DebuggerView.Toolbar = new ToolbarView();
DebuggerView.Options = new OptionsView();
DebuggerView.Filtering = new FilterView();
DebuggerView.FilteredSources = new FilteredSourcesView();
DebuggerView.ChromeGlobals = new ChromeGlobalsView();
DebuggerView.StackFrames = new StackFramesView();