mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1516 lines
46 KiB
JavaScript
1516 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) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|