michael@0: /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: // A time interval sufficient for the options popup panel to finish hiding michael@0: // itself. michael@0: const POPUP_HIDDEN_DELAY = 100; // ms michael@0: michael@0: /** michael@0: * Functions handling the toolbar view: close button, expand/collapse button, michael@0: * pause/resume and stepping buttons etc. michael@0: */ michael@0: function ToolbarView() { michael@0: dumpn("ToolbarView was instantiated"); michael@0: michael@0: this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this); michael@0: this._onResumePressed = this._onResumePressed.bind(this); michael@0: this._onStepOverPressed = this._onStepOverPressed.bind(this); michael@0: this._onStepInPressed = this._onStepInPressed.bind(this); michael@0: this._onStepOutPressed = this._onStepOutPressed.bind(this); michael@0: } michael@0: michael@0: ToolbarView.prototype = { michael@0: /** michael@0: * Initialization function, called when the debugger is started. michael@0: */ michael@0: initialize: function() { michael@0: dumpn("Initializing the ToolbarView"); michael@0: michael@0: this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle"); michael@0: this._resumeButton = document.getElementById("resume"); michael@0: this._stepOverButton = document.getElementById("step-over"); michael@0: this._stepInButton = document.getElementById("step-in"); michael@0: this._stepOutButton = document.getElementById("step-out"); michael@0: this._resumeOrderTooltip = new Tooltip(document); michael@0: this._resumeOrderTooltip.defaultPosition = TOOLBAR_ORDER_POPUP_POSITION; michael@0: michael@0: let resumeKey = ShortcutUtils.prettifyShortcut(document.getElementById("resumeKey")); michael@0: let stepOverKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOverKey")); michael@0: let stepInKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepInKey")); michael@0: let stepOutKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOutKey")); michael@0: this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey); michael@0: this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey); michael@0: this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey); michael@0: this._stepInTooltip = L10N.getFormatStr("stepInTooltip", stepInKey); michael@0: this._stepOutTooltip = L10N.getFormatStr("stepOutTooltip", stepOutKey); michael@0: michael@0: this._instrumentsPaneToggleButton.addEventListener("mousedown", this._onTogglePanesPressed, false); michael@0: this._resumeButton.addEventListener("mousedown", this._onResumePressed, false); michael@0: this._stepOverButton.addEventListener("mousedown", this._onStepOverPressed, false); michael@0: this._stepInButton.addEventListener("mousedown", this._onStepInPressed, false); michael@0: this._stepOutButton.addEventListener("mousedown", this._onStepOutPressed, false); michael@0: michael@0: this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip); michael@0: this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip); michael@0: this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip); michael@0: }, michael@0: michael@0: /** michael@0: * Destruction function, called when the debugger is closed. michael@0: */ michael@0: destroy: function() { michael@0: dumpn("Destroying the ToolbarView"); michael@0: michael@0: this._instrumentsPaneToggleButton.removeEventListener("mousedown", this._onTogglePanesPressed, false); michael@0: this._resumeButton.removeEventListener("mousedown", this._onResumePressed, false); michael@0: this._stepOverButton.removeEventListener("mousedown", this._onStepOverPressed, false); michael@0: this._stepInButton.removeEventListener("mousedown", this._onStepInPressed, false); michael@0: this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false); michael@0: }, michael@0: michael@0: /** michael@0: * Display a warning when trying to resume a debuggee while another is paused. michael@0: * Debuggees must be unpaused in a Last-In-First-Out order. michael@0: * michael@0: * @param string aPausedUrl michael@0: * The URL of the last paused debuggee. michael@0: */ michael@0: showResumeWarning: function(aPausedUrl) { michael@0: let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl); michael@0: let defaultStyle = "default-tooltip-simple-text-colors"; michael@0: this._resumeOrderTooltip.setTextContent({ messages: [label], isAlertTooltip: true }); michael@0: this._resumeOrderTooltip.show(this._resumeButton); michael@0: }, michael@0: michael@0: /** michael@0: * Sets the resume button state based on the debugger active thread. michael@0: * michael@0: * @param string aState michael@0: * Either "paused" or "attached". michael@0: */ michael@0: toggleResumeButtonState: function(aState) { michael@0: // If we're paused, check and show a resume label on the button. michael@0: if (aState == "paused") { michael@0: this._resumeButton.setAttribute("checked", "true"); michael@0: this._resumeButton.setAttribute("tooltiptext", this._resumeTooltip); michael@0: } michael@0: // If we're attached, do the opposite. michael@0: else if (aState == "attached") { michael@0: this._resumeButton.removeAttribute("checked"); michael@0: this._resumeButton.setAttribute("tooltiptext", this._pauseTooltip); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the toggle button click event. michael@0: */ michael@0: _onTogglePanesPressed: function() { michael@0: DebuggerView.toggleInstrumentsPane({ michael@0: visible: DebuggerView.instrumentsPaneHidden, michael@0: animated: true, michael@0: delayed: true michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the pause/resume button click event. michael@0: */ michael@0: _onResumePressed: function() { michael@0: if (DebuggerController.activeThread.paused) { michael@0: let warn = DebuggerController._ensureResumptionOrder; michael@0: DebuggerController.StackFrames.currentFrameDepth = -1; michael@0: DebuggerController.activeThread.resume(warn); michael@0: } else { michael@0: DebuggerController.activeThread.interrupt(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the step over button click event. michael@0: */ michael@0: _onStepOverPressed: function() { michael@0: if (DebuggerController.activeThread.paused) { michael@0: DebuggerController.StackFrames.currentFrameDepth = -1; michael@0: let warn = DebuggerController._ensureResumptionOrder; michael@0: DebuggerController.activeThread.stepOver(warn); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the step in button click event. michael@0: */ michael@0: _onStepInPressed: function() { michael@0: if (DebuggerController.activeThread.paused) { michael@0: DebuggerController.StackFrames.currentFrameDepth = -1; michael@0: let warn = DebuggerController._ensureResumptionOrder; michael@0: DebuggerController.activeThread.stepIn(warn); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the step out button click event. michael@0: */ michael@0: _onStepOutPressed: function() { michael@0: if (DebuggerController.activeThread.paused) { michael@0: DebuggerController.StackFrames.currentFrameDepth = -1; michael@0: let warn = DebuggerController._ensureResumptionOrder; michael@0: DebuggerController.activeThread.stepOut(warn); michael@0: } michael@0: }, michael@0: michael@0: _instrumentsPaneToggleButton: null, michael@0: _resumeButton: null, michael@0: _stepOverButton: null, michael@0: _stepInButton: null, michael@0: _stepOutButton: null, michael@0: _resumeOrderTooltip: null, michael@0: _resumeTooltip: "", michael@0: _pauseTooltip: "", michael@0: _stepOverTooltip: "", michael@0: _stepInTooltip: "", michael@0: _stepOutTooltip: "" michael@0: }; michael@0: michael@0: /** michael@0: * Functions handling the options UI. michael@0: */ michael@0: function OptionsView() { michael@0: dumpn("OptionsView was instantiated"); michael@0: michael@0: this._toggleAutoPrettyPrint = this._toggleAutoPrettyPrint.bind(this); michael@0: this._togglePauseOnExceptions = this._togglePauseOnExceptions.bind(this); michael@0: this._toggleIgnoreCaughtExceptions = this._toggleIgnoreCaughtExceptions.bind(this); michael@0: this._toggleShowPanesOnStartup = this._toggleShowPanesOnStartup.bind(this); michael@0: this._toggleShowVariablesOnlyEnum = this._toggleShowVariablesOnlyEnum.bind(this); michael@0: this._toggleShowVariablesFilterBox = this._toggleShowVariablesFilterBox.bind(this); michael@0: this._toggleShowOriginalSource = this._toggleShowOriginalSource.bind(this); michael@0: } michael@0: michael@0: OptionsView.prototype = { michael@0: /** michael@0: * Initialization function, called when the debugger is started. michael@0: */ michael@0: initialize: function() { michael@0: dumpn("Initializing the OptionsView"); michael@0: michael@0: this._button = document.getElementById("debugger-options"); michael@0: this._autoPrettyPrint = document.getElementById("auto-pretty-print"); michael@0: this._pauseOnExceptionsItem = document.getElementById("pause-on-exceptions"); michael@0: this._ignoreCaughtExceptionsItem = document.getElementById("ignore-caught-exceptions"); michael@0: this._showPanesOnStartupItem = document.getElementById("show-panes-on-startup"); michael@0: this._showVariablesOnlyEnumItem = document.getElementById("show-vars-only-enum"); michael@0: this._showVariablesFilterBoxItem = document.getElementById("show-vars-filter-box"); michael@0: this._showOriginalSourceItem = document.getElementById("show-original-source"); michael@0: michael@0: this._autoPrettyPrint.setAttribute("checked", Prefs.autoPrettyPrint); michael@0: this._pauseOnExceptionsItem.setAttribute("checked", Prefs.pauseOnExceptions); michael@0: this._ignoreCaughtExceptionsItem.setAttribute("checked", Prefs.ignoreCaughtExceptions); michael@0: this._showPanesOnStartupItem.setAttribute("checked", Prefs.panesVisibleOnStartup); michael@0: this._showVariablesOnlyEnumItem.setAttribute("checked", Prefs.variablesOnlyEnumVisible); michael@0: this._showVariablesFilterBoxItem.setAttribute("checked", Prefs.variablesSearchboxVisible); michael@0: this._showOriginalSourceItem.setAttribute("checked", Prefs.sourceMapsEnabled); michael@0: }, michael@0: michael@0: /** michael@0: * Destruction function, called when the debugger is closed. michael@0: */ michael@0: destroy: function() { michael@0: dumpn("Destroying the OptionsView"); michael@0: // Nothing to do here yet. michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the 'gear menu' popup showing event. michael@0: */ michael@0: _onPopupShowing: function() { michael@0: this._button.setAttribute("open", "true"); michael@0: window.emit(EVENTS.OPTIONS_POPUP_SHOWING); michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the 'gear menu' popup hiding event. michael@0: */ michael@0: _onPopupHiding: function() { michael@0: this._button.removeAttribute("open"); michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the 'gear menu' popup hidden event. michael@0: */ michael@0: _onPopupHidden: function() { michael@0: window.emit(EVENTS.OPTIONS_POPUP_HIDDEN); michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the 'auto pretty print' menuitem command. michael@0: */ michael@0: _toggleAutoPrettyPrint: function(){ michael@0: Prefs.autoPrettyPrint = michael@0: this._autoPrettyPrint.getAttribute("checked") == "true"; michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the 'pause on exceptions' menuitem command. michael@0: */ michael@0: _togglePauseOnExceptions: function() { michael@0: Prefs.pauseOnExceptions = michael@0: this._pauseOnExceptionsItem.getAttribute("checked") == "true"; michael@0: michael@0: DebuggerController.activeThread.pauseOnExceptions( michael@0: Prefs.pauseOnExceptions, michael@0: Prefs.ignoreCaughtExceptions); michael@0: }, michael@0: michael@0: _toggleIgnoreCaughtExceptions: function() { michael@0: Prefs.ignoreCaughtExceptions = michael@0: this._ignoreCaughtExceptionsItem.getAttribute("checked") == "true"; michael@0: michael@0: DebuggerController.activeThread.pauseOnExceptions( michael@0: Prefs.pauseOnExceptions, michael@0: Prefs.ignoreCaughtExceptions); michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the 'show panes on startup' menuitem command. michael@0: */ michael@0: _toggleShowPanesOnStartup: function() { michael@0: Prefs.panesVisibleOnStartup = michael@0: this._showPanesOnStartupItem.getAttribute("checked") == "true"; michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the 'show non-enumerables' menuitem command. michael@0: */ michael@0: _toggleShowVariablesOnlyEnum: function() { michael@0: let pref = Prefs.variablesOnlyEnumVisible = michael@0: this._showVariablesOnlyEnumItem.getAttribute("checked") == "true"; michael@0: michael@0: DebuggerView.Variables.onlyEnumVisible = pref; michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the 'show variables searchbox' menuitem command. michael@0: */ michael@0: _toggleShowVariablesFilterBox: function() { michael@0: let pref = Prefs.variablesSearchboxVisible = michael@0: this._showVariablesFilterBoxItem.getAttribute("checked") == "true"; michael@0: michael@0: DebuggerView.Variables.searchEnabled = pref; michael@0: }, michael@0: michael@0: /** michael@0: * Listener handling the 'show original source' menuitem command. michael@0: */ michael@0: _toggleShowOriginalSource: function() { michael@0: let pref = Prefs.sourceMapsEnabled = michael@0: this._showOriginalSourceItem.getAttribute("checked") == "true"; michael@0: michael@0: // Don't block the UI while reconfiguring the server. michael@0: window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => { michael@0: // The popup panel needs more time to hide after triggering onpopuphidden. michael@0: window.setTimeout(() => { michael@0: DebuggerController.reconfigureThread(pref); michael@0: }, POPUP_HIDDEN_DELAY); michael@0: }, false); michael@0: }, michael@0: michael@0: _button: null, michael@0: _pauseOnExceptionsItem: null, michael@0: _showPanesOnStartupItem: null, michael@0: _showVariablesOnlyEnumItem: null, michael@0: _showVariablesFilterBoxItem: null, michael@0: _showOriginalSourceItem: null michael@0: }; michael@0: michael@0: /** michael@0: * Functions handling the stackframes UI. michael@0: */ michael@0: function StackFramesView() { michael@0: dumpn("StackFramesView was instantiated"); michael@0: michael@0: this._onStackframeRemoved = this._onStackframeRemoved.bind(this); michael@0: this._onSelect = this._onSelect.bind(this); michael@0: this._onScroll = this._onScroll.bind(this); michael@0: this._afterScroll = this._afterScroll.bind(this); michael@0: } michael@0: michael@0: StackFramesView.prototype = Heritage.extend(WidgetMethods, { michael@0: /** michael@0: * Initialization function, called when the debugger is started. michael@0: */ michael@0: initialize: function() { michael@0: dumpn("Initializing the StackFramesView"); michael@0: michael@0: this.widget = new BreadcrumbsWidget(document.getElementById("stackframes")); michael@0: this.widget.addEventListener("select", this._onSelect, false); michael@0: this.widget.addEventListener("scroll", this._onScroll, true); michael@0: window.addEventListener("resize", this._onScroll, true); michael@0: michael@0: this.autoFocusOnFirstItem = false; michael@0: this.autoFocusOnSelection = false; michael@0: michael@0: // This view's contents are also mirrored in a different container. michael@0: this._mirror = DebuggerView.StackFramesClassicList; michael@0: }, michael@0: michael@0: /** michael@0: * Destruction function, called when the debugger is closed. michael@0: */ michael@0: destroy: function() { michael@0: dumpn("Destroying the StackFramesView"); michael@0: michael@0: this.widget.removeEventListener("select", this._onSelect, false); michael@0: this.widget.removeEventListener("scroll", this._onScroll, true); michael@0: window.removeEventListener("resize", this._onScroll, true); michael@0: }, michael@0: michael@0: /** michael@0: * Adds a frame in this stackframes container. michael@0: * michael@0: * @param string aTitle michael@0: * The frame title (function name). michael@0: * @param string aUrl michael@0: * The frame source url. michael@0: * @param string aLine michael@0: * The frame line number. michael@0: * @param number aDepth michael@0: * The frame depth in the stack. michael@0: * @param boolean aIsBlackBoxed michael@0: * Whether or not the frame is black boxed. michael@0: */ michael@0: addFrame: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) { michael@0: // Blackboxed stack frames are collapsed into a single entry in michael@0: // the view. By convention, only the first frame is displayed. michael@0: if (aIsBlackBoxed) { michael@0: if (this._prevBlackBoxedUrl == aUrl) { michael@0: return; michael@0: } michael@0: this._prevBlackBoxedUrl = aUrl; michael@0: } else { michael@0: this._prevBlackBoxedUrl = null; michael@0: } michael@0: michael@0: // Create the element node for the stack frame item. michael@0: let frameView = this._createFrameView.apply(this, arguments); michael@0: michael@0: // Append a stack frame item to this container. michael@0: this.push([frameView], { michael@0: index: 0, /* specifies on which position should the item be appended */ michael@0: attachment: { michael@0: title: aTitle, michael@0: url: aUrl, michael@0: line: aLine, michael@0: depth: aDepth michael@0: }, michael@0: // Make sure that when the stack frame item is removed, the corresponding michael@0: // mirrored item in the classic list is also removed. michael@0: finalize: this._onStackframeRemoved michael@0: }); michael@0: michael@0: // Mirror this newly inserted item inside the "Call Stack" tab. michael@0: this._mirror.addFrame(aTitle, aUrl, aLine, aDepth); michael@0: }, michael@0: michael@0: /** michael@0: * Selects the frame at the specified depth in this container. michael@0: * @param number aDepth michael@0: */ michael@0: set selectedDepth(aDepth) { michael@0: this.selectedItem = aItem => aItem.attachment.depth == aDepth; michael@0: }, michael@0: michael@0: /** michael@0: * Gets the currently selected stack frame's depth in this container. michael@0: * This will essentially be the opposite of |selectedIndex|, which deals michael@0: * with the position in the view, where the last item added is actually michael@0: * the bottommost, not topmost. michael@0: * @return number michael@0: */ michael@0: get selectedDepth() { michael@0: return this.selectedItem.attachment.depth; michael@0: }, michael@0: michael@0: /** michael@0: * Specifies if the active thread has more frames that need to be loaded. michael@0: */ michael@0: dirty: false, michael@0: michael@0: /** michael@0: * Customization function for creating an item's UI. michael@0: * michael@0: * @param string aTitle michael@0: * The frame title to be displayed in the list. michael@0: * @param string aUrl michael@0: * The frame source url. michael@0: * @param string aLine michael@0: * The frame line number. michael@0: * @param number aDepth michael@0: * The frame depth in the stack. michael@0: * @param boolean aIsBlackBoxed michael@0: * Whether or not the frame is black boxed. michael@0: * @return nsIDOMNode michael@0: * The stack frame view. michael@0: */ michael@0: _createFrameView: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) { michael@0: let container = document.createElement("hbox"); michael@0: container.id = "stackframe-" + aDepth; michael@0: container.className = "dbg-stackframe"; michael@0: michael@0: let frameDetails = SourceUtils.trimUrlLength( michael@0: SourceUtils.getSourceLabel(aUrl), michael@0: STACK_FRAMES_SOURCE_URL_MAX_LENGTH, michael@0: STACK_FRAMES_SOURCE_URL_TRIM_SECTION); michael@0: michael@0: if (aIsBlackBoxed) { michael@0: container.classList.add("dbg-stackframe-black-boxed"); michael@0: } else { michael@0: let frameTitleNode = document.createElement("label"); michael@0: frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag"; michael@0: frameTitleNode.setAttribute("value", aTitle); michael@0: container.appendChild(frameTitleNode); michael@0: michael@0: frameDetails += SEARCH_LINE_FLAG + aLine; michael@0: } michael@0: michael@0: let frameDetailsNode = document.createElement("label"); michael@0: frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id"; michael@0: frameDetailsNode.setAttribute("value", frameDetails); michael@0: container.appendChild(frameDetailsNode); michael@0: michael@0: return container; michael@0: }, michael@0: michael@0: /** michael@0: * Function called each time a stack frame item is removed. michael@0: * michael@0: * @param object aItem michael@0: * The corresponding item. michael@0: */ michael@0: _onStackframeRemoved: function(aItem) { michael@0: dumpn("Finalizing stackframe item: " + aItem); michael@0: michael@0: // Remove the mirrored item in the classic list. michael@0: let depth = aItem.attachment.depth; michael@0: this._mirror.remove(this._mirror.getItemForAttachment(e => e.depth == depth)); michael@0: michael@0: // Forget the previously blackboxed stack frame url. michael@0: this._prevBlackBoxedUrl = null; michael@0: }, michael@0: michael@0: /** michael@0: * The select listener for the stackframes container. michael@0: */ michael@0: _onSelect: function(e) { michael@0: let stackframeItem = this.selectedItem; michael@0: if (stackframeItem) { michael@0: // The container is not empty and an actual item was selected. michael@0: let depth = stackframeItem.attachment.depth; michael@0: DebuggerController.StackFrames.selectFrame(depth); michael@0: michael@0: // Mirror the selected item in the classic list. michael@0: this.suppressSelectionEvents = true; michael@0: this._mirror.selectedItem = e => e.attachment.depth == depth; michael@0: this.suppressSelectionEvents = false; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The scroll listener for the stackframes container. michael@0: */ michael@0: _onScroll: function() { michael@0: // Update the stackframes container only if we have to. michael@0: if (!this.dirty) { michael@0: return; michael@0: } michael@0: // Allow requests to settle down first. michael@0: setNamedTimeout("stack-scroll", STACK_FRAMES_SCROLL_DELAY, this._afterScroll); michael@0: }, michael@0: michael@0: /** michael@0: * Requests the addition of more frames from the controller. michael@0: */ michael@0: _afterScroll: function() { michael@0: let scrollPosition = this.widget.getAttribute("scrollPosition"); michael@0: let scrollWidth = this.widget.getAttribute("scrollWidth"); michael@0: michael@0: // If the stackframes container scrolled almost to the end, with only michael@0: // 1/10 of a breadcrumb remaining, load more content. michael@0: if (scrollPosition - scrollWidth / 10 < 1) { michael@0: this.ensureIndexIsVisible(CALL_STACK_PAGE_SIZE - 1); michael@0: this.dirty = false; michael@0: michael@0: // Loads more stack frames from the debugger server cache. michael@0: DebuggerController.StackFrames.addMoreFrames(); michael@0: } michael@0: }, michael@0: michael@0: _mirror: null, michael@0: _prevBlackBoxedUrl: null michael@0: }); michael@0: michael@0: /* michael@0: * Functions handling the stackframes classic list UI. michael@0: * Controlled by the DebuggerView.StackFrames isntance. michael@0: */ michael@0: function StackFramesClassicListView() { michael@0: dumpn("StackFramesClassicListView was instantiated"); michael@0: michael@0: this._onSelect = this._onSelect.bind(this); michael@0: } michael@0: michael@0: StackFramesClassicListView.prototype = Heritage.extend(WidgetMethods, { michael@0: /** michael@0: * Initialization function, called when the debugger is started. michael@0: */ michael@0: initialize: function() { michael@0: dumpn("Initializing the StackFramesClassicListView"); michael@0: michael@0: this.widget = new SideMenuWidget(document.getElementById("callstack-list")); michael@0: this.widget.addEventListener("select", this._onSelect, false); michael@0: michael@0: this.emptyText = L10N.getStr("noStackFramesText"); michael@0: this.autoFocusOnFirstItem = false; michael@0: this.autoFocusOnSelection = false; michael@0: michael@0: // This view's contents are also mirrored in a different container. michael@0: this._mirror = DebuggerView.StackFrames; michael@0: }, michael@0: michael@0: /** michael@0: * Destruction function, called when the debugger is closed. michael@0: */ michael@0: destroy: function() { michael@0: dumpn("Destroying the StackFramesClassicListView"); michael@0: michael@0: this.widget.removeEventListener("select", this._onSelect, false); michael@0: }, michael@0: michael@0: /** michael@0: * Adds a frame in this stackframes container. michael@0: * michael@0: * @param string aTitle michael@0: * The frame title (function name). michael@0: * @param string aUrl michael@0: * The frame source url. michael@0: * @param string aLine michael@0: * The frame line number. michael@0: * @param number aDepth michael@0: * The frame depth in the stack. michael@0: */ michael@0: addFrame: function(aTitle, aUrl, aLine, aDepth) { michael@0: // Create the element node for the stack frame item. michael@0: let frameView = this._createFrameView.apply(this, arguments); michael@0: michael@0: // Append a stack frame item to this container. michael@0: this.push([frameView], { michael@0: attachment: { michael@0: depth: aDepth michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Customization function for creating an item's UI. michael@0: * michael@0: * @param string aTitle michael@0: * The frame title to be displayed in the list. michael@0: * @param string aUrl michael@0: * The frame source url. michael@0: * @param string aLine michael@0: * The frame line number. michael@0: * @param number aDepth michael@0: * The frame depth in the stack. michael@0: * @return nsIDOMNode michael@0: * The stack frame view. michael@0: */ michael@0: _createFrameView: function(aTitle, aUrl, aLine, aDepth) { michael@0: let container = document.createElement("hbox"); michael@0: container.id = "classic-stackframe-" + aDepth; michael@0: container.className = "dbg-classic-stackframe"; michael@0: container.setAttribute("flex", "1"); michael@0: michael@0: let frameTitleNode = document.createElement("label"); michael@0: frameTitleNode.className = "plain dbg-classic-stackframe-title"; michael@0: frameTitleNode.setAttribute("value", aTitle); michael@0: frameTitleNode.setAttribute("crop", "center"); michael@0: michael@0: let frameDetailsNode = document.createElement("hbox"); michael@0: frameDetailsNode.className = "plain dbg-classic-stackframe-details"; michael@0: michael@0: let frameUrlNode = document.createElement("label"); michael@0: frameUrlNode.className = "plain dbg-classic-stackframe-details-url"; michael@0: frameUrlNode.setAttribute("value", SourceUtils.getSourceLabel(aUrl)); michael@0: frameUrlNode.setAttribute("crop", "center"); michael@0: frameDetailsNode.appendChild(frameUrlNode); michael@0: michael@0: let frameDetailsSeparator = document.createElement("label"); michael@0: frameDetailsSeparator.className = "plain dbg-classic-stackframe-details-sep"; michael@0: frameDetailsSeparator.setAttribute("value", SEARCH_LINE_FLAG); michael@0: frameDetailsNode.appendChild(frameDetailsSeparator); michael@0: michael@0: let frameLineNode = document.createElement("label"); michael@0: frameLineNode.className = "plain dbg-classic-stackframe-details-line"; michael@0: frameLineNode.setAttribute("value", aLine); michael@0: frameDetailsNode.appendChild(frameLineNode); michael@0: michael@0: container.appendChild(frameTitleNode); michael@0: container.appendChild(frameDetailsNode); michael@0: michael@0: return container; michael@0: }, michael@0: michael@0: /** michael@0: * The select listener for the stackframes container. michael@0: */ michael@0: _onSelect: function(e) { michael@0: let stackframeItem = this.selectedItem; michael@0: if (stackframeItem) { michael@0: // The container is not empty and an actual item was selected. michael@0: // Mirror the selected item in the breadcrumbs list. michael@0: let depth = stackframeItem.attachment.depth; michael@0: this._mirror.selectedItem = e => e.attachment.depth == depth; michael@0: } michael@0: }, michael@0: michael@0: _mirror: null michael@0: }); michael@0: michael@0: /** michael@0: * Functions handling the filtering UI. michael@0: */ michael@0: function FilterView() { michael@0: dumpn("FilterView was instantiated"); michael@0: michael@0: this._onClick = this._onClick.bind(this); michael@0: this._onInput = this._onInput.bind(this); michael@0: this._onKeyPress = this._onKeyPress.bind(this); michael@0: this._onBlur = this._onBlur.bind(this); michael@0: } michael@0: michael@0: FilterView.prototype = { michael@0: /** michael@0: * Initialization function, called when the debugger is started. michael@0: */ michael@0: initialize: function() { michael@0: dumpn("Initializing the FilterView"); michael@0: michael@0: this._searchbox = document.getElementById("searchbox"); michael@0: this._searchboxHelpPanel = document.getElementById("searchbox-help-panel"); michael@0: this._filterLabel = document.getElementById("filter-label"); michael@0: this._globalOperatorButton = document.getElementById("global-operator-button"); michael@0: this._globalOperatorLabel = document.getElementById("global-operator-label"); michael@0: this._functionOperatorButton = document.getElementById("function-operator-button"); michael@0: this._functionOperatorLabel = document.getElementById("function-operator-label"); michael@0: this._tokenOperatorButton = document.getElementById("token-operator-button"); michael@0: this._tokenOperatorLabel = document.getElementById("token-operator-label"); michael@0: this._lineOperatorButton = document.getElementById("line-operator-button"); michael@0: this._lineOperatorLabel = document.getElementById("line-operator-label"); michael@0: this._variableOperatorButton = document.getElementById("variable-operator-button"); michael@0: this._variableOperatorLabel = document.getElementById("variable-operator-label"); michael@0: michael@0: this._fileSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("fileSearchKey")); michael@0: this._globalSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("globalSearchKey")); michael@0: this._filteredFunctionsKey = ShortcutUtils.prettifyShortcut(document.getElementById("functionSearchKey")); michael@0: this._tokenSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("tokenSearchKey")); michael@0: this._lineSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("lineSearchKey")); michael@0: this._variableSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("variableSearchKey")); michael@0: michael@0: this._searchbox.addEventListener("click", this._onClick, false); michael@0: this._searchbox.addEventListener("select", this._onInput, false); michael@0: this._searchbox.addEventListener("input", this._onInput, false); michael@0: this._searchbox.addEventListener("keypress", this._onKeyPress, false); michael@0: this._searchbox.addEventListener("blur", this._onBlur, false); michael@0: michael@0: let placeholder = L10N.getFormatStr("emptySearchText", this._fileSearchKey); michael@0: this._searchbox.setAttribute("placeholder", placeholder); michael@0: michael@0: this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG); michael@0: this._functionOperatorButton.setAttribute("label", SEARCH_FUNCTION_FLAG); michael@0: this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG); michael@0: this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG); michael@0: this._variableOperatorButton.setAttribute("label", SEARCH_VARIABLE_FLAG); michael@0: michael@0: this._filterLabel.setAttribute("value", michael@0: L10N.getFormatStr("searchPanelFilter", this._fileSearchKey)); michael@0: this._globalOperatorLabel.setAttribute("value", michael@0: L10N.getFormatStr("searchPanelGlobal", this._globalSearchKey)); michael@0: this._functionOperatorLabel.setAttribute("value", michael@0: L10N.getFormatStr("searchPanelFunction", this._filteredFunctionsKey)); michael@0: this._tokenOperatorLabel.setAttribute("value", michael@0: L10N.getFormatStr("searchPanelToken", this._tokenSearchKey)); michael@0: this._lineOperatorLabel.setAttribute("value", michael@0: L10N.getFormatStr("searchPanelGoToLine", this._lineSearchKey)); michael@0: this._variableOperatorLabel.setAttribute("value", michael@0: L10N.getFormatStr("searchPanelVariable", this._variableSearchKey)); michael@0: }, michael@0: michael@0: /** michael@0: * Destruction function, called when the debugger is closed. michael@0: */ michael@0: destroy: function() { michael@0: dumpn("Destroying the FilterView"); michael@0: michael@0: this._searchbox.removeEventListener("click", this._onClick, false); michael@0: this._searchbox.removeEventListener("select", this._onInput, false); michael@0: this._searchbox.removeEventListener("input", this._onInput, false); michael@0: this._searchbox.removeEventListener("keypress", this._onKeyPress, false); michael@0: this._searchbox.removeEventListener("blur", this._onBlur, false); michael@0: }, michael@0: michael@0: /** michael@0: * Gets the entered operator and arguments in the searchbox. michael@0: * @return array michael@0: */ michael@0: get searchData() { michael@0: let operator = "", args = []; michael@0: michael@0: let rawValue = this._searchbox.value; michael@0: let rawLength = rawValue.length; michael@0: let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG); michael@0: let functionFlagIndex = rawValue.indexOf(SEARCH_FUNCTION_FLAG); michael@0: let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG); michael@0: let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG); michael@0: let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG); michael@0: michael@0: // This is not a global, function or variable search, allow file/line flags. michael@0: if (globalFlagIndex != 0 && functionFlagIndex != 0 && variableFlagIndex != 0) { michael@0: // Token search has precedence over line search. michael@0: if (tokenFlagIndex != -1) { michael@0: operator = SEARCH_TOKEN_FLAG; michael@0: args.push(rawValue.slice(0, tokenFlagIndex)); // file michael@0: args.push(rawValue.substr(tokenFlagIndex + 1, rawLength)); // token michael@0: } else if (lineFlagIndex != -1) { michael@0: operator = SEARCH_LINE_FLAG; michael@0: args.push(rawValue.slice(0, lineFlagIndex)); // file michael@0: args.push(+rawValue.substr(lineFlagIndex + 1, rawLength) || 0); // line michael@0: } else { michael@0: args.push(rawValue); michael@0: } michael@0: } michael@0: // Global searches dissalow the use of file or line flags. michael@0: else if (globalFlagIndex == 0) { michael@0: operator = SEARCH_GLOBAL_FLAG; michael@0: args.push(rawValue.slice(1)); michael@0: } michael@0: // Function searches dissalow the use of file or line flags. michael@0: else if (functionFlagIndex == 0) { michael@0: operator = SEARCH_FUNCTION_FLAG; michael@0: args.push(rawValue.slice(1)); michael@0: } michael@0: // Variable searches dissalow the use of file or line flags. michael@0: else if (variableFlagIndex == 0) { michael@0: operator = SEARCH_VARIABLE_FLAG; michael@0: args.push(rawValue.slice(1)); michael@0: } michael@0: michael@0: return [operator, args]; michael@0: }, michael@0: michael@0: /** michael@0: * Returns the current search operator. michael@0: * @return string michael@0: */ michael@0: get searchOperator() this.searchData[0], michael@0: michael@0: /** michael@0: * Returns the current search arguments. michael@0: * @return array michael@0: */ michael@0: get searchArguments() this.searchData[1], michael@0: michael@0: /** michael@0: * Clears the text from the searchbox and any changed views. michael@0: */ michael@0: clearSearch: function() { michael@0: this._searchbox.value = ""; michael@0: this.clearViews(); michael@0: }, michael@0: michael@0: /** michael@0: * Clears all the views that may pop up when searching. michael@0: */ michael@0: clearViews: function() { michael@0: DebuggerView.GlobalSearch.clearView(); michael@0: DebuggerView.FilteredSources.clearView(); michael@0: DebuggerView.FilteredFunctions.clearView(); michael@0: this._searchboxHelpPanel.hidePopup(); michael@0: }, michael@0: michael@0: /** michael@0: * Performs a line search if necessary. michael@0: * (Jump to lines in the currently visible source). michael@0: * michael@0: * @param number aLine michael@0: * The source line number to jump to. michael@0: */ michael@0: _performLineSearch: function(aLine) { michael@0: // Make sure we're actually searching for a valid line. michael@0: if (aLine) { michael@0: DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 }, "center"); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Performs a token search if necessary. michael@0: * (Search for tokens in the currently visible source). michael@0: * michael@0: * @param string aToken michael@0: * The source token to find. michael@0: */ michael@0: _performTokenSearch: function(aToken) { michael@0: // Make sure we're actually searching for a valid token. michael@0: if (!aToken) { michael@0: return; michael@0: } michael@0: DebuggerView.editor.find(aToken); michael@0: }, michael@0: michael@0: /** michael@0: * The click listener for the search container. michael@0: */ michael@0: _onClick: function() { michael@0: // If there's some text in the searchbox, displaying a panel would michael@0: // interfere with double/triple click default behaviors. michael@0: if (!this._searchbox.value) { michael@0: this._searchboxHelpPanel.openPopup(this._searchbox); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The input listener for the search container. michael@0: */ michael@0: _onInput: function() { michael@0: this.clearViews(); michael@0: michael@0: // Make sure we're actually searching for something. michael@0: if (!this._searchbox.value) { michael@0: return; michael@0: } michael@0: michael@0: // Perform the required search based on the specified operator. michael@0: switch (this.searchOperator) { michael@0: case SEARCH_GLOBAL_FLAG: michael@0: // Schedule a global search for when the user stops typing. michael@0: DebuggerView.GlobalSearch.scheduleSearch(this.searchArguments[0]); michael@0: break; michael@0: case SEARCH_FUNCTION_FLAG: michael@0: // Schedule a function search for when the user stops typing. michael@0: DebuggerView.FilteredFunctions.scheduleSearch(this.searchArguments[0]); michael@0: break; michael@0: case SEARCH_VARIABLE_FLAG: michael@0: // Schedule a variable search for when the user stops typing. michael@0: DebuggerView.Variables.scheduleSearch(this.searchArguments[0]); michael@0: break; michael@0: case SEARCH_TOKEN_FLAG: michael@0: // Schedule a file+token search for when the user stops typing. michael@0: DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]); michael@0: this._performTokenSearch(this.searchArguments[1]); michael@0: break; michael@0: case SEARCH_LINE_FLAG: michael@0: // Schedule a file+line search for when the user stops typing. michael@0: DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]); michael@0: this._performLineSearch(this.searchArguments[1]); michael@0: break; michael@0: default: michael@0: // Schedule a file only search for when the user stops typing. michael@0: DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The key press listener for the search container. michael@0: */ michael@0: _onKeyPress: function(e) { michael@0: // This attribute is not implemented in Gecko at this time, see bug 680830. michael@0: e.char = String.fromCharCode(e.charCode); michael@0: michael@0: // Perform the required action based on the specified operator. michael@0: let [operator, args] = this.searchData; michael@0: let isGlobalSearch = operator == SEARCH_GLOBAL_FLAG; michael@0: let isFunctionSearch = operator == SEARCH_FUNCTION_FLAG; michael@0: let isVariableSearch = operator == SEARCH_VARIABLE_FLAG; michael@0: let isTokenSearch = operator == SEARCH_TOKEN_FLAG; michael@0: let isLineSearch = operator == SEARCH_LINE_FLAG; michael@0: let isFileOnlySearch = !operator && args.length == 1; michael@0: michael@0: // Depending on the pressed keys, determine to correct action to perform. michael@0: let actionToPerform; michael@0: michael@0: // Meta+G and Ctrl+N focus next matches. michael@0: if ((e.char == "g" && e.metaKey) || e.char == "n" && e.ctrlKey) { michael@0: actionToPerform = "selectNext"; michael@0: } michael@0: // Meta+Shift+G and Ctrl+P focus previous matches. michael@0: else if ((e.char == "G" && e.metaKey) || e.char == "p" && e.ctrlKey) { michael@0: actionToPerform = "selectPrev"; michael@0: } michael@0: // Return, enter, down and up keys focus next or previous matches, while michael@0: // the escape key switches focus from the search container. michael@0: else switch (e.keyCode) { michael@0: case e.DOM_VK_RETURN: michael@0: var isReturnKey = true; michael@0: // If the shift key is pressed, focus on the previous result michael@0: actionToPerform = e.shiftKey ? "selectPrev" : "selectNext"; michael@0: break; michael@0: case e.DOM_VK_DOWN: michael@0: actionToPerform = "selectNext"; michael@0: break; michael@0: case e.DOM_VK_UP: michael@0: actionToPerform = "selectPrev"; michael@0: break; michael@0: } michael@0: michael@0: // If there's no action to perform, or no operator, file line or token michael@0: // were specified, then this is either a broken or empty search. michael@0: if (!actionToPerform || (!operator && !args.length)) { michael@0: DebuggerView.editor.dropSelection(); michael@0: return; michael@0: } michael@0: michael@0: e.preventDefault(); michael@0: e.stopPropagation(); michael@0: michael@0: // Jump to the next/previous entry in the global search, or perform michael@0: // a new global search immediately michael@0: if (isGlobalSearch) { michael@0: let targetView = DebuggerView.GlobalSearch; michael@0: if (!isReturnKey) { michael@0: targetView[actionToPerform](); michael@0: } else if (targetView.hidden) { michael@0: targetView.scheduleSearch(args[0], 0); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Jump to the next/previous entry in the function search, perform michael@0: // a new function search immediately, or clear it. michael@0: if (isFunctionSearch) { michael@0: let targetView = DebuggerView.FilteredFunctions; michael@0: if (!isReturnKey) { michael@0: targetView[actionToPerform](); michael@0: } else if (targetView.hidden) { michael@0: targetView.scheduleSearch(args[0], 0); michael@0: } else { michael@0: if (!targetView.selectedItem) { michael@0: targetView.selectedIndex = 0; michael@0: } michael@0: this.clearSearch(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Perform a new variable search immediately. michael@0: if (isVariableSearch) { michael@0: let targetView = DebuggerView.Variables; michael@0: if (isReturnKey) { michael@0: targetView.scheduleSearch(args[0], 0); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Jump to the next/previous entry in the file search, perform michael@0: // a new file search immediately, or clear it. michael@0: if (isFileOnlySearch) { michael@0: let targetView = DebuggerView.FilteredSources; michael@0: if (!isReturnKey) { michael@0: targetView[actionToPerform](); michael@0: } else if (targetView.hidden) { michael@0: targetView.scheduleSearch(args[0], 0); michael@0: } else { michael@0: if (!targetView.selectedItem) { michael@0: targetView.selectedIndex = 0; michael@0: } michael@0: this.clearSearch(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Jump to the next/previous instance of the currently searched token. michael@0: if (isTokenSearch) { michael@0: let methods = { selectNext: "findNext", selectPrev: "findPrev" }; michael@0: DebuggerView.editor[methods[actionToPerform]](); michael@0: return; michael@0: } michael@0: michael@0: // Increment/decrement the currently searched caret line. michael@0: if (isLineSearch) { michael@0: let [, line] = args; michael@0: let amounts = { selectNext: 1, selectPrev: -1 }; michael@0: michael@0: // Modify the line number and jump to it. michael@0: line += !isReturnKey ? amounts[actionToPerform] : 0; michael@0: let lineCount = DebuggerView.editor.lineCount(); michael@0: let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line; michael@0: this._doSearch(SEARCH_LINE_FLAG, lineTarget); michael@0: return; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The blur listener for the search container. michael@0: */ michael@0: _onBlur: function() { michael@0: this.clearViews(); michael@0: }, michael@0: michael@0: /** michael@0: * Called when a filtering key sequence was pressed. michael@0: * michael@0: * @param string aOperator michael@0: * The operator to use for filtering. michael@0: */ michael@0: _doSearch: function(aOperator = "", aText = "") { michael@0: this._searchbox.focus(); michael@0: this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738. michael@0: michael@0: if (aText) { michael@0: this._searchbox.value = aOperator + aText; michael@0: return; michael@0: } michael@0: if (DebuggerView.editor.somethingSelected()) { michael@0: this._searchbox.value = aOperator + DebuggerView.editor.getSelection(); michael@0: return; michael@0: } michael@0: if (SEARCH_AUTOFILL.indexOf(aOperator) != -1) { michael@0: let cursor = DebuggerView.editor.getCursor(); michael@0: let content = DebuggerView.editor.getText(); michael@0: let location = DebuggerView.Sources.selectedValue; michael@0: let source = DebuggerController.Parser.get(content, location); michael@0: let identifier = source.getIdentifierAt({ line: cursor.line+1, column: cursor.ch }); michael@0: michael@0: if (identifier && identifier.name) { michael@0: this._searchbox.value = aOperator + identifier.name; michael@0: this._searchbox.select(); michael@0: this._searchbox.selectionStart += aOperator.length; michael@0: return; michael@0: } michael@0: } michael@0: this._searchbox.value = aOperator; michael@0: }, michael@0: michael@0: /** michael@0: * Called when the source location filter key sequence was pressed. michael@0: */ michael@0: _doFileSearch: function() { michael@0: this._doSearch(); michael@0: this._searchboxHelpPanel.openPopup(this._searchbox); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the global search filter key sequence was pressed. michael@0: */ michael@0: _doGlobalSearch: function() { michael@0: this._doSearch(SEARCH_GLOBAL_FLAG); michael@0: this._searchboxHelpPanel.hidePopup(); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the source function filter key sequence was pressed. michael@0: */ michael@0: _doFunctionSearch: function() { michael@0: this._doSearch(SEARCH_FUNCTION_FLAG); michael@0: this._searchboxHelpPanel.hidePopup(); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the source token filter key sequence was pressed. michael@0: */ michael@0: _doTokenSearch: function() { michael@0: this._doSearch(SEARCH_TOKEN_FLAG); michael@0: this._searchboxHelpPanel.hidePopup(); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the source line filter key sequence was pressed. michael@0: */ michael@0: _doLineSearch: function() { michael@0: this._doSearch(SEARCH_LINE_FLAG); michael@0: this._searchboxHelpPanel.hidePopup(); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the variable search filter key sequence was pressed. michael@0: */ michael@0: _doVariableSearch: function() { michael@0: this._doSearch(SEARCH_VARIABLE_FLAG); michael@0: this._searchboxHelpPanel.hidePopup(); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the variables focus key sequence was pressed. michael@0: */ michael@0: _doVariablesFocus: function() { michael@0: DebuggerView.showInstrumentsPane(); michael@0: DebuggerView.Variables.focusFirstVisibleItem(); michael@0: }, michael@0: michael@0: _searchbox: null, michael@0: _searchboxHelpPanel: null, michael@0: _globalOperatorButton: null, michael@0: _globalOperatorLabel: null, michael@0: _functionOperatorButton: null, michael@0: _functionOperatorLabel: null, michael@0: _tokenOperatorButton: null, michael@0: _tokenOperatorLabel: null, michael@0: _lineOperatorButton: null, michael@0: _lineOperatorLabel: null, michael@0: _variableOperatorButton: null, michael@0: _variableOperatorLabel: null, michael@0: _fileSearchKey: "", michael@0: _globalSearchKey: "", michael@0: _filteredFunctionsKey: "", michael@0: _tokenSearchKey: "", michael@0: _lineSearchKey: "", michael@0: _variableSearchKey: "", michael@0: }; michael@0: michael@0: /** michael@0: * Functions handling the filtered sources UI. michael@0: */ michael@0: function FilteredSourcesView() { michael@0: dumpn("FilteredSourcesView was instantiated"); michael@0: michael@0: this._onClick = this._onClick.bind(this); michael@0: this._onSelect = this._onSelect.bind(this); michael@0: } michael@0: michael@0: FilteredSourcesView.prototype = Heritage.extend(ResultsPanelContainer.prototype, { michael@0: /** michael@0: * Initialization function, called when the debugger is started. michael@0: */ michael@0: initialize: function() { michael@0: dumpn("Initializing the FilteredSourcesView"); michael@0: michael@0: this.anchor = document.getElementById("searchbox"); michael@0: this.widget.addEventListener("select", this._onSelect, false); michael@0: this.widget.addEventListener("click", this._onClick, false); michael@0: }, michael@0: michael@0: /** michael@0: * Destruction function, called when the debugger is closed. michael@0: */ michael@0: destroy: function() { michael@0: dumpn("Destroying the FilteredSourcesView"); michael@0: michael@0: this.widget.removeEventListener("select", this._onSelect, false); michael@0: this.widget.removeEventListener("click", this._onClick, false); michael@0: this.anchor = null; michael@0: }, michael@0: michael@0: /** michael@0: * Schedules searching for a source. michael@0: * michael@0: * @param string aToken michael@0: * The function to search for. michael@0: * @param number aWait michael@0: * The amount of milliseconds to wait until draining. michael@0: */ michael@0: scheduleSearch: function(aToken, aWait) { michael@0: // The amount of time to wait for the requests to settle. michael@0: let maxDelay = FILE_SEARCH_ACTION_MAX_DELAY; michael@0: let delay = aWait === undefined ? maxDelay / aToken.length : aWait; michael@0: michael@0: // Allow requests to settle down first. michael@0: setNamedTimeout("sources-search", delay, () => this._doSearch(aToken)); michael@0: }, michael@0: michael@0: /** michael@0: * Finds file matches in all the displayed sources. michael@0: * michael@0: * @param string aToken michael@0: * The string to search for. michael@0: */ michael@0: _doSearch: function(aToken, aStore = []) { michael@0: // Don't continue filtering if the searched token is an empty string. michael@0: // In contrast with function searching, in this case we don't want to michael@0: // show a list of all the files when no search token was supplied. michael@0: if (!aToken) { michael@0: return; michael@0: } michael@0: michael@0: for (let item of DebuggerView.Sources.items) { michael@0: let lowerCaseLabel = item.attachment.label.toLowerCase(); michael@0: let lowerCaseToken = aToken.toLowerCase(); michael@0: if (lowerCaseLabel.match(lowerCaseToken)) { michael@0: aStore.push(item); michael@0: } michael@0: michael@0: // Once the maximum allowed number of results is reached, proceed michael@0: // with building the UI immediately. michael@0: if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) { michael@0: this._syncView(aStore); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Couldn't reach the maximum allowed number of results, but that's ok, michael@0: // continue building the UI. michael@0: this._syncView(aStore); michael@0: }, michael@0: michael@0: /** michael@0: * Updates the list of sources displayed in this container. michael@0: * michael@0: * @param array aSearchResults michael@0: * The results array, containing search details for each source. michael@0: */ michael@0: _syncView: function(aSearchResults) { michael@0: // If there are no matches found, keep the popup hidden and avoid michael@0: // creating the view. michael@0: if (!aSearchResults.length) { michael@0: window.emit(EVENTS.FILE_SEARCH_MATCH_NOT_FOUND); michael@0: return; michael@0: } michael@0: michael@0: for (let item of aSearchResults) { michael@0: // Create the element node for the location item. michael@0: let itemView = this._createItemView( michael@0: SourceUtils.trimUrlLength(item.attachment.label), michael@0: SourceUtils.trimUrlLength(item.value, 0, "start") michael@0: ); michael@0: michael@0: // Append a location item to this container for each match. michael@0: this.push([itemView], { michael@0: index: -1, /* specifies on which position should the item be appended */ michael@0: attachment: { michael@0: url: item.value michael@0: } michael@0: }); michael@0: } michael@0: michael@0: // There's at least one item displayed in this container. Don't select it michael@0: // automatically if not forced (by tests) or in tandem with an operator. michael@0: if (this._autoSelectFirstItem || DebuggerView.Filtering.searchOperator) { michael@0: this.selectedIndex = 0; michael@0: } michael@0: this.hidden = false; michael@0: michael@0: // Signal that file search matches were found and displayed. michael@0: window.emit(EVENTS.FILE_SEARCH_MATCH_FOUND); michael@0: }, michael@0: michael@0: /** michael@0: * The click listener for this container. michael@0: */ michael@0: _onClick: function(e) { michael@0: let locationItem = this.getItemForElement(e.target); michael@0: if (locationItem) { michael@0: this.selectedItem = locationItem; michael@0: DebuggerView.Filtering.clearSearch(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The select listener for this container. michael@0: * michael@0: * @param object aItem michael@0: * The item associated with the element to select. michael@0: */ michael@0: _onSelect: function({ detail: locationItem }) { michael@0: if (locationItem) { michael@0: DebuggerView.setEditorLocation(locationItem.attachment.url, undefined, { michael@0: noCaret: true, michael@0: noDebug: true michael@0: }); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Functions handling the function search UI. michael@0: */ michael@0: function FilteredFunctionsView() { michael@0: dumpn("FilteredFunctionsView was instantiated"); michael@0: michael@0: this._onClick = this._onClick.bind(this); michael@0: this._onSelect = this._onSelect.bind(this); michael@0: } michael@0: michael@0: FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototype, { michael@0: /** michael@0: * Initialization function, called when the debugger is started. michael@0: */ michael@0: initialize: function() { michael@0: dumpn("Initializing the FilteredFunctionsView"); michael@0: michael@0: this.anchor = document.getElementById("searchbox"); michael@0: this.widget.addEventListener("select", this._onSelect, false); michael@0: this.widget.addEventListener("click", this._onClick, false); michael@0: }, michael@0: michael@0: /** michael@0: * Destruction function, called when the debugger is closed. michael@0: */ michael@0: destroy: function() { michael@0: dumpn("Destroying the FilteredFunctionsView"); michael@0: michael@0: this.widget.removeEventListener("select", this._onSelect, false); michael@0: this.widget.removeEventListener("click", this._onClick, false); michael@0: this.anchor = null; michael@0: }, michael@0: michael@0: /** michael@0: * Schedules searching for a function in all of the sources. michael@0: * michael@0: * @param string aToken michael@0: * The function to search for. michael@0: * @param number aWait michael@0: * The amount of milliseconds to wait until draining. michael@0: */ michael@0: scheduleSearch: function(aToken, aWait) { michael@0: // The amount of time to wait for the requests to settle. michael@0: let maxDelay = FUNCTION_SEARCH_ACTION_MAX_DELAY; michael@0: let delay = aWait === undefined ? maxDelay / aToken.length : aWait; michael@0: michael@0: // Allow requests to settle down first. michael@0: setNamedTimeout("function-search", delay, () => { michael@0: // Start fetching as many sources as possible, then perform the search. michael@0: let urls = DebuggerView.Sources.values; michael@0: let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(urls); michael@0: sourcesFetched.then(aSources => this._doSearch(aToken, aSources)); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Finds function matches in all the sources stored in the cache, and groups michael@0: * them by location and line number. michael@0: * michael@0: * @param string aToken michael@0: * The string to search for. michael@0: * @param array aSources michael@0: * An array of [url, text] tuples for each source. michael@0: */ michael@0: _doSearch: function(aToken, aSources, aStore = []) { michael@0: // Continue parsing even if the searched token is an empty string, to michael@0: // cache the syntax tree nodes generated by the reflection API. michael@0: michael@0: // Make sure the currently displayed source is parsed first. Once the michael@0: // maximum allowed number of results are found, parsing will be halted. michael@0: let currentUrl = DebuggerView.Sources.selectedValue; michael@0: let currentSource = aSources.filter(([sourceUrl]) => sourceUrl == currentUrl)[0]; michael@0: aSources.splice(aSources.indexOf(currentSource), 1); michael@0: aSources.unshift(currentSource); michael@0: michael@0: // If not searching for a specific function, only parse the displayed source, michael@0: // which is now the first item in the sources array. michael@0: if (!aToken) { michael@0: aSources.splice(1); michael@0: } michael@0: michael@0: for (let [location, contents] of aSources) { michael@0: let parsedSource = DebuggerController.Parser.get(contents, location); michael@0: let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken); michael@0: michael@0: for (let scriptResult of sourceResults) { michael@0: for (let parseResult of scriptResult) { michael@0: aStore.push({ michael@0: sourceUrl: scriptResult.sourceUrl, michael@0: scriptOffset: scriptResult.scriptOffset, michael@0: functionName: parseResult.functionName, michael@0: functionLocation: parseResult.functionLocation, michael@0: inferredName: parseResult.inferredName, michael@0: inferredChain: parseResult.inferredChain, michael@0: inferredLocation: parseResult.inferredLocation michael@0: }); michael@0: michael@0: // Once the maximum allowed number of results is reached, proceed michael@0: // with building the UI immediately. michael@0: if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) { michael@0: this._syncView(aStore); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Couldn't reach the maximum allowed number of results, but that's ok, michael@0: // continue building the UI. michael@0: this._syncView(aStore); michael@0: }, michael@0: michael@0: /** michael@0: * Updates the list of functions displayed in this container. michael@0: * michael@0: * @param array aSearchResults michael@0: * The results array, containing search details for each source. michael@0: */ michael@0: _syncView: function(aSearchResults) { michael@0: // If there are no matches found, keep the popup hidden and avoid michael@0: // creating the view. michael@0: if (!aSearchResults.length) { michael@0: window.emit(EVENTS.FUNCTION_SEARCH_MATCH_NOT_FOUND); michael@0: return; michael@0: } michael@0: michael@0: for (let item of aSearchResults) { michael@0: // Some function expressions don't necessarily have a name, but the michael@0: // parser provides us with an inferred name from an enclosing michael@0: // VariableDeclarator, AssignmentExpression, ObjectExpression node. michael@0: if (item.functionName && item.inferredName && michael@0: item.functionName != item.inferredName) { michael@0: let s = " " + L10N.getStr("functionSearchSeparatorLabel") + " "; michael@0: item.displayedName = item.inferredName + s + item.functionName; michael@0: } michael@0: // The function doesn't have an explicit name, but it could be inferred. michael@0: else if (item.inferredName) { michael@0: item.displayedName = item.inferredName; michael@0: } michael@0: // The function only has an explicit name. michael@0: else { michael@0: item.displayedName = item.functionName; michael@0: } michael@0: michael@0: // Some function expressions have unexpected bounds, since they may not michael@0: // necessarily have an associated name defining them. michael@0: if (item.inferredLocation) { michael@0: item.actualLocation = item.inferredLocation; michael@0: } else { michael@0: item.actualLocation = item.functionLocation; michael@0: } michael@0: michael@0: // Create the element node for the function item. michael@0: let itemView = this._createItemView( michael@0: SourceUtils.trimUrlLength(item.displayedName + "()"), michael@0: SourceUtils.trimUrlLength(item.sourceUrl, 0, "start"), michael@0: (item.inferredChain || []).join(".") michael@0: ); michael@0: michael@0: // Append a function item to this container for each match. michael@0: this.push([itemView], { michael@0: index: -1, /* specifies on which position should the item be appended */ michael@0: attachment: item michael@0: }); michael@0: } michael@0: michael@0: // There's at least one item displayed in this container. Don't select it michael@0: // automatically if not forced (by tests). michael@0: if (this._autoSelectFirstItem) { michael@0: this.selectedIndex = 0; michael@0: } michael@0: this.hidden = false; michael@0: michael@0: // Signal that function search matches were found and displayed. michael@0: window.emit(EVENTS.FUNCTION_SEARCH_MATCH_FOUND); michael@0: }, michael@0: michael@0: /** michael@0: * The click listener for this container. michael@0: */ michael@0: _onClick: function(e) { michael@0: let functionItem = this.getItemForElement(e.target); michael@0: if (functionItem) { michael@0: this.selectedItem = functionItem; michael@0: DebuggerView.Filtering.clearSearch(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The select listener for this container. michael@0: */ michael@0: _onSelect: function({ detail: functionItem }) { michael@0: if (functionItem) { michael@0: let sourceUrl = functionItem.attachment.sourceUrl; michael@0: let scriptOffset = functionItem.attachment.scriptOffset; michael@0: let actualLocation = functionItem.attachment.actualLocation; michael@0: michael@0: DebuggerView.setEditorLocation(sourceUrl, actualLocation.start.line, { michael@0: charOffset: scriptOffset, michael@0: columnOffset: actualLocation.start.column, michael@0: align: "center", michael@0: noDebug: true michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: _searchTimeout: null, michael@0: _searchFunction: null, michael@0: _searchedToken: "" michael@0: }); michael@0: michael@0: /** michael@0: * Preliminary setup for the DebuggerView object. michael@0: */ michael@0: DebuggerView.Toolbar = new ToolbarView(); michael@0: DebuggerView.Options = new OptionsView(); michael@0: DebuggerView.Filtering = new FilterView(); michael@0: DebuggerView.FilteredSources = new FilteredSourcesView(); michael@0: DebuggerView.FilteredFunctions = new FilteredFunctionsView(); michael@0: DebuggerView.StackFrames = new StackFramesView(); michael@0: DebuggerView.StackFramesClassicList = new StackFramesClassicListView();