1+ /*jslint node:true, vars:true, todo:true, stupid:true, regexp:true*/
12/**
23 * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.
34 *
@@ -16,6 +17,7 @@ var Url = require("url")
1617 , fs = require ( 'fs' ) ;
1718
1819exports . XMLHttpRequest = function ( ) {
20+ 'use strict' ;
1921 /**
2022 * Private variables
2123 */
@@ -37,7 +39,7 @@ exports.XMLHttpRequest = function() {
3739 // Set some default headers
3840 var defaultHeaders = {
3941 "User-Agent" : "node-XMLHttpRequest" ,
40- "Accept" : "*/*" ,
42+ "Accept" : "*/*"
4143 } ;
4244
4345 var headers = defaultHeaders ;
@@ -113,6 +115,27 @@ exports.XMLHttpRequest = function() {
113115 * Private methods
114116 */
115117
118+ /**
119+ * Changes readyState and calls onreadystatechange.
120+ *
121+ * @param int state New state
122+ */
123+ var setState = function ( state ) {
124+ if ( state === self . LOADING || self . readyState !== state ) {
125+ self . readyState = state ;
126+
127+ if ( settings . async || self . readyState < self . OPENED || self . readyState === self . DONE ) {
128+ self . dispatchEvent ( "readystatechange" ) ;
129+ }
130+
131+ if ( self . readyState === self . DONE && ! errorFlag ) {
132+ self . dispatchEvent ( "load" ) ;
133+ // @TODO figure out InspectorInstrumentation::didLoadXHR(cookie)
134+ self . dispatchEvent ( "loadend" ) ;
135+ }
136+ }
137+ } ;
138+
116139 /**
117140 * Check if the specified header is allowed.
118141 *
@@ -183,7 +206,7 @@ exports.XMLHttpRequest = function() {
183206 * @param string value Header value
184207 */
185208 this . setRequestHeader = function ( header , value ) {
186- if ( this . readyState != this . OPENED ) {
209+ if ( this . readyState !== this . OPENED ) {
187210 throw "INVALID_STATE_ERR: setRequestHeader can only be called when state is OPEN" ;
188211 }
189212 if ( ! isAllowedHttpHeader ( header ) ) {
@@ -226,11 +249,13 @@ exports.XMLHttpRequest = function() {
226249 return "" ;
227250 }
228251 var result = "" ;
229-
230- for ( var i in response . headers ) {
231- // Cookie headers are excluded
232- if ( i !== "set-cookie" && i !== "set-cookie2" ) {
233- result += i + ": " + response . headers [ i ] + "\r\n" ;
252+ var i ;
253+ for ( i in response . headers ) {
254+ if ( response . headers . hasOwnProperty ( i ) ) {
255+ // Cookie headers are excluded
256+ if ( i !== "set-cookie" && i !== "set-cookie2" ) {
257+ result += i + ": " + response . headers [ i ] + "\r\n" ;
258+ }
234259 }
235260 }
236261 return result . substr ( 0 , result . length - 2 ) ;
@@ -257,7 +282,7 @@ exports.XMLHttpRequest = function() {
257282 * @param string data Optional data to send as request body.
258283 */
259284 this . send = function ( data ) {
260- if ( this . readyState != this . OPENED ) {
285+ if ( this . readyState !== this . OPENED ) {
261286 throw "INVALID_STATE_ERR: connection must be opened before send() is called" ;
262287 }
263288
@@ -273,6 +298,7 @@ exports.XMLHttpRequest = function() {
273298 case 'https:' :
274299 ssl = true ;
275300 // SSL & non-SSL both need host, no break here.
301+ /* falls through */
276302 case 'http:' :
277303 host = url . hostname ;
278304 break ;
@@ -323,21 +349,21 @@ exports.XMLHttpRequest = function() {
323349 // to use http://localhost:port/path
324350 var port = url . port || ( ssl ? 443 : 80 ) ;
325351 // Add query string if one is used
326- var uri = url . pathname + ( url . search ? url . search : '' ) ;
352+ var uri = url . pathname + ( url . search || '' ) ;
327353
328354 // Set the Host header or the server may reject the request
329- headers [ " Host" ] = host ;
355+ headers . Host = host ;
330356 if ( ! ( ( ssl && port === 443 ) || port === 80 ) ) {
331- headers [ " Host" ] += ':' + url . port ;
357+ headers . Host += ':' + url . port ;
332358 }
333359
334360 // Set Basic Auth if necessary
335361 if ( settings . user ) {
336- if ( typeof settings . password == " undefined" ) {
362+ if ( settings . password === undefined ) {
337363 settings . password = "" ;
338364 }
339365 var authBuf = new Buffer ( settings . user + ":" + settings . password ) ;
340- headers [ " Authorization" ] = "Basic " + authBuf . toString ( "base64" ) ;
366+ headers . Authorization = "Basic " + authBuf . toString ( "base64" ) ;
341367 }
342368
343369 // Set content length header
@@ -364,82 +390,84 @@ exports.XMLHttpRequest = function() {
364390 agent : false
365391 } ;
366392
393+ var doRequest ;
394+
367395 // Reset error flag
368396 errorFlag = false ;
369397
370- // Handle async requests
371- if ( settings . async ) {
372- // Use the proper protocol
373- var doRequest = ssl ? https . request : http . request ;
398+ // Error handler for the request
399+ function errorHandler ( error ) {
400+ self . handleError ( error ) ;
401+ }
374402
375- // Request is being sent, set send flag
376- sendFlag = true ;
403+ // Handler for the response
404+ function responseHandler ( resp ) {
405+ // Set response var to the response we got back
406+ // This is so it remains accessable outside this scope
407+ response = resp ;
408+ // Check for redirect
409+ // @TODO Prevent looped redirects
410+ if ( response . statusCode === 301 || response . statusCode === 302 || response . statusCode === 303 || response . statusCode === 307 ) {
411+ // Change URL to the redirect location
412+ settings . url = response . headers . location ;
413+ url = Url . parse ( settings . url ) ;
414+ // Set host var in case it's used later
415+ host = url . hostname ;
416+ // Options for the new request
417+ var newOptions = {
418+ hostname : url . hostname ,
419+ port : url . port ,
420+ path : url . path ,
421+ method : response . statusCode === 303 ? 'GET' : settings . method ,
422+ headers : headers
423+ } ;
424+
425+ // Issue the new request
426+ request = doRequest ( newOptions , responseHandler ) . on ( 'error' , errorHandler ) ;
427+ request . end ( ) ;
428+ // @TODO Check if an XHR event needs to be fired here
429+ return ;
430+ }
377431
378- // As per spec, this is called here for historical reasons.
379- self . dispatchEvent ( "readystatechange" ) ;
432+ response . setEncoding ( "utf8" ) ;
380433
381- // Handler for the response
382- function responseHandler ( resp ) {
383- // Set response var to the response we got back
384- // This is so it remains accessable outside this scope
385- response = resp ;
386- // Check for redirect
387- // @TODO Prevent looped redirects
388- if ( response . statusCode === 301 || response . statusCode === 302 || response . statusCode === 303 || response . statusCode === 307 ) {
389- // Change URL to the redirect location
390- settings . url = response . headers . location ;
391- var url = Url . parse ( settings . url ) ;
392- // Set host var in case it's used later
393- host = url . hostname ;
394- // Options for the new request
395- var newOptions = {
396- hostname : url . hostname ,
397- port : url . port ,
398- path : url . path ,
399- method : response . statusCode === 303 ? 'GET' : settings . method ,
400- headers : headers
401- } ;
402-
403- // Issue the new request
404- request = doRequest ( newOptions , responseHandler ) . on ( 'error' , errorHandler ) ;
405- request . end ( ) ;
406- // @TODO Check if an XHR event needs to be fired here
407- return ;
408- }
434+ setState ( self . HEADERS_RECEIVED ) ;
435+ self . status = response . statusCode ;
409436
410- response . setEncoding ( "utf8" ) ;
437+ response . on ( 'data' , function ( chunk ) {
438+ // Make sure there's some data
439+ if ( chunk ) {
440+ self . responseText += chunk ;
441+ }
442+ // Don't emit state changes if the connection has been aborted.
443+ if ( sendFlag ) {
444+ setState ( self . LOADING ) ;
445+ }
446+ } ) ;
411447
412- setState ( self . HEADERS_RECEIVED ) ;
413- self . status = response . statusCode ;
448+ response . on ( 'end' , function ( ) {
449+ if ( sendFlag ) {
450+ // Discard the 'end' event if the connection has been aborted
451+ setState ( self . DONE ) ;
452+ sendFlag = false ;
453+ }
454+ } ) ;
414455
415- response . on ( 'data' , function ( chunk ) {
416- // Make sure there's some data
417- if ( chunk ) {
418- self . responseText += chunk ;
419- }
420- // Don't emit state changes if the connection has been aborted.
421- if ( sendFlag ) {
422- setState ( self . LOADING ) ;
423- }
424- } ) ;
456+ response . on ( 'error' , function ( error ) {
457+ self . handleError ( error ) ;
458+ } ) ;
459+ }
425460
426- response . on ( 'end' , function ( ) {
427- if ( sendFlag ) {
428- // Discard the 'end' event if the connection has been aborted
429- setState ( self . DONE ) ;
430- sendFlag = false ;
431- }
432- } ) ;
461+ // Handle async requests
462+ if ( settings . async ) {
463+ // Use the proper protocol
464+ doRequest = ssl ? https . request : http . request ;
433465
434- response . on ( 'error' , function ( error ) {
435- self . handleError ( error ) ;
436- } ) ;
437- }
466+ // Request is being sent, set send flag
467+ sendFlag = true ;
438468
439- // Error handler for the request
440- function errorHandler ( error ) {
441- self . handleError ( error ) ;
442- }
469+ // As per spec, this is called here for historical reasons.
470+ self . dispatchEvent ( "readystatechange" ) ;
443471
444472 // Create the request
445473 request = doRequest ( options , responseHandler ) . on ( 'error' , errorHandler ) ;
@@ -483,7 +511,6 @@ exports.XMLHttpRequest = function() {
483511 + "req.end();" ;
484512 // Start the other Node Process, executing this string
485513 var syncProc = spawn ( process . argv [ 0 ] , [ "-e" , execString ] ) ;
486- var statusText ;
487514 while ( fs . existsSync ( syncFile ) ) {
488515 // Wait while the sync file is empty
489516 }
@@ -544,7 +571,7 @@ exports.XMLHttpRequest = function() {
544571 * Adds an event listener. Preferred method of binding to events.
545572 */
546573 this . addEventListener = function ( event , callback ) {
547- if ( ! ( event in listeners ) ) {
574+ if ( ! ( listeners . hasOwnProperty ( event ) ) ) {
548575 listeners [ event ] = [ ] ;
549576 }
550577 // Currently allows duplicate callbacks. Should it?
@@ -556,7 +583,7 @@ exports.XMLHttpRequest = function() {
556583 * Only works on the matching funciton, cannot be a copy.
557584 */
558585 this . removeEventListener = function ( event , callback ) {
559- if ( event in listeners ) {
586+ if ( listeners . hasOwnProperty ( event ) ) {
560587 // Filter will return a new array with the callback removed
561588 listeners [ event ] = listeners [ event ] . filter ( function ( ev ) {
562589 return ev !== callback ;
@@ -571,31 +598,12 @@ exports.XMLHttpRequest = function() {
571598 if ( typeof self [ "on" + event ] === "function" ) {
572599 self [ "on" + event ] ( ) ;
573600 }
574- if ( event in listeners ) {
575- for ( var i = 0 , len = listeners [ event ] . length ; i < len ; i ++ ) {
601+ var i , len ;
602+ if ( listeners . hasOwnProperty ( event ) ) {
603+ for ( i = 0 , len = listeners [ event ] . length ; i < len ; i ++ ) {
576604 listeners [ event ] [ i ] . call ( self ) ;
577605 }
578606 }
579607 } ;
580608
581- /**
582- * Changes readyState and calls onreadystatechange.
583- *
584- * @param int state New state
585- */
586- var setState = function ( state ) {
587- if ( state == self . LOADING || self . readyState !== state ) {
588- self . readyState = state ;
589-
590- if ( settings . async || self . readyState < self . OPENED || self . readyState === self . DONE ) {
591- self . dispatchEvent ( "readystatechange" ) ;
592- }
593-
594- if ( self . readyState === self . DONE && ! errorFlag ) {
595- self . dispatchEvent ( "load" ) ;
596- // @TODO figure out InspectorInstrumentation::didLoadXHR(cookie)
597- self . dispatchEvent ( "loadend" ) ;
598- }
599- }
600- } ;
601609} ;
0 commit comments