| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- var dom = exports.dom = require("./jsdom/level3/index").dom,
- 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;
- 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;
- }
- if (options.features && options.features.QuerySelector) {
- require("./jsdom/selectors/index").applyQuerySelectorPrototype(level);
- }
- var browser = exports.browserAugmentation(level, options),
- doc = (browser.HTMLDocument) ?
- new browser.HTMLDocument(options) :
- new browser.Document(options);
- exports.applyDocumentFeatures(doc, options.features);
-
- if (!!html) {
- doc.write(html + '');
- } else {
- doc.write('<html><head></head><body></body></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.availableDocumentFeatures = [
- 'FetchExternalResources',
- 'ProcessExternalResources',
- 'MutationEvents',
- 'QuerySelector'
- ];
- exports.defaultDocumentFeatures = {
- "FetchExternalResources" : ['script'/*, 'img', 'css', 'frame', 'link'*/],
- "ProcessExternalResources" : ['script'/*, 'frame', 'iframe'*/],
- "MutationEvents" : '2.0',
- "QuerySelector" : false
- };
- exports.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];
- } 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);
- }
- }
- }
- };
- 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");
- 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', ["1.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: {
- 'FetchExternalResources' : false,
- 'ProcessExternalResources' : false
- },
- url: config.url
- },
- window = exports.html(html, null, options).createWindow(),
- features = window.document.implementation._features,
- docsLoaded = 0,
- totalDocs = config.scripts.length,
- readyState = null,
- errors = null;
- if (!window || !window.document) {
- return callback(new Error('JSDOM: a window object could not be created.'));
- }
- window.document.implementation.addFeature('FetchExternalResources', ['script']);
- window.document.implementation.addFeature('ProcessExternalResources', ['script']);
- window.document.implementation.addFeature('MutationEvents', ['1.0']);
- var scriptComplete = function() {
- docsLoaded++;
- if (docsLoaded >= totalDocs) {
- window.document.implementation._features = features;
- callback(errors, window);
- }
- }
- if (config.scripts.length > 0 || config.src.length > 0) {
- config.scripts.forEach(function(src) {
- var script = window.document.createElement('script');
- script.onload = function() {
- scriptComplete()
- };
- script.onerror = function(e) {
- if (!errors) {
- errors = [];
- }
- errors.push(e.error);
- scriptComplete();
- };
- script.src = src;
- window.document.documentElement.appendChild(script);
- });
- 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 {
- callback(errors, window);
- }
- };
- 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 || {}
- },
- function(err, request, body) {
- processHTML(err, body);
- });
- } else {
- fs.readFile(url.pathname, 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
- },
- 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;
- };
|