applyDocumentFeatures = function (doc, features) {
var i, maxFeatures = exports.availableDocumentFeatures.length,
defaultFeatures = exports.defaultDocumentFeatures,
j,
k,
featureName,
featureSource;
features = features || {};
for (i=0; i<maxFeatures; i++) {
featureName = exports.availableDocumentFeatures[i];
if (typeof features[featureName] !== 'undefined') {
featureSource = features[featureName];
// We have to check the lowercase version also because the Document feature
// methods convert everything to lowercase.
} else if (typeof features[featureName.toLowerCase()] !== 'undefined') {
featureSource = features[featureName.toLowerCase()];
} else if (defaultFeatures[featureName]) {
featureSource = defaultFeatures[featureName];
} else {
continue;
}
doc.implementation._removeFeature(featureName);
if (typeof featureSource !== 'undefined') {
if (featureSource instanceof Array) {
k = featureSource.length;
for (j=0; j<k; j++) {
doc.implementation._addFeature(featureName, featureSource[j]);
}
} else {
doc.implementation._addFeature(featureName, featureSource);
}
}
}
}...
var browser = browserAugmentation(dom, options);
var doc = new browser.HTMLDocument(options);
if (options.created) {
options.created(null, doc.parentWindow);
}
features.applyDocumentFeatures(doc, options.features);
if (html === undefined) {
html = '';
}
html = String(html);
doc.write(html);
...env = function () {
var config = getConfigFromArguments(arguments);
if (config.file && canReadFilesFromFS) {
fs.readFile(config.file, 'utf-8', function (err, text) {
if (err) {
if (config.created) {
config.created(err);
}
if (config.done) {
config.done([err]);
}
return;
}
setParsingModeFromExtension(config, config.file);
config.html = text;
processHTML(config);
});
} else if (config.html !== undefined) {
processHTML(config);
} else if (config.url) {
handleUrl(config);
} else if (config.somethingToAutodetect !== undefined) {
var url = URL.parse(config.somethingToAutodetect);
if (url.protocol && url.hostname) {
config.url = config.somethingToAutodetect;
handleUrl(config.somethingToAutodetect);
} else if (canReadFilesFromFS) {
fs.readFile(config.somethingToAutodetect, 'utf-8', function (err, text) {
if (err) {
// the toString() test is because in Node.js, there is no proper code for this.
// This is fixed in io.js: https://github.com/iojs/io.js/issues/517 so:
// TODO: remove when we start requiring io.js
if (err.code === 'ENOENT' || err.code === 'ENAMETOOLONG'
|| (err.toString() == 'Error: Path must be a string without null bytes.')
) {
config.html = config.somethingToAutodetect;
processHTML(config);
} else {
if (config.created) {
config.created(err);
}
if (config.done) {
config.done([err]);
}
}
} else {
setParsingModeFromExtension(config, config.somethingToAutodetect);
config.html = text;
config.url = toFileUrl(config.somethingToAutodetect);
processHTML(config);
}
});
} else {
config.html = config.somethingToAutodetect;
processHTML(config);
}
}
function handleUrl() {
var options = {
uri: config.url,
encoding: config.encoding || 'utf8',
headers: config.headers || {},
proxy: config.proxy || null,
jar: config.jar !== undefined ? config.jar : true
};
request(options, function (err, res, responseText) {
if (err) {
if (config.created) {
config.created(err);
}
if (config.done) {
config.done([err]);
}
return;
}
// The use of `res.request.uri.href` ensures that `window.location.href`
// is updated when `request` follows redirects.
config.html = responseText;
config.url = res.request.uri.href;
if (config.parsingMode === "auto" && (
res.headers["content-type"] === "application/xml" ||
res.headers["content-type"] === "text/xml" ||
res.headers["content-type"] === "application/xhtml+xml")) {
config.parsingMode = "xml";
}
processHTML(config);
});
}
}...
You can use it with a URL
```js
// Count all of the links from the Node.js build page
var jsdom = require("node-jsdom");
jsdom.env(
"http://nodejs.org/dist/",
["http://code.jquery.com/jquery.js"],
function (errors, window) {
console.log("there have been", window.$("a").length, "nodejs releases!");
}
);
```
...getVirtualConsole = function (window) {
return window._virtualConsole;
}...
#### Forward a window's console output to the Node.js console
```js
var jsdom = require("node-jsdom");
var window = jsdom.jsdom().parentWindow;
jsdom.getVirtualConsole(window).sendTo(console);
```
#### Get an event emitter for a window's console
```js
var jsdom = require("node-jsdom");
var window = jsdom.jsdom().parentWindow;
...jQueryify = function (window, jqueryUrl, callback) {
if (!window || !window.document) {
return;
}
var features = window.document.implementation._features;
window.document.implementation._addFeature('FetchExternalResources', ['script']);
window.document.implementation._addFeature('ProcessExternalResources', ['script']);
window.document.implementation._addFeature('MutationEvents', ['2.0']);
var scriptEl = window.document.createElement('script');
scriptEl.className = 'jsdom';
scriptEl.src = jqueryUrl;
scriptEl.onload = scriptEl.onerror = function () {
window.document.implementation._features = features;
if (callback) {
callback(window, window.jQuery);
}
};
window.document.body.appendChild(scriptEl);
}...
### jQueryify
```js
var jsdom = require("node-jsdom");
var window = jsdom.jsdom().parentWindow;
jsdom.jQueryify(window, "http://code.jquery.com/jquery-2.1.1.js", function () {
window.$("body").append('<div class="testing">Hello World, It works</div>');
console.log(window.$(".testing").text());
});
```
### Passing objects to scripts inside the page
...jsdom = function (html, options) {
if (options === undefined) {
options = {};
}
if (options.parsingMode === undefined || options.parsingMode === 'auto') {
options.parsingMode = 'html';
}
var browser = browserAugmentation(dom, options);
var doc = new browser.HTMLDocument(options);
if (options.created) {
options.created(null, doc.parentWindow);
}
features.applyDocumentFeatures(doc, options.features);
if (html === undefined) {
html = '';
}
html = String(html);
doc.write(html);
if (doc.close && !options.deferClose) {
doc.close();
}
return doc;
}...
#### 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 side:
var window = jsdom.jsdom(...).parentWindow;
window.onModulesLoaded = function () {
console.log("ready to roll!");
};
```
```html
<!-- Inside the HTML you supply to jsdom -->
...serializeDocument = function (doc) {
return domToHtml(doc, true);
}n/a
jsdom = function (html, options) {
if (options === undefined) {
options = {};
}
if (options.parsingMode === undefined || options.parsingMode === 'auto') {
options.parsingMode = 'html';
}
var browser = browserAugmentation(dom, options);
var doc = new browser.HTMLDocument(options);
if (options.created) {
options.created(null, doc.parentWindow);
}
features.applyDocumentFeatures(doc, options.features);
if (html === undefined) {
html = '';
}
html = String(html);
doc.write(html);
if (doc.close && !options.deferClose) {
doc.close();
}
return doc;
}...
#### 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 side:
var window = jsdom.jsdom(...).parentWindow;
window.onModulesLoaded = function () {
console.log("ready to roll!");
};
```
```html
<!-- Inside the HTML you supply to jsdom -->
...env = function () {
var config = getConfigFromArguments(arguments);
if (config.file && canReadFilesFromFS) {
fs.readFile(config.file, 'utf-8', function (err, text) {
if (err) {
if (config.created) {
config.created(err);
}
if (config.done) {
config.done([err]);
}
return;
}
setParsingModeFromExtension(config, config.file);
config.html = text;
processHTML(config);
});
} else if (config.html !== undefined) {
processHTML(config);
} else if (config.url) {
handleUrl(config);
} else if (config.somethingToAutodetect !== undefined) {
var url = URL.parse(config.somethingToAutodetect);
if (url.protocol && url.hostname) {
config.url = config.somethingToAutodetect;
handleUrl(config.somethingToAutodetect);
} else if (canReadFilesFromFS) {
fs.readFile(config.somethingToAutodetect, 'utf-8', function (err, text) {
if (err) {
// the toString() test is because in Node.js, there is no proper code for this.
// This is fixed in io.js: https://github.com/iojs/io.js/issues/517 so:
// TODO: remove when we start requiring io.js
if (err.code === 'ENOENT' || err.code === 'ENAMETOOLONG'
|| (err.toString() == 'Error: Path must be a string without null bytes.')
) {
config.html = config.somethingToAutodetect;
processHTML(config);
} else {
if (config.created) {
config.created(err);
}
if (config.done) {
config.done([err]);
}
}
} else {
setParsingModeFromExtension(config, config.somethingToAutodetect);
config.html = text;
config.url = toFileUrl(config.somethingToAutodetect);
processHTML(config);
}
});
} else {
config.html = config.somethingToAutodetect;
processHTML(config);
}
}
function handleUrl() {
var options = {
uri: config.url,
encoding: config.encoding || 'utf8',
headers: config.headers || {},
proxy: config.proxy || null,
jar: config.jar !== undefined ? config.jar : true
};
request(options, function (err, res, responseText) {
if (err) {
if (config.created) {
config.created(err);
}
if (config.done) {
config.done([err]);
}
return;
}
// The use of `res.request.uri.href` ensures that `window.location.href`
// is updated when `request` follows redirects.
config.html = responseText;
config.url = res.request.uri.href;
if (config.parsingMode === "auto" && (
res.headers["content-type"] === "application/xml" ||
res.headers["content-type"] === "text/xml" ||
res.headers["content-type"] === "application/xhtml+xml")) {
config.parsingMode = "xml";
}
processHTML(config);
});
}
}...
You can use it with a URL
```js
// Count all of the links from the Node.js build page
var jsdom = require("node-jsdom");
jsdom.env(
"http://nodejs.org/dist/",
["http://code.jquery.com/jquery.js"],
function (errors, window) {
console.log("there have been", window.$("a").length, "nodejs releases!");
}
);
```
...jQueryify = function (window, jqueryUrl, callback) {
if (!window || !window.document) {
return;
}
var features = window.document.implementation._features;
window.document.implementation._addFeature('FetchExternalResources', ['script']);
window.document.implementation._addFeature('ProcessExternalResources', ['script']);
window.document.implementation._addFeature('MutationEvents', ['2.0']);
var scriptEl = window.document.createElement('script');
scriptEl.className = 'jsdom';
scriptEl.src = jqueryUrl;
scriptEl.onload = scriptEl.onerror = function () {
window.document.implementation._features = features;
if (callback) {
callback(window, window.jQuery);
}
};
window.document.body.appendChild(scriptEl);
}...
### jQueryify
```js
var jsdom = require("node-jsdom");
var window = jsdom.jsdom().parentWindow;
jsdom.jQueryify(window, "http://code.jquery.com/jquery-2.1.1.js", function () {
window.$("body").append('<div class="testing">Hello World, It works</div>');
console.log(window.$(".testing").text());
});
```
### Passing objects to scripts inside the page
...