/* 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 . */ 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; }); }