UNPKG

kitchensink

Version:

Dispatch's awesome components and style guide

380 lines (324 loc) 10.8 kB
var fs = require('fs'); var path = require('path'); var URL = require('url'); var toFileUrl = require('./jsdom/utils').toFileUrl; var defineGetter = require('./jsdom/utils').defineGetter; var defineSetter = require('./jsdom/utils').defineSetter; var features = require('./jsdom/browser/documentfeatures'); var dom = require('./jsdom/living'); var browserAugmentation = require('./jsdom/browser/index').browserAugmentation; var domToHtml = require('./jsdom/browser/domtohtml').domToHtml; var VirtualConsole = require('./jsdom/virtual-console'); var canReadFilesFromFS = !!fs.readFile; // in a browserify environment, this isn't present var request = function() { // lazy loading request request = require('request'); return request.apply(undefined, arguments); } exports.getVirtualConsole = function (window) { return window._virtualConsole; }; exports.debugMode = false; // Proxy feature functions to features module. ['availableDocumentFeatures', 'defaultDocumentFeatures', 'applyDocumentFeatures'].forEach(function (propName) { defineGetter(exports, propName, function () { return features[propName]; }); defineSetter(exports, propName, function (val) { return features[propName] = val; }); }); exports.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; }; exports.jQueryify = exports.jsdom.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); }; exports.env = exports.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); }); } }; exports.serializeDocument = function (doc) { return domToHtml(doc, true); }; function processHTML(config) { var options = { features: config.features, url: config.url, parser: config.parser, parsingMode: config.parsingMode, created: config.created, resourceLoader: config.resourceLoader }; if (config.document) { options.referrer = config.document.referrer; options.cookie = config.document.cookie; options.cookieDomain = config.document.cookieDomain; } var window = exports.jsdom(config.html, options).parentWindow; var features = JSON.parse(JSON.stringify(window.document.implementation._features)); var docsLoaded = 0; var totalDocs = config.scripts.length + config.src.length; var readyState = null; var errors = []; if (!window || !window.document) { if (config.created) { config.created(new Error('JSDOM: a window object could not be created.')); } if (config.done) { config.done([new Error('JSDOM: a window object could not be created.')]); } return; } window.document.implementation._addFeature('FetchExternalResources', ['script']); window.document.implementation._addFeature('ProcessExternalResources', ['script']); window.document.implementation._addFeature('MutationEvents', ['2.0']); function scriptComplete() { docsLoaded++; if (docsLoaded >= totalDocs) { window.document.implementation._features = features; errors = errors.concat(window.document.errors || []); if (errors.length === 0) { errors = null; } process.nextTick(function() { if (config.loaded) { config.loaded(errors, window); } if (config.done) { config.done(errors, window); } }); } } function handleScriptError(e) { if (!errors) { errors = []; } errors.push(e.error || e.message); // nextTick so that an exception within scriptComplete won't cause // another script onerror (which would be an infinite loop) process.nextTick(scriptComplete); } if (config.scripts.length > 0 || config.src.length > 0) { config.scripts.forEach(function (scriptSrc) { var script = window.document.createElement('script'); script.className = 'jsdom'; script.onload = scriptComplete; script.onerror = handleScriptError; script.src = scriptSrc; try { // protect against invalid dom // ex: http://www.google.com/foo#bar window.document.documentElement.appendChild(script); } catch (e) { handleScriptError(e); } }); config.src.forEach(function (scriptText) { var script = window.document.createElement('script'); script.onload = scriptComplete; script.onerror = handleScriptError; script.text = scriptText; window.document.documentElement.appendChild(script); window.document.documentElement.removeChild(script); }); } else { if (window.document.readyState === 'complete') { scriptComplete(); } else { window.addEventListener('load', function() { scriptComplete(); }); } } } function getConfigFromArguments(args, callback) { var config = {}; if (typeof args[0] === 'object') { var configToClone = args[0]; Object.keys(configToClone).forEach(function (key) { config[key] = configToClone[key]; }); } else { var stringToAutodetect = null; Array.prototype.forEach.call(args, function (arg) { switch (typeof arg) { case 'string': config.somethingToAutodetect = arg; break; case 'function': config.done = arg; break; case 'object': if (Array.isArray(arg)) { config.scripts = arg; } else { extend(config, arg); } break; } }); } if (!config.done && !config.created && !config.loaded) { throw new Error('Must pass a "created", "loaded", "done" option or a callback to jsdom.env.'); } if (config.somethingToAutodetect === undefined && config.html === undefined && !config.file && !config.url) { throw new Error('Must pass a "html", "file", or "url" option, or a string, to jsdom.env'); } config.scripts = ensureArray(config.scripts); config.src = ensureArray(config.src); config.parsingMode = config.parsingMode || "auto"; config.features = config.features || { FetchExternalResources: false, ProcessExternalResources: false, SkipExternalResources: false }; if (!config.url && config.file) { config.url = toFileUrl(config.file); } return config; } function ensureArray(value) { var array = value || []; if (typeof array === 'string') { array = [array]; } return array; } function extend(config, overrides) { Object.keys(overrides).forEach(function (key) { config[key] = overrides[key]; }); } function setParsingModeFromExtension(config, filename) { if (config.parsingMode === "auto") { var ext = path.extname(filename); if (ext === ".xhtml" || ext === ".xml") { config.parsingMode = "xml"; } } }