Javascript-浏览器环境模拟

本文最后更新于:2021年7月3日 下午

jsdom

jsdom 是一个纯粹由 javascript 实现的一系列 web标准,特别是 WHATWG 组织制定的DOM和 HTML 标准,用于在 nodejs 中使用
大体上来说,该项目的目标是模拟足够的 Web浏览器子集,以便用于测试和挖掘真实世界的Web应用程序

链接
github地址:https://github.com/jsdom/jsdom

安装

1
npm install jsdom

基本用法

1
2
const jsdom = require("jsdom");
const { JSDOM } = jsdom;

为了使用 jsdom,主要用到jsdom主模块的一个命名导出的 jsdom 构造函数
往构造器传递一个字符串,将会得到一个 jsdom 构造实例对象,这个对象有很多实用的属性,特别是 window 对象:

1
2
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello world"

(注意: jsdom会像浏览器一样解析传递的 HTML,包括隐含的<html>, <head><body> 标记)

生成的对象是 JSDOM类 的一个实例,其中包括 window 对象在内的许多有用的属性和方法
一般来说,它可以用来从“外部”对jsdom进行操作,而这些操作对于普通DOM API来说是不可能的
对于不需要任何功能的简单场景,推荐使用类似的编码模式

1
2
3
const { window } = new JSDOM(`...`);
// or even
const { document } = (new JSDOM(`...`)).window;

简单选项

1
2
3
4
5
6
7
const dom = new JSDOM(``, {
url: "https://example.org/",
referrer: "https://example.com/",
contentType: "text/html",
userAgent: "Mellblomenator/9000",
includeNodeLocations: true
});
  • url
    设置的值可以通过window.locationdocument.URLdocument.documentURI来返回,并会影响文档中相关URL的解析以及获取子资源时使用的同源限制和referrer。默认值为about:blank

  • referrer
    仅仅影响document.referrer的值。默认没有引用(即为空字符串)

  • contentType
    影响document.contentType的值,是按照HTML解析文档还是 XML来解析。它的值如果不是text/htmlXML mime type 值的话将会抛出异常。默认值为text/html

  • userAgent
    影响navigator.userAgent的值以及请求子资源时发送的User-Agent头。默认值为Mozilla / 5.0($ {process.platform})AppleWebKit / 537.36(KHTML,如Gecko)jsdom / $ {jsdomVersion}

  • includeNodeLocations
    保留由HTML解析器生成的位置信息,允许您使用nodeLocation()方法(如下所述)检索它

它还能确保在<script>元素内运行的代码的异常堆栈跟踪中报告的行号是正确的
默认值为false以提供最佳性能,并且不能与XML内容类型一起使用,因为我们的XML解析器不支持位置信息

请注意,url 和referrer在使用之前已经被规范化了,例如
如果你传入https:example.com,jsdom会自动规范化解释为https://example.com/
如果你传递了一个不可解析的URL,该调用将抛出错误。
(URL根据URL标准进行分析和序列化)

执行脚本

jsdom 最强大的功能是它可以在 jsdom 中执行脚本。这些脚本可以修改页面的内容并访问 jsdom 实现的所有Web平台API

但是,这在处理不可信内容时也非常危险
jsdom沙箱并不是万无一失的,在DOM的<script>内部运行的代码如果足够深入,就可以访问 Node.js 环境,从而访问您的计算机
因此,默认情况下,执行嵌入在HTML中的脚本的功能是禁用的:

1
2
3
4
5
const dom = new JSDOM(`<body>
<script>document.body.appendChild(document.createElement("hr"));</script>
</body>`);
// 脚本默认将不能执行:
dom.window.document.body.children.length === 1;

要在页面内启用脚本,可以使用 runScripts:"dangerously"选项:

1
2
3
4
5
const dom = new JSDOM(`<body>
<script>document.body.appendChild(document.createElement("hr"));</script>
</body>`, { runScripts: "dangerously" });
// 脚本将执行并修改 DOM:
dom.window.document.body.children.length === 2;

