1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/debugger/debugger-view.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,827 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 +"use strict"; 1.10 + 1.11 +const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes 1.12 +const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars 1.13 +const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars 1.14 +const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center"; 1.15 +const STACK_FRAMES_SCROLL_DELAY = 100; // ms 1.16 +const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars 1.17 +const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start"; 1.18 +const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px 1.19 +const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px 1.20 +const RESULTS_PANEL_POPUP_POSITION = "before_end"; 1.21 +const RESULTS_PANEL_MAX_RESULTS = 10; 1.22 +const FILE_SEARCH_ACTION_MAX_DELAY = 300; // ms 1.23 +const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50; 1.24 +const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars 1.25 +const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms 1.26 +const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms 1.27 +const SEARCH_GLOBAL_FLAG = "!"; 1.28 +const SEARCH_FUNCTION_FLAG = "@"; 1.29 +const SEARCH_TOKEN_FLAG = "#"; 1.30 +const SEARCH_LINE_FLAG = ":"; 1.31 +const SEARCH_VARIABLE_FLAG = "*"; 1.32 +const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG]; 1.33 +const EDITOR_VARIABLE_HOVER_DELAY = 350; // ms 1.34 +const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft"; 1.35 +const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft"; 1.36 + 1.37 +/** 1.38 + * Object defining the debugger view components. 1.39 + */ 1.40 +let DebuggerView = { 1.41 + /** 1.42 + * Initializes the debugger view. 1.43 + * 1.44 + * @return object 1.45 + * A promise that is resolved when the view finishes initializing. 1.46 + */ 1.47 + initialize: function() { 1.48 + if (this._startup) { 1.49 + return this._startup; 1.50 + } 1.51 + 1.52 + let deferred = promise.defer(); 1.53 + this._startup = deferred.promise; 1.54 + 1.55 + this._initializePanes(); 1.56 + this.Toolbar.initialize(); 1.57 + this.Options.initialize(); 1.58 + this.Filtering.initialize(); 1.59 + this.FilteredSources.initialize(); 1.60 + this.FilteredFunctions.initialize(); 1.61 + this.StackFrames.initialize(); 1.62 + this.StackFramesClassicList.initialize(); 1.63 + this.Sources.initialize(); 1.64 + this.VariableBubble.initialize(); 1.65 + this.Tracer.initialize(); 1.66 + this.WatchExpressions.initialize(); 1.67 + this.EventListeners.initialize(); 1.68 + this.GlobalSearch.initialize(); 1.69 + this._initializeVariablesView(); 1.70 + this._initializeEditor(deferred.resolve); 1.71 + 1.72 + document.title = L10N.getStr("DebuggerWindowTitle"); 1.73 + 1.74 + return deferred.promise; 1.75 + }, 1.76 + 1.77 + /** 1.78 + * Destroys the debugger view. 1.79 + * 1.80 + * @return object 1.81 + * A promise that is resolved when the view finishes destroying. 1.82 + */ 1.83 + destroy: function() { 1.84 + if (this._shutdown) { 1.85 + return this._shutdown; 1.86 + } 1.87 + 1.88 + let deferred = promise.defer(); 1.89 + this._shutdown = deferred.promise; 1.90 + 1.91 + this.Toolbar.destroy(); 1.92 + this.Options.destroy(); 1.93 + this.Filtering.destroy(); 1.94 + this.FilteredSources.destroy(); 1.95 + this.FilteredFunctions.destroy(); 1.96 + this.StackFrames.destroy(); 1.97 + this.StackFramesClassicList.destroy(); 1.98 + this.Sources.destroy(); 1.99 + this.VariableBubble.destroy(); 1.100 + this.Tracer.destroy(); 1.101 + this.WatchExpressions.destroy(); 1.102 + this.EventListeners.destroy(); 1.103 + this.GlobalSearch.destroy(); 1.104 + this._destroyPanes(); 1.105 + this._destroyEditor(deferred.resolve); 1.106 + 1.107 + return deferred.promise; 1.108 + }, 1.109 + 1.110 + /** 1.111 + * Initializes the UI for all the displayed panes. 1.112 + */ 1.113 + _initializePanes: function() { 1.114 + dumpn("Initializing the DebuggerView panes"); 1.115 + 1.116 + this._body = document.getElementById("body"); 1.117 + this._editorDeck = document.getElementById("editor-deck"); 1.118 + this._sourcesPane = document.getElementById("sources-pane"); 1.119 + this._instrumentsPane = document.getElementById("instruments-pane"); 1.120 + this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle"); 1.121 + 1.122 + this.showEditor = this.showEditor.bind(this); 1.123 + this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this); 1.124 + this.showProgressBar = this.showProgressBar.bind(this); 1.125 + this.maybeShowBlackBoxMessage = this.maybeShowBlackBoxMessage.bind(this); 1.126 + 1.127 + this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this); 1.128 + this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect); 1.129 + 1.130 + this._collapsePaneString = L10N.getStr("collapsePanes"); 1.131 + this._expandPaneString = L10N.getStr("expandPanes"); 1.132 + 1.133 + this._sourcesPane.setAttribute("width", Prefs.sourcesWidth); 1.134 + this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth); 1.135 + this.toggleInstrumentsPane({ visible: Prefs.panesVisibleOnStartup }); 1.136 + 1.137 + // Side hosts requires a different arrangement of the debugger widgets. 1.138 + if (gHostType == "side") { 1.139 + this.handleHostChanged(gHostType); 1.140 + } 1.141 + }, 1.142 + 1.143 + /** 1.144 + * Destroys the UI for all the displayed panes. 1.145 + */ 1.146 + _destroyPanes: function() { 1.147 + dumpn("Destroying the DebuggerView panes"); 1.148 + 1.149 + if (gHostType != "side") { 1.150 + Prefs.sourcesWidth = this._sourcesPane.getAttribute("width"); 1.151 + Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width"); 1.152 + } 1.153 + 1.154 + this._sourcesPane = null; 1.155 + this._instrumentsPane = null; 1.156 + this._instrumentsPaneToggleButton = null; 1.157 + }, 1.158 + 1.159 + /** 1.160 + * Initializes the VariablesView instance and attaches a controller. 1.161 + */ 1.162 + _initializeVariablesView: function() { 1.163 + this.Variables = new VariablesView(document.getElementById("variables"), { 1.164 + searchPlaceholder: L10N.getStr("emptyVariablesFilterText"), 1.165 + emptyText: L10N.getStr("emptyVariablesText"), 1.166 + onlyEnumVisible: Prefs.variablesOnlyEnumVisible, 1.167 + searchEnabled: Prefs.variablesSearchboxVisible, 1.168 + eval: (variable, value) => { 1.169 + let string = variable.evaluationMacro(variable, value); 1.170 + DebuggerController.StackFrames.evaluate(string); 1.171 + }, 1.172 + lazyEmpty: true 1.173 + }); 1.174 + 1.175 + // Attach the current toolbox to the VView so it can link DOMNodes to 1.176 + // the inspector/highlighter 1.177 + this.Variables.toolbox = DebuggerController._toolbox; 1.178 + 1.179 + // Attach a controller that handles interfacing with the debugger protocol. 1.180 + VariablesViewController.attach(this.Variables, { 1.181 + getEnvironmentClient: aObject => gThreadClient.environment(aObject), 1.182 + getObjectClient: aObject => { 1.183 + return aObject instanceof DebuggerController.Tracer.WrappedObject 1.184 + ? DebuggerController.Tracer.syncGripClient(aObject.object) 1.185 + : gThreadClient.pauseGrip(aObject) 1.186 + } 1.187 + }); 1.188 + 1.189 + // Relay events from the VariablesView. 1.190 + this.Variables.on("fetched", (aEvent, aType) => { 1.191 + switch (aType) { 1.192 + case "scopes": 1.193 + window.emit(EVENTS.FETCHED_SCOPES); 1.194 + break; 1.195 + case "variables": 1.196 + window.emit(EVENTS.FETCHED_VARIABLES); 1.197 + break; 1.198 + case "properties": 1.199 + window.emit(EVENTS.FETCHED_PROPERTIES); 1.200 + break; 1.201 + } 1.202 + }); 1.203 + }, 1.204 + 1.205 + /** 1.206 + * Initializes the Editor instance. 1.207 + * 1.208 + * @param function aCallback 1.209 + * Called after the editor finishes initializing. 1.210 + */ 1.211 + _initializeEditor: function(aCallback) { 1.212 + dumpn("Initializing the DebuggerView editor"); 1.213 + 1.214 + let extraKeys = {}; 1.215 + bindKey("_doTokenSearch", "tokenSearchKey"); 1.216 + bindKey("_doGlobalSearch", "globalSearchKey", { alt: true }); 1.217 + bindKey("_doFunctionSearch", "functionSearchKey"); 1.218 + extraKeys[Editor.keyFor("jumpToLine")] = false; 1.219 + 1.220 + function bindKey(func, key, modifiers = {}) { 1.221 + let key = document.getElementById(key).getAttribute("key"); 1.222 + let shortcut = Editor.accel(key, modifiers); 1.223 + extraKeys[shortcut] = () => DebuggerView.Filtering[func](); 1.224 + } 1.225 + 1.226 + this.editor = new Editor({ 1.227 + mode: Editor.modes.text, 1.228 + readOnly: true, 1.229 + lineNumbers: true, 1.230 + showAnnotationRuler: true, 1.231 + gutters: [ "breakpoints" ], 1.232 + extraKeys: extraKeys, 1.233 + contextMenu: "sourceEditorContextMenu" 1.234 + }); 1.235 + 1.236 + this.editor.appendTo(document.getElementById("editor")).then(() => { 1.237 + this.editor.extend(DebuggerEditor); 1.238 + this._loadingText = L10N.getStr("loadingText"); 1.239 + this._onEditorLoad(aCallback); 1.240 + }); 1.241 + 1.242 + this.editor.on("gutterClick", (ev, line) => { 1.243 + if (this.editor.hasBreakpoint(line)) { 1.244 + this.editor.removeBreakpoint(line); 1.245 + } else { 1.246 + this.editor.addBreakpoint(line); 1.247 + } 1.248 + }); 1.249 + }, 1.250 + 1.251 + /** 1.252 + * The load event handler for the source editor, also executing any necessary 1.253 + * post-load operations. 1.254 + * 1.255 + * @param function aCallback 1.256 + * Called after the editor finishes loading. 1.257 + */ 1.258 + _onEditorLoad: function(aCallback) { 1.259 + dumpn("Finished loading the DebuggerView editor"); 1.260 + 1.261 + DebuggerController.Breakpoints.initialize().then(() => { 1.262 + window.emit(EVENTS.EDITOR_LOADED, this.editor); 1.263 + aCallback(); 1.264 + }); 1.265 + }, 1.266 + 1.267 + /** 1.268 + * Destroys the Editor instance and also executes any necessary 1.269 + * post-unload operations. 1.270 + * 1.271 + * @param function aCallback 1.272 + * Called after the editor finishes destroying. 1.273 + */ 1.274 + _destroyEditor: function(aCallback) { 1.275 + dumpn("Destroying the DebuggerView editor"); 1.276 + 1.277 + DebuggerController.Breakpoints.destroy().then(() => { 1.278 + window.emit(EVENTS.EDITOR_UNLOADED, this.editor); 1.279 + this.editor.destroy(); 1.280 + this.editor = null; 1.281 + aCallback(); 1.282 + }); 1.283 + }, 1.284 + 1.285 + /** 1.286 + * Display the source editor. 1.287 + */ 1.288 + showEditor: function() { 1.289 + this._editorDeck.selectedIndex = 0; 1.290 + }, 1.291 + 1.292 + /** 1.293 + * Display the black box message. 1.294 + */ 1.295 + showBlackBoxMessage: function() { 1.296 + this._editorDeck.selectedIndex = 1; 1.297 + }, 1.298 + 1.299 + /** 1.300 + * Display the progress bar. 1.301 + */ 1.302 + showProgressBar: function() { 1.303 + this._editorDeck.selectedIndex = 2; 1.304 + }, 1.305 + 1.306 + /** 1.307 + * Show or hide the black box message vs. source editor depending on if the 1.308 + * selected source is black boxed or not. 1.309 + */ 1.310 + maybeShowBlackBoxMessage: function() { 1.311 + let { source } = DebuggerView.Sources.selectedItem.attachment; 1.312 + if (gThreadClient.source(source).isBlackBoxed) { 1.313 + this.showBlackBoxMessage(); 1.314 + } else { 1.315 + this.showEditor(); 1.316 + } 1.317 + }, 1.318 + 1.319 + /** 1.320 + * Sets the currently displayed text contents in the source editor. 1.321 + * This resets the mode and undo stack. 1.322 + * 1.323 + * @param string aTextContent 1.324 + * The source text content. 1.325 + */ 1.326 + _setEditorText: function(aTextContent = "") { 1.327 + this.editor.setMode(Editor.modes.text); 1.328 + this.editor.setText(aTextContent); 1.329 + this.editor.clearDebugLocation(); 1.330 + this.editor.clearHistory(); 1.331 + }, 1.332 + 1.333 + /** 1.334 + * Sets the proper editor mode (JS or HTML) according to the specified 1.335 + * content type, or by determining the type from the url or text content. 1.336 + * 1.337 + * @param string aUrl 1.338 + * The source url. 1.339 + * @param string aContentType [optional] 1.340 + * The source content type. 1.341 + * @param string aTextContent [optional] 1.342 + * The source text content. 1.343 + */ 1.344 + _setEditorMode: function(aUrl, aContentType = "", aTextContent = "") { 1.345 + // Avoid setting the editor mode for very large files. 1.346 + // Is this still necessary? See bug 929225. 1.347 + if (aTextContent.length >= SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) { 1.348 + return void this.editor.setMode(Editor.modes.text); 1.349 + } 1.350 + 1.351 + // Use JS mode for files with .js and .jsm extensions. 1.352 + if (SourceUtils.isJavaScript(aUrl, aContentType)) { 1.353 + return void this.editor.setMode(Editor.modes.js); 1.354 + } 1.355 + 1.356 + // Use HTML mode for files in which the first non whitespace character is 1.357 + // <, regardless of extension. 1.358 + if (aTextContent.match(/^\s*</)) { 1.359 + return void this.editor.setMode(Editor.modes.html); 1.360 + } 1.361 + 1.362 + // Unknown language, use text. 1.363 + this.editor.setMode(Editor.modes.text); 1.364 + }, 1.365 + 1.366 + /** 1.367 + * Sets the currently displayed source text in the editor. 1.368 + * 1.369 + * You should use DebuggerView.updateEditor instead. It updates the current 1.370 + * caret and debug location based on a requested url and line. 1.371 + * 1.372 + * @param object aSource 1.373 + * The source object coming from the active thread. 1.374 + * @param object aFlags 1.375 + * Additional options for setting the source. Supported options: 1.376 + * - force: boolean allowing whether we can get the selected url's 1.377 + * text again. 1.378 + * @return object 1.379 + * A promise that is resolved after the source text has been set. 1.380 + */ 1.381 + _setEditorSource: function(aSource, aFlags={}) { 1.382 + // Avoid setting the same source text in the editor again. 1.383 + if (this._editorSource.url == aSource.url && !aFlags.force) { 1.384 + return this._editorSource.promise; 1.385 + } 1.386 + let transportType = gClient.localTransport ? "_LOCAL" : "_REMOTE"; 1.387 + let histogramId = "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE" + transportType + "_MS"; 1.388 + let histogram = Services.telemetry.getHistogramById(histogramId); 1.389 + let startTime = Date.now(); 1.390 + 1.391 + let deferred = promise.defer(); 1.392 + 1.393 + this._setEditorText(L10N.getStr("loadingText")); 1.394 + this._editorSource = { url: aSource.url, promise: deferred.promise }; 1.395 + 1.396 + DebuggerController.SourceScripts.getText(aSource).then(([, aText, aContentType]) => { 1.397 + // Avoid setting an unexpected source. This may happen when switching 1.398 + // very fast between sources that haven't been fetched yet. 1.399 + if (this._editorSource.url != aSource.url) { 1.400 + return; 1.401 + } 1.402 + 1.403 + this._setEditorText(aText); 1.404 + this._setEditorMode(aSource.url, aContentType, aText); 1.405 + 1.406 + // Synchronize any other components with the currently displayed source. 1.407 + DebuggerView.Sources.selectedValue = aSource.url; 1.408 + DebuggerController.Breakpoints.updateEditorBreakpoints(); 1.409 + 1.410 + histogram.add(Date.now() - startTime); 1.411 + 1.412 + // Resolve and notify that a source file was shown. 1.413 + window.emit(EVENTS.SOURCE_SHOWN, aSource); 1.414 + deferred.resolve([aSource, aText, aContentType]); 1.415 + }, 1.416 + ([, aError]) => { 1.417 + let msg = L10N.getStr("errorLoadingText") + DevToolsUtils.safeErrorString(aError); 1.418 + this._setEditorText(msg); 1.419 + Cu.reportError(msg); 1.420 + dumpn(msg); 1.421 + 1.422 + // Reject and notify that there was an error showing the source file. 1.423 + window.emit(EVENTS.SOURCE_ERROR_SHOWN, aSource); 1.424 + deferred.reject([aSource, aError]); 1.425 + }); 1.426 + 1.427 + return deferred.promise; 1.428 + }, 1.429 + 1.430 + /** 1.431 + * Update the source editor's current caret and debug location based on 1.432 + * a requested url and line. 1.433 + * 1.434 + * @param string aUrl 1.435 + * The target source url. 1.436 + * @param number aLine [optional] 1.437 + * The target line in the source. 1.438 + * @param object aFlags [optional] 1.439 + * Additional options for showing the source. Supported options: 1.440 + * - charOffset: character offset for the caret or debug location 1.441 + * - lineOffset: line offset for the caret or debug location 1.442 + * - columnOffset: column offset for the caret or debug location 1.443 + * - noCaret: don't set the caret location at the specified line 1.444 + * - noDebug: don't set the debug location at the specified line 1.445 + * - align: string specifying whether to align the specified line 1.446 + * at the "top", "center" or "bottom" of the editor 1.447 + * - force: boolean allowing whether we can get the selected url's 1.448 + * text again 1.449 + * @return object 1.450 + * A promise that is resolved after the source text has been set. 1.451 + */ 1.452 + setEditorLocation: function(aUrl, aLine = 0, aFlags = {}) { 1.453 + // Avoid trying to set a source for a url that isn't known yet. 1.454 + if (!this.Sources.containsValue(aUrl)) { 1.455 + return promise.reject(new Error("Unknown source for the specified URL.")); 1.456 + } 1.457 + 1.458 + // If the line is not specified, default to the current frame's position, 1.459 + // if available and the frame's url corresponds to the requested url. 1.460 + if (!aLine) { 1.461 + let cachedFrames = DebuggerController.activeThread.cachedFrames; 1.462 + let currentDepth = DebuggerController.StackFrames.currentFrameDepth; 1.463 + let frame = cachedFrames[currentDepth]; 1.464 + if (frame && frame.where.url == aUrl) { 1.465 + aLine = frame.where.line; 1.466 + } 1.467 + } 1.468 + 1.469 + let sourceItem = this.Sources.getItemByValue(aUrl); 1.470 + let sourceForm = sourceItem.attachment.source; 1.471 + 1.472 + // Make sure the requested source client is shown in the editor, then 1.473 + // update the source editor's caret position and debug location. 1.474 + return this._setEditorSource(sourceForm, aFlags).then(([,, aContentType]) => { 1.475 + // Record the contentType learned from fetching 1.476 + sourceForm.contentType = aContentType; 1.477 + // Line numbers in the source editor should start from 1. If invalid 1.478 + // or not specified, then don't do anything. 1.479 + if (aLine < 1) { 1.480 + window.emit(EVENTS.EDITOR_LOCATION_SET); 1.481 + return; 1.482 + } 1.483 + if (aFlags.charOffset) { 1.484 + aLine += this.editor.getPosition(aFlags.charOffset).line; 1.485 + } 1.486 + if (aFlags.lineOffset) { 1.487 + aLine += aFlags.lineOffset; 1.488 + } 1.489 + if (!aFlags.noCaret) { 1.490 + let location = { line: aLine -1, ch: aFlags.columnOffset || 0 }; 1.491 + this.editor.setCursor(location, aFlags.align); 1.492 + } 1.493 + if (!aFlags.noDebug) { 1.494 + this.editor.setDebugLocation(aLine - 1); 1.495 + } 1.496 + window.emit(EVENTS.EDITOR_LOCATION_SET); 1.497 + }).then(null, console.error); 1.498 + }, 1.499 + 1.500 + /** 1.501 + * Gets the visibility state of the instruments pane. 1.502 + * @return boolean 1.503 + */ 1.504 + get instrumentsPaneHidden() 1.505 + this._instrumentsPane.hasAttribute("pane-collapsed"), 1.506 + 1.507 + /** 1.508 + * Gets the currently selected tab in the instruments pane. 1.509 + * @return string 1.510 + */ 1.511 + get instrumentsPaneTab() 1.512 + this._instrumentsPane.selectedTab.id, 1.513 + 1.514 + /** 1.515 + * Sets the instruments pane hidden or visible. 1.516 + * 1.517 + * @param object aFlags 1.518 + * An object containing some of the following properties: 1.519 + * - visible: true if the pane should be shown, false to hide 1.520 + * - animated: true to display an animation on toggle 1.521 + * - delayed: true to wait a few cycles before toggle 1.522 + * - callback: a function to invoke when the toggle finishes 1.523 + * @param number aTabIndex [optional] 1.524 + * The index of the intended selected tab in the details pane. 1.525 + */ 1.526 + toggleInstrumentsPane: function(aFlags, aTabIndex) { 1.527 + let pane = this._instrumentsPane; 1.528 + let button = this._instrumentsPaneToggleButton; 1.529 + 1.530 + ViewHelpers.togglePane(aFlags, pane); 1.531 + 1.532 + if (aFlags.visible) { 1.533 + button.removeAttribute("pane-collapsed"); 1.534 + button.setAttribute("tooltiptext", this._collapsePaneString); 1.535 + } else { 1.536 + button.setAttribute("pane-collapsed", ""); 1.537 + button.setAttribute("tooltiptext", this._expandPaneString); 1.538 + } 1.539 + 1.540 + if (aTabIndex !== undefined) { 1.541 + pane.selectedIndex = aTabIndex; 1.542 + } 1.543 + }, 1.544 + 1.545 + /** 1.546 + * Sets the instruments pane visible after a short period of time. 1.547 + * 1.548 + * @param function aCallback 1.549 + * A function to invoke when the toggle finishes. 1.550 + */ 1.551 + showInstrumentsPane: function(aCallback) { 1.552 + DebuggerView.toggleInstrumentsPane({ 1.553 + visible: true, 1.554 + animated: true, 1.555 + delayed: true, 1.556 + callback: aCallback 1.557 + }, 0); 1.558 + }, 1.559 + 1.560 + /** 1.561 + * Handles a tab selection event on the instruments pane. 1.562 + */ 1.563 + _onInstrumentsPaneTabSelect: function() { 1.564 + if (this._instrumentsPane.selectedTab.id == "events-tab") { 1.565 + DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch(); 1.566 + } 1.567 + }, 1.568 + 1.569 + /** 1.570 + * Handles a host change event issued by the parent toolbox. 1.571 + * 1.572 + * @param string aType 1.573 + * The host type, either "bottom", "side" or "window". 1.574 + */ 1.575 + handleHostChanged: function(aType) { 1.576 + let newLayout = ""; 1.577 + 1.578 + if (aType == "side") { 1.579 + newLayout = "vertical"; 1.580 + this._enterVerticalLayout(); 1.581 + } else { 1.582 + newLayout = "horizontal"; 1.583 + this._enterHorizontalLayout(); 1.584 + } 1.585 + 1.586 + this._hostType = aType; 1.587 + this._body.setAttribute("layout", newLayout); 1.588 + window.emit(EVENTS.LAYOUT_CHANGED, newLayout); 1.589 + }, 1.590 + 1.591 + /** 1.592 + * Switches the debugger widgets to a horizontal layout. 1.593 + */ 1.594 + _enterVerticalLayout: function() { 1.595 + let normContainer = document.getElementById("debugger-widgets"); 1.596 + let vertContainer = document.getElementById("vertical-layout-panes-container"); 1.597 + 1.598 + // Move the soruces and instruments panes in a different container. 1.599 + let splitter = document.getElementById("sources-and-instruments-splitter"); 1.600 + vertContainer.insertBefore(this._sourcesPane, splitter); 1.601 + vertContainer.appendChild(this._instrumentsPane); 1.602 + 1.603 + // Make sure the vertical layout container's height doesn't repeatedly 1.604 + // grow or shrink based on the displayed sources, variables etc. 1.605 + vertContainer.setAttribute("height", 1.606 + vertContainer.getBoundingClientRect().height); 1.607 + }, 1.608 + 1.609 + /** 1.610 + * Switches the debugger widgets to a vertical layout. 1.611 + */ 1.612 + _enterHorizontalLayout: function() { 1.613 + let normContainer = document.getElementById("debugger-widgets"); 1.614 + let vertContainer = document.getElementById("vertical-layout-panes-container"); 1.615 + 1.616 + // The sources and instruments pane need to be inserted at their 1.617 + // previous locations in their normal container. 1.618 + let splitter = document.getElementById("sources-and-editor-splitter"); 1.619 + normContainer.insertBefore(this._sourcesPane, splitter); 1.620 + normContainer.appendChild(this._instrumentsPane); 1.621 + 1.622 + // Revert to the preferred sources and instruments widths, because 1.623 + // they flexed in the vertical layout. 1.624 + this._sourcesPane.setAttribute("width", Prefs.sourcesWidth); 1.625 + this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth); 1.626 + }, 1.627 + 1.628 + /** 1.629 + * Handles any initialization on a tab navigation event issued by the client. 1.630 + */ 1.631 + handleTabNavigation: function() { 1.632 + dumpn("Handling tab navigation in the DebuggerView"); 1.633 + 1.634 + this.Filtering.clearSearch(); 1.635 + this.FilteredSources.clearView(); 1.636 + this.FilteredFunctions.clearView(); 1.637 + this.GlobalSearch.clearView(); 1.638 + this.StackFrames.empty(); 1.639 + this.Sources.empty(); 1.640 + this.Variables.empty(); 1.641 + this.EventListeners.empty(); 1.642 + 1.643 + if (this.editor) { 1.644 + this.editor.setMode(Editor.modes.text); 1.645 + this.editor.setText(""); 1.646 + this.editor.clearHistory(); 1.647 + this._editorSource = {}; 1.648 + } 1.649 + 1.650 + this.Sources.emptyText = L10N.getStr("loadingSourcesText"); 1.651 + }, 1.652 + 1.653 + _startup: null, 1.654 + _shutdown: null, 1.655 + Toolbar: null, 1.656 + Options: null, 1.657 + Filtering: null, 1.658 + FilteredSources: null, 1.659 + FilteredFunctions: null, 1.660 + GlobalSearch: null, 1.661 + StackFrames: null, 1.662 + Sources: null, 1.663 + Tracer: null, 1.664 + Variables: null, 1.665 + VariableBubble: null, 1.666 + WatchExpressions: null, 1.667 + EventListeners: null, 1.668 + editor: null, 1.669 + _editorSource: {}, 1.670 + _loadingText: "", 1.671 + _body: null, 1.672 + _editorDeck: null, 1.673 + _sourcesPane: null, 1.674 + _instrumentsPane: null, 1.675 + _instrumentsPaneToggleButton: null, 1.676 + _collapsePaneString: "", 1.677 + _expandPaneString: "" 1.678 +}; 1.679 + 1.680 +/** 1.681 + * A custom items container, used for displaying views like the 1.682 + * FilteredSources, FilteredFunctions etc., inheriting the generic WidgetMethods. 1.683 + */ 1.684 +function ResultsPanelContainer() { 1.685 +} 1.686 + 1.687 +ResultsPanelContainer.prototype = Heritage.extend(WidgetMethods, { 1.688 + /** 1.689 + * Sets the anchor node for this container panel. 1.690 + * @param nsIDOMNode aNode 1.691 + */ 1.692 + set anchor(aNode) { 1.693 + this._anchor = aNode; 1.694 + 1.695 + // If the anchor node is not null, create a panel to attach to the anchor 1.696 + // when showing the popup. 1.697 + if (aNode) { 1.698 + if (!this._panel) { 1.699 + this._panel = document.createElement("panel"); 1.700 + this._panel.id = "results-panel"; 1.701 + this._panel.setAttribute("level", "top"); 1.702 + this._panel.setAttribute("noautofocus", "true"); 1.703 + this._panel.setAttribute("consumeoutsideclicks", "false"); 1.704 + document.documentElement.appendChild(this._panel); 1.705 + } 1.706 + if (!this.widget) { 1.707 + this.widget = new SimpleListWidget(this._panel); 1.708 + this.autoFocusOnFirstItem = false; 1.709 + this.autoFocusOnSelection = false; 1.710 + this.maintainSelectionVisible = false; 1.711 + } 1.712 + } 1.713 + // Cleanup the anchor and remove the previously created panel. 1.714 + else { 1.715 + this._panel.remove(); 1.716 + this._panel = null; 1.717 + this.widget = null; 1.718 + } 1.719 + }, 1.720 + 1.721 + /** 1.722 + * Gets the anchor node for this container panel. 1.723 + * @return nsIDOMNode 1.724 + */ 1.725 + get anchor() { 1.726 + return this._anchor; 1.727 + }, 1.728 + 1.729 + /** 1.730 + * Sets the container panel hidden or visible. It's hidden by default. 1.731 + * @param boolean aFlag 1.732 + */ 1.733 + set hidden(aFlag) { 1.734 + if (aFlag) { 1.735 + this._panel.hidden = true; 1.736 + this._panel.hidePopup(); 1.737 + } else { 1.738 + this._panel.hidden = false; 1.739 + this._panel.openPopup(this._anchor, this.position, this.left, this.top); 1.740 + } 1.741 + }, 1.742 + 1.743 + /** 1.744 + * Gets this container's visibility state. 1.745 + * @return boolean 1.746 + */ 1.747 + get hidden() 1.748 + this._panel.state == "closed" || 1.749 + this._panel.state == "hiding", 1.750 + 1.751 + /** 1.752 + * Removes all items from this container and hides it. 1.753 + */ 1.754 + clearView: function() { 1.755 + this.hidden = true; 1.756 + this.empty(); 1.757 + }, 1.758 + 1.759 + /** 1.760 + * Selects the next found item in this container. 1.761 + * Does not change the currently focused node. 1.762 + */ 1.763 + selectNext: function() { 1.764 + let nextIndex = this.selectedIndex + 1; 1.765 + if (nextIndex >= this.itemCount) { 1.766 + nextIndex = 0; 1.767 + } 1.768 + this.selectedItem = this.getItemAtIndex(nextIndex); 1.769 + }, 1.770 + 1.771 + /** 1.772 + * Selects the previously found item in this container. 1.773 + * Does not change the currently focused node. 1.774 + */ 1.775 + selectPrev: function() { 1.776 + let prevIndex = this.selectedIndex - 1; 1.777 + if (prevIndex < 0) { 1.778 + prevIndex = this.itemCount - 1; 1.779 + } 1.780 + this.selectedItem = this.getItemAtIndex(prevIndex); 1.781 + }, 1.782 + 1.783 + /** 1.784 + * Customization function for creating an item's UI. 1.785 + * 1.786 + * @param string aLabel 1.787 + * The item's label string. 1.788 + * @param string aBeforeLabel 1.789 + * An optional string shown before the label. 1.790 + * @param string aBelowLabel 1.791 + * An optional string shown underneath the label. 1.792 + */ 1.793 + _createItemView: function(aLabel, aBelowLabel, aBeforeLabel) { 1.794 + let container = document.createElement("vbox"); 1.795 + container.className = "results-panel-item"; 1.796 + 1.797 + let firstRowLabels = document.createElement("hbox"); 1.798 + let secondRowLabels = document.createElement("hbox"); 1.799 + 1.800 + if (aBeforeLabel) { 1.801 + let beforeLabelNode = document.createElement("label"); 1.802 + beforeLabelNode.className = "plain results-panel-item-label-before"; 1.803 + beforeLabelNode.setAttribute("value", aBeforeLabel); 1.804 + firstRowLabels.appendChild(beforeLabelNode); 1.805 + } 1.806 + 1.807 + let labelNode = document.createElement("label"); 1.808 + labelNode.className = "plain results-panel-item-label"; 1.809 + labelNode.setAttribute("value", aLabel); 1.810 + firstRowLabels.appendChild(labelNode); 1.811 + 1.812 + if (aBelowLabel) { 1.813 + let belowLabelNode = document.createElement("label"); 1.814 + belowLabelNode.className = "plain results-panel-item-label-below"; 1.815 + belowLabelNode.setAttribute("value", aBelowLabel); 1.816 + secondRowLabels.appendChild(belowLabelNode); 1.817 + } 1.818 + 1.819 + container.appendChild(firstRowLabels); 1.820 + container.appendChild(secondRowLabels); 1.821 + 1.822 + return container; 1.823 + }, 1.824 + 1.825 + _anchor: null, 1.826 + _panel: null, 1.827 + position: RESULTS_PANEL_POPUP_POSITION, 1.828 + left: 0, 1.829 + top: 0 1.830 +});