1 var dom
= exports
.dom
= require("./jsdom/level3/index").dom
,
2 features
= require('./jsdom/browser/documentfeatures'),
4 pkg
= JSON
.parse(fs
.readFileSync(__dirname
+ "/../package.json")),
5 request
= require('request'),
8 var style
= require('./jsdom/level2/style');
9 exports
.defaultLevel
= dom
.level3
.html
;
10 exports
.browserAugmentation
= require("./jsdom/browser/index").browserAugmentation
;
11 exports
.windowAugmentation
= require("./jsdom/browser/index").windowAugmentation
;
13 // Proxy feature functions to features module.
14 ['availableDocumentFeatures',
15 'defaultDocumentFeatures',
16 'applyDocumentFeatures'].forEach(function (propName
) {
17 exports
.__defineGetter__(propName
, function () {
18 return features
[propName
];
20 exports
.__defineSetter__(propName
, function (val
) {
21 return features
[propName
] = val
;
25 exports
.debugMode
= false;
27 var createWindow
= exports
.createWindow
= require("./jsdom/browser/index").createWindow
;
29 exports
.__defineGetter__('version', function() {
33 exports
.level = function (level
, feature
) {
34 if(!feature
) feature
= 'core'
35 return require('./jsdom/level' + level
+ '/' + feature
).dom
['level' + level
][feature
]
38 exports
.jsdom = function (html
, level
, options
) {
40 options
= options
|| {};
41 if(typeof level
== "string") {
42 level
= exports
.level(level
, 'html')
44 level
= level
|| exports
.defaultLevel
;
48 options
.url
= (module
.parent
.id
=== 'jsdom') ?
49 module
.parent
.parent
.filename
:
50 module
.parent
.filename
;
53 var browser
= exports
.browserAugmentation(level
, options
),
54 doc
= (browser
.HTMLDocument
) ?
55 new browser
.HTMLDocument(options
) :
56 new browser
.Document(options
);
58 if (options
.features
&& options
.features
.QuerySelector
) {
59 require("./jsdom/selectors/index").applyQuerySelector(doc
, level
);
62 features
.applyDocumentFeatures(doc
, options
.features
);
64 if (typeof html
=== 'undefined' || html
=== null) {
65 doc
.write('<html><head></head><body></body></html>');
70 if (doc
.close
&& !options
.deferClose
) {
74 // Kept for backwards-compatibility. The window is lazily created when
75 // document.parentWindow or document.defaultView is accessed.
76 doc
.createWindow = function() {
78 if (doc
.createWindow
) {
79 delete doc
.createWindow
;
81 return doc
.parentWindow
;
87 exports
.html = function(html
, level
, options
) {
90 // TODO: cache a regex and use it here instead
91 // or make the parser handle it
92 var htmlLowered
= html
.toLowerCase();
95 if (!~htmlLowered
.indexOf('<body')) {
96 html
= '<body>' + html
+ '</body>';
100 if (!~htmlLowered
.indexOf('<html')) {
101 html
= '<html>' + html
+ '</html>';
103 return exports
.jsdom(html
, level
, options
);
106 exports
.jQueryify
= exports
.jsdom
.jQueryify = function (window
/* path [optional], callback */) {
108 if (!window
|| !window
.document
) { return; }
110 var args
= Array
.prototype.slice
.call(arguments
),
111 callback
= (typeof(args
[args
.length
- 1]) === 'function') && args
.pop(),
113 jQueryTag
= window
.document
.createElement("script");
114 jQueryTag
.className
= "jsdom";
116 if (args
.length
> 1 && typeof(args
[1] === 'string')) {
120 var features
= window
.document
.implementation
._features
;
122 window
.document
.implementation
.addFeature('FetchExternalResources', ['script']);
123 window
.document
.implementation
.addFeature('ProcessExternalResources', ['script']);
124 window
.document
.implementation
.addFeature('MutationEvents', ["1.0"]);
125 jQueryTag
.src
= path
|| 'http://code.jquery.com/jquery-latest.js';
126 window
.document
.body
.appendChild(jQueryTag
);
128 jQueryTag
.onload = function() {
130 callback(window
, window
.jQuery
);
133 window
.document
.implementation
._features
= features
;
140 exports
.env
= exports
.jsdom
.env = function() {
142 args
= Array
.prototype.slice
.call(arguments
),
143 config
= exports
.env
.processArguments(args
),
144 callback
= config
.done
,
145 processHTML = function(err
, html
) {
149 return callback(err
);
152 config
.scripts
= config
.scripts
|| [];
153 if (typeof config
.scripts
=== 'string') {
154 config
.scripts
= [config
.scripts
];
157 config
.src
= config
.src
|| [];
158 if (typeof config
.src
=== 'string') {
159 config
.src
= [config
.src
];
164 features
: config
.features
|| {
165 'FetchExternalResources' : false,
166 'ProcessExternalResources' : false
170 window
= exports
.html(html
, null, options
).createWindow(),
171 features
= JSON
.parse(JSON
.stringify(window
.document
.implementation
._features
)),
173 totalDocs
= config
.scripts
.length
+ config
.src
.length
,
177 if (!window
|| !window
.document
) {
178 return callback(new Error('JSDOM: a window object could not be created.'));
181 if( config
.document
) {
182 window
.document
._referrer
= config
.document
.referrer
;
183 window
.document
._cookie
= config
.document
.cookie
;
186 window
.document
.implementation
.addFeature('FetchExternalResources', ['script']);
187 window
.document
.implementation
.addFeature('ProcessExternalResources', ['script']);
188 window
.document
.implementation
.addFeature('MutationEvents', ['1.0']);
190 var scriptComplete = function() {
192 if (docsLoaded
>= totalDocs
) {
193 window
.document
.implementation
._features
= features
;
196 errors
= errors
.concat(window
.document
.errors
|| []);
199 process
.nextTick(function() { callback(errors
, window
); });
203 if (config
.scripts
.length
> 0 || config
.src
.length
> 0) {
204 config
.scripts
.forEach(function(src
) {
205 var script
= window
.document
.createElement('script');
206 script
.className
= "jsdom";
207 script
.onload = function() {
211 script
.onerror = function(e
) {
215 errors
.push(e
.error
);
221 // project against invalid dom
222 // ex: http://www.google.com/foo#bar
223 window
.document
.documentElement
.appendChild(script
);
228 errors
.push(e
.error
|| e
.message
);
233 config
.src
.forEach(function(src
) {
234 var script
= window
.document
.createElement('script');
235 script
.onload = function() {
236 process
.nextTick(scriptComplete
);
239 script
.onerror = function(e
) {
243 errors
.push(e
.error
|| e
.message
);
244 // nextTick so that an exception within scriptComplete won't cause
245 // another script onerror (which would be an infinite loop)
246 process
.nextTick(scriptComplete
);
250 window
.document
.documentElement
.appendChild(script
);
251 window
.document
.documentElement
.removeChild(script
);
261 if (config
.html
.indexOf("\n") > 0 || config
.html
.match(/^\W*</)) {
262 processHTML(null, config
.html
);
266 var url
= URL
.parse(config
.html
);
267 config
.url
= config
.url
|| url
.href
;
271 encoding
: config
.encoding
|| 'utf8',
272 headers
: config
.headers
|| {},
273 proxy
: config
.proxy
|| null
275 function(err
, request
, body
) {
276 processHTML(err
, body
);
279 fs
.readFile(config
.html
, processHTML
);
285 Since jsdom.env() is a helper for quickly and easily setting up a
286 window with scripts and such already loaded into it, the arguments
287 should be fairly flexible. Here are the requirements
289 1) collect `html` (url, string, or file on disk) (STRING)
290 2) load `code` into the window (array of scripts) (ARRAY)
291 3) callback when resources are `done` (FUNCTION)
292 4) configuration (OBJECT)
295 + if there is one argument it had better be an object with atleast
296 a `html` and `done` property (other properties are gravy)
298 + arguments above are pulled out of the arguments and put into the
299 config object that is returned
301 exports
.env
.processArguments = function(args
) {
302 if (!args
|| !args
.length
|| args
.length
< 1) {
303 throw new Error('No arguments passed to jsdom.env().');
312 'url' : false, // the URL for location.href if different from html
313 'document': false // HTMLDocument properties
315 propKeys
= Object
.keys(props
),
324 args
.forEach(function(v
) {
329 if (type
=== 'string' || v
+ '' === v
) {
331 } else if (type
=== 'object') {
333 if (v
.length
&& v
[0]) {
336 // apply missing required properties if appropriate
337 propKeys
.forEach(function(req
) {
339 if (typeof v
[req
] !== 'undefined' &&
340 typeof config
[req
] === 'undefined') {
342 config
[req
] = v
[req
];
348 } else if (type
=== 'function') {
354 propKeys
.forEach(function(req
) {
355 var required
= props
[req
];
356 if (required
&& typeof config
[req
] === 'undefined') {
357 throw new Error("jsdom.env requires a '" + req
+ "' argument");