browser/devtools/debugger/debugger-toolbar.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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";
     8 // A time interval sufficient for the options popup panel to finish hiding
     9 // itself.
    10 const POPUP_HIDDEN_DELAY = 100; // ms
    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");
    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 }
    26 ToolbarView.prototype = {
    27   /**
    28    * Initialization function, called when the debugger is started.
    29    */
    30   initialize: function() {
    31     dumpn("Initializing the ToolbarView");
    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;
    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);
    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);
    57     this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip);
    58     this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip);
    59     this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip);
    60   },
    62   /**
    63    * Destruction function, called when the debugger is closed.
    64    */
    65   destroy: function() {
    66     dumpn("Destroying the ToolbarView");
    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   },
    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   },
    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   },
   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   },
   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   },
   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   },
   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   },
   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   },
   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 };
   178 /**
   179  * Functions handling the options UI.
   180  */
   181 function OptionsView() {
   182   dumpn("OptionsView was instantiated");
   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 }
   193 OptionsView.prototype = {
   194   /**
   195    * Initialization function, called when the debugger is started.
   196    */
   197   initialize: function() {
   198     dumpn("Initializing the OptionsView");
   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");
   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   },
   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   },
   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   },
   234   /**
   235    * Listener handling the 'gear menu' popup hiding event.
   236    */
   237   _onPopupHiding: function() {
   238     this._button.removeAttribute("open");
   239   },
   241   /**
   242    * Listener handling the 'gear menu' popup hidden event.
   243    */
   244   _onPopupHidden: function() {
   245     window.emit(EVENTS.OPTIONS_POPUP_HIDDEN);
   246   },
   248   /**
   249    * Listener handling the 'auto pretty print' menuitem command.
   250    */
   251   _toggleAutoPrettyPrint: function(){
   252     Prefs.autoPrettyPrint =
   253       this._autoPrettyPrint.getAttribute("checked") == "true";
   254   },
   256   /**
   257    * Listener handling the 'pause on exceptions' menuitem command.
   258    */
   259   _togglePauseOnExceptions: function() {
   260     Prefs.pauseOnExceptions =
   261       this._pauseOnExceptionsItem.getAttribute("checked") == "true";
   263     DebuggerController.activeThread.pauseOnExceptions(
   264       Prefs.pauseOnExceptions,
   265       Prefs.ignoreCaughtExceptions);
   266   },
   268   _toggleIgnoreCaughtExceptions: function() {
   269     Prefs.ignoreCaughtExceptions =
   270       this._ignoreCaughtExceptionsItem.getAttribute("checked") == "true";
   272     DebuggerController.activeThread.pauseOnExceptions(
   273       Prefs.pauseOnExceptions,
   274       Prefs.ignoreCaughtExceptions);
   275   },
   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   },
   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";
   292     DebuggerView.Variables.onlyEnumVisible = pref;
   293   },
   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";
   302     DebuggerView.Variables.searchEnabled = pref;
   303   },
   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";
   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   },
   321   _button: null,
   322   _pauseOnExceptionsItem: null,
   323   _showPanesOnStartupItem: null,
   324   _showVariablesOnlyEnumItem: null,
   325   _showVariablesFilterBoxItem: null,
   326   _showOriginalSourceItem: null
   327 };
   329 /**
   330  * Functions handling the stackframes UI.
   331  */
   332 function StackFramesView() {
   333   dumpn("StackFramesView was instantiated");
   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 }
   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");
   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);
   353     this.autoFocusOnFirstItem = false;
   354     this.autoFocusOnSelection = false;
   356     // This view's contents are also mirrored in a different container.
   357     this._mirror = DebuggerView.StackFramesClassicList;
   358   },
   360   /**
   361    * Destruction function, called when the debugger is closed.
   362    */
   363   destroy: function() {
   364     dumpn("Destroying the StackFramesView");
   366     this.widget.removeEventListener("select", this._onSelect, false);
   367     this.widget.removeEventListener("scroll", this._onScroll, true);
   368     window.removeEventListener("resize", this._onScroll, true);
   369   },
   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     }
   397     // Create the element node for the stack frame item.
   398     let frameView = this._createFrameView.apply(this, arguments);
   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     });
   414     // Mirror this newly inserted item inside the "Call Stack" tab.
   415     this._mirror.addFrame(aTitle, aUrl, aLine, aDepth);
   416   },
   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   },
   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   },
   437   /**
   438    * Specifies if the active thread has more frames that need to be loaded.
   439    */
   440   dirty: false,
   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";
   463     let frameDetails = SourceUtils.trimUrlLength(
   464       SourceUtils.getSourceLabel(aUrl),
   465       STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
   466       STACK_FRAMES_SOURCE_URL_TRIM_SECTION);
   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);
   476       frameDetails += SEARCH_LINE_FLAG + aLine;
   477     }
   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);
   484     return container;
   485   },
   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);
   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));
   500     // Forget the previously blackboxed stack frame url.
   501     this._prevBlackBoxedUrl = null;
   502   },
   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);
   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   },
   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   },
   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");
   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;
   546       // Loads more stack frames from the debugger server cache.
   547       DebuggerController.StackFrames.addMoreFrames();
   548     }
   549   },
   551   _mirror: null,
   552   _prevBlackBoxedUrl: null
   553 });
   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");
   562   this._onSelect = this._onSelect.bind(this);
   563 }
   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");
   572     this.widget = new SideMenuWidget(document.getElementById("callstack-list"));
   573     this.widget.addEventListener("select", this._onSelect, false);
   575     this.emptyText = L10N.getStr("noStackFramesText");
   576     this.autoFocusOnFirstItem = false;
   577     this.autoFocusOnSelection = false;
   579     // This view's contents are also mirrored in a different container.
   580     this._mirror = DebuggerView.StackFrames;
   581   },
   583   /**
   584    * Destruction function, called when the debugger is closed.
   585    */
   586   destroy: function() {
   587     dumpn("Destroying the StackFramesClassicListView");
   589     this.widget.removeEventListener("select", this._onSelect, false);
   590   },
   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);
   608     // Append a stack frame item to this container.
   609     this.push([frameView], {
   610       attachment: {
   611         depth: aDepth
   612       }
   613     });
   614   },
   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");
   636     let frameTitleNode = document.createElement("label");
   637     frameTitleNode.className = "plain dbg-classic-stackframe-title";
   638     frameTitleNode.setAttribute("value", aTitle);
   639     frameTitleNode.setAttribute("crop", "center");
   641     let frameDetailsNode = document.createElement("hbox");
   642     frameDetailsNode.className = "plain dbg-classic-stackframe-details";
   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);
   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);
   655     let frameLineNode = document.createElement("label");
   656     frameLineNode.className = "plain dbg-classic-stackframe-details-line";
   657     frameLineNode.setAttribute("value", aLine);
   658     frameDetailsNode.appendChild(frameLineNode);
   660     container.appendChild(frameTitleNode);
   661     container.appendChild(frameDetailsNode);
   663     return container;
   664   },
   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   },
   679   _mirror: null
   680 });
   682 /**
   683  * Functions handling the filtering UI.
   684  */
   685 function FilterView() {
   686   dumpn("FilterView was instantiated");
   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 }
   694 FilterView.prototype = {
   695   /**
   696    * Initialization function, called when the debugger is started.
   697    */
   698   initialize: function() {
   699     dumpn("Initializing the FilterView");
   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");
   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"));
   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);
   728     let placeholder = L10N.getFormatStr("emptySearchText", this._fileSearchKey);
   729     this._searchbox.setAttribute("placeholder", placeholder);
   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);
   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   },
   751   /**
   752    * Destruction function, called when the debugger is closed.
   753    */
   754   destroy: function() {
   755     dumpn("Destroying the FilterView");
   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   },
   764   /**
   765    * Gets the entered operator and arguments in the searchbox.
   766    * @return array
   767    */
   768   get searchData() {
   769     let operator = "", args = [];
   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);
   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     }
   810     return [operator, args];
   811   },
   813   /**
   814    * Returns the current search operator.
   815    * @return string
   816    */
   817   get searchOperator() this.searchData[0],
   819   /**
   820    * Returns the current search arguments.
   821    * @return array
   822    */
   823   get searchArguments() this.searchData[1],
   825   /**
   826    * Clears the text from the searchbox and any changed views.
   827    */
   828   clearSearch: function() {
   829     this._searchbox.value = "";
   830     this.clearViews();
   831   },
   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   },
   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   },
   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   },
   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   },
   883   /**
   884    * The input listener for the search container.
   885    */
   886   _onInput: function() {
   887     this.clearViews();
   889     // Make sure we're actually searching for something.
   890     if (!this._searchbox.value) {
   891       return;
   892     }
   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   },
   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);
   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;
   941     // Depending on the pressed keys, determine to correct action to perform.
   942     let actionToPerform;
   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     }
   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     }
   975     e.preventDefault();
   976     e.stopPropagation();
   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     }
   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;
  1002         this.clearSearch();
  1004       return;
  1007     // Perform a new variable search immediately.
  1008     if (isVariableSearch) {
  1009       let targetView = DebuggerView.Variables;
  1010       if (isReturnKey) {
  1011         targetView.scheduleSearch(args[0], 0);
  1013       return;
  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;
  1028         this.clearSearch();
  1030       return;
  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;
  1040     // Increment/decrement the currently searched caret line.
  1041     if (isLineSearch) {
  1042       let [, line] = args;
  1043       let amounts = { selectNext: 1, selectPrev: -1 };
  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;
  1052   },
  1054   /**
  1055    * The blur listener for the search container.
  1056    */
  1057   _onBlur: function() {
  1058     this.clearViews();
  1059   },
  1061   /**
  1062    * Called when a filtering key sequence was pressed.
  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.
  1071     if (aText) {
  1072       this._searchbox.value = aOperator + aText;
  1073       return;
  1075     if (DebuggerView.editor.somethingSelected()) {
  1076       this._searchbox.value = aOperator + DebuggerView.editor.getSelection();
  1077       return;
  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 });
  1086       if (identifier && identifier.name) {
  1087         this._searchbox.value = aOperator + identifier.name;
  1088         this._searchbox.select();
  1089         this._searchbox.selectionStart += aOperator.length;
  1090         return;
  1093     this._searchbox.value = aOperator;
  1094   },
  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   },
  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   },
  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   },
  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   },
  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   },
  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   },
  1144   /**
  1145    * Called when the variables focus key sequence was pressed.
  1146    */
  1147   _doVariablesFocus: function() {
  1148     DebuggerView.showInstrumentsPane();
  1149     DebuggerView.Variables.focusFirstVisibleItem();
  1150   },
  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 };
  1172 /**
  1173  * Functions handling the filtered sources UI.
  1174  */
  1175 function FilteredSourcesView() {
  1176   dumpn("FilteredSourcesView was instantiated");
  1178   this._onClick = this._onClick.bind(this);
  1179   this._onSelect = this._onSelect.bind(this);
  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");
  1189     this.anchor = document.getElementById("searchbox");
  1190     this.widget.addEventListener("select", this._onSelect, false);
  1191     this.widget.addEventListener("click", this._onClick, false);
  1192   },
  1194   /**
  1195    * Destruction function, called when the debugger is closed.
  1196    */
  1197   destroy: function() {
  1198     dumpn("Destroying the FilteredSourcesView");
  1200     this.widget.removeEventListener("select", this._onSelect, false);
  1201     this.widget.removeEventListener("click", this._onClick, false);
  1202     this.anchor = null;
  1203   },
  1205   /**
  1206    * Schedules searching for a source.
  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;
  1218     // Allow requests to settle down first.
  1219     setNamedTimeout("sources-search", delay, () => this._doSearch(aToken));
  1220   },
  1222   /**
  1223    * Finds file matches in all the displayed sources.
  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;
  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);
  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;
  1251     // Couldn't reach the maximum allowed number of results, but that's ok,
  1252     // continue building the UI.
  1253     this._syncView(aStore);
  1254   },
  1256   /**
  1257    * Updates the list of sources displayed in this container.
  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;
  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       );
  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
  1283       });
  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;
  1291     this.hidden = false;
  1293     // Signal that file search matches were found and displayed.
  1294     window.emit(EVENTS.FILE_SEARCH_MATCH_FOUND);
  1295   },
  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();
  1306   },
  1308   /**
  1309    * The select listener for this container.
  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       });
  1322 });
  1324 /**
  1325  * Functions handling the function search UI.
  1326  */
  1327 function FilteredFunctionsView() {
  1328   dumpn("FilteredFunctionsView was instantiated");
  1330   this._onClick = this._onClick.bind(this);
  1331   this._onSelect = this._onSelect.bind(this);
  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");
  1341     this.anchor = document.getElementById("searchbox");
  1342     this.widget.addEventListener("select", this._onSelect, false);
  1343     this.widget.addEventListener("click", this._onClick, false);
  1344   },
  1346   /**
  1347    * Destruction function, called when the debugger is closed.
  1348    */
  1349   destroy: function() {
  1350     dumpn("Destroying the FilteredFunctionsView");
  1352     this.widget.removeEventListener("select", this._onSelect, false);
  1353     this.widget.removeEventListener("click", this._onClick, false);
  1354     this.anchor = null;
  1355   },
  1357   /**
  1358    * Schedules searching for a function in all of the sources.
  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;
  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   },
  1379   /**
  1380    * Finds function matches in all the sources stored in the cache, and groups
  1381    * them by location and line number.
  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.
  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);
  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);
  1405     for (let [location, contents] of aSources) {
  1406       let parsedSource = DebuggerController.Parser.get(contents, location);
  1407       let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken);
  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           });
  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;
  1431     // Couldn't reach the maximum allowed number of results, but that's ok,
  1432     // continue building the UI.
  1433     this._syncView(aStore);
  1434   },
  1436   /**
  1437    * Updates the list of functions displayed in this container.
  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;
  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;
  1459       // The function doesn't have an explicit name, but it could be inferred.
  1460       else if (item.inferredName) {
  1461         item.displayedName = item.inferredName;
  1463       // The function only has an explicit name.
  1464       else {
  1465         item.displayedName = item.functionName;
  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;
  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       );
  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       });
  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;
  1495     this.hidden = false;
  1497     // Signal that function search matches were found and displayed.
  1498     window.emit(EVENTS.FUNCTION_SEARCH_MATCH_FOUND);
  1499   },
  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();
  1510   },
  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;
  1521       DebuggerView.setEditorLocation(sourceUrl, actualLocation.start.line, {
  1522         charOffset: scriptOffset,
  1523         columnOffset: actualLocation.start.column,
  1524         align: "center",
  1525         noDebug: true
  1526       });
  1528   },
  1530   _searchTimeout: null,
  1531   _searchFunction: null,
  1532   _searchedToken: ""
  1533 });
  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