/* 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 . */
// @flow
import { sortBy } from "lodash";
import {
getOriginalFrameScope,
getGeneratedFrameScope,
getInlinePreviews,
getSelectedLocation,
} from "../../selectors";
import { features } from "../../utils/prefs";
import { validateThreadContext } from "../../utils/context";
import type { OriginalScope } from "../../utils/pause/mapScopes";
import type { ThreadContext, Frame, Scope, Preview } from "../../types";
import type { ThunkArgs } from "../types";
import type { SourceScope } from "../../workers/parser/getScopes";
// We need to display all variables in the current functional scope so
// include all data for block scopes until the first functional scope
function getLocalScopeLevels(originalAstScopes: SourceScope[]): number {
let levels = 0;
while (
originalAstScopes[levels] &&
originalAstScopes[levels].type === "block"
) {
levels++;
}
return levels;
}
export function generateInlinePreview(cx: ThreadContext, frame: ?Frame) {
return async function({ dispatch, getState, parser, client }: ThunkArgs) {
if (!frame || !features.inlinePreview) {
return;
}
const { thread } = cx;
// Avoid regenerating inline previews when we already have preview data
if (getInlinePreviews(getState(), thread, frame.id)) {
return;
}
const originalFrameScopes = getOriginalFrameScope(
getState(),
thread,
frame.location.sourceId,
frame.id
);
const generatedFrameScopes = getGeneratedFrameScope(
getState(),
thread,
frame.id
);
let scopes: ?OriginalScope | Scope | null =
originalFrameScopes?.scope || generatedFrameScopes?.scope;
if (!scopes || !scopes.bindings) {
return;
}
// It's important to use selectedLocation, because we don't know
// if we'll be viewing the original or generated frame location
const selectedLocation = getSelectedLocation(getState());
if (!selectedLocation) {
return;
}
const originalAstScopes = await parser.getScopes(selectedLocation);
validateThreadContext(getState(), cx);
if (!originalAstScopes) {
return;
}
const allPreviews = [];
const pausedOnLine: number = selectedLocation.line;
const levels: number = getLocalScopeLevels(originalAstScopes);
for (
let curLevel = 0;
curLevel <= levels && scopes && scopes.bindings;
curLevel++
) {
const bindings = { ...scopes.bindings.variables };
scopes.bindings.arguments.forEach(argument => {
Object.keys(argument).forEach(key => {
bindings[key] = argument[key];
});
});
const previewBindings = Object.keys(bindings).map(async name => {
// We want to show values of properties of objects only and not
// function calls on other data types like someArr.forEach etc..
let properties = null;
const objectFront = bindings[name].value;
if (objectFront.actorID && objectFront.class === "Object") {
properties = await client.loadObjectProperties({
name,
path: name,
contents: { value: objectFront },
});
}
const previewsFromBindings: Array = getBindingValues(
originalAstScopes,
pausedOnLine,
name,
bindings[name].value,
curLevel,
properties
);
allPreviews.push(...previewsFromBindings);
});
await Promise.all(previewBindings);
scopes = scopes.parent;
}
const previews = {};
const sortedPreviews = sortBy(allPreviews, ["line", "column"]);
sortedPreviews.forEach(preview => {
const { line } = preview;
if (!previews[line]) {
previews[line] = [preview];
} else {
previews[line].push(preview);
}
});
return dispatch({
type: "ADD_INLINE_PREVIEW",
thread,
frame,
previews,
});
};
}
function getBindingValues(
originalAstScopes: Object,
pausedOnLine: number,
name: string,
value: any,
curLevel: number,
properties: Array