1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/debugger/debugger-toolbar.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1544 @@ 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 +// A time interval sufficient for the options popup panel to finish hiding 1.12 +// itself. 1.13 +const POPUP_HIDDEN_DELAY = 100; // ms 1.14 + 1.15 +/** 1.16 + * Functions handling the toolbar view: close button, expand/collapse button, 1.17 + * pause/resume and stepping buttons etc. 1.18 + */ 1.19 +function ToolbarView() { 1.20 + dumpn("ToolbarView was instantiated"); 1.21 + 1.22 + this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this); 1.23 + this._onResumePressed = this._onResumePressed.bind(this); 1.24 + this._onStepOverPressed = this._onStepOverPressed.bind(this); 1.25 + this._onStepInPressed = this._onStepInPressed.bind(this); 1.26 + this._onStepOutPressed = this._onStepOutPressed.bind(this); 1.27 +} 1.28 + 1.29 +ToolbarView.prototype = { 1.30 + /** 1.31 + * Initialization function, called when the debugger is started. 1.32 + */ 1.33 + initialize: function() { 1.34 + dumpn("Initializing the ToolbarView"); 1.35 + 1.36 + this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle"); 1.37 + this._resumeButton = document.getElementById("resume"); 1.38 + this._stepOverButton = document.getElementById("step-over"); 1.39 + this._stepInButton = document.getElementById("step-in"); 1.40 + this._stepOutButton = document.getElementById("step-out"); 1.41 + this._resumeOrderTooltip = new Tooltip(document); 1.42 + this._resumeOrderTooltip.defaultPosition = TOOLBAR_ORDER_POPUP_POSITION; 1.43 + 1.44 + let resumeKey = ShortcutUtils.prettifyShortcut(document.getElementById("resumeKey")); 1.45 + let stepOverKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOverKey")); 1.46 + let stepInKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepInKey")); 1.47 + let stepOutKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOutKey")); 1.48 + this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey); 1.49 + this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey); 1.50 + this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey); 1.51 + this._stepInTooltip = L10N.getFormatStr("stepInTooltip", stepInKey); 1.52 + this._stepOutTooltip = L10N.getFormatStr("stepOutTooltip", stepOutKey); 1.53 + 1.54 + this._instrumentsPaneToggleButton.addEventListener("mousedown", this._onTogglePanesPressed, false); 1.55 + this._resumeButton.addEventListener("mousedown", this._onResumePressed, false); 1.56 + this._stepOverButton.addEventListener("mousedown", this._onStepOverPressed, false); 1.57 + this._stepInButton.addEventListener("mousedown", this._onStepInPressed, false); 1.58 + this._stepOutButton.addEventListener("mousedown", this._onStepOutPressed, false); 1.59 + 1.60 + this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip); 1.61 + this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip); 1.62 + this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip); 1.63 + }, 1.64 + 1.65 + /** 1.66 + * Destruction function, called when the debugger is closed. 1.67 + */ 1.68 + destroy: function() { 1.69 + dumpn("Destroying the ToolbarView"); 1.70 + 1.71 + this._instrumentsPaneToggleButton.removeEventListener("mousedown", this._onTogglePanesPressed, false); 1.72 + this._resumeButton.removeEventListener("mousedown", this._onResumePressed, false); 1.73 + this._stepOverButton.removeEventListener("mousedown", this._onStepOverPressed, false); 1.74 + this._stepInButton.removeEventListener("mousedown", this._onStepInPressed, false); 1.75 + this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false); 1.76 + }, 1.77 + 1.78 + /** 1.79 + * Display a warning when trying to resume a debuggee while another is paused. 1.80 + * Debuggees must be unpaused in a Last-In-First-Out order. 1.81 + * 1.82 + * @param string aPausedUrl 1.83 + * The URL of the last paused debuggee. 1.84 + */ 1.85 + showResumeWarning: function(aPausedUrl) { 1.86 + let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl); 1.87 + let defaultStyle = "default-tooltip-simple-text-colors"; 1.88 + this._resumeOrderTooltip.setTextContent({ messages: [label], isAlertTooltip: true }); 1.89 + this._resumeOrderTooltip.show(this._resumeButton); 1.90 + }, 1.91 + 1.92 + /** 1.93 + * Sets the resume button state based on the debugger active thread. 1.94 + * 1.95 + * @param string aState 1.96 + * Either "paused" or "attached". 1.97 + */ 1.98 + toggleResumeButtonState: function(aState) { 1.99 + // If we're paused, check and show a resume label on the button. 1.100 + if (aState == "paused") { 1.101 + this._resumeButton.setAttribute("checked", "true"); 1.102 + this._resumeButton.setAttribute("tooltiptext", this._resumeTooltip); 1.103 + } 1.104 + // If we're attached, do the opposite. 1.105 + else if (aState == "attached") { 1.106 + this._resumeButton.removeAttribute("checked"); 1.107 + this._resumeButton.setAttribute("tooltiptext", this._pauseTooltip); 1.108 + } 1.109 + }, 1.110 + 1.111 + /** 1.112 + * Listener handling the toggle button click event. 1.113 + */ 1.114 + _onTogglePanesPressed: function() { 1.115 + DebuggerView.toggleInstrumentsPane({ 1.116 + visible: DebuggerView.instrumentsPaneHidden, 1.117 + animated: true, 1.118 + delayed: true 1.119 + }); 1.120 + }, 1.121 + 1.122 + /** 1.123 + * Listener handling the pause/resume button click event. 1.124 + */ 1.125 + _onResumePressed: function() { 1.126 + if (DebuggerController.activeThread.paused) { 1.127 + let warn = DebuggerController._ensureResumptionOrder; 1.128 + DebuggerController.StackFrames.currentFrameDepth = -1; 1.129 + DebuggerController.activeThread.resume(warn); 1.130 + } else { 1.131 + DebuggerController.activeThread.interrupt(); 1.132 + } 1.133 + }, 1.134 + 1.135 + /** 1.136 + * Listener handling the step over button click event. 1.137 + */ 1.138 + _onStepOverPressed: function() { 1.139 + if (DebuggerController.activeThread.paused) { 1.140 + DebuggerController.StackFrames.currentFrameDepth = -1; 1.141 + let warn = DebuggerController._ensureResumptionOrder; 1.142 + DebuggerController.activeThread.stepOver(warn); 1.143 + } 1.144 + }, 1.145 + 1.146 + /** 1.147 + * Listener handling the step in button click event. 1.148 + */ 1.149 + _onStepInPressed: function() { 1.150 + if (DebuggerController.activeThread.paused) { 1.151 + DebuggerController.StackFrames.currentFrameDepth = -1; 1.152 + let warn = DebuggerController._ensureResumptionOrder; 1.153 + DebuggerController.activeThread.stepIn(warn); 1.154 + } 1.155 + }, 1.156 + 1.157 + /** 1.158 + * Listener handling the step out button click event. 1.159 + */ 1.160 + _onStepOutPressed: function() { 1.161 + if (DebuggerController.activeThread.paused) { 1.162 + DebuggerController.StackFrames.currentFrameDepth = -1; 1.163 + let warn = DebuggerController._ensureResumptionOrder; 1.164 + DebuggerController.activeThread.stepOut(warn); 1.165 + } 1.166 + }, 1.167 + 1.168 + _instrumentsPaneToggleButton: null, 1.169 + _resumeButton: null, 1.170 + _stepOverButton: null, 1.171 + _stepInButton: null, 1.172 + _stepOutButton: null, 1.173 + _resumeOrderTooltip: null, 1.174 + _resumeTooltip: "", 1.175 + _pauseTooltip: "", 1.176 + _stepOverTooltip: "", 1.177 + _stepInTooltip: "", 1.178 + _stepOutTooltip: "" 1.179 +}; 1.180 + 1.181 +/** 1.182 + * Functions handling the options UI. 1.183 + */ 1.184 +function OptionsView() { 1.185 + dumpn("OptionsView was instantiated"); 1.186 + 1.187 + this._toggleAutoPrettyPrint = this._toggleAutoPrettyPrint.bind(this); 1.188 + this._togglePauseOnExceptions = this._togglePauseOnExceptions.bind(this); 1.189 + this._toggleIgnoreCaughtExceptions = this._toggleIgnoreCaughtExceptions.bind(this); 1.190 + this._toggleShowPanesOnStartup = this._toggleShowPanesOnStartup.bind(this); 1.191 + this._toggleShowVariablesOnlyEnum = this._toggleShowVariablesOnlyEnum.bind(this); 1.192 + this._toggleShowVariablesFilterBox = this._toggleShowVariablesFilterBox.bind(this); 1.193 + this._toggleShowOriginalSource = this._toggleShowOriginalSource.bind(this); 1.194 +} 1.195 + 1.196 +OptionsView.prototype = { 1.197 + /** 1.198 + * Initialization function, called when the debugger is started. 1.199 + */ 1.200 + initialize: function() { 1.201 + dumpn("Initializing the OptionsView"); 1.202 + 1.203 + this._button = document.getElementById("debugger-options"); 1.204 + this._autoPrettyPrint = document.getElementById("auto-pretty-print"); 1.205 + this._pauseOnExceptionsItem = document.getElementById("pause-on-exceptions"); 1.206 + this._ignoreCaughtExceptionsItem = document.getElementById("ignore-caught-exceptions"); 1.207 + this._showPanesOnStartupItem = document.getElementById("show-panes-on-startup"); 1.208 + this._showVariablesOnlyEnumItem = document.getElementById("show-vars-only-enum"); 1.209 + this._showVariablesFilterBoxItem = document.getElementById("show-vars-filter-box"); 1.210 + this._showOriginalSourceItem = document.getElementById("show-original-source"); 1.211 + 1.212 + this._autoPrettyPrint.setAttribute("checked", Prefs.autoPrettyPrint); 1.213 + this._pauseOnExceptionsItem.setAttribute("checked", Prefs.pauseOnExceptions); 1.214 + this._ignoreCaughtExceptionsItem.setAttribute("checked", Prefs.ignoreCaughtExceptions); 1.215 + this._showPanesOnStartupItem.setAttribute("checked", Prefs.panesVisibleOnStartup); 1.216 + this._showVariablesOnlyEnumItem.setAttribute("checked", Prefs.variablesOnlyEnumVisible); 1.217 + this._showVariablesFilterBoxItem.setAttribute("checked", Prefs.variablesSearchboxVisible); 1.218 + this._showOriginalSourceItem.setAttribute("checked", Prefs.sourceMapsEnabled); 1.219 + }, 1.220 + 1.221 + /** 1.222 + * Destruction function, called when the debugger is closed. 1.223 + */ 1.224 + destroy: function() { 1.225 + dumpn("Destroying the OptionsView"); 1.226 + // Nothing to do here yet. 1.227 + }, 1.228 + 1.229 + /** 1.230 + * Listener handling the 'gear menu' popup showing event. 1.231 + */ 1.232 + _onPopupShowing: function() { 1.233 + this._button.setAttribute("open", "true"); 1.234 + window.emit(EVENTS.OPTIONS_POPUP_SHOWING); 1.235 + }, 1.236 + 1.237 + /** 1.238 + * Listener handling the 'gear menu' popup hiding event. 1.239 + */ 1.240 + _onPopupHiding: function() { 1.241 + this._button.removeAttribute("open"); 1.242 + }, 1.243 + 1.244 + /** 1.245 + * Listener handling the 'gear menu' popup hidden event. 1.246 + */ 1.247 + _onPopupHidden: function() { 1.248 + window.emit(EVENTS.OPTIONS_POPUP_HIDDEN); 1.249 + }, 1.250 + 1.251 + /** 1.252 + * Listener handling the 'auto pretty print' menuitem command. 1.253 + */ 1.254 + _toggleAutoPrettyPrint: function(){ 1.255 + Prefs.autoPrettyPrint = 1.256 + this._autoPrettyPrint.getAttribute("checked") == "true"; 1.257 + }, 1.258 + 1.259 + /** 1.260 + * Listener handling the 'pause on exceptions' menuitem command. 1.261 + */ 1.262 + _togglePauseOnExceptions: function() { 1.263 + Prefs.pauseOnExceptions = 1.264 + this._pauseOnExceptionsItem.getAttribute("checked") == "true"; 1.265 + 1.266 + DebuggerController.activeThread.pauseOnExceptions( 1.267 + Prefs.pauseOnExceptions, 1.268 + Prefs.ignoreCaughtExceptions); 1.269 + }, 1.270 + 1.271 + _toggleIgnoreCaughtExceptions: function() { 1.272 + Prefs.ignoreCaughtExceptions = 1.273 + this._ignoreCaughtExceptionsItem.getAttribute("checked") == "true"; 1.274 + 1.275 + DebuggerController.activeThread.pauseOnExceptions( 1.276 + Prefs.pauseOnExceptions, 1.277 + Prefs.ignoreCaughtExceptions); 1.278 + }, 1.279 + 1.280 + /** 1.281 + * Listener handling the 'show panes on startup' menuitem command. 1.282 + */ 1.283 + _toggleShowPanesOnStartup: function() { 1.284 + Prefs.panesVisibleOnStartup = 1.285 + this._showPanesOnStartupItem.getAttribute("checked") == "true"; 1.286 + }, 1.287 + 1.288 + /** 1.289 + * Listener handling the 'show non-enumerables' menuitem command. 1.290 + */ 1.291 + _toggleShowVariablesOnlyEnum: function() { 1.292 + let pref = Prefs.variablesOnlyEnumVisible = 1.293 + this._showVariablesOnlyEnumItem.getAttribute("checked") == "true"; 1.294 + 1.295 + DebuggerView.Variables.onlyEnumVisible = pref; 1.296 + }, 1.297 + 1.298 + /** 1.299 + * Listener handling the 'show variables searchbox' menuitem command. 1.300 + */ 1.301 + _toggleShowVariablesFilterBox: function() { 1.302 + let pref = Prefs.variablesSearchboxVisible = 1.303 + this._showVariablesFilterBoxItem.getAttribute("checked") == "true"; 1.304 + 1.305 + DebuggerView.Variables.searchEnabled = pref; 1.306 + }, 1.307 + 1.308 + /** 1.309 + * Listener handling the 'show original source' menuitem command. 1.310 + */ 1.311 + _toggleShowOriginalSource: function() { 1.312 + let pref = Prefs.sourceMapsEnabled = 1.313 + this._showOriginalSourceItem.getAttribute("checked") == "true"; 1.314 + 1.315 + // Don't block the UI while reconfiguring the server. 1.316 + window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => { 1.317 + // The popup panel needs more time to hide after triggering onpopuphidden. 1.318 + window.setTimeout(() => { 1.319 + DebuggerController.reconfigureThread(pref); 1.320 + }, POPUP_HIDDEN_DELAY); 1.321 + }, false); 1.322 + }, 1.323 + 1.324 + _button: null, 1.325 + _pauseOnExceptionsItem: null, 1.326 + _showPanesOnStartupItem: null, 1.327 + _showVariablesOnlyEnumItem: null, 1.328 + _showVariablesFilterBoxItem: null, 1.329 + _showOriginalSourceItem: null 1.330 +}; 1.331 + 1.332 +/** 1.333 + * Functions handling the stackframes UI. 1.334 + */ 1.335 +function StackFramesView() { 1.336 + dumpn("StackFramesView was instantiated"); 1.337 + 1.338 + this._onStackframeRemoved = this._onStackframeRemoved.bind(this); 1.339 + this._onSelect = this._onSelect.bind(this); 1.340 + this._onScroll = this._onScroll.bind(this); 1.341 + this._afterScroll = this._afterScroll.bind(this); 1.342 +} 1.343 + 1.344 +StackFramesView.prototype = Heritage.extend(WidgetMethods, { 1.345 + /** 1.346 + * Initialization function, called when the debugger is started. 1.347 + */ 1.348 + initialize: function() { 1.349 + dumpn("Initializing the StackFramesView"); 1.350 + 1.351 + this.widget = new BreadcrumbsWidget(document.getElementById("stackframes")); 1.352 + this.widget.addEventListener("select", this._onSelect, false); 1.353 + this.widget.addEventListener("scroll", this._onScroll, true); 1.354 + window.addEventListener("resize", this._onScroll, true); 1.355 + 1.356 + this.autoFocusOnFirstItem = false; 1.357 + this.autoFocusOnSelection = false; 1.358 + 1.359 + // This view's contents are also mirrored in a different container. 1.360 + this._mirror = DebuggerView.StackFramesClassicList; 1.361 + }, 1.362 + 1.363 + /** 1.364 + * Destruction function, called when the debugger is closed. 1.365 + */ 1.366 + destroy: function() { 1.367 + dumpn("Destroying the StackFramesView"); 1.368 + 1.369 + this.widget.removeEventListener("select", this._onSelect, false); 1.370 + this.widget.removeEventListener("scroll", this._onScroll, true); 1.371 + window.removeEventListener("resize", this._onScroll, true); 1.372 + }, 1.373 + 1.374 + /** 1.375 + * Adds a frame in this stackframes container. 1.376 + * 1.377 + * @param string aTitle 1.378 + * The frame title (function name). 1.379 + * @param string aUrl 1.380 + * The frame source url. 1.381 + * @param string aLine 1.382 + * The frame line number. 1.383 + * @param number aDepth 1.384 + * The frame depth in the stack. 1.385 + * @param boolean aIsBlackBoxed 1.386 + * Whether or not the frame is black boxed. 1.387 + */ 1.388 + addFrame: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) { 1.389 + // Blackboxed stack frames are collapsed into a single entry in 1.390 + // the view. By convention, only the first frame is displayed. 1.391 + if (aIsBlackBoxed) { 1.392 + if (this._prevBlackBoxedUrl == aUrl) { 1.393 + return; 1.394 + } 1.395 + this._prevBlackBoxedUrl = aUrl; 1.396 + } else { 1.397 + this._prevBlackBoxedUrl = null; 1.398 + } 1.399 + 1.400 + // Create the element node for the stack frame item. 1.401 + let frameView = this._createFrameView.apply(this, arguments); 1.402 + 1.403 + // Append a stack frame item to this container. 1.404 + this.push([frameView], { 1.405 + index: 0, /* specifies on which position should the item be appended */ 1.406 + attachment: { 1.407 + title: aTitle, 1.408 + url: aUrl, 1.409 + line: aLine, 1.410 + depth: aDepth 1.411 + }, 1.412 + // Make sure that when the stack frame item is removed, the corresponding 1.413 + // mirrored item in the classic list is also removed. 1.414 + finalize: this._onStackframeRemoved 1.415 + }); 1.416 + 1.417 + // Mirror this newly inserted item inside the "Call Stack" tab. 1.418 + this._mirror.addFrame(aTitle, aUrl, aLine, aDepth); 1.419 + }, 1.420 + 1.421 + /** 1.422 + * Selects the frame at the specified depth in this container. 1.423 + * @param number aDepth 1.424 + */ 1.425 + set selectedDepth(aDepth) { 1.426 + this.selectedItem = aItem => aItem.attachment.depth == aDepth; 1.427 + }, 1.428 + 1.429 + /** 1.430 + * Gets the currently selected stack frame's depth in this container. 1.431 + * This will essentially be the opposite of |selectedIndex|, which deals 1.432 + * with the position in the view, where the last item added is actually 1.433 + * the bottommost, not topmost. 1.434 + * @return number 1.435 + */ 1.436 + get selectedDepth() { 1.437 + return this.selectedItem.attachment.depth; 1.438 + }, 1.439 + 1.440 + /** 1.441 + * Specifies if the active thread has more frames that need to be loaded. 1.442 + */ 1.443 + dirty: false, 1.444 + 1.445 + /** 1.446 + * Customization function for creating an item's UI. 1.447 + * 1.448 + * @param string aTitle 1.449 + * The frame title to be displayed in the list. 1.450 + * @param string aUrl 1.451 + * The frame source url. 1.452 + * @param string aLine 1.453 + * The frame line number. 1.454 + * @param number aDepth 1.455 + * The frame depth in the stack. 1.456 + * @param boolean aIsBlackBoxed 1.457 + * Whether or not the frame is black boxed. 1.458 + * @return nsIDOMNode 1.459 + * The stack frame view. 1.460 + */ 1.461 + _createFrameView: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) { 1.462 + let container = document.createElement("hbox"); 1.463 + container.id = "stackframe-" + aDepth; 1.464 + container.className = "dbg-stackframe"; 1.465 + 1.466 + let frameDetails = SourceUtils.trimUrlLength( 1.467 + SourceUtils.getSourceLabel(aUrl), 1.468 + STACK_FRAMES_SOURCE_URL_MAX_LENGTH, 1.469 + STACK_FRAMES_SOURCE_URL_TRIM_SECTION); 1.470 + 1.471 + if (aIsBlackBoxed) { 1.472 + container.classList.add("dbg-stackframe-black-boxed"); 1.473 + } else { 1.474 + let frameTitleNode = document.createElement("label"); 1.475 + frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag"; 1.476 + frameTitleNode.setAttribute("value", aTitle); 1.477 + container.appendChild(frameTitleNode); 1.478 + 1.479 + frameDetails += SEARCH_LINE_FLAG + aLine; 1.480 + } 1.481 + 1.482 + let frameDetailsNode = document.createElement("label"); 1.483 + frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id"; 1.484 + frameDetailsNode.setAttribute("value", frameDetails); 1.485 + container.appendChild(frameDetailsNode); 1.486 + 1.487 + return container; 1.488 + }, 1.489 + 1.490 + /** 1.491 + * Function called each time a stack frame item is removed. 1.492 + * 1.493 + * @param object aItem 1.494 + * The corresponding item. 1.495 + */ 1.496 + _onStackframeRemoved: function(aItem) { 1.497 + dumpn("Finalizing stackframe item: " + aItem); 1.498 + 1.499 + // Remove the mirrored item in the classic list. 1.500 + let depth = aItem.attachment.depth; 1.501 + this._mirror.remove(this._mirror.getItemForAttachment(e => e.depth == depth)); 1.502 + 1.503 + // Forget the previously blackboxed stack frame url. 1.504 + this._prevBlackBoxedUrl = null; 1.505 + }, 1.506 + 1.507 + /** 1.508 + * The select listener for the stackframes container. 1.509 + */ 1.510 + _onSelect: function(e) { 1.511 + let stackframeItem = this.selectedItem; 1.512 + if (stackframeItem) { 1.513 + // The container is not empty and an actual item was selected. 1.514 + let depth = stackframeItem.attachment.depth; 1.515 + DebuggerController.StackFrames.selectFrame(depth); 1.516 + 1.517 + // Mirror the selected item in the classic list. 1.518 + this.suppressSelectionEvents = true; 1.519 + this._mirror.selectedItem = e => e.attachment.depth == depth; 1.520 + this.suppressSelectionEvents = false; 1.521 + } 1.522 + }, 1.523 + 1.524 + /** 1.525 + * The scroll listener for the stackframes container. 1.526 + */ 1.527 + _onScroll: function() { 1.528 + // Update the stackframes container only if we have to. 1.529 + if (!this.dirty) { 1.530 + return; 1.531 + } 1.532 + // Allow requests to settle down first. 1.533 + setNamedTimeout("stack-scroll", STACK_FRAMES_SCROLL_DELAY, this._afterScroll); 1.534 + }, 1.535 + 1.536 + /** 1.537 + * Requests the addition of more frames from the controller. 1.538 + */ 1.539 + _afterScroll: function() { 1.540 + let scrollPosition = this.widget.getAttribute("scrollPosition"); 1.541 + let scrollWidth = this.widget.getAttribute("scrollWidth"); 1.542 + 1.543 + // If the stackframes container scrolled almost to the end, with only 1.544 + // 1/10 of a breadcrumb remaining, load more content. 1.545 + if (scrollPosition - scrollWidth / 10 < 1) { 1.546 + this.ensureIndexIsVisible(CALL_STACK_PAGE_SIZE - 1); 1.547 + this.dirty = false; 1.548 + 1.549 + // Loads more stack frames from the debugger server cache. 1.550 + DebuggerController.StackFrames.addMoreFrames(); 1.551 + } 1.552 + }, 1.553 + 1.554 + _mirror: null, 1.555 + _prevBlackBoxedUrl: null 1.556 +}); 1.557 + 1.558 +/* 1.559 + * Functions handling the stackframes classic list UI. 1.560 + * Controlled by the DebuggerView.StackFrames isntance. 1.561 + */ 1.562 +function StackFramesClassicListView() { 1.563 + dumpn("StackFramesClassicListView was instantiated"); 1.564 + 1.565 + this._onSelect = this._onSelect.bind(this); 1.566 +} 1.567 + 1.568 +StackFramesClassicListView.prototype = Heritage.extend(WidgetMethods, { 1.569 + /** 1.570 + * Initialization function, called when the debugger is started. 1.571 + */ 1.572 + initialize: function() { 1.573 + dumpn("Initializing the StackFramesClassicListView"); 1.574 + 1.575 + this.widget = new SideMenuWidget(document.getElementById("callstack-list")); 1.576 + this.widget.addEventListener("select", this._onSelect, false); 1.577 + 1.578 + this.emptyText = L10N.getStr("noStackFramesText"); 1.579 + this.autoFocusOnFirstItem = false; 1.580 + this.autoFocusOnSelection = false; 1.581 + 1.582 + // This view's contents are also mirrored in a different container. 1.583 + this._mirror = DebuggerView.StackFrames; 1.584 + }, 1.585 + 1.586 + /** 1.587 + * Destruction function, called when the debugger is closed. 1.588 + */ 1.589 + destroy: function() { 1.590 + dumpn("Destroying the StackFramesClassicListView"); 1.591 + 1.592 + this.widget.removeEventListener("select", this._onSelect, false); 1.593 + }, 1.594 + 1.595 + /** 1.596 + * Adds a frame in this stackframes container. 1.597 + * 1.598 + * @param string aTitle 1.599 + * The frame title (function name). 1.600 + * @param string aUrl 1.601 + * The frame source url. 1.602 + * @param string aLine 1.603 + * The frame line number. 1.604 + * @param number aDepth 1.605 + * The frame depth in the stack. 1.606 + */ 1.607 + addFrame: function(aTitle, aUrl, aLine, aDepth) { 1.608 + // Create the element node for the stack frame item. 1.609 + let frameView = this._createFrameView.apply(this, arguments); 1.610 + 1.611 + // Append a stack frame item to this container. 1.612 + this.push([frameView], { 1.613 + attachment: { 1.614 + depth: aDepth 1.615 + } 1.616 + }); 1.617 + }, 1.618 + 1.619 + /** 1.620 + * Customization function for creating an item's UI. 1.621 + * 1.622 + * @param string aTitle 1.623 + * The frame title to be displayed in the list. 1.624 + * @param string aUrl 1.625 + * The frame source url. 1.626 + * @param string aLine 1.627 + * The frame line number. 1.628 + * @param number aDepth 1.629 + * The frame depth in the stack. 1.630 + * @return nsIDOMNode 1.631 + * The stack frame view. 1.632 + */ 1.633 + _createFrameView: function(aTitle, aUrl, aLine, aDepth) { 1.634 + let container = document.createElement("hbox"); 1.635 + container.id = "classic-stackframe-" + aDepth; 1.636 + container.className = "dbg-classic-stackframe"; 1.637 + container.setAttribute("flex", "1"); 1.638 + 1.639 + let frameTitleNode = document.createElement("label"); 1.640 + frameTitleNode.className = "plain dbg-classic-stackframe-title"; 1.641 + frameTitleNode.setAttribute("value", aTitle); 1.642 + frameTitleNode.setAttribute("crop", "center"); 1.643 + 1.644 + let frameDetailsNode = document.createElement("hbox"); 1.645 + frameDetailsNode.className = "plain dbg-classic-stackframe-details"; 1.646 + 1.647 + let frameUrlNode = document.createElement("label"); 1.648 + frameUrlNode.className = "plain dbg-classic-stackframe-details-url"; 1.649 + frameUrlNode.setAttribute("value", SourceUtils.getSourceLabel(aUrl)); 1.650 + frameUrlNode.setAttribute("crop", "center"); 1.651 + frameDetailsNode.appendChild(frameUrlNode); 1.652 + 1.653 + let frameDetailsSeparator = document.createElement("label"); 1.654 + frameDetailsSeparator.className = "plain dbg-classic-stackframe-details-sep"; 1.655 + frameDetailsSeparator.setAttribute("value", SEARCH_LINE_FLAG); 1.656 + frameDetailsNode.appendChild(frameDetailsSeparator); 1.657 + 1.658 + let frameLineNode = document.createElement("label"); 1.659 + frameLineNode.className = "plain dbg-classic-stackframe-details-line"; 1.660 + frameLineNode.setAttribute("value", aLine); 1.661 + frameDetailsNode.appendChild(frameLineNode); 1.662 + 1.663 + container.appendChild(frameTitleNode); 1.664 + container.appendChild(frameDetailsNode); 1.665 + 1.666 + return container; 1.667 + }, 1.668 + 1.669 + /** 1.670 + * The select listener for the stackframes container. 1.671 + */ 1.672 + _onSelect: function(e) { 1.673 + let stackframeItem = this.selectedItem; 1.674 + if (stackframeItem) { 1.675 + // The container is not empty and an actual item was selected. 1.676 + // Mirror the selected item in the breadcrumbs list. 1.677 + let depth = stackframeItem.attachment.depth; 1.678 + this._mirror.selectedItem = e => e.attachment.depth == depth; 1.679 + } 1.680 + }, 1.681 + 1.682 + _mirror: null 1.683 +}); 1.684 + 1.685 +/** 1.686 + * Functions handling the filtering UI. 1.687 + */ 1.688 +function FilterView() { 1.689 + dumpn("FilterView was instantiated"); 1.690 + 1.691 + this._onClick = this._onClick.bind(this); 1.692 + this._onInput = this._onInput.bind(this); 1.693 + this._onKeyPress = this._onKeyPress.bind(this); 1.694 + this._onBlur = this._onBlur.bind(this); 1.695 +} 1.696 + 1.697 +FilterView.prototype = { 1.698 + /** 1.699 + * Initialization function, called when the debugger is started. 1.700 + */ 1.701 + initialize: function() { 1.702 + dumpn("Initializing the FilterView"); 1.703 + 1.704 + this._searchbox = document.getElementById("searchbox"); 1.705 + this._searchboxHelpPanel = document.getElementById("searchbox-help-panel"); 1.706 + this._filterLabel = document.getElementById("filter-label"); 1.707 + this._globalOperatorButton = document.getElementById("global-operator-button"); 1.708 + this._globalOperatorLabel = document.getElementById("global-operator-label"); 1.709 + this._functionOperatorButton = document.getElementById("function-operator-button"); 1.710 + this._functionOperatorLabel = document.getElementById("function-operator-label"); 1.711 + this._tokenOperatorButton = document.getElementById("token-operator-button"); 1.712 + this._tokenOperatorLabel = document.getElementById("token-operator-label"); 1.713 + this._lineOperatorButton = document.getElementById("line-operator-button"); 1.714 + this._lineOperatorLabel = document.getElementById("line-operator-label"); 1.715 + this._variableOperatorButton = document.getElementById("variable-operator-button"); 1.716 + this._variableOperatorLabel = document.getElementById("variable-operator-label"); 1.717 + 1.718 + this._fileSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("fileSearchKey")); 1.719 + this._globalSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("globalSearchKey")); 1.720 + this._filteredFunctionsKey = ShortcutUtils.prettifyShortcut(document.getElementById("functionSearchKey")); 1.721 + this._tokenSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("tokenSearchKey")); 1.722 + this._lineSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("lineSearchKey")); 1.723 + this._variableSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("variableSearchKey")); 1.724 + 1.725 + this._searchbox.addEventListener("click", this._onClick, false); 1.726 + this._searchbox.addEventListener("select", this._onInput, false); 1.727 + this._searchbox.addEventListener("input", this._onInput, false); 1.728 + this._searchbox.addEventListener("keypress", this._onKeyPress, false); 1.729 + this._searchbox.addEventListener("blur", this._onBlur, false); 1.730 + 1.731 + let placeholder = L10N.getFormatStr("emptySearchText", this._fileSearchKey); 1.732 + this._searchbox.setAttribute("placeholder", placeholder); 1.733 + 1.734 + this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG); 1.735 + this._functionOperatorButton.setAttribute("label", SEARCH_FUNCTION_FLAG); 1.736 + this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG); 1.737 + this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG); 1.738 + this._variableOperatorButton.setAttribute("label", SEARCH_VARIABLE_FLAG); 1.739 + 1.740 + this._filterLabel.setAttribute("value", 1.741 + L10N.getFormatStr("searchPanelFilter", this._fileSearchKey)); 1.742 + this._globalOperatorLabel.setAttribute("value", 1.743 + L10N.getFormatStr("searchPanelGlobal", this._globalSearchKey)); 1.744 + this._functionOperatorLabel.setAttribute("value", 1.745 + L10N.getFormatStr("searchPanelFunction", this._filteredFunctionsKey)); 1.746 + this._tokenOperatorLabel.setAttribute("value", 1.747 + L10N.getFormatStr("searchPanelToken", this._tokenSearchKey)); 1.748 + this._lineOperatorLabel.setAttribute("value", 1.749 + L10N.getFormatStr("searchPanelGoToLine", this._lineSearchKey)); 1.750 + this._variableOperatorLabel.setAttribute("value", 1.751 + L10N.getFormatStr("searchPanelVariable", this._variableSearchKey)); 1.752 + }, 1.753 + 1.754 + /** 1.755 + * Destruction function, called when the debugger is closed. 1.756 + */ 1.757 + destroy: function() { 1.758 + dumpn("Destroying the FilterView"); 1.759 + 1.760 + this._searchbox.removeEventListener("click", this._onClick, false); 1.761 + this._searchbox.removeEventListener("select", this._onInput, false); 1.762 + this._searchbox.removeEventListener("input", this._onInput, false); 1.763 + this._searchbox.removeEventListener("keypress", this._onKeyPress, false); 1.764 + this._searchbox.removeEventListener("blur", this._onBlur, false); 1.765 + }, 1.766 + 1.767 + /** 1.768 + * Gets the entered operator and arguments in the searchbox. 1.769 + * @return array 1.770 + */ 1.771 + get searchData() { 1.772 + let operator = "", args = []; 1.773 + 1.774 + let rawValue = this._searchbox.value; 1.775 + let rawLength = rawValue.length; 1.776 + let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG); 1.777 + let functionFlagIndex = rawValue.indexOf(SEARCH_FUNCTION_FLAG); 1.778 + let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG); 1.779 + let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG); 1.780 + let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG); 1.781 + 1.782 + // This is not a global, function or variable search, allow file/line flags. 1.783 + if (globalFlagIndex != 0 && functionFlagIndex != 0 && variableFlagIndex != 0) { 1.784 + // Token search has precedence over line search. 1.785 + if (tokenFlagIndex != -1) { 1.786 + operator = SEARCH_TOKEN_FLAG; 1.787 + args.push(rawValue.slice(0, tokenFlagIndex)); // file 1.788 + args.push(rawValue.substr(tokenFlagIndex + 1, rawLength)); // token 1.789 + } else if (lineFlagIndex != -1) { 1.790 + operator = SEARCH_LINE_FLAG; 1.791 + args.push(rawValue.slice(0, lineFlagIndex)); // file 1.792 + args.push(+rawValue.substr(lineFlagIndex + 1, rawLength) || 0); // line 1.793 + } else { 1.794 + args.push(rawValue); 1.795 + } 1.796 + } 1.797 + // Global searches dissalow the use of file or line flags. 1.798 + else if (globalFlagIndex == 0) { 1.799 + operator = SEARCH_GLOBAL_FLAG; 1.800 + args.push(rawValue.slice(1)); 1.801 + } 1.802 + // Function searches dissalow the use of file or line flags. 1.803 + else if (functionFlagIndex == 0) { 1.804 + operator = SEARCH_FUNCTION_FLAG; 1.805 + args.push(rawValue.slice(1)); 1.806 + } 1.807 + // Variable searches dissalow the use of file or line flags. 1.808 + else if (variableFlagIndex == 0) { 1.809 + operator = SEARCH_VARIABLE_FLAG; 1.810 + args.push(rawValue.slice(1)); 1.811 + } 1.812 + 1.813 + return [operator, args]; 1.814 + }, 1.815 + 1.816 + /** 1.817 + * Returns the current search operator. 1.818 + * @return string 1.819 + */ 1.820 + get searchOperator() this.searchData[0], 1.821 + 1.822 + /** 1.823 + * Returns the current search arguments. 1.824 + * @return array 1.825 + */ 1.826 + get searchArguments() this.searchData[1], 1.827 + 1.828 + /** 1.829 + * Clears the text from the searchbox and any changed views. 1.830 + */ 1.831 + clearSearch: function() { 1.832 + this._searchbox.value = ""; 1.833 + this.clearViews(); 1.834 + }, 1.835 + 1.836 + /** 1.837 + * Clears all the views that may pop up when searching. 1.838 + */ 1.839 + clearViews: function() { 1.840 + DebuggerView.GlobalSearch.clearView(); 1.841 + DebuggerView.FilteredSources.clearView(); 1.842 + DebuggerView.FilteredFunctions.clearView(); 1.843 + this._searchboxHelpPanel.hidePopup(); 1.844 + }, 1.845 + 1.846 + /** 1.847 + * Performs a line search if necessary. 1.848 + * (Jump to lines in the currently visible source). 1.849 + * 1.850 + * @param number aLine 1.851 + * The source line number to jump to. 1.852 + */ 1.853 + _performLineSearch: function(aLine) { 1.854 + // Make sure we're actually searching for a valid line. 1.855 + if (aLine) { 1.856 + DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 }, "center"); 1.857 + } 1.858 + }, 1.859 + 1.860 + /** 1.861 + * Performs a token search if necessary. 1.862 + * (Search for tokens in the currently visible source). 1.863 + * 1.864 + * @param string aToken 1.865 + * The source token to find. 1.866 + */ 1.867 + _performTokenSearch: function(aToken) { 1.868 + // Make sure we're actually searching for a valid token. 1.869 + if (!aToken) { 1.870 + return; 1.871 + } 1.872 + DebuggerView.editor.find(aToken); 1.873 + }, 1.874 + 1.875 + /** 1.876 + * The click listener for the search container. 1.877 + */ 1.878 + _onClick: function() { 1.879 + // If there's some text in the searchbox, displaying a panel would 1.880 + // interfere with double/triple click default behaviors. 1.881 + if (!this._searchbox.value) { 1.882 + this._searchboxHelpPanel.openPopup(this._searchbox); 1.883 + } 1.884 + }, 1.885 + 1.886 + /** 1.887 + * The input listener for the search container. 1.888 + */ 1.889 + _onInput: function() { 1.890 + this.clearViews(); 1.891 + 1.892 + // Make sure we're actually searching for something. 1.893 + if (!this._searchbox.value) { 1.894 + return; 1.895 + } 1.896 + 1.897 + // Perform the required search based on the specified operator. 1.898 + switch (this.searchOperator) { 1.899 + case SEARCH_GLOBAL_FLAG: 1.900 + // Schedule a global search for when the user stops typing. 1.901 + DebuggerView.GlobalSearch.scheduleSearch(this.searchArguments[0]); 1.902 + break; 1.903 + case SEARCH_FUNCTION_FLAG: 1.904 + // Schedule a function search for when the user stops typing. 1.905 + DebuggerView.FilteredFunctions.scheduleSearch(this.searchArguments[0]); 1.906 + break; 1.907 + case SEARCH_VARIABLE_FLAG: 1.908 + // Schedule a variable search for when the user stops typing. 1.909 + DebuggerView.Variables.scheduleSearch(this.searchArguments[0]); 1.910 + break; 1.911 + case SEARCH_TOKEN_FLAG: 1.912 + // Schedule a file+token search for when the user stops typing. 1.913 + DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]); 1.914 + this._performTokenSearch(this.searchArguments[1]); 1.915 + break; 1.916 + case SEARCH_LINE_FLAG: 1.917 + // Schedule a file+line search for when the user stops typing. 1.918 + DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]); 1.919 + this._performLineSearch(this.searchArguments[1]); 1.920 + break; 1.921 + default: 1.922 + // Schedule a file only search for when the user stops typing. 1.923 + DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]); 1.924 + break; 1.925 + } 1.926 + }, 1.927 + 1.928 + /** 1.929 + * The key press listener for the search container. 1.930 + */ 1.931 + _onKeyPress: function(e) { 1.932 + // This attribute is not implemented in Gecko at this time, see bug 680830. 1.933 + e.char = String.fromCharCode(e.charCode); 1.934 + 1.935 + // Perform the required action based on the specified operator. 1.936 + let [operator, args] = this.searchData; 1.937 + let isGlobalSearch = operator == SEARCH_GLOBAL_FLAG; 1.938 + let isFunctionSearch = operator == SEARCH_FUNCTION_FLAG; 1.939 + let isVariableSearch = operator == SEARCH_VARIABLE_FLAG; 1.940 + let isTokenSearch = operator == SEARCH_TOKEN_FLAG; 1.941 + let isLineSearch = operator == SEARCH_LINE_FLAG; 1.942 + let isFileOnlySearch = !operator && args.length == 1; 1.943 + 1.944 + // Depending on the pressed keys, determine to correct action to perform. 1.945 + let actionToPerform; 1.946 + 1.947 + // Meta+G and Ctrl+N focus next matches. 1.948 + if ((e.char == "g" && e.metaKey) || e.char == "n" && e.ctrlKey) { 1.949 + actionToPerform = "selectNext"; 1.950 + } 1.951 + // Meta+Shift+G and Ctrl+P focus previous matches. 1.952 + else if ((e.char == "G" && e.metaKey) || e.char == "p" && e.ctrlKey) { 1.953 + actionToPerform = "selectPrev"; 1.954 + } 1.955 + // Return, enter, down and up keys focus next or previous matches, while 1.956 + // the escape key switches focus from the search container. 1.957 + else switch (e.keyCode) { 1.958 + case e.DOM_VK_RETURN: 1.959 + var isReturnKey = true; 1.960 + // If the shift key is pressed, focus on the previous result 1.961 + actionToPerform = e.shiftKey ? "selectPrev" : "selectNext"; 1.962 + break; 1.963 + case e.DOM_VK_DOWN: 1.964 + actionToPerform = "selectNext"; 1.965 + break; 1.966 + case e.DOM_VK_UP: 1.967 + actionToPerform = "selectPrev"; 1.968 + break; 1.969 + } 1.970 + 1.971 + // If there's no action to perform, or no operator, file line or token 1.972 + // were specified, then this is either a broken or empty search. 1.973 + if (!actionToPerform || (!operator && !args.length)) { 1.974 + DebuggerView.editor.dropSelection(); 1.975 + return; 1.976 + } 1.977 + 1.978 + e.preventDefault(); 1.979 + e.stopPropagation(); 1.980 + 1.981 + // Jump to the next/previous entry in the global search, or perform 1.982 + // a new global search immediately 1.983 + if (isGlobalSearch) { 1.984 + let targetView = DebuggerView.GlobalSearch; 1.985 + if (!isReturnKey) { 1.986 + targetView[actionToPerform](); 1.987 + } else if (targetView.hidden) { 1.988 + targetView.scheduleSearch(args[0], 0); 1.989 + } 1.990 + return; 1.991 + } 1.992 + 1.993 + // Jump to the next/previous entry in the function search, perform 1.994 + // a new function search immediately, or clear it. 1.995 + if (isFunctionSearch) { 1.996 + let targetView = DebuggerView.FilteredFunctions; 1.997 + if (!isReturnKey) { 1.998 + targetView[actionToPerform](); 1.999 + } else if (targetView.hidden) { 1.1000 + targetView.scheduleSearch(args[0], 0); 1.1001 + } else { 1.1002 + if (!targetView.selectedItem) { 1.1003 + targetView.selectedIndex = 0; 1.1004 + } 1.1005 + this.clearSearch(); 1.1006 + } 1.1007 + return; 1.1008 + } 1.1009 + 1.1010 + // Perform a new variable search immediately. 1.1011 + if (isVariableSearch) { 1.1012 + let targetView = DebuggerView.Variables; 1.1013 + if (isReturnKey) { 1.1014 + targetView.scheduleSearch(args[0], 0); 1.1015 + } 1.1016 + return; 1.1017 + } 1.1018 + 1.1019 + // Jump to the next/previous entry in the file search, perform 1.1020 + // a new file search immediately, or clear it. 1.1021 + if (isFileOnlySearch) { 1.1022 + let targetView = DebuggerView.FilteredSources; 1.1023 + if (!isReturnKey) { 1.1024 + targetView[actionToPerform](); 1.1025 + } else if (targetView.hidden) { 1.1026 + targetView.scheduleSearch(args[0], 0); 1.1027 + } else { 1.1028 + if (!targetView.selectedItem) { 1.1029 + targetView.selectedIndex = 0; 1.1030 + } 1.1031 + this.clearSearch(); 1.1032 + } 1.1033 + return; 1.1034 + } 1.1035 + 1.1036 + // Jump to the next/previous instance of the currently searched token. 1.1037 + if (isTokenSearch) { 1.1038 + let methods = { selectNext: "findNext", selectPrev: "findPrev" }; 1.1039 + DebuggerView.editor[methods[actionToPerform]](); 1.1040 + return; 1.1041 + } 1.1042 + 1.1043 + // Increment/decrement the currently searched caret line. 1.1044 + if (isLineSearch) { 1.1045 + let [, line] = args; 1.1046 + let amounts = { selectNext: 1, selectPrev: -1 }; 1.1047 + 1.1048 + // Modify the line number and jump to it. 1.1049 + line += !isReturnKey ? amounts[actionToPerform] : 0; 1.1050 + let lineCount = DebuggerView.editor.lineCount(); 1.1051 + let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line; 1.1052 + this._doSearch(SEARCH_LINE_FLAG, lineTarget); 1.1053 + return; 1.1054 + } 1.1055 + }, 1.1056 + 1.1057 + /** 1.1058 + * The blur listener for the search container. 1.1059 + */ 1.1060 + _onBlur: function() { 1.1061 + this.clearViews(); 1.1062 + }, 1.1063 + 1.1064 + /** 1.1065 + * Called when a filtering key sequence was pressed. 1.1066 + * 1.1067 + * @param string aOperator 1.1068 + * The operator to use for filtering. 1.1069 + */ 1.1070 + _doSearch: function(aOperator = "", aText = "") { 1.1071 + this._searchbox.focus(); 1.1072 + this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738. 1.1073 + 1.1074 + if (aText) { 1.1075 + this._searchbox.value = aOperator + aText; 1.1076 + return; 1.1077 + } 1.1078 + if (DebuggerView.editor.somethingSelected()) { 1.1079 + this._searchbox.value = aOperator + DebuggerView.editor.getSelection(); 1.1080 + return; 1.1081 + } 1.1082 + if (SEARCH_AUTOFILL.indexOf(aOperator) != -1) { 1.1083 + let cursor = DebuggerView.editor.getCursor(); 1.1084 + let content = DebuggerView.editor.getText(); 1.1085 + let location = DebuggerView.Sources.selectedValue; 1.1086 + let source = DebuggerController.Parser.get(content, location); 1.1087 + let identifier = source.getIdentifierAt({ line: cursor.line+1, column: cursor.ch }); 1.1088 + 1.1089 + if (identifier && identifier.name) { 1.1090 + this._searchbox.value = aOperator + identifier.name; 1.1091 + this._searchbox.select(); 1.1092 + this._searchbox.selectionStart += aOperator.length; 1.1093 + return; 1.1094 + } 1.1095 + } 1.1096 + this._searchbox.value = aOperator; 1.1097 + }, 1.1098 + 1.1099 + /** 1.1100 + * Called when the source location filter key sequence was pressed. 1.1101 + */ 1.1102 + _doFileSearch: function() { 1.1103 + this._doSearch(); 1.1104 + this._searchboxHelpPanel.openPopup(this._searchbox); 1.1105 + }, 1.1106 + 1.1107 + /** 1.1108 + * Called when the global search filter key sequence was pressed. 1.1109 + */ 1.1110 + _doGlobalSearch: function() { 1.1111 + this._doSearch(SEARCH_GLOBAL_FLAG); 1.1112 + this._searchboxHelpPanel.hidePopup(); 1.1113 + }, 1.1114 + 1.1115 + /** 1.1116 + * Called when the source function filter key sequence was pressed. 1.1117 + */ 1.1118 + _doFunctionSearch: function() { 1.1119 + this._doSearch(SEARCH_FUNCTION_FLAG); 1.1120 + this._searchboxHelpPanel.hidePopup(); 1.1121 + }, 1.1122 + 1.1123 + /** 1.1124 + * Called when the source token filter key sequence was pressed. 1.1125 + */ 1.1126 + _doTokenSearch: function() { 1.1127 + this._doSearch(SEARCH_TOKEN_FLAG); 1.1128 + this._searchboxHelpPanel.hidePopup(); 1.1129 + }, 1.1130 + 1.1131 + /** 1.1132 + * Called when the source line filter key sequence was pressed. 1.1133 + */ 1.1134 + _doLineSearch: function() { 1.1135 + this._doSearch(SEARCH_LINE_FLAG); 1.1136 + this._searchboxHelpPanel.hidePopup(); 1.1137 + }, 1.1138 + 1.1139 + /** 1.1140 + * Called when the variable search filter key sequence was pressed. 1.1141 + */ 1.1142 + _doVariableSearch: function() { 1.1143 + this._doSearch(SEARCH_VARIABLE_FLAG); 1.1144 + this._searchboxHelpPanel.hidePopup(); 1.1145 + }, 1.1146 + 1.1147 + /** 1.1148 + * Called when the variables focus key sequence was pressed. 1.1149 + */ 1.1150 + _doVariablesFocus: function() { 1.1151 + DebuggerView.showInstrumentsPane(); 1.1152 + DebuggerView.Variables.focusFirstVisibleItem(); 1.1153 + }, 1.1154 + 1.1155 + _searchbox: null, 1.1156 + _searchboxHelpPanel: null, 1.1157 + _globalOperatorButton: null, 1.1158 + _globalOperatorLabel: null, 1.1159 + _functionOperatorButton: null, 1.1160 + _functionOperatorLabel: null, 1.1161 + _tokenOperatorButton: null, 1.1162 + _tokenOperatorLabel: null, 1.1163 + _lineOperatorButton: null, 1.1164 + _lineOperatorLabel: null, 1.1165 + _variableOperatorButton: null, 1.1166 + _variableOperatorLabel: null, 1.1167 + _fileSearchKey: "", 1.1168 + _globalSearchKey: "", 1.1169 + _filteredFunctionsKey: "", 1.1170 + _tokenSearchKey: "", 1.1171 + _lineSearchKey: "", 1.1172 + _variableSearchKey: "", 1.1173 +}; 1.1174 + 1.1175 +/** 1.1176 + * Functions handling the filtered sources UI. 1.1177 + */ 1.1178 +function FilteredSourcesView() { 1.1179 + dumpn("FilteredSourcesView was instantiated"); 1.1180 + 1.1181 + this._onClick = this._onClick.bind(this); 1.1182 + this._onSelect = this._onSelect.bind(this); 1.1183 +} 1.1184 + 1.1185 +FilteredSourcesView.prototype = Heritage.extend(ResultsPanelContainer.prototype, { 1.1186 + /** 1.1187 + * Initialization function, called when the debugger is started. 1.1188 + */ 1.1189 + initialize: function() { 1.1190 + dumpn("Initializing the FilteredSourcesView"); 1.1191 + 1.1192 + this.anchor = document.getElementById("searchbox"); 1.1193 + this.widget.addEventListener("select", this._onSelect, false); 1.1194 + this.widget.addEventListener("click", this._onClick, false); 1.1195 + }, 1.1196 + 1.1197 + /** 1.1198 + * Destruction function, called when the debugger is closed. 1.1199 + */ 1.1200 + destroy: function() { 1.1201 + dumpn("Destroying the FilteredSourcesView"); 1.1202 + 1.1203 + this.widget.removeEventListener("select", this._onSelect, false); 1.1204 + this.widget.removeEventListener("click", this._onClick, false); 1.1205 + this.anchor = null; 1.1206 + }, 1.1207 + 1.1208 + /** 1.1209 + * Schedules searching for a source. 1.1210 + * 1.1211 + * @param string aToken 1.1212 + * The function to search for. 1.1213 + * @param number aWait 1.1214 + * The amount of milliseconds to wait until draining. 1.1215 + */ 1.1216 + scheduleSearch: function(aToken, aWait) { 1.1217 + // The amount of time to wait for the requests to settle. 1.1218 + let maxDelay = FILE_SEARCH_ACTION_MAX_DELAY; 1.1219 + let delay = aWait === undefined ? maxDelay / aToken.length : aWait; 1.1220 + 1.1221 + // Allow requests to settle down first. 1.1222 + setNamedTimeout("sources-search", delay, () => this._doSearch(aToken)); 1.1223 + }, 1.1224 + 1.1225 + /** 1.1226 + * Finds file matches in all the displayed sources. 1.1227 + * 1.1228 + * @param string aToken 1.1229 + * The string to search for. 1.1230 + */ 1.1231 + _doSearch: function(aToken, aStore = []) { 1.1232 + // Don't continue filtering if the searched token is an empty string. 1.1233 + // In contrast with function searching, in this case we don't want to 1.1234 + // show a list of all the files when no search token was supplied. 1.1235 + if (!aToken) { 1.1236 + return; 1.1237 + } 1.1238 + 1.1239 + for (let item of DebuggerView.Sources.items) { 1.1240 + let lowerCaseLabel = item.attachment.label.toLowerCase(); 1.1241 + let lowerCaseToken = aToken.toLowerCase(); 1.1242 + if (lowerCaseLabel.match(lowerCaseToken)) { 1.1243 + aStore.push(item); 1.1244 + } 1.1245 + 1.1246 + // Once the maximum allowed number of results is reached, proceed 1.1247 + // with building the UI immediately. 1.1248 + if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) { 1.1249 + this._syncView(aStore); 1.1250 + return; 1.1251 + } 1.1252 + } 1.1253 + 1.1254 + // Couldn't reach the maximum allowed number of results, but that's ok, 1.1255 + // continue building the UI. 1.1256 + this._syncView(aStore); 1.1257 + }, 1.1258 + 1.1259 + /** 1.1260 + * Updates the list of sources displayed in this container. 1.1261 + * 1.1262 + * @param array aSearchResults 1.1263 + * The results array, containing search details for each source. 1.1264 + */ 1.1265 + _syncView: function(aSearchResults) { 1.1266 + // If there are no matches found, keep the popup hidden and avoid 1.1267 + // creating the view. 1.1268 + if (!aSearchResults.length) { 1.1269 + window.emit(EVENTS.FILE_SEARCH_MATCH_NOT_FOUND); 1.1270 + return; 1.1271 + } 1.1272 + 1.1273 + for (let item of aSearchResults) { 1.1274 + // Create the element node for the location item. 1.1275 + let itemView = this._createItemView( 1.1276 + SourceUtils.trimUrlLength(item.attachment.label), 1.1277 + SourceUtils.trimUrlLength(item.value, 0, "start") 1.1278 + ); 1.1279 + 1.1280 + // Append a location item to this container for each match. 1.1281 + this.push([itemView], { 1.1282 + index: -1, /* specifies on which position should the item be appended */ 1.1283 + attachment: { 1.1284 + url: item.value 1.1285 + } 1.1286 + }); 1.1287 + } 1.1288 + 1.1289 + // There's at least one item displayed in this container. Don't select it 1.1290 + // automatically if not forced (by tests) or in tandem with an operator. 1.1291 + if (this._autoSelectFirstItem || DebuggerView.Filtering.searchOperator) { 1.1292 + this.selectedIndex = 0; 1.1293 + } 1.1294 + this.hidden = false; 1.1295 + 1.1296 + // Signal that file search matches were found and displayed. 1.1297 + window.emit(EVENTS.FILE_SEARCH_MATCH_FOUND); 1.1298 + }, 1.1299 + 1.1300 + /** 1.1301 + * The click listener for this container. 1.1302 + */ 1.1303 + _onClick: function(e) { 1.1304 + let locationItem = this.getItemForElement(e.target); 1.1305 + if (locationItem) { 1.1306 + this.selectedItem = locationItem; 1.1307 + DebuggerView.Filtering.clearSearch(); 1.1308 + } 1.1309 + }, 1.1310 + 1.1311 + /** 1.1312 + * The select listener for this container. 1.1313 + * 1.1314 + * @param object aItem 1.1315 + * The item associated with the element to select. 1.1316 + */ 1.1317 + _onSelect: function({ detail: locationItem }) { 1.1318 + if (locationItem) { 1.1319 + DebuggerView.setEditorLocation(locationItem.attachment.url, undefined, { 1.1320 + noCaret: true, 1.1321 + noDebug: true 1.1322 + }); 1.1323 + } 1.1324 + } 1.1325 +}); 1.1326 + 1.1327 +/** 1.1328 + * Functions handling the function search UI. 1.1329 + */ 1.1330 +function FilteredFunctionsView() { 1.1331 + dumpn("FilteredFunctionsView was instantiated"); 1.1332 + 1.1333 + this._onClick = this._onClick.bind(this); 1.1334 + this._onSelect = this._onSelect.bind(this); 1.1335 +} 1.1336 + 1.1337 +FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototype, { 1.1338 + /** 1.1339 + * Initialization function, called when the debugger is started. 1.1340 + */ 1.1341 + initialize: function() { 1.1342 + dumpn("Initializing the FilteredFunctionsView"); 1.1343 + 1.1344 + this.anchor = document.getElementById("searchbox"); 1.1345 + this.widget.addEventListener("select", this._onSelect, false); 1.1346 + this.widget.addEventListener("click", this._onClick, false); 1.1347 + }, 1.1348 + 1.1349 + /** 1.1350 + * Destruction function, called when the debugger is closed. 1.1351 + */ 1.1352 + destroy: function() { 1.1353 + dumpn("Destroying the FilteredFunctionsView"); 1.1354 + 1.1355 + this.widget.removeEventListener("select", this._onSelect, false); 1.1356 + this.widget.removeEventListener("click", this._onClick, false); 1.1357 + this.anchor = null; 1.1358 + }, 1.1359 + 1.1360 + /** 1.1361 + * Schedules searching for a function in all of the sources. 1.1362 + * 1.1363 + * @param string aToken 1.1364 + * The function to search for. 1.1365 + * @param number aWait 1.1366 + * The amount of milliseconds to wait until draining. 1.1367 + */ 1.1368 + scheduleSearch: function(aToken, aWait) { 1.1369 + // The amount of time to wait for the requests to settle. 1.1370 + let maxDelay = FUNCTION_SEARCH_ACTION_MAX_DELAY; 1.1371 + let delay = aWait === undefined ? maxDelay / aToken.length : aWait; 1.1372 + 1.1373 + // Allow requests to settle down first. 1.1374 + setNamedTimeout("function-search", delay, () => { 1.1375 + // Start fetching as many sources as possible, then perform the search. 1.1376 + let urls = DebuggerView.Sources.values; 1.1377 + let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(urls); 1.1378 + sourcesFetched.then(aSources => this._doSearch(aToken, aSources)); 1.1379 + }); 1.1380 + }, 1.1381 + 1.1382 + /** 1.1383 + * Finds function matches in all the sources stored in the cache, and groups 1.1384 + * them by location and line number. 1.1385 + * 1.1386 + * @param string aToken 1.1387 + * The string to search for. 1.1388 + * @param array aSources 1.1389 + * An array of [url, text] tuples for each source. 1.1390 + */ 1.1391 + _doSearch: function(aToken, aSources, aStore = []) { 1.1392 + // Continue parsing even if the searched token is an empty string, to 1.1393 + // cache the syntax tree nodes generated by the reflection API. 1.1394 + 1.1395 + // Make sure the currently displayed source is parsed first. Once the 1.1396 + // maximum allowed number of results are found, parsing will be halted. 1.1397 + let currentUrl = DebuggerView.Sources.selectedValue; 1.1398 + let currentSource = aSources.filter(([sourceUrl]) => sourceUrl == currentUrl)[0]; 1.1399 + aSources.splice(aSources.indexOf(currentSource), 1); 1.1400 + aSources.unshift(currentSource); 1.1401 + 1.1402 + // If not searching for a specific function, only parse the displayed source, 1.1403 + // which is now the first item in the sources array. 1.1404 + if (!aToken) { 1.1405 + aSources.splice(1); 1.1406 + } 1.1407 + 1.1408 + for (let [location, contents] of aSources) { 1.1409 + let parsedSource = DebuggerController.Parser.get(contents, location); 1.1410 + let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken); 1.1411 + 1.1412 + for (let scriptResult of sourceResults) { 1.1413 + for (let parseResult of scriptResult) { 1.1414 + aStore.push({ 1.1415 + sourceUrl: scriptResult.sourceUrl, 1.1416 + scriptOffset: scriptResult.scriptOffset, 1.1417 + functionName: parseResult.functionName, 1.1418 + functionLocation: parseResult.functionLocation, 1.1419 + inferredName: parseResult.inferredName, 1.1420 + inferredChain: parseResult.inferredChain, 1.1421 + inferredLocation: parseResult.inferredLocation 1.1422 + }); 1.1423 + 1.1424 + // Once the maximum allowed number of results is reached, proceed 1.1425 + // with building the UI immediately. 1.1426 + if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) { 1.1427 + this._syncView(aStore); 1.1428 + return; 1.1429 + } 1.1430 + } 1.1431 + } 1.1432 + } 1.1433 + 1.1434 + // Couldn't reach the maximum allowed number of results, but that's ok, 1.1435 + // continue building the UI. 1.1436 + this._syncView(aStore); 1.1437 + }, 1.1438 + 1.1439 + /** 1.1440 + * Updates the list of functions displayed in this container. 1.1441 + * 1.1442 + * @param array aSearchResults 1.1443 + * The results array, containing search details for each source. 1.1444 + */ 1.1445 + _syncView: function(aSearchResults) { 1.1446 + // If there are no matches found, keep the popup hidden and avoid 1.1447 + // creating the view. 1.1448 + if (!aSearchResults.length) { 1.1449 + window.emit(EVENTS.FUNCTION_SEARCH_MATCH_NOT_FOUND); 1.1450 + return; 1.1451 + } 1.1452 + 1.1453 + for (let item of aSearchResults) { 1.1454 + // Some function expressions don't necessarily have a name, but the 1.1455 + // parser provides us with an inferred name from an enclosing 1.1456 + // VariableDeclarator, AssignmentExpression, ObjectExpression node. 1.1457 + if (item.functionName && item.inferredName && 1.1458 + item.functionName != item.inferredName) { 1.1459 + let s = " " + L10N.getStr("functionSearchSeparatorLabel") + " "; 1.1460 + item.displayedName = item.inferredName + s + item.functionName; 1.1461 + } 1.1462 + // The function doesn't have an explicit name, but it could be inferred. 1.1463 + else if (item.inferredName) { 1.1464 + item.displayedName = item.inferredName; 1.1465 + } 1.1466 + // The function only has an explicit name. 1.1467 + else { 1.1468 + item.displayedName = item.functionName; 1.1469 + } 1.1470 + 1.1471 + // Some function expressions have unexpected bounds, since they may not 1.1472 + // necessarily have an associated name defining them. 1.1473 + if (item.inferredLocation) { 1.1474 + item.actualLocation = item.inferredLocation; 1.1475 + } else { 1.1476 + item.actualLocation = item.functionLocation; 1.1477 + } 1.1478 + 1.1479 + // Create the element node for the function item. 1.1480 + let itemView = this._createItemView( 1.1481 + SourceUtils.trimUrlLength(item.displayedName + "()"), 1.1482 + SourceUtils.trimUrlLength(item.sourceUrl, 0, "start"), 1.1483 + (item.inferredChain || []).join(".") 1.1484 + ); 1.1485 + 1.1486 + // Append a function item to this container for each match. 1.1487 + this.push([itemView], { 1.1488 + index: -1, /* specifies on which position should the item be appended */ 1.1489 + attachment: item 1.1490 + }); 1.1491 + } 1.1492 + 1.1493 + // There's at least one item displayed in this container. Don't select it 1.1494 + // automatically if not forced (by tests). 1.1495 + if (this._autoSelectFirstItem) { 1.1496 + this.selectedIndex = 0; 1.1497 + } 1.1498 + this.hidden = false; 1.1499 + 1.1500 + // Signal that function search matches were found and displayed. 1.1501 + window.emit(EVENTS.FUNCTION_SEARCH_MATCH_FOUND); 1.1502 + }, 1.1503 + 1.1504 + /** 1.1505 + * The click listener for this container. 1.1506 + */ 1.1507 + _onClick: function(e) { 1.1508 + let functionItem = this.getItemForElement(e.target); 1.1509 + if (functionItem) { 1.1510 + this.selectedItem = functionItem; 1.1511 + DebuggerView.Filtering.clearSearch(); 1.1512 + } 1.1513 + }, 1.1514 + 1.1515 + /** 1.1516 + * The select listener for this container. 1.1517 + */ 1.1518 + _onSelect: function({ detail: functionItem }) { 1.1519 + if (functionItem) { 1.1520 + let sourceUrl = functionItem.attachment.sourceUrl; 1.1521 + let scriptOffset = functionItem.attachment.scriptOffset; 1.1522 + let actualLocation = functionItem.attachment.actualLocation; 1.1523 + 1.1524 + DebuggerView.setEditorLocation(sourceUrl, actualLocation.start.line, { 1.1525 + charOffset: scriptOffset, 1.1526 + columnOffset: actualLocation.start.column, 1.1527 + align: "center", 1.1528 + noDebug: true 1.1529 + }); 1.1530 + } 1.1531 + }, 1.1532 + 1.1533 + _searchTimeout: null, 1.1534 + _searchFunction: null, 1.1535 + _searchedToken: "" 1.1536 +}); 1.1537 + 1.1538 +/** 1.1539 + * Preliminary setup for the DebuggerView object. 1.1540 + */ 1.1541 +DebuggerView.Toolbar = new ToolbarView(); 1.1542 +DebuggerView.Options = new OptionsView(); 1.1543 +DebuggerView.Filtering = new FilterView(); 1.1544 +DebuggerView.FilteredSources = new FilteredSourcesView(); 1.1545 +DebuggerView.FilteredFunctions = new FilteredFunctionsView(); 1.1546 +DebuggerView.StackFrames = new StackFramesView(); 1.1547 +DebuggerView.StackFramesClassicList = new StackFramesClassicListView();