Source code

Revision control

Copy as Markdown

Other Tools

/* 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 { clientCommands } from "../../../client/firefox";
import { locColumn } from "./locColumn";
import { getOptimizedOutGrip } from "./optimizedOut";
export function buildGeneratedBindingList(
scopes,
generatedAstScopes,
thisBinding
) {
// The server's binding data doesn't include general 'this' binding
// information, so we manually inject the one 'this' binding we have into
// the normal binding data we are working with.
const frameThisOwner = generatedAstScopes.find(
generated => "this" in generated.bindings
);
let globalScope = null;
const clientScopes = [];
for (let s = scopes; s; s = s.parent) {
const bindings = s.bindings
? Object.assign({}, ...s.bindings.arguments, s.bindings.variables)
: {};
clientScopes.push(bindings);
globalScope = s;
}
const generatedMainScopes = generatedAstScopes.slice(0, -2);
const generatedGlobalScopes = generatedAstScopes.slice(-2);
const clientMainScopes = clientScopes.slice(0, generatedMainScopes.length);
const clientGlobalScopes = clientScopes.slice(generatedMainScopes.length);
// Map the main parsed script body using the nesting hierarchy of the
// generated and client scopes.
const generatedBindings = generatedMainScopes.reduce((acc, generated, i) => {
const bindings = clientMainScopes[i];
if (generated === frameThisOwner && thisBinding) {
bindings.this = {
value: thisBinding,
};
}
for (const name of Object.keys(generated.bindings)) {
// If there is no 'this' value, we exclude the binding entirely.
// Otherwise it would pass through as found, but "(unscoped)", causing
// the search logic to stop with a match.
if (name === "this" && !bindings[name]) {
continue;
}
const { refs } = generated.bindings[name];
for (const loc of refs) {
acc.push({
name,
loc,
desc: () => Promise.resolve(bindings[name] || null),
});
}
}
return acc;
}, []);
// Bindings in the global/lexical global of the generated code may or
// may not be the real global if the generated code is running inside
// of an evaled context. To handle this, we just look up the client scope
// hierarchy to find the closest binding with that name.
for (const generated of generatedGlobalScopes) {
for (const name of Object.keys(generated.bindings)) {
const { refs } = generated.bindings[name];
const bindings = clientGlobalScopes.find(b => name in b);
for (const loc of refs) {
if (bindings) {
generatedBindings.push({
name,
loc,
desc: () => Promise.resolve(bindings[name]),
});
} else {
const globalGrip = globalScope?.object;
if (globalGrip) {
// Should always exist, just checking to keep Flow happy.
generatedBindings.push({
name,
loc,
desc: async () => {
const objectFront =
clientCommands.createObjectFront(globalGrip);
return (await objectFront.getProperty(name)).descriptor;
},
});
}
}
}
}
}
// Sort so we can binary-search.
return sortBindings(generatedBindings);
}
export function buildFakeBindingList(generatedAstScopes) {
// TODO if possible, inject real bindings for the global scope
const generatedBindings = generatedAstScopes.reduce((acc, generated) => {
for (const name of Object.keys(generated.bindings)) {
if (name === "this") {
continue;
}
const { refs } = generated.bindings[name];
for (const loc of refs) {
acc.push({
name,
loc,
desc: () => Promise.resolve(getOptimizedOutGrip()),
});
}
}
return acc;
}, []);
return sortBindings(generatedBindings);
}
function sortBindings(generatedBindings) {
return generatedBindings.sort((a, b) => {
const aStart = a.loc.start;
const bStart = b.loc.start;
if (aStart.line === bStart.line) {
return locColumn(aStart) - locColumn(bStart);
}
return aStart.line - bStart.line;
});
}