Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | "use strict"; |
michael@0 | 7 | |
michael@0 | 8 | // A time interval sufficient for the options popup panel to finish hiding |
michael@0 | 9 | // itself. |
michael@0 | 10 | const POPUP_HIDDEN_DELAY = 100; // ms |
michael@0 | 11 | |
michael@0 | 12 | /** |
michael@0 | 13 | * Functions handling the toolbar view: close button, expand/collapse button, |
michael@0 | 14 | * pause/resume and stepping buttons etc. |
michael@0 | 15 | */ |
michael@0 | 16 | function ToolbarView() { |
michael@0 | 17 | dumpn("ToolbarView was instantiated"); |
michael@0 | 18 | |
michael@0 | 19 | this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this); |
michael@0 | 20 | this._onResumePressed = this._onResumePressed.bind(this); |
michael@0 | 21 | this._onStepOverPressed = this._onStepOverPressed.bind(this); |
michael@0 | 22 | this._onStepInPressed = this._onStepInPressed.bind(this); |
michael@0 | 23 | this._onStepOutPressed = this._onStepOutPressed.bind(this); |
michael@0 | 24 | } |
michael@0 | 25 | |
michael@0 | 26 | ToolbarView.prototype = { |
michael@0 | 27 | /** |
michael@0 | 28 | * Initialization function, called when the debugger is started. |
michael@0 | 29 | */ |
michael@0 | 30 | initialize: function() { |
michael@0 | 31 | dumpn("Initializing the ToolbarView"); |
michael@0 | 32 | |
michael@0 | 33 | this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle"); |
michael@0 | 34 | this._resumeButton = document.getElementById("resume"); |
michael@0 | 35 | this._stepOverButton = document.getElementById("step-over"); |
michael@0 | 36 | this._stepInButton = document.getElementById("step-in"); |
michael@0 | 37 | this._stepOutButton = document.getElementById("step-out"); |
michael@0 | 38 | this._resumeOrderTooltip = new Tooltip(document); |
michael@0 | 39 | this._resumeOrderTooltip.defaultPosition = TOOLBAR_ORDER_POPUP_POSITION; |
michael@0 | 40 | |
michael@0 | 41 | let resumeKey = ShortcutUtils.prettifyShortcut(document.getElementById("resumeKey")); |
michael@0 | 42 | let stepOverKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOverKey")); |
michael@0 | 43 | let stepInKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepInKey")); |
michael@0 | 44 | let stepOutKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOutKey")); |
michael@0 | 45 | this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey); |
michael@0 | 46 | this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey); |
michael@0 | 47 | this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey); |
michael@0 | 48 | this._stepInTooltip = L10N.getFormatStr("stepInTooltip", stepInKey); |
michael@0 | 49 | this._stepOutTooltip = L10N.getFormatStr("stepOutTooltip", stepOutKey); |
michael@0 | 50 | |
michael@0 | 51 | this._instrumentsPaneToggleButton.addEventListener("mousedown", this._onTogglePanesPressed, false); |
michael@0 | 52 | this._resumeButton.addEventListener("mousedown", this._onResumePressed, false); |
michael@0 | 53 | this._stepOverButton.addEventListener("mousedown", this._onStepOverPressed, false); |
michael@0 | 54 | this._stepInButton.addEventListener("mousedown", this._onStepInPressed, false); |
michael@0 | 55 | this._stepOutButton.addEventListener("mousedown", this._onStepOutPressed, false); |
michael@0 | 56 | |
michael@0 | 57 | this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip); |
michael@0 | 58 | this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip); |
michael@0 | 59 | this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip); |
michael@0 | 60 | }, |
michael@0 | 61 | |
michael@0 | 62 | /** |
michael@0 | 63 | * Destruction function, called when the debugger is closed. |
michael@0 | 64 | */ |
michael@0 | 65 | destroy: function() { |
michael@0 | 66 | dumpn("Destroying the ToolbarView"); |
michael@0 | 67 | |
michael@0 | 68 | this._instrumentsPaneToggleButton.removeEventListener("mousedown", this._onTogglePanesPressed, false); |
michael@0 | 69 | this._resumeButton.removeEventListener("mousedown", this._onResumePressed, false); |
michael@0 | 70 | this._stepOverButton.removeEventListener("mousedown", this._onStepOverPressed, false); |
michael@0 | 71 | this._stepInButton.removeEventListener("mousedown", this._onStepInPressed, false); |
michael@0 | 72 | this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false); |
michael@0 | 73 | }, |
michael@0 | 74 | |
michael@0 | 75 | /** |
michael@0 | 76 | * Display a warning when trying to resume a debuggee while another is paused. |
michael@0 | 77 | * Debuggees must be unpaused in a Last-In-First-Out order. |
michael@0 | 78 | * |
michael@0 | 79 | * @param string aPausedUrl |
michael@0 | 80 | * The URL of the last paused debuggee. |
michael@0 | 81 | */ |
michael@0 | 82 | showResumeWarning: function(aPausedUrl) { |
michael@0 | 83 | let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl); |
michael@0 | 84 | let defaultStyle = "default-tooltip-simple-text-colors"; |
michael@0 | 85 | this._resumeOrderTooltip.setTextContent({ messages: [label], isAlertTooltip: true }); |
michael@0 | 86 | this._resumeOrderTooltip.show(this._resumeButton); |
michael@0 | 87 | }, |
michael@0 | 88 | |
michael@0 | 89 | /** |
michael@0 | 90 | * Sets the resume button state based on the debugger active thread. |
michael@0 | 91 | * |
michael@0 | 92 | * @param string aState |
michael@0 | 93 | * Either "paused" or "attached". |
michael@0 | 94 | */ |
michael@0 | 95 | toggleResumeButtonState: function(aState) { |
michael@0 | 96 | // If we're paused, check and show a resume label on the button. |
michael@0 | 97 | if (aState == "paused") { |
michael@0 | 98 | this._resumeButton.setAttribute("checked", "true"); |
michael@0 | 99 | this._resumeButton.setAttribute("tooltiptext", this._resumeTooltip); |
michael@0 | 100 | } |
michael@0 | 101 | // If we're attached, do the opposite. |
michael@0 | 102 | else if (aState == "attached") { |
michael@0 | 103 | this._resumeButton.removeAttribute("checked"); |
michael@0 | 104 | this._resumeButton.setAttribute("tooltiptext", this._pauseTooltip); |
michael@0 | 105 | } |
michael@0 | 106 | }, |
michael@0 | 107 | |
michael@0 | 108 | /** |
michael@0 | 109 | * Listener handling the toggle button click event. |
michael@0 | 110 | */ |
michael@0 | 111 | _onTogglePanesPressed: function() { |
michael@0 | 112 | DebuggerView.toggleInstrumentsPane({ |
michael@0 | 113 | visible: DebuggerView.instrumentsPaneHidden, |
michael@0 | 114 | animated: true, |
michael@0 | 115 | delayed: true |
michael@0 | 116 | }); |
michael@0 | 117 | }, |
michael@0 | 118 | |
michael@0 | 119 | /** |
michael@0 | 120 | * Listener handling the pause/resume button click event. |
michael@0 | 121 | */ |
michael@0 | 122 | _onResumePressed: function() { |
michael@0 | 123 | if (DebuggerController.activeThread.paused) { |
michael@0 | 124 | let warn = DebuggerController._ensureResumptionOrder; |
michael@0 | 125 | DebuggerController.StackFrames.currentFrameDepth = -1; |
michael@0 | 126 | DebuggerController.activeThread.resume(warn); |
michael@0 | 127 | } else { |
michael@0 | 128 | DebuggerController.activeThread.interrupt(); |
michael@0 | 129 | } |
michael@0 | 130 | }, |
michael@0 | 131 | |
michael@0 | 132 | /** |
michael@0 | 133 | * Listener handling the step over button click event. |
michael@0 | 134 | */ |
michael@0 | 135 | _onStepOverPressed: function() { |
michael@0 | 136 | if (DebuggerController.activeThread.paused) { |
michael@0 | 137 | DebuggerController.StackFrames.currentFrameDepth = -1; |
michael@0 | 138 | let warn = DebuggerController._ensureResumptionOrder; |
michael@0 | 139 | DebuggerController.activeThread.stepOver(warn); |
michael@0 | 140 | } |
michael@0 | 141 | }, |
michael@0 | 142 | |
michael@0 | 143 | /** |
michael@0 | 144 | * Listener handling the step in button click event. |
michael@0 | 145 | */ |
michael@0 | 146 | _onStepInPressed: function() { |
michael@0 | 147 | if (DebuggerController.activeThread.paused) { |
michael@0 | 148 | DebuggerController.StackFrames.currentFrameDepth = -1; |
michael@0 | 149 | let warn = DebuggerController._ensureResumptionOrder; |
michael@0 | 150 | DebuggerController.activeThread.stepIn(warn); |
michael@0 | 151 | } |
michael@0 | 152 | }, |
michael@0 | 153 | |
michael@0 | 154 | /** |
michael@0 | 155 | * Listener handling the step out button click event. |
michael@0 | 156 | */ |
michael@0 | 157 | _onStepOutPressed: function() { |
michael@0 | 158 | if (DebuggerController.activeThread.paused) { |
michael@0 | 159 | DebuggerController.StackFrames.currentFrameDepth = -1; |
michael@0 | 160 | let warn = DebuggerController._ensureResumptionOrder; |
michael@0 | 161 | DebuggerController.activeThread.stepOut(warn); |
michael@0 | 162 | } |
michael@0 | 163 | }, |
michael@0 | 164 | |
michael@0 | 165 | _instrumentsPaneToggleButton: null, |
michael@0 | 166 | _resumeButton: null, |
michael@0 | 167 | _stepOverButton: null, |
michael@0 | 168 | _stepInButton: null, |
michael@0 | 169 | _stepOutButton: null, |
michael@0 | 170 | _resumeOrderTooltip: null, |
michael@0 | 171 | _resumeTooltip: "", |
michael@0 | 172 | _pauseTooltip: "", |
michael@0 | 173 | _stepOverTooltip: "", |
michael@0 | 174 | _stepInTooltip: "", |
michael@0 | 175 | _stepOutTooltip: "" |
michael@0 | 176 | }; |
michael@0 | 177 | |
michael@0 | 178 | /** |
michael@0 | 179 | * Functions handling the options UI. |
michael@0 | 180 | */ |
michael@0 | 181 | function OptionsView() { |
michael@0 | 182 | dumpn("OptionsView was instantiated"); |
michael@0 | 183 | |
michael@0 | 184 | this._toggleAutoPrettyPrint = this._toggleAutoPrettyPrint.bind(this); |
michael@0 | 185 | this._togglePauseOnExceptions = this._togglePauseOnExceptions.bind(this); |
michael@0 | 186 | this._toggleIgnoreCaughtExceptions = this._toggleIgnoreCaughtExceptions.bind(this); |
michael@0 | 187 | this._toggleShowPanesOnStartup = this._toggleShowPanesOnStartup.bind(this); |
michael@0 | 188 | this._toggleShowVariablesOnlyEnum = this._toggleShowVariablesOnlyEnum.bind(this); |
michael@0 | 189 | this._toggleShowVariablesFilterBox = this._toggleShowVariablesFilterBox.bind(this); |
michael@0 | 190 | this._toggleShowOriginalSource = this._toggleShowOriginalSource.bind(this); |
michael@0 | 191 | } |
michael@0 | 192 | |
michael@0 | 193 | OptionsView.prototype = { |
michael@0 | 194 | /** |
michael@0 | 195 | * Initialization function, called when the debugger is started. |
michael@0 | 196 | */ |
michael@0 | 197 | initialize: function() { |
michael@0 | 198 | dumpn("Initializing the OptionsView"); |
michael@0 | 199 | |
michael@0 | 200 | this._button = document.getElementById("debugger-options"); |
michael@0 | 201 | this._autoPrettyPrint = document.getElementById("auto-pretty-print"); |
michael@0 | 202 | this._pauseOnExceptionsItem = document.getElementById("pause-on-exceptions"); |
michael@0 | 203 | this._ignoreCaughtExceptionsItem = document.getElementById("ignore-caught-exceptions"); |
michael@0 | 204 | this._showPanesOnStartupItem = document.getElementById("show-panes-on-startup"); |
michael@0 | 205 | this._showVariablesOnlyEnumItem = document.getElementById("show-vars-only-enum"); |
michael@0 | 206 | this._showVariablesFilterBoxItem = document.getElementById("show-vars-filter-box"); |
michael@0 | 207 | this._showOriginalSourceItem = document.getElementById("show-original-source"); |
michael@0 | 208 | |
michael@0 | 209 | this._autoPrettyPrint.setAttribute("checked", Prefs.autoPrettyPrint); |
michael@0 | 210 | this._pauseOnExceptionsItem.setAttribute("checked", Prefs.pauseOnExceptions); |
michael@0 | 211 | this._ignoreCaughtExceptionsItem.setAttribute("checked", Prefs.ignoreCaughtExceptions); |
michael@0 | 212 | this._showPanesOnStartupItem.setAttribute("checked", Prefs.panesVisibleOnStartup); |
michael@0 | 213 | this._showVariablesOnlyEnumItem.setAttribute("checked", Prefs.variablesOnlyEnumVisible); |
michael@0 | 214 | this._showVariablesFilterBoxItem.setAttribute("checked", Prefs.variablesSearchboxVisible); |
michael@0 | 215 | this._showOriginalSourceItem.setAttribute("checked", Prefs.sourceMapsEnabled); |
michael@0 | 216 | }, |
michael@0 | 217 | |
michael@0 | 218 | /** |
michael@0 | 219 | * Destruction function, called when the debugger is closed. |
michael@0 | 220 | */ |
michael@0 | 221 | destroy: function() { |
michael@0 | 222 | dumpn("Destroying the OptionsView"); |
michael@0 | 223 | // Nothing to do here yet. |
michael@0 | 224 | }, |
michael@0 | 225 | |
michael@0 | 226 | /** |
michael@0 | 227 | * Listener handling the 'gear menu' popup showing event. |
michael@0 | 228 | */ |
michael@0 | 229 | _onPopupShowing: function() { |
michael@0 | 230 | this._button.setAttribute("open", "true"); |
michael@0 | 231 | window.emit(EVENTS.OPTIONS_POPUP_SHOWING); |
michael@0 | 232 | }, |
michael@0 | 233 | |
michael@0 | 234 | /** |
michael@0 | 235 | * Listener handling the 'gear menu' popup hiding event. |
michael@0 | 236 | */ |
michael@0 | 237 | _onPopupHiding: function() { |
michael@0 | 238 | this._button.removeAttribute("open"); |
michael@0 | 239 | }, |
michael@0 | 240 | |
michael@0 | 241 | /** |
michael@0 | 242 | * Listener handling the 'gear menu' popup hidden event. |
michael@0 | 243 | */ |
michael@0 | 244 | _onPopupHidden: function() { |
michael@0 | 245 | window.emit(EVENTS.OPTIONS_POPUP_HIDDEN); |
michael@0 | 246 | }, |
michael@0 | 247 | |
michael@0 | 248 | /** |
michael@0 | 249 | * Listener handling the 'auto pretty print' menuitem command. |
michael@0 | 250 | */ |
michael@0 | 251 | _toggleAutoPrettyPrint: function(){ |
michael@0 | 252 | Prefs.autoPrettyPrint = |
michael@0 | 253 | this._autoPrettyPrint.getAttribute("checked") == "true"; |
michael@0 | 254 | }, |
michael@0 | 255 | |
michael@0 | 256 | /** |
michael@0 | 257 | * Listener handling the 'pause on exceptions' menuitem command. |
michael@0 | 258 | */ |
michael@0 | 259 | _togglePauseOnExceptions: function() { |
michael@0 | 260 | Prefs.pauseOnExceptions = |
michael@0 | 261 | this._pauseOnExceptionsItem.getAttribute("checked") == "true"; |
michael@0 | 262 | |
michael@0 | 263 | DebuggerController.activeThread.pauseOnExceptions( |
michael@0 | 264 | Prefs.pauseOnExceptions, |
michael@0 | 265 | Prefs.ignoreCaughtExceptions); |
michael@0 | 266 | }, |
michael@0 | 267 | |
michael@0 | 268 | _toggleIgnoreCaughtExceptions: function() { |
michael@0 | 269 | Prefs.ignoreCaughtExceptions = |
michael@0 | 270 | this._ignoreCaughtExceptionsItem.getAttribute("checked") == "true"; |
michael@0 | 271 | |
michael@0 | 272 | DebuggerController.activeThread.pauseOnExceptions( |
michael@0 | 273 | Prefs.pauseOnExceptions, |
michael@0 | 274 | Prefs.ignoreCaughtExceptions); |
michael@0 | 275 | }, |
michael@0 | 276 | |
michael@0 | 277 | /** |
michael@0 | 278 | * Listener handling the 'show panes on startup' menuitem command. |
michael@0 | 279 | */ |
michael@0 | 280 | _toggleShowPanesOnStartup: function() { |
michael@0 | 281 | Prefs.panesVisibleOnStartup = |
michael@0 | 282 | this._showPanesOnStartupItem.getAttribute("checked") == "true"; |
michael@0 | 283 | }, |
michael@0 | 284 | |
michael@0 | 285 | /** |
michael@0 | 286 | * Listener handling the 'show non-enumerables' menuitem command. |
michael@0 | 287 | */ |
michael@0 | 288 | _toggleShowVariablesOnlyEnum: function() { |
michael@0 | 289 | let pref = Prefs.variablesOnlyEnumVisible = |
michael@0 | 290 | this._showVariablesOnlyEnumItem.getAttribute("checked") == "true"; |
michael@0 | 291 | |
michael@0 | 292 | DebuggerView.Variables.onlyEnumVisible = pref; |
michael@0 | 293 | }, |
michael@0 | 294 | |
michael@0 | 295 | /** |
michael@0 | 296 | * Listener handling the 'show variables searchbox' menuitem command. |
michael@0 | 297 | */ |
michael@0 | 298 | _toggleShowVariablesFilterBox: function() { |
michael@0 | 299 | let pref = Prefs.variablesSearchboxVisible = |
michael@0 | 300 | this._showVariablesFilterBoxItem.getAttribute("checked") == "true"; |
michael@0 | 301 | |
michael@0 | 302 | DebuggerView.Variables.searchEnabled = pref; |
michael@0 | 303 | }, |
michael@0 | 304 | |
michael@0 | 305 | /** |
michael@0 | 306 | * Listener handling the 'show original source' menuitem command. |
michael@0 | 307 | */ |
michael@0 | 308 | _toggleShowOriginalSource: function() { |
michael@0 | 309 | let pref = Prefs.sourceMapsEnabled = |
michael@0 | 310 | this._showOriginalSourceItem.getAttribute("checked") == "true"; |
michael@0 | 311 | |
michael@0 | 312 | // Don't block the UI while reconfiguring the server. |
michael@0 | 313 | window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => { |
michael@0 | 314 | // The popup panel needs more time to hide after triggering onpopuphidden. |
michael@0 | 315 | window.setTimeout(() => { |
michael@0 | 316 | DebuggerController.reconfigureThread(pref); |
michael@0 | 317 | }, POPUP_HIDDEN_DELAY); |
michael@0 | 318 | }, false); |
michael@0 | 319 | }, |
michael@0 | 320 | |
michael@0 | 321 | _button: null, |
michael@0 | 322 | _pauseOnExceptionsItem: null, |
michael@0 | 323 | _showPanesOnStartupItem: null, |
michael@0 | 324 | _showVariablesOnlyEnumItem: null, |
michael@0 | 325 | _showVariablesFilterBoxItem: null, |
michael@0 | 326 | _showOriginalSourceItem: null |
michael@0 | 327 | }; |
michael@0 | 328 | |
michael@0 | 329 | /** |
michael@0 | 330 | * Functions handling the stackframes UI. |
michael@0 | 331 | */ |
michael@0 | 332 | function StackFramesView() { |
michael@0 | 333 | dumpn("StackFramesView was instantiated"); |
michael@0 | 334 | |
michael@0 | 335 | this._onStackframeRemoved = this._onStackframeRemoved.bind(this); |
michael@0 | 336 | this._onSelect = this._onSelect.bind(this); |
michael@0 | 337 | this._onScroll = this._onScroll.bind(this); |
michael@0 | 338 | this._afterScroll = this._afterScroll.bind(this); |
michael@0 | 339 | } |
michael@0 | 340 | |
michael@0 | 341 | StackFramesView.prototype = Heritage.extend(WidgetMethods, { |
michael@0 | 342 | /** |
michael@0 | 343 | * Initialization function, called when the debugger is started. |
michael@0 | 344 | */ |
michael@0 | 345 | initialize: function() { |
michael@0 | 346 | dumpn("Initializing the StackFramesView"); |
michael@0 | 347 | |
michael@0 | 348 | this.widget = new BreadcrumbsWidget(document.getElementById("stackframes")); |
michael@0 | 349 | this.widget.addEventListener("select", this._onSelect, false); |
michael@0 | 350 | this.widget.addEventListener("scroll", this._onScroll, true); |
michael@0 | 351 | window.addEventListener("resize", this._onScroll, true); |
michael@0 | 352 | |
michael@0 | 353 | this.autoFocusOnFirstItem = false; |
michael@0 | 354 | this.autoFocusOnSelection = false; |
michael@0 | 355 | |
michael@0 | 356 | // This view's contents are also mirrored in a different container. |
michael@0 | 357 | this._mirror = DebuggerView.StackFramesClassicList; |
michael@0 | 358 | }, |
michael@0 | 359 | |
michael@0 | 360 | /** |
michael@0 | 361 | * Destruction function, called when the debugger is closed. |
michael@0 | 362 | */ |
michael@0 | 363 | destroy: function() { |
michael@0 | 364 | dumpn("Destroying the StackFramesView"); |
michael@0 | 365 | |
michael@0 | 366 | this.widget.removeEventListener("select", this._onSelect, false); |
michael@0 | 367 | this.widget.removeEventListener("scroll", this._onScroll, true); |
michael@0 | 368 | window.removeEventListener("resize", this._onScroll, true); |
michael@0 | 369 | }, |
michael@0 | 370 | |
michael@0 | 371 | /** |
michael@0 | 372 | * Adds a frame in this stackframes container. |
michael@0 | 373 | * |
michael@0 | 374 | * @param string aTitle |
michael@0 | 375 | * The frame title (function name). |
michael@0 | 376 | * @param string aUrl |
michael@0 | 377 | * The frame source url. |
michael@0 | 378 | * @param string aLine |
michael@0 | 379 | * The frame line number. |
michael@0 | 380 | * @param number aDepth |
michael@0 | 381 | * The frame depth in the stack. |
michael@0 | 382 | * @param boolean aIsBlackBoxed |
michael@0 | 383 | * Whether or not the frame is black boxed. |
michael@0 | 384 | */ |
michael@0 | 385 | addFrame: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) { |
michael@0 | 386 | // Blackboxed stack frames are collapsed into a single entry in |
michael@0 | 387 | // the view. By convention, only the first frame is displayed. |
michael@0 | 388 | if (aIsBlackBoxed) { |
michael@0 | 389 | if (this._prevBlackBoxedUrl == aUrl) { |
michael@0 | 390 | return; |
michael@0 | 391 | } |
michael@0 | 392 | this._prevBlackBoxedUrl = aUrl; |
michael@0 | 393 | } else { |
michael@0 | 394 | this._prevBlackBoxedUrl = null; |
michael@0 | 395 | } |
michael@0 | 396 | |
michael@0 | 397 | // Create the element node for the stack frame item. |
michael@0 | 398 | let frameView = this._createFrameView.apply(this, arguments); |
michael@0 | 399 | |
michael@0 | 400 | // Append a stack frame item to this container. |
michael@0 | 401 | this.push([frameView], { |
michael@0 | 402 | index: 0, /* specifies on which position should the item be appended */ |
michael@0 | 403 | attachment: { |
michael@0 | 404 | title: aTitle, |
michael@0 | 405 | url: aUrl, |
michael@0 | 406 | line: aLine, |
michael@0 | 407 | depth: aDepth |
michael@0 | 408 | }, |
michael@0 | 409 | // Make sure that when the stack frame item is removed, the corresponding |
michael@0 | 410 | // mirrored item in the classic list is also removed. |
michael@0 | 411 | finalize: this._onStackframeRemoved |
michael@0 | 412 | }); |
michael@0 | 413 | |
michael@0 | 414 | // Mirror this newly inserted item inside the "Call Stack" tab. |
michael@0 | 415 | this._mirror.addFrame(aTitle, aUrl, aLine, aDepth); |
michael@0 | 416 | }, |
michael@0 | 417 | |
michael@0 | 418 | /** |
michael@0 | 419 | * Selects the frame at the specified depth in this container. |
michael@0 | 420 | * @param number aDepth |
michael@0 | 421 | */ |
michael@0 | 422 | set selectedDepth(aDepth) { |
michael@0 | 423 | this.selectedItem = aItem => aItem.attachment.depth == aDepth; |
michael@0 | 424 | }, |
michael@0 | 425 | |
michael@0 | 426 | /** |
michael@0 | 427 | * Gets the currently selected stack frame's depth in this container. |
michael@0 | 428 | * This will essentially be the opposite of |selectedIndex|, which deals |
michael@0 | 429 | * with the position in the view, where the last item added is actually |
michael@0 | 430 | * the bottommost, not topmost. |
michael@0 | 431 | * @return number |
michael@0 | 432 | */ |
michael@0 | 433 | get selectedDepth() { |
michael@0 | 434 | return this.selectedItem.attachment.depth; |
michael@0 | 435 | }, |
michael@0 | 436 | |
michael@0 | 437 | /** |
michael@0 | 438 | * Specifies if the active thread has more frames that need to be loaded. |
michael@0 | 439 | */ |
michael@0 | 440 | dirty: false, |
michael@0 | 441 | |
michael@0 | 442 | /** |
michael@0 | 443 | * Customization function for creating an item's UI. |
michael@0 | 444 | * |
michael@0 | 445 | * @param string aTitle |
michael@0 | 446 | * The frame title to be displayed in the list. |
michael@0 | 447 | * @param string aUrl |
michael@0 | 448 | * The frame source url. |
michael@0 | 449 | * @param string aLine |
michael@0 | 450 | * The frame line number. |
michael@0 | 451 | * @param number aDepth |
michael@0 | 452 | * The frame depth in the stack. |
michael@0 | 453 | * @param boolean aIsBlackBoxed |
michael@0 | 454 | * Whether or not the frame is black boxed. |
michael@0 | 455 | * @return nsIDOMNode |
michael@0 | 456 | * The stack frame view. |
michael@0 | 457 | */ |
michael@0 | 458 | _createFrameView: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) { |
michael@0 | 459 | let container = document.createElement("hbox"); |
michael@0 | 460 | container.id = "stackframe-" + aDepth; |
michael@0 | 461 | container.className = "dbg-stackframe"; |
michael@0 | 462 | |
michael@0 | 463 | let frameDetails = SourceUtils.trimUrlLength( |
michael@0 | 464 | SourceUtils.getSourceLabel(aUrl), |
michael@0 | 465 | STACK_FRAMES_SOURCE_URL_MAX_LENGTH, |
michael@0 | 466 | STACK_FRAMES_SOURCE_URL_TRIM_SECTION); |
michael@0 | 467 | |
michael@0 | 468 | if (aIsBlackBoxed) { |
michael@0 | 469 | container.classList.add("dbg-stackframe-black-boxed"); |
michael@0 | 470 | } else { |
michael@0 | 471 | let frameTitleNode = document.createElement("label"); |
michael@0 | 472 | frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag"; |
michael@0 | 473 | frameTitleNode.setAttribute("value", aTitle); |
michael@0 | 474 | container.appendChild(frameTitleNode); |
michael@0 | 475 | |
michael@0 | 476 | frameDetails += SEARCH_LINE_FLAG + aLine; |
michael@0 | 477 | } |
michael@0 | 478 | |
michael@0 | 479 | let frameDetailsNode = document.createElement("label"); |
michael@0 | 480 | frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id"; |
michael@0 | 481 | frameDetailsNode.setAttribute("value", frameDetails); |
michael@0 | 482 | container.appendChild(frameDetailsNode); |
michael@0 | 483 | |
michael@0 | 484 | return container; |
michael@0 | 485 | }, |
michael@0 | 486 | |
michael@0 | 487 | /** |
michael@0 | 488 | * Function called each time a stack frame item is removed. |
michael@0 | 489 | * |
michael@0 | 490 | * @param object aItem |
michael@0 | 491 | * The corresponding item. |
michael@0 | 492 | */ |
michael@0 | 493 | _onStackframeRemoved: function(aItem) { |
michael@0 | 494 | dumpn("Finalizing stackframe item: " + aItem); |
michael@0 | 495 | |
michael@0 | 496 | // Remove the mirrored item in the classic list. |
michael@0 | 497 | let depth = aItem.attachment.depth; |
michael@0 | 498 | this._mirror.remove(this._mirror.getItemForAttachment(e => e.depth == depth)); |
michael@0 | 499 | |
michael@0 | 500 | // Forget the previously blackboxed stack frame url. |
michael@0 | 501 | this._prevBlackBoxedUrl = null; |
michael@0 | 502 | }, |
michael@0 | 503 | |
michael@0 | 504 | /** |
michael@0 | 505 | * The select listener for the stackframes container. |
michael@0 | 506 | */ |
michael@0 | 507 | _onSelect: function(e) { |
michael@0 | 508 | let stackframeItem = this.selectedItem; |
michael@0 | 509 | if (stackframeItem) { |
michael@0 | 510 | // The container is not empty and an actual item was selected. |
michael@0 | 511 | let depth = stackframeItem.attachment.depth; |
michael@0 | 512 | DebuggerController.StackFrames.selectFrame(depth); |
michael@0 | 513 | |
michael@0 | 514 | // Mirror the selected item in the classic list. |
michael@0 | 515 | this.suppressSelectionEvents = true; |
michael@0 | 516 | this._mirror.selectedItem = e => e.attachment.depth == depth; |
michael@0 | 517 | this.suppressSelectionEvents = false; |
michael@0 | 518 | } |
michael@0 | 519 | }, |
michael@0 | 520 | |
michael@0 | 521 | /** |
michael@0 | 522 | * The scroll listener for the stackframes container. |
michael@0 | 523 | */ |
michael@0 | 524 | _onScroll: function() { |
michael@0 | 525 | // Update the stackframes container only if we have to. |
michael@0 | 526 | if (!this.dirty) { |
michael@0 | 527 | return; |
michael@0 | 528 | } |
michael@0 | 529 | // Allow requests to settle down first. |
michael@0 | 530 | setNamedTimeout("stack-scroll", STACK_FRAMES_SCROLL_DELAY, this._afterScroll); |
michael@0 | 531 | }, |
michael@0 | 532 | |
michael@0 | 533 | /** |
michael@0 | 534 | * Requests the addition of more frames from the controller. |
michael@0 | 535 | */ |
michael@0 | 536 | _afterScroll: function() { |
michael@0 | 537 | let scrollPosition = this.widget.getAttribute("scrollPosition"); |
michael@0 | 538 | let scrollWidth = this.widget.getAttribute("scrollWidth"); |
michael@0 | 539 | |
michael@0 | 540 | // If the stackframes container scrolled almost to the end, with only |
michael@0 | 541 | // 1/10 of a breadcrumb remaining, load more content. |
michael@0 | 542 | if (scrollPosition - scrollWidth / 10 < 1) { |
michael@0 | 543 | this.ensureIndexIsVisible(CALL_STACK_PAGE_SIZE - 1); |
michael@0 | 544 | this.dirty = false; |
michael@0 | 545 | |
michael@0 | 546 | // Loads more stack frames from the debugger server cache. |
michael@0 | 547 | DebuggerController.StackFrames.addMoreFrames(); |
michael@0 | 548 | } |
michael@0 | 549 | }, |
michael@0 | 550 | |
michael@0 | 551 | _mirror: null, |
michael@0 | 552 | _prevBlackBoxedUrl: null |
michael@0 | 553 | }); |
michael@0 | 554 | |
michael@0 | 555 | /* |
michael@0 | 556 | * Functions handling the stackframes classic list UI. |
michael@0 | 557 | * Controlled by the DebuggerView.StackFrames isntance. |
michael@0 | 558 | */ |
michael@0 | 559 | function StackFramesClassicListView() { |
michael@0 | 560 | dumpn("StackFramesClassicListView was instantiated"); |
michael@0 | 561 | |
michael@0 | 562 | this._onSelect = this._onSelect.bind(this); |
michael@0 | 563 | } |
michael@0 | 564 | |
michael@0 | 565 | StackFramesClassicListView.prototype = Heritage.extend(WidgetMethods, { |
michael@0 | 566 | /** |
michael@0 | 567 | * Initialization function, called when the debugger is started. |
michael@0 | 568 | */ |
michael@0 | 569 | initialize: function() { |
michael@0 | 570 | dumpn("Initializing the StackFramesClassicListView"); |
michael@0 | 571 | |
michael@0 | 572 | this.widget = new SideMenuWidget(document.getElementById("callstack-list")); |
michael@0 | 573 | this.widget.addEventListener("select", this._onSelect, false); |
michael@0 | 574 | |
michael@0 | 575 | this.emptyText = L10N.getStr("noStackFramesText"); |
michael@0 | 576 | this.autoFocusOnFirstItem = false; |
michael@0 | 577 | this.autoFocusOnSelection = false; |
michael@0 | 578 | |
michael@0 | 579 | // This view's contents are also mirrored in a different container. |
michael@0 | 580 | this._mirror = DebuggerView.StackFrames; |
michael@0 | 581 | }, |
michael@0 | 582 | |
michael@0 | 583 | /** |
michael@0 | 584 | * Destruction function, called when the debugger is closed. |
michael@0 | 585 | */ |
michael@0 | 586 | destroy: function() { |
michael@0 | 587 | dumpn("Destroying the StackFramesClassicListView"); |
michael@0 | 588 | |
michael@0 | 589 | this.widget.removeEventListener("select", this._onSelect, false); |
michael@0 | 590 | }, |
michael@0 | 591 | |
michael@0 | 592 | /** |
michael@0 | 593 | * Adds a frame in this stackframes container. |
michael@0 | 594 | * |
michael@0 | 595 | * @param string aTitle |
michael@0 | 596 | * The frame title (function name). |
michael@0 | 597 | * @param string aUrl |
michael@0 | 598 | * The frame source url. |
michael@0 | 599 | * @param string aLine |
michael@0 | 600 | * The frame line number. |
michael@0 | 601 | * @param number aDepth |
michael@0 | 602 | * The frame depth in the stack. |
michael@0 | 603 | */ |
michael@0 | 604 | addFrame: function(aTitle, aUrl, aLine, aDepth) { |
michael@0 | 605 | // Create the element node for the stack frame item. |
michael@0 | 606 | let frameView = this._createFrameView.apply(this, arguments); |
michael@0 | 607 | |
michael@0 | 608 | // Append a stack frame item to this container. |
michael@0 | 609 | this.push([frameView], { |
michael@0 | 610 | attachment: { |
michael@0 | 611 | depth: aDepth |
michael@0 | 612 | } |
michael@0 | 613 | }); |
michael@0 | 614 | }, |
michael@0 | 615 | |
michael@0 | 616 | /** |
michael@0 | 617 | * Customization function for creating an item's UI. |
michael@0 | 618 | * |
michael@0 | 619 | * @param string aTitle |
michael@0 | 620 | * The frame title to be displayed in the list. |
michael@0 | 621 | * @param string aUrl |
michael@0 | 622 | * The frame source url. |
michael@0 | 623 | * @param string aLine |
michael@0 | 624 | * The frame line number. |
michael@0 | 625 | * @param number aDepth |
michael@0 | 626 | * The frame depth in the stack. |
michael@0 | 627 | * @return nsIDOMNode |
michael@0 | 628 | * The stack frame view. |
michael@0 | 629 | */ |
michael@0 | 630 | _createFrameView: function(aTitle, aUrl, aLine, aDepth) { |
michael@0 | 631 | let container = document.createElement("hbox"); |
michael@0 | 632 | container.id = "classic-stackframe-" + aDepth; |
michael@0 | 633 | container.className = "dbg-classic-stackframe"; |
michael@0 | 634 | container.setAttribute("flex", "1"); |
michael@0 | 635 | |
michael@0 | 636 | let frameTitleNode = document.createElement("label"); |
michael@0 | 637 | frameTitleNode.className = "plain dbg-classic-stackframe-title"; |
michael@0 | 638 | frameTitleNode.setAttribute("value", aTitle); |
michael@0 | 639 | frameTitleNode.setAttribute("crop", "center"); |
michael@0 | 640 | |
michael@0 | 641 | let frameDetailsNode = document.createElement("hbox"); |
michael@0 | 642 | frameDetailsNode.className = "plain dbg-classic-stackframe-details"; |
michael@0 | 643 | |
michael@0 | 644 | let frameUrlNode = document.createElement("label"); |
michael@0 | 645 | frameUrlNode.className = "plain dbg-classic-stackframe-details-url"; |
michael@0 | 646 | frameUrlNode.setAttribute("value", SourceUtils.getSourceLabel(aUrl)); |
michael@0 | 647 | frameUrlNode.setAttribute("crop", "center"); |
michael@0 | 648 | frameDetailsNode.appendChild(frameUrlNode); |
michael@0 | 649 | |
michael@0 | 650 | let frameDetailsSeparator = document.createElement("label"); |
michael@0 | 651 | frameDetailsSeparator.className = "plain dbg-classic-stackframe-details-sep"; |
michael@0 | 652 | frameDetailsSeparator.setAttribute("value", SEARCH_LINE_FLAG); |
michael@0 | 653 | frameDetailsNode.appendChild(frameDetailsSeparator); |
michael@0 | 654 | |
michael@0 | 655 | let frameLineNode = document.createElement("label"); |
michael@0 | 656 | frameLineNode.className = "plain dbg-classic-stackframe-details-line"; |
michael@0 | 657 | frameLineNode.setAttribute("value", aLine); |
michael@0 | 658 | frameDetailsNode.appendChild(frameLineNode); |
michael@0 | 659 | |
michael@0 | 660 | container.appendChild(frameTitleNode); |
michael@0 | 661 | container.appendChild(frameDetailsNode); |
michael@0 | 662 | |
michael@0 | 663 | return container; |
michael@0 | 664 | }, |
michael@0 | 665 | |
michael@0 | 666 | /** |
michael@0 | 667 | * The select listener for the stackframes container. |
michael@0 | 668 | */ |
michael@0 | 669 | _onSelect: function(e) { |
michael@0 | 670 | let stackframeItem = this.selectedItem; |
michael@0 | 671 | if (stackframeItem) { |
michael@0 | 672 | // The container is not empty and an actual item was selected. |
michael@0 | 673 | // Mirror the selected item in the breadcrumbs list. |
michael@0 | 674 | let depth = stackframeItem.attachment.depth; |
michael@0 | 675 | this._mirror.selectedItem = e => e.attachment.depth == depth; |
michael@0 | 676 | } |
michael@0 | 677 | }, |
michael@0 | 678 | |
michael@0 | 679 | _mirror: null |
michael@0 | 680 | }); |
michael@0 | 681 | |
michael@0 | 682 | /** |
michael@0 | 683 | * Functions handling the filtering UI. |
michael@0 | 684 | */ |
michael@0 | 685 | function FilterView() { |
michael@0 | 686 | dumpn("FilterView was instantiated"); |
michael@0 | 687 | |
michael@0 | 688 | this._onClick = this._onClick.bind(this); |
michael@0 | 689 | this._onInput = this._onInput.bind(this); |
michael@0 | 690 | this._onKeyPress = this._onKeyPress.bind(this); |
michael@0 | 691 | this._onBlur = this._onBlur.bind(this); |
michael@0 | 692 | } |
michael@0 | 693 | |
michael@0 | 694 | FilterView.prototype = { |
michael@0 | 695 | /** |
michael@0 | 696 | * Initialization function, called when the debugger is started. |
michael@0 | 697 | */ |
michael@0 | 698 | initialize: function() { |
michael@0 | 699 | dumpn("Initializing the FilterView"); |
michael@0 | 700 | |
michael@0 | 701 | this._searchbox = document.getElementById("searchbox"); |
michael@0 | 702 | this._searchboxHelpPanel = document.getElementById("searchbox-help-panel"); |
michael@0 | 703 | this._filterLabel = document.getElementById("filter-label"); |
michael@0 | 704 | this._globalOperatorButton = document.getElementById("global-operator-button"); |
michael@0 | 705 | this._globalOperatorLabel = document.getElementById("global-operator-label"); |
michael@0 | 706 | this._functionOperatorButton = document.getElementById("function-operator-button"); |
michael@0 | 707 | this._functionOperatorLabel = document.getElementById("function-operator-label"); |
michael@0 | 708 | this._tokenOperatorButton = document.getElementById("token-operator-button"); |
michael@0 | 709 | this._tokenOperatorLabel = document.getElementById("token-operator-label"); |
michael@0 | 710 | this._lineOperatorButton = document.getElementById("line-operator-button"); |
michael@0 | 711 | this._lineOperatorLabel = document.getElementById("line-operator-label"); |
michael@0 | 712 | this._variableOperatorButton = document.getElementById("variable-operator-button"); |
michael@0 | 713 | this._variableOperatorLabel = document.getElementById("variable-operator-label"); |
michael@0 | 714 | |
michael@0 | 715 | this._fileSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("fileSearchKey")); |
michael@0 | 716 | this._globalSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("globalSearchKey")); |
michael@0 | 717 | this._filteredFunctionsKey = ShortcutUtils.prettifyShortcut(document.getElementById("functionSearchKey")); |
michael@0 | 718 | this._tokenSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("tokenSearchKey")); |
michael@0 | 719 | this._lineSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("lineSearchKey")); |
michael@0 | 720 | this._variableSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("variableSearchKey")); |
michael@0 | 721 | |
michael@0 | 722 | this._searchbox.addEventListener("click", this._onClick, false); |
michael@0 | 723 | this._searchbox.addEventListener("select", this._onInput, false); |
michael@0 | 724 | this._searchbox.addEventListener("input", this._onInput, false); |
michael@0 | 725 | this._searchbox.addEventListener("keypress", this._onKeyPress, false); |
michael@0 | 726 | this._searchbox.addEventListener("blur", this._onBlur, false); |
michael@0 | 727 | |
michael@0 | 728 | let placeholder = L10N.getFormatStr("emptySearchText", this._fileSearchKey); |
michael@0 | 729 | this._searchbox.setAttribute("placeholder", placeholder); |
michael@0 | 730 | |
michael@0 | 731 | this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG); |
michael@0 | 732 | this._functionOperatorButton.setAttribute("label", SEARCH_FUNCTION_FLAG); |
michael@0 | 733 | this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG); |
michael@0 | 734 | this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG); |
michael@0 | 735 | this._variableOperatorButton.setAttribute("label", SEARCH_VARIABLE_FLAG); |
michael@0 | 736 | |
michael@0 | 737 | this._filterLabel.setAttribute("value", |
michael@0 | 738 | L10N.getFormatStr("searchPanelFilter", this._fileSearchKey)); |
michael@0 | 739 | this._globalOperatorLabel.setAttribute("value", |
michael@0 | 740 | L10N.getFormatStr("searchPanelGlobal", this._globalSearchKey)); |
michael@0 | 741 | this._functionOperatorLabel.setAttribute("value", |
michael@0 | 742 | L10N.getFormatStr("searchPanelFunction", this._filteredFunctionsKey)); |
michael@0 | 743 | this._tokenOperatorLabel.setAttribute("value", |
michael@0 | 744 | L10N.getFormatStr("searchPanelToken", this._tokenSearchKey)); |
michael@0 | 745 | this._lineOperatorLabel.setAttribute("value", |
michael@0 | 746 | L10N.getFormatStr("searchPanelGoToLine", this._lineSearchKey)); |
michael@0 | 747 | this._variableOperatorLabel.setAttribute("value", |
michael@0 | 748 | L10N.getFormatStr("searchPanelVariable", this._variableSearchKey)); |
michael@0 | 749 | }, |
michael@0 | 750 | |
michael@0 | 751 | /** |
michael@0 | 752 | * Destruction function, called when the debugger is closed. |
michael@0 | 753 | */ |
michael@0 | 754 | destroy: function() { |
michael@0 | 755 | dumpn("Destroying the FilterView"); |
michael@0 | 756 | |
michael@0 | 757 | this._searchbox.removeEventListener("click", this._onClick, false); |
michael@0 | 758 | this._searchbox.removeEventListener("select", this._onInput, false); |
michael@0 | 759 | this._searchbox.removeEventListener("input", this._onInput, false); |
michael@0 | 760 | this._searchbox.removeEventListener("keypress", this._onKeyPress, false); |
michael@0 | 761 | this._searchbox.removeEventListener("blur", this._onBlur, false); |
michael@0 | 762 | }, |
michael@0 | 763 | |
michael@0 | 764 | /** |
michael@0 | 765 | * Gets the entered operator and arguments in the searchbox. |
michael@0 | 766 | * @return array |
michael@0 | 767 | */ |
michael@0 | 768 | get searchData() { |
michael@0 | 769 | let operator = "", args = []; |
michael@0 | 770 | |
michael@0 | 771 | let rawValue = this._searchbox.value; |
michael@0 | 772 | let rawLength = rawValue.length; |
michael@0 | 773 | let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG); |
michael@0 | 774 | let functionFlagIndex = rawValue.indexOf(SEARCH_FUNCTION_FLAG); |
michael@0 | 775 | let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG); |
michael@0 | 776 | let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG); |
michael@0 | 777 | let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG); |
michael@0 | 778 | |
michael@0 | 779 | // This is not a global, function or variable search, allow file/line flags. |
michael@0 | 780 | if (globalFlagIndex != 0 && functionFlagIndex != 0 && variableFlagIndex != 0) { |
michael@0 | 781 | // Token search has precedence over line search. |
michael@0 | 782 | if (tokenFlagIndex != -1) { |
michael@0 | 783 | operator = SEARCH_TOKEN_FLAG; |
michael@0 | 784 | args.push(rawValue.slice(0, tokenFlagIndex)); // file |
michael@0 | 785 | args.push(rawValue.substr(tokenFlagIndex + 1, rawLength)); // token |
michael@0 | 786 | } else if (lineFlagIndex != -1) { |
michael@0 | 787 | operator = SEARCH_LINE_FLAG; |
michael@0 | 788 | args.push(rawValue.slice(0, lineFlagIndex)); // file |
michael@0 | 789 | args.push(+rawValue.substr(lineFlagIndex + 1, rawLength) || 0); // line |
michael@0 | 790 | } else { |
michael@0 | 791 | args.push(rawValue); |
michael@0 | 792 | } |
michael@0 | 793 | } |
michael@0 | 794 | // Global searches dissalow the use of file or line flags. |
michael@0 | 795 | else if (globalFlagIndex == 0) { |
michael@0 | 796 | operator = SEARCH_GLOBAL_FLAG; |
michael@0 | 797 | args.push(rawValue.slice(1)); |
michael@0 | 798 | } |
michael@0 | 799 | // Function searches dissalow the use of file or line flags. |
michael@0 | 800 | else if (functionFlagIndex == 0) { |
michael@0 | 801 | operator = SEARCH_FUNCTION_FLAG; |
michael@0 | 802 | args.push(rawValue.slice(1)); |
michael@0 | 803 | } |
michael@0 | 804 | // Variable searches dissalow the use of file or line flags. |
michael@0 | 805 | else if (variableFlagIndex == 0) { |
michael@0 | 806 | operator = SEARCH_VARIABLE_FLAG; |
michael@0 | 807 | args.push(rawValue.slice(1)); |
michael@0 | 808 | } |
michael@0 | 809 | |
michael@0 | 810 | return [operator, args]; |
michael@0 | 811 | }, |
michael@0 | 812 | |
michael@0 | 813 | /** |
michael@0 | 814 | * Returns the current search operator. |
michael@0 | 815 | * @return string |
michael@0 | 816 | */ |
michael@0 | 817 | get searchOperator() this.searchData[0], |
michael@0 | 818 | |
michael@0 | 819 | /** |
michael@0 | 820 | * Returns the current search arguments. |
michael@0 | 821 | * @return array |
michael@0 | 822 | */ |
michael@0 | 823 | get searchArguments() this.searchData[1], |
michael@0 | 824 | |
michael@0 | 825 | /** |
michael@0 | 826 | * Clears the text from the searchbox and any changed views. |
michael@0 | 827 | */ |
michael@0 | 828 | clearSearch: function() { |
michael@0 | 829 | this._searchbox.value = ""; |
michael@0 | 830 | this.clearViews(); |
michael@0 | 831 | }, |
michael@0 | 832 | |
michael@0 | 833 | /** |
michael@0 | 834 | * Clears all the views that may pop up when searching. |
michael@0 | 835 | */ |
michael@0 | 836 | clearViews: function() { |
michael@0 | 837 | DebuggerView.GlobalSearch.clearView(); |
michael@0 | 838 | DebuggerView.FilteredSources.clearView(); |
michael@0 | 839 | DebuggerView.FilteredFunctions.clearView(); |
michael@0 | 840 | this._searchboxHelpPanel.hidePopup(); |
michael@0 | 841 | }, |
michael@0 | 842 | |
michael@0 | 843 | /** |
michael@0 | 844 | * Performs a line search if necessary. |
michael@0 | 845 | * (Jump to lines in the currently visible source). |
michael@0 | 846 | * |
michael@0 | 847 | * @param number aLine |
michael@0 | 848 | * The source line number to jump to. |
michael@0 | 849 | */ |
michael@0 | 850 | _performLineSearch: function(aLine) { |
michael@0 | 851 | // Make sure we're actually searching for a valid line. |
michael@0 | 852 | if (aLine) { |
michael@0 | 853 | DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 }, "center"); |
michael@0 | 854 | } |
michael@0 | 855 | }, |
michael@0 | 856 | |
michael@0 | 857 | /** |
michael@0 | 858 | * Performs a token search if necessary. |
michael@0 | 859 | * (Search for tokens in the currently visible source). |
michael@0 | 860 | * |
michael@0 | 861 | * @param string aToken |
michael@0 | 862 | * The source token to find. |
michael@0 | 863 | */ |
michael@0 | 864 | _performTokenSearch: function(aToken) { |
michael@0 | 865 | // Make sure we're actually searching for a valid token. |
michael@0 | 866 | if (!aToken) { |
michael@0 | 867 | return; |
michael@0 | 868 | } |
michael@0 | 869 | DebuggerView.editor.find(aToken); |
michael@0 | 870 | }, |
michael@0 | 871 | |
michael@0 | 872 | /** |
michael@0 | 873 | * The click listener for the search container. |
michael@0 | 874 | */ |
michael@0 | 875 | _onClick: function() { |
michael@0 | 876 | // If there's some text in the searchbox, displaying a panel would |
michael@0 | 877 | // interfere with double/triple click default behaviors. |
michael@0 | 878 | if (!this._searchbox.value) { |
michael@0 | 879 | this._searchboxHelpPanel.openPopup(this._searchbox); |
michael@0 | 880 | } |
michael@0 | 881 | }, |
michael@0 | 882 | |
michael@0 | 883 | /** |
michael@0 | 884 | * The input listener for the search container. |
michael@0 | 885 | */ |
michael@0 | 886 | _onInput: function() { |
michael@0 | 887 | this.clearViews(); |
michael@0 | 888 | |
michael@0 | 889 | // Make sure we're actually searching for something. |
michael@0 | 890 | if (!this._searchbox.value) { |
michael@0 | 891 | return; |
michael@0 | 892 | } |
michael@0 | 893 | |
michael@0 | 894 | // Perform the required search based on the specified operator. |
michael@0 | 895 | switch (this.searchOperator) { |
michael@0 | 896 | case SEARCH_GLOBAL_FLAG: |
michael@0 | 897 | // Schedule a global search for when the user stops typing. |
michael@0 | 898 | DebuggerView.GlobalSearch.scheduleSearch(this.searchArguments[0]); |
michael@0 | 899 | break; |
michael@0 | 900 | case SEARCH_FUNCTION_FLAG: |
michael@0 | 901 | // Schedule a function search for when the user stops typing. |
michael@0 | 902 | DebuggerView.FilteredFunctions.scheduleSearch(this.searchArguments[0]); |
michael@0 | 903 | break; |
michael@0 | 904 | case SEARCH_VARIABLE_FLAG: |
michael@0 | 905 | // Schedule a variable search for when the user stops typing. |
michael@0 | 906 | DebuggerView.Variables.scheduleSearch(this.searchArguments[0]); |
michael@0 | 907 | break; |
michael@0 | 908 | case SEARCH_TOKEN_FLAG: |
michael@0 | 909 | // Schedule a file+token search for when the user stops typing. |
michael@0 | 910 | DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]); |
michael@0 | 911 | this._performTokenSearch(this.searchArguments[1]); |
michael@0 | 912 | break; |
michael@0 | 913 | case SEARCH_LINE_FLAG: |
michael@0 | 914 | // Schedule a file+line search for when the user stops typing. |
michael@0 | 915 | DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]); |
michael@0 | 916 | this._performLineSearch(this.searchArguments[1]); |
michael@0 | 917 | break; |
michael@0 | 918 | default: |
michael@0 | 919 | // Schedule a file only search for when the user stops typing. |
michael@0 | 920 | DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]); |
michael@0 | 921 | break; |
michael@0 | 922 | } |
michael@0 | 923 | }, |
michael@0 | 924 | |
michael@0 | 925 | /** |
michael@0 | 926 | * The key press listener for the search container. |
michael@0 | 927 | */ |
michael@0 | 928 | _onKeyPress: function(e) { |
michael@0 | 929 | // This attribute is not implemented in Gecko at this time, see bug 680830. |
michael@0 | 930 | e.char = String.fromCharCode(e.charCode); |
michael@0 | 931 | |
michael@0 | 932 | // Perform the required action based on the specified operator. |
michael@0 | 933 | let [operator, args] = this.searchData; |
michael@0 | 934 | let isGlobalSearch = operator == SEARCH_GLOBAL_FLAG; |
michael@0 | 935 | let isFunctionSearch = operator == SEARCH_FUNCTION_FLAG; |
michael@0 | 936 | let isVariableSearch = operator == SEARCH_VARIABLE_FLAG; |
michael@0 | 937 | let isTokenSearch = operator == SEARCH_TOKEN_FLAG; |
michael@0 | 938 | let isLineSearch = operator == SEARCH_LINE_FLAG; |
michael@0 | 939 | let isFileOnlySearch = !operator && args.length == 1; |
michael@0 | 940 | |
michael@0 | 941 | // Depending on the pressed keys, determine to correct action to perform. |
michael@0 | 942 | let actionToPerform; |
michael@0 | 943 | |
michael@0 | 944 | // Meta+G and Ctrl+N focus next matches. |
michael@0 | 945 | if ((e.char == "g" && e.metaKey) || e.char == "n" && e.ctrlKey) { |
michael@0 | 946 | actionToPerform = "selectNext"; |
michael@0 | 947 | } |
michael@0 | 948 | // Meta+Shift+G and Ctrl+P focus previous matches. |
michael@0 | 949 | else if ((e.char == "G" && e.metaKey) || e.char == "p" && e.ctrlKey) { |
michael@0 | 950 | actionToPerform = "selectPrev"; |
michael@0 | 951 | } |
michael@0 | 952 | // Return, enter, down and up keys focus next or previous matches, while |
michael@0 | 953 | // the escape key switches focus from the search container. |
michael@0 | 954 | else switch (e.keyCode) { |
michael@0 | 955 | case e.DOM_VK_RETURN: |
michael@0 | 956 | var isReturnKey = true; |
michael@0 | 957 | // If the shift key is pressed, focus on the previous result |
michael@0 | 958 | actionToPerform = e.shiftKey ? "selectPrev" : "selectNext"; |
michael@0 | 959 | break; |
michael@0 | 960 | case e.DOM_VK_DOWN: |
michael@0 | 961 | actionToPerform = "selectNext"; |
michael@0 | 962 | break; |
michael@0 | 963 | case e.DOM_VK_UP: |
michael@0 | 964 | actionToPerform = "selectPrev"; |
michael@0 | 965 | break; |
michael@0 | 966 | } |
michael@0 | 967 | |
michael@0 | 968 | // If there's no action to perform, or no operator, file line or token |
michael@0 | 969 | // were specified, then this is either a broken or empty search. |
michael@0 | 970 | if (!actionToPerform || (!operator && !args.length)) { |
michael@0 | 971 | DebuggerView.editor.dropSelection(); |
michael@0 | 972 | return; |
michael@0 | 973 | } |
michael@0 | 974 | |
michael@0 | 975 | e.preventDefault(); |
michael@0 | 976 | e.stopPropagation(); |
michael@0 | 977 | |
michael@0 | 978 | // Jump to the next/previous entry in the global search, or perform |
michael@0 | 979 | // a new global search immediately |
michael@0 | 980 | if (isGlobalSearch) { |
michael@0 | 981 | let targetView = DebuggerView.GlobalSearch; |
michael@0 | 982 | if (!isReturnKey) { |
michael@0 | 983 | targetView[actionToPerform](); |
michael@0 | 984 | } else if (targetView.hidden) { |
michael@0 | 985 | targetView.scheduleSearch(args[0], 0); |
michael@0 | 986 | } |
michael@0 | 987 | return; |
michael@0 | 988 | } |
michael@0 | 989 | |
michael@0 | 990 | // Jump to the next/previous entry in the function search, perform |
michael@0 | 991 | // a new function search immediately, or clear it. |
michael@0 | 992 | if (isFunctionSearch) { |
michael@0 | 993 | let targetView = DebuggerView.FilteredFunctions; |
michael@0 | 994 | if (!isReturnKey) { |
michael@0 | 995 | targetView[actionToPerform](); |
michael@0 | 996 | } else if (targetView.hidden) { |
michael@0 | 997 | targetView.scheduleSearch(args[0], 0); |
michael@0 | 998 | } else { |
michael@0 | 999 | if (!targetView.selectedItem) { |
michael@0 | 1000 | targetView.selectedIndex = 0; |
michael@0 | 1001 | } |
michael@0 | 1002 | this.clearSearch(); |
michael@0 | 1003 | } |
michael@0 | 1004 | return; |
michael@0 | 1005 | } |
michael@0 | 1006 | |
michael@0 | 1007 | // Perform a new variable search immediately. |
michael@0 | 1008 | if (isVariableSearch) { |
michael@0 | 1009 | let targetView = DebuggerView.Variables; |
michael@0 | 1010 | if (isReturnKey) { |
michael@0 | 1011 | targetView.scheduleSearch(args[0], 0); |
michael@0 | 1012 | } |
michael@0 | 1013 | return; |
michael@0 | 1014 | } |
michael@0 | 1015 | |
michael@0 | 1016 | // Jump to the next/previous entry in the file search, perform |
michael@0 | 1017 | // a new file search immediately, or clear it. |
michael@0 | 1018 | if (isFileOnlySearch) { |
michael@0 | 1019 | let targetView = DebuggerView.FilteredSources; |
michael@0 | 1020 | if (!isReturnKey) { |
michael@0 | 1021 | targetView[actionToPerform](); |
michael@0 | 1022 | } else if (targetView.hidden) { |
michael@0 | 1023 | targetView.scheduleSearch(args[0], 0); |
michael@0 | 1024 | } else { |
michael@0 | 1025 | if (!targetView.selectedItem) { |
michael@0 | 1026 | targetView.selectedIndex = 0; |
michael@0 | 1027 | } |
michael@0 | 1028 | this.clearSearch(); |
michael@0 | 1029 | } |
michael@0 | 1030 | return; |
michael@0 | 1031 | } |
michael@0 | 1032 | |
michael@0 | 1033 | // Jump to the next/previous instance of the currently searched token. |
michael@0 | 1034 | if (isTokenSearch) { |
michael@0 | 1035 | let methods = { selectNext: "findNext", selectPrev: "findPrev" }; |
michael@0 | 1036 | DebuggerView.editor[methods[actionToPerform]](); |
michael@0 | 1037 | return; |
michael@0 | 1038 | } |
michael@0 | 1039 | |
michael@0 | 1040 | // Increment/decrement the currently searched caret line. |
michael@0 | 1041 | if (isLineSearch) { |
michael@0 | 1042 | let [, line] = args; |
michael@0 | 1043 | let amounts = { selectNext: 1, selectPrev: -1 }; |
michael@0 | 1044 | |
michael@0 | 1045 | // Modify the line number and jump to it. |
michael@0 | 1046 | line += !isReturnKey ? amounts[actionToPerform] : 0; |
michael@0 | 1047 | let lineCount = DebuggerView.editor.lineCount(); |
michael@0 | 1048 | let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line; |
michael@0 | 1049 | this._doSearch(SEARCH_LINE_FLAG, lineTarget); |
michael@0 | 1050 | return; |
michael@0 | 1051 | } |
michael@0 | 1052 | }, |
michael@0 | 1053 | |
michael@0 | 1054 | /** |
michael@0 | 1055 | * The blur listener for the search container. |
michael@0 | 1056 | */ |
michael@0 | 1057 | _onBlur: function() { |
michael@0 | 1058 | this.clearViews(); |
michael@0 | 1059 | }, |
michael@0 | 1060 | |
michael@0 | 1061 | /** |
michael@0 | 1062 | * Called when a filtering key sequence was pressed. |
michael@0 | 1063 | * |
michael@0 | 1064 | * @param string aOperator |
michael@0 | 1065 | * The operator to use for filtering. |
michael@0 | 1066 | */ |
michael@0 | 1067 | _doSearch: function(aOperator = "", aText = "") { |
michael@0 | 1068 | this._searchbox.focus(); |
michael@0 | 1069 | this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738. |
michael@0 | 1070 | |
michael@0 | 1071 | if (aText) { |
michael@0 | 1072 | this._searchbox.value = aOperator + aText; |
michael@0 | 1073 | return; |
michael@0 | 1074 | } |
michael@0 | 1075 | if (DebuggerView.editor.somethingSelected()) { |
michael@0 | 1076 | this._searchbox.value = aOperator + DebuggerView.editor.getSelection(); |
michael@0 | 1077 | return; |
michael@0 | 1078 | } |
michael@0 | 1079 | if (SEARCH_AUTOFILL.indexOf(aOperator) != -1) { |
michael@0 | 1080 | let cursor = DebuggerView.editor.getCursor(); |
michael@0 | 1081 | let content = DebuggerView.editor.getText(); |
michael@0 | 1082 | let location = DebuggerView.Sources.selectedValue; |
michael@0 | 1083 | let source = DebuggerController.Parser.get(content, location); |
michael@0 | 1084 | let identifier = source.getIdentifierAt({ line: cursor.line+1, column: cursor.ch }); |
michael@0 | 1085 | |
michael@0 | 1086 | if (identifier && identifier.name) { |
michael@0 | 1087 | this._searchbox.value = aOperator + identifier.name; |
michael@0 | 1088 | this._searchbox.select(); |
michael@0 | 1089 | this._searchbox.selectionStart += aOperator.length; |
michael@0 | 1090 | return; |
michael@0 | 1091 | } |
michael@0 | 1092 | } |
michael@0 | 1093 | this._searchbox.value = aOperator; |
michael@0 | 1094 | }, |
michael@0 | 1095 | |
michael@0 | 1096 | /** |
michael@0 | 1097 | * Called when the source location filter key sequence was pressed. |
michael@0 | 1098 | */ |
michael@0 | 1099 | _doFileSearch: function() { |
michael@0 | 1100 | this._doSearch(); |
michael@0 | 1101 | this._searchboxHelpPanel.openPopup(this._searchbox); |
michael@0 | 1102 | }, |
michael@0 | 1103 | |
michael@0 | 1104 | /** |
michael@0 | 1105 | * Called when the global search filter key sequence was pressed. |
michael@0 | 1106 | */ |
michael@0 | 1107 | _doGlobalSearch: function() { |
michael@0 | 1108 | this._doSearch(SEARCH_GLOBAL_FLAG); |
michael@0 | 1109 | this._searchboxHelpPanel.hidePopup(); |
michael@0 | 1110 | }, |
michael@0 | 1111 | |
michael@0 | 1112 | /** |
michael@0 | 1113 | * Called when the source function filter key sequence was pressed. |
michael@0 | 1114 | */ |
michael@0 | 1115 | _doFunctionSearch: function() { |
michael@0 | 1116 | this._doSearch(SEARCH_FUNCTION_FLAG); |
michael@0 | 1117 | this._searchboxHelpPanel.hidePopup(); |
michael@0 | 1118 | }, |
michael@0 | 1119 | |
michael@0 | 1120 | /** |
michael@0 | 1121 | * Called when the source token filter key sequence was pressed. |
michael@0 | 1122 | */ |
michael@0 | 1123 | _doTokenSearch: function() { |
michael@0 | 1124 | this._doSearch(SEARCH_TOKEN_FLAG); |
michael@0 | 1125 | this._searchboxHelpPanel.hidePopup(); |
michael@0 | 1126 | }, |
michael@0 | 1127 | |
michael@0 | 1128 | /** |
michael@0 | 1129 | * Called when the source line filter key sequence was pressed. |
michael@0 | 1130 | */ |
michael@0 | 1131 | _doLineSearch: function() { |
michael@0 | 1132 | this._doSearch(SEARCH_LINE_FLAG); |
michael@0 | 1133 | this._searchboxHelpPanel.hidePopup(); |
michael@0 | 1134 | }, |
michael@0 | 1135 | |
michael@0 | 1136 | /** |
michael@0 | 1137 | * Called when the variable search filter key sequence was pressed. |
michael@0 | 1138 | */ |
michael@0 | 1139 | _doVariableSearch: function() { |
michael@0 | 1140 | this._doSearch(SEARCH_VARIABLE_FLAG); |
michael@0 | 1141 | this._searchboxHelpPanel.hidePopup(); |
michael@0 | 1142 | }, |
michael@0 | 1143 | |
michael@0 | 1144 | /** |
michael@0 | 1145 | * Called when the variables focus key sequence was pressed. |
michael@0 | 1146 | */ |
michael@0 | 1147 | _doVariablesFocus: function() { |
michael@0 | 1148 | DebuggerView.showInstrumentsPane(); |
michael@0 | 1149 | DebuggerView.Variables.focusFirstVisibleItem(); |
michael@0 | 1150 | }, |
michael@0 | 1151 | |
michael@0 | 1152 | _searchbox: null, |
michael@0 | 1153 | _searchboxHelpPanel: null, |
michael@0 | 1154 | _globalOperatorButton: null, |
michael@0 | 1155 | _globalOperatorLabel: null, |
michael@0 | 1156 | _functionOperatorButton: null, |
michael@0 | 1157 | _functionOperatorLabel: null, |
michael@0 | 1158 | _tokenOperatorButton: null, |
michael@0 | 1159 | _tokenOperatorLabel: null, |
michael@0 | 1160 | _lineOperatorButton: null, |
michael@0 | 1161 | _lineOperatorLabel: null, |
michael@0 | 1162 | _variableOperatorButton: null, |
michael@0 | 1163 | _variableOperatorLabel: null, |
michael@0 | 1164 | _fileSearchKey: "", |
michael@0 | 1165 | _globalSearchKey: "", |
michael@0 | 1166 | _filteredFunctionsKey: "", |
michael@0 | 1167 | _tokenSearchKey: "", |
michael@0 | 1168 | _lineSearchKey: "", |
michael@0 | 1169 | _variableSearchKey: "", |
michael@0 | 1170 | }; |
michael@0 | 1171 | |
michael@0 | 1172 | /** |
michael@0 | 1173 | * Functions handling the filtered sources UI. |
michael@0 | 1174 | */ |
michael@0 | 1175 | function FilteredSourcesView() { |
michael@0 | 1176 | dumpn("FilteredSourcesView was instantiated"); |
michael@0 | 1177 | |
michael@0 | 1178 | this._onClick = this._onClick.bind(this); |
michael@0 | 1179 | this._onSelect = this._onSelect.bind(this); |
michael@0 | 1180 | } |
michael@0 | 1181 | |
michael@0 | 1182 | FilteredSourcesView.prototype = Heritage.extend(ResultsPanelContainer.prototype, { |
michael@0 | 1183 | /** |
michael@0 | 1184 | * Initialization function, called when the debugger is started. |
michael@0 | 1185 | */ |
michael@0 | 1186 | initialize: function() { |
michael@0 | 1187 | dumpn("Initializing the FilteredSourcesView"); |
michael@0 | 1188 | |
michael@0 | 1189 | this.anchor = document.getElementById("searchbox"); |
michael@0 | 1190 | this.widget.addEventListener("select", this._onSelect, false); |
michael@0 | 1191 | this.widget.addEventListener("click", this._onClick, false); |
michael@0 | 1192 | }, |
michael@0 | 1193 | |
michael@0 | 1194 | /** |
michael@0 | 1195 | * Destruction function, called when the debugger is closed. |
michael@0 | 1196 | */ |
michael@0 | 1197 | destroy: function() { |
michael@0 | 1198 | dumpn("Destroying the FilteredSourcesView"); |
michael@0 | 1199 | |
michael@0 | 1200 | this.widget.removeEventListener("select", this._onSelect, false); |
michael@0 | 1201 | this.widget.removeEventListener("click", this._onClick, false); |
michael@0 | 1202 | this.anchor = null; |
michael@0 | 1203 | }, |
michael@0 | 1204 | |
michael@0 | 1205 | /** |
michael@0 | 1206 | * Schedules searching for a source. |
michael@0 | 1207 | * |
michael@0 | 1208 | * @param string aToken |
michael@0 | 1209 | * The function to search for. |
michael@0 | 1210 | * @param number aWait |
michael@0 | 1211 | * The amount of milliseconds to wait until draining. |
michael@0 | 1212 | */ |
michael@0 | 1213 | scheduleSearch: function(aToken, aWait) { |
michael@0 | 1214 | // The amount of time to wait for the requests to settle. |
michael@0 | 1215 | let maxDelay = FILE_SEARCH_ACTION_MAX_DELAY; |
michael@0 | 1216 | let delay = aWait === undefined ? maxDelay / aToken.length : aWait; |
michael@0 | 1217 | |
michael@0 | 1218 | // Allow requests to settle down first. |
michael@0 | 1219 | setNamedTimeout("sources-search", delay, () => this._doSearch(aToken)); |
michael@0 | 1220 | }, |
michael@0 | 1221 | |
michael@0 | 1222 | /** |
michael@0 | 1223 | * Finds file matches in all the displayed sources. |
michael@0 | 1224 | * |
michael@0 | 1225 | * @param string aToken |
michael@0 | 1226 | * The string to search for. |
michael@0 | 1227 | */ |
michael@0 | 1228 | _doSearch: function(aToken, aStore = []) { |
michael@0 | 1229 | // Don't continue filtering if the searched token is an empty string. |
michael@0 | 1230 | // In contrast with function searching, in this case we don't want to |
michael@0 | 1231 | // show a list of all the files when no search token was supplied. |
michael@0 | 1232 | if (!aToken) { |
michael@0 | 1233 | return; |
michael@0 | 1234 | } |
michael@0 | 1235 | |
michael@0 | 1236 | for (let item of DebuggerView.Sources.items) { |
michael@0 | 1237 | let lowerCaseLabel = item.attachment.label.toLowerCase(); |
michael@0 | 1238 | let lowerCaseToken = aToken.toLowerCase(); |
michael@0 | 1239 | if (lowerCaseLabel.match(lowerCaseToken)) { |
michael@0 | 1240 | aStore.push(item); |
michael@0 | 1241 | } |
michael@0 | 1242 | |
michael@0 | 1243 | // Once the maximum allowed number of results is reached, proceed |
michael@0 | 1244 | // with building the UI immediately. |
michael@0 | 1245 | if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) { |
michael@0 | 1246 | this._syncView(aStore); |
michael@0 | 1247 | return; |
michael@0 | 1248 | } |
michael@0 | 1249 | } |
michael@0 | 1250 | |
michael@0 | 1251 | // Couldn't reach the maximum allowed number of results, but that's ok, |
michael@0 | 1252 | // continue building the UI. |
michael@0 | 1253 | this._syncView(aStore); |
michael@0 | 1254 | }, |
michael@0 | 1255 | |
michael@0 | 1256 | /** |
michael@0 | 1257 | * Updates the list of sources displayed in this container. |
michael@0 | 1258 | * |
michael@0 | 1259 | * @param array aSearchResults |
michael@0 | 1260 | * The results array, containing search details for each source. |
michael@0 | 1261 | */ |
michael@0 | 1262 | _syncView: function(aSearchResults) { |
michael@0 | 1263 | // If there are no matches found, keep the popup hidden and avoid |
michael@0 | 1264 | // creating the view. |
michael@0 | 1265 | if (!aSearchResults.length) { |
michael@0 | 1266 | window.emit(EVENTS.FILE_SEARCH_MATCH_NOT_FOUND); |
michael@0 | 1267 | return; |
michael@0 | 1268 | } |
michael@0 | 1269 | |
michael@0 | 1270 | for (let item of aSearchResults) { |
michael@0 | 1271 | // Create the element node for the location item. |
michael@0 | 1272 | let itemView = this._createItemView( |
michael@0 | 1273 | SourceUtils.trimUrlLength(item.attachment.label), |
michael@0 | 1274 | SourceUtils.trimUrlLength(item.value, 0, "start") |
michael@0 | 1275 | ); |
michael@0 | 1276 | |
michael@0 | 1277 | // Append a location item to this container for each match. |
michael@0 | 1278 | this.push([itemView], { |
michael@0 | 1279 | index: -1, /* specifies on which position should the item be appended */ |
michael@0 | 1280 | attachment: { |
michael@0 | 1281 | url: item.value |
michael@0 | 1282 | } |
michael@0 | 1283 | }); |
michael@0 | 1284 | } |
michael@0 | 1285 | |
michael@0 | 1286 | // There's at least one item displayed in this container. Don't select it |
michael@0 | 1287 | // automatically if not forced (by tests) or in tandem with an operator. |
michael@0 | 1288 | if (this._autoSelectFirstItem || DebuggerView.Filtering.searchOperator) { |
michael@0 | 1289 | this.selectedIndex = 0; |
michael@0 | 1290 | } |
michael@0 | 1291 | this.hidden = false; |
michael@0 | 1292 | |
michael@0 | 1293 | // Signal that file search matches were found and displayed. |
michael@0 | 1294 | window.emit(EVENTS.FILE_SEARCH_MATCH_FOUND); |
michael@0 | 1295 | }, |
michael@0 | 1296 | |
michael@0 | 1297 | /** |
michael@0 | 1298 | * The click listener for this container. |
michael@0 | 1299 | */ |
michael@0 | 1300 | _onClick: function(e) { |
michael@0 | 1301 | let locationItem = this.getItemForElement(e.target); |
michael@0 | 1302 | if (locationItem) { |
michael@0 | 1303 | this.selectedItem = locationItem; |
michael@0 | 1304 | DebuggerView.Filtering.clearSearch(); |
michael@0 | 1305 | } |
michael@0 | 1306 | }, |
michael@0 | 1307 | |
michael@0 | 1308 | /** |
michael@0 | 1309 | * The select listener for this container. |
michael@0 | 1310 | * |
michael@0 | 1311 | * @param object aItem |
michael@0 | 1312 | * The item associated with the element to select. |
michael@0 | 1313 | */ |
michael@0 | 1314 | _onSelect: function({ detail: locationItem }) { |
michael@0 | 1315 | if (locationItem) { |
michael@0 | 1316 | DebuggerView.setEditorLocation(locationItem.attachment.url, undefined, { |
michael@0 | 1317 | noCaret: true, |
michael@0 | 1318 | noDebug: true |
michael@0 | 1319 | }); |
michael@0 | 1320 | } |
michael@0 | 1321 | } |
michael@0 | 1322 | }); |
michael@0 | 1323 | |
michael@0 | 1324 | /** |
michael@0 | 1325 | * Functions handling the function search UI. |
michael@0 | 1326 | */ |
michael@0 | 1327 | function FilteredFunctionsView() { |
michael@0 | 1328 | dumpn("FilteredFunctionsView was instantiated"); |
michael@0 | 1329 | |
michael@0 | 1330 | this._onClick = this._onClick.bind(this); |
michael@0 | 1331 | this._onSelect = this._onSelect.bind(this); |
michael@0 | 1332 | } |
michael@0 | 1333 | |
michael@0 | 1334 | FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototype, { |
michael@0 | 1335 | /** |
michael@0 | 1336 | * Initialization function, called when the debugger is started. |
michael@0 | 1337 | */ |
michael@0 | 1338 | initialize: function() { |
michael@0 | 1339 | dumpn("Initializing the FilteredFunctionsView"); |
michael@0 | 1340 | |
michael@0 | 1341 | this.anchor = document.getElementById("searchbox"); |
michael@0 | 1342 | this.widget.addEventListener("select", this._onSelect, false); |
michael@0 | 1343 | this.widget.addEventListener("click", this._onClick, false); |
michael@0 | 1344 | }, |
michael@0 | 1345 | |
michael@0 | 1346 | /** |
michael@0 | 1347 | * Destruction function, called when the debugger is closed. |
michael@0 | 1348 | */ |
michael@0 | 1349 | destroy: function() { |
michael@0 | 1350 | dumpn("Destroying the FilteredFunctionsView"); |
michael@0 | 1351 | |
michael@0 | 1352 | this.widget.removeEventListener("select", this._onSelect, false); |
michael@0 | 1353 | this.widget.removeEventListener("click", this._onClick, false); |
michael@0 | 1354 | this.anchor = null; |
michael@0 | 1355 | }, |
michael@0 | 1356 | |
michael@0 | 1357 | /** |
michael@0 | 1358 | * Schedules searching for a function in all of the sources. |
michael@0 | 1359 | * |
michael@0 | 1360 | * @param string aToken |
michael@0 | 1361 | * The function to search for. |
michael@0 | 1362 | * @param number aWait |
michael@0 | 1363 | * The amount of milliseconds to wait until draining. |
michael@0 | 1364 | */ |
michael@0 | 1365 | scheduleSearch: function(aToken, aWait) { |
michael@0 | 1366 | // The amount of time to wait for the requests to settle. |
michael@0 | 1367 | let maxDelay = FUNCTION_SEARCH_ACTION_MAX_DELAY; |
michael@0 | 1368 | let delay = aWait === undefined ? maxDelay / aToken.length : aWait; |
michael@0 | 1369 | |
michael@0 | 1370 | // Allow requests to settle down first. |
michael@0 | 1371 | setNamedTimeout("function-search", delay, () => { |
michael@0 | 1372 | // Start fetching as many sources as possible, then perform the search. |
michael@0 | 1373 | let urls = DebuggerView.Sources.values; |
michael@0 | 1374 | let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(urls); |
michael@0 | 1375 | sourcesFetched.then(aSources => this._doSearch(aToken, aSources)); |
michael@0 | 1376 | }); |
michael@0 | 1377 | }, |
michael@0 | 1378 | |
michael@0 | 1379 | /** |
michael@0 | 1380 | * Finds function matches in all the sources stored in the cache, and groups |
michael@0 | 1381 | * them by location and line number. |
michael@0 | 1382 | * |
michael@0 | 1383 | * @param string aToken |
michael@0 | 1384 | * The string to search for. |
michael@0 | 1385 | * @param array aSources |
michael@0 | 1386 | * An array of [url, text] tuples for each source. |
michael@0 | 1387 | */ |
michael@0 | 1388 | _doSearch: function(aToken, aSources, aStore = []) { |
michael@0 | 1389 | // Continue parsing even if the searched token is an empty string, to |
michael@0 | 1390 | // cache the syntax tree nodes generated by the reflection API. |
michael@0 | 1391 | |
michael@0 | 1392 | // Make sure the currently displayed source is parsed first. Once the |
michael@0 | 1393 | // maximum allowed number of results are found, parsing will be halted. |
michael@0 | 1394 | let currentUrl = DebuggerView.Sources.selectedValue; |
michael@0 | 1395 | let currentSource = aSources.filter(([sourceUrl]) => sourceUrl == currentUrl)[0]; |
michael@0 | 1396 | aSources.splice(aSources.indexOf(currentSource), 1); |
michael@0 | 1397 | aSources.unshift(currentSource); |
michael@0 | 1398 | |
michael@0 | 1399 | // If not searching for a specific function, only parse the displayed source, |
michael@0 | 1400 | // which is now the first item in the sources array. |
michael@0 | 1401 | if (!aToken) { |
michael@0 | 1402 | aSources.splice(1); |
michael@0 | 1403 | } |
michael@0 | 1404 | |
michael@0 | 1405 | for (let [location, contents] of aSources) { |
michael@0 | 1406 | let parsedSource = DebuggerController.Parser.get(contents, location); |
michael@0 | 1407 | let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken); |
michael@0 | 1408 | |
michael@0 | 1409 | for (let scriptResult of sourceResults) { |
michael@0 | 1410 | for (let parseResult of scriptResult) { |
michael@0 | 1411 | aStore.push({ |
michael@0 | 1412 | sourceUrl: scriptResult.sourceUrl, |
michael@0 | 1413 | scriptOffset: scriptResult.scriptOffset, |
michael@0 | 1414 | functionName: parseResult.functionName, |
michael@0 | 1415 | functionLocation: parseResult.functionLocation, |
michael@0 | 1416 | inferredName: parseResult.inferredName, |
michael@0 | 1417 | inferredChain: parseResult.inferredChain, |
michael@0 | 1418 | inferredLocation: parseResult.inferredLocation |
michael@0 | 1419 | }); |
michael@0 | 1420 | |
michael@0 | 1421 | // Once the maximum allowed number of results is reached, proceed |
michael@0 | 1422 | // with building the UI immediately. |
michael@0 | 1423 | if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) { |
michael@0 | 1424 | this._syncView(aStore); |
michael@0 | 1425 | return; |
michael@0 | 1426 | } |
michael@0 | 1427 | } |
michael@0 | 1428 | } |
michael@0 | 1429 | } |
michael@0 | 1430 | |
michael@0 | 1431 | // Couldn't reach the maximum allowed number of results, but that's ok, |
michael@0 | 1432 | // continue building the UI. |
michael@0 | 1433 | this._syncView(aStore); |
michael@0 | 1434 | }, |
michael@0 | 1435 | |
michael@0 | 1436 | /** |
michael@0 | 1437 | * Updates the list of functions displayed in this container. |
michael@0 | 1438 | * |
michael@0 | 1439 | * @param array aSearchResults |
michael@0 | 1440 | * The results array, containing search details for each source. |
michael@0 | 1441 | */ |
michael@0 | 1442 | _syncView: function(aSearchResults) { |
michael@0 | 1443 | // If there are no matches found, keep the popup hidden and avoid |
michael@0 | 1444 | // creating the view. |
michael@0 | 1445 | if (!aSearchResults.length) { |
michael@0 | 1446 | window.emit(EVENTS.FUNCTION_SEARCH_MATCH_NOT_FOUND); |
michael@0 | 1447 | return; |
michael@0 | 1448 | } |
michael@0 | 1449 | |
michael@0 | 1450 | for (let item of aSearchResults) { |
michael@0 | 1451 | // Some function expressions don't necessarily have a name, but the |
michael@0 | 1452 | // parser provides us with an inferred name from an enclosing |
michael@0 | 1453 | // VariableDeclarator, AssignmentExpression, ObjectExpression node. |
michael@0 | 1454 | if (item.functionName && item.inferredName && |
michael@0 | 1455 | item.functionName != item.inferredName) { |
michael@0 | 1456 | let s = " " + L10N.getStr("functionSearchSeparatorLabel") + " "; |
michael@0 | 1457 | item.displayedName = item.inferredName + s + item.functionName; |
michael@0 | 1458 | } |
michael@0 | 1459 | // The function doesn't have an explicit name, but it could be inferred. |
michael@0 | 1460 | else if (item.inferredName) { |
michael@0 | 1461 | item.displayedName = item.inferredName; |
michael@0 | 1462 | } |
michael@0 | 1463 | // The function only has an explicit name. |
michael@0 | 1464 | else { |
michael@0 | 1465 | item.displayedName = item.functionName; |
michael@0 | 1466 | } |
michael@0 | 1467 | |
michael@0 | 1468 | // Some function expressions have unexpected bounds, since they may not |
michael@0 | 1469 | // necessarily have an associated name defining them. |
michael@0 | 1470 | if (item.inferredLocation) { |
michael@0 | 1471 | item.actualLocation = item.inferredLocation; |
michael@0 | 1472 | } else { |
michael@0 | 1473 | item.actualLocation = item.functionLocation; |
michael@0 | 1474 | } |
michael@0 | 1475 | |
michael@0 | 1476 | // Create the element node for the function item. |
michael@0 | 1477 | let itemView = this._createItemView( |
michael@0 | 1478 | SourceUtils.trimUrlLength(item.displayedName + "()"), |
michael@0 | 1479 | SourceUtils.trimUrlLength(item.sourceUrl, 0, "start"), |
michael@0 | 1480 | (item.inferredChain || []).join(".") |
michael@0 | 1481 | ); |
michael@0 | 1482 | |
michael@0 | 1483 | // Append a function item to this container for each match. |
michael@0 | 1484 | this.push([itemView], { |
michael@0 | 1485 | index: -1, /* specifies on which position should the item be appended */ |
michael@0 | 1486 | attachment: item |
michael@0 | 1487 | }); |
michael@0 | 1488 | } |
michael@0 | 1489 | |
michael@0 | 1490 | // There's at least one item displayed in this container. Don't select it |
michael@0 | 1491 | // automatically if not forced (by tests). |
michael@0 | 1492 | if (this._autoSelectFirstItem) { |
michael@0 | 1493 | this.selectedIndex = 0; |
michael@0 | 1494 | } |
michael@0 | 1495 | this.hidden = false; |
michael@0 | 1496 | |
michael@0 | 1497 | // Signal that function search matches were found and displayed. |
michael@0 | 1498 | window.emit(EVENTS.FUNCTION_SEARCH_MATCH_FOUND); |
michael@0 | 1499 | }, |
michael@0 | 1500 | |
michael@0 | 1501 | /** |
michael@0 | 1502 | * The click listener for this container. |
michael@0 | 1503 | */ |
michael@0 | 1504 | _onClick: function(e) { |
michael@0 | 1505 | let functionItem = this.getItemForElement(e.target); |
michael@0 | 1506 | if (functionItem) { |
michael@0 | 1507 | this.selectedItem = functionItem; |
michael@0 | 1508 | DebuggerView.Filtering.clearSearch(); |
michael@0 | 1509 | } |
michael@0 | 1510 | }, |
michael@0 | 1511 | |
michael@0 | 1512 | /** |
michael@0 | 1513 | * The select listener for this container. |
michael@0 | 1514 | */ |
michael@0 | 1515 | _onSelect: function({ detail: functionItem }) { |
michael@0 | 1516 | if (functionItem) { |
michael@0 | 1517 | let sourceUrl = functionItem.attachment.sourceUrl; |
michael@0 | 1518 | let scriptOffset = functionItem.attachment.scriptOffset; |
michael@0 | 1519 | let actualLocation = functionItem.attachment.actualLocation; |
michael@0 | 1520 | |
michael@0 | 1521 | DebuggerView.setEditorLocation(sourceUrl, actualLocation.start.line, { |
michael@0 | 1522 | charOffset: scriptOffset, |
michael@0 | 1523 | columnOffset: actualLocation.start.column, |
michael@0 | 1524 | align: "center", |
michael@0 | 1525 | noDebug: true |
michael@0 | 1526 | }); |
michael@0 | 1527 | } |
michael@0 | 1528 | }, |
michael@0 | 1529 | |
michael@0 | 1530 | _searchTimeout: null, |
michael@0 | 1531 | _searchFunction: null, |
michael@0 | 1532 | _searchedToken: "" |
michael@0 | 1533 | }); |
michael@0 | 1534 | |
michael@0 | 1535 | /** |
michael@0 | 1536 | * Preliminary setup for the DebuggerView object. |
michael@0 | 1537 | */ |
michael@0 | 1538 | DebuggerView.Toolbar = new ToolbarView(); |
michael@0 | 1539 | DebuggerView.Options = new OptionsView(); |
michael@0 | 1540 | DebuggerView.Filtering = new FilterView(); |
michael@0 | 1541 | DebuggerView.FilteredSources = new FilteredSourcesView(); |
michael@0 | 1542 | DebuggerView.FilteredFunctions = new FilteredFunctionsView(); |
michael@0 | 1543 | DebuggerView.StackFrames = new StackFramesView(); |
michael@0 | 1544 | DebuggerView.StackFramesClassicList = new StackFramesClassicListView(); |