Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

jsdom

Package Overview
Dependencies
Maintainers
3
Versions
264
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jsdom - npm Package Compare versions

Comparing version 9.12.0 to 10.0.0

lib/api.js

2

lib/jsdom/browser/documentfeatures.js

@@ -11,3 +11,3 @@ "use strict";

FetchExternalResources: ["script", "link"], // omitted by default: "frame"
ProcessExternalResources: ["script"], // omitted by default: "frame", "iframe"
ProcessExternalResources: ["script"],
SkipExternalResources: false

@@ -14,0 +14,0 @@ };

@@ -42,3 +42,3 @@ "use strict";

appendHtmlToDocument(html, element) {
appendHtmlToDocument(html, documentImpl) {
if (typeof html !== "string") {

@@ -48,3 +48,4 @@ html = String(html);

return this["_parseWith" + this.parserType](html, false, element);
const options = documentImpl._parseOptions;
return this["_parseWith" + this.parserType](html, false, documentImpl, options);
}

@@ -74,3 +75,3 @@

_parseWithparse5v1(html, fragment, element) {
_parseWithparse5v1(html, fragment, element, options) {
if (this.parsingMode === "xml") {

@@ -87,3 +88,3 @@ throw new Error("Can't parse XML with parse5, please use htmlparser2 instead.");

} else {
const instance = new this.parser.Parser(htmlparser2Adapter, { locationInfo: true });
const instance = new this.parser.Parser(htmlparser2Adapter, options);
dom = instance.parse(html);

@@ -190,5 +191,3 @@ }

while ((result = entityMatcher.exec(dt))) {
// TODO Node v6 const [, name, value] = result;
const name = result[1];
const value = result[2];
const [, name, value] = result;
if (!(name in parser.ENTITIES)) {

@@ -195,0 +194,0 @@ parser.ENTITIES[name] = value;

@@ -126,7 +126,7 @@ "use strict";

// request's wrapper and monkey patch it with our jar.
function wrapCookieJarForRequest(cookieJar) {
exports.wrapCookieJarForRequest = cookieJar => {
const jarWrapper = request.jar();
jarWrapper._jar = cookieJar;
return jarWrapper;
}
};

@@ -167,3 +167,3 @@ function fetch(urlObj, options, callback) {

gzip: true,
jar: wrapCookieJarForRequest(options.cookieJar),
jar: exports.wrapCookieJarForRequest(options.cookieJar),
encoding: null,

@@ -170,0 +170,0 @@ headers: {

@@ -89,2 +89,3 @@ "use strict";

proxy: options.proxy,
parseOptions: options.parseOptions,
defaultView: this._globalProxy,

@@ -108,2 +109,3 @@ global: this

// TODO NEWAPI can remove this
if (options.virtualConsole) {

@@ -283,5 +285,4 @@ if (options.virtualConsole instanceof VirtualConsole) {

function wrapConsoleMethod(method) {
return function () {
const args = Array.prototype.slice.call(arguments);
window._virtualConsole.emit.apply(window._virtualConsole, [method].concat(args));
return (...args) => {
window._virtualConsole.emit(method, ...args);
};

@@ -288,0 +289,0 @@ }

@@ -141,3 +141,3 @@ "use strict";

return attributeList.indexOf(A) !== -1;
return attributeList.includes(A);
};

@@ -144,0 +144,0 @@

@@ -7,4 +7,3 @@ "use strict";

constructor(args, privateData) {
const type = args[0]; // TODO: Replace with destructuring
const eventInitDict = args[1] || EventInit.convert(undefined);
const [type, eventInitDict = EventInit.convert(undefined)] = args;

@@ -11,0 +10,0 @@ this.type = type;

@@ -521,2 +521,14 @@ "use strict";

Object.defineProperty(Document.prototype, "dir", {
get() {
return this[impl].dir;
},
set(V) {
V = conversions["DOMString"](V);
this[impl].dir = V;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Document.prototype, "body", {

@@ -523,0 +535,0 @@ get() {

@@ -92,8 +92,7 @@ "use strict";

get() {
const value = this.getAttribute("dir");
return value === null ? "" : value;
return this[impl].dir;
},
set(V) {
V = conversions["DOMString"](V);
this.setAttribute("dir", V);
this[impl].dir = V;
},

@@ -100,0 +99,0 @@ enumerable: true,

@@ -93,3 +93,3 @@ "use strict";

// Don't override existing named ones
if (keys.indexOf(value) !== -1) {
if (keys.includes(value)) {
return;

@@ -96,0 +96,0 @@ }

@@ -62,4 +62,3 @@ "use strict";

forEach(callback) {
const thisArg = arguments[1]; // TODO Node v6: use default arguments
forEach(callback, thisArg = undefined) {
let values = Array.from(this);

@@ -66,0 +65,0 @@ let i = 0;

@@ -226,2 +226,3 @@ "use strict";

this._cookieJar = privateData.options.cookieJar;
this._parseOptions = privateData.options.parseOptions;
if (this._cookieJar === undefined) {

@@ -461,8 +462,3 @@ this._cookieJar = new CookieJar(null, { looseMode: true });

writeln() {
const args = [];
for (let i = 0; i < arguments.length; ++i) {
args.push(arguments[i]);
}
args.push("\n");
this.write.apply(this, args);
this.write(...arguments, "\n");
}

@@ -573,2 +569,11 @@

get dir() {
return this.documentElement ? this.documentElement.dir : "";
}
set dir(value) {
if (this.documentElement) {
this.documentElement.dir = value;
}
}
get head() {

@@ -640,3 +645,3 @@ return this.documentElement ? firstChildWithHTMLLocalName(this.documentElement, "head") : null;

if (data.indexOf("?>") !== -1) {
if (data.includes("?>")) {
throw new DOMException(DOMException.INVALID_CHARACTER_ERR,

@@ -661,3 +666,3 @@ "Processing instruction data cannot contain the string \"?>\"");

if (data.indexOf("]]>") !== -1) {
if (data.includes("]]>")) {
throw new DOMException(DOMException.INVALID_CHARACTER_ERR,

@@ -664,0 +669,0 @@ "CDATA section data cannot contain the string \"]]>\"");

@@ -53,8 +53,6 @@ "use strict";

if (html !== "") {
if (node.nodeName === "#document") {
document._htmlToDom.appendHtmlToDocument(html, node);
} else {
document._htmlToDom.appendHtmlToElement(html, node);
}
if (node.nodeName === "#document") {
document._htmlToDom.appendHtmlToDocument(html, node);
} else {
document._htmlToDom.appendHtmlToElement(html, node);
}

@@ -61,0 +59,0 @@ }

@@ -103,2 +103,17 @@ "use strict";

get dir() {
let dirValue = this.getAttribute("dir");
if (dirValue !== null) {
dirValue = dirValue.toLowerCase();
if (["ltr", "rtl", "auto"].includes(dirValue)) {
return dirValue;
}
}
return "";
}
set dir(value) {
this.setAttribute("dir", value);
}
get style() {

@@ -105,0 +120,0 @@ return this._style;

@@ -15,3 +15,3 @@ "use strict";

get href() {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -32,3 +32,3 @@

get origin() {
setTheURL(this);
reinitializeURL(this);

@@ -43,3 +43,3 @@ if (this.url === null) {

get protocol() {
setTheURL(this);
reinitializeURL(this);

@@ -54,2 +54,4 @@ if (this.url === null) {

set protocol(v) {
reinitializeURL(this);
if (this.url === null) {

@@ -64,3 +66,3 @@ return;

get username() {
setTheURL(this);
reinitializeURL(this);

@@ -75,5 +77,6 @@ if (this.url === null) {

set username(v) {
reinitializeURL(this);
const url = this.url;
if (url === null || url.host === null || url.cannotBeABaseURL) {
if (url === null || url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file") {
return;

@@ -87,6 +90,6 @@ }

get password() {
setTheURL(this);
reinitializeURL(this);
const url = this.url;
if (url === null || url.password === null) {
if (url === null) {
return "";

@@ -99,5 +102,6 @@ }

set password(v) {
reinitializeURL(this);
const url = this.url;
if (url === null || url.host === null || url.cannotBeABaseURL) {
if (url === null || url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file") {
return;

@@ -111,3 +115,3 @@ }

get host() {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -127,3 +131,3 @@

set host(v) {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -140,3 +144,3 @@

get hostname() {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -152,3 +156,3 @@

set hostname(v) {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -165,3 +169,3 @@

get port() {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -177,10 +181,14 @@

set port(v) {
setTheURL(this);
reinitializeURL(this);
const url = this.url;
if (url === null || url.host === null || url.cannotBeABaseURL || url.scheme === "file") {
if (url === null || url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file") {
return;
}
whatwgURL.basicURLParse(v, { url, stateOverride: "port" });
if (v === "") {
url.port = null;
} else {
whatwgURL.basicURLParse(v, { url, stateOverride: "port" });
}
updateHref(this);

@@ -190,3 +198,3 @@ }

get pathname() {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -206,3 +214,3 @@

set pathname(v) {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -216,6 +224,7 @@

whatwgURL.basicURLParse(v, { url, stateOverride: "path start" });
updateHref(this);
}
get search() {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -231,3 +240,3 @@

set search(v) {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -244,3 +253,7 @@

url.query = "";
whatwgURL.basicURLParse(input, { url, stateOverride: "query" });
whatwgURL.basicURLParse(input, {
url,
stateOverride: "query",
encodingOverride: this._ownerDocument.charset
});
}

@@ -251,3 +264,3 @@ updateHref(this);

get hash() {
setTheURL(this);
reinitializeURL(this);
const url = this.url;

@@ -263,6 +276,6 @@

set hash(v) {
setTheURL(this);
reinitializeURL(this);
const url = this.url;
if (url === null || url.scheme === "javascript") {
if (url === null) {
return;

@@ -282,2 +295,10 @@ }

function reinitializeURL(hheu) {
if (hheu.url !== null && hheu.url.scheme === "blob" && hheu.url.cannotBeABaseURL) {
return;
}
setTheURL(hheu);
}
function setTheURL(hheu) {

@@ -284,0 +305,0 @@ const href = hheu.getAttribute("href");

@@ -34,4 +34,2 @@ "use strict";

});
} else {
resourceLoader.enqueue(this)();
}

@@ -38,0 +36,0 @@ }

@@ -31,3 +31,3 @@ "use strict";

}
sections.push.apply(sections, childrenByHTMLLocalName(this, "tbody"));
sections.push(...childrenByHTMLLocalName(this, "tbody"));
if (this.tFoot) {

@@ -43,3 +43,3 @@ sections.push(this.tFoot);

for (const s of sections) {
rows.push.apply(rows, childrenByHTMLLocalName(s, "tr"));
rows.push(...childrenByHTMLLocalName(s, "tr"));
}

@@ -46,0 +46,0 @@ return rows;

@@ -19,3 +19,3 @@ "use strict";

// See https://github.com/tmpvar/jsdom/pull/1140#issuecomment-111587499
if (targetOrigin !== "*" && targetOrigin !== this.origin) {
if (targetOrigin !== "*" && targetOrigin !== this.location.origin) {
return;

@@ -22,0 +22,0 @@ }

"use strict";
/* eslint-disable no-process-exit */
const util = require("util");
const jsdom = require("../../jsdom");
const xhrSymbols = require("./xmlhttprequest-symbols");
const { JSDOM } = require("../../..");
const tough = require("tough-cookie");
const xhrSymbols = require("./xmlhttprequest-symbols.js");
const doc = jsdom.jsdom();
const xhr = new doc.defaultView.XMLHttpRequest();
const dom = new JSDOM();
const xhr = new dom.window.XMLHttpRequest();

@@ -33,3 +31,3 @@ const chunks = [];

const properties = xhr[xhrSymbols.properties];
properties.readyState = doc.defaultView.XMLHttpRequest.OPENED;
properties.readyState = xhr.OPENED;
try {

@@ -36,0 +34,0 @@ xhr.addEventListener("loadend", () => {

@@ -42,4 +42,16 @@ "use strict";

function mergeHeaders(lhs, rhs) {
const rhsParts = rhs.split(",");
const lhsParts = lhs.split(",");
return rhsParts.concat(lhsParts.filter(p => rhsParts.indexOf(p) < 0)).join(",");
}
const simpleMethods = new Set(["GET", "HEAD", "POST"]);
const simpleHeaders = new Set(["accept", "accept-language", "content-language", "content-type"]);
const preflightHeaders = new Set([
"access-control-expose-headers",
"access-control-allow-headers",
"access-control-allow-credentials",
"access-control-allow-origin"
]);

@@ -49,2 +61,3 @@ exports.getRequestHeader = getRequestHeader;

exports.simpleHeaders = simpleHeaders;
exports.preflightHeaders = preflightHeaders;

@@ -283,3 +296,11 @@ // return a "request" client object or an event emitter matching the same behaviour for unsupported protocols

const realClient = doRequest();
realClient.on("response", res => client.emit("response", res));
realClient.on("response", res => {
for (const header in resp.headers) {
if (preflightHeaders.has(header)) {
res.headers[header] = res.headers.hasOwnProperty(header) ?
mergeHeaders(res.headers[header], resp.headers[header]) : resp.headers[header];
}
}
client.emit("response", res);
});
realClient.on("data", chunk => client.emit("data", chunk));

@@ -286,0 +307,0 @@ realClient.on("end", () => client.emit("end"));

@@ -303,3 +303,4 @@ "use strict";

cookieJar: { setCookieSync: () => undefined, getCookieStringSync: () => "" },
encoding
encoding,
parseOptions: this._ownerDocument._parseOptions
} });

@@ -451,3 +452,3 @@ const resImpl = idlUtils.implForWrapper(res);

if (argumentCount < 2) {
throw new TypeError("Not enought arguments");
throw new TypeError("Not enough arguments (expected at least 2)");
}

@@ -454,0 +455,0 @@ method = toByteString(method);

@@ -22,3 +22,3 @@ "use strict";

function onMethodCall() {
anyConsole[method].apply(anyConsole, arguments);
anyConsole[method](...arguments);
}

@@ -29,3 +29,3 @@ this.on(method, onMethodCall);

if (!options.omitJsdomErrors) {
if (!options.omitJSDOMErrors) {
this.on("jsdomError", e => anyConsole.error(e.stack, e.detail));

@@ -32,0 +32,0 @@ }

@@ -12,3 +12,3 @@ "use strict";

const builtInConsts = ["Infinity", "NaN", "undefined"];
const otherBuiltIns = ["eval", "isFinite", "isNaN", "parseFloat", "parseInt", "decodeURI", "decodeURIComponent",
const otherBuiltIns = ["isFinite", "isNaN", "parseFloat", "parseInt", "decodeURI", "decodeURIComponent",
"encodeURI", "encodeURIComponent", "Array", "ArrayBuffer", "Boolean", "DataView", "Date", "Error", "EvalError",

@@ -45,2 +45,11 @@ "Float32Array", "Float64Array", "Function", "Int8Array", "Int16Array", "Int32Array", "Map", "Number", "Object",

}
Object.defineProperty(sandbox, "eval", {
value(code) {
return exports.runInContext(code, sandbox);
},
writable: true,
configurable: true,
enumerable: false
});
};

@@ -78,3 +87,3 @@

for (let i = 0; i < globals.length; ++i) {
if (globals[i].name === "window") {
if (globals[i].name === "window" || globals[i].name === "this") {
continue;

@@ -109,1 +118,12 @@ }

};
exports.Script = class VMShimScript {
constructor(code, options) {
this._code = code;
this._options = options;
}
runInContext(sandbox, options) {
return exports.runInContext(this._code, sandbox, Object.assign({}, this._options, options));
}
};
{
"name": "jsdom",
"version": "9.12.0",
"description": "A JavaScript implementation of the DOM and HTML standards",
"version": "10.0.0",
"description": "A JavaScript implementation of many web standards",
"keywords": [

@@ -31,3 +31,5 @@ "dom",

"parse5": "^1.5.1",
"pn": "^1.0.0",
"request": "^2.79.0",
"request-promise-native": "^1.0.3",
"sax": "^1.2.1",

@@ -46,6 +48,6 @@ "symbol-tree": "^3.2.1",

"colors": "^1.1.2",
"ecstatic": "^2.1.0",
"eslint": "^3.14.1",
"eslint-plugin-html": "^2.0.0",
"fs-readdir-recursive": "^1.0.0",
"http-server": "^0.9.0",
"karma": "^1.4.1",

@@ -61,5 +63,5 @@ "karma-browserify": "^5.1.1",

"optimist": "0.6.1",
"portfinder": "^1.0.12",
"portfinder": "^1.0.13",
"q": "^1.4.1",
"selenium-standalone": "^6.0.0",
"selenium-standalone": "^6.0.1",
"server-destroy": "^1.0.1",

@@ -82,2 +84,3 @@ "st": "^1.2.0",

"test-mocha-all": "mocha test/index.js",
"test-api": "mocha test/api",
"test-old": "node ./test/runner",

@@ -95,3 +98,3 @@ "test": "npm run test-mocha-all && npm run test-old",

},
"main": "./lib/jsdom"
"main": "./lib/api.js"
}
# jsdom
A JavaScript implementation of the WHATWG DOM and HTML standards, for use with [Node.js](https://nodejs.org/).
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.
## Install
The latest versions of jsdom require Node.js v6 or newer. (Versions of jsdom below v10 still work with Node.js v4, but are unsupported.)
```bash
$ npm install jsdom
```
As of v10, jsdom has a new API (documented below). The old API is still supported for now; [see its documentation](./lib/old-api.md) for details.
Note that as of our 7.0.0 release, jsdom requires Node.js 4 or newer ([why?](https://github.com/tmpvar/jsdom/blob/master/Changelog.md#700)). In the meantime you are still welcome to install a release in [the 3.x series](https://github.com/tmpvar/jsdom/tree/3.x) if you use legacy Node.js versions like 0.10 or 0.12. There are also various releases between 3.x and 7.0.0 that work with various io.js versions.
## Basic usage
## Human contact
```js
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
```
- [Mailing list](http://groups.google.com/group/jsdom)
- IRC channel: [#jsdom on freenode](irc://irc.freenode.net/jsdom)
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`:
## Easymode: `jsdom.env`
```js
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello world"
```
`jsdom.env` is an API that allows you to throw a bunch of stuff at it, and it will generally do the right thing.
(Note that jsdom will parse the HTML you pass it just like a browser does, including implied `<html>`, `<head>`, and `<body>` tags.)
You can use it with a URL
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
```js
// Count all of the links from the io.js build page
var jsdom = require("jsdom");
jsdom.env(
"https://iojs.org/dist/",
["http://code.jquery.com/jquery.js"],
function (err, window) {
console.log("there have been", window.$("a").length - 4, "io.js releases!");
}
);
const { window } = new JSDOM(`...`);
// or even
const { document } = (new JSDOM(`...`)).window;
```
or with raw HTML
Full documentation on everything you can do with the `JSDOM` class is below, in the section "`JSDOM` Object API".
```js
// Run some jQuery on a html fragment
var jsdom = require("jsdom");
## Customizing jsdom
jsdom.env(
'<p><a class="the-link" href="https://github.com/tmpvar/jsdom">jsdom!</a></p>',
["http://code.jquery.com/jquery.js"],
function (err, window) {
console.log("contents of a.the-link:", window.$("a.the-link").text());
}
);
```
The `JSDOM` constructor accepts a second parameter which can be used to customize your jsdom in the following ways.
or with a configuration object
### Simple options
```js
// Print all of the news items on Hacker News
var jsdom = require("jsdom");
jsdom.env({
url: "http://news.ycombinator.com/",
scripts: ["http://code.jquery.com/jquery.js"],
done: function (err, window) {
var $ = window.$;
console.log("HN Links");
$("td.title:not(:last) a").each(function() {
console.log(" -", $(this).text());
});
}
const dom = new JSDOM(``, {
url: "https://example.org/",
referrer: "https://example.com/",
contentType: "text/html",
userAgent: "Mellblomenator/9000",
includeNodeLocations: true
});
```
or with raw JavaScript source
- `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"`.
- `referrer` just affects the value read from `document.referrer`. It defaults to no referrer (which reflects as the empty string).
- `contentType` affects the value read from `document.contentType`, and how the document is parsed: as HTML or as XML. Values that are not `"text/html"` or an [XML mime type](https://html.spec.whatwg.org/multipage/infrastructure.html#xml-mime-type) will throw. It defaults to `"text/html"`.
- `userAgent` affects the value read from `navigator.userAgent`, as well as the `User-Agent` header sent while fetching subresources. It defaults to <code>\`Mozilla/5.0 (${process.platform}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}\`</code>.
- `includeNodeLocations` preserves the location info produced by the HTML parser, allowing you to retrieve it with the `nodeLocation()` method (described below). 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.
```js
// Print all of the news items on Hacker News
var jsdom = require("jsdom");
var fs = require("fs");
var jquery = fs.readFileSync("./path/to/jquery.js", "utf-8");
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](http://url.spec.whatwg.org/).)
jsdom.env({
url: "http://news.ycombinator.com/",
src: [jquery],
done: function (err, window) {
var $ = window.$;
console.log("HN Links");
$("td.title:not(:last) a").each(function () {
console.log(" -", $(this).text());
});
}
});
```
### Executing scripts
### How it works
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.
The do-what-I-mean API is used like so:
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:
```js
jsdom.env(string, [scripts], [config], callback);
const dom = new JSDOM(`<body>
<script>document.body.appendChild(document.createElement("hr"));</script>
</body>`);
// The script will not be executed, by default:
dom.window.document.body.children.length === 1;
```
- `string`: may be a URL, file name, or HTML fragment
- `scripts`: a string or array of strings, containing file names or URLs that will be inserted as `<script>` tags
- `config`: see below
- `callback`: takes two arguments
- `err`: either `null`, if nothing goes wrong, or an error, if the window could not be created
- `window`: a brand new `window`, if there wasn't an error
To enable executing scripts inside the page, you can use the `runScripts: "dangerously"` option:
_Example:_
```js
jsdom.env(html, function (err, window) {
// free memory associated with the window
window.close();
});
```
const dom = new JSDOM(`<body>
<script>document.body.appendChild(document.createElement("hr"));</script>
</body>`, { runScripts: "dangerously" });
If you would like to specify a configuration object only:
```js
jsdom.env(config);
// The script will be executed and modify the DOM:
dom.window.document.body.children.length === 2;
```
- `config.html`: a HTML fragment
- `config.file`: a file which jsdom will load HTML from; the resulting document's URL will be a `file://` URL.
- `config.url`: sets the resulting document's URL, which is reflected in various properties like `document.URL` and `location.href`, and is also used for cross-origin request restrictions. If `config.html` and `config.file` are not provided, jsdom will load HTML from this URL.
- `config.scripts`: see `scripts` above.
- `config.src`: an array of JavaScript strings that will be evaluated against the resulting document. Similar to `scripts`, but it accepts JavaScript instead of paths/URLs.
- `config.cookieJar`: cookie jar which will be used by document and related resource requests. Can be created by `jsdom.createCookieJar()` method. Useful to share cookie state among different documents as browsers does.
- `config.parsingMode`: either `"auto"`, `"html"`, or `"xml"`. The default is `"auto"`, which uses HTML behavior unless `config.url` responds with an XML `Content-Type`, or `config.file` contains a filename ending in `.xml` or `.xhtml`. Setting to `"xml"` will attempt to parse the document as an XHTML document. (jsdom is [currently only OK at doing that](https://github.com/tmpvar/jsdom/labels/x%28ht%29ml).)
- `config.referrer`: the new document will have this referrer.
- `config.cookie`: manually set a cookie value, e.g. `'key=value; expires=Wed, Sep 21 2011 12:00:00 GMT; path=/'`. Accepts cookie string or array of cookie strings.
- `config.headers`: an object giving any headers that will be used while loading the HTML from `config.url`, if applicable.
- `config.userAgent`: the user agent string used in requests; defaults to `Node.js (#process.platform#; U; rv:#process.version#)`
- `config.features`: see Flexibility section below. **Note**: the default feature set for `jsdom.env` does _not_ include fetching remote JavaScript and executing it. This is something that you will need to _carefully_ enable yourself.
- `config.resourceLoader`: a function that intercepts subresource requests and allows you to re-route them, modify, or outright replace them with your own content. More below.
- `config.done`, `config.onload`, `config.created`: see below.
- `config.concurrentNodeIterators`: the maximum amount of `NodeIterator`s that you can use at the same time. The default is `10`; setting this to a high value will hurt performance.
- `config.virtualConsole`: a virtual console instance that can capture the window’s console output; see the "Capturing Console Output" examples.
- `config.pool`: an object describing which agents to use for the requests; defaults to `{ maxSockets: 6 }`, see [request module](https://github.com/request/request#requestoptions-callback) for more details.
- `config.agent`: `http(s).Agent` instance to use
- `config.agentClass`: alternatively specify your agent's class name
- `config.agentOptions`: the agent options; defaults to `{ keepAlive: true, keepAliveMsecs: 115000 }`, see [http api](https://nodejs.org/api/http.html) for more details.
- `config.strictSSL`: if `true`, requires SSL certificates be valid; defaults to `true`, see [request module](https://github.com/request/request#requestoptions-callback) for more details.
- `config.proxy`: a URL for a HTTP proxy to use for the requests.
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.
If you are simply trying to execute script "from the outside", instead of letting `<script>` elements (and inline event handlers) run "from the inside", you can use the `runScripts: "outside-only"` option, which enables `window.eval`:
Note that at least one of the callbacks (`done`, `onload`, or `created`) is required, as is one of `html`, `file`, or `url`.
```js
const window = (new JSDOM(``, { runScripts: "outside-only" })).window;
### Initialization lifecycle
window.eval(`document.body.innerHTML = "<p>Hello, world!</p>";`);
window.document.body.children.length === 1;
```
If you just want to load the document and execute it, the `done` callback shown above is the simplest. If anything goes wrong while loading the document and creating the window, the problem will show up in the `error` passed as the first argument.
This is turned off by default for performance reasons, but is safe to enable.
However, if you want more control over or insight into the initialization lifecycle, you'll want to use the `created` and/or `onload` callbacks:
Note that 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.
#### `created(error, window)`
Finally, for advanced use cases you can use the `dom.runVMScript(script)` method, documented below.
The `created` callback is called as soon as the window is created, or if that process fails. You may access all `window` properties here; however, `window.document` is not ready for use yet, as the HTML has not been parsed.
### Loading subresources
The primary use-case for `created` is to modify the window object (e.g. add new functions on built-in prototypes) before any scripts execute.
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:
You can also set an event handler for `'load'` or other events on the window if you wish.
* Frames and iframes, via `<frame>` and `<iframe>`
* Stylesheets, via `<link rel="stylesheet">`
* Scripts, via `<script>`, but only if `runScripts: "dangerously"` is also set
* Images, via `<img>`, but only if the `canvas` (or `canvas-prebuilt`) npm package is also installed (see "Canvas Support" below)
If the `error` argument is non-`null`, it will contain whatever loading or initialization error caused the window creation to fail; in that case `window` will not be passed.
In the future we plan to offer more customization of resource loading via this option, but for now the default and the `"usable"` option are the two modes offered.
#### `onload(window)`
### Virtual consoles
The `onload` callback is called along with the window's `'load'` event. This means it will only be called if creation succeeds without error. Note that by the time it has called, any external resources will have been downloaded, and any `<script>`s will have finished executing.
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.
#### `done(error, window)`
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
Now that you know about `created` and `onload`, you can see that `done` is essentially both of them smashed together:
```js
const virtualConsole = new jsdom.VirtualConsole();
const dom = new JSDOM(``, { virtualConsole });
```
- If window creation fails, then `error` will be the creation error.
- Otherwise, `window` will be a fully-loaded window, with all external resources downloaded and `<script>`s executed.
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:
#### Dealing with asynchronous script loading
If you load scripts asynchronously, e.g. with a module loader like RequireJS, none of the above hooks will really give you what you want. There's nothing, either in jsdom or in browsers, to say "notify me after all asynchronous loads have completed." The solution is to use the mechanisms of the framework you are using to notify about this finishing up. E.g., with RequireJS, you could do
```js
// On the Node.js side:
var window = jsdom.jsdom(...).defaultView;
window.onModulesLoaded = function () {
console.log("ready to roll!");
};
virtualConsole.on("error", () => { ... });
virtualConsole.on("warn", () => { ... });
virtualConsole.on("info", () => { ... });
virtualConsole.on("dir", () => { ... });
// ... etc. See https://console.spec.whatwg.org/#logging
```
```html
<!-- Inside the HTML you supply to jsdom -->
<script>
requirejs(["entry-module"], function () {
window.onModulesLoaded();
});
</script>
```
(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.)
For more details, see the discussion in [#640](https://github.com/tmpvar/jsdom/issues/640), especially [@matthewkastor](https://github.com/matthewkastor)'s [insightful comment](https://github.com/tmpvar/jsdom/issues/640#issuecomment-22216965).
If you simply want to redirect the virtual console output to another console, like the default Node.js one, you can do
#### Listening for script errors during initialization
Although it is easy to listen for script errors after initialization, via code like
```js
var window = jsdom.jsdom(...).defaultView;
window.addEventListener("error", function (event) {
console.error("script error!!", event.error);
});
virtualConsole.sendTo(console);
```
it is often also desirable to listen for any script errors during initialization, or errors loading scripts passed to `jsdom.env`. To do this, use the virtual console feature, described in more detail later:
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:
```js
var virtualConsole = jsdom.createVirtualConsole();
virtualConsole.on("jsdomError", function (error) {
console.error(error.stack, error.detail);
});
- Errors loading or parsing subresources (scripts, stylesheets, frames, and iframes)
- Script execution errors that are not handled by a window `onerror` event handler that returns `true` or calls `event.preventDefault()`
- Not-implemented errors resulting from calls to methods, like `window.alert`, which jsdom does not implement, but installs anyway for web compatibility
var window = jsdom.jsdom(..., { virtualConsole }).defaultView;
```
If you're using `sendTo(c)` to send errors to `c`, by default it will call `console.error` 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
You also get this functionality for free by default if you use `virtualConsole.sendTo`; again, see more below:
```js
var virtualConsole = jsdom.createVirtualConsole().sendTo(console);
var window = jsdom.jsdom(..., { virtualConsole }).defaultView;
virtualConsole.sendTo(c, { omitJSDOMErrors: true });
```
### On running scripts and being safe
### Cookie jars
By default, `jsdom.env` will not process and run external JavaScript, since our sandbox is not foolproof. That is, code running inside the DOM's `<script>`s can, if it tries hard enough, get access to the Node environment, and thus to your machine. If you want to (carefully!) enable running JavaScript, you can use `jsdom.jsdom`, `jsdom.jQueryify`, or modify the defaults passed to `jsdom.env`.
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.
### On timers and process lifetime
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
Timers in the page (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, note that 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. 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`).
## For the hardcore: `jsdom.jsdom`
The `jsdom.jsdom` method does fewer things automatically; it takes in only HTML source, and it does not allow you to separately supply scripts that it will inject and execute. It just gives you back a `document` object, with usable `document.defaultView`, and starts asynchronously executing any `<script>`s included in the HTML source. You can listen for the `'load'` event to wait until scripts are done loading and executing, just like you would in a normal HTML page.
Usage of the API generally looks like this:
```js
var jsdom = require("jsdom").jsdom;
var doc = jsdom(markup, options);
var window = doc.defaultView;
const cookieJar = new jsdom.CookieJar(store, options);
const dom = new JSDOM(``, { cookieJar });
```
- `markup` is a HTML document to be parsed. You can also pass `undefined` to get the basic document, equivalent to what a browser will give if you open up an empty `.html` file.
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.
- `options`: see the explanation of the `config` object above.
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.
### Flexibility
### Intervening before parsing
One of the goals of jsdom is to be as minimal and light as possible. This section details how someone can change the behavior of `Document`s before they are created. These features are baked into the `DOMImplementation` that every `Document` has, and may be tweaked in two ways:
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:
1. When you create a new `Document`, by overriding the configuration:
```js
const dom = new JSDOM(`<p>Hello</p>`, {
beforeParse(window) {
window.document.childNodes.length === 0;
window.someCoolAPI = () => { /* ... */ };
}
});
```
```js
var jsdom = require("jsdom").jsdom;
var doc = jsdom("<html><body></body></html>", {
features: {
FetchExternalResources : ["link"]
}
});
```
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.
Do note, that this will only affect the document that is currently being created. All other documents will use the defaults specified below (see: Default Features).
## `JSDOM` object API
2. Before creating any documents, you can modify the defaults for all future documents:
Once you have constructed a `JSDOM` object, it will have the following useful capabilities:
```js
require("jsdom").defaultDocumentFeatures = {
FetchExternalResources: ["script"],
ProcessExternalResources: false
};
```
### Properties
#### External Resources
The property `window` retrieves the `Window` object that was created for you.
Default features are extremely important for jsdom as they lower the configuration requirement and present developers a set of consistent default behaviors. The following sections detail the available features, their defaults, and the values that jsdom uses.
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.
`FetchExternalResources`
### Serializing the document with `serialize()`
- _Default_: `["script", "link"]`
- _Allowed_: `["script", "frame", "iframe", "link", "img"]` or `false`
- _Default for `jsdom.env`_: `false`
The `serialize()` method will return the [HTML serialization](https://html.spec.whatwg.org/#html-fragment-serialisation-algorithm) of the document, including the doctype:
Enables/disables fetching files over the file system/HTTP
```js
const dom = new JSDOM(`<!DOCTYPE html>hello`);
`ProcessExternalResources`
dom.serialize() === "<!DOCTYPE html><html><head></head><body>hello</body></html>";
- _Default_: `["script"]`
- _Allowed_: `["script"]` or `false`
- _Default for `jsdom.env`_: `false`
// Contrast with:
dom.window.document.documentElement.outerHTML === "<html><head></head><body>hello</body></html>";
```
Enables/disables JavaScript execution
### Getting the source location of a node with `nodeLocation(node)`
`SkipExternalResources`
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:
- _Default_: `false` (allow all)
- _Allowed_: `/url to be skipped/` or `false`
- _Example_: `/http:\/\/example.org/js/bad\.js/`
```js
const dom = new JSDOM(
`<p>Hello
<img src="foo.jpg">
</p>`,
{ includeNodeLocations: true }
);
Filters resource downloading and processing to disallow those matching the given regular expression
const document = dom.window.document;
const bodyEl = document.body; // implicitly created
const pEl = document.querySelector("p");
const textNode = pEl.firstChild;
const imgEl = document.querySelector("img");
#### Custom External Resource Loader
console.log(dom.nodeLocation(bodyEl)); // null; it's not in the source
console.log(dom.nodeLocation(pEl)); // { start: 0, end: 39, startTag: ..., endTag: ... }
console.log(dom.nodeLocation(textNode)); // { start: 3, end: 13 }
console.log(dom.nodeLocation(imgEl)); // { start: 13, end: 32 }
```
jsdom lets you intercept subresource requests using `config.resourceLoader`. `config.resourceLoader` expects a function which is called for each subresource request with the following arguments:
Note that this feature only works if you have set the `includeNodeLocations` option; node locations are off by default for performance reasons.
- `resource`: a vanilla JavaScript object with the following properties
- `element`: the element that requested the resource.
- `url`: a parsed URL object.
- `cookie`: the content of the HTTP cookie header (`key=value` pairs separated by semicolons).
- `baseUrl`: the base URL used to resolve relative URLs.
- `defaultFetch(callback)`: a convenience method to fetch the resource online.
- `callback`: a function to be called with two arguments
- `error`: either `null`, if nothing goes wrong, or an `Error` object.
- `body`: a string representing the body of the resource.
### Running vm-created scripts with `runVMScript(script)`
For example, fetching all JS files from a different directory and running them in strict mode:
The built-in `vm` module of Node.js allows you to create `Script` instances, which can be compiled ahead of time and then run multiple times on a given "VM context". Behind the scenes, a jsdom `Window` is indeed a VM context. To get access to this ability, use the `runVMScript()` method:
```js
var jsdom = require("jsdom");
const { Script } = require("vm");
jsdom.env({
url: "http://example.com/",
resourceLoader: function (resource, callback) {
var pathname = resource.url.pathname;
if (/\.js$/.test(pathname)) {
resource.url.pathname = pathname.replace("/js/", "/js/raw/");
return resource.defaultFetch(function (err, body) {
if (err) return callback(err);
callback(null, '"use strict";\n' + body);
});
} else {
return resource.defaultFetch(callback);
}
},
features: {
FetchExternalResources: ["script"],
ProcessExternalResources: ["script"],
SkipExternalResources: false
const dom = new JSDOM(``, { runScripts: "outside-only" });
const s = new Script(`
if (!this.ran) {
this.ran = 0;
}
});
```
You can return an object containing an `abort()` function which will be called if the window is closed or stopped before the request ends.
The `abort()` function should stop the request and call the callback with an error.
++this.ran;
`);
For example, simulating a long request:
dom.runVMScript(s);
dom.runVMScript(s);
dom.runVMScript(s);
```js
var jsdom = require("jsdom");
jsdom.env({
url: "http://example.com/",
resourceLoader: function (resource, callback) {
var pathname = resource.url.pathname;
if (/\.json$/.test(pathname)) {
var timeout = setTimeout(function() {
callback(null, "{\"test\":\"test\"}");
}, 10000);
return {
abort: function() {
clearTimeout(timeout);
callback(new Error("request canceled by user"));
}
};
} else {
return resource.defaultFetch(callback);
}
},
features: {
FetchExternalResources: ["script"],
ProcessExternalResources: ["script"],
SkipExternalResources: false
}
});
dom.window.ran === 3;
```
## Canvas
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.
jsdom includes support for using the [canvas](https://npmjs.org/package/canvas) or [canvas-prebuilt](https://npmjs.org/package/canvas-prebuilt) 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.
### Reconfiguring the jsdom with `reconfigure(settings)`
## More Examples
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`.
### Creating a browser-like window object
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.
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:
```js
var jsdom = require("jsdom").jsdom;
var document = jsdom("hello world");
var window = document.defaultView;
const dom = new JSDOM();
console.log(window.document.documentElement.outerHTML);
// output: "<html><head></head><body>hello world</body></html>"
dom.window.top === dom.window;
dom.window.location.href === "about:blank";
console.log(window.innerWidth);
// output: 1024
dom.reconfigure({ windowTop: myFakeTopForTesting, url: "https://example.com/" });
console.log(typeof window.document.getElementsByClassName);
// outputs: function
dom.window.top === myFakeTopForTesting;
dom.window.location.href === "https://example.com/";
```
### jQueryify
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 resolution of relative URLs within the document, and the same-origin checks and referrer used while fetching subresources. It will not, however, perform a 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.
```js
var jsdom = require("jsdom");
var window = jsdom.jsdom().defaultView;
## Convenience APIs
jsdom.jQueryify(window, "http://code.jquery.com/jquery-2.1.1.js", function () {
window.$("body").append('<div class="testing">Hello World, It works</div>');
### `fromURL()`
console.log(window.$(".testing").text());
In addition to the `JSDOM` constructor itself, jsdom provides a promise-returning factory method for constructing a jsdom from a URL:
```js
JSDOM.fromURL("https://example.com/", options).then(dom => {
console.log(dom.serialize());
});
```
### Passing objects to scripts inside the page
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.
```js
var jsdom = require("jsdom").jsdom;
var window = jsdom().defaultView;
The options provided to `fromURL()` are similar to those provided to the `JSDOM` constructor, with the following additional restrictions and consequences:
window.__myObject = { foo: "bar" };
- The `url` and `contentType` options cannot be provided.
- The `referrer` option is used as the HTTP `Referer` request header of the initial request.
- The `userAgent` option is used as the HTTP `User-Agent` request header of any requests.
- The resulting jsdom's URL, content type, and referrer are determined from the response.
- 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.
var scriptEl = window.document.createElement("script");
scriptEl.src = "anotherScript.js";
window.document.body.appendChild(scriptEl);
The initial request is not infinitely customizable to the same extent as is possible in a package like [request](https://www.npmjs.com/package/request); `fromURL()` is meant to be a convenience API for the majority of cases. If you need greater control over the initial request, you should perform it yourself, and then use the `JSDOM` constructor manually.
// anotherScript.js will have the ability to read `window.__myObject`, even
// though it originated in Node.js!
```
### `fromFile()`
### Shimming unimplemented APIs
Similar to `fromURL()`, jsom also provides a `fromFile()` factory method for constructing a jsdom from a filename:
```js
var jsdom = require("jsdom");
var document = jsdom("", {
created(err, window) {
window.alert = () => {
// Do something different than jsdom's default "not implemented" virtual console error
};
Object.defineProperty(window, "outerWidth", {
get() { return 400; },
enumerable: true,
configurable: true
});
}
JSDOM.fromFile("stuff.html", options).then(dom => {
console.log(dom.serialize());
});
```
### Serializing a document
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.
```js
var jsdom = require("jsdom").jsdom;
var serializeDocument = require("jsdom").serializeDocument;
The options provided to `fromFile()` are similar to those provided to the `JSDOM` constructor, with the following additional defaults:
var doc = jsdom("<!DOCTYPE html>hello");
- The `url` option will default to a file URL corresponding to the given filename, instead of to `"about:blank"`.
- The `contentType` option will default to `"application/xhtml+xml"` if the given filename ends in `.xhtml` or `.xml`; otherwise it will continue to default to `"text/html"`.
serializeDocument(doc) === "<!DOCTYPE html><html><head></head><body>hello</body></html>";
doc.documentElement.outerHTML === "<html><head></head><body>hello</body></html>";
```
### `fragment()`
### Sharing cookie state among pages
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:
```js
var jsdom = require("jsdom");
var cookieJar = jsdom.createCookieJar();
const frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`);
jsdom.env({
url: 'http://google.com',
cookieJar: cookieJar,
done: function (err1, window1) {
//...
jsdom.env({
url: 'http://code.google.com',
cookieJar: cookieJar,
done: function (err2, window2) {
//...
}
});
}
});
frag.childNodes.length === 2;
frag.querySelector("strong").textContent = "Why hello there!";
// etc.
```
### Capturing Console Output
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>`).
#### Forward a window's console output to the Node.js console
All invocations of the `framgment()` factory result in `DocumentFragment`s that share the same owner `Document` and `Window`. This allows many calls to `fragment()` with no extra overhead. But it also means that calls to `fragment()` cannot be customized with any options.
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:
```js
var jsdom = require("jsdom");
var document = jsdom.jsdom(undefined, {
virtualConsole: jsdom.createVirtualConsole().sendTo(console)
});
const frag = JSDOM.fragment(`<p>Hello</p>`);
console.log(frag.firstChild.outerHTML); // logs "<p>Hello</p>"
```
By default this will forward all `"jsdomError"` events to `console.error`. If you want to maintain only a strict one-to-one mapping of events to method calls, and perhaps handle `"jsdomErrors"` yourself, then you can do `sendTo(console, { omitJsdomErrors: true })`.
## Other noteworthy features
#### Create an event emitter for a window's console
### Canvas support
```js
var jsdom = require("jsdom");
jsdom includes support for using the [`canvas`](https://www.npmjs.com/package/canvas) or [`canvas-prebuilt`](https://npmjs.org/package/canvas-prebuilt) 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.
var virtualConsole = jsdom.createVirtualConsole();
### Encoding sniffing
virtualConsole.on("log", function (message) {
console.log("console.log called ->", message);
});
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.
var document = jsdom.jsdom(undefined, {
virtualConsole: virtualConsole
});
```
This encoding sniffing also applies to `JSDOM.fromFile()` and `JSDOM.fromURL()`. In the latter case, just as in a browser, any `Content-Type` headers sent with the response will take priority.
Post-initialization, if you didn't pass in a `virtualConsole` or no longer have a reference to it, you can retrieve the `virtualConsole` by using:
### Closing down a jsdom
```js
var virtualConsole = jsdom.getVirtualConsole(window);
```
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.
#### Virtual console `jsdomError` error reporting
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).
Besides the usual events, corresponding to `console` methods, the virtual console is also used for reporting 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:
### Running jsdom inside a web browser
- Errors loading or parsing external resources (scripts, stylesheets, frames, and iframes)
- Script execution errors that are not handled by a window `onerror` event handler that returns `true` or calls `event.preventDefault()`
- Calls to methods, like `window.alert`, which jsdom does not implement, but installs anyway for web compatibility
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!
### Getting a node's location within the source
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 v6+). Thus, older browsers will likely not work. (Even transpilation will not help much: we plan to use `Proxy`s extensively throughout the course of jsdom v10.x.)
To find where a DOM node is within the source document, we provide the `jsdom.nodeLocation` function:
Notably, jsdom works well inside a web worker. The original contributor, [@lawnsea](TODO), who made this possible, has [published a paper](TODO) about the results.
```js
var jsdom = require("jsdom");
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.
var document = jsdom.jsdom(`<p>Hello
<img src="foo.jpg">
</p>`);
## Caveats
var bodyEl = document.body; // implicitly created
var pEl = document.querySelector("p");
var textNode = pEl.firstChild;
var imgEl = document.querySelector("img");
### Asynchronous script loading
console.log(jsdom.nodeLocation(bodyEl)); // null; it's not in the source
console.log(jsdom.nodeLocation(pEl)); // { start: 0, end: 39, startTag: ..., endTag: ... }
console.log(jsdom.nodeLocation(textNode)); // { start: 3, end: 13 }
console.log(jsdom.nodeLocation(imgEl)); // { start: 13, end: 32 }
```
People often have trouble with asynchronous script loading when using jsdom. Many pages loads 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.
This returns the [parse5 location info](https://www.npmjs.com/package/parse5#options-locationinfo) for the node.
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:
#### Overriding `window.top`
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 window, even using `Object.defineProperty`. However, if you're acting from outside the window, e.g. in some test framework that creates jsdom instances, you can override it using the special `jsdom.reconfigureWindow` function:
```js
jsdom.reconfigureWindow(window, { top: myFakeTopForTesting });
// On the Node.js side:
const window = (new JSDOM(...)).window;
window.onModulesLoaded = () => {
console.log("ready to roll!");
};
```
In the future we may expand `reconfigureWindow` to allow overriding other `[Unforgeable]` properties. Let us know if you need this capability.
```html
<!-- Inside the HTML you supply to jsdom -->
<script>
requirejs(["entry-module"], () => {
window.onModulesLoaded();
});
</script>
```
#### Changing the URL of an existing jsdom `Window` instance
If you do not control the page, you could try workarounds such as polling for the presence of a specific element.
At present jsdom does not handle navigation (such as setting `window.location.href === "https://example.com/"`). However, if you'd like to change the URL of an existing `Window` instance (such as for testing purposes), you can use the `jsdom.changeURL` method:
For more details, see the discussion in [#640](https://github.com/tmpvar/jsdom/issues/640), especially [@matthewkastor](https://github.com/matthewkastor)'s [insightful comment](https://github.com/tmpvar/jsdom/issues/640#issuecomment-22216965).
```js
jsdom.changeURL(window, "https://example.com/");
```
### Shared constructors and prototypes
#### Running vm scripts
At the present time, for most web platform APIs, jsdom shares the same class definition between multiple seemingly-independent jsdoms. That means that, for example, the following situation can occur:
Although in most cases it's simplest to just insert a `<script>` element or call `window.eval`, in some cases you want access to the raw [vm context](https://nodejs.org/api/vm.html) underlying jsdom to run scripts. You can do that like so:
```js
const dom1 = new JSDOM();
const dom2 = new JSDOM();
```js
const script = new vm.Script("globalVariable = 5;", { filename: "test.js" });
jsdom.evalVMScript(window, script);
dom1.window.Element.prototype.expando = "blah";
console.log(dom2.window.document.createElement("frameset").expando); // logs "blah"
```
## jsdom vs. PhantomJS
This is done mainly for performance and memory reasons: creating separate copies of all the many classes on the web platform, each time we create a jsdom, would be rather expensive.
Some people wonder what the differences are between jsdom and [PhantomJS](http://phantomjs.org/), and when you would use one over the other. Here we attempt to explain some of the differences, and why we find jsdom to be a pleasure to use for testing and scraping use cases.
Nevertheless, we remain interested in one day providing an option to create an "independent" jsdom, at the cost of some performance.
PhantomJS is a complete browser (although it uses a very old and rare rendering engine). It even performs layout and rendering, allowing you to query element positions or take a screenshot. jsdom is not a full browser: it does not perform layout or rendering, and it does not support navigation between pages. It _does_ support the DOM, HTML, canvas, many other web platform APIs, and running scripts.
### Missing features in the new API
So you could use jsdom to fetch the HTML of your web application (while also executing the JavaScript code within that HTML). And then you could examine and modify the resulting DOM tree. Or you could trigger event listeners to test how the web application reacts. You could also use jsdom to build up your own DOM tree from scratch, and then serialize it to a HTML string.
Compared to the old JSDOM API from v9.x and before, the new API is noticably missing fine-grained control of resource loads. Previous versions of jsdom allowed you to set options that were used when making requests (both for the initial request, in the old equivalent of `JSDOM.fromURL()`, and for subresource requests). They also allowed you to control which subresources were requested and applied to the main document, so that you could e.g. download stylesheets but not scripts. Finally, they provided a customizable resource loader that let you intercept any outgoing request and fulfill it with a completely synthetic response.
You need an executable to run PhantomJS. It is written in native code, and has to be compiled for each platform. jsdom is pure JavaScript, and runs wherever Node.js runs. It even has experimental support for running within browsers, giving you the ability to create a whole DOM Document inside a web worker.
None of these features are yet in the new jsdom API, although we are hoping to add them back soon! This requires a decent amount of behind-the-scenes work to implement in a reasonable way, unfortunately.
One of the reasons jsdom is used a lot for testing is that creating a new document instance has very little overhead in jsdom. Opening a new page in PhantomJS takes a lot of time, so running a lot of small tests in fresh documents could take minutes in PhantomJS, but only seconds in jsdom.
In the meantime, please feel free to use the old jsdom API to get access to this functionality. It is supported and maintained, although it will not be getting new features. The documentation is found in [lib/old-api.md](./lib/old-api.md).
Another important benefit jsdom has for testing is a bit more complicated: it is easy to suffer race conditions using an external process like PhantomJS (or Selenium). For example if you create a script to test something using PhantomJS, that script will live in a different process than the web application. If you perform multiple steps in your test that are dependent on each other (for example, step 1: find the element; step 2: click on the element), the application might change the DOM during those steps (step 1.5: the page's JavaScript removes the element). This is not an issue in jsdom, since your tests live in exactly the same thread and event loop as the web application, so if your test is executing JavaScript code, the web application cannot run its code until your test releases control of the event loop.
### Unimplemented parts of the web platform
In general the same reasons that make jsdom pleasant for testing also make it pleasant for web scraping. In both cases, the extra power of a full browser is not as important as getting things done easily and quickly.
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.
## What Standards Does jsdom Support, Exactly?
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:
Our mission is to get something very close to a headless browser, with emphasis more on the DOM/HTML side of things than the CSS side. As such, our primary goals are supporting [The DOM Standard](http://dom.spec.whatwg.org/) and [The HTML Standard](http://www.whatwg.org/specs/web-apps/current-work/multipage/). We only support some subset of these so far; in particular we have the subset covered by the outdated DOM 2 spec family down pretty well. We're slowly including more and more from the modern DOM and HTML specs, including some `Node` APIs, `querySelector(All)`, attribute semantics, the history and URL APIs, and the HTML parsing algorithm.
- **Navigation**: the ability to change the global object, and all other objects, when clicking a link or assigning `location.href` or similar.
- **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`.
We also support some subset of the [CSSOM](http://dev.w3.org/csswg/cssom/), largely via [@chad3814](https://github.com/chad3814)'s excellent [cssstyle](https://www.npmjs.org/package/cssstyle) package. In general we want to make webpages run headlessly as best we can, and if there are other specs we should be incorporating, let us know.
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.
### Supported encodings
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/tmpvar/jsdom/wiki/jsdom-vs.-PhantomJS).
The supported encodings are the ones listed [in the Encoding Standard](https://encoding.spec.whatwg.org/#names-and-labels) excluding these:
## Getting help
- ISO-8859-8-I
- x-mac-cyrillic
- ISO-2022-JP
- replacement
- x-user-defined
If you need help with jsdom, please feel free to use any of the following venues:
- The [mailing list](http://groups.google.com/group/jsdom) (best for "how do I" questions)
- The [issue tracker](https://github.com/tmpvar/jsdom/issues) (best for bug reports)
- The IRC channel: [#jsdom on freenode](irc://irc.freenode.net/jsdom)

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc