jsdom
Advanced tools
Comparing version 12.0.0 to 12.1.0
@@ -75,3 +75,3 @@ "use strict"; | ||
runVMScript(script) { | ||
runVMScript(script, options) { | ||
if (!vm.isContext(this[window])) { | ||
@@ -82,3 +82,3 @@ throw new TypeError("This jsdom was not configured to allow script running. " + | ||
return script.runInContext(this[window]); | ||
return script.runInContext(this[window], options); | ||
} | ||
@@ -250,6 +250,2 @@ | ||
if (options.userAgent !== undefined) { | ||
transformed.windowOptions.userAgent = String(options.userAgent); | ||
} | ||
if (options.includeNodeLocations) { | ||
@@ -256,0 +252,0 @@ if (transformed.windowOptions.parsingMode === "xml") { |
"use strict"; | ||
const parse5 = require("parse5"); | ||
const sax = require("sax"); | ||
const saxes = require("saxes"); | ||
const attributes = require("../living/attributes"); | ||
@@ -32,3 +32,3 @@ const DocumentType = require("../living/generated/DocumentType"); | ||
constructor(parsingMode) { | ||
this.parser = parsingMode === "xml" ? sax : parse5; | ||
this.parser = parsingMode === "xml" ? saxes : parse5; | ||
} | ||
@@ -49,3 +49,3 @@ | ||
_doParse(...args) { | ||
return this.parser === parse5 ? this._parseWithParse5(...args) : this._parseWithSax(...args); | ||
return this.parser === parse5 ? this._parseWithParse5(...args) : this._parseWithSaxes(...args); | ||
} | ||
@@ -72,86 +72,75 @@ | ||
_parseWithSax(html, isFragment, contextNode) { | ||
const SaxParser = this.parser.parser; | ||
const parser = new SaxParser(/* strict = */true, { xmlns: true, strictEntities: true }); | ||
parser.noscript = false; | ||
parser.looseCase = "toString"; | ||
_parseWithSaxes(html, isFragment, contextNode) { | ||
const parserOptions = { xmlns: true }; | ||
if (isFragment) { | ||
parserOptions.fragment = true; | ||
parserOptions.resolvePrefix = prefix => { | ||
// saxes wants undefined as the return value if the prefix is not | ||
// defined, not null. | ||
return contextNode.lookupNamespaceURI(prefix) || undefined; | ||
}; | ||
} | ||
const parser = new this.parser.SaxesParser(parserOptions); | ||
const openStack = [contextNode]; | ||
const currentDocument = contextNode._ownerDocument || contextNode; | ||
parser.ontext = text => { | ||
setChildForSax(openStack[openStack.length - 1], { | ||
type: "text", | ||
data: text | ||
}); | ||
appendChild( | ||
openStack[openStack.length - 1], | ||
currentDocument.createTextNode(text) | ||
); | ||
}; | ||
parser.oncdata = cdata => { | ||
setChildForSax(openStack[openStack.length - 1], { | ||
type: "cdata", | ||
data: cdata | ||
}); | ||
appendChild( | ||
openStack[openStack.length - 1], | ||
currentDocument.createCDATASection(cdata) | ||
); | ||
}; | ||
parser.onopentag = arg => { | ||
const attrs = Object.keys(arg.attributes).map(key => { | ||
const rawAttribute = arg.attributes[key]; | ||
parser.onopentag = tag => { | ||
const { local: tagLocal, uri: tagURI, prefix: tagPrefix, attributes: tagAttributes } = tag; | ||
const elem = currentDocument._createElementWithCorrectElementInterface(tagLocal, tagURI); | ||
elem._prefix = tagPrefix || null; | ||
elem._namespaceURI = tagURI || null; | ||
// We mark a script element as "parser-inserted", which prevents it from | ||
// being immediately executed. | ||
if (tagLocal === "script" && tagURI === HTML_NS) { | ||
elem._parserInserted = true; | ||
} | ||
let { prefix } = rawAttribute; | ||
let localName = rawAttribute.local; | ||
if (prefix === "xmlns" && localName === "") { | ||
// intended weirdness in node-sax, see https://github.com/isaacs/sax-js/issues/165 | ||
localName = prefix; | ||
prefix = null; | ||
} | ||
for (const key of Object.keys(tagAttributes)) { | ||
const { prefix, local, uri, value } = tagAttributes[key]; | ||
attributes.setAttributeValue( | ||
elem, local, value, prefix === "" ? null : prefix, | ||
uri === "" ? null : uri | ||
); | ||
} | ||
if (prefix === "") { | ||
prefix = null; | ||
} | ||
const namespace = rawAttribute.uri === "" ? null : rawAttribute.uri; | ||
return { name: rawAttribute.name, value: rawAttribute.value, prefix, localName, namespace }; | ||
}); | ||
const tag = { | ||
type: "tag", | ||
name: arg.local, | ||
prefix: arg.prefix, | ||
namespace: arg.uri, | ||
attributes: attrs | ||
}; | ||
if (arg.local === "script" && arg.uri === HTML_NS) { | ||
openStack.push(tag); | ||
} else { | ||
const elem = setChildForSax(openStack[openStack.length - 1], tag); | ||
openStack.push(elem); | ||
} | ||
appendChild(openStack[openStack.length - 1], elem); | ||
openStack.push(elem); | ||
}; | ||
parser.onclosetag = () => { | ||
const elem = openStack.pop(); | ||
if (elem.constructor.name === "Object") { // we have an empty script tag | ||
setChildForSax(openStack[openStack.length - 1], elem); | ||
// Once a script is populated, we can execute it. | ||
if (elem.localName === "script" && elem.namespaceURI === HTML_NS) { | ||
elem._eval(); | ||
} | ||
}; | ||
parser.onscript = scriptText => { | ||
const tag = openStack.pop(); | ||
tag.children = [{ type: "text", data: scriptText }]; | ||
const elem = setChildForSax(openStack[openStack.length - 1], tag); | ||
openStack.push(elem); | ||
}; | ||
parser.oncomment = comment => { | ||
setChildForSax(openStack[openStack.length - 1], { | ||
type: "comment", | ||
data: comment | ||
}); | ||
appendChild( | ||
openStack[openStack.length - 1], | ||
currentDocument.createComment(comment) | ||
); | ||
}; | ||
parser.onprocessinginstruction = pi => { | ||
setChildForSax(openStack[openStack.length - 1], { | ||
type: "directive", | ||
name: "?" + pi.name, | ||
data: "?" + pi.name + " " + pi.body + "?" | ||
}); | ||
parser.onprocessinginstruction = ({ target, body }) => { | ||
appendChild( | ||
openStack[openStack.length - 1], | ||
currentDocument.createProcessingInstruction(target, body) | ||
); | ||
}; | ||
parser.ondoctype = dt => { | ||
setChildForSax(openStack[openStack.length - 1], { | ||
type: "directive", | ||
name: "!doctype", | ||
data: "!doctype " + dt | ||
}); | ||
appendChild( | ||
openStack[openStack.length - 1], | ||
parseDocType(currentDocument, `<!doctype ${dt}>`) | ||
); | ||
@@ -175,73 +164,10 @@ const entityMatcher = /<!ENTITY ([^ ]+) "([^"]+)">/g; | ||
function setChildForSax(parentImpl, node) { | ||
const currentDocument = (parentImpl && parentImpl._ownerDocument) || parentImpl; | ||
let newNode; | ||
let isTemplateContents = false; | ||
switch (node.type) { | ||
case "tag": | ||
case "script": | ||
case "style": | ||
newNode = currentDocument._createElementWithCorrectElementInterface(node.name, node.namespace); | ||
newNode._prefix = node.prefix || null; | ||
newNode._namespaceURI = node.namespace || null; | ||
break; | ||
case "root": | ||
// If we are in <template> then add all children to the parent's _templateContents; skip this virtual root node. | ||
if (parentImpl.tagName === "TEMPLATE" && parentImpl._namespaceURI === HTML_NS) { | ||
newNode = parentImpl._templateContents; | ||
isTemplateContents = true; | ||
} | ||
break; | ||
case "text": | ||
// HTML entities should already be decoded by the parser, so no need to decode them | ||
newNode = currentDocument.createTextNode(node.data); | ||
break; | ||
case "cdata": | ||
newNode = currentDocument.createCDATASection(node.data); | ||
break; | ||
case "comment": | ||
newNode = currentDocument.createComment(node.data); | ||
break; | ||
case "directive": | ||
if (node.name[0] === "?" && node.name.toLowerCase() !== "?xml") { | ||
const data = node.data.slice(node.name.length + 1, -1); | ||
newNode = currentDocument.createProcessingInstruction(node.name.substring(1), data); | ||
} else if (node.name.toLowerCase() === "!doctype") { | ||
newNode = parseDocType(currentDocument, "<" + node.data + ">"); | ||
} | ||
break; | ||
function appendChild(parent, child) { | ||
if (parent._templateContents) { | ||
// Template elements do not have children but instead store their content | ||
// in a separate hierarchy. | ||
parent._templateContents.appendChild(child); | ||
} else { | ||
parent.appendChild(child); | ||
} | ||
if (!newNode) { | ||
return null; | ||
} | ||
if (node.attributes) { | ||
for (const a of node.attributes) { | ||
attributes.setAttributeValue(newNode, a.localName, a.value, a.prefix, a.namespace); | ||
} | ||
} | ||
if (node.children) { | ||
for (let c = 0; c < node.children.length; c++) { | ||
setChildForSax(newNode, node.children[c]); | ||
} | ||
} | ||
if (!isTemplateContents) { | ||
if (parentImpl._templateContents) { | ||
// Setting innerHTML on a <template> | ||
parentImpl._templateContents.appendChild(newNode); | ||
} else { | ||
parentImpl.appendChild(newNode); | ||
} | ||
} | ||
return newNode; | ||
} | ||
@@ -248,0 +174,0 @@ |
"use strict"; | ||
class QueueItem { | ||
constructor(onLoad, onError, dependentItem) { | ||
this.onLoad = onLoad; | ||
this.onError = onError; | ||
this.data = null; | ||
this.error = null; | ||
this.dependentItem = dependentItem; | ||
} | ||
} | ||
/** | ||
@@ -10,6 +20,7 @@ * AsyncResourceQueue is the queue in charge of run the async scripts | ||
this.items = new Set(); | ||
this.dependentItems = new Set(); | ||
} | ||
count() { | ||
return this.items.size; | ||
return this.items.size + this.dependentItems.size; | ||
} | ||
@@ -23,2 +34,22 @@ | ||
_check(item) { | ||
let promise; | ||
if (item.onError && item.error) { | ||
promise = item.onError(item.error); | ||
} else if (item.onLoad && item.data) { | ||
promise = item.onLoad(item.data); | ||
} | ||
promise | ||
.then(() => { | ||
this.items.delete(item); | ||
this.dependentItems.delete(item); | ||
if (this.count() === 0) { | ||
this._notify(); | ||
} | ||
}); | ||
} | ||
setListener(listener) { | ||
@@ -28,33 +59,23 @@ this._listener = listener; | ||
push(request, onLoad, onError) { | ||
push(request, onLoad, onError, dependentItem) { | ||
const q = this; | ||
q.items.add(request); | ||
const item = new QueueItem(onLoad, onError, dependentItem); | ||
function check(error, data) { | ||
let promise; | ||
q.items.add(item); | ||
if (onError && error) { | ||
promise = onError(error); | ||
} else if (onLoad && data) { | ||
promise = onLoad(data); | ||
} | ||
return request | ||
.then(data => { | ||
item.data = data; | ||
promise | ||
.then(() => { | ||
q.items.delete(request); | ||
if (dependentItem && !dependentItem.finished) { | ||
q.dependentItems.add(item); | ||
return q.items.delete(item); | ||
} | ||
if (q.count() === 0) { | ||
q._notify(); | ||
} | ||
}); | ||
} | ||
return request | ||
.then(data => { | ||
if (onLoad) { | ||
return check(null, data); | ||
return q._check(item); | ||
} | ||
q.items.delete(request); | ||
q.items.delete(item); | ||
@@ -68,7 +89,14 @@ if (q.count() === 0) { | ||
.catch(err => { | ||
item.error = err; | ||
if (dependentItem && !dependentItem.finished) { | ||
q.dependentItems.add(item); | ||
return q.items.delete(item); | ||
} | ||
if (onError) { | ||
return check(err); | ||
return q._check(item); | ||
} | ||
q.items.delete(request); | ||
q.items.delete(item); | ||
@@ -82,2 +110,10 @@ if (q.count() === 0) { | ||
} | ||
notifyItem(syncItem) { | ||
for (const item of this.dependentItems) { | ||
if (item.dependentItem === syncItem) { | ||
this._check(item); | ||
} | ||
} | ||
} | ||
}; |
"use strict"; | ||
const idlUtils = require("../../living/generated/utils"); | ||
@@ -17,2 +18,3 @@ module.exports = class PerDocumentResourceLoader { | ||
cookieJar: this._document._cookieJar, | ||
element: idlUtils.wrapperForImpl(element), | ||
referrer: this._document.URL | ||
@@ -74,3 +76,3 @@ }); | ||
if (element.localName === "script" && element.hasAttribute("async")) { | ||
this._asyncQueue.push(request, onLoadWrapped, onErrorWrapped); | ||
this._asyncQueue.push(request, onLoadWrapped, onErrorWrapped, this._queue.getLastScript()); | ||
} else if (element.localName === "script" && element.hasAttribute("defer")) { | ||
@@ -77,0 +79,0 @@ this._deferQueue.push(request, onLoadWrapped, onErrorWrapped, false, element); |
@@ -8,6 +8,20 @@ "use strict"; | ||
module.exports = class ResourceQueue { | ||
constructor({ paused } = {}) { | ||
constructor({ paused, asyncQueue } = {}) { | ||
this.paused = Boolean(paused); | ||
this._asyncQueue = asyncQueue; | ||
} | ||
getLastScript() { | ||
let head = this.tail; | ||
while (head) { | ||
if (head.isScript) { | ||
return head; | ||
} | ||
head = head.prev; | ||
} | ||
return null; | ||
} | ||
_moreScripts() { | ||
@@ -75,2 +89,8 @@ let found = false; | ||
} | ||
this.finished = true; | ||
if (q._asyncQueue) { | ||
q._asyncQueue.notifyItem(this); | ||
} | ||
}); | ||
@@ -77,0 +97,0 @@ } |
@@ -106,2 +106,20 @@ "use strict"; | ||
get type() { | ||
if (!this || !module.exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
} | ||
return this[impl]["type"]; | ||
} | ||
get elements() { | ||
if (!this || !module.exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
} | ||
return utils.getSameObject(this, "elements", () => { | ||
return utils.tryWrapperForImpl(this[impl]["elements"]); | ||
}); | ||
} | ||
get willValidate() { | ||
@@ -140,2 +158,4 @@ if (!this || !module.exports.is(this)) { | ||
name: { enumerable: true }, | ||
type: { enumerable: true }, | ||
elements: { enumerable: true }, | ||
willValidate: { enumerable: true }, | ||
@@ -142,0 +162,0 @@ validity: { enumerable: true }, |
"use strict"; | ||
const { domSymbolTree } = require("./internal-constants"); | ||
const { TEXT_NODE } = require("../node-type"); | ||
const { CDATA_SECTION_NODE, TEXT_NODE } = require("../node-type"); | ||
// | ||
// https://dom.spec.whatwg.org/#concept-child-text-content | ||
// | ||
exports.childTextContent = node => { | ||
@@ -9,3 +12,5 @@ let result = ""; | ||
for (const child of iterator) { | ||
if (child.nodeType === TEXT_NODE) { | ||
if (child.nodeType === TEXT_NODE || | ||
// The CDataSection extends Text. | ||
child.nodeType === CDATA_SECTION_NODE) { | ||
result += child.data; | ||
@@ -12,0 +17,0 @@ } |
@@ -180,5 +180,5 @@ "use strict"; | ||
this._lastModified = toLastModifiedString(privateData.options.lastModified || new Date()); | ||
this._queue = new ResourceQueue({ paused: false }); | ||
this._asyncQueue = new AsyncResourceQueue(); | ||
this._queue = new ResourceQueue({ asyncQueue: this._asyncQueue, paused: false }); | ||
this._deferQueue = new ResourceQueue({ paused: true }); | ||
this._asyncQueue = new AsyncResourceQueue(); | ||
this._requestManager = new RequestManager(); | ||
@@ -486,19 +486,24 @@ this.readyState = "loading"; | ||
const onLoad = () => { | ||
this.readyState = "complete"; | ||
const ev = this.createEvent("HTMLEvents"); | ||
const doc = this; | ||
function dispatchEvent() { | ||
doc.readyState = "complete"; | ||
const ev = doc.createEvent("HTMLEvents"); | ||
ev.initEvent("load", false, false); | ||
ev.initEvent("load", false, false); | ||
this.dispatchEvent(ev); | ||
}; | ||
const waitForAsync = new Promise(resolve => { | ||
if (this._asyncQueue.count() === 0) { | ||
return resolve(); | ||
doc.dispatchEvent(ev); | ||
} | ||
return this._asyncQueue.setListener(() => { | ||
resolve(); | ||
return new Promise(resolve => { | ||
if (this._asyncQueue.count() === 0) { | ||
dispatchEvent(); | ||
return resolve(); | ||
} | ||
return this._asyncQueue.setListener(() => { | ||
dispatchEvent(); | ||
resolve(); | ||
}); | ||
}); | ||
}); | ||
}; | ||
@@ -508,3 +513,3 @@ this._queue.push(dummyPromise, onDOMContentLoad, null); | ||
// As a side-effect the document's load-event will be dispatched. | ||
this._queue.push(waitForAsync, onLoad, null, true); | ||
this._queue.push(dummyPromise, onLoad, null, true); | ||
} | ||
@@ -511,0 +516,0 @@ |
"use strict"; | ||
const HTMLCollection = require("../generated/HTMLCollection"); | ||
const HTMLElementImpl = require("./HTMLElement-impl").implementation; | ||
@@ -6,5 +7,14 @@ const DefaultConstraintValidationImpl = | ||
const { mixin } = require("../../utils"); | ||
const { closest } = require("../helpers/traversal"); | ||
const { closest, descendantsByHTMLLocalNames } = require("../helpers/traversal"); | ||
const listedElements = new Set(["button", "fieldset", "input", "object", "output", "select", "textarea"]); | ||
class HTMLFieldSetElementImpl extends HTMLElementImpl { | ||
get elements() { | ||
return HTMLCollection.createImpl([], { | ||
element: this, | ||
query: () => descendantsByHTMLLocalNames(this, listedElements) | ||
}); | ||
} | ||
get form() { | ||
@@ -14,2 +24,6 @@ return closest(this, "form"); | ||
get type() { | ||
return "fieldset"; | ||
} | ||
_barredFromConstraintValidationSpecialization() { | ||
@@ -16,0 +30,0 @@ return true; |
@@ -214,7 +214,19 @@ "use strict"; | ||
if (this.type === "checkbox" || (this.type === "radio" && !this._preCheckedRadioState)) { | ||
const inputEvent = Event.createImpl(["input", { isTrusted: true, bubbles: true, cancelable: true }], {}); | ||
this.dispatchEvent(inputEvent); | ||
const inputEvent = Event.createImpl( | ||
[ | ||
"input", | ||
{ bubbles: true, cancelable: false } | ||
], | ||
{ isTrusted: true } | ||
); | ||
this._dispatch(inputEvent); | ||
const changeEvent = Event.createImpl(["change", { bubbles: true, cancelable: true }], {}); | ||
this.dispatchEvent(changeEvent); | ||
const changeEvent = Event.createImpl( | ||
[ | ||
"change", | ||
{ bubbles: true, cancelable: false } | ||
], | ||
{ isTrusted: true } | ||
); | ||
this._dispatch(changeEvent); | ||
} else if (this.type === "submit") { | ||
@@ -221,0 +233,0 @@ const { form } = this; |
@@ -50,2 +50,6 @@ "use strict"; | ||
prepend(...nodes) { | ||
this.insertBefore(convertNodesIntoNode(this._ownerDocument, nodes), this.firstChild); | ||
} | ||
append(...nodes) { | ||
@@ -55,26 +59,22 @@ this.appendChild(convertNodesIntoNode(this._ownerDocument, nodes)); | ||
prepend(...nodes) { | ||
this.insertBefore(convertNodesIntoNode(this._ownerDocument, nodes), this.firstChild); | ||
querySelector(selectors) { | ||
if (shouldAlwaysSelectNothing(this)) { | ||
return null; | ||
} | ||
const matcher = addNwsapi(this); | ||
return idlUtils.implForWrapper(matcher.first(selectors, idlUtils.wrapperForImpl(this))); | ||
} | ||
} | ||
ParentNodeImpl.prototype.querySelector = function (selectors) { | ||
if (shouldAlwaysSelectNothing(this)) { | ||
return null; | ||
} | ||
const matcher = addNwsapi(this); | ||
return idlUtils.implForWrapper(matcher.first(selectors, idlUtils.wrapperForImpl(this))); | ||
}; | ||
// Warning for internal users: this returns a NodeList containing IDL wrappers instead of impls | ||
querySelectorAll(selectors) { | ||
if (shouldAlwaysSelectNothing(this)) { | ||
return NodeList.create([], { nodes: [] }); | ||
} | ||
const matcher = addNwsapi(this); | ||
const list = matcher.select(selectors, idlUtils.wrapperForImpl(this)); | ||
// WARNING: this returns a NodeList containing IDL wrappers instead of impls | ||
ParentNodeImpl.prototype.querySelectorAll = function (selectors) { | ||
if (shouldAlwaysSelectNothing(this)) { | ||
return NodeList.create([], { nodes: [] }); | ||
return NodeList.create([], { nodes: list.map(n => idlUtils.tryImplForWrapper(n)) }); | ||
} | ||
const matcher = addNwsapi(this); | ||
const list = matcher.select(selectors, idlUtils.wrapperForImpl(this)); | ||
} | ||
return NodeList.create([], { nodes: list.map(n => idlUtils.tryImplForWrapper(n)) }); | ||
}; | ||
function shouldAlwaysSelectNothing(elImpl) { | ||
@@ -81,0 +81,0 @@ // This is true during initialization. |
{ | ||
"name": "jsdom", | ||
"version": "12.0.0", | ||
"version": "12.1.0", | ||
"description": "A JavaScript implementation of many web standards", | ||
@@ -27,3 +27,3 @@ "keywords": [ | ||
"cssom": ">= 0.3.2 < 0.4.0", | ||
"cssstyle": "^1.0.0", | ||
"cssstyle": "^1.1.1", | ||
"data-urls": "^1.0.1", | ||
@@ -38,3 +38,3 @@ "domexception": "^1.0.1", | ||
"request-promise-native": "^1.0.5", | ||
"sax": "^1.2.4", | ||
"saxes": "^3.1.2", | ||
"symbol-tree": "^3.2.2", | ||
@@ -106,3 +106,6 @@ "tough-cookie": "^2.4.3", | ||
}, | ||
"main": "./lib/api.js" | ||
"main": "./lib/api.js", | ||
"engines": { | ||
"node": ">=8" | ||
} | ||
} |
@@ -155,3 +155,3 @@ <h1 align="center"> | ||
- `proxy` is the address of a HTTP proxy to be used. | ||
- `proxy` is the address of an HTTP proxy to be used. | ||
- `strictSSL` can be set to false to disable the requirement that SSL certificates be valid. | ||
@@ -167,3 +167,3 @@ - `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>. | ||
if (url === "https://example.com/some-specific-script.js") { | ||
return Buffer.from("window.someGlobal = 5;"); | ||
return Promise.resolve(Buffer.from("window.someGlobal = 5;")); | ||
} | ||
@@ -178,2 +178,16 @@ | ||
One of the options you will receive in `fetch()` will be the element (if applicable) that is fetching a resource. | ||
```js | ||
class CustomResourceLoader extends jsdom.ResourceLoader { | ||
fetch(url, options) { | ||
if (options.element) { | ||
console.log(`Element ${options.element.localName} is requestion the url ${url}`); | ||
} | ||
return super.fetch(url, options); | ||
} | ||
} | ||
``` | ||
### Virtual consoles | ||
@@ -299,3 +313,3 @@ | ||
### Running vm-created scripts with `runVMScript(script)` | ||
### Running vm-created scripts with `runVMScript(script[, options])` | ||
@@ -325,2 +339,4 @@ 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: | ||
`runVMScript()` also takes an `options` object as its second argument. See the [Node.js docs](https://nodejs.org/api/vm.html#vm_script_runincontext_contextifiedsandbox_options) for details. (This functionality does not work when [using jsdom in a web browser](running-jsdom-inside-a-web-browser).) | ||
### Reconfiguring the jsdom with `reconfigure(settings)` | ||
@@ -327,0 +343,0 @@ |
Sorry, the diff of this file is too big to display
1912588
54412
522
+ Addedsaxes@^3.1.2
+ Addedsaxes@3.1.11(transitive)
+ Addedxmlchars@2.2.0(transitive)
- Removedsax@^1.2.4
- Removedsax@1.3.0(transitive)
Updatedcssstyle@^1.1.1