乐于分享
好东西不私藏

【node源码-8】node源码接入jsdom

【node源码-8】node源码接入jsdom

完成了jsdom 的底层自动使用。遇到自定义函数比如navigator_userActivation_get 等jsdom缺少的,可以自己定义。

先看一下结果:

代码如下:

const vm require('vm');const { JSDOM } = require('jsdom');const html = `<!DOCTYPE html><html><head>  <title>My Custom Page</title></head><body>  <div id="app">Hello World</div></body></html>`;const dom new JSDOM(html, {  url'http://localhost:3000',  pretendToBeVisualtrue,  resources'usable'});// 2. 配置 KhBox 并注册 jsdomKhBox.setTraceLog(true);  // 开启日志KhBox.setJsdomWindow(dom.window);// jsdom对象传到c层// 3. 注册自定义的 envFuncs(jsdom 不支持的 API)KhBox.envFuncs = {  // navigator.userActivation(jsdom 不支持)- getter  navigator_userActivation_get: function() {    console.log('[CUSTOM] navigator_userActivation_get called');    return {      hasBeenActive: true,      isActive: false    };  },};console.log('[MAIN] jsdom and envFuncs registered'); const sandbox = vm.createContext({  console: console   // 普通的vm用法});// 4. 在 vm 沙箱里执行代码const code = `  console.log('[VM] === 基本测试 ===');  console.log('[VM] document.title:', document.title);  document.title = 'Modified in VM';  console.log('[VM] new title:', document.title);  console.log('[VM] navigator.userAgent:', navigator.userAgent);  console.log('[VM] location.href:', location.href);  document.cookie = 'test=123';  console.log('[VM] document.cookie:', document.cookie);  console.log('[VM] window.innerWidth:', window.innerWidth);  try {    localStorage.setItem('foo''bar');    console.log('[VM] localStorage.getItem:', localStorage.getItem('foo'));  } catch(e) {    console.log('[VM] localStorage error:', e.message);  }  console.log('[VM] === 测试完成 ===');`;const code3=`var a={"age":33};console.log(a.age)`try {  vm.runInContext(code, sandbox);  vm.runInContext(code3, sandbox);  console.log('[MAIN] VM execution successful');catch(err) {  console.error('[MAIN] VM execution failed:', err.message);  console.error(err.stack);}console.log('[MAIN] Demo finished');

日志如下:

