/* 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 {
getSettledSourceTextContent,
isMapScopesEnabled,
getSelectedFrame,
getGeneratedFrameScope,
getOriginalFrameScope,
getFirstSourceActorForGeneratedSource,
} from
"../../selectors/index";
import {
loadOriginalSourceText,
loadGeneratedSourceText,
} from
"../sources/loadSourceText";
import { validateSelectedFrame } from
"../../utils/context";
import { PROMISE } from
"../utils/middleware/promise";
import { log } from
"../../utils/log";
import { buildMappedScopes } from
"../../utils/pause/mapScopes/index";
import { isFulfilled } from
"../../utils/async-value";
import { getMappedLocation } from
"../../utils/source-maps";
const expressionRegex = /\bfp\(\)/g;
export async
function buildOriginalScopes(
selectedFrame,
client,
generatedScopes
) {
if (!selectedFrame.originalVariables) {
throw new TypeError(
"(frame.originalVariables: XScopeVariables)");
}
const originalVariables = selectedFrame.originalVariables;
const frameBase = originalVariables.frameBase ||
"";
const inputs = [];
for (let i = 0; i < originalVariables.vars.length; i++) {
const { expr } = originalVariables.vars[i];
const expression = expr
? expr.replace(expressionRegex, frameBase)
:
"void 0";
inputs[i] = expression;
}
const results = await client.evaluateExpressions(inputs, {
frameId: selectedFrame.id,
});
const variables = {};
for (let i = 0; i < originalVariables.vars.length; i++) {
const { name } = originalVariables.vars[i];
variables[name] = { value: results[i].result };
}
const bindings = {
arguments: [],
variables,
};
const { actor } = await generatedScopes;
const scope = {
type:
"function",
scopeKind:
"",
actor,
bindings,
parent:
null,
function:
null,
block:
null,
};
return {
mappings: {},
scope,
};
}
export
function toggleMapScopes() {
return async
function ({ dispatch, getState }) {
if (isMapScopesEnabled(getState())) {
dispatch({ type:
"TOGGLE_MAP_SCOPES", mapScopes:
false });
return;
}
dispatch({ type:
"TOGGLE_MAP_SCOPES", mapScopes:
true });
// Ignore the call if there is no selected frame (we are not paused?)
const state = getState();
const selectedFrame = getSelectedFrame(state);
if (!selectedFrame) {
return;
}
if (getOriginalFrameScope(getState(), selectedFrame)) {
return;
}
// Also ignore the call if we didn't fetch the scopes for the selected frame
const scopes = getGeneratedFrameScope(getState(), selectedFrame);
if (!scopes) {
return;
}
dispatch(mapScopes(selectedFrame, Promise.resolve(scopes.scope)));
};
}
export
function mapScopes(selectedFrame, scopes) {
return async
function (thunkArgs) {
const { getState, dispatch, client } = thunkArgs;
await dispatch({
type:
"MAP_SCOPES",
selectedFrame,
[PROMISE]: (async
function () {
if (selectedFrame.isOriginal && selectedFrame.originalVariables) {
return buildOriginalScopes(selectedFrame, client, scopes);
}
// getMappedScopes is only specific to the sources where we map the variables
// in scope and so only need a thread context. Assert that we are on the same thread
// before retrieving a thread context.
validateSelectedFrame(getState(), selectedFrame);
return dispatch(getMappedScopes(scopes, selectedFrame));
})(),
});
};
}
/**
* Get scopes mapped for a precise location.
*
* @param {Promise} scopes
* Can be null. Result of Commands.js's client.getFrameScopes
* @param {Objects locations
* Frame object, or custom object with 'location' and 'generatedLocation' attributes.
*/
export
function getMappedScopes(scopes, locations) {
return async
function (thunkArgs) {
const { getState, dispatch } = thunkArgs;
const generatedSource = locations.generatedLocation.source;
const source = locations.location.source;
if (
!isMapScopesEnabled(getState()) ||
!source ||
!generatedSource ||
generatedSource.isWasm ||
source.isPrettyPrinted ||
!source.isOriginal
) {
return null;
}
// Load source text for the original source
await dispatch(loadOriginalSourceText(source));
const generatedSourceActor = getFirstSourceActorForGeneratedSource(
getState(),
generatedSource.id
);
// Also load source text for its corresponding generated source
await dispatch(loadGeneratedSourceText(generatedSourceActor));
try {
const content =
// load original source text content
getSettledSourceTextContent(getState(), locations.location);
return await buildMappedScopes(
source,
content && isFulfilled(content)
? content.value
: { type:
"text", value:
"", contentType: undefined },
locations,
await scopes,
thunkArgs
);
}
catch (e) {
log(e);
return null;
}
};
}
/**
* Used to map variables used within conditional and log breakpoints.
*/
export
function getMappedScopesForLocation(location) {
return async
function (thunkArgs) {
const { dispatch } = thunkArgs;
const mappedLocation = await getMappedLocation(location, thunkArgs);
return dispatch(getMappedScopes(
null, mappedLocation));
};
}