Skip to content

Commit

Permalink
fix differing eval behavior between eval and scopedEval
Browse files Browse the repository at this point in the history
  • Loading branch information
swimmadude66 committed Jul 30, 2024
1 parent 9e6ce27 commit 8eaecb6
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 13 deletions.
2 changes: 1 addition & 1 deletion packages/vm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tectonica/vm",
"version": "1.0.1",
"version": "1.0.2",
"module": "dist/esm/index.js",
"types": "dist/types/index.d.ts",
"exports": {
Expand Down
47 changes: 35 additions & 12 deletions packages/vm/src/vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
type QuickJSAsyncVariant,
type QuickJSSyncVariant,
Scope,
QuickJSHandle,
} from 'quickjs-emscripten'
import { ModuleOptions, VMInitOpts } from './types'
import { Marshaller } from './marshal'
Expand All @@ -31,6 +32,8 @@ export class VMManager {
resolve: () => void
}

private vmContextMapKey: string = generateMagicToken({ prefix: '__context_map_', suffix: '_key' })

constructor() {}

async init({ debug, async, runtimeOpts, contextOpts, variant, variantOptions }: VMInitOpts = {}) {
Expand Down Expand Up @@ -127,20 +130,21 @@ export class VMManager {
scopedEval(code: string, contextVars: Record<string, any>) {
const vm = this.requireVM()
const scope = new Scope()
const scopeArgName = generateMagicToken({ prefix: '__context_args' })
const scopedFunc = scope.manage(
vm.unwrapResult(
vm.evalCode(`(${scopeArgName} = {}) => {
const contextId = generateMagicToken({ prefix: '__scoped_context_' })
try {
const contextHandle = scope.manage(this.marshaller.marshal(contextVars))
const contextMapHandle = scope.manage(this.getOrCreateContextMap(vm))
vm.setProp(contextMapHandle, contextId, contextHandle)
const resultHandle = scope.manage(
vm.unwrapResult(
vm.evalCode(`
{
const { ${Object.keys(contextVars).join(', ')} } = ${scopeArgName}
return (${code})
}
}`)
const { ${Object.keys(contextVars).join(', ')} } = globalThis[Symbol.for('${this.vmContextMapKey}')]['${contextId}']
${code}
}`)
)
)
)
const contextHandle = scope.manage(this.marshaller.marshal(contextVars))
try {
const resultHandle = scope.manage(vm.unwrapResult(vm.callFunction(scopedFunc, vm.global, contextHandle)))
vm.setProp(contextMapHandle, contextId, vm.undefined)
return this.marshaller.unmarshal(resultHandle)
} finally {
scope.dispose()
Expand Down Expand Up @@ -168,4 +172,23 @@ export class VMManager {
}
return variant
}

private getOrCreateContextMap(vm: QuickJSContext): QuickJSHandle {
const contextMapHandle = vm.unwrapResult(
vm.evalCode(`
{
(() => {
const cacheSymbol = Symbol.for('${this.vmContextMapKey}')
let cacheMap = globalThis[cacheSymbol]
if (!cacheMap) {
cacheMap = {}
Object.defineProperty(globalThis, cacheSymbol, { value: cacheMap, enumerable: false, configurable: false, writable: false})
}
return cacheMap
})()
}
`)
)
return contextMapHandle
}
}
22 changes: 22 additions & 0 deletions packages/vm/tests/vm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,27 @@ describe('VMManager', () => {
expect(domProxy).not.to.be.null
expect(typeof domProxy).to.equal('object')
})

it('handles scoped dom proxying', () => {
const btn = document.createElement('button')
const context = { btn, counter: { val: 0 } }
const btnProxy = vm.scopedEval(
`
function run() {
const b = btn
b.addEventListener('click', () => ++counter.val)
return b
}
run()
`,
context
)
expect(btnProxy).not.to.be.null
expect(typeof btnProxy).to.equal('object')
btn.click()
expect(context.counter.val).to.equal(1)
btnProxy.click()
expect(context.counter.val).to.equal(2)
})
})
})

0 comments on commit 8eaecb6

Please sign in to comment.