browser/devtools/debugger/debugger-view.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 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
     9 const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars
    10 const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars
    11 const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center";
    12 const STACK_FRAMES_SCROLL_DELAY = 100; // ms
    13 const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
    14 const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
    15 const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px
    16 const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px
    17 const RESULTS_PANEL_POPUP_POSITION = "before_end";
    18 const RESULTS_PANEL_MAX_RESULTS = 10;
    19 const FILE_SEARCH_ACTION_MAX_DELAY = 300; // ms
    20 const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
    21 const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
    22 const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
    23 const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms
    24 const SEARCH_GLOBAL_FLAG = "!";
    25 const SEARCH_FUNCTION_FLAG = "@";
    26 const SEARCH_TOKEN_FLAG = "#";
    27 const SEARCH_LINE_FLAG = ":";
    28 const SEARCH_VARIABLE_FLAG = "*";
    29 const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
    30 const EDITOR_VARIABLE_HOVER_DELAY = 350; // ms
    31 const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
    32 const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
    34 /**
    35  * Object defining the debugger view components.
    36  */
    37 let DebuggerView = {
    38   /**
    39    * Initializes the debugger view.
    40    *
    41    * @return object
    42    *         A promise that is resolved when the view finishes initializing.
    43    */
    44   initialize: function() {
    45     if (this._startup) {
    46       return this._startup;
    47     }
    49     let deferred = promise.defer();
    50     this._startup = deferred.promise;
    52     this._initializePanes();
    53     this.Toolbar.initialize();
    54     this.Options.initialize();
    55     this.Filtering.initialize();
    56     this.FilteredSources.initialize();
    57     this.FilteredFunctions.initialize();
    58     this.StackFrames.initialize();
    59     this.StackFramesClassicList.initialize();
    60     this.Sources.initialize();
    61     this.VariableBubble.initialize();
    62     this.Tracer.initialize();
    63     this.WatchExpressions.initialize();
    64     this.EventListeners.initialize();
    65     this.GlobalSearch.initialize();
    66     this._initializeVariablesView();
    67     this._initializeEditor(deferred.resolve);
    69     document.title = L10N.getStr("DebuggerWindowTitle");
    71     return deferred.promise;
    72   },
    74   /**
    75    * Destroys the debugger view.
    76    *
    77    * @return object
    78    *         A promise that is resolved when the view finishes destroying.
    79    */
    80   destroy: function() {
    81     if (this._shutdown) {
    82       return this._shutdown;
    83     }
    85     let deferred = promise.defer();
    86     this._shutdown = deferred.promise;
    88     this.Toolbar.destroy();
    89     this.Options.destroy();
    90     this.Filtering.destroy();
    91     this.FilteredSources.destroy();
    92     this.FilteredFunctions.destroy();
    93     this.StackFrames.destroy();
    94     this.StackFramesClassicList.destroy();
    95     this.Sources.destroy();
    96     this.VariableBubble.destroy();
    97     this.Tracer.destroy();
    98     this.WatchExpressions.destroy();
    99     this.EventListeners.destroy();
   100     this.GlobalSearch.destroy();
   101     this._destroyPanes();
   102     this._destroyEditor(deferred.resolve);
   104     return deferred.promise;
   105   },
   107   /**
   108    * Initializes the UI for all the displayed panes.
   109    */
   110   _initializePanes: function() {
   111     dumpn("Initializing the DebuggerView panes");
   113     this._body = document.getElementById("body");
   114     this._editorDeck = document.getElementById("editor-deck");
   115     this._sourcesPane = document.getElementById("sources-pane");
   116     this._instrumentsPane = document.getElementById("instruments-pane");
   117     this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
   119     this.showEditor = this.showEditor.bind(this);
   120     this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this);
   121     this.showProgressBar = this.showProgressBar.bind(this);
   122     this.maybeShowBlackBoxMessage = this.maybeShowBlackBoxMessage.bind(this);
   124     this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this);
   125     this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect);
   127     this._collapsePaneString = L10N.getStr("collapsePanes");
   128     this._expandPaneString = L10N.getStr("expandPanes");
   130     this._sourcesPane.setAttribute("width", Prefs.sourcesWidth);
   131     this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth);
   132     this.toggleInstrumentsPane({ visible: Prefs.panesVisibleOnStartup });
   134     // Side hosts requires a different arrangement of the debugger widgets.
   135     if (gHostType == "side") {
   136       this.handleHostChanged(gHostType);
   137     }
   138   },
   140   /**
   141    * Destroys the UI for all the displayed panes.
   142    */
   143   _destroyPanes: function() {
   144     dumpn("Destroying the DebuggerView panes");
   146     if (gHostType != "side") {
   147       Prefs.sourcesWidth = this._sourcesPane.getAttribute("width");
   148       Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width");
   149     }
   151     this._sourcesPane = null;
   152     this._instrumentsPane = null;
   153     this._instrumentsPaneToggleButton = null;
   154   },
   156   /**
   157    * Initializes the VariablesView instance and attaches a controller.
   158    */
   159   _initializeVariablesView: function() {
   160     this.Variables = new VariablesView(document.getElementById("variables"), {
   161       searchPlaceholder: L10N.getStr("emptyVariablesFilterText"),
   162       emptyText: L10N.getStr("emptyVariablesText"),
   163       onlyEnumVisible: Prefs.variablesOnlyEnumVisible,
   164       searchEnabled: Prefs.variablesSearchboxVisible,
   165       eval: (variable, value) => {
   166         let string = variable.evaluationMacro(variable, value);
   167         DebuggerController.StackFrames.evaluate(string);
   168       },
   169       lazyEmpty: true
   170     });
   172     // Attach the current toolbox to the VView so it can link DOMNodes to
   173     // the inspector/highlighter
   174     this.Variables.toolbox = DebuggerController._toolbox;
   176     // Attach a controller that handles interfacing with the debugger protocol.
   177     VariablesViewController.attach(this.Variables, {
   178       getEnvironmentClient: aObject => gThreadClient.environment(aObject),
   179       getObjectClient: aObject => {
   180         return aObject instanceof DebuggerController.Tracer.WrappedObject
   181           ? DebuggerController.Tracer.syncGripClient(aObject.object)
   182           : gThreadClient.pauseGrip(aObject)
   183       }
   184     });
   186     // Relay events from the VariablesView.
   187     this.Variables.on("fetched", (aEvent, aType) => {
   188       switch (aType) {
   189         case "scopes":
   190           window.emit(EVENTS.FETCHED_SCOPES);
   191           break;
   192         case "variables":
   193           window.emit(EVENTS.FETCHED_VARIABLES);
   194           break;
   195         case "properties":
   196           window.emit(EVENTS.FETCHED_PROPERTIES);
   197           break;
   198       }
   199     });
   200   },
   202   /**
   203    * Initializes the Editor instance.
   204    *
   205    * @param function aCallback
   206    *        Called after the editor finishes initializing.
   207    */
   208   _initializeEditor: function(aCallback) {
   209     dumpn("Initializing the DebuggerView editor");
   211     let extraKeys = {};
   212     bindKey("_doTokenSearch", "tokenSearchKey");
   213     bindKey("_doGlobalSearch", "globalSearchKey", { alt: true });
   214     bindKey("_doFunctionSearch", "functionSearchKey");
   215     extraKeys[Editor.keyFor("jumpToLine")] = false;
   217     function bindKey(func, key, modifiers = {}) {
   218       let key = document.getElementById(key).getAttribute("key");
   219       let shortcut = Editor.accel(key, modifiers);
   220       extraKeys[shortcut] = () => DebuggerView.Filtering[func]();
   221     }
   223     this.editor = new Editor({
   224       mode: Editor.modes.text,
   225       readOnly: true,
   226       lineNumbers: true,
   227       showAnnotationRuler: true,
   228       gutters: [ "breakpoints" ],
   229       extraKeys: extraKeys,
   230       contextMenu: "sourceEditorContextMenu"
   231     });
   233     this.editor.appendTo(document.getElementById("editor")).then(() => {
   234       this.editor.extend(DebuggerEditor);
   235       this._loadingText = L10N.getStr("loadingText");
   236       this._onEditorLoad(aCallback);
   237     });
   239     this.editor.on("gutterClick", (ev, line) => {
   240       if (this.editor.hasBreakpoint(line)) {
   241         this.editor.removeBreakpoint(line);
   242       } else {
   243         this.editor.addBreakpoint(line);
   244       }
   245     });
   246   },
   248   /**
   249    * The load event handler for the source editor, also executing any necessary
   250    * post-load operations.
   251    *
   252    * @param function aCallback
   253    *        Called after the editor finishes loading.
   254    */
   255   _onEditorLoad: function(aCallback) {
   256     dumpn("Finished loading the DebuggerView editor");
   258     DebuggerController.Breakpoints.initialize().then(() => {
   259       window.emit(EVENTS.EDITOR_LOADED, this.editor);
   260       aCallback();
   261     });
   262   },
   264   /**
   265    * Destroys the Editor instance and also executes any necessary
   266    * post-unload operations.
   267    *
   268    * @param function aCallback
   269    *        Called after the editor finishes destroying.
   270    */
   271   _destroyEditor: function(aCallback) {
   272     dumpn("Destroying the DebuggerView editor");
   274     DebuggerController.Breakpoints.destroy().then(() => {
   275       window.emit(EVENTS.EDITOR_UNLOADED, this.editor);
   276       this.editor.destroy();
   277       this.editor = null;
   278       aCallback();
   279     });
   280   },
   282   /**
   283    * Display the source editor.
   284    */
   285   showEditor: function() {
   286     this._editorDeck.selectedIndex = 0;
   287   },
   289   /**
   290    * Display the black box message.
   291    */
   292   showBlackBoxMessage: function() {
   293     this._editorDeck.selectedIndex = 1;
   294   },
   296   /**
   297    * Display the progress bar.
   298    */
   299   showProgressBar: function() {
   300     this._editorDeck.selectedIndex = 2;
   301   },
   303   /**
   304    * Show or hide the black box message vs. source editor depending on if the
   305    * selected source is black boxed or not.
   306    */
   307   maybeShowBlackBoxMessage: function() {
   308     let { source } = DebuggerView.Sources.selectedItem.attachment;
   309     if (gThreadClient.source(source).isBlackBoxed) {
   310       this.showBlackBoxMessage();
   311     } else {
   312       this.showEditor();
   313     }
   314   },
   316   /**
   317    * Sets the currently displayed text contents in the source editor.
   318    * This resets the mode and undo stack.
   319    *
   320    * @param string aTextContent
   321    *        The source text content.
   322    */
   323   _setEditorText: function(aTextContent = "") {
   324     this.editor.setMode(Editor.modes.text);
   325     this.editor.setText(aTextContent);
   326     this.editor.clearDebugLocation();
   327     this.editor.clearHistory();
   328   },
   330   /**
   331    * Sets the proper editor mode (JS or HTML) according to the specified
   332    * content type, or by determining the type from the url or text content.
   333    *
   334    * @param string aUrl
   335    *        The source url.
   336    * @param string aContentType [optional]
   337    *        The source content type.
   338    * @param string aTextContent [optional]
   339    *        The source text content.
   340    */
   341   _setEditorMode: function(aUrl, aContentType = "", aTextContent = "") {
   342     // Avoid setting the editor mode for very large files.
   343     // Is this still necessary? See bug 929225.
   344     if (aTextContent.length >= SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
   345       return void this.editor.setMode(Editor.modes.text);
   346     }
   348     // Use JS mode for files with .js and .jsm extensions.
   349     if (SourceUtils.isJavaScript(aUrl, aContentType)) {
   350       return void this.editor.setMode(Editor.modes.js);
   351     }
   353     // Use HTML mode for files in which the first non whitespace character is
   354     // &lt;, regardless of extension.
   355     if (aTextContent.match(/^\s*</)) {
   356       return void this.editor.setMode(Editor.modes.html);
   357     }
   359     // Unknown language, use text.
   360     this.editor.setMode(Editor.modes.text);
   361   },
   363   /**
   364    * Sets the currently displayed source text in the editor.
   365    *
   366    * You should use DebuggerView.updateEditor instead. It updates the current
   367    * caret and debug location based on a requested url and line.
   368    *
   369    * @param object aSource
   370    *        The source object coming from the active thread.
   371    * @param object aFlags
   372    *        Additional options for setting the source. Supported options:
   373    *          - force: boolean allowing whether we can get the selected url's
   374    *                   text again.
   375    * @return object
   376    *         A promise that is resolved after the source text has been set.
   377    */
   378   _setEditorSource: function(aSource, aFlags={}) {
   379     // Avoid setting the same source text in the editor again.
   380     if (this._editorSource.url == aSource.url && !aFlags.force) {
   381       return this._editorSource.promise;
   382     }
   383     let transportType = gClient.localTransport ? "_LOCAL" : "_REMOTE";
   384     let histogramId = "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE" + transportType + "_MS";
   385     let histogram = Services.telemetry.getHistogramById(histogramId);
   386     let startTime = Date.now();
   388     let deferred = promise.defer();
   390     this._setEditorText(L10N.getStr("loadingText"));
   391     this._editorSource = { url: aSource.url, promise: deferred.promise };
   393     DebuggerController.SourceScripts.getText(aSource).then(([, aText, aContentType]) => {
   394       // Avoid setting an unexpected source. This may happen when switching
   395       // very fast between sources that haven't been fetched yet.
   396       if (this._editorSource.url != aSource.url) {
   397         return;
   398       }
   400       this._setEditorText(aText);
   401       this._setEditorMode(aSource.url, aContentType, aText);
   403       // Synchronize any other components with the currently displayed source.
   404       DebuggerView.Sources.selectedValue = aSource.url;
   405       DebuggerController.Breakpoints.updateEditorBreakpoints();
   407       histogram.add(Date.now() - startTime);
   409       // Resolve and notify that a source file was shown.
   410       window.emit(EVENTS.SOURCE_SHOWN, aSource);
   411       deferred.resolve([aSource, aText, aContentType]);
   412     },
   413     ([, aError]) => {
   414       let msg = L10N.getStr("errorLoadingText") + DevToolsUtils.safeErrorString(aError);
   415       this._setEditorText(msg);
   416       Cu.reportError(msg);
   417       dumpn(msg);
   419       // Reject and notify that there was an error showing the source file.
   420       window.emit(EVENTS.SOURCE_ERROR_SHOWN, aSource);
   421       deferred.reject([aSource, aError]);
   422     });
   424     return deferred.promise;
   425   },
   427   /**
   428    * Update the source editor's current caret and debug location based on
   429    * a requested url and line.
   430    *
   431    * @param string aUrl
   432    *        The target source url.
   433    * @param number aLine [optional]
   434    *        The target line in the source.
   435    * @param object aFlags [optional]
   436    *        Additional options for showing the source. Supported options:
   437    *          - charOffset: character offset for the caret or debug location
   438    *          - lineOffset: line offset for the caret or debug location
   439    *          - columnOffset: column offset for the caret or debug location
   440    *          - noCaret: don't set the caret location at the specified line
   441    *          - noDebug: don't set the debug location at the specified line
   442    *          - align: string specifying whether to align the specified line
   443    *                   at the "top", "center" or "bottom" of the editor
   444    *          - force: boolean allowing whether we can get the selected url's
   445    *                   text again
   446    * @return object
   447    *         A promise that is resolved after the source text has been set.
   448    */
   449   setEditorLocation: function(aUrl, aLine = 0, aFlags = {}) {
   450     // Avoid trying to set a source for a url that isn't known yet.
   451     if (!this.Sources.containsValue(aUrl)) {
   452       return promise.reject(new Error("Unknown source for the specified URL."));
   453     }
   455     // If the line is not specified, default to the current frame's position,
   456     // if available and the frame's url corresponds to the requested url.
   457     if (!aLine) {
   458       let cachedFrames = DebuggerController.activeThread.cachedFrames;
   459       let currentDepth = DebuggerController.StackFrames.currentFrameDepth;
   460       let frame = cachedFrames[currentDepth];
   461       if (frame && frame.where.url == aUrl) {
   462         aLine = frame.where.line;
   463       }
   464     }
   466     let sourceItem = this.Sources.getItemByValue(aUrl);
   467     let sourceForm = sourceItem.attachment.source;
   469     // Make sure the requested source client is shown in the editor, then
   470     // update the source editor's caret position and debug location.
   471     return this._setEditorSource(sourceForm, aFlags).then(([,, aContentType]) => {
   472       // Record the contentType learned from fetching
   473       sourceForm.contentType = aContentType;
   474       // Line numbers in the source editor should start from 1. If invalid
   475       // or not specified, then don't do anything.
   476       if (aLine < 1) {
   477         window.emit(EVENTS.EDITOR_LOCATION_SET);
   478         return;
   479       }
   480       if (aFlags.charOffset) {
   481         aLine += this.editor.getPosition(aFlags.charOffset).line;
   482       }
   483       if (aFlags.lineOffset) {
   484         aLine += aFlags.lineOffset;
   485       }
   486       if (!aFlags.noCaret) {
   487         let location = { line: aLine -1, ch: aFlags.columnOffset || 0 };
   488         this.editor.setCursor(location, aFlags.align);
   489       }
   490       if (!aFlags.noDebug) {
   491         this.editor.setDebugLocation(aLine - 1);
   492       }
   493       window.emit(EVENTS.EDITOR_LOCATION_SET);
   494     }).then(null, console.error);
   495   },
   497   /**
   498    * Gets the visibility state of the instruments pane.
   499    * @return boolean
   500    */
   501   get instrumentsPaneHidden()
   502     this._instrumentsPane.hasAttribute("pane-collapsed"),
   504   /**
   505    * Gets the currently selected tab in the instruments pane.
   506    * @return string
   507    */
   508   get instrumentsPaneTab()
   509     this._instrumentsPane.selectedTab.id,
   511   /**
   512    * Sets the instruments pane hidden or visible.
   513    *
   514    * @param object aFlags
   515    *        An object containing some of the following properties:
   516    *        - visible: true if the pane should be shown, false to hide
   517    *        - animated: true to display an animation on toggle
   518    *        - delayed: true to wait a few cycles before toggle
   519    *        - callback: a function to invoke when the toggle finishes
   520    * @param number aTabIndex [optional]
   521    *        The index of the intended selected tab in the details pane.
   522    */
   523   toggleInstrumentsPane: function(aFlags, aTabIndex) {
   524     let pane = this._instrumentsPane;
   525     let button = this._instrumentsPaneToggleButton;
   527     ViewHelpers.togglePane(aFlags, pane);
   529     if (aFlags.visible) {
   530       button.removeAttribute("pane-collapsed");
   531       button.setAttribute("tooltiptext", this._collapsePaneString);
   532     } else {
   533       button.setAttribute("pane-collapsed", "");
   534       button.setAttribute("tooltiptext", this._expandPaneString);
   535     }
   537     if (aTabIndex !== undefined) {
   538       pane.selectedIndex = aTabIndex;
   539     }
   540   },
   542   /**
   543    * Sets the instruments pane visible after a short period of time.
   544    *
   545    * @param function aCallback
   546    *        A function to invoke when the toggle finishes.
   547    */
   548   showInstrumentsPane: function(aCallback) {
   549     DebuggerView.toggleInstrumentsPane({
   550       visible: true,
   551       animated: true,
   552       delayed: true,
   553       callback: aCallback
   554     }, 0);
   555   },
   557   /**
   558    * Handles a tab selection event on the instruments pane.
   559    */
   560   _onInstrumentsPaneTabSelect: function() {
   561     if (this._instrumentsPane.selectedTab.id == "events-tab") {
   562       DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch();
   563     }
   564   },
   566   /**
   567    * Handles a host change event issued by the parent toolbox.
   568    *
   569    * @param string aType
   570    *        The host type, either "bottom", "side" or "window".
   571    */
   572   handleHostChanged: function(aType) {
   573     let newLayout = "";
   575     if (aType == "side") {
   576       newLayout = "vertical";
   577       this._enterVerticalLayout();
   578     } else {
   579       newLayout = "horizontal";
   580       this._enterHorizontalLayout();
   581     }
   583     this._hostType = aType;
   584     this._body.setAttribute("layout", newLayout);
   585     window.emit(EVENTS.LAYOUT_CHANGED, newLayout);
   586   },
   588   /**
   589    * Switches the debugger widgets to a horizontal layout.
   590    */
   591   _enterVerticalLayout: function() {
   592     let normContainer = document.getElementById("debugger-widgets");
   593     let vertContainer = document.getElementById("vertical-layout-panes-container");
   595     // Move the soruces and instruments panes in a different container.
   596     let splitter = document.getElementById("sources-and-instruments-splitter");
   597     vertContainer.insertBefore(this._sourcesPane, splitter);
   598     vertContainer.appendChild(this._instrumentsPane);
   600     // Make sure the vertical layout container's height doesn't repeatedly
   601     // grow or shrink based on the displayed sources, variables etc.
   602     vertContainer.setAttribute("height",
   603       vertContainer.getBoundingClientRect().height);
   604   },
   606   /**
   607    * Switches the debugger widgets to a vertical layout.
   608    */
   609   _enterHorizontalLayout: function() {
   610     let normContainer = document.getElementById("debugger-widgets");
   611     let vertContainer = document.getElementById("vertical-layout-panes-container");
   613     // The sources and instruments pane need to be inserted at their
   614     // previous locations in their normal container.
   615     let splitter = document.getElementById("sources-and-editor-splitter");
   616     normContainer.insertBefore(this._sourcesPane, splitter);
   617     normContainer.appendChild(this._instrumentsPane);
   619     // Revert to the preferred sources and instruments widths, because
   620     // they flexed in the vertical layout.
   621     this._sourcesPane.setAttribute("width", Prefs.sourcesWidth);
   622     this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth);
   623   },
   625   /**
   626    * Handles any initialization on a tab navigation event issued by the client.
   627    */
   628   handleTabNavigation: function() {
   629     dumpn("Handling tab navigation in the DebuggerView");
   631     this.Filtering.clearSearch();
   632     this.FilteredSources.clearView();
   633     this.FilteredFunctions.clearView();
   634     this.GlobalSearch.clearView();
   635     this.StackFrames.empty();
   636     this.Sources.empty();
   637     this.Variables.empty();
   638     this.EventListeners.empty();
   640     if (this.editor) {
   641       this.editor.setMode(Editor.modes.text);
   642       this.editor.setText("");
   643       this.editor.clearHistory();
   644       this._editorSource = {};
   645     }
   647     this.Sources.emptyText = L10N.getStr("loadingSourcesText");
   648   },
   650   _startup: null,
   651   _shutdown: null,
   652   Toolbar: null,
   653   Options: null,
   654   Filtering: null,
   655   FilteredSources: null,
   656   FilteredFunctions: null,
   657   GlobalSearch: null,
   658   StackFrames: null,
   659   Sources: null,
   660   Tracer: null,
   661   Variables: null,
   662   VariableBubble: null,
   663   WatchExpressions: null,
   664   EventListeners: null,
   665   editor: null,
   666   _editorSource: {},
   667   _loadingText: "",
   668   _body: null,
   669   _editorDeck: null,
   670   _sourcesPane: null,
   671   _instrumentsPane: null,
   672   _instrumentsPaneToggleButton: null,
   673   _collapsePaneString: "",
   674   _expandPaneString: ""
   675 };
   677 /**
   678  * A custom items container, used for displaying views like the
   679  * FilteredSources, FilteredFunctions etc., inheriting the generic WidgetMethods.
   680  */
   681 function ResultsPanelContainer() {
   682 }
   684 ResultsPanelContainer.prototype = Heritage.extend(WidgetMethods, {
   685   /**
   686    * Sets the anchor node for this container panel.
   687    * @param nsIDOMNode aNode
   688    */
   689   set anchor(aNode) {
   690     this._anchor = aNode;
   692     // If the anchor node is not null, create a panel to attach to the anchor
   693     // when showing the popup.
   694     if (aNode) {
   695       if (!this._panel) {
   696         this._panel = document.createElement("panel");
   697         this._panel.id = "results-panel";
   698         this._panel.setAttribute("level", "top");
   699         this._panel.setAttribute("noautofocus", "true");
   700         this._panel.setAttribute("consumeoutsideclicks", "false");
   701         document.documentElement.appendChild(this._panel);
   702       }
   703       if (!this.widget) {
   704         this.widget = new SimpleListWidget(this._panel);
   705         this.autoFocusOnFirstItem = false;
   706         this.autoFocusOnSelection = false;
   707         this.maintainSelectionVisible = false;
   708       }
   709     }
   710     // Cleanup the anchor and remove the previously created panel.
   711     else {
   712       this._panel.remove();
   713       this._panel = null;
   714       this.widget = null;
   715     }
   716   },
   718   /**
   719    * Gets the anchor node for this container panel.
   720    * @return nsIDOMNode
   721    */
   722   get anchor() {
   723     return this._anchor;
   724   },
   726   /**
   727    * Sets the container panel hidden or visible. It's hidden by default.
   728    * @param boolean aFlag
   729    */
   730   set hidden(aFlag) {
   731     if (aFlag) {
   732       this._panel.hidden = true;
   733       this._panel.hidePopup();
   734     } else {
   735       this._panel.hidden = false;
   736       this._panel.openPopup(this._anchor, this.position, this.left, this.top);
   737     }
   738   },
   740   /**
   741    * Gets this container's visibility state.
   742    * @return boolean
   743    */
   744   get hidden()
   745     this._panel.state == "closed" ||
   746     this._panel.state == "hiding",
   748   /**
   749    * Removes all items from this container and hides it.
   750    */
   751   clearView: function() {
   752     this.hidden = true;
   753     this.empty();
   754   },
   756   /**
   757    * Selects the next found item in this container.
   758    * Does not change the currently focused node.
   759    */
   760   selectNext: function() {
   761     let nextIndex = this.selectedIndex + 1;
   762     if (nextIndex >= this.itemCount) {
   763       nextIndex = 0;
   764     }
   765     this.selectedItem = this.getItemAtIndex(nextIndex);
   766   },
   768   /**
   769    * Selects the previously found item in this container.
   770    * Does not change the currently focused node.
   771    */
   772   selectPrev: function() {
   773     let prevIndex = this.selectedIndex - 1;
   774     if (prevIndex < 0) {
   775       prevIndex = this.itemCount - 1;
   776     }
   777     this.selectedItem = this.getItemAtIndex(prevIndex);
   778   },
   780   /**
   781    * Customization function for creating an item's UI.
   782    *
   783    * @param string aLabel
   784    *        The item's label string.
   785    * @param string aBeforeLabel
   786    *        An optional string shown before the label.
   787    * @param string aBelowLabel
   788    *        An optional string shown underneath the label.
   789    */
   790   _createItemView: function(aLabel, aBelowLabel, aBeforeLabel) {
   791     let container = document.createElement("vbox");
   792     container.className = "results-panel-item";
   794     let firstRowLabels = document.createElement("hbox");
   795     let secondRowLabels = document.createElement("hbox");
   797     if (aBeforeLabel) {
   798       let beforeLabelNode = document.createElement("label");
   799       beforeLabelNode.className = "plain results-panel-item-label-before";
   800       beforeLabelNode.setAttribute("value", aBeforeLabel);
   801       firstRowLabels.appendChild(beforeLabelNode);
   802     }
   804     let labelNode = document.createElement("label");
   805     labelNode.className = "plain results-panel-item-label";
   806     labelNode.setAttribute("value", aLabel);
   807     firstRowLabels.appendChild(labelNode);
   809     if (aBelowLabel) {
   810       let belowLabelNode = document.createElement("label");
   811       belowLabelNode.className = "plain results-panel-item-label-below";
   812       belowLabelNode.setAttribute("value", aBelowLabel);
   813       secondRowLabels.appendChild(belowLabelNode);
   814     }
   816     container.appendChild(firstRowLabels);
   817     container.appendChild(secondRowLabels);
   819     return container;
   820   },
   822   _anchor: null,
   823   _panel: null,
   824   position: RESULTS_PANEL_POPUP_POSITION,
   825   left: 0,
   826   top: 0
   827 });

mercurial