281 lines
7.5 KiB
JavaScript
281 lines
7.5 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
|
|
|
"use strict";
|
|
|
|
const { MultiLocalizationHelper } = require("devtools/shared/l10n");
|
|
const {
|
|
FluentL10n,
|
|
} = require("devtools/client/shared/fluent-l10n/fluent-l10n");
|
|
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"openContentLink",
|
|
"resource://devtools/client/shared/link.js",
|
|
true
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"features",
|
|
"resource://devtools/client/debugger/src/utils/prefs.js",
|
|
true
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"registerStoreObserver",
|
|
"resource://devtools/client/shared/redux/subscriber.js",
|
|
true
|
|
);
|
|
|
|
const DBG_STRINGS_URI = [
|
|
"devtools/client/locales/debugger.properties",
|
|
// These are used in the AppErrorBoundary component
|
|
"devtools/client/locales/startup.properties",
|
|
"devtools/client/locales/components.properties",
|
|
];
|
|
const L10N = new MultiLocalizationHelper(...DBG_STRINGS_URI);
|
|
|
|
async function getNodeFront(gripOrFront, toolbox) {
|
|
// Given a NodeFront
|
|
if ("actorID" in gripOrFront) {
|
|
return new Promise(resolve => resolve(gripOrFront));
|
|
}
|
|
|
|
const inspectorFront = await toolbox.target.getFront("inspector");
|
|
return inspectorFront.getNodeFrontFromNodeGrip(gripOrFront);
|
|
}
|
|
|
|
class DebuggerPanel {
|
|
constructor(iframeWindow, toolbox, commands) {
|
|
this.panelWin = iframeWindow;
|
|
this.panelWin.L10N = L10N;
|
|
|
|
this.toolbox = toolbox;
|
|
this.commands = commands;
|
|
}
|
|
|
|
async open() {
|
|
// whypaused-* strings are in devtools/shared as they're used in the PausedDebuggerOverlay as well
|
|
const fluentL10n = new FluentL10n();
|
|
await fluentL10n.init(["devtools/shared/debugger-paused-reasons.ftl"]);
|
|
|
|
const {
|
|
actions,
|
|
store,
|
|
selectors,
|
|
client,
|
|
} = await this.panelWin.Debugger.bootstrap({
|
|
commands: this.commands,
|
|
fluentBundles: fluentL10n.getBundles(),
|
|
resourceCommand: this.toolbox.resourceCommand,
|
|
workers: {
|
|
sourceMaps: this.toolbox.sourceMapService,
|
|
evaluationsParser: this.toolbox.parserService,
|
|
},
|
|
panel: this,
|
|
});
|
|
|
|
this._actions = actions;
|
|
this._store = store;
|
|
this._selectors = selectors;
|
|
this._client = client;
|
|
|
|
registerStoreObserver(this._store, this._onDebuggerStateChange.bind(this));
|
|
|
|
return this;
|
|
}
|
|
|
|
_onDebuggerStateChange(state, oldState) {
|
|
const { getCurrentThread } = this._selectors;
|
|
|
|
const currentThreadActorID = getCurrentThread(state);
|
|
if (
|
|
currentThreadActorID &&
|
|
currentThreadActorID !== getCurrentThread(oldState)
|
|
) {
|
|
const threadFront = this.commands.client.getFrontByID(
|
|
currentThreadActorID
|
|
);
|
|
this.toolbox.selectTarget(threadFront?.targetFront.actorID);
|
|
}
|
|
}
|
|
|
|
getVarsForTests() {
|
|
return {
|
|
store: this._store,
|
|
selectors: this._selectors,
|
|
actions: this._actions,
|
|
client: this._client,
|
|
};
|
|
}
|
|
|
|
_getState() {
|
|
return this._store.getState();
|
|
}
|
|
|
|
getToolboxStore() {
|
|
return this.toolbox.store;
|
|
}
|
|
|
|
openLink(url) {
|
|
openContentLink(url);
|
|
}
|
|
|
|
async openConsoleAndEvaluate(input) {
|
|
const { hud } = await this.toolbox.selectTool("webconsole");
|
|
hud.ui.wrapper.dispatchEvaluateExpression(input);
|
|
}
|
|
|
|
async openInspector() {
|
|
this.toolbox.selectTool("inspector");
|
|
}
|
|
|
|
async openElementInInspector(gripOrFront) {
|
|
const onSelectInspector = this.toolbox.selectTool("inspector");
|
|
const onGripNodeToFront = getNodeFront(gripOrFront, this.toolbox);
|
|
|
|
const [front, inspector] = await Promise.all([
|
|
onGripNodeToFront,
|
|
onSelectInspector,
|
|
]);
|
|
|
|
const onInspectorUpdated = inspector.once("inspector-updated");
|
|
const onNodeFrontSet = this.toolbox.selection.setNodeFront(front, {
|
|
reason: "debugger",
|
|
});
|
|
|
|
return Promise.all([onNodeFrontSet, onInspectorUpdated]);
|
|
}
|
|
|
|
highlightDomElement(gripOrFront) {
|
|
if (!this._highlight) {
|
|
const { highlight, unhighlight } = this.toolbox.getHighlighter();
|
|
this._highlight = highlight;
|
|
this._unhighlight = unhighlight;
|
|
}
|
|
|
|
return this._highlight(gripOrFront);
|
|
}
|
|
|
|
unHighlightDomElement() {
|
|
if (!this._unhighlight) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return this._unhighlight();
|
|
}
|
|
|
|
/**
|
|
* Return the Frame Actor ID of the currently selected frame,
|
|
* or null if the debugger isn't paused.
|
|
*/
|
|
getSelectedFrameActorID() {
|
|
const thread = this._selectors.getCurrentThread(this._getState());
|
|
const selectedFrame = this._selectors.getSelectedFrame(
|
|
this._getState(),
|
|
thread
|
|
);
|
|
if (selectedFrame) {
|
|
return selectedFrame.id;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getMappedExpression(expression) {
|
|
return this._actions.getMappedExpression(expression);
|
|
}
|
|
|
|
/**
|
|
* Return the source-mapped variables for the current scope.
|
|
* @returns {{[String]: String} | null} A dictionary mapping original variable names to generated
|
|
* variable names if map scopes is enabled, otherwise null.
|
|
*/
|
|
getMappedVariables() {
|
|
if (!this._selectors.isMapScopesEnabled(this._getState())) {
|
|
return null;
|
|
}
|
|
const thread = this._selectors.getCurrentThread(this._getState());
|
|
return this._selectors.getSelectedScopeMappings(this._getState(), thread);
|
|
}
|
|
|
|
isPaused() {
|
|
const thread = this._selectors.getCurrentThread(this._getState());
|
|
return this._selectors.getIsPaused(this._getState(), thread);
|
|
}
|
|
|
|
selectSourceURL(url, line, column) {
|
|
const cx = this._selectors.getContext(this._getState());
|
|
return this._actions.selectSourceURL(cx, url, { line, column });
|
|
}
|
|
|
|
async selectWorker(workerDescriptorFront) {
|
|
const threadActorID = workerDescriptorFront.threadFront?.actorID;
|
|
|
|
const isThreadAvailable = this._selectors
|
|
.getThreads(this._getState())
|
|
.find(x => x.actor === threadActorID);
|
|
|
|
if (!features.windowlessServiceWorkers) {
|
|
console.error(
|
|
"Selecting a worker needs the pref debugger.features.windowless-service-workers set to true"
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!isThreadAvailable) {
|
|
console.error(`Worker ${threadActorID} is not available for debugging`);
|
|
return;
|
|
}
|
|
|
|
// select worker's thread
|
|
this.selectThread(threadActorID);
|
|
|
|
// select worker's source
|
|
const source = this.getSourceByURL(workerDescriptorFront._url);
|
|
await this.selectSource(source.id, 1, 1);
|
|
}
|
|
|
|
selectThread(threadActorID) {
|
|
const cx = this._selectors.getContext(this._getState());
|
|
this._actions.selectThread(cx, threadActorID);
|
|
}
|
|
|
|
async selectSource(sourceId, line, column) {
|
|
const cx = this._selectors.getContext(this._getState());
|
|
const location = { sourceId, line, column };
|
|
|
|
await this._actions.selectSource(cx, sourceId, location);
|
|
if (this._selectors.hasLogpoint(this._getState(), location)) {
|
|
this._actions.openConditionalPanel(location, true);
|
|
}
|
|
}
|
|
|
|
getSourceActorsForSource(sourceId) {
|
|
return this._selectors.getSourceActorsForSource(this._getState(), sourceId);
|
|
}
|
|
|
|
getSourceByActorId(sourceId) {
|
|
return this._selectors.getSourceByActorId(this._getState(), sourceId);
|
|
}
|
|
|
|
getSourceByURL(sourceURL) {
|
|
return this._selectors.getSourceByURL(this._getState(), sourceURL);
|
|
}
|
|
|
|
getSource(sourceId) {
|
|
return this._selectors.getSource(this._getState(), sourceId);
|
|
}
|
|
|
|
getLocationSource(location) {
|
|
return this._selectors.getLocationSource(this._getState(), location);
|
|
}
|
|
|
|
destroy() {
|
|
this.panelWin.Debugger.destroy();
|
|
this.emit("destroyed");
|
|
}
|
|
}
|
|
|
|
exports.DebuggerPanel = DebuggerPanel;
|