mazda-specs-crepe/node_modules/jsdom/lib/jsdom.js
2013-02-19 18:02:59 +11:00

368 lines
10 KiB
JavaScript

var dom = exports.dom = require("./jsdom/level3/index").dom,
features = require('./jsdom/browser/documentfeatures'),
fs = require("fs"),
pkg = JSON.parse(fs.readFileSync(__dirname + "/../package.json")),
request = require('request'),
URL = require('url');
var style = require('./jsdom/level2/style');
exports.defaultLevel = dom.level3.html;
exports.browserAugmentation = require("./jsdom/browser/index").browserAugmentation;
exports.windowAugmentation = require("./jsdom/browser/index").windowAugmentation;
// Proxy feature functions to features module.
['availableDocumentFeatures',
'defaultDocumentFeatures',
'applyDocumentFeatures'].forEach(function (propName) {
exports.__defineGetter__(propName, function () {
return features[propName];
});
exports.__defineSetter__(propName, function (val) {
return features[propName] = val;
});
});
exports.debugMode = false;
var createWindow = exports.createWindow = require("./jsdom/browser/index").createWindow;
exports.__defineGetter__('version', function() {
return pkg.version;
});
exports.level = function (level, feature) {
if(!feature) {
feature = 'core';
}
return require('./jsdom/level' + level + '/' + feature).dom['level' + level][feature];
};
exports.jsdom = function (html, level, options) {
options = options || {};
if(typeof level == "string") {
level = exports.level(level, 'html');
} else {
level = level || exports.defaultLevel;
}
if (!options.url) {
options.url = (module.parent.id === 'jsdom') ?
module.parent.parent.filename :
module.parent.filename;
options.url = options.url.replace(/\\/g, '/');
if (options.url[0] !== '/') {
options.url = '/' + options.url;
}
options.url = 'file://' + options.url;
}
var browser = exports.browserAugmentation(level, options),
doc = (browser.HTMLDocument) ?
new browser.HTMLDocument(options) :
new browser.Document(options);
require("./jsdom/selectors/index").applyQuerySelector(doc, level);
features.applyDocumentFeatures(doc, options.features);
if (typeof html === 'undefined' || html === null) {
doc.write('<html><head></head><body></body></html>');
} else {
doc.write(html + '');
}
if (doc.close && !options.deferClose) {
doc.close();
}
// Kept for backwards-compatibility. The window is lazily created when
// document.parentWindow or document.defaultView is accessed.
doc.createWindow = function() {
// Remove ourself
if (doc.createWindow) {
delete doc.createWindow;
}
return doc.parentWindow;
};
return doc;
};
exports.html = function(html, level, options) {
html += '';
// TODO: cache a regex and use it here instead
// or make the parser handle it
var htmlLowered = html.toLowerCase();
// body
if (!~htmlLowered.indexOf('<body')) {
html = '<body>' + html + '</body>';
}
// html
if (!~htmlLowered.indexOf('<html')) {
html = '<html>' + html + '</html>';
}
return exports.jsdom(html, level, options);
};
exports.jQueryify = exports.jsdom.jQueryify = function (window /* path [optional], callback */) {
if (!window || !window.document) { return; }
var args = Array.prototype.slice.call(arguments),
callback = (typeof(args[args.length - 1]) === 'function') && args.pop(),
path,
jQueryTag = window.document.createElement("script");
jQueryTag.className = "jsdom";
if (args.length > 1 && typeof(args[1] === 'string')) {
path = args[1];
}
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"]);
jQueryTag.src = path || 'http://code.jquery.com/jquery-latest.js';
window.document.body.appendChild(jQueryTag);
jQueryTag.onload = function() {
if (callback) {
callback(window, window.jQuery);
}
window.document.implementation._features = features;
};
return window;
};
exports.env = exports.jsdom.env = function() {
var
args = Array.prototype.slice.call(arguments),
config = exports.env.processArguments(args),
callback = config.done,
processHTML = function(err, html) {
html += '';
if(err) {
return callback(err);
}
config.scripts = config.scripts || [];
if (typeof config.scripts === 'string') {
config.scripts = [config.scripts];
}
config.src = config.src || [];
if (typeof config.src === 'string') {
config.src = [config.src];
}
var
options = {
features: config.features || {
'FetchExternalResources' : false,
'ProcessExternalResources' : false,
'SkipExternalResources' : false
},
url: config.url
},
window = exports.html(html, null, options).createWindow(),
features = JSON.parse(JSON.stringify(window.document.implementation._features)),
docsLoaded = 0,
totalDocs = config.scripts.length + config.src.length,
readyState = null,
errors = null;
if (!window || !window.document) {
return callback(new Error('JSDOM: a window object could not be created.'));
}
if( config.document ) {
window.document._referrer = config.document.referrer;
window.document._cookie = config.document.cookie;
}
window.document.implementation.addFeature('FetchExternalResources', ['script']);
window.document.implementation.addFeature('ProcessExternalResources', ['script']);
window.document.implementation.addFeature('MutationEvents', ['2.0']);
var scriptComplete = function() {
docsLoaded++;
if (docsLoaded >= totalDocs) {
window.document.implementation._features = features;
if (errors) {
errors = errors.concat(window.document.errors || []);
}
process.nextTick(function() { callback(errors, window); });
}
}
if (config.scripts.length > 0 || config.src.length > 0) {
config.scripts.forEach(function(src) {
var script = window.document.createElement('script');
script.className = "jsdom";
script.onload = function() {
scriptComplete()
};
script.onerror = function(e) {
if (!errors) {
errors = [];
}
errors.push(e.error);
scriptComplete();
};
script.src = src;
try {
// project against invalid dom
// ex: http://www.google.com/foo#bar
window.document.documentElement.appendChild(script);
} catch(e) {
if(!errors) {
errors=[];
}
errors.push(e.error || e.message);
scriptComplete();
}
});
config.src.forEach(function(src) {
var script = window.document.createElement('script');
script.onload = function() {
process.nextTick(scriptComplete);
};
script.onerror = function(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);
};
script.text = src;
window.document.documentElement.appendChild(script);
window.document.documentElement.removeChild(script);
});
} else {
scriptComplete();
}
};
config.html += '';
// Handle markup
if (config.html.indexOf("\n") > 0 || config.html.match(/^\W*</)) {
processHTML(null, config.html);
// Handle url/file
} else {
var url = URL.parse(config.html);
config.url = config.url || url.href;
if (url.hostname) {
request({
uri : url,
encoding : config.encoding || 'utf8',
headers : config.headers || {},
proxy : config.proxy || null
},
function(err, request, body) {
processHTML(err, body);
});
} else {
fs.readFile(config.html, processHTML);
}
}
};
/*
Since jsdom.env() is a helper for quickly and easily setting up a
window with scripts and such already loaded into it, the arguments
should be fairly flexible. Here are the requirements
1) collect `html` (url, string, or file on disk) (STRING)
2) load `code` into the window (array of scripts) (ARRAY)
3) callback when resources are `done` (FUNCTION)
4) configuration (OBJECT)
Rules:
+ if there is one argument it had better be an object with atleast
a `html` and `done` property (other properties are gravy)
+ arguments above are pulled out of the arguments and put into the
config object that is returned
*/
exports.env.processArguments = function(args) {
if (!args || !args.length || args.length < 1) {
throw new Error('No arguments passed to jsdom.env().');
}
var
props = {
'html' : true,
'done' : true,
'scripts' : false,
'config' : false,
'url' : false, // the URL for location.href if different from html
'document': false // HTMLDocument properties
},
propKeys = Object.keys(props),
config = {
code : []
},
l = args.length
;
if (l === 1) {
config = args[0];
} else {
args.forEach(function(v) {
var type = typeof v;
if (!v) {
return;
}
if (type === 'string' || v + '' === v) {
config.html = v;
} else if (type === 'object') {
// Array
if (v.length && v[0]) {
config.scripts = v;
} else {
// apply missing required properties if appropriate
propKeys.forEach(function(req) {
if (typeof v[req] !== 'undefined' &&
typeof config[req] === 'undefined') {
config[req] = v[req];
delete v[req];
}
});
config.config = v;
}
} else if (type === 'function') {
config.done = v;
}
});
}
propKeys.forEach(function(req) {
var required = props[req];
if (required && typeof config[req] === 'undefined') {
throw new Error("jsdom.env requires a '" + req + "' argument");
}
});
return config;
};