/* 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
// $FlowIgnore
import { objectInspector } from "devtools/client/shared/components/reps/index";
import { getBindingVariables } from "./getVariables";
import { getFramePopVariables, getThisVariable } from "./utils";
import { simplifyDisplayName } from "../../pause/frames";
import type { Frame, Why, Scope } from "../../../types";
import type { NamedValue } from "./types";
export type RenderableScope = {
type: $ElementType,
scopeKind: $ElementType,
actor: $ElementType,
bindings: $ElementType,
parent: ?RenderableScope,
object?: ?Object,
function?: ?{
displayName: string,
},
block?: ?{
displayName: string,
},
};
const {
utils: {
node: { NODE_TYPES },
},
} = objectInspector;
function getScopeTitle(type, scope: RenderableScope): string | void {
if (type === "block" && scope.block && scope.block.displayName) {
return scope.block.displayName;
}
if (type === "function" && scope.function) {
return scope.function.displayName
? simplifyDisplayName(scope.function.displayName)
: L10N.getStr("anonymousFunction");
}
return L10N.getStr("scopes.block");
}
export function getScope(
scope: RenderableScope,
selectedFrame: Frame,
frameScopes: RenderableScope,
why: Why,
scopeIndex: number
): ?NamedValue {
const { type, actor } = scope;
const isLocalScope = scope.actor === frameScopes.actor;
const key = `${actor}-${scopeIndex}`;
if (type === "function" || type === "block") {
const { bindings } = scope;
let vars = getBindingVariables(bindings, key);
// show exception, return, and this variables in innermost scope
if (isLocalScope) {
vars = vars.concat(getFramePopVariables(why, key));
let thisDesc_ = selectedFrame.this;
if (bindings && "this" in bindings) {
// The presence of "this" means we're rendering a "this" binding
// generated from mapScopes and this can override the binding
// provided by the current frame.
thisDesc_ = bindings.this ? bindings.this.value : null;
}
const this_ = getThisVariable(thisDesc_, key);
if (this_) {
vars.push(this_);
}
}
if (vars?.length) {
const title = getScopeTitle(type, scope) || "";
vars.sort((a, b) => a.name.localeCompare(b.name));
return {
name: title,
path: key,
contents: vars,
type: NODE_TYPES.BLOCK,
};
}
} else if (type === "object" && scope.object) {
let value = scope.object;
// If this is the global window scope, mark it as such so that it will
// preview Window: Global instead of Window: Window
if (value.class === "Window") {
value = { ...scope.object, displayClass: "Global" };
}
return {
name: scope.object.class,
path: key,
contents: { value },
};
}
return null;
}
export function mergeScopes(
scope: RenderableScope,
parentScope: RenderableScope,
item: NamedValue,
parentItem: NamedValue
): NamedValue | void {
if (scope.scopeKind == "function lexical" && parentScope.type == "function") {
const contents = (item.contents: any).concat(parentItem.contents);
contents.sort((a, b) => a.name.localeCompare(b.name));
return {
name: parentItem.name,
path: parentItem.path,
contents,
type: NODE_TYPES.BLOCK,
};
}
}