browser/devtools/debugger/debugger-toolbar.js

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

mercurial