mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 14:15:30 +00:00
85611a7b6d
--HG-- extra : rebase_source : a22344ee1569f58f1f0a01017bfe0d46a6a14602
322 lines
11 KiB
JavaScript
322 lines
11 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
/* import-globals-from ../debugger-controller.js */
|
|
/* import-globals-from ../debugger-view.js */
|
|
/* import-globals-from ../utils.js */
|
|
/* globals document, window */
|
|
"use strict";
|
|
|
|
const {setTooltipVariableContent} = require("devtools/client/shared/widgets/tooltip/VariableContentHelper");
|
|
|
|
/**
|
|
* Functions handling the variables bubble UI.
|
|
*/
|
|
function VariableBubbleView(DebuggerController, DebuggerView) {
|
|
dumpn("VariableBubbleView was instantiated");
|
|
|
|
this.StackFrames = DebuggerController.StackFrames;
|
|
this.Parser = DebuggerController.Parser;
|
|
this.DebuggerView = DebuggerView;
|
|
|
|
this._onMouseMove = this._onMouseMove.bind(this);
|
|
this._onMouseOut = this._onMouseOut.bind(this);
|
|
this._onPopupHiding = this._onPopupHiding.bind(this);
|
|
}
|
|
|
|
VariableBubbleView.prototype = {
|
|
/**
|
|
* Delay before showing the variables bubble tooltip when hovering a valid
|
|
* target.
|
|
*/
|
|
TOOLTIP_SHOW_DELAY: 750,
|
|
|
|
/**
|
|
* Tooltip position for the variables bubble tooltip.
|
|
*/
|
|
TOOLTIP_POSITION: "topcenter bottomleft",
|
|
|
|
/**
|
|
* Initialization function, called when the debugger is started.
|
|
*/
|
|
initialize: function () {
|
|
dumpn("Initializing the VariableBubbleView");
|
|
|
|
this._toolbox = DebuggerController._toolbox;
|
|
this._editorContainer = document.getElementById("editor");
|
|
this._editorContainer.addEventListener("mousemove", this._onMouseMove);
|
|
this._editorContainer.addEventListener("mouseout", this._onMouseOut);
|
|
|
|
this._tooltip = new Tooltip(document, {
|
|
closeOnEvents: [{
|
|
emitter: this._toolbox,
|
|
event: "select"
|
|
}, {
|
|
emitter: this._editorContainer,
|
|
event: "scroll",
|
|
useCapture: true
|
|
}, {
|
|
emitter: document,
|
|
event: "keydown"
|
|
}]
|
|
});
|
|
this._tooltip.defaultPosition = this.TOOLTIP_POSITION;
|
|
this._tooltip.panel.addEventListener("popuphiding", this._onPopupHiding);
|
|
},
|
|
|
|
/**
|
|
* Destruction function, called when the debugger is closed.
|
|
*/
|
|
destroy: function () {
|
|
dumpn("Destroying the VariableBubbleView");
|
|
|
|
this._tooltip.panel.removeEventListener("popuphiding", this._onPopupHiding);
|
|
this._editorContainer.removeEventListener("mousemove", this._onMouseMove);
|
|
this._editorContainer.removeEventListener("mouseout", this._onMouseOut);
|
|
},
|
|
|
|
/**
|
|
* Specifies whether literals can be (redundantly) inspected in a popup.
|
|
* This behavior is deprecated, but still tested in a few places.
|
|
*/
|
|
_ignoreLiterals: true,
|
|
|
|
/**
|
|
* Searches for an identifier underneath the specified position in the
|
|
* source editor, and if found, opens a VariablesView inspection popup.
|
|
*
|
|
* @param number x, y
|
|
* The left/top coordinates where to look for an identifier.
|
|
*/
|
|
_findIdentifier: function (x, y) {
|
|
let editor = this.DebuggerView.editor;
|
|
|
|
// Calculate the editor's line and column at the current x and y coords.
|
|
let hoveredPos = editor.getPositionFromCoords({ left: x, top: y });
|
|
let hoveredOffset = editor.getOffset(hoveredPos);
|
|
let hoveredLine = hoveredPos.line;
|
|
let hoveredColumn = hoveredPos.ch;
|
|
|
|
// A source contains multiple scripts. Find the start index of the script
|
|
// containing the specified offset relative to its parent source.
|
|
let contents = editor.getText();
|
|
let location = this.DebuggerView.Sources.selectedValue;
|
|
let parsedSource = this.Parser.get(contents, location);
|
|
let scriptInfo = parsedSource.getScriptInfo(hoveredOffset);
|
|
|
|
// If the script length is negative, we're not hovering JS source code.
|
|
if (scriptInfo.length == -1) {
|
|
return;
|
|
}
|
|
|
|
// Using the script offset, determine the actual line and column inside the
|
|
// script, to use when finding identifiers.
|
|
let scriptStart = editor.getPosition(scriptInfo.start);
|
|
let scriptLineOffset = scriptStart.line;
|
|
let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
|
|
|
|
let scriptLine = hoveredLine - scriptLineOffset;
|
|
let scriptColumn = hoveredColumn - scriptColumnOffset;
|
|
let identifierInfo = parsedSource.getIdentifierAt({
|
|
line: scriptLine + 1,
|
|
column: scriptColumn,
|
|
scriptIndex: scriptInfo.index,
|
|
ignoreLiterals: this._ignoreLiterals
|
|
});
|
|
|
|
// If the info is null, we're not hovering any identifier.
|
|
if (!identifierInfo) {
|
|
return;
|
|
}
|
|
|
|
// Transform the line and column relative to the parsed script back
|
|
// to the context of the parent source.
|
|
let { start: identifierStart, end: identifierEnd } = identifierInfo.location;
|
|
let identifierCoords = {
|
|
line: identifierStart.line + scriptLineOffset,
|
|
column: identifierStart.column + scriptColumnOffset,
|
|
length: identifierEnd.column - identifierStart.column
|
|
};
|
|
|
|
// Evaluate the identifier in the current stack frame and show the
|
|
// results in a VariablesView inspection popup.
|
|
this.StackFrames.evaluate(identifierInfo.evalString)
|
|
.then(frameFinished => {
|
|
if ("return" in frameFinished) {
|
|
this.showContents({
|
|
coords: identifierCoords,
|
|
evalPrefix: identifierInfo.evalString,
|
|
objectActor: frameFinished.return
|
|
});
|
|
} else {
|
|
let msg = "Evaluation has thrown for: " + identifierInfo.evalString;
|
|
console.warn(msg);
|
|
dumpn(msg);
|
|
}
|
|
})
|
|
.then(null, err => {
|
|
let msg = "Couldn't evaluate: " + err.message;
|
|
console.error(msg);
|
|
dumpn(msg);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Shows an inspection popup for a specified object actor grip.
|
|
*
|
|
* @param string object
|
|
* An object containing the following properties:
|
|
* - coords: the inspected identifier coordinates in the editor,
|
|
* containing the { line, column, length } properties.
|
|
* - evalPrefix: a prefix for the variables view evaluation macros.
|
|
* - objectActor: the value grip for the object actor.
|
|
*/
|
|
showContents: function ({ coords, evalPrefix, objectActor }) {
|
|
let editor = this.DebuggerView.editor;
|
|
let { line, column, length } = coords;
|
|
|
|
// Highlight the function found at the mouse position.
|
|
this._markedText = editor.markText(
|
|
{ line: line - 1, ch: column },
|
|
{ line: line - 1, ch: column + length });
|
|
|
|
// If the grip represents a primitive value, use a more lightweight
|
|
// machinery to display it.
|
|
if (VariablesView.isPrimitive({ value: objectActor })) {
|
|
let className = VariablesView.getClass(objectActor);
|
|
let textContent = VariablesView.getString(objectActor);
|
|
this._tooltip.setTextContent({
|
|
messages: [textContent],
|
|
messagesClass: className,
|
|
containerClass: "plain"
|
|
}, [{
|
|
label: L10N.getStr("addWatchExpressionButton"),
|
|
className: "dbg-expression-button",
|
|
command: () => {
|
|
this.DebuggerView.VariableBubble.hideContents();
|
|
this.DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
|
|
}
|
|
}]);
|
|
} else {
|
|
setTooltipVariableContent(this._tooltip, objectActor, {
|
|
searchPlaceholder: L10N.getStr("emptyPropertiesFilterText"),
|
|
searchEnabled: Prefs.variablesSearchboxVisible,
|
|
eval: (variable, value) => {
|
|
let string = variable.evaluationMacro(variable, value);
|
|
this.StackFrames.evaluate(string);
|
|
this.DebuggerView.VariableBubble.hideContents();
|
|
}
|
|
}, {
|
|
getEnvironmentClient: aObject => gThreadClient.environment(aObject),
|
|
getObjectClient: aObject => gThreadClient.pauseGrip(aObject),
|
|
simpleValueEvalMacro: this._getSimpleValueEvalMacro(evalPrefix),
|
|
getterOrSetterEvalMacro: this._getGetterOrSetterEvalMacro(evalPrefix),
|
|
overrideValueEvalMacro: this._getOverrideValueEvalMacro(evalPrefix)
|
|
}, {
|
|
fetched: (aEvent, aType) => {
|
|
if (aType == "properties") {
|
|
window.emit(EVENTS.FETCHED_BUBBLE_PROPERTIES);
|
|
}
|
|
}
|
|
}, [{
|
|
label: L10N.getStr("addWatchExpressionButton"),
|
|
className: "dbg-expression-button",
|
|
command: () => {
|
|
this.DebuggerView.VariableBubble.hideContents();
|
|
this.DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
|
|
}
|
|
}], this._toolbox);
|
|
}
|
|
|
|
this._tooltip.show(this._markedText.anchor);
|
|
},
|
|
|
|
/**
|
|
* Hides the inspection popup.
|
|
*/
|
|
hideContents: function () {
|
|
clearNamedTimeout("editor-mouse-move");
|
|
this._tooltip.hide();
|
|
},
|
|
|
|
/**
|
|
* Checks whether the inspection popup is shown.
|
|
*
|
|
* @return boolean
|
|
* True if the panel is shown or showing, false otherwise.
|
|
*/
|
|
contentsShown: function () {
|
|
return this._tooltip.isShown();
|
|
},
|
|
|
|
/**
|
|
* Functions for getting customized variables view evaluation macros.
|
|
*
|
|
* @param string aPrefix
|
|
* See the corresponding VariablesView.* functions.
|
|
*/
|
|
_getSimpleValueEvalMacro: function (aPrefix) {
|
|
return (item, string) =>
|
|
VariablesView.simpleValueEvalMacro(item, string, aPrefix);
|
|
},
|
|
_getGetterOrSetterEvalMacro: function (aPrefix) {
|
|
return (item, string) =>
|
|
VariablesView.getterOrSetterEvalMacro(item, string, aPrefix);
|
|
},
|
|
_getOverrideValueEvalMacro: function (aPrefix) {
|
|
return (item, string) =>
|
|
VariablesView.overrideValueEvalMacro(item, string, aPrefix);
|
|
},
|
|
|
|
/**
|
|
* The mousemove listener for the source editor.
|
|
*/
|
|
_onMouseMove: function (e) {
|
|
// Prevent the variable inspection popup from showing when the thread client
|
|
// is not paused, or while a popup is already visible, or when the user tries
|
|
// to select text in the editor.
|
|
let isResumed = gThreadClient && gThreadClient.state != "paused";
|
|
let isSelecting = this.DebuggerView.editor.somethingSelected() && e.buttons > 0;
|
|
let isPopupVisible = !this._tooltip.isHidden();
|
|
if (isResumed || isSelecting || isPopupVisible) {
|
|
clearNamedTimeout("editor-mouse-move");
|
|
return;
|
|
}
|
|
// Allow events to settle down first. If the mouse hovers over
|
|
// a certain point in the editor long enough, try showing a variable bubble.
|
|
setNamedTimeout("editor-mouse-move",
|
|
this.TOOLTIP_SHOW_DELAY, () => this._findIdentifier(e.clientX, e.clientY));
|
|
},
|
|
|
|
/**
|
|
* The mouseout listener for the source editor container node.
|
|
*/
|
|
_onMouseOut: function () {
|
|
clearNamedTimeout("editor-mouse-move");
|
|
},
|
|
|
|
/**
|
|
* Listener handling the popup hiding event.
|
|
*/
|
|
_onPopupHiding: function ({ target }) {
|
|
if (this._tooltip.panel != target) {
|
|
return;
|
|
}
|
|
if (this._markedText) {
|
|
this._markedText.clear();
|
|
this._markedText = null;
|
|
}
|
|
if (!this._tooltip.isEmpty()) {
|
|
this._tooltip.empty();
|
|
}
|
|
},
|
|
|
|
_editorContainer: null,
|
|
_markedText: null,
|
|
_tooltip: null
|
|
};
|
|
|
|
DebuggerView.VariableBubble = new VariableBubbleView(DebuggerController, DebuggerView);
|