README.md
  1  <h1 align="center">
  2      <img width="100" height="100" src="logo.svg" alt=""><br>
  3      jsdom
  4  </h1>
  5  
  6  jsdom is a pure-JavaScript implementation of many web standards, notably the WHATWG [DOM](https://dom.spec.whatwg.org/) and [HTML](https://html.spec.whatwg.org/multipage/) Standards, for use with Node.js. In general, the goal of the project is to emulate enough of a subset of a web browser to be useful for testing and scraping real-world web applications.
  7  
  8  The latest versions of jsdom require Node.js v10 or newer. (Versions of jsdom below v16 still work with previous Node.js versions, but are unsupported.)
  9  
 10  ## Basic usage
 11  
 12  ```js
 13  const jsdom = require("jsdom");
 14  const { JSDOM } = jsdom;
 15  ```
 16  
 17  To use jsdom, you will primarily use the `JSDOM` constructor, which is a named export of the jsdom main module. Pass the constructor a string. You will get back a `JSDOM` object, which has a number of useful properties, notably `window`:
 18  
 19  ```js
 20  const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
 21  console.log(dom.window.document.querySelector("p").textContent); // "Hello world"
 22  ```
 23  
 24  (Note that jsdom will parse the HTML you pass it just like a browser does, including implied `<html>`, `<head>`, and `<body>` tags.)
 25  
 26  The resulting object is an instance of the `JSDOM` class, which contains a number of useful properties and methods besides `window`. In general, it can be used to act on the jsdom from the "outside," doing things that are not possible with the normal DOM APIs. For simple cases, where you don't need any of this functionality, we recommend a coding pattern like
 27  
 28  ```js
 29  const { window } = new JSDOM(`...`);
 30  // or even
 31  const { document } = (new JSDOM(`...`)).window;
 32  ```
 33  
 34  Full documentation on everything you can do with the `JSDOM` class is below, in the section "`JSDOM` Object API".
 35  
 36  ## Customizing jsdom
 37  
 38  The `JSDOM` constructor accepts a second parameter which can be used to customize your jsdom in the following ways.
 39  
 40  ### Simple options
 41  
 42  ```js
 43  const dom = new JSDOM(``, {
 44    url: "https://example.org/",
 45    referrer: "https://example.com/",
 46    contentType: "text/html",
 47    includeNodeLocations: true,
 48    storageQuota: 10000000
 49  });
 50  ```
 51  
 52  - `url` sets the value returned by `window.location`, `document.URL`, and `document.documentURI`, and affects things like resolution of relative URLs within the document and the same-origin restrictions and referrer used while fetching subresources. It defaults to `"about:blank"`.
 53  - `referrer` just affects the value read from `document.referrer`. It defaults to no referrer (which reflects as the empty string).
 54  - `contentType` affects the value read from `document.contentType`, as well as how the document is parsed: as HTML or as XML. Values that are not a [HTML mime type](https://mimesniff.spec.whatwg.org/#html-mime-type) or an [XML mime type](https://mimesniff.spec.whatwg.org/#xml-mime-type) will throw. It defaults to `"text/html"`. If a `charset` parameter is present, it can affect [binary data processing](#encoding-sniffing).
 55  - `includeNodeLocations` preserves the location info produced by the HTML parser, allowing you to retrieve it with the `nodeLocation()` method (described below). It also ensures that line numbers reported in exception stack traces for code running inside `<script>` elements are correct. It defaults to `false` to give the best performance, and cannot be used with an XML content type since our XML parser does not support location info.
 56  - `storageQuota` is the maximum size in code units for the separate storage areas used by `localStorage` and `sessionStorage`. Attempts to store data larger than this limit will cause a `DOMException` to be thrown. By default, it is set to 5,000,000 code units per origin, as inspired by the HTML specification.
 57  
 58  Note that both `url` and `referrer` are canonicalized before they're used, so e.g. if you pass in `"https:example.com"`, jsdom will interpret that as if you had given `"https://example.com/"`. If you pass an unparseable URL, the call will throw. (URLs are parsed and serialized according to the [URL Standard](https://url.spec.whatwg.org/).)
 59  
 60  ### Executing scripts
 61  
 62  jsdom's most powerful ability is that it can execute scripts inside the jsdom. These scripts can modify the content of the page and access all the web platform APIs jsdom implements.
 63  
 64  However, this is also highly dangerous when dealing with untrusted content. The jsdom sandbox is not foolproof, and code running inside the DOM's `<script>`s can, if it tries hard enough, get access to the Node.js environment, and thus to your machine. As such, the ability to execute scripts embedded in the HTML is disabled by default:
 65  
 66  ```js
 67  const dom = new JSDOM(`<body>
 68    <script>document.body.appendChild(document.createElement("hr"));</script>
 69  </body>`);
 70  
 71  // The script will not be executed, by default:
 72  dom.window.document.body.children.length === 1;
 73  ```
 74  
 75  To enable executing scripts inside the page, you can use the `runScripts: "dangerously"` option:
 76  
 77  ```js
 78  const dom = new JSDOM(`<body>
 79    <script>document.body.appendChild(document.createElement("hr"));</script>
 80  </body>`, { runScripts: "dangerously" });
 81  
 82  // The script will be executed and modify the DOM:
 83  dom.window.document.body.children.length === 2;
 84  ```
 85  
 86  Again we emphasize to only use this when feeding jsdom code you know is safe. If you use it on arbitrary user-supplied code, or code from the Internet, you are effectively running untrusted Node.js code, and your machine could be compromised.
 87  
 88  If you want to execute _external_ scripts, included via `<script src="">`, you'll also need to ensure that they load them. To do this, add the option `resources: "usable"` [as described below](#loading-subresources). (You'll likely also want to set the `url` option, for the reasons discussed there.)
 89  
 90  Event handler attributes, like `<div onclick="">`, are also governed by this setting; they will not function unless `runScripts` is set to `"dangerously"`. (However, event handler _properties_, like `div.onclick = ...`, will function regardless of `runScripts`.)
 91  
 92  If you are simply trying to execute script "from the outside", instead of letting `<script>` elements and event handlers attributes run "from the inside", you can use the `runScripts: "outside-only"` option, which enables fresh copies of all the JavaScript spec-provided globals to be installed on `window`. This includes things like `window.Array`, `window.Promise`, etc. It also, notably, includes `window.eval`, which allows running scripts, but with the jsdom `window` as the global:
 93  
 94  ```js
 95  const { window } = new JSDOM(``, { runScripts: "outside-only" });
 96  
 97  window.eval(`document.body.innerHTML = "<p>Hello, world!</p>";`);
 98  window.document.body.children.length === 1;
 99  ```
100  
101  This is turned off by default for performance reasons, but is safe to enable.
102  
103  (Note that in the default configuration, without setting `runScripts`, the values of `window.Array`, `window.eval`, etc. will be the same as those provided by the outer Node.js environment. That is, `window.eval === eval` will hold, so `window.eval` will not run scripts in a useful way.)
104  
105  We strongly advise against trying to "execute scripts" by mashing together the jsdom and Node global environments (e.g. by doing `global.window = dom.window`), and then executing scripts or test code inside the Node global environment. Instead, you should treat jsdom like you would a browser, and run all scripts and tests that need access to a DOM inside the jsdom environment, using `window.eval` or `runScripts: "dangerously"`. This might require, for example, creating a browserify bundle to execute as a `<script>` element—just like you would in a browser.
106  
107  Finally, for advanced use cases you can use the `dom.getInternalVMContext()` method, documented below.
108  
109  ### Pretending to be a visual browser
110  
111  jsdom does not have the capability to render visual content, and will act like a headless browser by default. It provides hints to web pages through APIs such as `document.hidden` that their content is not visible.
112  
113  When the `pretendToBeVisual` option is set to `true`, jsdom will pretend that it is rendering and displaying content. It does this by:
114  
115  * Changing `document.hidden` to return `false` instead of `true`
116  * Changing `document.visibilityState` to return `"visible"` instead of `"prerender"`
117  * Enabling `window.requestAnimationFrame()` and `window.cancelAnimationFrame()` methods, which otherwise do not exist
118  
119  ```js
120  const window = (new JSDOM(``, { pretendToBeVisual: true })).window;
121  
122  window.requestAnimationFrame(timestamp => {
123    console.log(timestamp > 0);
124  });
125  ```
126  
127  Note that jsdom still [does not do any layout or rendering](#unimplemented-parts-of-the-web-platform), so this is really just about _pretending_ to be visual, not about implementing the parts of the platform a real, visual web browser would implement.
128  
129  ### Loading subresources
130  
131  #### Basic options
132  
133  By default, jsdom will not load any subresources such as scripts, stylesheets, images, or iframes. If you'd like jsdom to load such resources, you can pass the `resources: "usable"` option, which will load all usable resources. Those are:
134  
135  * Frames and iframes, via `<frame>` and `<iframe>`
136  * Stylesheets, via `<link rel="stylesheet">`
137  * Scripts, via `<script>`, but only if `runScripts: "dangerously"` is also set
138  * Images, via `<img>`, but only if the `canvas` npm package is also installed (see "[Canvas Support](#canvas-support)" below)
139  
140  When attempting to load resources, recall that the default value for the `url` option is `"about:blank"`, which means that any resources included via relative URLs will fail to load. (The result of trying to parse the URL `/something` against the URL `about:blank` is an error.) So, you'll likely want to set a non-default value for the `url` option in those cases, or use one of the [convenience APIs](#convenience-apis) that do so automatically.
141  
142  #### Advanced configuration
143  
144  _This resource loader system is new as of jsdom v12.0.0, and we'd love your feedback on whether it meets your needs and how easy it is to use. Please file an issue to discuss!_
145  
146  To more fully customize jsdom's resource-loading behavior, you can pass an instance of the `ResourceLoader` class as the `resources` option value:
147  
148  ```js
149  const resourceLoader = new jsdom.ResourceLoader({
150    proxy: "http://127.0.0.1:9001",
151    strictSSL: false,
152    userAgent: "Mellblomenator/9000",
153  });
154  const dom = new JSDOM(``, { resources: resourceLoader });
155  ```
156  
157  The three options to the `ResourceLoader` constructor are:
158  
159  - `proxy` is the address of an HTTP proxy to be used.
160  - `strictSSL` can be set to false to disable the requirement that SSL certificates be valid.
161  - `userAgent` affects the `User-Agent` header sent, and thus the resulting value for `navigator.userAgent`. It defaults to <code>\`Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}\`</code>.
162  
163  You can further customize resource fetching by subclassing `ResourceLoader` and overriding the `fetch()` method. For example, here is a version that only returns results for requests to a trusted origin:
164  
165  ```js
166  class CustomResourceLoader extends jsdom.ResourceLoader {
167    fetch(url, options) {
168      // Override the contents of this script to do something unusual.
169      if (url === "https://example.com/some-specific-script.js") {
170        return Promise.resolve(Buffer.from("window.someGlobal = 5;"));
171      }
172  
173      return super.fetch(url, options);
174    }
175  }
176  ```
177  
178  jsdom will call your custom resource loader's `fetch()` method whenever it encounters a "usable" resource, per the above section. The method takes a URL string, as well as a few options which you should pass through unmodified if calling `super.fetch()`. It must return a promise for a Node.js `Buffer` object, or return `null` if the resource is intentionally not to be loaded. In general, most cases will want to delegate to `super.fetch()`, as shown.
179  
180  One of the options you will receive in `fetch()` will be the element (if applicable) that is fetching a resource.
181  
182  ```js
183  class CustomResourceLoader extends jsdom.ResourceLoader {
184    fetch(url, options) {
185      if (options.element) {
186        console.log(`Element ${options.element.localName} is requesting the url ${url}`);
187      }
188  
189      return super.fetch(url, options);
190    }
191  }
192  ```
193  
194  ### Virtual consoles
195  
196  Like web browsers, jsdom has the concept of a "console". This records both information directly sent from the page, via scripts executing inside the document, as well as information from the jsdom implementation itself. We call the user-controllable console a "virtual console", to distinguish it from the Node.js `console` API and from the inside-the-page `window.console` API.
197  
198  By default, the `JSDOM` constructor will return an instance with a virtual console that forwards all its output to the Node.js console. To create your own virtual console and pass it to jsdom, you can override this default by doing
199  
200  ```js
201  const virtualConsole = new jsdom.VirtualConsole();
202  const dom = new JSDOM(``, { virtualConsole });
203  ```
204  
205  Code like this will create a virtual console with no behavior. You can give it behavior by adding event listeners for all the possible console methods:
206  
207  ```js
208  virtualConsole.on("error", () => { ... });
209  virtualConsole.on("warn", () => { ... });
210  virtualConsole.on("info", () => { ... });
211  virtualConsole.on("dir", () => { ... });
212  // ... etc. See https://console.spec.whatwg.org/#logging
213  ```
214  
215  (Note that it is probably best to set up these event listeners *before* calling `new JSDOM()`, since errors or console-invoking script might occur during parsing.)
216  
217  If you simply want to redirect the virtual console output to another console, like the default Node.js one, you can do
218  
219  ```js
220  virtualConsole.sendTo(console);
221  ```
222  
223  There is also a special event, `"jsdomError"`, which will fire with error objects to report errors from jsdom itself. This is similar to how error messages often show up in web browser consoles, even if they are not initiated by `console.error`. So far, the following errors are output this way:
224  
225  - Errors loading or parsing subresources (scripts, stylesheets, frames, and iframes)
226  - Script execution errors that are not handled by a window `onerror` event handler that returns `true` or calls `event.preventDefault()`
227  - Not-implemented errors resulting from calls to methods, like `window.alert`, which jsdom does not implement, but installs anyway for web compatibility
228  
229  If you're using `sendTo(c)` to send errors to `c`, by default it will call `c.error(errorStack[, errorDetail])` with information from `"jsdomError"` events. If you'd prefer to maintain a strict one-to-one mapping of events to method calls, and perhaps handle `"jsdomError"`s yourself, then you can do
230  
231  ```js
232  virtualConsole.sendTo(c, { omitJSDOMErrors: true });
233  ```
234  
235  ### Cookie jars
236  
237  Like web browsers, jsdom has the concept of a cookie jar, storing HTTP cookies. Cookies that have a URL on the same domain as the document, and are not marked HTTP-only, are accessible via the `document.cookie` API. Additionally, all cookies in the cookie jar will impact the fetching of subresources.
238  
239  By default, the `JSDOM` constructor will return an instance with an empty cookie jar. To create your own cookie jar and pass it to jsdom, you can override this default by doing
240  
241  ```js
242  const cookieJar = new jsdom.CookieJar(store, options);
243  const dom = new JSDOM(``, { cookieJar });
244  ```
245  
246  This is mostly useful if you want to share the same cookie jar among multiple jsdoms, or prime the cookie jar with certain values ahead of time.
247  
248  Cookie jars are provided by the [tough-cookie](https://www.npmjs.com/package/tough-cookie) package. The `jsdom.CookieJar` constructor is a subclass of the tough-cookie cookie jar which by default sets the `looseMode: true` option, since that [matches better how browsers behave](https://github.com/whatwg/html/issues/804). If you want to use tough-cookie's utilities and classes yourself, you can use the `jsdom.toughCookie` module export to get access to the tough-cookie module instance packaged with jsdom.
249  
250  ### Intervening before parsing
251  
252  jsdom allows you to intervene in the creation of a jsdom very early: after the `Window` and `Document` objects are created, but before any HTML is parsed to populate the document with nodes:
253  
254  ```js
255  const dom = new JSDOM(`<p>Hello</p>`, {
256    beforeParse(window) {
257      window.document.childNodes.length === 0;
258      window.someCoolAPI = () => { /* ... */ };
259    }
260  });
261  ```
262  
263  This is especially useful if you are wanting to modify the environment in some way, for example adding shims for web platform APIs jsdom does not support.
264  
265  ## `JSDOM` object API
266  
267  Once you have constructed a `JSDOM` object, it will have the following useful capabilities:
268  
269  ### Properties
270  
271  The property `window` retrieves the `Window` object that was created for you.
272  
273  The properties `virtualConsole` and `cookieJar` reflect the options you pass in, or the defaults created for you if nothing was passed in for those options.
274  
275  ### Serializing the document with `serialize()`
276  
277  The `serialize()` method will return the [HTML serialization](https://html.spec.whatwg.org/#html-fragment-serialisation-algorithm) of the document, including the doctype:
278  
279  ```js
280  const dom = new JSDOM(`<!DOCTYPE html>hello`);
281  
282  dom.serialize() === "<!DOCTYPE html><html><head></head><body>hello</body></html>";
283  
284  // Contrast with:
285  dom.window.document.documentElement.outerHTML === "<html><head></head><body>hello</body></html>";
286  ```
287  
288  ### Getting the source location of a node with `nodeLocation(node)`
289  
290  The `nodeLocation()` method will find where a DOM node is within the source document, returning the [parse5 location info](https://www.npmjs.com/package/parse5#options-locationinfo) for the node:
291  
292  ```js
293  const dom = new JSDOM(
294    `<p>Hello
295      <img src="foo.jpg">
296    </p>`,
297    { includeNodeLocations: true }
298  );
299  
300  const document = dom.window.document;
301  const bodyEl = document.body; // implicitly created
302  const pEl = document.querySelector("p");
303  const textNode = pEl.firstChild;
304  const imgEl = document.querySelector("img");
305  
306  console.log(dom.nodeLocation(bodyEl));   // null; it's not in the source
307  console.log(dom.nodeLocation(pEl));      // { startOffset: 0, endOffset: 39, startTag: ..., endTag: ... }
308  console.log(dom.nodeLocation(textNode)); // { startOffset: 3, endOffset: 13 }
309  console.log(dom.nodeLocation(imgEl));    // { startOffset: 13, endOffset: 32 }
310  ```
311  
312  Note that this feature only works if you have set the `includeNodeLocations` option; node locations are off by default for performance reasons.
313  
314  ### Interfacing with the Node.js `vm` module using `getInternalVMContext()`
315  
316  The built-in [`vm`](https://nodejs.org/api/vm.html) module of Node.js is what underpins jsdom's script-running magic. Some advanced use cases, like pre-compiling a script and then running it multiple times, benefit from using the `vm` module directly with a jsdom-created `Window`.
317  
318  To get access to the [contextified global object](https://nodejs.org/api/vm.html#vm_what_does_it_mean_to_contextify_an_object), suitable for use with the `vm` APIs, you can use the `getInternalVMContext()` method:
319  
320  ```js
321  const { Script } = require("vm");
322  
323  const dom = new JSDOM(``, { runScripts: "outside-only" });
324  const script = new Script(`
325    if (!this.ran) {
326      this.ran = 0;
327    }
328  
329    ++this.ran;
330  `);
331  
332  const vmContext = dom.getInternalVMContext();
333  
334  script.runInContext(vmContext);
335  script.runInContext(vmContext);
336  script.runInContext(vmContext);
337  
338  console.assert(dom.window.ran === 3);
339  ```
340  
341  This is somewhat-advanced functionality, and we advise sticking to normal DOM APIs (such as `window.eval()` or `document.createElement("script")`) unless you have very specific needs.
342  
343  Note that this method will throw an exception if the `JSDOM` instance was created without `runScripts` set, or if you are [using jsdom in a web browser](#running-jsdom-inside-a-web-browser).
344  
345  ### Reconfiguring the jsdom with `reconfigure(settings)`
346  
347  The `top` property on `window` is marked `[Unforgeable]` in the spec, meaning it is a non-configurable own property and thus cannot be overridden or shadowed by normal code running inside the jsdom, even using `Object.defineProperty`.
348  
349  Similarly, at present jsdom does not handle navigation (such as setting `window.location.href = "https://example.com/"`); doing so will cause the virtual console to emit a `"jsdomError"` explaining that this feature is not implemented, and nothing will change: there will be no new `Window` or `Document` object, and the existing `window`'s `location` object will still have all the same property values.
350  
351  However, if you're acting from outside the window, e.g. in some test framework that creates jsdoms, you can override one or both of these using the special `reconfigure()` method:
352  
353  ```js
354  const dom = new JSDOM();
355  
356  dom.window.top === dom.window;
357  dom.window.location.href === "about:blank";
358  
359  dom.reconfigure({ windowTop: myFakeTopForTesting, url: "https://example.com/" });
360  
361  dom.window.top === myFakeTopForTesting;
362  dom.window.location.href === "https://example.com/";
363  ```
364  
365  Note that changing the jsdom's URL will impact all APIs that return the current document URL, such as `window.location`, `document.URL`, and `document.documentURI`, as well as the resolution of relative URLs within the document, and the same-origin checks and referrer used while fetching subresources. It will not, however, perform navigation to the contents of that URL; the contents of the DOM will remain unchanged, and no new instances of `Window`, `Document`, etc. will be created.
366  
367  ## Convenience APIs
368  
369  ### `fromURL()`
370  
371  In addition to the `JSDOM` constructor itself, jsdom provides a promise-returning factory method for constructing a jsdom from a URL:
372  
373  ```js
374  JSDOM.fromURL("https://example.com/", options).then(dom => {
375    console.log(dom.serialize());
376  });
377  ```
378  
379  The returned promise will fulfill with a `JSDOM` instance if the URL is valid and the request is successful. Any redirects will be followed to their ultimate destination.
380  
381  The options provided to `fromURL()` are similar to those provided to the `JSDOM` constructor, with the following additional restrictions and consequences:
382  
383  - The `url` and `contentType` options cannot be provided.
384  - The `referrer` option is used as the HTTP `Referer` request header of the initial request.
385  - The `resources` option also affects the initial request; this is useful if you want to, for example, configure a proxy (see above).
386  - The resulting jsdom's URL, content type, and referrer are determined from the response.
387  - Any cookies set via HTTP `Set-Cookie` response headers are stored in the jsdom's cookie jar. Similarly, any cookies already in a supplied cookie jar are sent as HTTP `Cookie` request headers.
388  
389  ### `fromFile()`
390  
391  Similar to `fromURL()`, jsdom also provides a `fromFile()` factory method for constructing a jsdom from a filename:
392  
393  ```js
394  JSDOM.fromFile("stuff.html", options).then(dom => {
395    console.log(dom.serialize());
396  });
397  ```
398  
399  The returned promise will fulfill with a `JSDOM` instance if the given file can be opened. As usual in Node.js APIs, the filename is given relative to the current working directory.
400  
401  The options provided to `fromFile()` are similar to those provided to the `JSDOM` constructor, with the following additional defaults:
402  
403  - The `url` option will default to a file URL corresponding to the given filename, instead of to `"about:blank"`.
404  - The `contentType` option will default to `"application/xhtml+xml"` if the given filename ends in `.xht`, `.xhtml`, or `.xml`; otherwise it will continue to default to `"text/html"`.
405  
406  ### `fragment()`
407  
408  For the very simplest of cases, you might not need a whole `JSDOM` instance with all its associated power. You might not even need a `Window` or `Document`! Instead, you just need to parse some HTML, and get a DOM object you can manipulate. For that, we have `fragment()`, which creates a `DocumentFragment` from a given string:
409  
410  ```js
411  const frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`);
412  
413  frag.childNodes.length === 2;
414  frag.querySelector("strong").textContent === "Hi!";
415  // etc.
416  ```
417  
418  Here `frag` is a [`DocumentFragment`](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment) instance, whose contents are created by parsing the provided string. The parsing is done using a `<template>` element, so you can include any element there (including ones with weird parsing rules like `<td>`). It's also important to note that the resulting `DocumentFragment` will not have [an associated browsing context](https://html.spec.whatwg.org/multipage/#concept-document-bc): that is, elements' `ownerDocument` will have a null `defaultView` property, resources will not load, etc.
419  
420  All invocations of the `fragment()` factory result in `DocumentFragment`s that share the same template owner `Document`. This allows many calls to `fragment()` with no extra overhead. But it also means that calls to `fragment()` cannot be customized with any options.
421  
422  Note that serialization is not as easy with `DocumentFragment`s as it is with full `JSDOM` objects. If you need to serialize your DOM, you should probably use the `JSDOM` constructor more directly. But for the special case of a fragment containing a single element, it's pretty easy to do through normal means:
423  
424  ```js
425  const frag = JSDOM.fragment(`<p>Hello</p>`);
426  console.log(frag.firstChild.outerHTML); // logs "<p>Hello</p>"
427  ```
428  
429  ## Other noteworthy features
430  
431  ### Canvas support
432  
433  jsdom includes support for using the [`canvas`](https://www.npmjs.com/package/canvas) package to extend any `<canvas>` elements with the canvas API. To make this work, you need to include `canvas` as a dependency in your project, as a peer of `jsdom`. If jsdom can find the `canvas` package, it will use it, but if it's not present, then `<canvas>` elements will behave like `<div>`s. Since jsdom v13, version 2.x of `canvas` is required; version 1.x is no longer supported.
434  
435  ### Encoding sniffing
436  
437  In addition to supplying a string, the `JSDOM` constructor can also be supplied binary data, in the form of a Node.js [`Buffer`](https://nodejs.org/docs/latest/api/buffer.html) or a standard JavaScript binary data type like `ArrayBuffer`, `Uint8Array`, `DataView`, etc. When this is done, jsdom will [sniff the encoding](https://html.spec.whatwg.org/multipage/syntax.html#encoding-sniffing-algorithm) from the supplied bytes, scanning for `<meta charset>` tags just like a browser does.
438  
439  If the supplied `contentType` option contains a `charset` parameter, that encoding will override the sniffed encoding—unless a UTF-8 or UTF-16 BOM is present, in which case those take precedence. (Again, this is just like a browser.)
440  
441  This encoding sniffing also applies to `JSDOM.fromFile()` and `JSDOM.fromURL()`. In the latter case, any `Content-Type` headers sent with the response will take priority, in the same fashion as the constructor's `contentType` option.
442  
443  Note that in many cases supplying bytes in this fashion can be better than supplying a string. For example, if you attempt to use Node.js's `buffer.toString("utf-8")` API, Node.js will not strip any leading BOMs. If you then give this string to jsdom, it will interpret it verbatim, leaving the BOM intact. But jsdom's binary data decoding code will strip leading BOMs, just like a browser; in such cases, supplying `buffer` directly will give the desired result.
444  
445  ### Closing down a jsdom
446  
447  Timers in the jsdom (set by `window.setTimeout()` or `window.setInterval()`) will, by definition, execute code in the future in the context of the window. Since there is no way to execute code in the future without keeping the process alive, outstanding jsdom timers will keep your Node.js process alive. Similarly, since there is no way to execute code in the context of an object without keeping that object alive, outstanding jsdom timers will prevent garbage collection of the window on which they are scheduled.
448  
449  If you want to be sure to shut down a jsdom window, use `window.close()`, which will terminate all running timers (and also remove any event listeners on the window and document).
450  
451  ### Running jsdom inside a web browser
452  
453  jsdom has some support for being run inside a web browser, using [browserify](https://browserify.org/). That is, inside a web browser, you can use a browserified jsdom to create an entirely self-contained set of plain JavaScript objects which look and act much like the browser's existing DOM objects, while being entirely independent of them. "Virtual DOM", indeed!
454  
455  jsdom's primary target is still Node.js, and so we use language features that are only present in recent Node.js versions (namely, Node.js v8+). Thus, older browsers will likely not work. (Even transpilation will not help: we use `Proxy`s extensively throughout the jsdom codebase.)
456  
457  Notably, jsdom works well inside a web worker. The original contributor, [@lawnsea](https://github.com/lawnsea/), who made this possible, has [published a paper](https://pdfs.semanticscholar.org/47f0/6bb6607a975500a30e9e52d7c9fbc0034e27.pdf) about his project which uses this capability.
458  
459  Not everything works perfectly when running jsdom inside a web browser. Sometimes that is because of fundamental limitations (such as not having filesystem access), but sometimes it is simply because we haven't spent enough time making the appropriate small tweaks. Bug reports are certainly welcome.
460  
461  ### Debugging the DOM using Chrome Devtools
462  
463  As of Node.js v6 you can debug programs using Chrome Devtools. See the [official documentation](https://nodejs.org/en/docs/inspector/) for how to get started.
464  
465  By default jsdom elements are formatted as plain old JS objects in the console. To make it easier to debug, you can use [jsdom-devtools-formatter](https://github.com/viddo/jsdom-devtools-formatter), which lets you inspect them like real DOM elements.
466  
467  ## Caveats
468  
469  ### Asynchronous script loading
470  
471  People often have trouble with asynchronous script loading when using jsdom. Many pages load scripts asynchronously, but there is no way to tell when they're done doing so, and thus when it's a good time to run your code and inspect the resulting DOM structure. This is a fundamental limitation; we cannot predict what scripts on the web page will do, and so cannot tell you when they are done loading more scripts.
472  
473  This can be worked around in a few ways. The best way, if you control the page in question, is to use whatever mechanisms are given by the script loader to detect when loading is done. For example, if you're using a module loader like RequireJS, the code could look like:
474  
475  ```js
476  // On the Node.js side:
477  const window = (new JSDOM(...)).window;
478  window.onModulesLoaded = () => {
479    console.log("ready to roll!");
480  };
481  ```
482  
483  ```html
484  <!-- Inside the HTML you supply to jsdom -->
485  <script>
486  requirejs(["entry-module"], () => {
487    window.onModulesLoaded();
488  });
489  </script>
490  ```
491  
492  If you do not control the page, you could try workarounds such as polling for the presence of a specific element.
493  
494  For more details, see the discussion in [#640](https://github.com/jsdom/jsdom/issues/640), especially [@matthewkastor](https://github.com/matthewkastor)'s [insightful comment](https://github.com/jsdom/jsdom/issues/640#issuecomment-22216965).
495  
496  ### Unimplemented parts of the web platform
497  
498  Although we enjoy adding new features to jsdom and keeping it up to date with the latest web specs, it has many missing APIs. Please feel free to file an issue for anything missing, but we're a small and busy team, so a pull request might work even better.
499  
500  Beyond just features that we haven't gotten to yet, there are two major features that are currently outside the scope of jsdom. These are:
501  
502  - **Navigation**: the ability to change the global object, and all other objects, when clicking a link or assigning `location.href` or similar.
503  - **Layout**: the ability to calculate where elements will be visually laid out as a result of CSS, which impacts methods like `getBoundingClientRects()` or properties like `offsetTop`.
504  
505  Currently jsdom has dummy behaviors for some aspects of these features, such as sending a "not implemented" `"jsdomError"` to the virtual console for navigation, or returning zeros for many layout-related properties. Often you can work around these limitations in your code, e.g. by creating new `JSDOM` instances for each page you "navigate" to during a crawl, or using `Object.defineProperty()` to change what various layout-related getters and methods return.
506  
507  Note that other tools in the same space, such as PhantomJS, do support these features. On the wiki, we have a more complete writeup about [jsdom vs. PhantomJS](https://github.com/jsdom/jsdom/wiki/jsdom-vs.-PhantomJS).
508  
509  ## Supporting jsdom
510  
511  jsdom is a community-driven project maintained by a team of [volunteers](https://github.com/orgs/jsdom/people). You could support jsdom by:
512  
513  - [Getting professional support for jsdom](https://tidelift.com/subscription/pkg/npm-jsdom?utm_source=npm-jsdom&utm_medium=referral&utm_campaign=readme) as part of a Tidelift subscription. Tidelift helps making open source sustainable for us while giving teams assurances for maintenance, licensing, and security.
514  - [Contributing](https://github.com/jsdom/jsdom/blob/master/Contributing.md) directly to the project.
515  
516  ## Getting help
517  
518  If you need help with jsdom, please feel free to use any of the following venues:
519  
520  - The [mailing list](https://groups.google.com/group/jsdom) (best for "how do I" questions)
521  - The [issue tracker](https://github.com/jsdom/jsdom/issues) (best for bug reports)
522  - The Matrix room: [#jsdom:matrix.org](https://matrix.to/#/#jsdom:matrix.org)