D:\Code\C\node\out\Release>node.exe demo.js[KhBox] Trace logging enabled[KhBox] jsdom window set in Environment[KhBoxjsdom window registered via setJsdomWindow()[MAIN] jsdom and envFuncs registered[VM] === 基本测试 ===[KhBox] Found in jsdom: document[KhBox] {vm-jsdom|get} caller:[Object] -> prop:[document] -> result:[object: Document][KhBox] Created ProxyObject for: document[KhBox] {proxy|get} caller:[Object] -> prop:[title] -> result:[string"My Custom Page"][VM] document.title: My Custom Page[KhBox] Found in jsdom: document[KhBox] {vm-jsdom|get} caller:[Object] -> prop:[document] -> result:[object: Document][KhBox] Created ProxyObject for: document[KhBox] {proxy|set} caller:[Object] -> prop:[title] -> value:[string"Modified in VM"][KhBox] Found in jsdom: document[KhBox] {vm-jsdom|get} caller:[Object] -> prop:[document] -> result:[object: Document][KhBox] Created ProxyObject for: document[KhBox] {proxy|get} caller:[Object] -> prop:[title] -> result:[string"My Custom Page"][VMnew title: My Custom Page[KhBox] Found in jsdom: navigator[KhBox] {vm-jsdom|get} caller:[Object] -> prop:[navigator] -> result:[object: Navigator][KhBox] Created ProxyObject for: navigator[KhBox] {proxy|get} caller:[Object] -> prop:[userAgent] -> result:[string"Mozilla/5.0 (win32) AppleWebKit/537.36 (KHTML, lik..."][VM] navigator.userAgent: Mozilla/5.0 (win32) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/27.4.0[KhBox] Found in jsdom: location[KhBox] {vm-jsdom|get} caller:[Object] -> prop:[location] -> result:[object: Location][KhBox] Created ProxyObject for: location[KhBox] {proxy|get} caller:[Object] -> prop:[href] -> result:[string"http://localhost:3000/"][VM] location.href: http://localhost:3000/[KhBox] Found in jsdom: document[KhBox] {vm-jsdom|get} caller:[Object] -> prop:[document] -> result:[object: Document][KhBox] Created ProxyObject for: document[KhBox] {proxy|set} caller:[Object] -> prop:[cookie] -> value:[string"test=123"][KhBox] Found in jsdom: document[KhBox] {vm-jsdom|get} caller:[Object] -> prop:[document] -> result:[object: Document][KhBox] Created ProxyObject for: document[KhBox] {proxy|get} caller:[Object] -> prop:[cookie] -> result:[string""][VM] document.cookie:[KhBox] Found in jsdom: window[KhBox] {vm-jsdom|get} caller:[Object] -> prop:[window] -> result:[object: Window][KhBox] Created ProxyObject for: window[KhBox] {proxy|get} caller:[Object] -> prop:[innerWidth] -> result:[number: 1024.000000][VM] window.innerWidth: 1024[KhBox] Found in jsdom: localStorage[KhBox] {vm-jsdom|get} caller:[Object] -> prop:[localStorage] -> result:[object: Object][KhBox] Created ProxyObject for: localStorage[KhBox] {proxy|get} caller:[Object] -> prop:[setItem] -> result:[function: setItem][KhBox] localStorage->has (null)

成功了一点,运行

KhBox.setJsdomWindow(dom.window);

后,c层会自动去调用jsdom的方法,显示get ,set   。不过日志有点杂乱。

next:

  1. 自己实现的方法KhBox.envFuncs 应该优先于jsdom。如果不写 就是默认jsdom,或者抛异常方法不存在。

  2. 让自己写的方法生效。

  3. 目前只在PropertyGetterCallback内部进行了一点修改,其他的set等 应该也得修改,比如自己没有处理过的proxyobj,被直接set ,就需要在PropertySetterCallback 中进行输出。

接下来看一下大概思路。


一、整体流程总览

  1. JS:demo.js 创建 jsdom 和 KhBox,注册 dom.window、配置 khBox.envFuncs
  2. JS:在 vm 沙箱里执行脚本,访问 document / navigator / ...
  3. C++:node_contextify.cc 里的全局属性拦截(PropertyGetterCallback)接管访问
    • 先看沙箱本地对象
    • 再走 koohai:GetFromJsdom / GetFromKhBox
  4. C++:如果是 jsdom 对象(比如 document),用 CreateProxyObject 包一层带拦截器的 ProxyObject
  5. JS:在 khBox.envFuncs 里定义的函数实际执行业务逻辑,或者让 jsdom 完成默认行为

二、JS 侧:jsdom 与 KhBox 的准备工作

在 demo.js 中,做了三件核心事情:

  1. 创建 jsdom:
  • new JSDOM(html, { url, pretendToBeVisual, resources })
  1. 把 jsdom 的 window 注册给 C++:
  • 使用全局 KhBox 单例:khBox.setJsdomWindow(dom.window)
  1. 给 khBox.envFuncs 挂自己的函数:
  • 比如:
    • navigator_userActivation_get
    • location_href_get / location_href_set
    • document_cookie_get / document_cookie_set
    • localStorage_setItem / localStorage_getItem
    • Node_appendChild

这些函数是后面 C++ 通过 GetFromKhBox 和 CallKhBoxFunction 找到并执行的核心。


三、node_contextify.cc:VM 全局属性拦截与三级查找

VM 沙箱里的 document / navigator 首次访问时,会触发 node::contextify::ContextifyContext::PropertyGetterCallback(在这里做了增强)。

这一步做的是“全局属性三级查找”:

  1. 沙箱本地对象(sandbox 自身)
  2. global_proxy(Node 原有的 global 对象代理)
  3. koohai 扩展:
    • 先 GetFromJsdom(env, context, name, &result)
    • 再 GetFromKhBox(env, context, name, &result)

同时,如果从 jsdom 拿到的是对象(如 window/document/navigator),这里会调用:

  • CreateProxyObject(env, result, name)

直接把 jsdom 原始对象包成一个带完整 trap 的 ProxyObject,以便后面深层访问也能被拦截。


四、node_koohai_interceptor.cc:jsdom 与 KhBox.envFuncs 的查找

1. GetFromJsdom:从 jsdom 取原始对象

大致流程:

  1. 通过 Environment 拿出之前设置的 jsdom window
    • 在 KhBox::SetJsdomWindow 里,把 dom.window 存到了 env 上
  2. 根据 property 名字决定到底返回 window 上哪个对象:
    • 比如 document → window.document
    • navigator → window.navigator
    • location → window.location

在日志中,能看到类似:

  • [KhBox] Found in jsdom: document

这说明 GetFromJsdom 找到了对应对象。

2. GetFromKhBox:从 khBox.envFuncs 取补丁函数

还未生效


五、node_koohai_proxy.cc:ProxyObject 深层拦截与命名规则

当 node_contextify.cc 从 jsdom 拿到 document 这类对象时,不是直接返回,而是调用:

  • CreateProxyObject(env, target, "document")

CreateProxyObject 负责:

  1. 创建一个 ObjectTemplate,设置 NamedPropertyHandlerConfiguration
    • get:ProxyPropertyGetter
    • set:ProxyPropertySetter
    • has / deleteProperty / ownKeys / defineProperty / getOwnPropertyDescriptor 等
  2. 实例化对象 proxy
  3. 把“真实目标对象”和“基名”塞进 internal fields:
    • kTargetObject:指向 jsdom 的原始对象
    • kPropertyName:比如 "document""navigator"

后续对 proxy.xxx 的操作,就都会走到这些 C++ 回调里。

1. Getter:ProxyPropertyGetter

逻辑简化为:

  1. 从 internal fields 读出:
    • base_name:比如 "navigator"
    • property:比如 "userActivation"
  2. 拼出 envFuncs 查找 key:
    • lookup_key = base_name + "_" + property + "_get"
    • 如:"navigator_userActivation_get""document_cookie_get""location_href_get"
  3. 调 CallKhBoxFunction(env, lookup_key, ...)
    • 记录日志:谁 get 了谁,结果是什么
    • 把结果返回给 JS
    • 如果 khBox.envFuncs[lookup_key] 存在并返回值,则:
    • 否则回退到 jsdom 原始对象:target_obj->Get(context, property)

2. Setter:ProxyPropertySetter

逻辑类似:

  1. 一样拿 base_name 和 property
  2. 拼 key:

例子:

- `lookup_key = base_name + "_" + property + "_set"`- `document.cookie = ...` → `"document_cookie_set"`- `location.href = ...` → `"location_href_set"`
  1. 记录 set 日志(谁设置了谁,设置了什么)
  2. CallKhBoxFunction(env, lookup_key, 1, argv, ...)
    • 在 khBox.envFuncs 里控制最终行为

其他的has 等以及一些属性定义的后续完善。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 【node源码-8】node源码接入jsdom

评论 抢沙发

6 + 1 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