forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1517 lines
		
	
	
	
		
			46 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1517 lines
		
	
	
	
		
			46 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
"use strict";
 | 
						|
 | 
						|
const SOURCE_URL_MAX_LENGTH = 64; // chars
 | 
						|
const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
 | 
						|
const PANES_APPEARANCE_DELAY = 50; // ms
 | 
						|
const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
 | 
						|
const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "after_start";
 | 
						|
const BREAKPOINT_CONDITIONAL_POPUP_OFFSET = 50; // px
 | 
						|
const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
 | 
						|
const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
 | 
						|
const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
 | 
						|
const SEARCH_GLOBAL_FLAG = "!";
 | 
						|
const SEARCH_TOKEN_FLAG = "#";
 | 
						|
const SEARCH_LINE_FLAG = ":";
 | 
						|
const SEARCH_VARIABLE_FLAG = "*";
 | 
						|
 | 
						|
/**
 | 
						|
 * Object defining the debugger view components.
 | 
						|
 */
 | 
						|
let DebuggerView = {
 | 
						|
  /**
 | 
						|
   * Initializes the debugger view.
 | 
						|
   *
 | 
						|
   * @param function aCallback
 | 
						|
   *        Called after the view finishes initializing.
 | 
						|
   */
 | 
						|
  initialize: function DV_initialize(aCallback) {
 | 
						|
    dumpn("Initializing the DebuggerView");
 | 
						|
 | 
						|
    this._initializeWindow();
 | 
						|
    this._initializePanes();
 | 
						|
 | 
						|
    this.Toolbar.initialize();
 | 
						|
    this.Options.initialize();
 | 
						|
    this.ChromeGlobals.initialize();
 | 
						|
    this.Sources.initialize();
 | 
						|
    this.Filtering.initialize();
 | 
						|
    this.StackFrames.initialize();
 | 
						|
    this.Breakpoints.initialize();
 | 
						|
    this.WatchExpressions.initialize();
 | 
						|
    this.GlobalSearch.initialize();
 | 
						|
 | 
						|
    this.Variables = new VariablesView(document.getElementById("variables"));
 | 
						|
    this.Variables.searchPlaceholder = L10N.getStr("emptyVariablesFilterText");
 | 
						|
    this.Variables.emptyText = L10N.getStr("emptyVariablesText");
 | 
						|
    this.Variables.nonEnumVisible = Prefs.variablesNonEnumVisible;
 | 
						|
    this.Variables.searchEnabled = Prefs.variablesSearchboxVisible;
 | 
						|
    this.Variables.eval = DebuggerController.StackFrames.evaluate;
 | 
						|
    this.Variables.lazyEmpty = true;
 | 
						|
 | 
						|
    this._initializeEditor(aCallback);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Destroys the debugger view.
 | 
						|
   *
 | 
						|
   * @param function aCallback
 | 
						|
   *        Called after the view finishes destroying.
 | 
						|
   */
 | 
						|
  destroy: function DV_destroy(aCallback) {
 | 
						|
    dumpn("Destroying the DebuggerView");
 | 
						|
 | 
						|
    this.Toolbar.destroy();
 | 
						|
    this.Options.destroy();
 | 
						|
    this.ChromeGlobals.destroy();
 | 
						|
    this.Sources.destroy();
 | 
						|
    this.Filtering.destroy();
 | 
						|
    this.StackFrames.destroy();
 | 
						|
    this.Breakpoints.destroy();
 | 
						|
    this.WatchExpressions.destroy();
 | 
						|
    this.GlobalSearch.destroy();
 | 
						|
 | 
						|
    this._destroyWindow();
 | 
						|
    this._destroyPanes();
 | 
						|
    this._destroyEditor();
 | 
						|
    aCallback();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Initializes the UI for the window.
 | 
						|
   */
 | 
						|
  _initializeWindow: function DV__initializeWindow() {
 | 
						|
    dumpn("Initializing the DebuggerView window");
 | 
						|
 | 
						|
    let isRemote = window._isRemoteDebugger;
 | 
						|
    let isChrome = window._isChromeDebugger;
 | 
						|
 | 
						|
    if (isRemote || isChrome) {
 | 
						|
      window.moveTo(Prefs.windowX, Prefs.windowY);
 | 
						|
      window.resizeTo(Prefs.windowWidth, Prefs.windowHeight);
 | 
						|
 | 
						|
      if (isRemote) {
 | 
						|
        document.title = L10N.getStr("remoteDebuggerWindowTitle");
 | 
						|
      } else {
 | 
						|
        document.title = L10N.getStr("chromeDebuggerWindowTitle");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Destroys the UI for the window.
 | 
						|
   */
 | 
						|
  _destroyWindow: function DV__initializeWindow() {
 | 
						|
    dumpn("Destroying the DebuggerView window");
 | 
						|
 | 
						|
    if (window._isRemoteDebugger || window._isChromeDebugger) {
 | 
						|
      Prefs.windowX = window.screenX;
 | 
						|
      Prefs.windowY = window.screenY;
 | 
						|
      Prefs.windowWidth = window.outerWidth;
 | 
						|
      Prefs.windowHeight = window.outerHeight;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Initializes the UI for all the displayed panes.
 | 
						|
   */
 | 
						|
  _initializePanes: function DV__initializePanes() {
 | 
						|
    dumpn("Initializing the DebuggerView panes");
 | 
						|
 | 
						|
    this._togglePanesButton = document.getElementById("toggle-panes");
 | 
						|
    this._stackframesAndBreakpoints = document.getElementById("stackframes+breakpoints");
 | 
						|
    this._variablesAndExpressions = document.getElementById("variables+expressions");
 | 
						|
 | 
						|
    this._stackframesAndBreakpoints.setAttribute("width", Prefs.stackframesWidth);
 | 
						|
    this._variablesAndExpressions.setAttribute("width", Prefs.variablesWidth);
 | 
						|
    this.togglePanes({
 | 
						|
      visible: Prefs.panesVisibleOnStartup,
 | 
						|
      animated: false
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Destroys the UI for all the displayed panes.
 | 
						|
   */
 | 
						|
  _destroyPanes: function DV__initializePanes() {
 | 
						|
    dumpn("Destroying the DebuggerView panes");
 | 
						|
 | 
						|
    Prefs.stackframesWidth = this._stackframesAndBreakpoints.getAttribute("width");
 | 
						|
    Prefs.variablesWidth = this._variablesAndExpressions.getAttribute("width");
 | 
						|
 | 
						|
    this._togglePanesButton = null;
 | 
						|
    this._stackframesAndBreakpoints = null;
 | 
						|
    this._variablesAndExpressions = null;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Initializes the SourceEditor instance.
 | 
						|
   *
 | 
						|
   * @param function aCallback
 | 
						|
   *        Called after the editor finishes initializing.
 | 
						|
   */
 | 
						|
  _initializeEditor: function DV__initializeEditor(aCallback) {
 | 
						|
    dumpn("Initializing the DebuggerView editor");
 | 
						|
 | 
						|
    let placeholder = document.getElementById("editor");
 | 
						|
    let config = {
 | 
						|
      mode: SourceEditor.MODES.JAVASCRIPT,
 | 
						|
      readOnly: true,
 | 
						|
      showLineNumbers: true,
 | 
						|
      showAnnotationRuler: true,
 | 
						|
      showOverviewRuler: true
 | 
						|
    };
 | 
						|
 | 
						|
    this.editor = new SourceEditor();
 | 
						|
    this.editor.init(placeholder, config, function() {
 | 
						|
      this._onEditorLoad();
 | 
						|
      aCallback();
 | 
						|
    }.bind(this));
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * The load event handler for the source editor, also executing any necessary
 | 
						|
   * post-load operations.
 | 
						|
   */
 | 
						|
  _onEditorLoad: function DV__onEditorLoad() {
 | 
						|
    dumpn("Finished loading the DebuggerView editor");
 | 
						|
 | 
						|
    DebuggerController.Breakpoints.initialize();
 | 
						|
    window.dispatchEvent("Debugger:EditorLoaded", this.editor);
 | 
						|
    this.editor.focus();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Destroys the SourceEditor instance and also executes any necessary
 | 
						|
   * post-unload operations.
 | 
						|
   */
 | 
						|
  _destroyEditor: function DV__destroyEditor() {
 | 
						|
    dumpn("Destroying the DebuggerView editor");
 | 
						|
 | 
						|
    DebuggerController.Breakpoints.destroy();
 | 
						|
    window.dispatchEvent("Debugger:EditorUnloaded", this.editor);
 | 
						|
    this.editor = null;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the proper editor mode (JS or HTML) according to the specified
 | 
						|
   * content type, or by determining the type from the url.
 | 
						|
   *
 | 
						|
   * @param string aUrl
 | 
						|
   *        The script url.
 | 
						|
   * @param string aContentType [optional]
 | 
						|
   *        The script content type.
 | 
						|
   * @param string aTextContent [optional]
 | 
						|
   *        The script text content.
 | 
						|
   */
 | 
						|
  setEditorMode:
 | 
						|
  function DV_setEditorMode(aUrl, aContentType = "", aTextContent = "") {
 | 
						|
    if (!this.editor) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    dumpn("Setting the DebuggerView editor mode: " + aUrl +
 | 
						|
          ", content type: " + aContentType);
 | 
						|
 | 
						|
    if (aContentType) {
 | 
						|
      if (/javascript/.test(aContentType)) {
 | 
						|
        this.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
 | 
						|
      } else {
 | 
						|
        this.editor.setMode(SourceEditor.MODES.HTML);
 | 
						|
      }
 | 
						|
    } else if (aTextContent.match(/^\s*</)) {
 | 
						|
      // Use HTML mode for files in which the first non whitespace character is
 | 
						|
      // <, regardless of extension.
 | 
						|
      this.editor.setMode(SourceEditor.MODES.HTML);
 | 
						|
    } else {
 | 
						|
      // Use JS mode for files with .js and .jsm extensions.
 | 
						|
      if (/\.jsm?$/.test(SourceUtils.trimUrlQuery(aUrl))) {
 | 
						|
        this.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
 | 
						|
      } else {
 | 
						|
        this.editor.setMode(SourceEditor.MODES.TEXT);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Load the editor with the specified source text.
 | 
						|
   *
 | 
						|
   * @param object aSource
 | 
						|
   *        The source object coming from the active thread.
 | 
						|
   * @param object aOptions [optional]
 | 
						|
   *        Additional options for showing the source. Supported options:
 | 
						|
   *        - caretLine: place the caret position at the given line number
 | 
						|
   *        - debugLine: place the debug location at the given line number
 | 
						|
   *        - callback: function called when the source is shown
 | 
						|
   */
 | 
						|
  setEditorSource: function DV_setEditorSource(aSource, aOptions = {}) {
 | 
						|
    if (!this.editor) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    dumpn("Setting the DebuggerView editor source: " + aSource.url +
 | 
						|
          ", loaded: " + aSource.loaded +
 | 
						|
          ", options: " + aOptions.toSource());
 | 
						|
 | 
						|
    // If the source is not loaded, display a placeholder text.
 | 
						|
    if (!aSource.loaded) {
 | 
						|
      this.editor.setMode(SourceEditor.MODES.TEXT);
 | 
						|
      this.editor.setText(L10N.getStr("loadingText"));
 | 
						|
      this.editor.resetUndo();
 | 
						|
 | 
						|
      // Get the source text from the active thread.
 | 
						|
      DebuggerController.SourceScripts.getText(aSource, function(aUrl, aText) {
 | 
						|
        aSource.loaded = true;
 | 
						|
        aSource.text = aText;
 | 
						|
        this.setEditorSource(aSource, aOptions);
 | 
						|
      }.bind(this));
 | 
						|
    }
 | 
						|
    // If the source is already loaded, display it immediately.
 | 
						|
    else {
 | 
						|
      if (this._editorSource != aSource) {
 | 
						|
        // Avoid setting the editor mode for very large files.
 | 
						|
        if (aSource.text.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
 | 
						|
          this.setEditorMode(aSource.url, aSource.contentType, aSource.text);
 | 
						|
        } else {
 | 
						|
          this.editor.setMode(SourceEditor.MODES.TEXT);
 | 
						|
        }
 | 
						|
        this.editor.setText(aSource.text);
 | 
						|
        this.editor.resetUndo();
 | 
						|
      }
 | 
						|
      this._editorSource = aSource;
 | 
						|
      this.updateEditor();
 | 
						|
 | 
						|
      DebuggerView.Sources.selectedValue = aSource.url;
 | 
						|
      DebuggerController.Breakpoints.updateEditorBreakpoints();
 | 
						|
 | 
						|
      // Handle any additional options for showing the source.
 | 
						|
      if (aOptions.caretLine) {
 | 
						|
        editor.setCaretPosition(aOptions.caretLine - 1);
 | 
						|
      }
 | 
						|
      if (aOptions.debugLine) {
 | 
						|
        editor.setDebugLocation(aOptions.debugLine - 1);
 | 
						|
      }
 | 
						|
      if (aOptions.callback) {
 | 
						|
        aOptions.callback(aSource);
 | 
						|
      }
 | 
						|
      // Notify that we've shown a source file.
 | 
						|
      window.dispatchEvent("Debugger:SourceShown", aSource);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Update the source editor's current caret and debug location based on
 | 
						|
   * a requested url and line. If unspecified, they default to the location
 | 
						|
   * given by the currently active frame in the stack.
 | 
						|
   *
 | 
						|
   * @param string aUrl [optional]
 | 
						|
   *        The target source url.
 | 
						|
   * @param number aLine [optional]
 | 
						|
   *        The target line number in the source.
 | 
						|
   * @param object aFlags [optional]
 | 
						|
   *        An object containing some of the following boolean properties:
 | 
						|
   *          - noSwitch: don't switch to the source if not currently selected
 | 
						|
   *          - noCaret: don't set the caret location at the specified line
 | 
						|
   *          - noDebug: don't set the debug location at the specified line
 | 
						|
   */
 | 
						|
  updateEditor: function DV_updateEditor(aUrl, aLine, aFlags = {}) {
 | 
						|
    if (!this.editor) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // If the location is not specified, default to the location given by
 | 
						|
    // the currently active frame in the stack.
 | 
						|
    if (!aUrl && !aLine) {
 | 
						|
      let cachedFrames = DebuggerController.activeThread.cachedFrames;
 | 
						|
      let currentFrame = DebuggerController.StackFrames.currentFrame;
 | 
						|
      let frame = cachedFrames[currentFrame];
 | 
						|
      if (frame) {
 | 
						|
        let { url, line } = frame.where;
 | 
						|
        this.updateEditor(url, line, { noSwitch: true });
 | 
						|
      }
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    dumpn("Updating the DebuggerView editor: " + aUrl + " @ " + aLine +
 | 
						|
          ", flags: " + aFlags.toSource());
 | 
						|
 | 
						|
    // If the currently displayed source is the requested one, update.
 | 
						|
    if (this.Sources.selectedValue == aUrl) {
 | 
						|
      updateLine(aLine);
 | 
						|
    }
 | 
						|
    // If the requested source exists, display it and update.
 | 
						|
    else if (this.Sources.containsValue(aUrl) && !aFlags.noSwitch) {
 | 
						|
      this.Sources.selectedValue = aUrl;
 | 
						|
      updateLine(aLine);
 | 
						|
    }
 | 
						|
    // Dumb request, invalidate the caret position and debug location.
 | 
						|
    else {
 | 
						|
      updateLine(0);
 | 
						|
    }
 | 
						|
 | 
						|
    // Updates the source editor's caret position and debug location.
 | 
						|
    // @param number a Line
 | 
						|
    function updateLine(aLine) {
 | 
						|
      if (!aFlags.noCaret) {
 | 
						|
        DebuggerView.editor.setCaretPosition(aLine - 1);
 | 
						|
      }
 | 
						|
      if (!aFlags.noDebug) {
 | 
						|
        DebuggerView.editor.setDebugLocation(aLine - 1);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the text in the source editor's specified line.
 | 
						|
   *
 | 
						|
   * @param number aLine [optional]
 | 
						|
   *        The line to get the text from.
 | 
						|
   *        If unspecified, it defaults to the current caret position line.
 | 
						|
   * @return string
 | 
						|
   *         The specified line's text.
 | 
						|
   */
 | 
						|
  getEditorLine: function SS_getEditorLine(aLine) {
 | 
						|
    let line = aLine || this.editor.getCaretPosition().line;
 | 
						|
    let start = this.editor.getLineStart(line);
 | 
						|
    let end = this.editor.getLineEnd(line);
 | 
						|
    return this.editor.getText(start, end);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the visibility state of the panes.
 | 
						|
   * @return boolean
 | 
						|
   */
 | 
						|
  get panesHidden()
 | 
						|
    this._togglePanesButton.hasAttribute("panesHidden"),
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets all the panes hidden or visible.
 | 
						|
   *
 | 
						|
   * @param object aFlags [optional]
 | 
						|
   *        An object containing some of the following boolean properties:
 | 
						|
   *        - visible: true if the pane should be shown, false for hidden
 | 
						|
   *        - animated: true to display an animation on toggle
 | 
						|
   *        - callback: a function to invoke when the panes toggle finishes
 | 
						|
   */
 | 
						|
  togglePanes: function DV__togglePanes(aFlags = {}) {
 | 
						|
    // Avoid useless toggles.
 | 
						|
    if (aFlags.visible == !this.panesHidden) {
 | 
						|
      aFlags.callback && aFlags.callback();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aFlags.visible) {
 | 
						|
      this._stackframesAndBreakpoints.style.marginLeft = "0";
 | 
						|
      this._variablesAndExpressions.style.marginRight = "0";
 | 
						|
      this._togglePanesButton.removeAttribute("panesHidden");
 | 
						|
      this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("collapsePanes"));
 | 
						|
    } else {
 | 
						|
      let marginL = ~~(this._stackframesAndBreakpoints.getAttribute("width")) + 1;
 | 
						|
      let marginR = ~~(this._variablesAndExpressions.getAttribute("width")) + 1;
 | 
						|
      this._stackframesAndBreakpoints.style.marginLeft = -marginL + "px";
 | 
						|
      this._variablesAndExpressions.style.marginRight = -marginR + "px";
 | 
						|
      this._togglePanesButton.setAttribute("panesHidden", "true");
 | 
						|
      this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("expandPanes"));
 | 
						|
    }
 | 
						|
 | 
						|
    if (aFlags.animated) {
 | 
						|
      this._stackframesAndBreakpoints.setAttribute("animated", "");
 | 
						|
      this._variablesAndExpressions.setAttribute("animated", "");
 | 
						|
 | 
						|
      // Displaying the panes may have the effect of triggering scrollbars to
 | 
						|
      // appear in the source editor, which would render the currently
 | 
						|
      // highlighted line to appear behind them in some cases.
 | 
						|
      let self = this;
 | 
						|
 | 
						|
      window.addEventListener("transitionend", function onEvent() {
 | 
						|
        window.removeEventListener("transitionend", onEvent, false);
 | 
						|
        aFlags.callback && aFlags.callback();
 | 
						|
        self.updateEditor();
 | 
						|
      }, false);
 | 
						|
    } else {
 | 
						|
      this._stackframesAndBreakpoints.removeAttribute("animated");
 | 
						|
      this._variablesAndExpressions.removeAttribute("animated");
 | 
						|
      aFlags.callback && aFlags.callback();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets all the panes visible after a short period of time.
 | 
						|
   *
 | 
						|
   * @param function aCallback
 | 
						|
   *        A function to invoke when the panes toggle finishes.
 | 
						|
   */
 | 
						|
  showPanesSoon: function DV__showPanesSoon(aCallback) {
 | 
						|
    // Try to keep animations as smooth as possible, so wait a few cycles.
 | 
						|
    window.setTimeout(function() {
 | 
						|
      DebuggerView.togglePanes({
 | 
						|
        visible: true,
 | 
						|
        animated: true,
 | 
						|
        callback: aCallback
 | 
						|
      });
 | 
						|
    }, PANES_APPEARANCE_DELAY);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Handles any initialization on a tab navigation event issued by the client.
 | 
						|
   */
 | 
						|
  _handleTabNavigation: function DV__handleTabNavigation() {
 | 
						|
    dumpn("Handling tab navigation in the DebuggerView");
 | 
						|
 | 
						|
    this.ChromeGlobals.empty();
 | 
						|
    this.Sources.empty();
 | 
						|
    this.Filtering.clearSearch();
 | 
						|
    this.GlobalSearch.clearView();
 | 
						|
    this.GlobalSearch.clearCache();
 | 
						|
    this.StackFrames.empty();
 | 
						|
    this.Breakpoints.empty();
 | 
						|
    this.Breakpoints.unhighlightBreakpoint();
 | 
						|
    this.Variables.empty();
 | 
						|
    SourceUtils.clearLabelsCache();
 | 
						|
 | 
						|
    if (this.editor) {
 | 
						|
      this.editor.setText("");
 | 
						|
      this._editorSource = null;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  Toolbar: null,
 | 
						|
  Options: null,
 | 
						|
  ChromeGlobals: null,
 | 
						|
  Sources: null,
 | 
						|
  Filtering: null,
 | 
						|
  StackFrames: null,
 | 
						|
  Breakpoints: null,
 | 
						|
  GlobalSearch: null,
 | 
						|
  Variables: null,
 | 
						|
  _editor: null,
 | 
						|
  _editorSource: null,
 | 
						|
  _togglePanesButton: null,
 | 
						|
  _stackframesAndBreakpoints: null,
 | 
						|
  _variablesAndExpressions: null,
 | 
						|
  _isInitialized: false,
 | 
						|
  _isDestroyed: false
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * A generic item used to describe elements present in views like the
 | 
						|
 * ChromeGlobals, Sources, Stackframes, Breakpoints etc.
 | 
						|
 *
 | 
						|
 * @param string aLabel
 | 
						|
 *        The label displayed in the container.
 | 
						|
 * @param string aValue
 | 
						|
 *        The actual internal value of the item.
 | 
						|
 * @param string aDescription [optional]
 | 
						|
 *        An optional description of the item.
 | 
						|
 * @param any aAttachment [optional]
 | 
						|
 *        Some attached primitive/object.
 | 
						|
 */
 | 
						|
function MenuItem(aLabel, aValue, aDescription, aAttachment) {
 | 
						|
  this._label = aLabel + "";
 | 
						|
  this._value = aValue + "";
 | 
						|
  this._description = aDescription + "";
 | 
						|
  this.attachment = aAttachment;
 | 
						|
}
 | 
						|
 | 
						|
MenuItem.prototype = {
 | 
						|
  /**
 | 
						|
   * Gets the label set for this item.
 | 
						|
   * @return string
 | 
						|
   */
 | 
						|
  get label() this._label,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the value set for this item.
 | 
						|
   * @return string
 | 
						|
   */
 | 
						|
  get value() this._value,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the description set for this item.
 | 
						|
   * @return string
 | 
						|
   */
 | 
						|
  get description() this._description,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the element associated with this item.
 | 
						|
   * @return nsIDOMNode
 | 
						|
   */
 | 
						|
  get target() this._target,
 | 
						|
 | 
						|
  _label: "",
 | 
						|
  _value: "",
 | 
						|
  _description: "",
 | 
						|
  _target: null,
 | 
						|
  finalize: null,
 | 
						|
  attachment: null
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * A generic items container, used for displaying views like the
 | 
						|
 * ChromeGlobals, Sources, Stackframes, Breakpoints etc.
 | 
						|
 * Iterable via "for (let item in menuContainer) { }".
 | 
						|
 *
 | 
						|
 * Language:
 | 
						|
 *   - An "item" is an instance (or compatible iterface) of a MenuItem.
 | 
						|
 *   - An "element" or "node" is a nsIDOMNode.
 | 
						|
 *
 | 
						|
 * The container node supplied to all instances of this constructor can either
 | 
						|
 * be a <menulist> element, or any other object interfacing the following
 | 
						|
 * methods:
 | 
						|
 *   - function:nsIDOMNode appendItem(aLabel:string, aValue:string)
 | 
						|
 *   - function:nsIDOMNode insertItemAt(aIndex:number, aLabel:string, aValue:string)
 | 
						|
 *   - function:nsIDOMNode getItemAtIndex(aIndex:number)
 | 
						|
 *   - function removeChild(aChild:nsIDOMNode)
 | 
						|
 *   - function removeAllItems()
 | 
						|
 *   - get:number itemCount()
 | 
						|
 *   - get:number selectedIndex()
 | 
						|
 *   - set selectedIndex(aIndex:number)
 | 
						|
 *   - get:nsIDOMNode selectedItem()
 | 
						|
 *   - set selectedItem(aChild:nsIDOMNode)
 | 
						|
 *   - function getAttribute(aName:string)
 | 
						|
 *   - function setAttribute(aName:string, aValue:string)
 | 
						|
 *   - function removeAttribute(aName:string)
 | 
						|
 *   - function addEventListener(aName:string, aCallback:function, aBubbleFlag:boolean)
 | 
						|
 *   - function removeEventListener(aName:string, aCallback:function, aBubbleFlag:boolean)
 | 
						|
 *
 | 
						|
 * @param nsIDOMNode aContainerNode [optional]
 | 
						|
 *        The element associated with the displayed container. Although required,
 | 
						|
 *        derived objects may set this value later, upon debugger initialization.
 | 
						|
 */
 | 
						|
function MenuContainer(aContainerNode) {
 | 
						|
  this._container = aContainerNode;
 | 
						|
  this._stagedItems = [];
 | 
						|
  this._itemsByLabel = new Map();
 | 
						|
  this._itemsByValue = new Map();
 | 
						|
  this._itemsByElement = new Map();
 | 
						|
}
 | 
						|
 | 
						|
MenuContainer.prototype = {
 | 
						|
  /**
 | 
						|
   * Prepares an item to be added to this container. This allows for a large
 | 
						|
   * number of items to be batched up before being alphabetically sorted and
 | 
						|
   * added in this menu.
 | 
						|
   *
 | 
						|
   * If the "forced" flag is true, the item will be immediately inserted at the
 | 
						|
   * correct position in this container, so that all the items remain sorted.
 | 
						|
   * This can (possibly) be much slower than batching up multiple items.
 | 
						|
   *
 | 
						|
   * By default, this container assumes that all the items should be displayed
 | 
						|
   * sorted by their label. This can be overridden with the "unsorted" flag.
 | 
						|
   *
 | 
						|
   * Furthermore, this container makes sure that all the items are unique
 | 
						|
   * (two items with the same label or value are not allowed) and non-degenerate
 | 
						|
   * (items with "undefined" or "null" labels/values). This can, as well, be
 | 
						|
   * overridden via the "relaxed" flag.
 | 
						|
   *
 | 
						|
   * @param string aLabel
 | 
						|
   *        The label displayed in the container.
 | 
						|
   * @param string aValue
 | 
						|
   *        The actual internal value of the item.
 | 
						|
   * @param object aOptions [optional]
 | 
						|
   *        Additional options or flags supported by this operation:
 | 
						|
   *          - forced: true to force the item to be immediately appended
 | 
						|
   *          - unsorted: true if the items should not always remain sorted
 | 
						|
   *          - relaxed: true if this container should allow dupes & degenerates
 | 
						|
   *          - description: an optional description of the item
 | 
						|
   *          - attachment: some attached primitive/object
 | 
						|
   * @return MenuItem
 | 
						|
   *         The item associated with the displayed element if a forced push,
 | 
						|
   *         undefined if the item was staged for a later commit.
 | 
						|
   */
 | 
						|
  push: function DVMC_push(aLabel, aValue, aOptions = {}) {
 | 
						|
    let item = new MenuItem(
 | 
						|
      aLabel, aValue, aOptions.description, aOptions.attachment);
 | 
						|
 | 
						|
    // Batch the item to be added later.
 | 
						|
    if (!aOptions.forced) {
 | 
						|
      this._stagedItems.push(item);
 | 
						|
    }
 | 
						|
    // Immediately insert the item at the specified index.
 | 
						|
    else if (aOptions.forced && aOptions.forced.atIndex !== undefined) {
 | 
						|
      return this._insertItemAt(aOptions.forced.atIndex, item, aOptions);
 | 
						|
    }
 | 
						|
    // Find the target position in this container and insert the item there.
 | 
						|
    else if (!aOptions.unsorted) {
 | 
						|
      return this._insertItemAt(this._findExpectedIndex(aLabel), item, aOptions);
 | 
						|
    }
 | 
						|
    // Just append the item in this container.
 | 
						|
    else {
 | 
						|
      return this._appendItem(item, aOptions);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Flushes all the prepared items into this container.
 | 
						|
   *
 | 
						|
   * @param object aOptions [optional]
 | 
						|
   *        Additional options or flags supported by this operation:
 | 
						|
   *          - unsorted: true if the items should not be sorted beforehand
 | 
						|
   */
 | 
						|
  commit: function DVMC_commit(aOptions = {}) {
 | 
						|
    let stagedItems = this._stagedItems;
 | 
						|
 | 
						|
    // By default, sort the items before adding them to this container.
 | 
						|
    if (!aOptions.unsorted) {
 | 
						|
      stagedItems.sort(function(a, b) a.label.toLowerCase() > b.label.toLowerCase());
 | 
						|
    }
 | 
						|
    // Append the prepared items to this container.
 | 
						|
    for (let item of stagedItems) {
 | 
						|
      this._appendItem(item, aOptions);
 | 
						|
    }
 | 
						|
    // Recreate the temporary items list for ulterior pushes.
 | 
						|
    this._stagedItems = [];
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Updates this container to reflect the information provided by the
 | 
						|
   * currently selected item.
 | 
						|
   *
 | 
						|
   * @return boolean
 | 
						|
   *         True if a selected item was available, false otherwise.
 | 
						|
   */
 | 
						|
  refresh: function DVMC_refresh() {
 | 
						|
    let selectedValue = this.selectedValue;
 | 
						|
    if (!selectedValue) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    let entangledLabel = this.getItemByValue(selectedValue).label;
 | 
						|
 | 
						|
    this._container.setAttribute("label", entangledLabel);
 | 
						|
    this._container.setAttribute("tooltiptext", selectedValue);
 | 
						|
    return true;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Immediately removes the specified item from this container.
 | 
						|
   *
 | 
						|
   * @param MenuItem aItem
 | 
						|
   *        The item associated with the element to remove.
 | 
						|
   */
 | 
						|
  remove: function DVMC__remove(aItem) {
 | 
						|
    this._container.removeChild(aItem.target);
 | 
						|
    this._untangleItem(aItem);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes all items from this container.
 | 
						|
   */
 | 
						|
  empty: function DVMC_empty() {
 | 
						|
    this._preferredValue = this.selectedValue;
 | 
						|
    this._container.selectedIndex = -1;
 | 
						|
    this._container.setAttribute("label", this._emptyLabel);
 | 
						|
    this._container.removeAttribute("tooltiptext");
 | 
						|
    this._container.removeAllItems();
 | 
						|
 | 
						|
    for (let [, item] of this._itemsByElement) {
 | 
						|
      this._untangleItem(item);
 | 
						|
    }
 | 
						|
 | 
						|
    this._itemsByLabel = new Map();
 | 
						|
    this._itemsByValue = new Map();
 | 
						|
    this._itemsByElement = new Map();
 | 
						|
    this._stagedItems = [];
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Toggles all the items in this container hidden or visible.
 | 
						|
   *
 | 
						|
   * @param boolean aVisibleFlag
 | 
						|
   *        Specifies the intended visibility.
 | 
						|
   */
 | 
						|
  toggleContents: function DVMC_toggleContents(aVisibleFlag) {
 | 
						|
    for (let [, item] of this._itemsByElement) {
 | 
						|
      item.target.hidden = !aVisibleFlag;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Does not remove any item in this container. Instead, it overrides the
 | 
						|
   * current label to signal that it is unavailable and removes the tooltip.
 | 
						|
   */
 | 
						|
  setUnavailable: function DVMC_setUnavailable() {
 | 
						|
    this._container.setAttribute("label", this._unavailableLabel);
 | 
						|
    this._container.removeAttribute("tooltiptext");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Checks whether an item with the specified label is among the elements
 | 
						|
   * shown in this container.
 | 
						|
   *
 | 
						|
   * @param string aLabel
 | 
						|
   *        The item's label.
 | 
						|
   * @return boolean
 | 
						|
   *         True if the label is known, false otherwise.
 | 
						|
   */
 | 
						|
  containsLabel: function DVMC_containsLabel(aLabel) {
 | 
						|
    return this._itemsByLabel.has(aLabel) ||
 | 
						|
           this._stagedItems.some(function(o) o.label == aLabel);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Checks whether an item with the specified value is among the elements
 | 
						|
   * shown in this container.
 | 
						|
   *
 | 
						|
   * @param string aValue
 | 
						|
   *        The item's value.
 | 
						|
   * @return boolean
 | 
						|
   *         True if the value is known, false otherwise.
 | 
						|
   */
 | 
						|
  containsValue: function DVMC_containsValue(aValue) {
 | 
						|
    return this._itemsByValue.has(aValue) ||
 | 
						|
           this._stagedItems.some(function(o) o.value == aValue);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Checks whether an item with the specified trimmed value is among the
 | 
						|
   * elements shown in this container.
 | 
						|
   *
 | 
						|
   * @param string aValue
 | 
						|
   *        The item's value.
 | 
						|
   * @param function aTrim [optional]
 | 
						|
   *        A custom trimming function.
 | 
						|
   * @return boolean
 | 
						|
   *         True if the trimmed value is known, false otherwise.
 | 
						|
   */
 | 
						|
  containsTrimmedValue:
 | 
						|
  function DVMC_containsTrimmedValue(aValue,
 | 
						|
                                     aTrim = SourceUtils.trimUrlQuery) {
 | 
						|
    let trimmedValue = aTrim(aValue);
 | 
						|
 | 
						|
    for (let [value] of this._itemsByValue) {
 | 
						|
      if (aTrim(value) == trimmedValue) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return this._stagedItems.some(function(o) aTrim(o.value) == trimmedValue);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the preferred selected value to be displayed in this container.
 | 
						|
   * @return string
 | 
						|
   */
 | 
						|
  get preferredValue() this._preferredValue,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Retrieves the selected element's index in this container.
 | 
						|
   * @return number
 | 
						|
   */
 | 
						|
  get selectedIndex() this._container.selectedIndex,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Retrieves the item associated with the selected element.
 | 
						|
   * @return MenuItem
 | 
						|
   */
 | 
						|
  get selectedItem()
 | 
						|
    this._container.selectedItem ?
 | 
						|
    this._itemsByElement.get(this._container.selectedItem) : null,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Retrieves the label of the selected element.
 | 
						|
   * @return string
 | 
						|
   */
 | 
						|
  get selectedLabel()
 | 
						|
    this._container.selectedItem ?
 | 
						|
    this._itemsByElement.get(this._container.selectedItem).label : null,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Retrieves the value of the selected element.
 | 
						|
   * @return string
 | 
						|
   */
 | 
						|
  get selectedValue()
 | 
						|
    this._container.selectedItem ?
 | 
						|
    this._itemsByElement.get(this._container.selectedItem).value : null,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Selects the element at the specified index in this container.
 | 
						|
   * @param number aIndex
 | 
						|
   */
 | 
						|
  set selectedIndex(aIndex) this._container.selectedIndex = aIndex,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Selects the element with the entangled item in this container.
 | 
						|
   * @param MenuItem aItem
 | 
						|
   */
 | 
						|
  set selectedItem(aItem) this._container.selectedItem = aItem.target,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Selects the element with the specified label in this container.
 | 
						|
   * @param string aLabel
 | 
						|
   */
 | 
						|
  set selectedLabel(aLabel) {
 | 
						|
    let item = this._itemsByLabel.get(aLabel);
 | 
						|
    if (item) {
 | 
						|
      this._container.selectedItem = item.target;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Selects the element with the specified value in this container.
 | 
						|
   * @param string aValue
 | 
						|
   */
 | 
						|
  set selectedValue(aValue) {
 | 
						|
    let item = this._itemsByValue.get(aValue);
 | 
						|
    if (item) {
 | 
						|
      this._container.selectedItem = item.target;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the item in the container having the specified index.
 | 
						|
   *
 | 
						|
   * @param number aIndex
 | 
						|
   *        The index used to identify the element.
 | 
						|
   * @return MenuItem
 | 
						|
   *         The matched item, or null if nothing is found.
 | 
						|
   */
 | 
						|
  getItemAtIndex: function DVMC_getItemAtIndex(aIndex) {
 | 
						|
    return this.getItemForElement(this._container.getItemAtIndex(aIndex));
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the item in the container having the specified label.
 | 
						|
   *
 | 
						|
   * @param string aLabel
 | 
						|
   *        The label used to identify the element.
 | 
						|
   * @return MenuItem
 | 
						|
   *         The matched item, or null if nothing is found.
 | 
						|
   */
 | 
						|
  getItemByLabel: function DVMC_getItemByLabel(aLabel) {
 | 
						|
    return this._itemsByLabel.get(aLabel);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the item in the container having the specified value.
 | 
						|
   *
 | 
						|
   * @param string aValue
 | 
						|
   *        The value used to identify the element.
 | 
						|
   * @return MenuItem
 | 
						|
   *         The matched item, or null if nothing is found.
 | 
						|
   */
 | 
						|
  getItemByValue: function DVMC_getItemByValue(aValue) {
 | 
						|
    return this._itemsByValue.get(aValue);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the item in the container associated with the specified element.
 | 
						|
   *
 | 
						|
   * @param nsIDOMNode aElement
 | 
						|
   *        The element used to identify the item.
 | 
						|
   * @return MenuItem
 | 
						|
   *         The matched item, or null if nothing is found.
 | 
						|
   */
 | 
						|
  getItemForElement:
 | 
						|
  function DVMC_getItemForElement(aElement) {
 | 
						|
    while (aElement) {
 | 
						|
      let item = this._itemsByElement.get(aElement);
 | 
						|
      if (item) {
 | 
						|
        return item;
 | 
						|
      }
 | 
						|
      aElement = aElement.parentNode;
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the list of labels in this container.
 | 
						|
   * @return array
 | 
						|
   */
 | 
						|
  get labels() {
 | 
						|
    let labels = [];
 | 
						|
    for (let [label] of this._itemsByLabel) {
 | 
						|
      labels.push(label);
 | 
						|
    }
 | 
						|
    return labels;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the list of values in this container.
 | 
						|
   * @return array
 | 
						|
   */
 | 
						|
  get values() {
 | 
						|
    let values = [];
 | 
						|
    for (let [value] of this._itemsByValue) {
 | 
						|
      values.push(value);
 | 
						|
    }
 | 
						|
    return values;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the total items in this container.
 | 
						|
   * @return number
 | 
						|
   */
 | 
						|
  get totalItems() {
 | 
						|
    return this._itemsByElement.size;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the total visible (non-hidden) items in this container.
 | 
						|
   * @return number
 | 
						|
   */
 | 
						|
  get visibleItems() {
 | 
						|
    let count = 0;
 | 
						|
    for (let [element] of this._itemsByElement) {
 | 
						|
      count += element.hidden ? 0 : 1;
 | 
						|
    }
 | 
						|
    return count;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Specifies the required conditions for an item to be considered unique.
 | 
						|
   * Possible values:
 | 
						|
   *   - 1: label AND value are different from all other items
 | 
						|
   *   - 2: label OR value are different from all other items
 | 
						|
   *   - 3: only label is required to be different
 | 
						|
   *   - 4: only value is required to be different
 | 
						|
   */
 | 
						|
  uniquenessQualifier: 1,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Checks if an item is unique in this container.
 | 
						|
   *
 | 
						|
   * @param MenuItem aItem
 | 
						|
   *        An object containing a label and a value property.
 | 
						|
   * @return boolean
 | 
						|
   *         True if the element is unique, false otherwise.
 | 
						|
   */
 | 
						|
  isUnique: function DVMC_isUnique(aItem) {
 | 
						|
    switch (this.uniquenessQualifier) {
 | 
						|
      case 1:
 | 
						|
        return !this._itemsByLabel.has(aItem.label) &&
 | 
						|
               !this._itemsByValue.has(aItem.value);
 | 
						|
      case 2:
 | 
						|
        return !this._itemsByLabel.has(aItem.label) ||
 | 
						|
               !this._itemsByValue.has(aItem.value);
 | 
						|
      case 3:
 | 
						|
        return !this._itemsByLabel.has(aItem.label);
 | 
						|
      case 4:
 | 
						|
        return !this._itemsByValue.has(aItem.value);
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Checks if an item's label and value are eligible for this container.
 | 
						|
   *
 | 
						|
   * @param MenuItem aItem
 | 
						|
   *        An object containing a label and a value property.
 | 
						|
   * @return boolean
 | 
						|
   *         True if the element is eligible, false otherwise.
 | 
						|
   */
 | 
						|
  isEligible: function DVMC_isEligible(aItem) {
 | 
						|
    return this.isUnique(aItem) &&
 | 
						|
           aItem.label != "undefined" && aItem.label != "null" &&
 | 
						|
           aItem.value != "undefined" && aItem.value != "null";
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Finds the expected item index in this container based on its label.
 | 
						|
   *
 | 
						|
   * @param string aLabel
 | 
						|
   *        The label used to identify the element.
 | 
						|
   * @return number
 | 
						|
   *         The expected item index.
 | 
						|
   */
 | 
						|
  _findExpectedIndex: function DVMC__findExpectedIndex(aLabel) {
 | 
						|
    let container = this._container;
 | 
						|
    let itemCount = container.itemCount;
 | 
						|
 | 
						|
    for (let i = 0; i < itemCount; i++) {
 | 
						|
      if (this.getItemForElement(container.getItemAtIndex(i)).label > aLabel) {
 | 
						|
        return i;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return itemCount;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Immediately appends an item in this container.
 | 
						|
   *
 | 
						|
   * @param MenuItem aItem
 | 
						|
   *        An object containing a label and a value property.
 | 
						|
   * @param object aOptions [optional]
 | 
						|
   *        Additional options or flags supported by this operation:
 | 
						|
   *          - relaxed: true if this container should allow dupes & degenerates
 | 
						|
   * @return MenuItem
 | 
						|
   *         The item associated with the displayed element, null if rejected.
 | 
						|
   */
 | 
						|
  _appendItem:
 | 
						|
  function DVMC__appendItem(aItem, aOptions = {}) {
 | 
						|
    if (!aOptions.relaxed && !this.isEligible(aItem)) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    return this._entangleItem(aItem, this._container.appendItem(
 | 
						|
      aItem.label, aItem.value, "", aOptions.attachment));
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Immediately inserts an item in this container at the specified index.
 | 
						|
   *
 | 
						|
   * @param number aIndex
 | 
						|
   *        The position in the container intended for this item.
 | 
						|
   * @param MenuItem aItem
 | 
						|
   *        An object containing a label and a value property.
 | 
						|
   * @param object aOptions [optional]
 | 
						|
   *        Additional options or flags supported by this operation:
 | 
						|
   *          - relaxed: true if this container should allow dupes & degenerates
 | 
						|
   * @return MenuItem
 | 
						|
   *         The item associated with the displayed element, null if rejected.
 | 
						|
   */
 | 
						|
  _insertItemAt:
 | 
						|
  function DVMC__insertItemAt(aIndex, aItem, aOptions) {
 | 
						|
    if (!aOptions.relaxed && !this.isEligible(aItem)) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    return this._entangleItem(aItem, this._container.insertItemAt(
 | 
						|
      aIndex, aItem.label, aItem.value, "", aOptions.attachment));
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Entangles an item (model) with a displayed node element (view).
 | 
						|
   *
 | 
						|
   * @param MenuItem aItem
 | 
						|
   *        The item describing the element.
 | 
						|
   * @param nsIDOMNode aElement
 | 
						|
   *        The element displaying the item.
 | 
						|
   * @return MenuItem
 | 
						|
   *         The same item.
 | 
						|
   */
 | 
						|
  _entangleItem: function DVMC__entangleItem(aItem, aElement) {
 | 
						|
    this._itemsByLabel.set(aItem.label, aItem);
 | 
						|
    this._itemsByValue.set(aItem.value, aItem);
 | 
						|
    this._itemsByElement.set(aElement, aItem);
 | 
						|
 | 
						|
    aItem._target = aElement;
 | 
						|
    return aItem;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Untangles an item (model) from a displayed node element (view).
 | 
						|
   *
 | 
						|
   * @param MenuItem aItem
 | 
						|
   *        The item describing the element.
 | 
						|
   * @return MenuItem
 | 
						|
   *         The same item.
 | 
						|
   */
 | 
						|
  _untangleItem: function DVMC__untangleItem(aItem) {
 | 
						|
    if (aItem.finalize instanceof Function) {
 | 
						|
      aItem.finalize(aItem);
 | 
						|
    }
 | 
						|
 | 
						|
    this._itemsByLabel.delete(aItem.label);
 | 
						|
    this._itemsByValue.delete(aItem.value);
 | 
						|
    this._itemsByElement.delete(aItem.target);
 | 
						|
 | 
						|
    aItem._target = null;
 | 
						|
    return aItem;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * A generator-iterator over all the items in this container.
 | 
						|
   */
 | 
						|
  __iterator__: function DVMC_iterator() {
 | 
						|
    for (let [, item] of this._itemsByElement) {
 | 
						|
      yield item;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _container: null,
 | 
						|
  _stagedItems: null,
 | 
						|
  _itemsByLabel: null,
 | 
						|
  _itemsByValue: null,
 | 
						|
  _itemsByElement: null,
 | 
						|
  _preferredValue: null,
 | 
						|
  _emptyLabel: "",
 | 
						|
  _unavailableLabel: ""
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * A stacked list of items, compatible with MenuContainer instances, used for
 | 
						|
 * displaying views like the StackFrames, Breakpoints etc.
 | 
						|
 *
 | 
						|
 * Custom methods introduced by this view, not necessary for a MenuContainer:
 | 
						|
 * set emptyText(aValue:string)
 | 
						|
 * set permaText(aValue:string)
 | 
						|
 * set itemType(aType:string)
 | 
						|
 * set itemFactory(aCallback:function)
 | 
						|
 *
 | 
						|
 * TODO: Use this in #796135 - "Provide some obvious UI for scripts filtering".
 | 
						|
 *
 | 
						|
 * @param nsIDOMNode aAssociatedNode
 | 
						|
 *        The element associated with the displayed container.
 | 
						|
 */
 | 
						|
function StackList(aAssociatedNode) {
 | 
						|
  this._parent = aAssociatedNode;
 | 
						|
 | 
						|
  // Create an internal list container.
 | 
						|
  this._list = document.createElement("vbox");
 | 
						|
  this._parent.appendChild(this._list);
 | 
						|
}
 | 
						|
 | 
						|
StackList.prototype = {
 | 
						|
  /**
 | 
						|
   * Immediately appends an item in this container.
 | 
						|
   *
 | 
						|
   * @param string aLabel
 | 
						|
   *        The label displayed in the container.
 | 
						|
   * @param string aValue
 | 
						|
   *        The actual internal value of the item.
 | 
						|
   * @param string aDescription [optional]
 | 
						|
   *        An optional description of the item.
 | 
						|
   * @param any aAttachment [optional]
 | 
						|
   *        Some attached primitive/object.
 | 
						|
   * @return nsIDOMNode
 | 
						|
   *         The element associated with the displayed item.
 | 
						|
   */
 | 
						|
  appendItem:
 | 
						|
  function DVSL_appendItem(aLabel, aValue, aDescription, aAttachment) {
 | 
						|
    return this.insertItemAt(
 | 
						|
      Number.MAX_VALUE, aLabel, aValue, aDescription, aAttachment);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Immediately inserts an item in this container at the specified index.
 | 
						|
   *
 | 
						|
   * @param number aIndex
 | 
						|
   *        The position in the container intended for this item.
 | 
						|
   * @param string aLabel
 | 
						|
   *        The label displayed in the container.
 | 
						|
   * @param string aValue
 | 
						|
   *        The actual internal value of the item.
 | 
						|
   * @param string aDescription [optional]
 | 
						|
   *        An optional description of the item.
 | 
						|
   * @param any aAttachment [optional]
 | 
						|
   *        Some attached primitive/object.
 | 
						|
   * @return nsIDOMNode
 | 
						|
   *         The element associated with the displayed item.
 | 
						|
   */
 | 
						|
  insertItemAt:
 | 
						|
  function DVSL_insertItemAt(aIndex, aLabel, aValue, aDescription, aAttachment) {
 | 
						|
    let list = this._list;
 | 
						|
    let childNodes = list.childNodes;
 | 
						|
 | 
						|
    let element = document.createElement(this.itemType);
 | 
						|
    this._createItemView(element, aLabel, aValue, aAttachment);
 | 
						|
    this._removeEmptyNotice();
 | 
						|
 | 
						|
    return list.insertBefore(element, childNodes[aIndex]);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the child node in this container situated at the specified index.
 | 
						|
   *
 | 
						|
   * @param number aIndex
 | 
						|
   *        The position in the container intended for this item.
 | 
						|
   * @return nsIDOMNode
 | 
						|
   *         The element associated with the displayed item.
 | 
						|
   */
 | 
						|
  getItemAtIndex: function DVSL_getItemAtIndex(aIndex) {
 | 
						|
    return this._list.childNodes[aIndex];
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Immediately removes the specified child node from this container.
 | 
						|
   *
 | 
						|
   * @param nsIDOMNode aChild
 | 
						|
   *        The element associated with the displayed item.
 | 
						|
   */
 | 
						|
  removeChild: function DVSL__removeChild(aChild) {
 | 
						|
    this._list.removeChild(aChild);
 | 
						|
 | 
						|
    if (!this.itemCount) {
 | 
						|
      this._appendEmptyNotice();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Immediately removes all of the child nodes from this container.
 | 
						|
   */
 | 
						|
  removeAllItems: function DVSL_removeAllItems() {
 | 
						|
    let parent = this._parent;
 | 
						|
    let list = this._list;
 | 
						|
    let firstChild;
 | 
						|
 | 
						|
    while (firstChild = list.firstChild) {
 | 
						|
      list.removeChild(firstChild);
 | 
						|
    }
 | 
						|
    parent.scrollTop = 0;
 | 
						|
    parent.scrollLeft = 0;
 | 
						|
 | 
						|
    this._selectedItem = null;
 | 
						|
    this._selectedIndex = -1;
 | 
						|
    this._appendEmptyNotice();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the number of child nodes present in this container.
 | 
						|
   * @return number
 | 
						|
   */
 | 
						|
  get itemCount() this._list.childNodes.length,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the index of the selected child node in this container.
 | 
						|
   * @return number
 | 
						|
   */
 | 
						|
  get selectedIndex() this._selectedIndex,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the index of the selected child node in this container.
 | 
						|
   * Only one child node may be selected at a time.
 | 
						|
   * @param number aIndex
 | 
						|
   */
 | 
						|
  set selectedIndex(aIndex) this.selectedItem = this._list.childNodes[aIndex],
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the currently selected child node in this container.
 | 
						|
   * @return nsIDOMNode
 | 
						|
   */
 | 
						|
  get selectedItem() this._selectedItem,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the currently selected child node in this container.
 | 
						|
   * @param nsIDOMNode aChild
 | 
						|
   */
 | 
						|
  set selectedItem(aChild) {
 | 
						|
    let childNodes = this._list.childNodes;
 | 
						|
 | 
						|
    if (!aChild) {
 | 
						|
      this._selectedItem = null;
 | 
						|
      this._selectedIndex = -1;
 | 
						|
    }
 | 
						|
    for (let node of childNodes) {
 | 
						|
      if (node == aChild) {
 | 
						|
        node.classList.add("selected");
 | 
						|
        this._selectedIndex = Array.indexOf(childNodes, node);
 | 
						|
        this._selectedItem = node;
 | 
						|
      } else {
 | 
						|
        node.classList.remove("selected");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Applies an attribute to this container.
 | 
						|
   *
 | 
						|
   * @param string aName
 | 
						|
   *        The name of the attribute to set.
 | 
						|
   * @return string
 | 
						|
   *         The attribute value.
 | 
						|
   */
 | 
						|
  getAttribute: function DVSL_setAttribute(aName) {
 | 
						|
    return this._parent.getAttribute(aName);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Applies an attribute to this container.
 | 
						|
   *
 | 
						|
   * @param string aName
 | 
						|
   *        The name of the attribute to set.
 | 
						|
   * @param any aValue
 | 
						|
   *        The supplied attribute value.
 | 
						|
   */
 | 
						|
  setAttribute: function DVSL_setAttribute(aName, aValue) {
 | 
						|
    this._parent.setAttribute(aName, aValue);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes an attribute applied to this container.
 | 
						|
   *
 | 
						|
   * @param string aName
 | 
						|
   *        The name of the attribute to remove.
 | 
						|
   */
 | 
						|
  removeAttribute: function DVSL_removeAttribute(aName) {
 | 
						|
    this._parent.removeAttribute(aName);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Adds an event listener to this container.
 | 
						|
   *
 | 
						|
   * @param string aName
 | 
						|
   *        The name of the listener to set.
 | 
						|
   * @param function aCallback
 | 
						|
   *        The function to be called when the event is triggered.
 | 
						|
   * @param boolean aBubbleFlag
 | 
						|
   *        True if the event should bubble.
 | 
						|
   */
 | 
						|
  addEventListener:
 | 
						|
  function DVSL_addEventListener(aName, aCallback, aBubbleFlag) {
 | 
						|
    this._parent.addEventListener(aName, aCallback, aBubbleFlag);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes an event listener added to this container.
 | 
						|
   *
 | 
						|
   * @param string aName
 | 
						|
   *        The name of the listener to remove.
 | 
						|
   * @param function aCallback
 | 
						|
   *        The function called when the event was triggered.
 | 
						|
   * @param boolean aBubbleFlag
 | 
						|
   *        True if the event was bubbling.
 | 
						|
   */
 | 
						|
  removeEventListener:
 | 
						|
  function DVSL_removeEventListener(aName, aCallback, aBubbleFlag) {
 | 
						|
    this._parent.removeEventListener(aName, aCallback, aBubbleFlag);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the text displayed permanently in this container's header.
 | 
						|
   * @param string aValue
 | 
						|
   */
 | 
						|
  set permaText(aValue) {
 | 
						|
    if (this._permaTextNode) {
 | 
						|
      this._permaTextNode.setAttribute("value", aValue);
 | 
						|
    }
 | 
						|
    this._permaTextValue = aValue;
 | 
						|
    this._appendPermaNotice();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the text displayed in this container when there are no available items.
 | 
						|
   * @param string aValue
 | 
						|
   */
 | 
						|
  set emptyText(aValue) {
 | 
						|
    if (this._emptyTextNode) {
 | 
						|
      this._emptyTextNode.setAttribute("value", aValue);
 | 
						|
    }
 | 
						|
    this._emptyTextValue = aValue;
 | 
						|
    this._appendEmptyNotice();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Overrides the item's element type (e.g. "vbox" or "hbox").
 | 
						|
   * @param string aType
 | 
						|
   */
 | 
						|
  itemType: "hbox",
 | 
						|
 | 
						|
  /**
 | 
						|
   * Overrides the customization function for creating an item's UI.
 | 
						|
   * @param function aCallback
 | 
						|
   */
 | 
						|
  set itemFactory(aCallback) this._createItemView = aCallback,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Customization function for creating an item's UI for this container.
 | 
						|
   *
 | 
						|
   * @param nsIDOMNode aElementNode
 | 
						|
   *        The element associated with the displayed item.
 | 
						|
   * @param string aLabel
 | 
						|
   *        The item's label.
 | 
						|
   * @param string aValue
 | 
						|
   *        The item's value.
 | 
						|
   */
 | 
						|
  _createItemView: function DVSL__createItemView(aElementNode, aLabel, aValue) {
 | 
						|
    let labelNode = document.createElement("label");
 | 
						|
    let valueNode = document.createElement("label");
 | 
						|
    let spacer = document.createElement("spacer");
 | 
						|
 | 
						|
    labelNode.setAttribute("value", aLabel);
 | 
						|
    valueNode.setAttribute("value", aValue);
 | 
						|
    spacer.setAttribute("flex", "1");
 | 
						|
 | 
						|
    aElementNode.appendChild(labelNode);
 | 
						|
    aElementNode.appendChild(spacer);
 | 
						|
    aElementNode.appendChild(valueNode);
 | 
						|
 | 
						|
    aElementNode.labelNode = labelNode;
 | 
						|
    aElementNode.valueNode = valueNode;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates and appends a label displayed permanently in this container's header.
 | 
						|
   */
 | 
						|
  _appendPermaNotice: function DVSL__appendPermaNotice() {
 | 
						|
    if (this._permaTextNode || !this._permaTextValue) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let label = document.createElement("label");
 | 
						|
    label.className = "empty list-item";
 | 
						|
    label.setAttribute("value", this._permaTextValue);
 | 
						|
 | 
						|
    this._parent.insertBefore(label, this._list);
 | 
						|
    this._permaTextNode = label;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates and appends a label signaling that this container is empty.
 | 
						|
   */
 | 
						|
  _appendEmptyNotice: function DVSL__appendEmptyNotice() {
 | 
						|
    if (this._emptyTextNode || !this._emptyTextValue) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let label = document.createElement("label");
 | 
						|
    label.className = "empty list-item";
 | 
						|
    label.setAttribute("value", this._emptyTextValue);
 | 
						|
 | 
						|
    this._parent.appendChild(label);
 | 
						|
    this._emptyTextNode = label;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes the label signaling that this container is empty.
 | 
						|
   */
 | 
						|
  _removeEmptyNotice: function DVSL__removeEmptyNotice() {
 | 
						|
    if (!this._emptyTextNode) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    this._parent.removeChild(this._emptyTextNode);
 | 
						|
    this._emptyTextNode = null;
 | 
						|
  },
 | 
						|
 | 
						|
  _parent: null,
 | 
						|
  _list: null,
 | 
						|
  _selectedIndex: -1,
 | 
						|
  _selectedItem: null,
 | 
						|
  _permaTextNode: null,
 | 
						|
  _permaTextValue: "",
 | 
						|
  _emptyTextNode: null,
 | 
						|
  _emptyTextValue: ""
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * A simple way of displaying a "Connect to..." prompt.
 | 
						|
 */
 | 
						|
function RemoteDebuggerPrompt() {
 | 
						|
  this.remote = {};
 | 
						|
}
 | 
						|
 | 
						|
RemoteDebuggerPrompt.prototype = {
 | 
						|
  /**
 | 
						|
   * Shows the prompt and waits for a remote host and port to connect to.
 | 
						|
   *
 | 
						|
   * @param boolean aIsReconnectingFlag
 | 
						|
   *        True to show the reconnect message instead of the connect request.
 | 
						|
   */
 | 
						|
  show: function RDP_show(aIsReconnectingFlag) {
 | 
						|
    let check = { value: Prefs.remoteAutoConnect };
 | 
						|
    let input = { value: Prefs.remoteHost + ":" + Prefs.remotePort };
 | 
						|
    let parts;
 | 
						|
 | 
						|
    while (true) {
 | 
						|
      let result = Services.prompt.prompt(null,
 | 
						|
        L10N.getStr("remoteDebuggerPromptTitle"),
 | 
						|
        L10N.getStr(aIsReconnectingFlag
 | 
						|
          ? "remoteDebuggerReconnectMessage"
 | 
						|
          : "remoteDebuggerPromptMessage"), input,
 | 
						|
        L10N.getStr("remoteDebuggerPromptCheck"), check);
 | 
						|
 | 
						|
      if (!result) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
      if ((parts = input.value.split(":")).length == 2) {
 | 
						|
        let [host, port] = parts;
 | 
						|
 | 
						|
        if (host.length && port.length) {
 | 
						|
          this.remote = { host: host, port: port, auto: check.value };
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
};
 |