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: const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes michael@0: const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars michael@0: const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars michael@0: const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center"; michael@0: const STACK_FRAMES_SCROLL_DELAY = 100; // ms michael@0: const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars michael@0: const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start"; michael@0: const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px michael@0: const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px michael@0: const RESULTS_PANEL_POPUP_POSITION = "before_end"; michael@0: const RESULTS_PANEL_MAX_RESULTS = 10; michael@0: const FILE_SEARCH_ACTION_MAX_DELAY = 300; // ms michael@0: const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50; michael@0: const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars michael@0: const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms michael@0: const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms michael@0: const SEARCH_GLOBAL_FLAG = "!"; michael@0: const SEARCH_FUNCTION_FLAG = "@"; michael@0: const SEARCH_TOKEN_FLAG = "#"; michael@0: const SEARCH_LINE_FLAG = ":"; michael@0: const SEARCH_VARIABLE_FLAG = "*"; michael@0: const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG]; michael@0: const EDITOR_VARIABLE_HOVER_DELAY = 350; // ms michael@0: const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft"; michael@0: const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft"; michael@0: michael@0: /** michael@0: * Object defining the debugger view components. michael@0: */ michael@0: let DebuggerView = { michael@0: /** michael@0: * Initializes the debugger view. michael@0: * michael@0: * @return object michael@0: * A promise that is resolved when the view finishes initializing. michael@0: */ michael@0: initialize: function() { michael@0: if (this._startup) { michael@0: return this._startup; michael@0: } michael@0: michael@0: let deferred = promise.defer(); michael@0: this._startup = deferred.promise; michael@0: michael@0: this._initializePanes(); michael@0: this.Toolbar.initialize(); michael@0: this.Options.initialize(); michael@0: this.Filtering.initialize(); michael@0: this.FilteredSources.initialize(); michael@0: this.FilteredFunctions.initialize(); michael@0: this.StackFrames.initialize(); michael@0: this.StackFramesClassicList.initialize(); michael@0: this.Sources.initialize(); michael@0: this.VariableBubble.initialize(); michael@0: this.Tracer.initialize(); michael@0: this.WatchExpressions.initialize(); michael@0: this.EventListeners.initialize(); michael@0: this.GlobalSearch.initialize(); michael@0: this._initializeVariablesView(); michael@0: this._initializeEditor(deferred.resolve); michael@0: michael@0: document.title = L10N.getStr("DebuggerWindowTitle"); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: /** michael@0: * Destroys the debugger view. michael@0: * michael@0: * @return object michael@0: * A promise that is resolved when the view finishes destroying. michael@0: */ michael@0: destroy: function() { michael@0: if (this._shutdown) { michael@0: return this._shutdown; michael@0: } michael@0: michael@0: let deferred = promise.defer(); michael@0: this._shutdown = deferred.promise; michael@0: michael@0: this.Toolbar.destroy(); michael@0: this.Options.destroy(); michael@0: this.Filtering.destroy(); michael@0: this.FilteredSources.destroy(); michael@0: this.FilteredFunctions.destroy(); michael@0: this.StackFrames.destroy(); michael@0: this.StackFramesClassicList.destroy(); michael@0: this.Sources.destroy(); michael@0: this.VariableBubble.destroy(); michael@0: this.Tracer.destroy(); michael@0: this.WatchExpressions.destroy(); michael@0: this.EventListeners.destroy(); michael@0: this.GlobalSearch.destroy(); michael@0: this._destroyPanes(); michael@0: this._destroyEditor(deferred.resolve); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: /** michael@0: * Initializes the UI for all the displayed panes. michael@0: */ michael@0: _initializePanes: function() { michael@0: dumpn("Initializing the DebuggerView panes"); michael@0: michael@0: this._body = document.getElementById("body"); michael@0: this._editorDeck = document.getElementById("editor-deck"); michael@0: this._sourcesPane = document.getElementById("sources-pane"); michael@0: this._instrumentsPane = document.getElementById("instruments-pane"); michael@0: this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle"); michael@0: michael@0: this.showEditor = this.showEditor.bind(this); michael@0: this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this); michael@0: this.showProgressBar = this.showProgressBar.bind(this); michael@0: this.maybeShowBlackBoxMessage = this.maybeShowBlackBoxMessage.bind(this); michael@0: michael@0: this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this); michael@0: this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect); michael@0: michael@0: this._collapsePaneString = L10N.getStr("collapsePanes"); michael@0: this._expandPaneString = L10N.getStr("expandPanes"); michael@0: michael@0: this._sourcesPane.setAttribute("width", Prefs.sourcesWidth); michael@0: this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth); michael@0: this.toggleInstrumentsPane({ visible: Prefs.panesVisibleOnStartup }); michael@0: michael@0: // Side hosts requires a different arrangement of the debugger widgets. michael@0: if (gHostType == "side") { michael@0: this.handleHostChanged(gHostType); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Destroys the UI for all the displayed panes. michael@0: */ michael@0: _destroyPanes: function() { michael@0: dumpn("Destroying the DebuggerView panes"); michael@0: michael@0: if (gHostType != "side") { michael@0: Prefs.sourcesWidth = this._sourcesPane.getAttribute("width"); michael@0: Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width"); michael@0: } michael@0: michael@0: this._sourcesPane = null; michael@0: this._instrumentsPane = null; michael@0: this._instrumentsPaneToggleButton = null; michael@0: }, michael@0: michael@0: /** michael@0: * Initializes the VariablesView instance and attaches a controller. michael@0: */ michael@0: _initializeVariablesView: function() { michael@0: this.Variables = new VariablesView(document.getElementById("variables"), { michael@0: searchPlaceholder: L10N.getStr("emptyVariablesFilterText"), michael@0: emptyText: L10N.getStr("emptyVariablesText"), michael@0: onlyEnumVisible: Prefs.variablesOnlyEnumVisible, michael@0: searchEnabled: Prefs.variablesSearchboxVisible, michael@0: eval: (variable, value) => { michael@0: let string = variable.evaluationMacro(variable, value); michael@0: DebuggerController.StackFrames.evaluate(string); michael@0: }, michael@0: lazyEmpty: true michael@0: }); michael@0: michael@0: // Attach the current toolbox to the VView so it can link DOMNodes to michael@0: // the inspector/highlighter michael@0: this.Variables.toolbox = DebuggerController._toolbox; michael@0: michael@0: // Attach a controller that handles interfacing with the debugger protocol. michael@0: VariablesViewController.attach(this.Variables, { michael@0: getEnvironmentClient: aObject => gThreadClient.environment(aObject), michael@0: getObjectClient: aObject => { michael@0: return aObject instanceof DebuggerController.Tracer.WrappedObject michael@0: ? DebuggerController.Tracer.syncGripClient(aObject.object) michael@0: : gThreadClient.pauseGrip(aObject) michael@0: } michael@0: }); michael@0: michael@0: // Relay events from the VariablesView. michael@0: this.Variables.on("fetched", (aEvent, aType) => { michael@0: switch (aType) { michael@0: case "scopes": michael@0: window.emit(EVENTS.FETCHED_SCOPES); michael@0: break; michael@0: case "variables": michael@0: window.emit(EVENTS.FETCHED_VARIABLES); michael@0: break; michael@0: case "properties": michael@0: window.emit(EVENTS.FETCHED_PROPERTIES); michael@0: break; michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Initializes the Editor instance. michael@0: * michael@0: * @param function aCallback michael@0: * Called after the editor finishes initializing. michael@0: */ michael@0: _initializeEditor: function(aCallback) { michael@0: dumpn("Initializing the DebuggerView editor"); michael@0: michael@0: let extraKeys = {}; michael@0: bindKey("_doTokenSearch", "tokenSearchKey"); michael@0: bindKey("_doGlobalSearch", "globalSearchKey", { alt: true }); michael@0: bindKey("_doFunctionSearch", "functionSearchKey"); michael@0: extraKeys[Editor.keyFor("jumpToLine")] = false; michael@0: michael@0: function bindKey(func, key, modifiers = {}) { michael@0: let key = document.getElementById(key).getAttribute("key"); michael@0: let shortcut = Editor.accel(key, modifiers); michael@0: extraKeys[shortcut] = () => DebuggerView.Filtering[func](); michael@0: } michael@0: michael@0: this.editor = new Editor({ michael@0: mode: Editor.modes.text, michael@0: readOnly: true, michael@0: lineNumbers: true, michael@0: showAnnotationRuler: true, michael@0: gutters: [ "breakpoints" ], michael@0: extraKeys: extraKeys, michael@0: contextMenu: "sourceEditorContextMenu" michael@0: }); michael@0: michael@0: this.editor.appendTo(document.getElementById("editor")).then(() => { michael@0: this.editor.extend(DebuggerEditor); michael@0: this._loadingText = L10N.getStr("loadingText"); michael@0: this._onEditorLoad(aCallback); michael@0: }); michael@0: michael@0: this.editor.on("gutterClick", (ev, line) => { michael@0: if (this.editor.hasBreakpoint(line)) { michael@0: this.editor.removeBreakpoint(line); michael@0: } else { michael@0: this.editor.addBreakpoint(line); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * The load event handler for the source editor, also executing any necessary michael@0: * post-load operations. michael@0: * michael@0: * @param function aCallback michael@0: * Called after the editor finishes loading. michael@0: */ michael@0: _onEditorLoad: function(aCallback) { michael@0: dumpn("Finished loading the DebuggerView editor"); michael@0: michael@0: DebuggerController.Breakpoints.initialize().then(() => { michael@0: window.emit(EVENTS.EDITOR_LOADED, this.editor); michael@0: aCallback(); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Destroys the Editor instance and also executes any necessary michael@0: * post-unload operations. michael@0: * michael@0: * @param function aCallback michael@0: * Called after the editor finishes destroying. michael@0: */ michael@0: _destroyEditor: function(aCallback) { michael@0: dumpn("Destroying the DebuggerView editor"); michael@0: michael@0: DebuggerController.Breakpoints.destroy().then(() => { michael@0: window.emit(EVENTS.EDITOR_UNLOADED, this.editor); michael@0: this.editor.destroy(); michael@0: this.editor = null; michael@0: aCallback(); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Display the source editor. michael@0: */ michael@0: showEditor: function() { michael@0: this._editorDeck.selectedIndex = 0; michael@0: }, michael@0: michael@0: /** michael@0: * Display the black box message. michael@0: */ michael@0: showBlackBoxMessage: function() { michael@0: this._editorDeck.selectedIndex = 1; michael@0: }, michael@0: michael@0: /** michael@0: * Display the progress bar. michael@0: */ michael@0: showProgressBar: function() { michael@0: this._editorDeck.selectedIndex = 2; michael@0: }, michael@0: michael@0: /** michael@0: * Show or hide the black box message vs. source editor depending on if the michael@0: * selected source is black boxed or not. michael@0: */ michael@0: maybeShowBlackBoxMessage: function() { michael@0: let { source } = DebuggerView.Sources.selectedItem.attachment; michael@0: if (gThreadClient.source(source).isBlackBoxed) { michael@0: this.showBlackBoxMessage(); michael@0: } else { michael@0: this.showEditor(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Sets the currently displayed text contents in the source editor. michael@0: * This resets the mode and undo stack. michael@0: * michael@0: * @param string aTextContent michael@0: * The source text content. michael@0: */ michael@0: _setEditorText: function(aTextContent = "") { michael@0: this.editor.setMode(Editor.modes.text); michael@0: this.editor.setText(aTextContent); michael@0: this.editor.clearDebugLocation(); michael@0: this.editor.clearHistory(); michael@0: }, michael@0: michael@0: /** michael@0: * Sets the proper editor mode (JS or HTML) according to the specified michael@0: * content type, or by determining the type from the url or text content. michael@0: * michael@0: * @param string aUrl michael@0: * The source url. michael@0: * @param string aContentType [optional] michael@0: * The source content type. michael@0: * @param string aTextContent [optional] michael@0: * The source text content. michael@0: */ michael@0: _setEditorMode: function(aUrl, aContentType = "", aTextContent = "") { michael@0: // Avoid setting the editor mode for very large files. michael@0: // Is this still necessary? See bug 929225. michael@0: if (aTextContent.length >= SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) { michael@0: return void this.editor.setMode(Editor.modes.text); michael@0: } michael@0: michael@0: // Use JS mode for files with .js and .jsm extensions. michael@0: if (SourceUtils.isJavaScript(aUrl, aContentType)) { michael@0: return void this.editor.setMode(Editor.modes.js); michael@0: } michael@0: michael@0: // Use HTML mode for files in which the first non whitespace character is michael@0: // <, regardless of extension. michael@0: if (aTextContent.match(/^\s* { michael@0: // Avoid setting an unexpected source. This may happen when switching michael@0: // very fast between sources that haven't been fetched yet. michael@0: if (this._editorSource.url != aSource.url) { michael@0: return; michael@0: } michael@0: michael@0: this._setEditorText(aText); michael@0: this._setEditorMode(aSource.url, aContentType, aText); michael@0: michael@0: // Synchronize any other components with the currently displayed source. michael@0: DebuggerView.Sources.selectedValue = aSource.url; michael@0: DebuggerController.Breakpoints.updateEditorBreakpoints(); michael@0: michael@0: histogram.add(Date.now() - startTime); michael@0: michael@0: // Resolve and notify that a source file was shown. michael@0: window.emit(EVENTS.SOURCE_SHOWN, aSource); michael@0: deferred.resolve([aSource, aText, aContentType]); michael@0: }, michael@0: ([, aError]) => { michael@0: let msg = L10N.getStr("errorLoadingText") + DevToolsUtils.safeErrorString(aError); michael@0: this._setEditorText(msg); michael@0: Cu.reportError(msg); michael@0: dumpn(msg); michael@0: michael@0: // Reject and notify that there was an error showing the source file. michael@0: window.emit(EVENTS.SOURCE_ERROR_SHOWN, aSource); michael@0: deferred.reject([aSource, aError]); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: /** michael@0: * Update the source editor's current caret and debug location based on michael@0: * a requested url and line. michael@0: * michael@0: * @param string aUrl michael@0: * The target source url. michael@0: * @param number aLine [optional] michael@0: * The target line in the source. michael@0: * @param object aFlags [optional] michael@0: * Additional options for showing the source. Supported options: michael@0: * - charOffset: character offset for the caret or debug location michael@0: * - lineOffset: line offset for the caret or debug location michael@0: * - columnOffset: column offset for the caret or debug location michael@0: * - noCaret: don't set the caret location at the specified line michael@0: * - noDebug: don't set the debug location at the specified line michael@0: * - align: string specifying whether to align the specified line michael@0: * at the "top", "center" or "bottom" of the editor michael@0: * - force: boolean allowing whether we can get the selected url's michael@0: * text again michael@0: * @return object michael@0: * A promise that is resolved after the source text has been set. michael@0: */ michael@0: setEditorLocation: function(aUrl, aLine = 0, aFlags = {}) { michael@0: // Avoid trying to set a source for a url that isn't known yet. michael@0: if (!this.Sources.containsValue(aUrl)) { michael@0: return promise.reject(new Error("Unknown source for the specified URL.")); michael@0: } michael@0: michael@0: // If the line is not specified, default to the current frame's position, michael@0: // if available and the frame's url corresponds to the requested url. michael@0: if (!aLine) { michael@0: let cachedFrames = DebuggerController.activeThread.cachedFrames; michael@0: let currentDepth = DebuggerController.StackFrames.currentFrameDepth; michael@0: let frame = cachedFrames[currentDepth]; michael@0: if (frame && frame.where.url == aUrl) { michael@0: aLine = frame.where.line; michael@0: } michael@0: } michael@0: michael@0: let sourceItem = this.Sources.getItemByValue(aUrl); michael@0: let sourceForm = sourceItem.attachment.source; michael@0: michael@0: // Make sure the requested source client is shown in the editor, then michael@0: // update the source editor's caret position and debug location. michael@0: return this._setEditorSource(sourceForm, aFlags).then(([,, aContentType]) => { michael@0: // Record the contentType learned from fetching michael@0: sourceForm.contentType = aContentType; michael@0: // Line numbers in the source editor should start from 1. If invalid michael@0: // or not specified, then don't do anything. michael@0: if (aLine < 1) { michael@0: window.emit(EVENTS.EDITOR_LOCATION_SET); michael@0: return; michael@0: } michael@0: if (aFlags.charOffset) { michael@0: aLine += this.editor.getPosition(aFlags.charOffset).line; michael@0: } michael@0: if (aFlags.lineOffset) { michael@0: aLine += aFlags.lineOffset; michael@0: } michael@0: if (!aFlags.noCaret) { michael@0: let location = { line: aLine -1, ch: aFlags.columnOffset || 0 }; michael@0: this.editor.setCursor(location, aFlags.align); michael@0: } michael@0: if (!aFlags.noDebug) { michael@0: this.editor.setDebugLocation(aLine - 1); michael@0: } michael@0: window.emit(EVENTS.EDITOR_LOCATION_SET); michael@0: }).then(null, console.error); michael@0: }, michael@0: michael@0: /** michael@0: * Gets the visibility state of the instruments pane. michael@0: * @return boolean michael@0: */ michael@0: get instrumentsPaneHidden() michael@0: this._instrumentsPane.hasAttribute("pane-collapsed"), michael@0: michael@0: /** michael@0: * Gets the currently selected tab in the instruments pane. michael@0: * @return string michael@0: */ michael@0: get instrumentsPaneTab() michael@0: this._instrumentsPane.selectedTab.id, michael@0: michael@0: /** michael@0: * Sets the instruments pane hidden or visible. michael@0: * michael@0: * @param object aFlags michael@0: * An object containing some of the following properties: michael@0: * - visible: true if the pane should be shown, false to hide michael@0: * - animated: true to display an animation on toggle michael@0: * - delayed: true to wait a few cycles before toggle michael@0: * - callback: a function to invoke when the toggle finishes michael@0: * @param number aTabIndex [optional] michael@0: * The index of the intended selected tab in the details pane. michael@0: */ michael@0: toggleInstrumentsPane: function(aFlags, aTabIndex) { michael@0: let pane = this._instrumentsPane; michael@0: let button = this._instrumentsPaneToggleButton; michael@0: michael@0: ViewHelpers.togglePane(aFlags, pane); michael@0: michael@0: if (aFlags.visible) { michael@0: button.removeAttribute("pane-collapsed"); michael@0: button.setAttribute("tooltiptext", this._collapsePaneString); michael@0: } else { michael@0: button.setAttribute("pane-collapsed", ""); michael@0: button.setAttribute("tooltiptext", this._expandPaneString); michael@0: } michael@0: michael@0: if (aTabIndex !== undefined) { michael@0: pane.selectedIndex = aTabIndex; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Sets the instruments pane visible after a short period of time. michael@0: * michael@0: * @param function aCallback michael@0: * A function to invoke when the toggle finishes. michael@0: */ michael@0: showInstrumentsPane: function(aCallback) { michael@0: DebuggerView.toggleInstrumentsPane({ michael@0: visible: true, michael@0: animated: true, michael@0: delayed: true, michael@0: callback: aCallback michael@0: }, 0); michael@0: }, michael@0: michael@0: /** michael@0: * Handles a tab selection event on the instruments pane. michael@0: */ michael@0: _onInstrumentsPaneTabSelect: function() { michael@0: if (this._instrumentsPane.selectedTab.id == "events-tab") { michael@0: DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handles a host change event issued by the parent toolbox. michael@0: * michael@0: * @param string aType michael@0: * The host type, either "bottom", "side" or "window". michael@0: */ michael@0: handleHostChanged: function(aType) { michael@0: let newLayout = ""; michael@0: michael@0: if (aType == "side") { michael@0: newLayout = "vertical"; michael@0: this._enterVerticalLayout(); michael@0: } else { michael@0: newLayout = "horizontal"; michael@0: this._enterHorizontalLayout(); michael@0: } michael@0: michael@0: this._hostType = aType; michael@0: this._body.setAttribute("layout", newLayout); michael@0: window.emit(EVENTS.LAYOUT_CHANGED, newLayout); michael@0: }, michael@0: michael@0: /** michael@0: * Switches the debugger widgets to a horizontal layout. michael@0: */ michael@0: _enterVerticalLayout: function() { michael@0: let normContainer = document.getElementById("debugger-widgets"); michael@0: let vertContainer = document.getElementById("vertical-layout-panes-container"); michael@0: michael@0: // Move the soruces and instruments panes in a different container. michael@0: let splitter = document.getElementById("sources-and-instruments-splitter"); michael@0: vertContainer.insertBefore(this._sourcesPane, splitter); michael@0: vertContainer.appendChild(this._instrumentsPane); michael@0: michael@0: // Make sure the vertical layout container's height doesn't repeatedly michael@0: // grow or shrink based on the displayed sources, variables etc. michael@0: vertContainer.setAttribute("height", michael@0: vertContainer.getBoundingClientRect().height); michael@0: }, michael@0: michael@0: /** michael@0: * Switches the debugger widgets to a vertical layout. michael@0: */ michael@0: _enterHorizontalLayout: function() { michael@0: let normContainer = document.getElementById("debugger-widgets"); michael@0: let vertContainer = document.getElementById("vertical-layout-panes-container"); michael@0: michael@0: // The sources and instruments pane need to be inserted at their michael@0: // previous locations in their normal container. michael@0: let splitter = document.getElementById("sources-and-editor-splitter"); michael@0: normContainer.insertBefore(this._sourcesPane, splitter); michael@0: normContainer.appendChild(this._instrumentsPane); michael@0: michael@0: // Revert to the preferred sources and instruments widths, because michael@0: // they flexed in the vertical layout. michael@0: this._sourcesPane.setAttribute("width", Prefs.sourcesWidth); michael@0: this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth); michael@0: }, michael@0: michael@0: /** michael@0: * Handles any initialization on a tab navigation event issued by the client. michael@0: */ michael@0: handleTabNavigation: function() { michael@0: dumpn("Handling tab navigation in the DebuggerView"); michael@0: michael@0: this.Filtering.clearSearch(); michael@0: this.FilteredSources.clearView(); michael@0: this.FilteredFunctions.clearView(); michael@0: this.GlobalSearch.clearView(); michael@0: this.StackFrames.empty(); michael@0: this.Sources.empty(); michael@0: this.Variables.empty(); michael@0: this.EventListeners.empty(); michael@0: michael@0: if (this.editor) { michael@0: this.editor.setMode(Editor.modes.text); michael@0: this.editor.setText(""); michael@0: this.editor.clearHistory(); michael@0: this._editorSource = {}; michael@0: } michael@0: michael@0: this.Sources.emptyText = L10N.getStr("loadingSourcesText"); michael@0: }, michael@0: michael@0: _startup: null, michael@0: _shutdown: null, michael@0: Toolbar: null, michael@0: Options: null, michael@0: Filtering: null, michael@0: FilteredSources: null, michael@0: FilteredFunctions: null, michael@0: GlobalSearch: null, michael@0: StackFrames: null, michael@0: Sources: null, michael@0: Tracer: null, michael@0: Variables: null, michael@0: VariableBubble: null, michael@0: WatchExpressions: null, michael@0: EventListeners: null, michael@0: editor: null, michael@0: _editorSource: {}, michael@0: _loadingText: "", michael@0: _body: null, michael@0: _editorDeck: null, michael@0: _sourcesPane: null, michael@0: _instrumentsPane: null, michael@0: _instrumentsPaneToggleButton: null, michael@0: _collapsePaneString: "", michael@0: _expandPaneString: "" michael@0: }; michael@0: michael@0: /** michael@0: * A custom items container, used for displaying views like the michael@0: * FilteredSources, FilteredFunctions etc., inheriting the generic WidgetMethods. michael@0: */ michael@0: function ResultsPanelContainer() { michael@0: } michael@0: michael@0: ResultsPanelContainer.prototype = Heritage.extend(WidgetMethods, { michael@0: /** michael@0: * Sets the anchor node for this container panel. michael@0: * @param nsIDOMNode aNode michael@0: */ michael@0: set anchor(aNode) { michael@0: this._anchor = aNode; michael@0: michael@0: // If the anchor node is not null, create a panel to attach to the anchor michael@0: // when showing the popup. michael@0: if (aNode) { michael@0: if (!this._panel) { michael@0: this._panel = document.createElement("panel"); michael@0: this._panel.id = "results-panel"; michael@0: this._panel.setAttribute("level", "top"); michael@0: this._panel.setAttribute("noautofocus", "true"); michael@0: this._panel.setAttribute("consumeoutsideclicks", "false"); michael@0: document.documentElement.appendChild(this._panel); michael@0: } michael@0: if (!this.widget) { michael@0: this.widget = new SimpleListWidget(this._panel); michael@0: this.autoFocusOnFirstItem = false; michael@0: this.autoFocusOnSelection = false; michael@0: this.maintainSelectionVisible = false; michael@0: } michael@0: } michael@0: // Cleanup the anchor and remove the previously created panel. michael@0: else { michael@0: this._panel.remove(); michael@0: this._panel = null; michael@0: this.widget = null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Gets the anchor node for this container panel. michael@0: * @return nsIDOMNode michael@0: */ michael@0: get anchor() { michael@0: return this._anchor; michael@0: }, michael@0: michael@0: /** michael@0: * Sets the container panel hidden or visible. It's hidden by default. michael@0: * @param boolean aFlag michael@0: */ michael@0: set hidden(aFlag) { michael@0: if (aFlag) { michael@0: this._panel.hidden = true; michael@0: this._panel.hidePopup(); michael@0: } else { michael@0: this._panel.hidden = false; michael@0: this._panel.openPopup(this._anchor, this.position, this.left, this.top); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Gets this container's visibility state. michael@0: * @return boolean michael@0: */ michael@0: get hidden() michael@0: this._panel.state == "closed" || michael@0: this._panel.state == "hiding", michael@0: michael@0: /** michael@0: * Removes all items from this container and hides it. michael@0: */ michael@0: clearView: function() { michael@0: this.hidden = true; michael@0: this.empty(); michael@0: }, michael@0: michael@0: /** michael@0: * Selects the next found item in this container. michael@0: * Does not change the currently focused node. michael@0: */ michael@0: selectNext: function() { michael@0: let nextIndex = this.selectedIndex + 1; michael@0: if (nextIndex >= this.itemCount) { michael@0: nextIndex = 0; michael@0: } michael@0: this.selectedItem = this.getItemAtIndex(nextIndex); michael@0: }, michael@0: michael@0: /** michael@0: * Selects the previously found item in this container. michael@0: * Does not change the currently focused node. michael@0: */ michael@0: selectPrev: function() { michael@0: let prevIndex = this.selectedIndex - 1; michael@0: if (prevIndex < 0) { michael@0: prevIndex = this.itemCount - 1; michael@0: } michael@0: this.selectedItem = this.getItemAtIndex(prevIndex); michael@0: }, michael@0: michael@0: /** michael@0: * Customization function for creating an item's UI. michael@0: * michael@0: * @param string aLabel michael@0: * The item's label string. michael@0: * @param string aBeforeLabel michael@0: * An optional string shown before the label. michael@0: * @param string aBelowLabel michael@0: * An optional string shown underneath the label. michael@0: */ michael@0: _createItemView: function(aLabel, aBelowLabel, aBeforeLabel) { michael@0: let container = document.createElement("vbox"); michael@0: container.className = "results-panel-item"; michael@0: michael@0: let firstRowLabels = document.createElement("hbox"); michael@0: let secondRowLabels = document.createElement("hbox"); michael@0: michael@0: if (aBeforeLabel) { michael@0: let beforeLabelNode = document.createElement("label"); michael@0: beforeLabelNode.className = "plain results-panel-item-label-before"; michael@0: beforeLabelNode.setAttribute("value", aBeforeLabel); michael@0: firstRowLabels.appendChild(beforeLabelNode); michael@0: } michael@0: michael@0: let labelNode = document.createElement("label"); michael@0: labelNode.className = "plain results-panel-item-label"; michael@0: labelNode.setAttribute("value", aLabel); michael@0: firstRowLabels.appendChild(labelNode); michael@0: michael@0: if (aBelowLabel) { michael@0: let belowLabelNode = document.createElement("label"); michael@0: belowLabelNode.className = "plain results-panel-item-label-below"; michael@0: belowLabelNode.setAttribute("value", aBelowLabel); michael@0: secondRowLabels.appendChild(belowLabelNode); michael@0: } michael@0: michael@0: container.appendChild(firstRowLabels); michael@0: container.appendChild(secondRowLabels); michael@0: michael@0: return container; michael@0: }, michael@0: michael@0: _anchor: null, michael@0: _panel: null, michael@0: position: RESULTS_PANEL_POPUP_POSITION, michael@0: left: 0, michael@0: top: 0 michael@0: });