2012-02-07 09:22:30 -08:00
|
|
|
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
2012-04-08 22:15:47 -07:00
|
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
2012-05-21 04:12:37 -07:00
|
|
|
/* 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/. */
|
2012-02-07 09:22:30 -08:00
|
|
|
"use strict";
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
const BREAKPOINT_LINE_TOOLTIP_MAX_SIZE = 1000; // chars
|
2012-10-01 07:55:32 -07:00
|
|
|
const PANES_APPEARANCE_DELAY = 50; // ms
|
2012-05-09 09:05:53 -07:00
|
|
|
const PROPERTY_VIEW_FLASH_DURATION = 400; // ms
|
2012-08-18 02:29:47 -07:00
|
|
|
const GLOBAL_SEARCH_MATCH_FLASH_DURATION = 100; // ms
|
|
|
|
const GLOBAL_SEARCH_URL_MAX_SIZE = 100; // chars
|
|
|
|
const GLOBAL_SEARCH_LINE_MAX_SIZE = 300; // chars
|
|
|
|
const GLOBAL_SEARCH_ACTION_DELAY = 150; // ms
|
|
|
|
|
|
|
|
const SEARCH_GLOBAL_FLAG = "!";
|
|
|
|
const SEARCH_LINE_FLAG = ":";
|
|
|
|
const SEARCH_TOKEN_FLAG = "#";
|
2012-05-09 09:05:53 -07:00
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Object mediating visual changes and event listeners between the debugger and
|
|
|
|
* the html view.
|
|
|
|
*/
|
|
|
|
let DebuggerView = {
|
|
|
|
|
|
|
|
/**
|
2012-04-08 22:15:47 -07:00
|
|
|
* An instance of SourceEditor.
|
|
|
|
*/
|
|
|
|
editor: null,
|
|
|
|
|
2012-07-20 01:56:46 -07:00
|
|
|
/**
|
|
|
|
* Caches frequently used global view elements.
|
|
|
|
*/
|
|
|
|
cacheView: function DV_cacheView() {
|
|
|
|
this._onTogglePanesButtonPressed = this._onTogglePanesButtonPressed.bind(this);
|
|
|
|
|
2012-09-12 03:39:51 -07:00
|
|
|
// Panes and view containers
|
2012-07-20 01:56:46 -07:00
|
|
|
this._togglePanesButton = document.getElementById("toggle-panes");
|
|
|
|
this._stackframesAndBreakpoints = document.getElementById("stackframes+breakpoints");
|
|
|
|
this._stackframes = document.getElementById("stackframes");
|
|
|
|
this._breakpoints = document.getElementById("breakpoints");
|
|
|
|
this._variables = document.getElementById("variables");
|
2012-09-12 03:39:51 -07:00
|
|
|
this._scripts = document.getElementById("scripts");
|
2012-07-20 01:56:46 -07:00
|
|
|
this._globalSearch = document.getElementById("globalsearch");
|
2012-09-12 03:39:51 -07:00
|
|
|
this._globalSearchSplitter = document.getElementById("globalsearch-splitter");
|
|
|
|
|
|
|
|
// Keys
|
|
|
|
this._fileSearchKey = document.getElementById("fileSearchKey");
|
|
|
|
this._lineSearchKey = document.getElementById("lineSearchKey");
|
|
|
|
this._tokenSearchKey = document.getElementById("tokenSearchKey");
|
|
|
|
this._globalSearchKey = document.getElementById("globalSearchKey");
|
|
|
|
this._resumeKey = document.getElementById("resumeKey");
|
|
|
|
this._stepOverKey = document.getElementById("stepOverKey");
|
|
|
|
this._stepInKey = document.getElementById("stepInKey");
|
|
|
|
this._stepOutKey = document.getElementById("stepOutKey");
|
|
|
|
|
|
|
|
// Buttons, textboxes etc.
|
|
|
|
this._resumeButton = document.getElementById("resume");
|
|
|
|
this._stepOverButton = document.getElementById("step-over");
|
|
|
|
this._stepInButton = document.getElementById("step-in");
|
|
|
|
this._stepOutButton = document.getElementById("step-out");
|
|
|
|
this._scriptsSearchbox = document.getElementById("scripts-search");
|
2012-09-13 14:00:19 -07:00
|
|
|
this._globalOperatorLabel = document.getElementById("global-operator-label");
|
|
|
|
this._globalOperatorButton = document.getElementById("global-operator-button");
|
|
|
|
this._tokenOperatorLabel = document.getElementById("token-operator-label");
|
|
|
|
this._tokenOperatorButton = document.getElementById("token-operator-button");
|
|
|
|
this._lineOperatorLabel = document.getElementById("line-operator-label");
|
|
|
|
this._lineOperatorButton = document.getElementById("line-operator-button");
|
2012-09-12 03:39:51 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Applies the correct key labels and tooltips across global view elements.
|
|
|
|
*/
|
|
|
|
initializeKeys: function DV_initializeKeys() {
|
|
|
|
this._resumeButton.setAttribute("tooltiptext",
|
|
|
|
L10N.getFormatStr("pauseButtonTooltip", [LayoutHelpers.prettyKey(this._resumeKey)]));
|
|
|
|
this._stepOverButton.setAttribute("tooltiptext",
|
|
|
|
L10N.getFormatStr("stepOverTooltip", [LayoutHelpers.prettyKey(this._stepOverKey)]));
|
|
|
|
this._stepInButton.setAttribute("tooltiptext",
|
|
|
|
L10N.getFormatStr("stepInTooltip", [LayoutHelpers.prettyKey(this._stepInKey)]));
|
|
|
|
this._stepOutButton.setAttribute("tooltiptext",
|
|
|
|
L10N.getFormatStr("stepOutTooltip", [LayoutHelpers.prettyKey(this._stepOutKey)]));
|
|
|
|
|
|
|
|
this._scriptsSearchbox.setAttribute("placeholder",
|
|
|
|
L10N.getFormatStr("emptyFilterText", [LayoutHelpers.prettyKey(this._fileSearchKey)]));
|
2012-09-13 14:00:19 -07:00
|
|
|
this._globalOperatorLabel.setAttribute("value",
|
2012-09-12 03:39:51 -07:00
|
|
|
L10N.getFormatStr("searchPanelGlobal", [LayoutHelpers.prettyKey(this._globalSearchKey)]));
|
2012-09-13 14:00:19 -07:00
|
|
|
this._tokenOperatorLabel.setAttribute("value",
|
2012-09-12 03:39:51 -07:00
|
|
|
L10N.getFormatStr("searchPanelToken", [LayoutHelpers.prettyKey(this._tokenSearchKey)]));
|
2012-09-13 14:00:19 -07:00
|
|
|
this._lineOperatorLabel.setAttribute("value",
|
2012-09-12 03:39:51 -07:00
|
|
|
L10N.getFormatStr("searchPanelLine", [LayoutHelpers.prettyKey(this._lineSearchKey)]));
|
2012-09-13 14:00:19 -07:00
|
|
|
|
|
|
|
this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG);
|
|
|
|
this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG);
|
|
|
|
this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG);
|
2012-07-20 01:56:46 -07:00
|
|
|
},
|
|
|
|
|
2012-05-31 02:27:22 -07:00
|
|
|
/**
|
|
|
|
* Initializes UI properties for all the displayed panes.
|
|
|
|
*/
|
|
|
|
initializePanes: function DV_initializePanes() {
|
2012-07-20 01:56:46 -07:00
|
|
|
this._togglePanesButton.addEventListener("click", this._onTogglePanesButtonPressed);
|
2012-05-31 02:27:22 -07:00
|
|
|
|
2012-07-20 01:56:46 -07:00
|
|
|
this._stackframesAndBreakpoints.setAttribute("width", Prefs.stackframesWidth);
|
|
|
|
this._variables.setAttribute("width", Prefs.variablesWidth);
|
|
|
|
|
2012-10-01 07:55:32 -07:00
|
|
|
this.toggleStackframesAndBreakpointsPane({ silent: true });
|
|
|
|
this.toggleVariablesPane({ silent: true });
|
2012-05-31 02:27:22 -07:00
|
|
|
},
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Initializes the SourceEditor instance.
|
2012-08-08 14:36:58 -07:00
|
|
|
*
|
|
|
|
* @param function aCallback
|
|
|
|
* Called after the editor finishes initializing.
|
2012-04-08 22:15:47 -07:00
|
|
|
*/
|
2012-08-08 14:36:58 -07:00
|
|
|
initializeEditor: function DV_initializeEditor(aCallback) {
|
2012-04-08 22:15:47 -07:00
|
|
|
let placeholder = document.getElementById("editor");
|
|
|
|
|
|
|
|
let config = {
|
|
|
|
mode: SourceEditor.MODES.JAVASCRIPT,
|
|
|
|
showLineNumbers: true,
|
|
|
|
readOnly: true,
|
|
|
|
showAnnotationRuler: true,
|
|
|
|
showOverviewRuler: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
this.editor = new SourceEditor();
|
2012-08-08 14:36:58 -07:00
|
|
|
this.editor.init(placeholder, config, function() {
|
|
|
|
this._onEditorLoad();
|
|
|
|
aCallback();
|
|
|
|
}.bind(this));
|
2012-04-08 22:15:47 -07:00
|
|
|
},
|
|
|
|
|
2012-05-31 02:27:22 -07:00
|
|
|
/**
|
|
|
|
* Removes the displayed panes and saves any necessary state.
|
|
|
|
*/
|
|
|
|
destroyPanes: function DV_destroyPanes() {
|
2012-07-20 01:56:46 -07:00
|
|
|
this._togglePanesButton.removeEventListener("click", this._onTogglePanesButtonPressed);
|
2012-07-18 07:21:57 -07:00
|
|
|
|
2012-07-20 01:56:46 -07:00
|
|
|
Prefs.stackframesWidth = this._stackframesAndBreakpoints.getAttribute("width");
|
|
|
|
Prefs.variablesWidth = this._variables.getAttribute("width");
|
2012-08-18 02:29:47 -07:00
|
|
|
|
2012-07-20 01:56:46 -07:00
|
|
|
this._breakpoints.parentNode.removeChild(this._breakpoints);
|
|
|
|
this._stackframes.parentNode.removeChild(this._stackframes);
|
|
|
|
this._stackframesAndBreakpoints.parentNode.removeChild(this._stackframesAndBreakpoints);
|
|
|
|
this._variables.parentNode.removeChild(this._variables);
|
|
|
|
this._globalSearch.parentNode.removeChild(this._globalSearch);
|
2012-09-12 03:39:51 -07:00
|
|
|
|
|
|
|
// Delete all the cached global view elements.
|
|
|
|
for (let i in this) {
|
|
|
|
if (!(this[i] instanceof Function)) delete this[i];
|
|
|
|
}
|
2012-05-31 02:27:22 -07:00
|
|
|
},
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Removes the SourceEditor instance and added breakpoints.
|
|
|
|
*/
|
|
|
|
destroyEditor: function DV_destroyEditor() {
|
|
|
|
DebuggerController.Breakpoints.destroy();
|
|
|
|
this.editor = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The load event handler for the source editor. This method does post-load
|
|
|
|
* editor initialization.
|
|
|
|
*/
|
|
|
|
_onEditorLoad: function DV__onEditorLoad() {
|
|
|
|
DebuggerController.Breakpoints.initialize();
|
2012-08-01 13:25:46 -07:00
|
|
|
this.editor.focus();
|
2012-05-03 10:36:40 -07:00
|
|
|
},
|
|
|
|
|
2012-07-20 01:56:46 -07:00
|
|
|
/**
|
|
|
|
* Called when the panes toggle button is clicked.
|
|
|
|
*/
|
|
|
|
_onTogglePanesButtonPressed: function DV__onTogglePanesButtonPressed() {
|
2012-10-01 07:55:32 -07:00
|
|
|
this.toggleStackframesAndBreakpointsPane({
|
|
|
|
visible: !!this._togglePanesButton.getAttribute("stackframesAndBreakpointsHidden"),
|
|
|
|
animated: true
|
|
|
|
});
|
|
|
|
this.toggleVariablesPane({
|
|
|
|
visible: !!this._togglePanesButton.getAttribute("variablesHidden"),
|
|
|
|
animated: true
|
|
|
|
});
|
|
|
|
this._onPanesToggle();
|
2012-07-20 01:56:46 -07:00
|
|
|
},
|
|
|
|
|
2012-05-03 10:36:40 -07:00
|
|
|
/**
|
|
|
|
* Sets the close button hidden or visible. It's hidden by default.
|
|
|
|
* @param boolean aVisibleFlag
|
|
|
|
*/
|
2012-10-01 07:55:32 -07:00
|
|
|
toggleCloseButton: function DV_toggleCloseButton(aVisibleFlag) {
|
2012-05-03 10:36:40 -07:00
|
|
|
document.getElementById("close").setAttribute("hidden", !aVisibleFlag);
|
2012-07-20 01:56:46 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the stackframes and breakpoints pane hidden or visible.
|
2012-10-01 07:55:32 -07:00
|
|
|
*
|
|
|
|
* @param object aFlags [optional]
|
|
|
|
* An object containing some of the following booleans:
|
|
|
|
* - visible: true if the pane should be shown, false for hidden
|
|
|
|
* - animated: true to display an animation on toggle
|
|
|
|
* - silent: true to not update any designated prefs
|
|
|
|
*/
|
|
|
|
toggleStackframesAndBreakpointsPane:
|
|
|
|
function DV_toggleStackframesAndBreakpointsPane(aFlags = {}) {
|
|
|
|
if (aFlags.animated) {
|
2012-07-20 01:56:46 -07:00
|
|
|
this._stackframesAndBreakpoints.setAttribute("animated", "");
|
|
|
|
} else {
|
|
|
|
this._stackframesAndBreakpoints.removeAttribute("animated");
|
|
|
|
}
|
2012-10-01 07:55:32 -07:00
|
|
|
if (aFlags.visible) {
|
2012-07-20 01:56:46 -07:00
|
|
|
this._stackframesAndBreakpoints.style.marginLeft = "0";
|
|
|
|
this._togglePanesButton.removeAttribute("stackframesAndBreakpointsHidden");
|
2012-09-12 03:39:51 -07:00
|
|
|
this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("collapsePanes"));
|
2012-07-20 01:56:46 -07:00
|
|
|
} else {
|
|
|
|
let margin = parseInt(this._stackframesAndBreakpoints.getAttribute("width")) + 1;
|
|
|
|
this._stackframesAndBreakpoints.style.marginLeft = -margin + "px";
|
|
|
|
this._togglePanesButton.setAttribute("stackframesAndBreakpointsHidden", "true");
|
2012-09-12 03:39:51 -07:00
|
|
|
this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("expandPanes"));
|
2012-07-20 01:56:46 -07:00
|
|
|
}
|
2012-10-01 07:55:32 -07:00
|
|
|
if (!aFlags.silent) {
|
|
|
|
Prefs.stackframesPaneVisible = !!aFlags.visible;
|
|
|
|
}
|
2012-07-20 01:56:46 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the variable spane hidden or visible.
|
2012-10-01 07:55:32 -07:00
|
|
|
*
|
|
|
|
* @param object aFlags [optional]
|
|
|
|
* An object containing some of the following booleans:
|
|
|
|
* - visible: true if the pane should be shown, false for hidden
|
|
|
|
* - animated: true to display an animation on toggle
|
|
|
|
* - silent: true to not update any designated prefs
|
|
|
|
*/
|
|
|
|
toggleVariablesPane:
|
|
|
|
function DV_toggleVariablesPane(aFlags = {}) {
|
|
|
|
if (aFlags.animated) {
|
2012-07-20 01:56:46 -07:00
|
|
|
this._variables.setAttribute("animated", "");
|
|
|
|
} else {
|
|
|
|
this._variables.removeAttribute("animated");
|
|
|
|
}
|
2012-10-01 07:55:32 -07:00
|
|
|
if (aFlags.visible) {
|
2012-07-20 01:56:46 -07:00
|
|
|
this._variables.style.marginRight = "0";
|
|
|
|
this._togglePanesButton.removeAttribute("variablesHidden");
|
2012-09-12 03:39:51 -07:00
|
|
|
this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("collapsePanes"));
|
2012-07-20 01:56:46 -07:00
|
|
|
} else {
|
|
|
|
let margin = parseInt(this._variables.getAttribute("width")) + 1;
|
|
|
|
this._variables.style.marginRight = -margin + "px";
|
|
|
|
this._togglePanesButton.setAttribute("variablesHidden", "true");
|
2012-09-12 03:39:51 -07:00
|
|
|
this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("expandPanes"));
|
2012-07-20 01:56:46 -07:00
|
|
|
}
|
2012-10-01 07:55:32 -07:00
|
|
|
if (!aFlags.silent) {
|
|
|
|
Prefs.variablesPaneVisible = !!aFlags.visible;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows the stackframes, breakpoints and variable panes if currently hidden
|
|
|
|
* and the preferences dictate otherwise.
|
|
|
|
*/
|
|
|
|
showPanesIfAllowed: function DV_showPanesIfAllowed() {
|
|
|
|
// Try to keep animations as smooth as possible, so wait a few cycles.
|
|
|
|
window.setTimeout(function() {
|
|
|
|
let shown;
|
|
|
|
|
|
|
|
if (Prefs.stackframesPaneVisible &&
|
|
|
|
this._togglePanesButton.getAttribute("stackframesAndBreakpointsHidden")) {
|
|
|
|
this.toggleStackframesAndBreakpointsPane({
|
|
|
|
visible: true,
|
|
|
|
animated: true,
|
|
|
|
silent: true
|
|
|
|
});
|
|
|
|
shown = true;
|
|
|
|
}
|
|
|
|
if (Prefs.variablesPaneVisible &&
|
|
|
|
this._togglePanesButton.getAttribute("variablesHidden")) {
|
|
|
|
this.toggleVariablesPane({
|
|
|
|
visible: true,
|
|
|
|
animated: true,
|
|
|
|
silent: true
|
|
|
|
});
|
|
|
|
shown = true;
|
|
|
|
}
|
|
|
|
if (shown) {
|
|
|
|
this._onPanesToggle();
|
|
|
|
}
|
|
|
|
}.bind(this), PANES_APPEARANCE_DELAY);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
_onPanesToggle: function DV__onPanesToggle() {
|
|
|
|
document.addEventListener("transitionend", function onEvent() {
|
|
|
|
document.removeEventListener("transitionend", onEvent);
|
|
|
|
DebuggerController.StackFrames.updateEditorLocation();
|
|
|
|
});
|
2012-07-20 01:56:46 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The cached global view elements.
|
|
|
|
*/
|
|
|
|
_togglePanesButton: null,
|
|
|
|
_stackframesAndBreakpoints: null,
|
|
|
|
_stackframes: null,
|
|
|
|
_breakpoints: null,
|
|
|
|
_variables: null,
|
2012-09-12 03:39:51 -07:00
|
|
|
_scripts: null,
|
|
|
|
_globalSearch: null,
|
|
|
|
_globalSearchSplitter: null,
|
|
|
|
_fileSearchKey: null,
|
|
|
|
_lineSearchKey: null,
|
|
|
|
_tokenSearchKey: null,
|
|
|
|
_globalSearchKey: null,
|
|
|
|
_resumeKey: null,
|
|
|
|
_stepOverKey: null,
|
|
|
|
_stepInKey: null,
|
|
|
|
_stepOutKey: null,
|
|
|
|
_resumeButton: null,
|
|
|
|
_stepOverButton: null,
|
|
|
|
_stepInButton: null,
|
|
|
|
_stepOutButton: null,
|
|
|
|
_scriptsSearchbox: null,
|
2012-09-13 14:00:19 -07:00
|
|
|
_globalOperatorLabel: null,
|
2012-09-12 03:39:51 -07:00
|
|
|
_globalOperatorButton: null,
|
2012-09-13 14:00:19 -07:00
|
|
|
_tokenOperatorLabel: null,
|
2012-09-12 03:39:51 -07:00
|
|
|
_tokenOperatorButton: null,
|
2012-09-13 14:00:19 -07:00
|
|
|
_lineOperatorLabel: null,
|
2012-09-12 03:39:51 -07:00
|
|
|
_lineOperatorButton: null
|
2012-04-08 22:15:47 -07:00
|
|
|
};
|
|
|
|
|
2012-04-27 14:18:02 -07:00
|
|
|
/**
|
|
|
|
* A simple way of displaying a "Connect to..." prompt.
|
|
|
|
*/
|
|
|
|
function RemoteDebuggerPrompt() {
|
|
|
|
|
|
|
|
/**
|
2012-07-14 23:47:03 -07:00
|
|
|
* The remote host and port the user wants to connect to.
|
2012-04-27 14:18:02 -07:00
|
|
|
*/
|
2012-07-14 23:47:03 -07:00
|
|
|
this.remote = {};
|
2012-04-27 14:18:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
RemoteDebuggerPrompt.prototype = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows the prompt and sets the uri using the user input.
|
|
|
|
*
|
|
|
|
* @param boolean aIsReconnectingFlag
|
|
|
|
* True to show the reconnect message instead.
|
|
|
|
*/
|
|
|
|
show: function RDP_show(aIsReconnectingFlag) {
|
|
|
|
let check = { value: Prefs.remoteAutoConnect };
|
2012-07-14 23:47:03 -07:00
|
|
|
let input = { value: Prefs.remoteHost + ":" + Prefs.remotePort };
|
|
|
|
let parts;
|
2012-04-27 14:18:02 -07:00
|
|
|
|
|
|
|
while (true) {
|
|
|
|
let result = Services.prompt.prompt(null,
|
|
|
|
L10N.getStr("remoteDebuggerPromptTitle"),
|
|
|
|
L10N.getStr(aIsReconnectingFlag
|
|
|
|
? "remoteDebuggerReconnectMessage"
|
|
|
|
: "remoteDebuggerPromptMessage"), input,
|
|
|
|
L10N.getStr("remoteDebuggerPromptCheck"), check);
|
|
|
|
|
|
|
|
Prefs.remoteAutoConnect = check.value;
|
|
|
|
|
2012-07-14 23:47:03 -07:00
|
|
|
if (!result) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ((parts = input.value.split(":")).length === 2) {
|
|
|
|
let [host, port] = parts;
|
2012-04-27 14:18:02 -07:00
|
|
|
|
2012-07-14 23:47:03 -07:00
|
|
|
if (host.length && port.length) {
|
|
|
|
this.remote = { host: host, port: port };
|
|
|
|
return true;
|
|
|
|
}
|
2012-04-27 14:18:02 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
/**
|
|
|
|
* Functions handling the global search UI.
|
|
|
|
*/
|
|
|
|
function GlobalSearchView() {
|
|
|
|
this._onFetchScriptFinished = this._onFetchScriptFinished.bind(this);
|
|
|
|
this._onFetchScriptsFinished = this._onFetchScriptsFinished.bind(this);
|
|
|
|
this._onLineClick = this._onLineClick.bind(this);
|
|
|
|
this._onMatchClick = this._onMatchClick.bind(this);
|
|
|
|
this._onResultsScroll = this._onResultsScroll.bind(this);
|
2012-09-11 12:50:19 -07:00
|
|
|
this._onFocusLost = this._onFocusLost.bind(this);
|
2012-08-18 02:29:47 -07:00
|
|
|
this._startSearch = this._startSearch.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
GlobalSearchView.prototype = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides or shows the search results container.
|
|
|
|
* @param boolean value
|
|
|
|
*/
|
|
|
|
set hidden(value) {
|
|
|
|
this._pane.hidden = value;
|
|
|
|
this._splitter.hidden = value;
|
|
|
|
},
|
|
|
|
|
2012-09-11 12:50:19 -07:00
|
|
|
/**
|
|
|
|
* True if the search results container is hidden.
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
get hidden() this._pane.hidden,
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
/**
|
|
|
|
* Removes all elements from the search results container, leaving it empty.
|
|
|
|
*/
|
|
|
|
empty: function DVGS_empty() {
|
|
|
|
while (this._pane.firstChild) {
|
|
|
|
this._pane.removeChild(this._pane.firstChild);
|
|
|
|
}
|
|
|
|
this._pane.scrollTop = 0;
|
|
|
|
this._pane.scrollLeft = 0;
|
|
|
|
this._currentlyFocusedMatch = -1;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides and empties the search results container.
|
|
|
|
*/
|
|
|
|
hideAndEmpty: function DVGS_hideAndEmpty() {
|
|
|
|
this.hidden = true;
|
|
|
|
this.empty();
|
|
|
|
DebuggerController.dispatchEvent("Debugger:GlobalSearch:ViewCleared");
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears all the fetched scripts from the cache.
|
|
|
|
*/
|
|
|
|
clearCache: function DVGS_clearCache() {
|
|
|
|
this._scriptSources = new Map();
|
|
|
|
DebuggerController.dispatchEvent("Debugger:GlobalSearch:CacheCleared");
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts fetching all the script sources, silently.
|
|
|
|
*
|
|
|
|
* @param function aFetchCallback [optional]
|
|
|
|
* Called after each script is fetched.
|
|
|
|
* @param function aFetchedCallback [optional]
|
|
|
|
* Called if all the scripts were already fetched.
|
|
|
|
* @param array aUrls [optional]
|
|
|
|
* The urls for the scripts to fetch. If undefined, it defaults to
|
|
|
|
* all the currently known scripts.
|
|
|
|
*/
|
|
|
|
fetchScripts:
|
|
|
|
function DVGS_fetchScripts(aFetchCallback = null,
|
|
|
|
aFetchedCallback = null,
|
|
|
|
aUrls = DebuggerView.Scripts.scriptLocations) {
|
|
|
|
|
|
|
|
// If all the scripts sources were already fetched, then don't do anything.
|
|
|
|
if (this._scriptSources.size() === aUrls.length) {
|
|
|
|
aFetchedCallback && aFetchedCallback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch each new script's source.
|
|
|
|
for (let url of aUrls) {
|
|
|
|
if (this._scriptSources.has(url)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
DebuggerController.dispatchEvent("Debugger:LoadSource", {
|
2012-09-27 01:31:00 -07:00
|
|
|
script: DebuggerView.Scripts.getScriptByLocation(url).getUserData("sourceScript"),
|
2012-08-18 02:29:47 -07:00
|
|
|
options: {
|
|
|
|
silent: true,
|
|
|
|
callback: aFetchCallback
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Schedules searching for a token in all the scripts.
|
|
|
|
*/
|
|
|
|
scheduleSearch: function DVGS_scheduleSearch() {
|
|
|
|
window.clearTimeout(this._searchTimeout);
|
|
|
|
this._searchTimeout = window.setTimeout(this._startSearch, GLOBAL_SEARCH_ACTION_DELAY);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts searching for a token in all the scripts.
|
|
|
|
*/
|
|
|
|
_startSearch: function DVGS__startSearch() {
|
|
|
|
let scriptLocations = DebuggerView.Scripts.scriptLocations;
|
|
|
|
this._scriptCount = scriptLocations.length;
|
|
|
|
|
|
|
|
this.fetchScripts(
|
|
|
|
this._onFetchScriptFinished, this._onFetchScriptsFinished, scriptLocations);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when a script's source has been fetched.
|
|
|
|
*
|
|
|
|
* @param string aScriptUrl
|
|
|
|
* The URL of the source script.
|
|
|
|
* @param string aSourceText
|
|
|
|
* The text of the source script.
|
|
|
|
*/
|
|
|
|
_onFetchScriptFinished: function DVGS__onFetchScriptFinished(aScriptUrl, aSourceText) {
|
|
|
|
this._scriptSources.set(aScriptUrl, aSourceText);
|
|
|
|
|
|
|
|
if (this._scriptSources.size() === this._scriptCount) {
|
|
|
|
this._onFetchScriptsFinished();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when all the script's sources have been fetched.
|
|
|
|
*/
|
|
|
|
_onFetchScriptsFinished: function DVGS__onFetchScriptsFinished() {
|
|
|
|
this.empty();
|
|
|
|
|
|
|
|
let token = DebuggerView.Scripts.searchToken;
|
|
|
|
let lowerCaseToken = token.toLowerCase();
|
|
|
|
|
|
|
|
// Make sure we're actually searching for something.
|
|
|
|
if (!token) {
|
|
|
|
DebuggerController.dispatchEvent("Debugger:GlobalSearch:TokenEmpty");
|
|
|
|
this.hidden = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the results map, containing search details for each script/line.
|
|
|
|
let globalResults = new Map();
|
|
|
|
|
|
|
|
for (let [url, text] of this._scriptSources) {
|
|
|
|
// Check if the search token is not found anywhere in the script source.
|
2012-09-12 07:23:34 -07:00
|
|
|
if (!text.toLowerCase().contains(lowerCaseToken)) {
|
2012-08-18 02:29:47 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let lines = text.split("\n");
|
|
|
|
let scriptResults = {
|
|
|
|
lineResults: [],
|
|
|
|
matchCount: 0
|
|
|
|
};
|
|
|
|
|
|
|
|
for (let i = 0, len = lines.length; i < len; i++) {
|
|
|
|
let line = lines[i];
|
|
|
|
let lowerCaseLine = line.toLowerCase();
|
|
|
|
|
|
|
|
// Search is not case sensitive, and is tied to each line in the source.
|
2012-09-12 07:23:34 -07:00
|
|
|
if (!lowerCaseLine.contains(lowerCaseToken)) {
|
2012-08-18 02:29:47 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let lineNumber = i;
|
|
|
|
let lineContents = [];
|
|
|
|
|
|
|
|
lowerCaseLine.split(lowerCaseToken).reduce(function(prev, curr, index, {length}) {
|
|
|
|
let unmatched = line.substr(prev.length, curr.length);
|
|
|
|
lineContents.push({ string: unmatched });
|
|
|
|
|
|
|
|
if (index !== length - 1) {
|
|
|
|
let matched = line.substr(prev.length + curr.length, token.length);
|
|
|
|
let range = {
|
|
|
|
start: prev.length + curr.length,
|
|
|
|
length: matched.length
|
|
|
|
};
|
|
|
|
lineContents.push({
|
|
|
|
string: matched,
|
|
|
|
range: range,
|
|
|
|
match: true
|
|
|
|
});
|
|
|
|
scriptResults.matchCount++;
|
|
|
|
}
|
|
|
|
return prev + token + curr;
|
|
|
|
}, "");
|
|
|
|
|
|
|
|
scriptResults.lineResults.push({
|
|
|
|
lineNumber: lineNumber,
|
|
|
|
lineContents: lineContents
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (scriptResults.matchCount) {
|
|
|
|
globalResults.set(url, scriptResults);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (globalResults.size()) {
|
|
|
|
this._createGlobalResultsUI(globalResults);
|
|
|
|
this.hidden = false;
|
|
|
|
DebuggerController.dispatchEvent("Debugger:GlobalSearch:MatchFound");
|
|
|
|
} else {
|
|
|
|
this.hidden = true;
|
|
|
|
DebuggerController.dispatchEvent("Debugger:GlobalSearch:MatchNotFound");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates global search results elements and adds them to the results container.
|
|
|
|
*
|
|
|
|
* @param Map aGlobalResults
|
|
|
|
* A map containing the search results, grouped by script url.
|
|
|
|
*/
|
|
|
|
_createGlobalResultsUI:
|
|
|
|
function DVGS__createGlobalResultsUI(aGlobalResults) {
|
|
|
|
let i = 0;
|
|
|
|
|
|
|
|
for (let [scriptUrl, scriptResults] of aGlobalResults) {
|
|
|
|
if (i++ === 0) {
|
|
|
|
this._createScriptResultsUI(scriptUrl, scriptResults, true);
|
|
|
|
} else {
|
|
|
|
// Dispatch subsequent document manipulation operations, to avoid
|
|
|
|
// blocking the main thread when a large number of search results
|
|
|
|
// is found, thus giving the impression of faster searching.
|
|
|
|
Services.tm.currentThread.dispatch({ run:
|
|
|
|
this._createScriptResultsUI.bind(this, scriptUrl, scriptResults) }, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates script search results elements and adds them to the results container.
|
|
|
|
*
|
|
|
|
* @param string aScriptUrl
|
|
|
|
* The URL of the source script.
|
|
|
|
* @param array aScriptResults
|
|
|
|
* An array containing the search results for a single script url.
|
|
|
|
* @param boolean aExpandFlag
|
|
|
|
* True to expand the script results container.
|
|
|
|
*/
|
|
|
|
_createScriptResultsUI:
|
|
|
|
function DVGS__createScriptResultsUI(aScriptUrl, aScriptResults, aExpandFlag) {
|
|
|
|
let { lineResults, matchCount } = aScriptResults;
|
|
|
|
let element;
|
|
|
|
|
|
|
|
for (let lineResult of lineResults) {
|
|
|
|
element = this._createLineSearchResultsUI({
|
|
|
|
scriptUrl: aScriptUrl,
|
|
|
|
matchCount: matchCount,
|
|
|
|
lineNumber: lineResult.lineNumber + 1,
|
|
|
|
lineContents: lineResult.lineContents
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (aExpandFlag) {
|
|
|
|
element.expand(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates per-line search results elements and adds them to the results container.
|
|
|
|
*
|
|
|
|
* @param object aLineResults
|
|
|
|
* An object containing the search results for each line in a script.
|
|
|
|
* @return object
|
|
|
|
* The newly created html node representing the added search results.
|
|
|
|
*/
|
|
|
|
_createLineSearchResultsUI:
|
|
|
|
function DVGS__createLineSearchresultsUI(aLineResults) {
|
|
|
|
let scriptResultsId = "search-results-" + aLineResults.scriptUrl;
|
|
|
|
let scriptResults = document.getElementById(scriptResultsId);
|
|
|
|
|
|
|
|
// Create the script results container if not available yet.
|
|
|
|
if (!scriptResults) {
|
|
|
|
let trimFunc = DebuggerController.SourceScripts.trimUrlLength;
|
|
|
|
let urlLabel = trimFunc(aLineResults.scriptUrl, GLOBAL_SEARCH_URL_MAX_SIZE);
|
|
|
|
|
|
|
|
let resultsUrl = document.createElement("label");
|
|
|
|
resultsUrl.className = "plain script-url";
|
|
|
|
resultsUrl.setAttribute("value", urlLabel);
|
|
|
|
|
|
|
|
let resultsCount = document.createElement("label");
|
|
|
|
resultsCount.className = "plain match-count";
|
|
|
|
resultsCount.setAttribute("value", "(" + aLineResults.matchCount + ")");
|
|
|
|
|
|
|
|
let arrow = document.createElement("box");
|
|
|
|
arrow.className = "arrow";
|
|
|
|
|
|
|
|
let resultsHeader = document.createElement("hbox");
|
|
|
|
resultsHeader.className = "dbg-results-header";
|
|
|
|
resultsHeader.setAttribute("align", "center")
|
|
|
|
resultsHeader.appendChild(arrow);
|
|
|
|
resultsHeader.appendChild(resultsUrl);
|
|
|
|
resultsHeader.appendChild(resultsCount);
|
|
|
|
|
|
|
|
let resultsContainer = document.createElement("vbox");
|
|
|
|
resultsContainer.className = "dbg-results-container";
|
|
|
|
|
|
|
|
scriptResults = document.createElement("vbox");
|
|
|
|
scriptResults.id = scriptResultsId;
|
|
|
|
scriptResults.className = "dbg-script-results";
|
|
|
|
scriptResults.header = resultsHeader;
|
|
|
|
scriptResults.container = resultsContainer;
|
|
|
|
scriptResults.appendChild(resultsHeader);
|
|
|
|
scriptResults.appendChild(resultsContainer);
|
|
|
|
this._pane.appendChild(scriptResults);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expands the element, showing all the added details.
|
|
|
|
*
|
|
|
|
* @param boolean aSkipAnimationFlag
|
|
|
|
* Pass true to not show an opening animation.
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
scriptResults.expand = function DVGS_element_expand(aSkipAnimationFlag) {
|
|
|
|
resultsContainer.setAttribute("open", "");
|
|
|
|
arrow.setAttribute("open", "");
|
|
|
|
|
|
|
|
if (!aSkipAnimationFlag) {
|
|
|
|
resultsContainer.setAttribute("animated", "");
|
|
|
|
}
|
|
|
|
return scriptResults;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Collapses the element, hiding all the added details.
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
scriptResults.collapse = function DVGS_element_collapse() {
|
|
|
|
resultsContainer.removeAttribute("animated");
|
|
|
|
resultsContainer.removeAttribute("open");
|
|
|
|
arrow.removeAttribute("open");
|
|
|
|
return scriptResults;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggles between the element collapse/expand state.
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
scriptResults.toggle = function DVGS_element_toggle(e) {
|
|
|
|
if (e instanceof Event) {
|
|
|
|
scriptResults._userToggle = true;
|
|
|
|
}
|
|
|
|
scriptResults.expanded = !scriptResults.expanded;
|
|
|
|
return scriptResults;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns if the element is expanded.
|
|
|
|
* @return boolean
|
|
|
|
* True if the element is expanded.
|
|
|
|
*/
|
|
|
|
Object.defineProperty(scriptResults, "expanded", {
|
|
|
|
get: function DVP_element_getExpanded() {
|
|
|
|
return arrow.hasAttribute("open");
|
|
|
|
},
|
|
|
|
set: function DVP_element_setExpanded(value) {
|
|
|
|
if (value) {
|
|
|
|
scriptResults.expand();
|
|
|
|
} else {
|
|
|
|
scriptResults.collapse();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when a header in the search results container is clicked.
|
|
|
|
*/
|
|
|
|
resultsHeader.addEventListener("click", scriptResults.toggle, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
let lineNumber = document.createElement("label");
|
|
|
|
lineNumber.className = "plain line-number";
|
|
|
|
lineNumber.setAttribute("value", aLineResults.lineNumber);
|
|
|
|
|
|
|
|
let lineContents = document.createElement("hbox");
|
|
|
|
lineContents.setAttribute("flex", "1");
|
|
|
|
lineContents.className = "line-contents";
|
|
|
|
lineContents.addEventListener("click", this._onLineClick, false);
|
|
|
|
|
|
|
|
let lineContent;
|
|
|
|
let totalLength = 0;
|
|
|
|
let ellipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString);
|
|
|
|
|
|
|
|
for (lineContent of aLineResults.lineContents) {
|
|
|
|
let string = lineContent.string;
|
|
|
|
let match = lineContent.match;
|
|
|
|
|
|
|
|
string = string.substr(0, GLOBAL_SEARCH_LINE_MAX_SIZE - totalLength);
|
|
|
|
totalLength += string.length;
|
|
|
|
|
|
|
|
let label = document.createElement("label");
|
|
|
|
label.className = "plain string";
|
|
|
|
label.setAttribute("value", string);
|
|
|
|
label.setAttribute("match", match || false);
|
|
|
|
lineContents.appendChild(label);
|
|
|
|
|
|
|
|
if (match) {
|
|
|
|
label.addEventListener("click", this._onMatchClick, false);
|
|
|
|
label.setUserData("lineResults", aLineResults, null);
|
|
|
|
label.setUserData("lineContentRange", lineContent.range, null);
|
|
|
|
label.container = scriptResults;
|
|
|
|
}
|
|
|
|
if (totalLength >= GLOBAL_SEARCH_LINE_MAX_SIZE) {
|
|
|
|
label = document.createElement("label");
|
|
|
|
label.className = "plain string";
|
|
|
|
label.setAttribute("value", ellipsis.data);
|
|
|
|
lineContents.appendChild(label);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let searchResult = document.createElement("hbox");
|
|
|
|
searchResult.className = "dbg-search-result";
|
|
|
|
searchResult.appendChild(lineNumber);
|
|
|
|
searchResult.appendChild(lineContents);
|
|
|
|
|
|
|
|
let resultsContainer = scriptResults.container;
|
|
|
|
resultsContainer.appendChild(searchResult);
|
|
|
|
|
|
|
|
// Return the element for later use if necessary.
|
|
|
|
return scriptResults;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Focuses the next found match in the source editor.
|
|
|
|
*/
|
|
|
|
focusNextMatch: function DVGS_focusNextMatch() {
|
|
|
|
let matches = this._pane.querySelectorAll(".string[match=true]");
|
|
|
|
if (!matches.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (++this._currentlyFocusedMatch >= matches.length) {
|
|
|
|
this._currentlyFocusedMatch = 0;
|
|
|
|
}
|
|
|
|
this._onMatchClick({ target: matches[this._currentlyFocusedMatch] });
|
|
|
|
},
|
|
|
|
|
2012-09-12 03:39:29 -07:00
|
|
|
/**
|
|
|
|
* Focuses the previously found match in the source editor.
|
|
|
|
*/
|
|
|
|
focusPrevMatch: function DVGS_focusPrevMatch() {
|
|
|
|
let matches = this._pane.querySelectorAll(".string[match=true]");
|
|
|
|
if (!matches.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (--this._currentlyFocusedMatch < 0) {
|
|
|
|
this._currentlyFocusedMatch = matches.length - 1;
|
|
|
|
}
|
|
|
|
this._onMatchClick({ target: matches[this._currentlyFocusedMatch] });
|
|
|
|
},
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
/**
|
|
|
|
* Called when a line in the search results container is clicked.
|
|
|
|
*/
|
|
|
|
_onLineClick: function DVGS__onLineClick(e) {
|
|
|
|
let firstMatch = e.target.parentNode.querySelector(".string[match=true]");
|
|
|
|
this._onMatchClick({ target: firstMatch });
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when a match in the search results container is clicked.
|
|
|
|
*/
|
|
|
|
_onMatchClick: function DVGLS__onMatchClick(e) {
|
|
|
|
if (e instanceof Event) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
}
|
|
|
|
let match = e.target;
|
|
|
|
|
|
|
|
match.container.expand(true);
|
|
|
|
this._scrollMatchIntoViewIfNeeded(match);
|
|
|
|
this._animateMatchBounce(match);
|
|
|
|
|
|
|
|
let results = match.getUserData("lineResults");
|
|
|
|
let range = match.getUserData("lineContentRange");
|
|
|
|
|
|
|
|
let stackframes = DebuggerController.StackFrames;
|
|
|
|
stackframes.updateEditorToLocation(results.scriptUrl, results.lineNumber, 0, 0, 1);
|
|
|
|
|
|
|
|
let editor = DebuggerView.editor;
|
|
|
|
let offset = editor.getCaretOffset();
|
|
|
|
editor.setSelection(offset + range.start, offset + range.start + range.length);
|
|
|
|
},
|
|
|
|
|
2012-09-11 12:50:19 -07:00
|
|
|
/**
|
|
|
|
* Listener handling the searchbox blur event.
|
|
|
|
*/
|
|
|
|
_onFocusLost: function DVGS__onFocusLost(e) {
|
|
|
|
this.hideAndEmpty();
|
|
|
|
},
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
/**
|
|
|
|
* Listener handling the global search container scroll event.
|
|
|
|
*/
|
|
|
|
_onResultsScroll: function DVGS__onResultsScroll(e) {
|
|
|
|
this._expandAllVisibleResults();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expands all the script results that are currently visible.
|
|
|
|
*/
|
|
|
|
_expandAllVisibleResults: function DVGS__expandAllVisibleResults() {
|
|
|
|
let collapsed = this._pane.querySelectorAll(".dbg-results-container:not([open])");
|
|
|
|
|
|
|
|
for (let i = 0, l = collapsed.length; i < l; i++) {
|
|
|
|
this._expandResultsIfNeeded(collapsed[i].parentNode);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expands the script results it they are currently visible.
|
|
|
|
* @param nsIDOMElement aTarget
|
|
|
|
*/
|
|
|
|
_expandResultsIfNeeded: function DVGS__expandResultsIfNeeded(aTarget) {
|
|
|
|
if (aTarget.expanded || aTarget._userToggle) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let { clientHeight } = this._pane;
|
|
|
|
let { top, height } = aTarget.getBoundingClientRect();
|
|
|
|
|
|
|
|
if (top - height <= clientHeight || this._forceExpandResults) {
|
|
|
|
aTarget.expand(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Scrolls a match into view.
|
|
|
|
* @param nsIDOMElement aTarget
|
|
|
|
*/
|
|
|
|
_scrollMatchIntoViewIfNeeded: function DVGS__scrollMatchIntoViewIfNeeded(aTarget) {
|
|
|
|
let { clientHeight } = this._pane;
|
|
|
|
let { top, height } = aTarget.getBoundingClientRect();
|
|
|
|
|
|
|
|
let style = window.getComputedStyle(aTarget);
|
|
|
|
let topBorderSize = window.parseInt(style.getPropertyValue("border-top-width"));
|
|
|
|
let bottomBorderSize = window.parseInt(style.getPropertyValue("border-bottom-width"));
|
|
|
|
|
|
|
|
let marginY = top - (height + topBorderSize + bottomBorderSize) * 2;
|
|
|
|
if (marginY <= 0) {
|
|
|
|
this._pane.scrollTop += marginY;
|
|
|
|
}
|
|
|
|
if (marginY + height > clientHeight) {
|
|
|
|
this._pane.scrollTop += height - (clientHeight - marginY);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts a bounce animation for a match.
|
|
|
|
* @param nsIDOMElement aTarget
|
|
|
|
*/
|
|
|
|
_animateMatchBounce: function DVGS__animateMatchBounce(aTarget) {
|
|
|
|
aTarget.setAttribute("focused", "");
|
|
|
|
|
|
|
|
window.setTimeout(function() {
|
|
|
|
aTarget.removeAttribute("focused");
|
|
|
|
}, GLOBAL_SEARCH_MATCH_FLASH_DURATION);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Map containing the sources for all the currently known scripts.
|
|
|
|
*/
|
|
|
|
_scriptSources: new Map(),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The currently focused match from the search results container.
|
|
|
|
*/
|
|
|
|
_currentlyFocusedMatch: -1,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The cached global search results container.
|
|
|
|
*/
|
|
|
|
_pane: null,
|
|
|
|
_splitter: null,
|
2012-09-11 12:50:19 -07:00
|
|
|
_searchbox: null,
|
2012-08-18 02:29:47 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialization function, called when the debugger is initialized.
|
|
|
|
*/
|
2012-09-12 03:39:51 -07:00
|
|
|
initialize: function DVGS_initialize() {
|
|
|
|
this._pane = DebuggerView._globalSearch;
|
|
|
|
this._splitter = DebuggerView._globalSearchSplitter;
|
|
|
|
this._searchbox = DebuggerView._scriptsSearchbox;
|
2012-08-18 02:29:47 -07:00
|
|
|
|
|
|
|
this._pane.addEventListener("scroll", this._onResultsScroll, false);
|
2012-09-11 12:50:19 -07:00
|
|
|
this._searchbox.addEventListener("blur", this._onFocusLost, false);
|
2012-08-18 02:29:47 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destruction function, called when the debugger is shut down.
|
|
|
|
*/
|
|
|
|
destroy: function DVS_destroy() {
|
|
|
|
this._pane.removeEventListener("scroll", this._onResultsScroll, false);
|
2012-09-11 12:50:19 -07:00
|
|
|
this._searchbox.removeEventListener("blur", this._onFocusLost, false);
|
2012-08-18 02:29:47 -07:00
|
|
|
|
|
|
|
this.hideAndEmpty();
|
|
|
|
this._pane = null;
|
|
|
|
this._splitter = null;
|
2012-09-11 12:50:19 -07:00
|
|
|
this._searchbox = null;
|
2012-08-18 02:29:47 -07:00
|
|
|
this._scriptSources = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Functions handling the scripts UI.
|
|
|
|
*/
|
|
|
|
function ScriptsView() {
|
|
|
|
this._onScriptsChange = this._onScriptsChange.bind(this);
|
2012-09-11 12:50:20 -07:00
|
|
|
this._onScriptsSearchClick = this._onScriptsSearchClick.bind(this);
|
2012-09-12 03:39:29 -07:00
|
|
|
this._onScriptsSearchBlur = this._onScriptsSearchBlur.bind(this);
|
2012-04-19 03:44:33 -07:00
|
|
|
this._onScriptsSearch = this._onScriptsSearch.bind(this);
|
2012-09-12 03:39:29 -07:00
|
|
|
this._onScriptsKeyPress = this._onScriptsKeyPress.bind(this);
|
2012-04-08 22:15:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
ScriptsView.prototype = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all elements from the scripts container, leaving it empty.
|
|
|
|
*/
|
|
|
|
empty: function DVS_empty() {
|
2012-07-15 00:06:02 -07:00
|
|
|
this._scripts.selectedIndex = -1;
|
2012-07-18 06:13:15 -07:00
|
|
|
this._scripts.setAttribute("label", L10N.getStr("noScriptsText"));
|
|
|
|
this._scripts.removeAttribute("tooltiptext");
|
2012-07-15 00:06:02 -07:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
while (this._scripts.firstChild) {
|
|
|
|
this._scripts.removeChild(this._scripts.firstChild);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-04-19 03:44:33 -07:00
|
|
|
/**
|
|
|
|
* Removes the input in the searchbox and unhides all the scripts.
|
|
|
|
*/
|
|
|
|
clearSearch: function DVS_clearSearch() {
|
|
|
|
this._searchbox.value = "";
|
|
|
|
this._onScriptsSearch({});
|
|
|
|
},
|
|
|
|
|
2012-06-04 05:35:24 -07:00
|
|
|
/**
|
|
|
|
* Checks whether the script with the specified URL is among the scripts
|
|
|
|
* known to the debugger (ignoring the query & reference).
|
|
|
|
*
|
|
|
|
* @param string aUrl
|
|
|
|
* The script URL.
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
containsIgnoringQuery: function DVS_containsIgnoringQuery(aUrl) {
|
|
|
|
let sourceScripts = DebuggerController.SourceScripts;
|
|
|
|
aUrl = sourceScripts.trimUrlQuery(aUrl);
|
|
|
|
|
|
|
|
if (this._tmpScripts.some(function(element) {
|
|
|
|
return sourceScripts.trimUrlQuery(element.script.url) == aUrl;
|
|
|
|
})) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (this.scriptLocations.some(function(url) {
|
|
|
|
return sourceScripts.trimUrlQuery(url) == aUrl;
|
|
|
|
})) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Checks whether the script with the specified URL is among the scripts
|
|
|
|
* known to the debugger and shown in the list.
|
2012-02-07 09:22:30 -08:00
|
|
|
*
|
2012-04-08 22:15:47 -07:00
|
|
|
* @param string aUrl
|
|
|
|
* The script URL.
|
|
|
|
* @return boolean
|
2012-02-07 09:22:30 -08:00
|
|
|
*/
|
2012-04-08 22:15:47 -07:00
|
|
|
contains: function DVS_contains(aUrl) {
|
2012-04-17 11:16:57 -07:00
|
|
|
if (this._tmpScripts.some(function(element) {
|
|
|
|
return element.script.url == aUrl;
|
|
|
|
})) {
|
|
|
|
return true;
|
|
|
|
}
|
2012-04-08 22:15:47 -07:00
|
|
|
if (this._scripts.getElementsByAttribute("value", aUrl).length > 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2012-02-07 09:22:30 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-04-08 22:15:47 -07:00
|
|
|
* Checks whether the script with the specified label is among the scripts
|
|
|
|
* known to the debugger and shown in the list.
|
2012-02-07 09:22:30 -08:00
|
|
|
*
|
2012-04-08 22:15:47 -07:00
|
|
|
* @param string aLabel
|
|
|
|
* The script label.
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
containsLabel: function DVS_containsLabel(aLabel) {
|
2012-04-17 11:16:57 -07:00
|
|
|
if (this._tmpScripts.some(function(element) {
|
|
|
|
return element.label == aLabel;
|
|
|
|
})) {
|
|
|
|
return true;
|
|
|
|
}
|
2012-04-08 22:15:47 -07:00
|
|
|
if (this._scripts.getElementsByAttribute("label", aLabel).length > 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2012-08-02 22:20:33 -07:00
|
|
|
/**
|
|
|
|
* Selects the script with the specified index from the list.
|
|
|
|
*
|
|
|
|
* @param number aIndex
|
|
|
|
* The script index.
|
|
|
|
*/
|
|
|
|
selectIndex: function DVS_selectIndex(aIndex) {
|
|
|
|
this._scripts.selectedIndex = aIndex;
|
|
|
|
},
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Selects the script with the specified URL from the list.
|
|
|
|
*
|
|
|
|
* @param string aUrl
|
|
|
|
* The script URL.
|
2012-02-07 09:22:30 -08:00
|
|
|
*/
|
2012-04-08 22:15:47 -07:00
|
|
|
selectScript: function DVS_selectScript(aUrl) {
|
|
|
|
for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
|
|
|
|
if (this._scripts.getItemAtIndex(i).value == aUrl) {
|
|
|
|
this._scripts.selectedIndex = i;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether the script with the specified URL is selected in the list.
|
|
|
|
*
|
|
|
|
* @param string aUrl
|
|
|
|
* The script URL.
|
|
|
|
*/
|
|
|
|
isSelected: function DVS_isSelected(aUrl) {
|
|
|
|
if (this._scripts.selectedItem &&
|
|
|
|
this._scripts.selectedItem.value == aUrl) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the URL of the selected script.
|
|
|
|
* @return string | null
|
|
|
|
*/
|
|
|
|
get selected() {
|
|
|
|
return this._scripts.selectedItem ?
|
|
|
|
this._scripts.selectedItem.value : null;
|
|
|
|
},
|
|
|
|
|
2012-08-02 22:20:33 -07:00
|
|
|
/**
|
|
|
|
* Gets the most recently selected script url.
|
|
|
|
* @return string | null
|
|
|
|
*/
|
|
|
|
get preferredScriptUrl()
|
|
|
|
this._preferredScriptUrl ? this._preferredScriptUrl : null,
|
|
|
|
|
2012-08-25 07:23:23 -07:00
|
|
|
/**
|
|
|
|
* Sets the most recently selected script url.
|
|
|
|
* @param string
|
|
|
|
*/
|
|
|
|
set preferredScriptUrl(value) this._preferredScriptUrl = value,
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
/**
|
|
|
|
* Gets the script in the container having the specified label.
|
|
|
|
*
|
|
|
|
* @param string aLabel
|
|
|
|
* The label used to identify the script.
|
|
|
|
* @return element | null
|
|
|
|
* The matched element, or null if nothing is found.
|
|
|
|
*/
|
|
|
|
getScriptByLabel: function DVS_getScriptByLabel(aLabel) {
|
|
|
|
return this._scripts.getElementsByAttribute("label", aLabel)[0];
|
|
|
|
},
|
|
|
|
|
2012-04-17 11:16:57 -07:00
|
|
|
/**
|
|
|
|
* Returns the list of labels in the scripts container.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
get scriptLabels() {
|
|
|
|
let labels = [];
|
|
|
|
for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
|
|
|
|
labels.push(this._scripts.getItemAtIndex(i).label);
|
|
|
|
}
|
|
|
|
return labels;
|
|
|
|
},
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
/**
|
|
|
|
* Gets the script in the container having the specified label.
|
|
|
|
*
|
|
|
|
* @param string aUrl
|
|
|
|
* The url used to identify the script.
|
|
|
|
* @return element | null
|
|
|
|
* The matched element, or null if nothing is found.
|
|
|
|
*/
|
|
|
|
getScriptByLocation: function DVS_getScriptByLocation(aUrl) {
|
|
|
|
return this._scripts.getElementsByAttribute("value", aUrl)[0];
|
|
|
|
},
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Returns the list of URIs for scripts in the page.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
get scriptLocations() {
|
|
|
|
let locations = [];
|
|
|
|
for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
|
|
|
|
locations.push(this._scripts.getItemAtIndex(i).value);
|
|
|
|
}
|
|
|
|
return locations;
|
|
|
|
},
|
|
|
|
|
2012-04-19 03:44:33 -07:00
|
|
|
/**
|
|
|
|
* Gets the number of visible (hidden=false) scripts in the container.
|
|
|
|
* @return number
|
|
|
|
*/
|
|
|
|
get visibleItemsCount() {
|
|
|
|
let count = 0;
|
|
|
|
for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
|
|
|
|
count += this._scripts.getItemAtIndex(i).hidden ? 0 : 1;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
},
|
|
|
|
|
2012-04-19 03:44:33 -07:00
|
|
|
/**
|
2012-04-17 11:16:57 -07:00
|
|
|
* Prepares a script to be added to the scripts container. This allows
|
|
|
|
* for a large number of scripts to be batched up before being
|
|
|
|
* alphabetically sorted and added in the container.
|
|
|
|
* @see ScriptsView.commitScripts
|
|
|
|
*
|
|
|
|
* If aForceFlag is true, the script will be immediately inserted at the
|
|
|
|
* necessary position in the container so that all the scripts remain sorted.
|
|
|
|
* This can be much slower than batching up multiple scripts.
|
2012-04-17 11:16:57 -07:00
|
|
|
*
|
|
|
|
* @param string aLabel
|
|
|
|
* The simplified script location to be shown.
|
|
|
|
* @param string aScript
|
|
|
|
* The source script.
|
2012-04-17 11:16:57 -07:00
|
|
|
* @param boolean aForceFlag
|
|
|
|
* True to force the script to be immediately added.
|
2012-04-17 11:16:57 -07:00
|
|
|
*/
|
2012-04-17 11:16:57 -07:00
|
|
|
addScript: function DVS_addScript(aLabel, aScript, aForceFlag) {
|
|
|
|
// Batch the script to be added later.
|
|
|
|
if (!aForceFlag) {
|
|
|
|
this._tmpScripts.push({ label: aLabel, script: aScript });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the target position in the menulist and insert the script there.
|
|
|
|
for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
|
|
|
|
if (this._scripts.getItemAtIndex(i).label > aLabel) {
|
|
|
|
this._createScriptElement(aLabel, aScript, i);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// The script is alphabetically the last one.
|
2012-08-02 22:20:33 -07:00
|
|
|
this._createScriptElement(aLabel, aScript, -1);
|
2012-04-17 11:16:57 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds all the prepared scripts to the scripts container.
|
|
|
|
* If a script already exists (was previously added), nothing happens.
|
|
|
|
*/
|
|
|
|
commitScripts: function DVS_commitScripts() {
|
|
|
|
let newScripts = this._tmpScripts;
|
|
|
|
this._tmpScripts = [];
|
|
|
|
|
|
|
|
if (!newScripts || !newScripts.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
newScripts.sort(function(a, b) {
|
|
|
|
return a.label.toLowerCase() > b.label.toLowerCase();
|
|
|
|
});
|
|
|
|
|
|
|
|
for (let i = 0, l = newScripts.length; i < l; i++) {
|
|
|
|
let item = newScripts[i];
|
2012-08-02 22:20:33 -07:00
|
|
|
this._createScriptElement(item.label, item.script, -1);
|
2012-04-17 11:16:57 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a custom script element and adds it to the scripts container.
|
|
|
|
* If the script with the specified label already exists, nothing happens.
|
|
|
|
*
|
|
|
|
* @param string aLabel
|
|
|
|
* The simplified script location to be shown.
|
|
|
|
* @param string aScript
|
|
|
|
* The source script.
|
|
|
|
* @param number aIndex
|
|
|
|
* The index where to insert to new script in the container.
|
|
|
|
* Pass -1 to append the script at the end.
|
|
|
|
*/
|
2012-08-02 22:20:33 -07:00
|
|
|
_createScriptElement: function DVS__createScriptElement(aLabel, aScript, aIndex)
|
2012-04-17 11:16:57 -07:00
|
|
|
{
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure we don't duplicate anything.
|
2012-06-04 05:35:24 -07:00
|
|
|
if (aLabel == "null" || this.containsLabel(aLabel) || this.contains(aScript.url)) {
|
2012-04-17 11:16:57 -07:00
|
|
|
return;
|
2012-04-08 22:15:47 -07:00
|
|
|
}
|
|
|
|
|
2012-04-17 11:16:57 -07:00
|
|
|
let scriptItem =
|
|
|
|
aIndex == -1 ? this._scripts.appendItem(aLabel, aScript.url)
|
|
|
|
: this._scripts.insertItemAt(aIndex, aLabel, aScript.url);
|
2012-04-17 11:16:57 -07:00
|
|
|
|
2012-04-17 11:16:57 -07:00
|
|
|
scriptItem.setAttribute("tooltiptext", aScript.url);
|
|
|
|
scriptItem.setUserData("sourceScript", aScript, null);
|
2012-04-08 22:15:47 -07:00
|
|
|
},
|
|
|
|
|
2012-06-19 08:11:27 -07:00
|
|
|
/**
|
|
|
|
* Gets the entered file, line and token entered in the searchbox.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
* A [file, line, token] array.
|
|
|
|
*/
|
2012-08-18 02:29:47 -07:00
|
|
|
get searchboxInfo() {
|
|
|
|
let file, line, token, isGlobal;
|
2012-06-19 08:11:27 -07:00
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
let rawValue = this._searchbox.value;
|
2012-06-19 08:11:27 -07:00
|
|
|
let rawLength = rawValue.length;
|
2012-08-18 02:29:47 -07:00
|
|
|
let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG);
|
|
|
|
let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
|
|
|
|
let globalFlagIndex = rawValue.lastIndexOf(SEARCH_GLOBAL_FLAG);
|
|
|
|
|
|
|
|
if (globalFlagIndex !== 0) {
|
|
|
|
let fileEnd = lineFlagIndex !== -1 ? lineFlagIndex : tokenFlagIndex !== -1 ? tokenFlagIndex : rawLength;
|
|
|
|
let lineEnd = tokenFlagIndex !== -1 ? tokenFlagIndex : rawLength;
|
|
|
|
|
|
|
|
file = rawValue.slice(0, fileEnd);
|
|
|
|
line = window.parseInt(rawValue.slice(fileEnd + 1, lineEnd)) || -1;
|
|
|
|
token = rawValue.slice(lineEnd + 1);
|
|
|
|
isGlobal = false;
|
|
|
|
} else {
|
|
|
|
file = "";
|
|
|
|
line = -1;
|
|
|
|
token = rawValue.slice(1);
|
|
|
|
isGlobal = true;
|
|
|
|
}
|
2012-06-19 08:11:27 -07:00
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
return [file, line, token, isGlobal];
|
2012-06-19 08:11:27 -07:00
|
|
|
},
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
/**
|
|
|
|
* Returns the current search token.
|
|
|
|
*/
|
|
|
|
get searchToken() this.searchboxInfo[2],
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
2012-04-19 03:44:33 -07:00
|
|
|
* The click listener for the scripts container.
|
2012-04-08 22:15:47 -07:00
|
|
|
*/
|
|
|
|
_onScriptsChange: function DVS__onScriptsChange() {
|
2012-07-15 00:06:02 -07:00
|
|
|
let selectedItem = this._scripts.selectedItem;
|
|
|
|
if (!selectedItem) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-07-18 06:13:15 -07:00
|
|
|
this._preferredScript = selectedItem;
|
2012-08-02 22:20:33 -07:00
|
|
|
this._preferredScriptUrl = selectedItem.value;
|
2012-07-17 23:42:51 -07:00
|
|
|
this._scripts.setAttribute("tooltiptext", selectedItem.value);
|
2012-07-18 06:13:15 -07:00
|
|
|
DebuggerController.SourceScripts.showScript(selectedItem.getUserData("sourceScript"));
|
2012-04-08 22:15:47 -07:00
|
|
|
},
|
|
|
|
|
2012-09-12 03:39:51 -07:00
|
|
|
_prevSearchedFile: "",
|
|
|
|
_prevSearchedLine: 0,
|
|
|
|
_prevSearchedToken: "",
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
2012-08-18 02:29:47 -07:00
|
|
|
* Performs a file search if necessary.
|
|
|
|
*
|
|
|
|
* @param string aFile
|
|
|
|
* The script filename to search for.
|
2012-04-19 03:44:33 -07:00
|
|
|
*/
|
2012-08-18 02:29:47 -07:00
|
|
|
_performFileSearch: function DVS__performFileSearch(aFile) {
|
2012-04-19 03:44:33 -07:00
|
|
|
let scripts = this._scripts;
|
2012-07-18 06:13:15 -07:00
|
|
|
|
2012-04-19 03:44:33 -07:00
|
|
|
// Presume we won't find anything.
|
|
|
|
scripts.selectedItem = this._preferredScript;
|
2012-07-18 06:13:15 -07:00
|
|
|
scripts.setAttribute("label", this._preferredScript.label);
|
|
|
|
scripts.setAttribute("tooltiptext", this._preferredScript.value);
|
2012-04-19 03:44:33 -07:00
|
|
|
|
|
|
|
// If we're not searching for a file anymore, unhide all the scripts.
|
2012-08-18 02:29:47 -07:00
|
|
|
if (!aFile && this._someScriptsHidden) {
|
|
|
|
this._someScriptsHidden = false;
|
|
|
|
|
2012-04-19 03:44:33 -07:00
|
|
|
for (let i = 0, l = scripts.itemCount; i < l; i++) {
|
|
|
|
scripts.getItemAtIndex(i).hidden = false;
|
|
|
|
}
|
2012-08-18 02:29:47 -07:00
|
|
|
} else if (this._prevSearchedFile !== aFile) {
|
|
|
|
let lowerCaseFile = aFile.toLowerCase();
|
2012-07-18 06:13:15 -07:00
|
|
|
let found = false;
|
|
|
|
|
|
|
|
for (let i = 0, l = scripts.itemCount; i < l; i++) {
|
2012-04-19 03:44:33 -07:00
|
|
|
let item = scripts.getItemAtIndex(i);
|
2012-08-18 02:29:47 -07:00
|
|
|
let lowerCaseLabel = item.label.toLowerCase();
|
2012-04-19 03:44:33 -07:00
|
|
|
|
2012-06-19 08:11:27 -07:00
|
|
|
// Search is not case sensitive, and is tied to the label not the url.
|
2012-08-18 02:29:47 -07:00
|
|
|
if (lowerCaseLabel.match(aFile)) {
|
2012-04-19 03:44:33 -07:00
|
|
|
item.hidden = false;
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
found = true;
|
|
|
|
scripts.selectedItem = item;
|
2012-07-18 06:13:15 -07:00
|
|
|
scripts.setAttribute("label", item.label);
|
|
|
|
scripts.setAttribute("tooltiptext", item.value);
|
2012-04-19 03:44:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Hide what doesn't match our search.
|
|
|
|
else {
|
|
|
|
item.hidden = true;
|
2012-08-18 02:29:47 -07:00
|
|
|
this._someScriptsHidden = true;
|
2012-04-19 03:44:33 -07:00
|
|
|
}
|
|
|
|
}
|
2012-07-18 06:13:15 -07:00
|
|
|
if (!found) {
|
|
|
|
scripts.setAttribute("label", L10N.getStr("noMatchingScriptsText"));
|
|
|
|
scripts.removeAttribute("tooltiptext");
|
|
|
|
}
|
2012-04-19 03:44:33 -07:00
|
|
|
}
|
2012-08-18 02:29:47 -07:00
|
|
|
this._prevSearchedFile = aFile;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs a line search if necessary.
|
|
|
|
*
|
|
|
|
* @param number aLine
|
|
|
|
* The script line number to jump to.
|
|
|
|
*/
|
|
|
|
_performLineSearch: function DVS__performLineSearch(aLine) {
|
|
|
|
// Jump to lines in the currently visible source.
|
|
|
|
if (this._prevSearchedLine !== aLine && aLine > -1) {
|
|
|
|
DebuggerView.editor.setCaretPosition(aLine - 1);
|
2012-04-19 03:44:33 -07:00
|
|
|
}
|
2012-08-18 02:29:47 -07:00
|
|
|
this._prevSearchedLine = aLine;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs a token search if necessary.
|
|
|
|
*
|
|
|
|
* @param string aToken
|
|
|
|
* The script token to find.
|
|
|
|
*/
|
|
|
|
_performTokenSearch: function DVS__performTokenSearch(aToken) {
|
|
|
|
// Search for tokens in the currently visible source.
|
|
|
|
if (this._prevSearchedToken !== aToken && aToken.length > 0) {
|
|
|
|
let editor = DebuggerView.editor;
|
|
|
|
let offset = editor.find(aToken, { ignoreCase: true });
|
2012-04-19 03:44:33 -07:00
|
|
|
if (offset > -1) {
|
2012-08-18 02:29:47 -07:00
|
|
|
editor.setSelection(offset, offset + aToken.length)
|
2012-04-19 03:44:33 -07:00
|
|
|
}
|
|
|
|
}
|
2012-08-18 02:29:47 -07:00
|
|
|
this._prevSearchedToken = aToken;
|
|
|
|
},
|
|
|
|
|
2012-09-11 12:50:20 -07:00
|
|
|
/**
|
|
|
|
* The focus listener for the scripts search box.
|
|
|
|
*/
|
|
|
|
_onScriptsSearchClick: function DVS__onScriptsSearchClick() {
|
|
|
|
this._searchboxPanel.openPopup(this._searchbox);
|
|
|
|
},
|
|
|
|
|
2012-09-12 03:39:29 -07:00
|
|
|
/**
|
|
|
|
* The blur listener for the scripts search box.
|
|
|
|
*/
|
|
|
|
_onScriptsSearchBlur: function DVS__onScriptsSearchBlur() {
|
|
|
|
this._searchboxPanel.hidePopup();
|
|
|
|
},
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
/**
|
|
|
|
* The search listener for the scripts search box.
|
|
|
|
*/
|
|
|
|
_onScriptsSearch: function DVS__onScriptsSearch() {
|
|
|
|
// If the webpage has no scripts, searching is redundant.
|
|
|
|
if (!this._scripts.itemCount) {
|
|
|
|
return;
|
|
|
|
}
|
2012-09-11 12:50:20 -07:00
|
|
|
this._searchboxPanel.hidePopup();
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
let [file, line, token, isGlobal] = this.searchboxInfo;
|
|
|
|
|
|
|
|
// If this is a global script search, schedule a search in all the sources,
|
|
|
|
// or hide the pane otherwise.
|
|
|
|
if (isGlobal) {
|
|
|
|
DebuggerView.GlobalSearch.scheduleSearch();
|
|
|
|
} else {
|
|
|
|
DebuggerView.GlobalSearch.hideAndEmpty();
|
|
|
|
this._performFileSearch(file);
|
|
|
|
this._performLineSearch(line);
|
|
|
|
this._performTokenSearch(token);
|
|
|
|
}
|
2012-04-19 03:44:33 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-09-12 03:39:29 -07:00
|
|
|
* The keypress listener for the scripts search box.
|
2012-04-19 03:44:33 -07:00
|
|
|
*/
|
2012-09-12 03:39:29 -07:00
|
|
|
_onScriptsKeyPress: function DVS__onScriptsKeyPress(e) {
|
2012-04-19 03:44:33 -07:00
|
|
|
if (e.keyCode === e.DOM_VK_ESCAPE) {
|
|
|
|
DebuggerView.editor.focus();
|
|
|
|
return;
|
|
|
|
}
|
2012-09-12 03:39:29 -07:00
|
|
|
var action;
|
|
|
|
|
|
|
|
if (e.keyCode === e.DOM_VK_DOWN ||
|
|
|
|
e.keyCode === e.DOM_VK_RETURN ||
|
|
|
|
e.keyCode === e.DOM_VK_ENTER) {
|
|
|
|
action = 1;
|
|
|
|
} else if (e.keyCode === e.DOM_VK_UP) {
|
|
|
|
action = 2;
|
|
|
|
}
|
2012-04-19 03:44:33 -07:00
|
|
|
|
2012-09-12 03:39:29 -07:00
|
|
|
if (action) {
|
|
|
|
let [file, line, token, isGlobal] = this.searchboxInfo;
|
2012-08-18 02:29:47 -07:00
|
|
|
|
2012-09-12 03:39:29 -07:00
|
|
|
if (token.length) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
} else {
|
2012-07-15 00:12:06 -07:00
|
|
|
return;
|
|
|
|
}
|
2012-08-18 02:29:47 -07:00
|
|
|
if (isGlobal) {
|
2012-09-11 12:50:19 -07:00
|
|
|
if (DebuggerView.GlobalSearch.hidden) {
|
|
|
|
DebuggerView.GlobalSearch.scheduleSearch();
|
|
|
|
} else {
|
2012-09-12 03:39:29 -07:00
|
|
|
DebuggerView.GlobalSearch[action === 1
|
|
|
|
? "focusNextMatch"
|
|
|
|
: "focusPrevMatch"]();
|
2012-09-11 12:50:19 -07:00
|
|
|
}
|
2012-08-18 02:29:47 -07:00
|
|
|
return;
|
|
|
|
}
|
2012-07-15 00:12:06 -07:00
|
|
|
|
2012-04-19 03:44:33 -07:00
|
|
|
let editor = DebuggerView.editor;
|
2012-09-12 03:39:29 -07:00
|
|
|
let offset = editor[action === 1 ? "findNext" : "findPrevious"](true);
|
2012-04-19 03:44:33 -07:00
|
|
|
if (offset > -1) {
|
2012-06-19 08:11:27 -07:00
|
|
|
editor.setSelection(offset, offset + token.length)
|
2012-04-19 03:44:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-08-01 13:25:46 -07:00
|
|
|
/**
|
|
|
|
* Called when the scripts filter key sequence was pressed.
|
|
|
|
*/
|
2012-08-18 02:29:47 -07:00
|
|
|
_onSearch: function DVS__onSearch(aValue = "") {
|
2012-08-01 13:25:46 -07:00
|
|
|
this._searchbox.focus();
|
2012-08-18 02:29:47 -07:00
|
|
|
this._searchbox.value = aValue;
|
|
|
|
DebuggerView.GlobalSearch.hideAndEmpty();
|
|
|
|
},
|
|
|
|
|
2012-09-11 12:50:20 -07:00
|
|
|
/**
|
|
|
|
* Called when the scripts path filter key sequence was pressed.
|
|
|
|
*/
|
|
|
|
_onFileSearch: function DVS__onFileSearch() {
|
|
|
|
this._onSearch();
|
|
|
|
this._searchboxPanel.openPopup(this._searchbox);
|
|
|
|
},
|
|
|
|
|
2012-08-18 02:29:47 -07:00
|
|
|
/**
|
|
|
|
* Called when the scripts token filter key sequence was pressed.
|
|
|
|
*/
|
|
|
|
_onLineSearch: function DVS__onLineSearch() {
|
|
|
|
this._onSearch(SEARCH_LINE_FLAG);
|
2012-09-11 12:50:20 -07:00
|
|
|
this._searchboxPanel.hidePopup();
|
2012-08-01 13:25:46 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the scripts token filter key sequence was pressed.
|
|
|
|
*/
|
|
|
|
_onTokenSearch: function DVS__onTokenSearch() {
|
2012-08-18 02:29:47 -07:00
|
|
|
this._onSearch(SEARCH_TOKEN_FLAG);
|
2012-09-11 12:50:20 -07:00
|
|
|
this._searchboxPanel.hidePopup();
|
2012-08-18 02:29:47 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the scripts token filter key sequence was pressed.
|
|
|
|
*/
|
|
|
|
_onGlobalSearch: function DVS__onGlobalSearch() {
|
|
|
|
this._onSearch(SEARCH_GLOBAL_FLAG);
|
2012-09-11 12:50:20 -07:00
|
|
|
this._searchboxPanel.hidePopup();
|
2012-08-01 13:25:46 -07:00
|
|
|
},
|
|
|
|
|
2012-04-19 03:44:33 -07:00
|
|
|
/**
|
|
|
|
* The cached scripts container and search box.
|
2012-04-08 22:15:47 -07:00
|
|
|
*/
|
|
|
|
_scripts: null,
|
2012-04-19 03:44:33 -07:00
|
|
|
_searchbox: null,
|
2012-09-11 12:50:20 -07:00
|
|
|
_searchboxPanel: null,
|
2012-04-08 22:15:47 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialization function, called when the debugger is initialized.
|
|
|
|
*/
|
|
|
|
initialize: function DVS_initialize() {
|
2012-09-12 03:39:51 -07:00
|
|
|
this._scripts = DebuggerView._scripts;
|
2012-04-19 03:44:33 -07:00
|
|
|
this._searchbox = document.getElementById("scripts-search");
|
2012-09-11 12:50:20 -07:00
|
|
|
this._searchboxPanel = document.getElementById("scripts-search-panel");
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
this._scripts.addEventListener("select", this._onScriptsChange, false);
|
2012-09-11 12:50:20 -07:00
|
|
|
this._searchbox.addEventListener("click", this._onScriptsSearchClick, false);
|
2012-09-12 03:39:29 -07:00
|
|
|
this._searchbox.addEventListener("blur", this._onScriptsSearchBlur, false);
|
2012-04-19 03:44:33 -07:00
|
|
|
this._searchbox.addEventListener("select", this._onScriptsSearch, false);
|
|
|
|
this._searchbox.addEventListener("input", this._onScriptsSearch, false);
|
2012-09-12 03:39:29 -07:00
|
|
|
this._searchbox.addEventListener("keypress", this._onScriptsKeyPress, false);
|
2012-04-17 11:16:57 -07:00
|
|
|
this.commitScripts();
|
2012-04-08 22:15:47 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destruction function, called when the debugger is shut down.
|
|
|
|
*/
|
|
|
|
destroy: function DVS_destroy() {
|
|
|
|
this._scripts.removeEventListener("select", this._onScriptsChange, false);
|
2012-09-11 12:50:20 -07:00
|
|
|
this._searchbox.removeEventListener("click", this._onScriptsSearchClick, false);
|
2012-09-12 03:39:29 -07:00
|
|
|
this._searchbox.removeEventListener("blur", this._onScriptsSearchBlur, false);
|
2012-04-19 03:44:33 -07:00
|
|
|
this._searchbox.removeEventListener("select", this._onScriptsSearch, false);
|
|
|
|
this._searchbox.removeEventListener("input", this._onScriptsSearch, false);
|
2012-09-12 03:39:29 -07:00
|
|
|
this._searchbox.removeEventListener("keypress", this._onScriptsKeyPress, false);
|
2012-07-18 07:21:57 -07:00
|
|
|
|
|
|
|
this.empty();
|
2012-04-08 22:15:47 -07:00
|
|
|
this._scripts = null;
|
2012-04-19 03:44:33 -07:00
|
|
|
this._searchbox = null;
|
2012-09-11 12:50:20 -07:00
|
|
|
this._searchboxPanel = null;
|
2012-02-07 09:22:30 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Functions handling the html stackframes UI.
|
|
|
|
*/
|
2012-04-08 22:15:47 -07:00
|
|
|
function StackFramesView() {
|
|
|
|
this._onFramesScroll = this._onFramesScroll.bind(this);
|
2012-06-03 06:39:51 -07:00
|
|
|
this._onPauseExceptionsClick = this._onPauseExceptionsClick.bind(this);
|
2012-04-08 22:15:47 -07:00
|
|
|
this._onCloseButtonClick = this._onCloseButtonClick.bind(this);
|
2012-08-01 22:31:15 -07:00
|
|
|
this._onResume = this._onResume.bind(this);
|
|
|
|
this._onStepOver = this._onStepOver.bind(this);
|
|
|
|
this._onStepIn = this._onStepIn.bind(this);
|
|
|
|
this._onStepOut = this._onStepOut.bind(this);
|
2012-04-08 22:15:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
StackFramesView.prototype = {
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-09-12 03:39:51 -07:00
|
|
|
/**
|
|
|
|
* Sets the current frames state based on the debugger active thread state.
|
|
|
|
*
|
|
|
|
* @param string aState
|
|
|
|
* Either "paused" or "attached".
|
|
|
|
*/
|
|
|
|
updateState: function DVF_updateState(aState) {
|
|
|
|
let resume = DebuggerView._resumeButton;
|
|
|
|
let resumeKey = LayoutHelpers.prettyKey(DebuggerView._resumeKey);
|
2012-05-05 02:35:00 -07:00
|
|
|
|
2012-09-12 03:39:51 -07:00
|
|
|
// If we're paused, show a pause label and a resume label on the button.
|
|
|
|
if (aState == "paused") {
|
|
|
|
resume.setAttribute("tooltiptext", L10N.getFormatStr("resumeButtonTooltip", [resumeKey]));
|
|
|
|
resume.setAttribute("checked", true);
|
|
|
|
}
|
|
|
|
// If we're attached, do the opposite.
|
|
|
|
else if (aState == "attached") {
|
|
|
|
resume.setAttribute("tooltiptext", L10N.getFormatStr("pauseButtonTooltip", [resumeKey]));
|
|
|
|
resume.removeAttribute("checked");
|
|
|
|
}
|
|
|
|
},
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all elements from the stackframes container, leaving it empty.
|
|
|
|
*/
|
|
|
|
empty: function DVF_empty() {
|
|
|
|
while (this._frames.firstChild) {
|
|
|
|
this._frames.removeChild(this._frames.firstChild);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all elements from the stackframes container, and adds a child node
|
|
|
|
* with an empty text note attached.
|
|
|
|
*/
|
|
|
|
emptyText: function DVF_emptyText() {
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure the container is empty first.
|
2012-02-07 09:22:30 -08:00
|
|
|
this.empty();
|
|
|
|
|
2012-05-20 13:49:51 -07:00
|
|
|
let item = document.createElement("label");
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// The empty node should look grayed out to avoid confusion.
|
2012-05-20 13:49:51 -07:00
|
|
|
item.className = "list-item empty";
|
|
|
|
item.setAttribute("value", L10N.getStr("emptyStackText"));
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
this._frames.appendChild(item);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a frame to the stackframes container.
|
|
|
|
* If the frame already exists (was previously added), null is returned.
|
|
|
|
* Otherwise, the newly created element is returned.
|
|
|
|
*
|
|
|
|
* @param number aDepth
|
|
|
|
* The frame depth specified by the debugger.
|
|
|
|
* @param string aFrameNameText
|
|
|
|
* The name to be displayed in the list.
|
2012-02-25 01:04:07 -08:00
|
|
|
* @param string aFrameDetailsText
|
|
|
|
* The details to be displayed in the list.
|
2012-02-07 09:22:30 -08:00
|
|
|
* @return object
|
|
|
|
* The newly created html node representing the added frame.
|
|
|
|
*/
|
2012-02-25 01:04:07 -08:00
|
|
|
addFrame: function DVF_addFrame(aDepth, aFrameNameText, aFrameDetailsText) {
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure we don't duplicate anything.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (document.getElementById("stackframe-" + aDepth)) {
|
|
|
|
return null;
|
|
|
|
}
|
2012-10-01 07:55:32 -07:00
|
|
|
// Stackframes are UI elements which benefit from visible panes.
|
|
|
|
DebuggerView.showPanesIfAllowed();
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-05-20 13:49:51 -07:00
|
|
|
let frame = document.createElement("box");
|
|
|
|
let frameName = document.createElement("label");
|
|
|
|
let frameDetails = document.createElement("label");
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Create a list item to be added to the stackframes container.
|
2012-02-07 09:22:30 -08:00
|
|
|
frame.id = "stackframe-" + aDepth;
|
|
|
|
frame.className = "dbg-stackframe list-item";
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// This list should display the name and details for the frame.
|
2012-05-20 13:49:51 -07:00
|
|
|
frameName.className = "dbg-stackframe-name plain";
|
|
|
|
frameDetails.className = "dbg-stackframe-details plain";
|
|
|
|
frameName.setAttribute("value", aFrameNameText);
|
|
|
|
frameDetails.setAttribute("value", aFrameDetailsText);
|
|
|
|
|
|
|
|
let spacer = document.createElement("spacer");
|
|
|
|
spacer.setAttribute("flex", "1");
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
frame.appendChild(frameName);
|
2012-05-20 13:49:51 -07:00
|
|
|
frame.appendChild(spacer);
|
2012-02-25 01:04:07 -08:00
|
|
|
frame.appendChild(frameDetails);
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
this._frames.appendChild(frame);
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Return the element for later use if necessary.
|
2012-02-07 09:22:30 -08:00
|
|
|
return frame;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Highlights a frame from the stackframe container as selected/deselected.
|
|
|
|
*
|
|
|
|
* @param number aDepth
|
|
|
|
* The frame depth specified by the debugger.
|
2012-04-08 22:15:47 -07:00
|
|
|
* @param boolean aFlag
|
|
|
|
* True if the frame should be deselected, false otherwise.
|
2012-02-07 09:22:30 -08:00
|
|
|
*/
|
2012-04-08 22:15:47 -07:00
|
|
|
highlightFrame: function DVF_highlightFrame(aDepth, aFlag) {
|
2012-02-07 09:22:30 -08:00
|
|
|
let frame = document.getElementById("stackframe-" + aDepth);
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// The list item wasn't found in the stackframe container.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (!frame) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Add the 'selected' css class if the frame isn't already selected.
|
|
|
|
if (!aFlag && !frame.classList.contains("selected")) {
|
2012-02-07 09:22:30 -08:00
|
|
|
frame.classList.add("selected");
|
2012-04-08 22:15:47 -07:00
|
|
|
}
|
|
|
|
// Remove the 'selected' css class if the frame is already selected.
|
|
|
|
else if (aFlag && frame.classList.contains("selected")) {
|
2012-02-07 09:22:30 -08:00
|
|
|
frame.classList.remove("selected");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Deselects a frame from the stackframe container.
|
|
|
|
*
|
|
|
|
* @param number aDepth
|
|
|
|
* The frame depth specified by the debugger.
|
|
|
|
*/
|
|
|
|
unhighlightFrame: function DVF_unhighlightFrame(aDepth) {
|
2012-04-17 11:16:57 -07:00
|
|
|
this.highlightFrame(aDepth, true);
|
2012-04-08 22:15:47 -07:00
|
|
|
},
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Gets the current dirty state.
|
|
|
|
*
|
|
|
|
* @return boolean value
|
|
|
|
* True if should load more frames.
|
|
|
|
*/
|
|
|
|
get dirty() {
|
|
|
|
return this._dirty;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets if the active thread has more frames that need to be loaded.
|
|
|
|
*
|
2012-01-23 00:29:15 -08:00
|
|
|
* @param boolean aValue
|
2012-02-07 09:22:30 -08:00
|
|
|
* True if should load more frames.
|
|
|
|
*/
|
2012-01-23 00:29:15 -08:00
|
|
|
set dirty(aValue) {
|
|
|
|
this._dirty = aValue;
|
2012-02-07 09:22:30 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-04-08 22:15:47 -07:00
|
|
|
* Listener handling the stackframes container click event.
|
2012-02-07 09:22:30 -08:00
|
|
|
*/
|
2012-04-08 22:15:47 -07:00
|
|
|
_onFramesClick: function DVF__onFramesClick(aEvent) {
|
|
|
|
let target = aEvent.target;
|
|
|
|
|
|
|
|
while (target) {
|
|
|
|
if (target.debuggerFrame) {
|
|
|
|
DebuggerController.StackFrames.selectFrame(target.debuggerFrame.depth);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
target = target.parentNode;
|
|
|
|
}
|
|
|
|
},
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the stackframes container scroll event.
|
|
|
|
*/
|
2012-01-23 00:29:15 -08:00
|
|
|
_onFramesScroll: function DVF__onFramesScroll(aEvent) {
|
2012-04-08 22:15:47 -07:00
|
|
|
// Update the stackframes container only if we have to.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (this._dirty) {
|
|
|
|
let clientHeight = this._frames.clientHeight;
|
|
|
|
let scrollTop = this._frames.scrollTop;
|
|
|
|
let scrollHeight = this._frames.scrollHeight;
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// If the stackframes container was scrolled past 95% of the height,
|
|
|
|
// load more content.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (scrollTop >= (scrollHeight - clientHeight) * 0.95) {
|
|
|
|
this._dirty = false;
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
DebuggerController.StackFrames.addMoreFrames();
|
2012-02-07 09:22:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the close button click event.
|
|
|
|
*/
|
|
|
|
_onCloseButtonClick: function DVF__onCloseButtonClick() {
|
2012-04-08 22:15:47 -07:00
|
|
|
DebuggerController.dispatchEvent("Debugger:Close");
|
2012-02-07 09:22:30 -08:00
|
|
|
},
|
|
|
|
|
2012-06-03 06:39:51 -07:00
|
|
|
/**
|
|
|
|
* Listener handling the pause-on-exceptions click event.
|
|
|
|
*/
|
|
|
|
_onPauseExceptionsClick: function DVF__onPauseExceptionsClick() {
|
|
|
|
let option = document.getElementById("pause-exceptions");
|
|
|
|
DebuggerController.StackFrames.updatePauseOnExceptions(option.checked);
|
|
|
|
},
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
2012-02-09 23:46:10 -08:00
|
|
|
* Listener handling the pause/resume button click event.
|
2012-02-07 09:22:30 -08:00
|
|
|
*/
|
2012-08-01 22:31:15 -07:00
|
|
|
_onResume: function DVF__onResume(e) {
|
2012-04-08 22:15:47 -07:00
|
|
|
if (DebuggerController.activeThread.paused) {
|
|
|
|
DebuggerController.activeThread.resume();
|
2012-02-09 23:46:10 -08:00
|
|
|
} else {
|
2012-04-08 22:15:47 -07:00
|
|
|
DebuggerController.activeThread.interrupt();
|
2012-02-09 23:46:10 -08:00
|
|
|
}
|
2012-02-07 09:22:30 -08:00
|
|
|
},
|
|
|
|
|
2012-03-17 23:50:43 -07:00
|
|
|
/**
|
|
|
|
* Listener handling the step over button click event.
|
|
|
|
*/
|
2012-08-01 22:31:15 -07:00
|
|
|
_onStepOver: function DVF__onStepOver(e) {
|
|
|
|
if (DebuggerController.activeThread.paused) {
|
|
|
|
DebuggerController.activeThread.stepOver();
|
|
|
|
}
|
2012-03-17 23:50:43 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the step in button click event.
|
|
|
|
*/
|
2012-08-01 22:31:15 -07:00
|
|
|
_onStepIn: function DVF__onStepIn(e) {
|
|
|
|
if (DebuggerController.activeThread.paused) {
|
|
|
|
DebuggerController.activeThread.stepIn();
|
|
|
|
}
|
2012-03-17 23:50:43 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the step out button click event.
|
|
|
|
*/
|
2012-08-01 22:31:15 -07:00
|
|
|
_onStepOut: function DVF__onStepOut(e) {
|
|
|
|
if (DebuggerController.activeThread.paused) {
|
|
|
|
DebuggerController.activeThread.stepOut();
|
|
|
|
}
|
2012-03-17 23:50:43 -07:00
|
|
|
},
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Specifies if the active thread has more frames which need to be loaded.
|
|
|
|
*/
|
|
|
|
_dirty: false,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The cached stackframes container.
|
|
|
|
*/
|
|
|
|
_frames: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialization function, called when the debugger is initialized.
|
|
|
|
*/
|
|
|
|
initialize: function DVF_initialize() {
|
|
|
|
let close = document.getElementById("close");
|
2012-06-03 06:39:51 -07:00
|
|
|
let pauseOnExceptions = document.getElementById("pause-exceptions");
|
2012-09-12 03:39:51 -07:00
|
|
|
let resume = DebuggerView._resumeButton;
|
|
|
|
let stepOver = DebuggerView._stepOverButton;
|
|
|
|
let stepIn = DebuggerView._stepInButton;
|
|
|
|
let stepOut = DebuggerView._stepOutButton;
|
|
|
|
let frames = DebuggerView._stackframes;
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
close.addEventListener("click", this._onCloseButtonClick, false);
|
2012-06-03 06:39:51 -07:00
|
|
|
pauseOnExceptions.checked = DebuggerController.StackFrames.pauseOnExceptions;
|
2012-08-01 22:31:15 -07:00
|
|
|
pauseOnExceptions.addEventListener("click", this._onPauseExceptionsClick, false);
|
|
|
|
resume.addEventListener("click", this._onResume, false);
|
|
|
|
stepOver.addEventListener("click", this._onStepOver, false);
|
|
|
|
stepIn.addEventListener("click", this._onStepIn, false);
|
|
|
|
stepOut.addEventListener("click", this._onStepOut, false);
|
2012-04-08 22:15:47 -07:00
|
|
|
frames.addEventListener("click", this._onFramesClick, false);
|
2012-02-07 09:22:30 -08:00
|
|
|
frames.addEventListener("scroll", this._onFramesScroll, false);
|
|
|
|
window.addEventListener("resize", this._onFramesScroll, false);
|
|
|
|
|
|
|
|
this._frames = frames;
|
2012-07-14 23:40:37 -07:00
|
|
|
this.emptyText();
|
2012-02-07 09:22:30 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destruction function, called when the debugger is shut down.
|
|
|
|
*/
|
|
|
|
destroy: function DVF_destroy() {
|
|
|
|
let close = document.getElementById("close");
|
2012-06-03 06:39:51 -07:00
|
|
|
let pauseOnExceptions = document.getElementById("pause-exceptions");
|
2012-09-12 03:39:51 -07:00
|
|
|
let resume = DebuggerView._resumeButton;
|
|
|
|
let stepOver = DebuggerView._stepOverButton;
|
|
|
|
let stepIn = DebuggerView._stepInButton;
|
|
|
|
let stepOut = DebuggerView._stepOutButton;
|
|
|
|
let frames = DebuggerView._stackframes;
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
close.removeEventListener("click", this._onCloseButtonClick, false);
|
2012-08-01 22:31:15 -07:00
|
|
|
pauseOnExceptions.removeEventListener("click", this._onPauseExceptionsClick, false);
|
|
|
|
resume.removeEventListener("click", this._onResume, false);
|
|
|
|
stepOver.removeEventListener("click", this._onStepOver, false);
|
|
|
|
stepIn.removeEventListener("click", this._onStepIn, false);
|
|
|
|
stepOut.removeEventListener("click", this._onStepOut, false);
|
2012-02-07 09:22:30 -08:00
|
|
|
frames.removeEventListener("click", this._onFramesClick, false);
|
|
|
|
frames.removeEventListener("scroll", this._onFramesScroll, false);
|
|
|
|
window.removeEventListener("resize", this._onFramesScroll, false);
|
|
|
|
|
2012-07-18 07:21:57 -07:00
|
|
|
this.empty();
|
2012-02-07 09:22:30 -08:00
|
|
|
this._frames = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-07-14 23:40:37 -07:00
|
|
|
/**
|
|
|
|
* Functions handling the breakpoints view.
|
|
|
|
*/
|
|
|
|
function BreakpointsView() {
|
|
|
|
this._onBreakpointClick = this._onBreakpointClick.bind(this);
|
|
|
|
this._onBreakpointCheckboxChange = this._onBreakpointCheckboxChange.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
BreakpointsView.prototype = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all elements from the breakpoints container, leaving it empty.
|
|
|
|
*/
|
|
|
|
empty: function DVB_empty() {
|
|
|
|
let firstChild;
|
2012-07-18 07:21:57 -07:00
|
|
|
|
2012-07-14 23:40:37 -07:00
|
|
|
while (firstChild = this._breakpoints.firstChild) {
|
|
|
|
this._destroyContextMenu(firstChild);
|
|
|
|
this._breakpoints.removeChild(firstChild);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all elements from the breakpoints container, and adds a child node
|
|
|
|
* with an empty text note attached.
|
|
|
|
*/
|
|
|
|
emptyText: function DVB_emptyText() {
|
|
|
|
// Make sure the container is empty first.
|
|
|
|
this.empty();
|
|
|
|
|
|
|
|
let item = document.createElement("label");
|
|
|
|
|
|
|
|
// The empty node should look grayed out to avoid confusion.
|
|
|
|
item.className = "list-item empty";
|
|
|
|
item.setAttribute("value", L10N.getStr("emptyBreakpointsText"));
|
|
|
|
|
|
|
|
this._breakpoints.appendChild(item);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether the breakpoint with the specified script URL and line is
|
|
|
|
* among the breakpoints known to the debugger and shown in the list, and
|
|
|
|
* returns the matched result or null if nothing is found.
|
|
|
|
*
|
|
|
|
* @param string aUrl
|
|
|
|
* The original breakpoint script url.
|
|
|
|
* @param number aLine
|
|
|
|
* The original breakpoint script line.
|
|
|
|
* @return object | null
|
|
|
|
* The queried breakpoint
|
|
|
|
*/
|
|
|
|
getBreakpoint: function DVB_getBreakpoint(aUrl, aLine) {
|
|
|
|
return this._breakpoints.getElementsByAttribute("location", aUrl + ":" + aLine)[0];
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a breakpoint only from the breakpoints container.
|
|
|
|
* This doesn't remove the breakpoint from the DebuggerController!
|
|
|
|
*
|
|
|
|
* @param string aId
|
|
|
|
* A breakpoint identifier specified by the debugger.
|
|
|
|
*/
|
|
|
|
removeBreakpoint: function DVB_removeBreakpoint(aId) {
|
|
|
|
let breakpoint = document.getElementById("breakpoint-" + aId);
|
|
|
|
|
|
|
|
// Make sure we have something to remove.
|
|
|
|
if (!breakpoint) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._destroyContextMenu(breakpoint);
|
|
|
|
this._breakpoints.removeChild(breakpoint);
|
|
|
|
|
|
|
|
if (!this.count) {
|
|
|
|
this.emptyText();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a breakpoint to the breakpoints container.
|
|
|
|
* If the breakpoint already exists (was previously added), null is returned.
|
|
|
|
* If it's already added but disabled, it will be enabled and null is returned.
|
|
|
|
* Otherwise, the newly created element is returned.
|
|
|
|
*
|
|
|
|
* @param string aId
|
|
|
|
* A breakpoint identifier specified by the debugger.
|
|
|
|
* @param string aLineInfo
|
|
|
|
* The script line information to be displayed in the list.
|
|
|
|
* @param string aLineText
|
|
|
|
* The script line text to be displayed in the list.
|
|
|
|
* @param string aUrl
|
|
|
|
* The original breakpoint script url.
|
|
|
|
* @param number aLine
|
|
|
|
* The original breakpoint script line.
|
|
|
|
* @return object
|
|
|
|
* The newly created html node representing the added breakpoint.
|
|
|
|
*/
|
|
|
|
addBreakpoint: function DVB_addBreakpoint(aId, aLineInfo, aLineText, aUrl, aLine) {
|
|
|
|
// Make sure we don't duplicate anything.
|
|
|
|
if (document.getElementById("breakpoint-" + aId)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
// Remove the empty list text if it was there.
|
|
|
|
if (!this.count) {
|
|
|
|
this.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the breakpoint was already added but disabled, enable it now.
|
|
|
|
let breakpoint = this.getBreakpoint(aUrl, aLine);
|
|
|
|
if (breakpoint) {
|
|
|
|
breakpoint.id = "breakpoint-" + aId;
|
|
|
|
breakpoint.breakpointActor = aId;
|
|
|
|
breakpoint.getElementsByTagName("checkbox")[0].setAttribute("checked", "true");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
breakpoint = document.createElement("box");
|
|
|
|
let bkpCheckbox = document.createElement("checkbox");
|
|
|
|
let bkpLineInfo = document.createElement("label");
|
|
|
|
let bkpLineText = document.createElement("label");
|
|
|
|
|
|
|
|
// Create a list item to be added to the stackframes container.
|
|
|
|
breakpoint.id = "breakpoint-" + aId;
|
|
|
|
breakpoint.className = "dbg-breakpoint list-item";
|
|
|
|
breakpoint.setAttribute("location", aUrl + ":" + aLine);
|
|
|
|
breakpoint.breakpointUrl = aUrl;
|
|
|
|
breakpoint.breakpointLine = aLine;
|
|
|
|
breakpoint.breakpointActor = aId;
|
|
|
|
|
|
|
|
aLineInfo = aLineInfo.trim();
|
|
|
|
aLineText = aLineText.trim();
|
|
|
|
|
|
|
|
// A checkbox specifies if the breakpoint is enabled or not.
|
|
|
|
bkpCheckbox.setAttribute("checked", "true");
|
|
|
|
bkpCheckbox.addEventListener("click", this._onBreakpointCheckboxChange, false);
|
|
|
|
|
|
|
|
// This list should display the line info and text for the breakpoint.
|
|
|
|
bkpLineInfo.className = "dbg-breakpoint-info plain";
|
|
|
|
bkpLineText.className = "dbg-breakpoint-text plain";
|
|
|
|
bkpLineInfo.setAttribute("value", aLineInfo);
|
|
|
|
bkpLineText.setAttribute("value", aLineText);
|
|
|
|
bkpLineInfo.setAttribute("crop", "end");
|
|
|
|
bkpLineText.setAttribute("crop", "end");
|
|
|
|
bkpLineText.setAttribute("tooltiptext", aLineText.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_SIZE));
|
|
|
|
|
|
|
|
// Create a context menu for the breakpoint.
|
|
|
|
let menupopupId = this._createContextMenu(breakpoint);
|
|
|
|
breakpoint.setAttribute("contextmenu", menupopupId);
|
|
|
|
|
|
|
|
let state = document.createElement("vbox");
|
|
|
|
state.className = "state";
|
|
|
|
state.appendChild(bkpCheckbox);
|
|
|
|
|
|
|
|
let content = document.createElement("vbox");
|
|
|
|
content.className = "content";
|
|
|
|
content.setAttribute("flex", "1");
|
|
|
|
content.appendChild(bkpLineInfo);
|
|
|
|
content.appendChild(bkpLineText);
|
|
|
|
|
|
|
|
breakpoint.appendChild(state);
|
|
|
|
breakpoint.appendChild(content);
|
|
|
|
|
|
|
|
this._breakpoints.appendChild(breakpoint);
|
|
|
|
|
|
|
|
// Return the element for later use if necessary.
|
|
|
|
return breakpoint;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables a breakpoint.
|
|
|
|
*
|
|
|
|
* @param object aBreakpoint
|
|
|
|
* An element representing a breakpoint.
|
|
|
|
* @param function aCallback
|
|
|
|
* Optional function to invoke once the breakpoint is enabled.
|
|
|
|
* @param boolean aNoCheckboxUpdate
|
|
|
|
* Pass true to not update the checkbox checked state.
|
|
|
|
* This is usually necessary when the checked state will be updated
|
|
|
|
* automatically (e.g: on a checkbox click).
|
|
|
|
*/
|
|
|
|
enableBreakpoint:
|
|
|
|
function DVB_enableBreakpoint(aTarget, aCallback, aNoCheckboxUpdate) {
|
|
|
|
let { breakpointUrl: url, breakpointLine: line } = aTarget;
|
2012-10-08 10:08:12 -07:00
|
|
|
let breakpoint = DebuggerController.Breakpoints.getBreakpoint(url, line);
|
2012-07-14 23:40:37 -07:00
|
|
|
|
|
|
|
if (!breakpoint) {
|
|
|
|
if (!aNoCheckboxUpdate) {
|
|
|
|
aTarget.getElementsByTagName("checkbox")[0].setAttribute("checked", "true");
|
|
|
|
}
|
|
|
|
DebuggerController.Breakpoints.
|
|
|
|
addBreakpoint({ url: url, line: line }, aCallback);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disables a breakpoint.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* An element representing a breakpoint.
|
|
|
|
* @param function aCallback
|
|
|
|
* Optional function to invoke once the breakpoint is disabled.
|
|
|
|
* @param boolean aNoCheckboxUpdate
|
|
|
|
* Pass true to not update the checkbox checked state.
|
|
|
|
* This is usually necessary when the checked state will be updated
|
|
|
|
* automatically (e.g: on a checkbox click).
|
|
|
|
*/
|
|
|
|
disableBreakpoint:
|
|
|
|
function DVB_disableBreakpoint(aTarget, aCallback, aNoCheckboxUpdate) {
|
|
|
|
let { breakpointUrl: url, breakpointLine: line } = aTarget;
|
|
|
|
let breakpoint = DebuggerController.Breakpoints.getBreakpoint(url, line)
|
|
|
|
|
|
|
|
if (breakpoint) {
|
|
|
|
if (!aNoCheckboxUpdate) {
|
|
|
|
aTarget.getElementsByTagName("checkbox")[0].removeAttribute("checked");
|
|
|
|
}
|
|
|
|
DebuggerController.Breakpoints.
|
|
|
|
removeBreakpoint(breakpoint, aCallback, false, true);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the current number of added breakpoints.
|
|
|
|
*/
|
|
|
|
get count() {
|
|
|
|
return this._breakpoints.getElementsByClassName("dbg-breakpoint").length;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterates through all the added breakpoints.
|
|
|
|
*
|
|
|
|
* @param function aCallback
|
|
|
|
* Function called for each element.
|
|
|
|
*/
|
|
|
|
_iterate: function DVB_iterate(aCallback) {
|
|
|
|
Array.forEach(Array.slice(this._breakpoints.childNodes), aCallback);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the real breakpoint target when an event is handled.
|
|
|
|
* @return object
|
|
|
|
*/
|
|
|
|
_getBreakpointTarget: function DVB__getBreakpointTarget(aEvent) {
|
|
|
|
let target = aEvent.target;
|
|
|
|
|
|
|
|
while (target) {
|
|
|
|
if (target.breakpointActor) {
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
target = target.parentNode;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the breakpoint click event.
|
|
|
|
*/
|
|
|
|
_onBreakpointClick: function DVB__onBreakpointClick(aEvent) {
|
|
|
|
let target = this._getBreakpointTarget(aEvent);
|
|
|
|
let { breakpointUrl: url, breakpointLine: line } = target;
|
|
|
|
|
|
|
|
DebuggerController.StackFrames.updateEditorToLocation(url, line, 0, 0, 1);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the breakpoint checkbox change event.
|
|
|
|
*/
|
|
|
|
_onBreakpointCheckboxChange: function DVB__onBreakpointCheckboxChange(aEvent) {
|
|
|
|
aEvent.stopPropagation();
|
|
|
|
|
|
|
|
let target = this._getBreakpointTarget(aEvent);
|
|
|
|
let { breakpointUrl: url, breakpointLine: line } = target;
|
|
|
|
|
|
|
|
if (aEvent.target.getAttribute("checked") === "true") {
|
|
|
|
this.disableBreakpoint(target, null, true);
|
|
|
|
} else {
|
|
|
|
this.enableBreakpoint(target, null, true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the "enableSelf" menuitem command.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The corresponding breakpoint element.
|
|
|
|
*/
|
|
|
|
_onEnableSelf: function DVB__onEnableSelf(aTarget) {
|
|
|
|
if (!aTarget) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this.enableBreakpoint(aTarget)) {
|
|
|
|
aTarget.enableSelf.menuitem.setAttribute("hidden", "true");
|
|
|
|
aTarget.disableSelf.menuitem.removeAttribute("hidden");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the "disableSelf" menuitem command.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The corresponding breakpoint element.
|
|
|
|
*/
|
|
|
|
_onDisableSelf: function DVB__onDisableSelf(aTarget) {
|
|
|
|
if (!aTarget) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this.disableBreakpoint(aTarget)) {
|
|
|
|
aTarget.enableSelf.menuitem.removeAttribute("hidden");
|
|
|
|
aTarget.disableSelf.menuitem.setAttribute("hidden", "true");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the "deleteSelf" menuitem command.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The corresponding breakpoint element.
|
|
|
|
*/
|
|
|
|
_onDeleteSelf: function DVB__onDeleteSelf(aTarget) {
|
|
|
|
let { breakpointUrl: url, breakpointLine: line } = aTarget;
|
|
|
|
let breakpoint = DebuggerController.Breakpoints.getBreakpoint(url, line)
|
|
|
|
|
|
|
|
if (aTarget) {
|
|
|
|
this.removeBreakpoint(aTarget.breakpointActor);
|
|
|
|
}
|
|
|
|
if (breakpoint) {
|
|
|
|
DebuggerController.Breakpoints.removeBreakpoint(breakpoint);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the "enableOthers" menuitem command.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The corresponding breakpoint element.
|
|
|
|
*/
|
|
|
|
_onEnableOthers: function DVB__onEnableOthers(aTarget) {
|
|
|
|
this._iterate(function(element) {
|
|
|
|
if (element !== aTarget) {
|
|
|
|
this._onEnableSelf(element);
|
|
|
|
}
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the "disableOthers" menuitem command.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The corresponding breakpoint element.
|
|
|
|
*/
|
|
|
|
_onDisableOthers: function DVB__onDisableOthers(aTarget) {
|
|
|
|
this._iterate(function(element) {
|
|
|
|
if (element !== aTarget) {
|
|
|
|
this._onDisableSelf(element);
|
|
|
|
}
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the "deleteOthers" menuitem command.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The corresponding breakpoint element.
|
|
|
|
*/
|
|
|
|
_onDeleteOthers: function DVB__onDeleteOthers(aTarget) {
|
|
|
|
this._iterate(function(element) {
|
|
|
|
if (element !== aTarget) {
|
|
|
|
this._onDeleteSelf(element);
|
|
|
|
}
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the "disableAll" menuitem command.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The corresponding breakpoint element.
|
|
|
|
*/
|
|
|
|
_onEnableAll: function DVB__onEnableAll(aTarget) {
|
|
|
|
this._onEnableOthers(aTarget);
|
|
|
|
this._onEnableSelf(aTarget);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the "disableAll" menuitem command.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The corresponding breakpoint element.
|
|
|
|
*/
|
|
|
|
_onDisableAll: function DVB__onDisableAll(aTarget) {
|
|
|
|
this._onDisableOthers(aTarget);
|
|
|
|
this._onDisableSelf(aTarget);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the "deleteAll" menuitem command.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The corresponding breakpoint element.
|
|
|
|
*/
|
|
|
|
_onDeleteAll: function DVB__onDeleteAll(aTarget) {
|
|
|
|
this._onDeleteOthers(aTarget);
|
|
|
|
this._onDeleteSelf(aTarget);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The cached breakpoints container.
|
|
|
|
*/
|
|
|
|
_breakpoints: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a breakpoint context menu.
|
|
|
|
*
|
|
|
|
* @param object aBreakpoint
|
|
|
|
* An element representing a breakpoint.
|
|
|
|
* @return string
|
|
|
|
* The popup id.
|
|
|
|
*/
|
|
|
|
_createContextMenu: function DVB_createContextMenu(aBreakpoint) {
|
|
|
|
let commandsetId = "breakpointMenuCommands-" + aBreakpoint.id;
|
|
|
|
let menupopupId = "breakpointContextMenu-" + aBreakpoint.id;
|
|
|
|
|
2012-07-18 07:21:57 -07:00
|
|
|
let commandset = document.createElement("commandset");
|
|
|
|
commandset.setAttribute("id", commandsetId);
|
2012-07-14 23:40:37 -07:00
|
|
|
|
|
|
|
let menupopup = document.createElement("menupopup");
|
|
|
|
menupopup.setAttribute("id", menupopupId);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a menu item specified by a name with the appropriate attributes
|
|
|
|
* (label and command handler).
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* A global identifier for the menu item.
|
|
|
|
* @param boolean aHiddenFlag
|
|
|
|
* True if this menuitem should be hidden.
|
|
|
|
*/
|
|
|
|
function createMenuItem(aName, aHiddenFlag) {
|
|
|
|
let menuitem = document.createElement("menuitem");
|
|
|
|
let command = document.createElement("command");
|
|
|
|
|
|
|
|
let func = this["_on" + aName.charAt(0).toUpperCase() + aName.slice(1)];
|
|
|
|
let label = L10N.getStr("breakpointMenuItem." + aName);
|
|
|
|
|
|
|
|
let prefix = "bp-cMenu-";
|
|
|
|
let commandId = prefix + aName + "-" + aBreakpoint.id + "-command";
|
|
|
|
let menuitemId = prefix + aName + "-" + aBreakpoint.id + "-menuitem";
|
|
|
|
|
|
|
|
command.setAttribute("id", commandId);
|
|
|
|
command.setAttribute("label", label);
|
|
|
|
command.addEventListener("command", func.bind(this, aBreakpoint), true);
|
|
|
|
|
|
|
|
menuitem.setAttribute("id", menuitemId);
|
|
|
|
menuitem.setAttribute("command", commandId);
|
|
|
|
menuitem.setAttribute("hidden", aHiddenFlag);
|
|
|
|
|
2012-07-18 07:21:57 -07:00
|
|
|
commandset.appendChild(command);
|
2012-07-14 23:40:37 -07:00
|
|
|
menupopup.appendChild(menuitem);
|
|
|
|
|
|
|
|
aBreakpoint[aName] = {
|
|
|
|
menuitem: menuitem,
|
|
|
|
command: command
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a simple menu separator element and appends it to the current
|
|
|
|
* menupopup hierarchy.
|
|
|
|
*/
|
|
|
|
function createMenuSeparator() {
|
|
|
|
let menuseparator = document.createElement("menuseparator");
|
|
|
|
menupopup.appendChild(menuseparator);
|
|
|
|
}
|
|
|
|
|
|
|
|
createMenuItem.call(this, "enableSelf", true);
|
|
|
|
createMenuItem.call(this, "disableSelf");
|
|
|
|
createMenuItem.call(this, "deleteSelf");
|
|
|
|
createMenuSeparator();
|
|
|
|
createMenuItem.call(this, "enableOthers");
|
|
|
|
createMenuItem.call(this, "disableOthers");
|
|
|
|
createMenuItem.call(this, "deleteOthers");
|
|
|
|
createMenuSeparator();
|
|
|
|
createMenuItem.call(this, "enableAll");
|
|
|
|
createMenuItem.call(this, "disableAll");
|
|
|
|
createMenuSeparator();
|
|
|
|
createMenuItem.call(this, "deleteAll");
|
|
|
|
|
|
|
|
let popupset = document.getElementById("debugger-popups");
|
|
|
|
popupset.appendChild(menupopup);
|
2012-07-18 07:21:57 -07:00
|
|
|
document.documentElement.appendChild(commandset);
|
|
|
|
|
|
|
|
aBreakpoint.commandsetId = commandsetId;
|
|
|
|
aBreakpoint.menupopupId = menupopupId;
|
2012-07-14 23:40:37 -07:00
|
|
|
|
|
|
|
return menupopupId;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroys a breakpoint context menu.
|
|
|
|
*
|
|
|
|
* @param object aBreakpoint
|
|
|
|
* An element representing a breakpoint.
|
|
|
|
*/
|
|
|
|
_destroyContextMenu: function DVB__destroyContextMenu(aBreakpoint) {
|
2012-07-18 07:21:57 -07:00
|
|
|
if (!aBreakpoint.commandsetId || !aBreakpoint.menupopupId) {
|
|
|
|
return;
|
|
|
|
}
|
2012-07-14 23:40:37 -07:00
|
|
|
|
2012-07-18 07:21:57 -07:00
|
|
|
let commandset = document.getElementById(aBreakpoint.commandsetId);
|
|
|
|
let menupopup = document.getElementById(aBreakpoint.menupopupId);
|
2012-07-14 23:40:37 -07:00
|
|
|
|
2012-07-18 07:21:57 -07:00
|
|
|
commandset.parentNode.removeChild(commandset);
|
|
|
|
menupopup.parentNode.removeChild(menupopup);
|
2012-07-14 23:40:37 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialization function, called when the debugger is initialized.
|
|
|
|
*/
|
|
|
|
initialize: function DVB_initialize() {
|
2012-09-12 03:39:51 -07:00
|
|
|
let breakpoints = DebuggerView._breakpoints;
|
2012-07-14 23:40:37 -07:00
|
|
|
breakpoints.addEventListener("click", this._onBreakpointClick, false);
|
|
|
|
|
|
|
|
this._breakpoints = breakpoints;
|
|
|
|
this.emptyText();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destruction function, called when the debugger is shut down.
|
|
|
|
*/
|
|
|
|
destroy: function DVB_destroy() {
|
|
|
|
let breakpoints = this._breakpoints;
|
|
|
|
breakpoints.removeEventListener("click", this._onBreakpointClick, false);
|
|
|
|
|
2012-07-18 07:21:57 -07:00
|
|
|
this.empty();
|
2012-07-14 23:40:37 -07:00
|
|
|
this._breakpoints = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Functions handling the properties view.
|
|
|
|
*/
|
2012-04-08 22:15:47 -07:00
|
|
|
function PropertiesView() {
|
2012-05-29 02:08:20 -07:00
|
|
|
this.addScope = this._addScope.bind(this);
|
2012-04-08 22:15:47 -07:00
|
|
|
this._addVar = this._addVar.bind(this);
|
|
|
|
this._addProperties = this._addProperties.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
PropertiesView.prototype = {
|
2012-07-14 23:40:37 -07:00
|
|
|
|
2012-05-29 02:08:20 -07:00
|
|
|
/**
|
|
|
|
* A monotonically-increasing counter, that guarantees the uniqueness of scope
|
|
|
|
* IDs.
|
|
|
|
*/
|
|
|
|
_idCount: 1,
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a scope to contain any inspected variables.
|
|
|
|
* If the optional id is not specified, the scope html node will have a
|
|
|
|
* default id set as aName-scope.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The scope name (e.g. "Local", "Global" or "With block").
|
|
|
|
* @param string aId
|
|
|
|
* Optional, an id for the scope html node.
|
|
|
|
* @return object
|
|
|
|
* The newly created html node representing the added scope or null
|
|
|
|
* if a node was not created.
|
|
|
|
*/
|
|
|
|
_addScope: function DVP__addScope(aName, aId) {
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure the parent container exists.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (!this._vars) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-05-29 02:08:20 -07:00
|
|
|
// Generate a unique id for the element, if not specified.
|
2012-05-25 11:40:40 -07:00
|
|
|
aId = aId || aName.toLowerCase().trim().replace(/\s+/g, "-") + this._idCount++;
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Contains generic nodes and functionality.
|
2012-02-07 09:22:30 -08:00
|
|
|
let element = this._createPropertyElement(aName, aId, "scope", this._vars);
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure the element was created successfully.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (!element) {
|
|
|
|
return null;
|
|
|
|
}
|
2012-05-25 11:40:40 -07:00
|
|
|
element._identifier = aName;
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @see DebuggerView.Properties._addVar
|
|
|
|
*/
|
|
|
|
element.addVar = this._addVar.bind(this, element);
|
|
|
|
|
2012-05-25 11:40:40 -07:00
|
|
|
/**
|
|
|
|
* @see DebuggerView.Properties.addScopeToHierarchy
|
|
|
|
*/
|
|
|
|
element.addToHierarchy = this.addScopeToHierarchy.bind(this, element);
|
|
|
|
|
2012-05-29 06:12:29 -07:00
|
|
|
// Setup the additional elements specific for a scope node.
|
|
|
|
element.refresh(function() {
|
|
|
|
let title = element.getElementsByClassName("title")[0];
|
|
|
|
title.classList.add("devtools-toolbar");
|
|
|
|
}.bind(this));
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Return the element for later use if necessary.
|
2012-02-07 09:22:30 -08:00
|
|
|
return element;
|
|
|
|
},
|
|
|
|
|
2012-05-29 02:08:20 -07:00
|
|
|
/**
|
|
|
|
* Removes all added scopes in the property container tree.
|
|
|
|
*/
|
|
|
|
empty: function DVP_empty() {
|
|
|
|
while (this._vars.firstChild) {
|
|
|
|
this._vars.removeChild(this._vars.firstChild);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all elements from the variables container, and adds a child node
|
|
|
|
* with an empty text note attached.
|
|
|
|
*/
|
|
|
|
emptyText: function DVP_emptyText() {
|
|
|
|
// Make sure the container is empty first.
|
|
|
|
this.empty();
|
|
|
|
|
|
|
|
let item = document.createElement("label");
|
|
|
|
|
|
|
|
// The empty node should look grayed out to avoid confusion.
|
|
|
|
item.className = "list-item empty";
|
|
|
|
item.setAttribute("value", L10N.getStr("emptyVariablesText"));
|
|
|
|
|
|
|
|
this._vars.appendChild(item);
|
|
|
|
},
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Adds a variable to a specified scope.
|
|
|
|
* If the optional id is not specified, the variable html node will have a
|
|
|
|
* default id set as aScope.id->aName-variable.
|
|
|
|
*
|
|
|
|
* @param object aScope
|
|
|
|
* The parent scope element.
|
|
|
|
* @param string aName
|
|
|
|
* The variable name.
|
2012-05-30 06:13:05 -07:00
|
|
|
* @param object aFlags
|
|
|
|
* Optional, contains configurable, enumerable or writable flags.
|
2012-02-07 09:22:30 -08:00
|
|
|
* @param string aId
|
|
|
|
* Optional, an id for the variable html node.
|
|
|
|
* @return object
|
|
|
|
* The newly created html node representing the added var.
|
|
|
|
*/
|
2012-05-30 06:13:05 -07:00
|
|
|
_addVar: function DVP__addVar(aScope, aName, aFlags, aId) {
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure the scope container exists.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (!aScope) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Compute the id of the element if not specified.
|
2012-02-07 09:22:30 -08:00
|
|
|
aId = aId || (aScope.id + "->" + aName + "-variable");
|
|
|
|
|
2012-10-05 11:20:47 -07:00
|
|
|
let parent;
|
|
|
|
if (aFlags && !aFlags.enumerable) {
|
|
|
|
parent = aScope.childNodes[2];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
parent = aScope.childNodes[1];
|
|
|
|
}
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Contains generic nodes and functionality.
|
2012-10-05 11:20:47 -07:00
|
|
|
let element = this._createPropertyElement(aName, aId, "variable", parent);
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure the element was created successfully.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (!element) {
|
|
|
|
return null;
|
|
|
|
}
|
2012-05-25 11:40:40 -07:00
|
|
|
element._identifier = aName;
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @see DebuggerView.Properties._setGrip
|
|
|
|
*/
|
|
|
|
element.setGrip = this._setGrip.bind(this, element);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @see DebuggerView.Properties._addProperties
|
|
|
|
*/
|
|
|
|
element.addProperties = this._addProperties.bind(this, element);
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Setup the additional elements specific for a variable node.
|
2012-02-07 09:22:30 -08:00
|
|
|
element.refresh(function() {
|
2012-05-20 13:49:51 -07:00
|
|
|
let separatorLabel = document.createElement("label");
|
|
|
|
let valueLabel = document.createElement("label");
|
2012-03-15 01:17:03 -07:00
|
|
|
let title = element.getElementsByClassName("title")[0];
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-06-21 08:25:34 -07:00
|
|
|
// Use attribute flags to specify the element type and tooltip text.
|
|
|
|
this._setAttributes(element, aName, aFlags);
|
|
|
|
|
2012-05-10 10:59:55 -07:00
|
|
|
// Separator between the variable name and its value.
|
2012-05-20 13:49:51 -07:00
|
|
|
separatorLabel.className = "plain";
|
|
|
|
separatorLabel.setAttribute("value", ":");
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// The variable information (type, class and/or value).
|
2012-05-20 13:49:51 -07:00
|
|
|
valueLabel.className = "value plain";
|
2012-03-15 01:17:03 -07:00
|
|
|
|
|
|
|
// Handle the click event when pressing the element value label.
|
2012-05-20 13:49:51 -07:00
|
|
|
valueLabel.addEventListener("click", this._activateElementInputMode.bind({
|
2012-03-15 01:17:03 -07:00
|
|
|
scope: this,
|
|
|
|
element: element,
|
2012-05-20 13:49:51 -07:00
|
|
|
valueLabel: valueLabel
|
2012-03-15 01:17:03 -07:00
|
|
|
}));
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-05-24 04:23:53 -07:00
|
|
|
// Maintain the symbolic name of the variable.
|
|
|
|
Object.defineProperty(element, "token", {
|
|
|
|
value: aName,
|
|
|
|
writable: false,
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true
|
|
|
|
});
|
|
|
|
|
2012-05-20 13:49:51 -07:00
|
|
|
title.appendChild(separatorLabel);
|
|
|
|
title.appendChild(valueLabel);
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-05-09 09:05:53 -07:00
|
|
|
// Remember a simple hierarchy between the parent and the element.
|
|
|
|
this._saveHierarchy({
|
|
|
|
parent: aScope,
|
|
|
|
element: element,
|
2012-05-20 13:49:51 -07:00
|
|
|
valueLabel: valueLabel
|
2012-05-09 09:05:53 -07:00
|
|
|
});
|
2012-02-07 09:22:30 -08:00
|
|
|
}.bind(this));
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Return the element for later use if necessary.
|
2012-02-07 09:22:30 -08:00
|
|
|
return element;
|
|
|
|
},
|
|
|
|
|
2012-06-21 08:25:34 -07:00
|
|
|
/**
|
|
|
|
* Sets a variable's configurable, enumerable or writable attributes.
|
|
|
|
*
|
|
|
|
* @param object aVar
|
|
|
|
* The object to set the attributes on.
|
|
|
|
* @param object aName
|
|
|
|
* The varialbe name.
|
|
|
|
* @param object aFlags
|
|
|
|
* Contains configurable, enumerable or writable flags.
|
|
|
|
*/
|
|
|
|
_setAttributes: function DVP_setAttributes(aVar, aName, aFlags) {
|
|
|
|
if (aFlags) {
|
|
|
|
if (!aFlags.configurable) {
|
|
|
|
aVar.setAttribute("non-configurable", "");
|
|
|
|
}
|
|
|
|
if (!aFlags.enumerable) {
|
|
|
|
aVar.setAttribute("non-enumerable", "");
|
|
|
|
}
|
|
|
|
if (!aFlags.writable) {
|
|
|
|
aVar.setAttribute("non-writable", "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (aName === "this") {
|
|
|
|
aVar.setAttribute("self", "");
|
|
|
|
}
|
|
|
|
if (aName === "__proto__ ") {
|
|
|
|
aVar.setAttribute("proto", "");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Sets the specific grip for a variable.
|
|
|
|
* The grip should contain the value or the type & class, as defined in the
|
|
|
|
* remote debugger protocol. For convenience, undefined and null are
|
|
|
|
* both considered types.
|
|
|
|
*
|
|
|
|
* @param object aVar
|
|
|
|
* The parent variable element.
|
|
|
|
* @param object aGrip
|
|
|
|
* The primitive or object defining the grip, specifying
|
|
|
|
* the value and/or type & class of the variable (if the type
|
|
|
|
* is not specified, it will be inferred from the value).
|
|
|
|
* e.g. 42
|
|
|
|
* true
|
|
|
|
* "nasu"
|
2012-04-08 22:15:47 -07:00
|
|
|
* { type: "undefined" }
|
|
|
|
* { type: "null" }
|
|
|
|
* { type: "object", class: "Object" }
|
2012-02-07 09:22:30 -08:00
|
|
|
* @return object
|
|
|
|
* The same variable.
|
|
|
|
*/
|
|
|
|
_setGrip: function DVP__setGrip(aVar, aGrip) {
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure the variable container exists.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (!aVar) {
|
|
|
|
return null;
|
|
|
|
}
|
2012-04-08 22:15:47 -07:00
|
|
|
if (aGrip === undefined) {
|
|
|
|
aGrip = { type: "undefined" };
|
|
|
|
}
|
|
|
|
if (aGrip === null) {
|
|
|
|
aGrip = { type: "null" };
|
|
|
|
}
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-05-20 13:49:51 -07:00
|
|
|
let valueLabel = aVar.getElementsByClassName("value")[0];
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-03-15 01:17:03 -07:00
|
|
|
// Make sure the value node exists.
|
2012-05-20 13:49:51 -07:00
|
|
|
if (!valueLabel) {
|
2012-02-07 09:22:30 -08:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-05-20 13:49:51 -07:00
|
|
|
this._applyGrip(valueLabel, aGrip);
|
2012-02-07 09:22:30 -08:00
|
|
|
return aVar;
|
|
|
|
},
|
|
|
|
|
2012-03-15 01:17:03 -07:00
|
|
|
/**
|
|
|
|
* Applies the necessary text content and class name to a value node based
|
|
|
|
* on a grip.
|
|
|
|
*
|
2012-05-20 13:49:51 -07:00
|
|
|
* @param object aValueLabel
|
2012-03-15 01:17:03 -07:00
|
|
|
* The value node to apply the changes to.
|
|
|
|
* @param object aGrip
|
|
|
|
* @see DebuggerView.Properties._setGrip
|
|
|
|
*/
|
2012-05-20 13:49:51 -07:00
|
|
|
_applyGrip: function DVP__applyGrip(aValueLabel, aGrip) {
|
|
|
|
let prevGrip = aValueLabel.currentGrip;
|
2012-03-15 01:17:03 -07:00
|
|
|
if (prevGrip) {
|
2012-05-20 13:49:51 -07:00
|
|
|
aValueLabel.classList.remove(this._propertyColor(prevGrip));
|
2012-03-15 01:17:03 -07:00
|
|
|
}
|
|
|
|
|
2012-05-20 13:49:51 -07:00
|
|
|
aValueLabel.setAttribute("value", this._propertyString(aGrip));
|
|
|
|
aValueLabel.classList.add(this._propertyColor(aGrip));
|
|
|
|
aValueLabel.currentGrip = aGrip;
|
2012-03-15 01:17:03 -07:00
|
|
|
},
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Adds multiple properties to a specified variable.
|
|
|
|
* This function handles two types of properties: data properties and
|
|
|
|
* accessor properties, as defined in the remote debugger protocol spec.
|
|
|
|
*
|
|
|
|
* @param object aVar
|
|
|
|
* The parent variable element.
|
|
|
|
* @param object aProperties
|
|
|
|
* An object containing the key: descriptor data properties,
|
|
|
|
* specifying the value and/or type & class of the variable,
|
|
|
|
* or 'get' & 'set' accessor properties.
|
|
|
|
* e.g. { "someProp0": { value: 42 },
|
|
|
|
* "someProp1": { value: true },
|
|
|
|
* "someProp2": { value: "nasu" },
|
|
|
|
* "someProp3": { value: { type: "undefined" } },
|
|
|
|
* "someProp4": { value: { type: "null" } },
|
|
|
|
* "someProp5": { value: { type: "object", class: "Object" } },
|
2012-04-08 22:15:47 -07:00
|
|
|
* "someProp6": { get: { type: "object", class: "Function" },
|
|
|
|
* set: { type: "undefined" } }
|
2012-02-07 09:22:30 -08:00
|
|
|
* @return object
|
|
|
|
* The same variable.
|
|
|
|
*/
|
|
|
|
_addProperties: function DVP__addProperties(aVar, aProperties) {
|
2012-04-08 22:15:47 -07:00
|
|
|
// For each property, add it using the passed object key/grip.
|
2012-02-07 09:22:30 -08:00
|
|
|
for (let i in aProperties) {
|
|
|
|
// Can't use aProperties.hasOwnProperty(i), because it may be overridden.
|
|
|
|
if (Object.getOwnPropertyDescriptor(aProperties, i)) {
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Get the specified descriptor for current property.
|
2012-02-07 09:22:30 -08:00
|
|
|
let desc = aProperties[i];
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// As described in the remote debugger protocol, the value grip must be
|
|
|
|
// contained in a 'value' property.
|
2012-02-07 09:22:30 -08:00
|
|
|
let value = desc["value"];
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// For accessor property descriptors, the two grips need to be
|
|
|
|
// contained in 'get' and 'set' properties.
|
2012-02-07 09:22:30 -08:00
|
|
|
let getter = desc["get"];
|
|
|
|
let setter = desc["set"];
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Handle data property and accessor property descriptors.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (value !== undefined) {
|
2012-05-29 06:12:29 -07:00
|
|
|
this._addProperty(aVar, [i, value], desc);
|
2012-02-07 09:22:30 -08:00
|
|
|
}
|
2012-10-05 11:20:47 -07:00
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
if (getter !== undefined || setter !== undefined) {
|
|
|
|
let prop = this._addProperty(aVar, [i]).expand();
|
2012-05-29 06:12:29 -07:00
|
|
|
prop.getter = this._addProperty(prop, ["get", getter], desc);
|
|
|
|
prop.setter = this._addProperty(prop, ["set", setter], desc);
|
2012-02-07 09:22:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return aVar;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a property to a specified variable.
|
|
|
|
* If the optional id is not specified, the property html node will have a
|
|
|
|
* default id set as aVar.id->aKey-property.
|
|
|
|
*
|
|
|
|
* @param object aVar
|
|
|
|
* The parent variable element.
|
2012-05-29 06:12:29 -07:00
|
|
|
* @param array aProperty
|
2012-02-07 09:22:30 -08:00
|
|
|
* An array containing the key and grip properties, specifying
|
|
|
|
* the value and/or type & class of the variable (if the type
|
|
|
|
* is not specified, it will be inferred from the value).
|
2012-02-24 06:37:40 -08:00
|
|
|
* e.g. ["someProp0", 42]
|
|
|
|
* ["someProp1", true]
|
|
|
|
* ["someProp2", "nasu"]
|
|
|
|
* ["someProp3", { type: "undefined" }]
|
|
|
|
* ["someProp4", { type: "null" }]
|
|
|
|
* ["someProp5", { type: "object", class: "Object" }]
|
2012-05-29 06:12:29 -07:00
|
|
|
* @param object aFlags
|
2012-05-30 06:13:05 -07:00
|
|
|
* Contains configurable, enumerable or writable flags.
|
2012-02-07 09:22:30 -08:00
|
|
|
* @param string aName
|
|
|
|
* Optional, the property name.
|
|
|
|
* @paarm string aId
|
|
|
|
* Optional, an id for the property html node.
|
|
|
|
* @return object
|
|
|
|
* The newly created html node representing the added prop.
|
|
|
|
*/
|
2012-05-29 06:12:29 -07:00
|
|
|
_addProperty: function DVP__addProperty(aVar, aProperty, aFlags, aName, aId) {
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure the variable container exists.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (!aVar) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Compute the id of the element if not specified.
|
2012-02-07 09:22:30 -08:00
|
|
|
aId = aId || (aVar.id + "->" + aProperty[0] + "-property");
|
|
|
|
|
2012-10-05 11:20:47 -07:00
|
|
|
let parent;
|
|
|
|
if (aFlags && !aFlags.enumerable) {
|
|
|
|
parent = aVar.childNodes[2];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
parent = aVar.childNodes[1];
|
|
|
|
}
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Contains generic nodes and functionality.
|
2012-10-05 11:20:47 -07:00
|
|
|
let element = this._createPropertyElement(aName, aId, "property", parent);
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure the element was created successfully.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (!element) {
|
|
|
|
return null;
|
|
|
|
}
|
2012-05-25 11:40:40 -07:00
|
|
|
element._identifier = aName;
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @see DebuggerView.Properties._setGrip
|
|
|
|
*/
|
|
|
|
element.setGrip = this._setGrip.bind(this, element);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @see DebuggerView.Properties._addProperties
|
|
|
|
*/
|
|
|
|
element.addProperties = this._addProperties.bind(this, element);
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Setup the additional elements specific for a variable node.
|
2012-02-07 09:22:30 -08:00
|
|
|
element.refresh(function(pKey, pGrip) {
|
2012-03-15 01:17:03 -07:00
|
|
|
let title = element.getElementsByClassName("title")[0];
|
2012-05-20 13:49:51 -07:00
|
|
|
let nameLabel = title.getElementsByClassName("name")[0];
|
|
|
|
let separatorLabel = document.createElement("label");
|
|
|
|
let valueLabel = document.createElement("label");
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-06-21 08:25:34 -07:00
|
|
|
// Use attribute flags to specify the element type and tooltip text.
|
|
|
|
this._setAttributes(element, pKey, aFlags);
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
if ("undefined" !== typeof pKey) {
|
2012-05-20 13:49:51 -07:00
|
|
|
// Use a key element to specify the property name.
|
2012-05-30 06:13:05 -07:00
|
|
|
nameLabel.className = "key plain";
|
2012-05-20 13:49:51 -07:00
|
|
|
nameLabel.setAttribute("value", pKey.trim());
|
|
|
|
title.appendChild(nameLabel);
|
2012-02-07 09:22:30 -08:00
|
|
|
}
|
2012-04-08 22:15:47 -07:00
|
|
|
if ("undefined" !== typeof pGrip) {
|
2012-05-20 13:49:51 -07:00
|
|
|
// Separator between the variable name and its value.
|
|
|
|
separatorLabel.className = "plain";
|
|
|
|
separatorLabel.setAttribute("value", ":");
|
|
|
|
|
|
|
|
// Use a value element to specify the property value.
|
|
|
|
valueLabel.className = "value plain";
|
|
|
|
this._applyGrip(valueLabel, pGrip);
|
|
|
|
|
|
|
|
title.appendChild(separatorLabel);
|
|
|
|
title.appendChild(valueLabel);
|
2012-02-07 09:22:30 -08:00
|
|
|
}
|
|
|
|
|
2012-03-15 01:17:03 -07:00
|
|
|
// Handle the click event when pressing the element value label.
|
2012-05-20 13:49:51 -07:00
|
|
|
valueLabel.addEventListener("click", this._activateElementInputMode.bind({
|
2012-03-15 01:17:03 -07:00
|
|
|
scope: this,
|
|
|
|
element: element,
|
2012-05-20 13:49:51 -07:00
|
|
|
valueLabel: valueLabel
|
2012-03-15 01:17:03 -07:00
|
|
|
}));
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-05-24 04:23:53 -07:00
|
|
|
// Maintain the symbolic name of the property.
|
|
|
|
Object.defineProperty(element, "token", {
|
|
|
|
value: aVar.token + "['" + pKey + "']",
|
|
|
|
writable: false,
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true
|
|
|
|
});
|
|
|
|
|
2012-05-09 09:05:53 -07:00
|
|
|
// Remember a simple hierarchy between the parent and the element.
|
|
|
|
this._saveHierarchy({
|
|
|
|
parent: aVar,
|
|
|
|
element: element,
|
2012-05-20 13:49:51 -07:00
|
|
|
valueLabel: valueLabel
|
2012-05-09 09:05:53 -07:00
|
|
|
});
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Save the property to the variable for easier access.
|
2012-02-07 09:22:30 -08:00
|
|
|
Object.defineProperty(aVar, pKey, { value: element,
|
|
|
|
writable: false,
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true });
|
|
|
|
}.bind(this), aProperty);
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Return the element for later use if necessary.
|
2012-02-07 09:22:30 -08:00
|
|
|
return element;
|
|
|
|
},
|
|
|
|
|
2012-03-15 01:17:03 -07:00
|
|
|
/**
|
|
|
|
* Makes an element's (variable or priperty) value editable.
|
|
|
|
* Make sure 'this' is bound to an object containing the properties:
|
|
|
|
* {
|
|
|
|
* "scope": the original scope to be used, probably DebuggerView.Properties,
|
|
|
|
* "element": the element whose value should be made editable,
|
2012-05-20 13:49:51 -07:00
|
|
|
* "valueLabel": the label displaying the value
|
2012-03-15 01:17:03 -07:00
|
|
|
* }
|
|
|
|
*
|
|
|
|
* @param event aEvent [optional]
|
|
|
|
* The event requesting this action.
|
|
|
|
*/
|
|
|
|
_activateElementInputMode: function DVP__activateElementInputMode(aEvent) {
|
|
|
|
if (aEvent) {
|
|
|
|
aEvent.stopPropagation();
|
|
|
|
}
|
|
|
|
|
|
|
|
let self = this.scope;
|
|
|
|
let element = this.element;
|
2012-05-20 13:49:51 -07:00
|
|
|
let valueLabel = this.valueLabel;
|
|
|
|
let titleNode = valueLabel.parentNode;
|
|
|
|
let initialValue = valueLabel.getAttribute("value");
|
2012-03-15 01:17:03 -07:00
|
|
|
|
|
|
|
// When editing an object we need to collapse it first, in order to avoid
|
|
|
|
// displaying an inconsistent state while the user is editing.
|
|
|
|
element._previouslyExpanded = element.expanded;
|
|
|
|
element._preventExpand = true;
|
|
|
|
element.collapse();
|
|
|
|
element.forceHideArrow();
|
|
|
|
|
|
|
|
// Create a texbox input element which will be shown in the current
|
|
|
|
// element's value location.
|
|
|
|
let textbox = document.createElement("textbox");
|
2012-05-20 13:49:51 -07:00
|
|
|
textbox.setAttribute("value", initialValue);
|
2012-03-15 01:17:03 -07:00
|
|
|
textbox.className = "element-input";
|
2012-05-20 13:49:51 -07:00
|
|
|
textbox.width = valueLabel.clientWidth + 1;
|
2012-03-15 01:17:03 -07:00
|
|
|
|
|
|
|
// Save the new value when the texbox looses focus or ENTER is pressed.
|
|
|
|
function DVP_element_textbox_blur(aTextboxEvent) {
|
|
|
|
DVP_element_textbox_save();
|
|
|
|
}
|
|
|
|
|
2012-05-20 13:49:51 -07:00
|
|
|
function DVP_element_textbox_keyup(aTextboxEvent) {
|
|
|
|
if (aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_LEFT ||
|
|
|
|
aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_RIGHT ||
|
|
|
|
aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_UP ||
|
|
|
|
aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_DOWN) {
|
|
|
|
return;
|
|
|
|
}
|
2012-03-15 01:17:03 -07:00
|
|
|
if (aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_RETURN ||
|
|
|
|
aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_ENTER) {
|
|
|
|
DVP_element_textbox_save();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_ESCAPE) {
|
2012-05-20 13:49:51 -07:00
|
|
|
valueLabel.setAttribute("value", initialValue);
|
2012-03-15 01:17:03 -07:00
|
|
|
DVP_element_textbox_clear();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The actual save mechanism for the new variable/property value.
|
|
|
|
function DVP_element_textbox_save() {
|
2012-05-20 13:49:51 -07:00
|
|
|
if (textbox.value !== valueLabel.getAttribute("value")) {
|
|
|
|
valueLabel.setAttribute("value", textbox.value);
|
|
|
|
|
2012-05-24 04:23:53 -07:00
|
|
|
let expr = "(" + element.token + "=" + textbox.value + ")";
|
|
|
|
DebuggerController.StackFrames.evaluate(expr);
|
2012-03-15 01:17:03 -07:00
|
|
|
}
|
|
|
|
DVP_element_textbox_clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Removes the event listeners and appends the value node again.
|
|
|
|
function DVP_element_textbox_clear() {
|
|
|
|
element._preventExpand = false;
|
|
|
|
if (element._previouslyExpanded) {
|
|
|
|
element._previouslyExpanded = false;
|
|
|
|
element.expand();
|
|
|
|
}
|
|
|
|
element.showArrow();
|
|
|
|
|
|
|
|
textbox.removeEventListener("blur", DVP_element_textbox_blur, false);
|
2012-05-20 13:49:51 -07:00
|
|
|
textbox.removeEventListener("keyup", DVP_element_textbox_keyup, false);
|
|
|
|
titleNode.removeChild(textbox);
|
|
|
|
titleNode.appendChild(valueLabel);
|
2012-03-15 01:17:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
textbox.addEventListener("blur", DVP_element_textbox_blur, false);
|
2012-05-20 13:49:51 -07:00
|
|
|
textbox.addEventListener("keyup", DVP_element_textbox_keyup, false);
|
|
|
|
titleNode.removeChild(valueLabel);
|
|
|
|
titleNode.appendChild(textbox);
|
2012-03-15 01:17:03 -07:00
|
|
|
|
|
|
|
textbox.select();
|
|
|
|
|
|
|
|
// When the value is a string (displayed as "value"), then we probably want
|
|
|
|
// to change it to another string in the textbox, so to avoid typing the ""
|
|
|
|
// again, tackle with the selection bounds just a bit.
|
2012-05-20 13:49:51 -07:00
|
|
|
if (valueLabel.getAttribute("value").match(/^"[^"]*"$/)) {
|
2012-03-15 01:17:03 -07:00
|
|
|
textbox.selectionEnd--;
|
|
|
|
textbox.selectionStart++;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Returns a custom formatted property string for a type and a value.
|
|
|
|
*
|
|
|
|
* @param string | object aGrip
|
|
|
|
* The variable grip.
|
|
|
|
* @return string
|
|
|
|
* The formatted property string.
|
|
|
|
*/
|
|
|
|
_propertyString: function DVP__propertyString(aGrip) {
|
|
|
|
if (aGrip && "object" === typeof aGrip) {
|
2012-04-08 22:15:47 -07:00
|
|
|
switch (aGrip.type) {
|
2012-02-07 09:22:30 -08:00
|
|
|
case "undefined":
|
|
|
|
return "undefined";
|
|
|
|
case "null":
|
|
|
|
return "null";
|
|
|
|
default:
|
2012-04-08 22:15:47 -07:00
|
|
|
return "[" + aGrip.type + " " + aGrip.class + "]";
|
2012-02-07 09:22:30 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (typeof aGrip) {
|
|
|
|
case "string":
|
|
|
|
return "\"" + aGrip + "\"";
|
|
|
|
case "boolean":
|
|
|
|
return aGrip ? "true" : "false";
|
|
|
|
default:
|
|
|
|
return aGrip + "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return aGrip + "";
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a custom class style for a type and a value.
|
|
|
|
*
|
|
|
|
* @param string | object aGrip
|
|
|
|
* The variable grip.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
* The css class style.
|
|
|
|
*/
|
|
|
|
_propertyColor: function DVP__propertyColor(aGrip) {
|
|
|
|
if (aGrip && "object" === typeof aGrip) {
|
2012-04-08 22:15:47 -07:00
|
|
|
switch (aGrip.type) {
|
2012-02-07 09:22:30 -08:00
|
|
|
case "undefined":
|
|
|
|
return "token-undefined";
|
|
|
|
case "null":
|
|
|
|
return "token-null";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (typeof aGrip) {
|
|
|
|
case "string":
|
|
|
|
return "token-string";
|
|
|
|
case "boolean":
|
|
|
|
return "token-boolean";
|
|
|
|
case "number":
|
|
|
|
return "token-number";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "token-other";
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an element which contains generic nodes and functionality used by
|
|
|
|
* any scope, variable or property added to the tree.
|
2012-05-09 09:05:53 -07:00
|
|
|
* If the variable or property already exists, null is returned.
|
|
|
|
* Otherwise, the newly created element is returned.
|
2012-02-07 09:22:30 -08:00
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* A generic name used in a title strip.
|
|
|
|
* @param string aId
|
|
|
|
* id used by the created element node.
|
|
|
|
* @param string aClass
|
|
|
|
* Recommended style class used by the created element node.
|
|
|
|
* @param object aParent
|
|
|
|
* The parent node which will contain the element.
|
|
|
|
* @return object
|
|
|
|
* The newly created html node representing the generic elem.
|
|
|
|
*/
|
|
|
|
_createPropertyElement: function DVP__createPropertyElement(aName, aId, aClass, aParent) {
|
2012-04-08 22:15:47 -07:00
|
|
|
// Make sure we don't duplicate anything and the parent exists.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (document.getElementById(aId)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (!aParent) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-05-20 13:49:51 -07:00
|
|
|
let element = document.createElement("vbox");
|
|
|
|
let arrow = document.createElement("box");
|
|
|
|
let name = document.createElement("label");
|
|
|
|
|
|
|
|
let title = document.createElement("box");
|
|
|
|
let details = document.createElement("vbox");
|
2012-10-05 11:20:47 -07:00
|
|
|
let nonEnum = document.createElement("vbox");
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Create a scope node to contain all the elements.
|
2012-02-07 09:22:30 -08:00
|
|
|
element.id = aId;
|
|
|
|
element.className = aClass;
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// The expand/collapse arrow.
|
2012-02-07 09:22:30 -08:00
|
|
|
arrow.className = "arrow";
|
|
|
|
arrow.style.visibility = "hidden";
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// The name element.
|
2012-05-20 13:49:51 -07:00
|
|
|
name.className = "name plain";
|
|
|
|
name.setAttribute("value", aName || "");
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// The title element, containing the arrow and the name.
|
2012-02-07 09:22:30 -08:00
|
|
|
title.className = "title";
|
2012-05-20 13:49:51 -07:00
|
|
|
title.setAttribute("align", "center")
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// The node element which will contain any added scope variables.
|
2012-02-07 09:22:30 -08:00
|
|
|
details.className = "details";
|
2012-10-05 11:20:47 -07:00
|
|
|
nonEnum.className = "details nonenum";
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-03-15 01:17:03 -07:00
|
|
|
// Add the click event handler for the title, or arrow and name.
|
|
|
|
if (aClass === "scope") {
|
2012-08-18 02:29:47 -07:00
|
|
|
title.addEventListener("click", function() element.toggle(), false);
|
2012-03-15 01:17:03 -07:00
|
|
|
} else {
|
2012-08-18 02:29:47 -07:00
|
|
|
arrow.addEventListener("click", function() element.toggle(), false);
|
|
|
|
name.addEventListener("click", function() element.toggle(), false);
|
|
|
|
name.addEventListener("mouseover", function() element.updateTooltip(name), false);
|
2012-03-15 01:17:03 -07:00
|
|
|
}
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
title.appendChild(arrow);
|
|
|
|
title.appendChild(name);
|
|
|
|
|
|
|
|
element.appendChild(title);
|
|
|
|
element.appendChild(details);
|
2012-10-05 11:20:47 -07:00
|
|
|
element.appendChild(nonEnum);
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
aParent.appendChild(element);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows the element, setting the display style to "block".
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
element.show = function DVP_element_show() {
|
|
|
|
element.style.display = "-moz-box";
|
|
|
|
|
|
|
|
if ("function" === typeof element.onshow) {
|
|
|
|
element.onshow(element);
|
|
|
|
}
|
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides the element, setting the display style to "none".
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
element.hide = function DVP_element_hide() {
|
|
|
|
element.style.display = "none";
|
|
|
|
|
|
|
|
if ("function" === typeof element.onhide) {
|
|
|
|
element.onhide(element);
|
|
|
|
}
|
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expands the element, showing all the added details.
|
2012-05-25 11:40:40 -07:00
|
|
|
*
|
|
|
|
* @param boolean aSkipAnimationFlag
|
|
|
|
* Pass true to not show an opening animation.
|
2012-02-07 09:22:30 -08:00
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
2012-05-25 11:40:40 -07:00
|
|
|
element.expand = function DVP_element_expand(aSkipAnimationFlag) {
|
2012-03-15 01:17:03 -07:00
|
|
|
if (element._preventExpand) {
|
|
|
|
return;
|
|
|
|
}
|
2012-02-07 09:22:30 -08:00
|
|
|
arrow.setAttribute("open", "");
|
|
|
|
details.setAttribute("open", "");
|
|
|
|
|
2012-10-05 11:20:47 -07:00
|
|
|
if (Prefs.nonEnumVisible) {
|
|
|
|
nonEnum.setAttribute("open", "");
|
|
|
|
}
|
|
|
|
|
2012-05-25 11:40:40 -07:00
|
|
|
if (!aSkipAnimationFlag) {
|
|
|
|
details.setAttribute("animated", "");
|
2012-10-05 11:20:47 -07:00
|
|
|
nonEnum.setAttribute("animated", "");
|
2012-05-25 11:40:40 -07:00
|
|
|
}
|
2012-10-05 11:20:47 -07:00
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
if ("function" === typeof element.onexpand) {
|
|
|
|
element.onexpand(element);
|
|
|
|
}
|
2012-10-05 11:20:47 -07:00
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Collapses the element, hiding all the added details.
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
element.collapse = function DVP_element_collapse() {
|
2012-03-15 01:17:03 -07:00
|
|
|
if (element._preventCollapse) {
|
|
|
|
return;
|
|
|
|
}
|
2012-10-05 11:20:47 -07:00
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
arrow.removeAttribute("open");
|
|
|
|
details.removeAttribute("open");
|
2012-05-25 11:40:40 -07:00
|
|
|
details.removeAttribute("animated");
|
2012-10-05 11:20:47 -07:00
|
|
|
nonEnum.removeAttribute("open");
|
|
|
|
nonEnum.removeAttribute("animated");
|
2012-02-07 09:22:30 -08:00
|
|
|
|
|
|
|
if ("function" === typeof element.oncollapse) {
|
|
|
|
element.oncollapse(element);
|
|
|
|
}
|
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggles between the element collapse/expand state.
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
element.toggle = function DVP_element_toggle() {
|
|
|
|
element.expanded = !element.expanded;
|
|
|
|
|
|
|
|
if ("function" === typeof element.ontoggle) {
|
|
|
|
element.ontoggle(element);
|
|
|
|
}
|
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Shows the element expand/collapse arrow (only if necessary!).
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
element.showArrow = function DVP_element_showArrow() {
|
2012-10-05 11:20:47 -07:00
|
|
|
let len = details.childNodes.length + nonEnum.childNodes.length;
|
|
|
|
|
|
|
|
if (element._forceShowArrow || len) {
|
2012-04-08 22:15:47 -07:00
|
|
|
arrow.style.visibility = "visible";
|
|
|
|
}
|
2012-10-05 11:20:47 -07:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
2012-03-15 01:17:03 -07:00
|
|
|
/**
|
|
|
|
* Hides the element expand/collapse arrow.
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
element.hideArrow = function DVP_element_hideArrow() {
|
|
|
|
if (!element._forceShowArrow) {
|
|
|
|
arrow.style.visibility = "hidden";
|
|
|
|
}
|
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Forces the element expand/collapse arrow to be visible, even if there
|
|
|
|
* are no child elements.
|
|
|
|
*
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
2012-03-15 01:17:03 -07:00
|
|
|
element.forceShowArrow = function DVP_element_forceShowArrow() {
|
|
|
|
element._forceShowArrow = true;
|
2012-04-08 22:15:47 -07:00
|
|
|
arrow.style.visibility = "visible";
|
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2012-03-15 01:17:03 -07:00
|
|
|
* Forces the element expand/collapse arrow to be hidden, even if there
|
|
|
|
* are some child elements.
|
|
|
|
*
|
2012-04-08 22:15:47 -07:00
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
2012-03-15 01:17:03 -07:00
|
|
|
element.forceHideArrow = function DVP_element_forceHideArrow() {
|
|
|
|
arrow.style.visibility = "hidden";
|
2012-04-08 22:15:47 -07:00
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Returns if the element is visible.
|
|
|
|
* @return boolean
|
|
|
|
* True if the element is visible.
|
|
|
|
*/
|
|
|
|
Object.defineProperty(element, "visible", {
|
|
|
|
get: function DVP_element_getVisible() {
|
|
|
|
return element.style.display !== "none";
|
|
|
|
},
|
|
|
|
set: function DVP_element_setVisible(value) {
|
|
|
|
if (value) {
|
|
|
|
element.show();
|
|
|
|
} else {
|
|
|
|
element.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns if the element is expanded.
|
|
|
|
* @return boolean
|
|
|
|
* True if the element is expanded.
|
|
|
|
*/
|
|
|
|
Object.defineProperty(element, "expanded", {
|
|
|
|
get: function DVP_element_getExpanded() {
|
|
|
|
return arrow.hasAttribute("open");
|
|
|
|
},
|
|
|
|
set: function DVP_element_setExpanded(value) {
|
|
|
|
if (value) {
|
|
|
|
element.expand();
|
|
|
|
} else {
|
|
|
|
element.collapse();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all added children in the details container tree.
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
element.empty = function DVP_element_empty() {
|
2012-03-15 01:17:03 -07:00
|
|
|
// This details node won't have any elements, so hide the arrow.
|
2012-02-07 09:22:30 -08:00
|
|
|
arrow.style.visibility = "hidden";
|
2012-10-05 11:20:47 -07:00
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
while (details.firstChild) {
|
|
|
|
details.removeChild(details.firstChild);
|
|
|
|
}
|
|
|
|
|
2012-10-05 11:20:47 -07:00
|
|
|
while (nonEnum.firstChild) {
|
|
|
|
nonEnum.removeChild(nonEnum.firstChild);
|
|
|
|
}
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
if ("function" === typeof element.onempty) {
|
|
|
|
element.onempty(element);
|
|
|
|
}
|
2012-10-05 11:20:47 -07:00
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the element from the parent node details container tree.
|
|
|
|
* @return object
|
|
|
|
* The same element.
|
|
|
|
*/
|
|
|
|
element.remove = function DVP_element_remove() {
|
|
|
|
element.parentNode.removeChild(element);
|
|
|
|
|
|
|
|
if ("function" === typeof element.onremove) {
|
|
|
|
element.onremove(element);
|
|
|
|
}
|
|
|
|
return element;
|
|
|
|
};
|
|
|
|
|
2012-03-15 01:17:03 -07:00
|
|
|
/**
|
|
|
|
* Returns if the element expander (arrow) is visible.
|
|
|
|
* @return boolean
|
|
|
|
* True if the arrow is visible.
|
|
|
|
*/
|
|
|
|
Object.defineProperty(element, "arrowVisible", {
|
|
|
|
get: function DVP_element_getArrowVisible() {
|
|
|
|
return arrow.style.visibility !== "hidden";
|
|
|
|
},
|
|
|
|
set: function DVP_element_setExpanded(value) {
|
|
|
|
if (value) {
|
|
|
|
element.showArrow();
|
|
|
|
} else {
|
|
|
|
element.hideArrow();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2012-06-21 08:25:34 -07:00
|
|
|
/**
|
|
|
|
* Creates a tooltip for the element displaying certain attributes.
|
|
|
|
*
|
|
|
|
* @param object aAnchor
|
|
|
|
* The element which will anchor the tooltip.
|
|
|
|
*/
|
|
|
|
element.updateTooltip = function DVP_element_updateTooltip(aAnchor) {
|
|
|
|
let tooltip = document.getElementById("element-tooltip");
|
|
|
|
if (tooltip) {
|
|
|
|
document.documentElement.removeChild(tooltip);
|
|
|
|
}
|
|
|
|
|
|
|
|
tooltip = document.createElement("tooltip");
|
|
|
|
tooltip.id = "element-tooltip";
|
|
|
|
|
|
|
|
let configurableLabel = document.createElement("label");
|
|
|
|
configurableLabel.id = "configurableLabel";
|
|
|
|
configurableLabel.setAttribute("value", "configurable");
|
|
|
|
|
|
|
|
let enumerableLabel = document.createElement("label");
|
|
|
|
enumerableLabel.id = "enumerableLabel";
|
|
|
|
enumerableLabel.setAttribute("value", "enumerable");
|
|
|
|
|
|
|
|
let writableLabel = document.createElement("label");
|
|
|
|
writableLabel.id = "writableLabel";
|
|
|
|
writableLabel.setAttribute("value", "writable");
|
|
|
|
|
|
|
|
tooltip.setAttribute("orient", "horizontal")
|
|
|
|
tooltip.appendChild(configurableLabel);
|
|
|
|
tooltip.appendChild(enumerableLabel);
|
|
|
|
tooltip.appendChild(writableLabel);
|
|
|
|
|
|
|
|
if (element.hasAttribute("non-configurable")) {
|
|
|
|
configurableLabel.setAttribute("non-configurable", "");
|
|
|
|
}
|
|
|
|
if (element.hasAttribute("non-enumerable")) {
|
|
|
|
enumerableLabel.setAttribute("non-enumerable", "");
|
|
|
|
}
|
|
|
|
if (element.hasAttribute("non-writable")) {
|
|
|
|
writableLabel.setAttribute("non-writable", "");
|
|
|
|
}
|
|
|
|
|
|
|
|
document.documentElement.appendChild(tooltip);
|
|
|
|
aAnchor.setAttribute("tooltip", tooltip.id);
|
|
|
|
};
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Generic function refreshing the internal state of the element when
|
|
|
|
* it's modified (e.g. a child detail, variable, property is added).
|
|
|
|
*
|
|
|
|
* @param function aFunction
|
|
|
|
* The function logic used to modify the internal state.
|
|
|
|
* @param array aArguments
|
|
|
|
* Optional arguments array to be applied to aFunction.
|
|
|
|
*/
|
|
|
|
element.refresh = function DVP_element_refresh(aFunction, aArguments) {
|
|
|
|
if ("function" === typeof aFunction) {
|
|
|
|
aFunction.apply(this, aArguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
let node = aParent.parentNode;
|
2012-03-15 01:17:03 -07:00
|
|
|
let arrow = node.getElementsByClassName("arrow")[0];
|
2012-10-05 11:20:47 -07:00
|
|
|
let children = node.querySelectorAll(".details > vbox").length;
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// If the parent details node has at least one element, set the
|
|
|
|
// expand/collapse arrow visible.
|
2012-02-07 09:22:30 -08:00
|
|
|
if (children) {
|
|
|
|
arrow.style.visibility = "visible";
|
|
|
|
} else {
|
|
|
|
arrow.style.visibility = "hidden";
|
|
|
|
}
|
|
|
|
}.bind(this);
|
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
// Return the element for later use and customization.
|
2012-02-07 09:22:30 -08:00
|
|
|
return element;
|
|
|
|
},
|
|
|
|
|
2012-05-09 09:05:53 -07:00
|
|
|
/**
|
|
|
|
* Remember a simple hierarchy of parent->element->children.
|
|
|
|
*
|
|
|
|
* @param object aProperties
|
|
|
|
* Container for the parent, element and the associated value node.
|
|
|
|
*/
|
|
|
|
_saveHierarchy: function DVP__saveHierarchy(aProperties) {
|
|
|
|
let parent = aProperties.parent;
|
|
|
|
let element = aProperties.element;
|
2012-05-20 13:49:51 -07:00
|
|
|
let valueLabel = aProperties.valueLabel;
|
2012-05-09 09:05:53 -07:00
|
|
|
let store = aProperties.store || parent._children;
|
|
|
|
|
|
|
|
// Make sure we have a valid element and a children storage object.
|
|
|
|
if (!element || !store) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let relation = {
|
|
|
|
root: parent ? (parent._root || parent) : null,
|
|
|
|
parent: parent || null,
|
|
|
|
element: element,
|
2012-05-20 13:49:51 -07:00
|
|
|
valueLabel: valueLabel,
|
2012-05-09 09:05:53 -07:00
|
|
|
children: {}
|
|
|
|
};
|
|
|
|
|
2012-05-25 11:40:40 -07:00
|
|
|
store[element._identifier] = relation;
|
2012-05-09 09:05:53 -07:00
|
|
|
element._root = relation.root;
|
|
|
|
element._children = relation.children;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an object to store a hierarchy of scopes, variables and properties
|
|
|
|
* and saves the previous store.
|
|
|
|
*/
|
|
|
|
createHierarchyStore: function DVP_createHierarchyStore() {
|
|
|
|
this._prevHierarchy = this._currHierarchy;
|
|
|
|
this._currHierarchy = {};
|
2012-05-25 11:40:40 -07:00
|
|
|
},
|
2012-05-09 09:05:53 -07:00
|
|
|
|
2012-05-25 11:40:40 -07:00
|
|
|
/**
|
|
|
|
* Creates a hierarchy holder for a scope.
|
|
|
|
*
|
|
|
|
* @param object aScope
|
|
|
|
* The designated scope to track.
|
|
|
|
*/
|
|
|
|
addScopeToHierarchy: function DVP_addScopeToHierarchy(aScope) {
|
|
|
|
this._saveHierarchy({ element: aScope, store: this._currHierarchy });
|
2012-05-09 09:05:53 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Briefly flash the variables that changed between pauses.
|
|
|
|
*/
|
|
|
|
commitHierarchy: function DVS_commitHierarchy() {
|
|
|
|
for (let i in this._currHierarchy) {
|
|
|
|
let currScope = this._currHierarchy[i];
|
|
|
|
let prevScope = this._prevHierarchy[i];
|
|
|
|
|
2012-05-29 04:22:18 -07:00
|
|
|
if (!prevScope) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-05-09 09:05:53 -07:00
|
|
|
for (let v in currScope.children) {
|
|
|
|
let currVar = currScope.children[v];
|
2012-05-29 04:22:18 -07:00
|
|
|
let prevVar = prevScope.children[v];
|
2012-05-09 09:05:53 -07:00
|
|
|
|
|
|
|
let action = "";
|
|
|
|
|
|
|
|
if (prevVar) {
|
2012-05-20 13:49:51 -07:00
|
|
|
let prevValue = prevVar.valueLabel.getAttribute("value");
|
|
|
|
let currValue = currVar.valueLabel.getAttribute("value");
|
2012-05-09 09:05:53 -07:00
|
|
|
|
|
|
|
if (currValue != prevValue) {
|
|
|
|
action = "changed";
|
|
|
|
} else {
|
|
|
|
action = "unchanged";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
action = "added";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (action) {
|
|
|
|
currVar.element.setAttribute(action, "");
|
|
|
|
|
|
|
|
window.setTimeout(function() {
|
|
|
|
currVar.element.removeAttribute(action);
|
2012-05-25 11:40:40 -07:00
|
|
|
}, PROPERTY_VIEW_FLASH_DURATION);
|
2012-05-09 09:05:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A simple model representation of all the scopes, variables and properties,
|
|
|
|
* with parent-child relations.
|
|
|
|
*/
|
|
|
|
_currHierarchy: null,
|
|
|
|
_prevHierarchy: null,
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* The cached variable properties container.
|
|
|
|
*/
|
|
|
|
_vars: null,
|
|
|
|
|
2012-10-05 11:20:47 -07:00
|
|
|
_onShowNonEnums: function DVP__onShowNonEnums() {
|
|
|
|
let option = document.getElementById("show-nonenum");
|
|
|
|
Prefs.nonEnumVisible = option.checked;
|
|
|
|
|
|
|
|
let els = document.getElementsByClassName("nonenum").iterator();
|
|
|
|
for (let el of els) {
|
|
|
|
if (el.parentNode.expanded) {
|
|
|
|
if (Prefs.nonEnumVisible) {
|
|
|
|
el.setAttribute("open", "");
|
|
|
|
} else {
|
|
|
|
el.removeAttribute("open");
|
|
|
|
el.removeAttribute("animated");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-02-07 09:22:30 -08:00
|
|
|
/**
|
|
|
|
* Initialization function, called when the debugger is initialized.
|
|
|
|
*/
|
|
|
|
initialize: function DVP_initialize() {
|
2012-10-05 11:20:47 -07:00
|
|
|
let showNonEnums = document.getElementById("show-nonenum");
|
|
|
|
showNonEnums.addEventListener("click", this._onShowNonEnums, false);
|
|
|
|
showNonEnums.checked = Prefs.nonEnumVisible;
|
|
|
|
|
2012-09-12 03:39:51 -07:00
|
|
|
this._vars = DebuggerView._variables;
|
2012-07-14 23:40:37 -07:00
|
|
|
|
|
|
|
this.emptyText();
|
|
|
|
this.createHierarchyStore();
|
2012-02-07 09:22:30 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destruction function, called when the debugger is shut down.
|
|
|
|
*/
|
|
|
|
destroy: function DVP_destroy() {
|
2012-07-18 07:21:57 -07:00
|
|
|
this.empty();
|
|
|
|
|
2012-05-09 09:05:53 -07:00
|
|
|
this._currHierarchy = null;
|
|
|
|
this._prevHierarchy = null;
|
2012-02-07 09:22:30 -08:00
|
|
|
this._vars = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2012-04-08 22:15:47 -07:00
|
|
|
* Preliminary setup for the DebuggerView object.
|
2012-02-07 09:22:30 -08:00
|
|
|
*/
|
2012-08-18 02:29:47 -07:00
|
|
|
DebuggerView.GlobalSearch = new GlobalSearchView();
|
2012-04-08 22:15:47 -07:00
|
|
|
DebuggerView.Scripts = new ScriptsView();
|
|
|
|
DebuggerView.StackFrames = new StackFramesView();
|
2012-07-14 23:40:37 -07:00
|
|
|
DebuggerView.Breakpoints = new BreakpointsView();
|
2012-04-08 22:15:47 -07:00
|
|
|
DebuggerView.Properties = new PropertiesView();
|
2012-02-07 09:22:30 -08:00
|
|
|
|
2012-04-08 22:15:47 -07:00
|
|
|
/**
|
|
|
|
* Export the source editor to the global scope for easier access in tests.
|
|
|
|
*/
|
|
|
|
Object.defineProperty(window, "editor", {
|
|
|
|
get: function() { return DebuggerView.editor; }
|
|
|
|
});
|