只有在提供给 jsdom 的代码是你已知道是安全的代码时方可使用它。如果您运行了任意用户提供的或 Internet 上的不可信的 Node.js 代码,可能会危及您的计算机

假如你想通过<script src="">来执行外部脚本,你需要确保已经加载了它们。为此,请添加选项 resources:"usable" 如下所述

请注意,除非runScripts设置为dangerously,否则事件处理程序属性(如<div onclick =“”>)也将不起作用。(但是,事件处理函数属性,比如div.onclick = ...,将无视runScripts参数 并且会起作用)

如果您只是试图从“外部”执行脚本,而不是通过<script>元素(和内联事件处理程序)从内部运行“,则可以使用runScripts: "outside-only"选项,该选项会启用window.eval

1
2
3
4
const window = (new JSDOM(``, { runScripts: "outside-only" })).window;

window.eval(`document.body.innerHTML = "<p>Hello, world!</p>";`);
window.document.body.children.length === 1;

由于性能原因,默认情况下会关闭此功能,但可以安全启用

请注意,我们强烈建议不要试图通过将 jsdom 和 Node 全局环境混合在一起(例如,通过执行global.window = dom.window)来“执行脚本”,然后在 Node 全局环境中执行脚本或测试代码
相反,您应该像对待浏览器一样对待 jsdom,并使用 window.evalrunScripts: "dangerously"来运行需要访问 jsdom 环境内的 DOM 的所有脚本和测试
例如,这可能需要创建一个browserify包作为<script>元素执行 - 就像在浏览器中一样

像网页浏览器一样,jsdom 也具有cookie jar的概念,存储 HTTP cookie 在文档的同一个域上一个 URL,并且没有标记为 HTTP only 的 cookies,可以通过 document.cookie API 来访问。此外,Cookie jar 中的所有 cookie 都会影响子资源的 http 加载

默认情况下,JSDOM构造函数将返回一个带有空cookie的实例。要创建自己的cookie jar并将其传递给jsdom,可以通过以下代码来覆盖默认值

1
2
const cookieJar = new jsdom.CookieJar(store, options);
const dom = new JSDOM(``, { cookieJar });

如果您想要在多个 jsdoms 中共享同一个 cookie jar,或者提前使用特定的值来填充 cookie jar,这将非常有用

Cookie jar 包由 tough-cookie 包提供的。jsdom.CookieJar 构造函数是 tough-cookie cookie jar 的子类,并且默认设置了looseMode:true选项,因为它更符合浏览器的行为方式
如果您想自己使用 tough-cookie 的方法和类,则可以使用 jsdom.toughCookie 模块导出来访问使用 jsdom 打包的 tough-cookie 模块实例

API

见github文档: https://github.com/jsdom/jsdom#jsdom-object-api

abab

jsdom 提供的 window对象 并没有 atobbtoa 这两个处理 base64 字符的方法。使用这个库可以获取到一样的方法,既标准 base64 编码解码方法

链接
github地址:https://github.com/jsdom/abab

安装

1
npm install abab

btoa (base64 encode)

1
2
const { btoa } = require('abab');
btoa('Hello, world!'); // 'SGVsbG8sIHdvcmxkIQ=='

atob (base64 decode)

1
2
const { atob } = require('abab');
atob('SGVsbG8sIHdvcmxkIQ=='); // 'Hello, world!'

有效字符

根据规范,btoa 将接受字符串“仅包含 U + 0000U + 00FF范围内的字符”
如果传递的字符串的字符大于 U + 00FF,则 btoa将返回 null
如果传递的atob字符串不是base64合法的,则它还将返回null
在这两种情况下,当返回null时,规范都要求抛出InvalidCharacterError类型的DOMException

在浏览器中使用

如果希望在浏览器使用

1
2
const atob = require('abab/lib/atob');
const btoa = require('abab/lib/btoa');