buildGeneratedBindingList.js (4436B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 import { clientCommands } from "../../../client/firefox"; 6 7 import { locColumn } from "./locColumn"; 8 import { getOptimizedOutGrip } from "./optimizedOut"; 9 10 export function buildGeneratedBindingList( 11 scopes, 12 generatedAstScopes, 13 thisBinding 14 ) { 15 // The server's binding data doesn't include general 'this' binding 16 // information, so we manually inject the one 'this' binding we have into 17 // the normal binding data we are working with. 18 const frameThisOwner = generatedAstScopes.find( 19 generated => "this" in generated.bindings 20 ); 21 22 let globalScope = null; 23 const clientScopes = []; 24 for (let s = scopes; s; s = s.parent) { 25 const bindings = s.bindings 26 ? Object.assign({}, ...s.bindings.arguments, s.bindings.variables) 27 : {}; 28 29 clientScopes.push(bindings); 30 globalScope = s; 31 } 32 33 const generatedMainScopes = generatedAstScopes.slice(0, -2); 34 const generatedGlobalScopes = generatedAstScopes.slice(-2); 35 36 const clientMainScopes = clientScopes.slice(0, generatedMainScopes.length); 37 const clientGlobalScopes = clientScopes.slice(generatedMainScopes.length); 38 39 // Map the main parsed script body using the nesting hierarchy of the 40 // generated and client scopes. 41 const generatedBindings = generatedMainScopes.reduce((acc, generated, i) => { 42 const bindings = clientMainScopes[i]; 43 44 if (generated === frameThisOwner && thisBinding) { 45 bindings.this = { 46 value: thisBinding, 47 }; 48 } 49 50 for (const name of Object.keys(generated.bindings)) { 51 // If there is no 'this' value, we exclude the binding entirely. 52 // Otherwise it would pass through as found, but "(unscoped)", causing 53 // the search logic to stop with a match. 54 if (name === "this" && !bindings[name]) { 55 continue; 56 } 57 58 const { refs } = generated.bindings[name]; 59 for (const loc of refs) { 60 acc.push({ 61 name, 62 loc, 63 desc: () => Promise.resolve(bindings[name] || null), 64 }); 65 } 66 } 67 return acc; 68 }, []); 69 70 // Bindings in the global/lexical global of the generated code may or 71 // may not be the real global if the generated code is running inside 72 // of an evaled context. To handle this, we just look up the client scope 73 // hierarchy to find the closest binding with that name. 74 for (const generated of generatedGlobalScopes) { 75 for (const name of Object.keys(generated.bindings)) { 76 const { refs } = generated.bindings[name]; 77 const bindings = clientGlobalScopes.find(b => name in b); 78 79 for (const loc of refs) { 80 if (bindings) { 81 generatedBindings.push({ 82 name, 83 loc, 84 desc: () => Promise.resolve(bindings[name]), 85 }); 86 } else { 87 const globalGrip = globalScope?.object; 88 if (globalGrip) { 89 // Should always exist, just checking to keep Flow happy. 90 91 generatedBindings.push({ 92 name, 93 loc, 94 desc: async () => { 95 const objectFront = 96 clientCommands.createObjectFront(globalGrip); 97 return (await objectFront.getProperty(name)).descriptor; 98 }, 99 }); 100 } 101 } 102 } 103 } 104 } 105 106 // Sort so we can binary-search. 107 return sortBindings(generatedBindings); 108 } 109 110 export function buildFakeBindingList(generatedAstScopes) { 111 // TODO if possible, inject real bindings for the global scope 112 const generatedBindings = generatedAstScopes.reduce((acc, generated) => { 113 for (const name of Object.keys(generated.bindings)) { 114 if (name === "this") { 115 continue; 116 } 117 const { refs } = generated.bindings[name]; 118 for (const loc of refs) { 119 acc.push({ 120 name, 121 loc, 122 desc: () => Promise.resolve(getOptimizedOutGrip()), 123 }); 124 } 125 } 126 return acc; 127 }, []); 128 return sortBindings(generatedBindings); 129 } 130 131 function sortBindings(generatedBindings) { 132 return generatedBindings.sort((a, b) => { 133 const aStart = a.loc.start; 134 const bStart = b.loc.start; 135 136 if (aStart.line === bStart.line) { 137 return locColumn(aStart) - locColumn(bStart); 138 } 139 return aStart.line - bStart.line; 140 }); 141 }