jsdom 与 worker-dom 学习记录

Last Edited Time
Jun 10, 2023 03:56 AM
date
Jan 9, 2023
slug
jsdom-and-worker-dom
status
Published
tags
W3C
JavaScript
DOM
summary
jsdom 与 worker-dom 学习以及改造成脱离 Node 的库
type
Post

简介

目前想在一个纯 JS 沙箱环境中使用 DOM 相关的操作,发现有 worker-domjsdom 两个库,其中:
  • worker-dom 是在 web-worker 内实现的 DOM API 标准,主要用于提升 JS 代码运行速度
  • jsdom 是纯 javascript 与 node 的实现的 DOM 与 HTML 标准,主要用于测试与抓包

为什么不选 worker-dom?

原因

  • 原因一: worker-dom 本质上还是使用的是浏览器的 DOM 标准,在 worker 中操作 DOM 并发送消息到宿主,由宿主代为执行,并没有从零开发一个 DOM 标准
  • 原因二:由于 worker 的通信是异步的订阅发布式,导致 DOM 的同步方法操作在 worker 中会变成异步操作,例如 getBoundingClientRect 方法

使用实例(Hello World)

  • index.html
    • notion image
  • hello-world.js
    • notion image
运行效果
notion image

宿主核心代码

  • 在执行 MainThread.upgradeElement 的时候,会先请求代码字符串,然后执行 install 方法
  • install 方法会创建 workerContext 用于执行 worker 代码,并监听来自 worker 的消息
    • notion image
  • 其中消息格式是被简化过的,具体格式见调试日志
    • notion image
  • 宿主环境接收到消息之后通过统一的 mutatorContext 来对宿主环境的 DOM 进行操作

jsdom it is

实现原理

总的来说,Web 平台的类(像 WindowNodeLocationCSSStyleSheet)可以被 Web IDL 语言来描述,Web IDL 抽象并减少了很多样板代码,比如类型转换、参数校验、属性映射等。
综上所述,大多数 jsdom 里的 Web 类由以下两个文件实现
  • IDL 文件,比如 Attr.webidl ,或多或少直接从过饭中搬来
  • 实现文件,比如 Attr-impl.js,包括相关的实现逻辑
最后通过 yarn prepare 命令生成一个公共 API 文件(例如 Attr.js),里面包括了从 Web IDL 衍生出来的样板代码,引用到实现文件里的重要逻辑。然后把所有的功能通过 lib/jsdom/living/interfaces.js 文件暴露到 jsdom windows 里面。

Web IDL 示例详解(History)

  • History.webidl 文件从 WHATWG HTML Spec History Interface 拷贝而来(有微调),如下:
    • enum ScrollRestoration { "auto", "manual" };
      
      [Exposed=Window]
      interface History {
        readonly attribute unsigned long length;
        attribute ScrollRestoration scrollRestoration;
        readonly attribute any state;
        undefined go(optional long delta = 0);
        undefined back();
        undefined forward();
        undefined pushState(any data, DOMString unused, optional USVString? url = null);
        undefined replaceState(any data, DOMString unused, optional USVString? url = null);
      };
  • History-impl.js 实现核心的逻辑
    • notion image
  • 自动生成 History.js 文件
    • notion image
  • 其中 install 方法会被 interface.jsinstallInterface 方法引用到
    • notion image
  • installInterface 方法会在创建 window 的时候被执行
    • notion image
      notion image

快速开始

  1. 下载代码并用 yarn 安装 NPM 包
  1. 执行构建命令 yarn prepare,该命令会执行以下逻辑
    1. 执行 ./scripts/webidl/convert.js 命令,生成 lib/jsdom/living/generated 目录与文件,改文件会被lib/jsdom/living/interfaces.js 引用到
    2. 执行 yarn generate-js-globals 命令,运行 vm 并通过 Object.getOwnPropertyDescriptors(this) 命令获取所有的全局变量属性,并写到 lib/jsdom/browser/js-globals.json 文件里面,后续会用于生成 windowInstance 对象
  1. 初始化 web-platform-tests 源码与环境
    1. 初始化 git submodule,这一步有可能要挂代理,切等待时间较长
    2. 设置 python3 环境与 host ,详情见 Running Tests from the Local System
    3. 由于 wpt 的执行需要 Python3.6 或以上版本,而且 node child_process 的执行不支持 bash alias 和 bash function,需要把 start-wpt-serverrun-tuwpt.jspython 改为 python3
  1. 执行测试命令

测试方式

  1. web 平台功能测试 yarn test-wpt ,可以指定测试执行 yarn test-wpt --fgrep dom/events
  1. 自定义的 web 平台功能测试 yarn test-tuwpt,可以指定测试运行yarn test-tuwpt --fgrep domparsin
  1. 与 wpt 无关的 API 测试 yarn test-api,使用 mocha 合 chai 编写
  1. 在浏览器中直接运行 yarn test-browser-*,使用 karma 编写

运行 WPT 测试

  • 运行 yarn test-wpt --fgrep dom/events 结果
    • notion image

在 worker 里运行 tests

  • 刚开始看到 yarn test-browser-worker 十分开心,感觉 jsdom 一定能在 worker 里面运行,然而在看了 karma 配置后发现大意了,wpt 相关测试都不能在 iframe 或 worker 内运行
    • notion image
  • 不过至少可以确定打包后的代码在经过 browserify 转换后可以在 worker 与 iframe 中运行不报错
  • 运行 yarn test-browser-worker 结果如下
    • notion image
 

Reference