/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center"; const STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH = 32; // chars const STACK_FRAMES_POPUP_SOURCE_URL_TRIM_SECTION = "center"; const STACK_FRAMES_SCROLL_DELAY = 100; // ms const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start"; const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px const RESULTS_PANEL_POPUP_POSITION = "before_end"; const RESULTS_PANEL_MAX_RESULTS = 10; const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50; const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms const SEARCH_GLOBAL_FLAG = "!"; const SEARCH_FUNCTION_FLAG = "@"; const SEARCH_TOKEN_FLAG = "#"; const SEARCH_LINE_FLAG = ":"; const SEARCH_VARIABLE_FLAG = "*"; /** * Object defining the debugger view components. */ let DebuggerView = { /** * Initializes the debugger view. * * @param function aCallback * Called after the view finishes initializing. */ initialize: function(aCallback) { dumpn("Initializing the DebuggerView"); this._initializeWindow(); this._initializePanes(); this.Toolbar.initialize(); this.Options.initialize(); this.Filtering.initialize(); this.FilteredSources.initialize(); this.FilteredFunctions.initialize(); this.ChromeGlobals.initialize(); this.StackFrames.initialize(); this.Sources.initialize(); this.WatchExpressions.initialize(); this.GlobalSearch.initialize(); this.Variables = new VariablesView(document.getElementById("variables")); this.Variables.searchPlaceholder = L10N.getStr("emptyVariablesFilterText"); this.Variables.emptyText = L10N.getStr("emptyVariablesText"); this.Variables.onlyEnumVisible = Prefs.variablesOnlyEnumVisible; this.Variables.searchEnabled = Prefs.variablesSearchboxVisible; this.Variables.eval = DebuggerController.StackFrames.evaluate; this.Variables.lazyEmpty = true; this._initializeEditor(aCallback); }, /** * Destroys the debugger view. * * @param function aCallback * Called after the view finishes destroying. */ destroy: function(aCallback) { dumpn("Destroying the DebuggerView"); this.Toolbar.destroy(); this.Options.destroy(); this.Filtering.destroy(); this.FilteredSources.destroy(); this.FilteredFunctions.destroy(); this.ChromeGlobals.destroy(); this.StackFrames.destroy(); this.Sources.destroy(); this.WatchExpressions.destroy(); this.GlobalSearch.destroy(); this._destroyWindow(); this._destroyPanes(); this._destroyEditor(); aCallback(); }, /** * Initializes the UI for the window. */ _initializeWindow: function() { dumpn("Initializing the DebuggerView window"); let isRemote = window._isRemoteDebugger; let isChrome = window._isChromeDebugger; if (isRemote || isChrome) { window.moveTo(Prefs.windowX, Prefs.windowY); window.resizeTo(Prefs.windowWidth, Prefs.windowHeight); if (isRemote) { document.title = L10N.getStr("remoteDebuggerWindowTitle"); } else { document.title = L10N.getStr("chromeDebuggerWindowTitle"); } } }, /** * Destroys the UI for the window. */ _destroyWindow: function() { dumpn("Destroying the DebuggerView window"); if (window._isRemoteDebugger || window._isChromeDebugger) { Prefs.windowX = window.screenX; Prefs.windowY = window.screenY; Prefs.windowWidth = window.outerWidth; Prefs.windowHeight = window.outerHeight; } }, /** * Initializes the UI for all the displayed panes. */ _initializePanes: function() { dumpn("Initializing the DebuggerView panes"); this._sourcesPane = document.getElementById("sources-pane"); this._instrumentsPane = document.getElementById("instruments-pane"); this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle"); this._collapsePaneString = L10N.getStr("collapsePanes"); this._expandPaneString = L10N.getStr("expandPanes"); this._sourcesPane.setAttribute("width", Prefs.sourcesWidth); this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth); this.toggleInstrumentsPane({ visible: Prefs.panesVisibleOnStartup }); }, /** * Destroys the UI for all the displayed panes. */ _destroyPanes: function() { dumpn("Destroying the DebuggerView panes"); Prefs.sourcesWidth = this._sourcesPane.getAttribute("width"); Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width"); this._sourcesPane = null; this._instrumentsPane = null; this._instrumentsPaneToggleButton = null; }, /** * Initializes the SourceEditor instance. * * @param function aCallback * Called after the editor finishes initializing. */ _initializeEditor: function(aCallback) { dumpn("Initializing the DebuggerView editor"); let placeholder = document.getElementById("editor"); let config = { mode: SourceEditor.MODES.JAVASCRIPT, readOnly: true, showLineNumbers: true, showAnnotationRuler: true, showOverviewRuler: true }; this.editor = new SourceEditor(); this.editor.init(placeholder, config, () => { this._loadingText = L10N.getStr("loadingText"); this._onEditorLoad(); aCallback(); }); }, /** * The load event handler for the source editor, also executing any necessary * post-load operations. */ _onEditorLoad: function() { dumpn("Finished loading the DebuggerView editor"); DebuggerController.Breakpoints.initialize(); window.dispatchEvent(document, "Debugger:EditorLoaded", this.editor); this.editor.focus(); }, /** * Destroys the SourceEditor instance and also executes any necessary * post-unload operations. */ _destroyEditor: function() { dumpn("Destroying the DebuggerView editor"); DebuggerController.Breakpoints.destroy(); window.dispatchEvent(document, "Debugger:EditorUnloaded", this.editor); }, /** * Sets the proper editor mode (JS or HTML) according to the specified * content type, or by determining the type from the url or text content. * * @param string aUrl * The source url. * @param string aContentType [optional] * The source content type. * @param string aTextContent [optional] * The source text content. */ setEditorMode: function(aUrl, aContentType = "", aTextContent = "") { if (aContentType) { if (/javascript/.test(aContentType)) { this.editor.setMode(SourceEditor.MODES.JAVASCRIPT); } else { this.editor.setMode(SourceEditor.MODES.HTML); } } else if (aTextContent.match(/^\s*= this.itemCount) { nextIndex = 0; } this.select(this.getItemAtIndex(nextIndex)); }, /** * Focuses the previously found item in this container. */ focusPrev: function() { let prevIndex = this.selectedIndex - 1; if (prevIndex < 0) { prevIndex = this.itemCount - 1; } this.select(this.getItemAtIndex(prevIndex)); }, /** * Updates the selected item in this container. * * @param MenuItem | number aItem * The item associated with the element to select. */ select: function(aItem) { if (typeof aItem == "number") { this.select(this.getItemAtIndex(aItem)); return; } // Update the currently selected item in this container using the // selectedItem setter in the MenuContainer prototype chain. this.selectedItem = aItem; // Invoke the attached selection callback if available in any // inheriting prototype. if (this.onSelect) { this.onSelect({ target: aItem.target }); } }, /** * Customization function for creating an item's UI. * * @param nsIDOMNode aElementNode * The element associated with the displayed item. * @param any aAttachment * Some attached primitive/object. * @param string aLabel * The item's label. * @param string aValue * The item's value. * @param string aDescription * An optional description of the item. */ _createItemView: function RPC__createItemView(aElementNode, aAttachment, aLabel, aValue, aDescription) { let labelsGroup = document.createElement("hbox"); if (aDescription) { let preLabelNode = document.createElement("label"); preLabelNode.className = "plain results-panel-item-pre"; preLabelNode.setAttribute("value", aDescription); labelsGroup.appendChild(preLabelNode); } if (aLabel) { let labelNode = document.createElement("label"); labelNode.className = "plain results-panel-item-name"; labelNode.setAttribute("value", aLabel); labelsGroup.appendChild(labelNode); } let valueNode = document.createElement("label"); valueNode.className = "plain results-panel-item-details"; valueNode.setAttribute("value", aValue); aElementNode.className = "light results-panel-item"; aElementNode.appendChild(labelsGroup); aElementNode.appendChild(valueNode); }, _anchor: null, _panel: null, _position: RESULTS_PANEL_POPUP_POSITION, _left: 0, _top: 0 }); /** * A simple way of displaying a "Connect to..." prompt. */ function RemoteDebuggerPrompt() { this.remote = {}; } RemoteDebuggerPrompt.prototype = { /** * Shows the prompt and waits for a remote host and port to connect to. * * @param boolean aIsReconnectingFlag * True to show the reconnect message instead of the connect request. */ show: function(aIsReconnectingFlag) { let check = { value: Prefs.remoteAutoConnect }; let input = { value: Prefs.remoteHost + ":" + Prefs.remotePort }; let parts; while (true) { let result = Services.prompt.prompt(null, L10N.getStr("remoteDebuggerPromptTitle"), L10N.getStr(aIsReconnectingFlag ? "remoteDebuggerReconnectMessage" : "remoteDebuggerPromptMessage"), input, L10N.getStr("remoteDebuggerPromptCheck"), check); if (!result) { return false; } if ((parts = input.value.split(":")).length == 2) { let [host, port] = parts; if (host.length && port.length) { this.remote = { host: host, port: port, auto: check.value }; return true; } } } } };