Skip to content
This repository has been archived by the owner on Dec 16, 2023. It is now read-only.

Commit

Permalink
The Zombie follows redirect.
Browse files Browse the repository at this point in the history
  • Loading branch information
assaf committed Dec 18, 2010
1 parent 60668c2 commit 85633a8
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 78 deletions.
98 changes: 55 additions & 43 deletions lib/zombie/history.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -94,57 +94,69 @@ class History
# Make a request to external resource. We use this to fetch pages and
# submit forms, see _loadPage and _submit.
resource = (url, method = "GET", data, enctype)=>
# Create new DOM Level 3 document, add features (load external resources,
# etc) and associate it with current document. From this point on the
# browser sees a new document, client register event handlers for
# DOMContentLoaded/error.
aug = jsdom.browserAugmentation(jsdom.dom.level3.html)
document = new aug.HTMLDocument(url: URL.format(url), deferClose: true)
jsdom.applyDocumentFeatures document
window.document = document
if window.document && window.document.readyState == "loading"
# Redirecting, so reuse existing window.
document = window.document
else
# Create new DOM Level 3 document, add features (load external
# resources, etc) and associate it with current document. From this
# point on the browser sees a new document, client register event
# handlers for DOMContentLoaded/error.
aug = jsdom.browserAugmentation(jsdom.dom.level3.html)
document = new aug.HTMLDocument(url: URL.format(url), deferClose: true)
jsdom.applyDocumentFeatures document
window.document = document
# HTTP request, nothing fancy.
client = http.createClient(url.port || 80, url.hostname)
headers = { "host": url.hostname }
if method == "GET"
url.search = URL.resolve(url, { query: data }).split("?")[1]
else
body = URL.format({ query: data }).substring(1)
data = URL.format({ query: data }).substring(1)
headers["content-type"] = enctype || "application/x-www-form-urlencoded"
headers["content-length"] = body.length
headers["content-length"] = data.length
headers["cookie"] = cookies._header(url)
path = url.pathname + (url.search || "")
window.request { url: URL.format(url), method: method, headers: headers, body: body }, (done)=>
request = client.request(method, path, headers)
client.on "error", (err)->
console.error "Error requesting #{URL.format(url)}", error
event = document.createEvent("HTMLEvents")
event.initEvent "error", true, false
document.dispatchEvent event
request.on "response", (response)->
response.setEncoding "utf8"
body = ""
response.on "data", (chunk)-> body += chunk
response.on "end", ->
browser.response = [response.statusCode, response.headers, body]
if response.statusCode == 200
cookies._update url, response.headers["set-cookie"]
document.open()
document.write body
document.close()
error = "Could not parse document at #{URL.format(url)}" unless document.documentElement
else
error = "Could not load document at #{URL.format(url)}, got #{response.statusCode}"
# onerror is the only reliable way we have to notify the
# application.
if error
console.error error
done error
event = document.createEvent("HTMLEvents")
event.initEvent "error", true, false
document.dispatchEvent event
else
makeRequest = (url, method, headers, data)=>
window.request { url: URL.format(url), method: method, headers: headers, body: data }, (done)=>
client = http.createClient(url.port || 80, url.hostname)
path = url.pathname + (url.search || "")
request = client.request(method, path, headers)

request.on "response", (response)=>
response.setEncoding "utf8"
body = ""
response.on "data", (chunk)-> body += chunk
response.on "end", =>
browser.response = [response.statusCode, response.headers, body]
done null, { status: response.statusCode, headers: response.headers, body: body }
request.end body, "utf8"
switch response.statusCode
when 200
cookies._update url, response.headers["set-cookie"]
document.open()
document.write body
document.close()
error = "Could not parse document at #{URL.format(url)}" unless document.documentElement
when 301, 302, 303, 307
redirect = URL.parse(URL.resolve(url, response.headers["location"]))
stack[index] = { url: redirect }
makeRequest redirect, "GET", headers
else
error = "Could not load document at #{URL.format(url)}, got #{response.statusCode}"
# onerror is the only reliable way we have to notify the
# application.
if error
console.error error
event = document.createEvent("HTMLEvents")
event.initEvent "error", true, false
document.dispatchEvent event

client.on "error", (error)->
console.error "Error requesting #{URL.format(url)}", error
event = document.createEvent("HTMLEvents")
event.initEvent "error", true, false
document.dispatchEvent event
done error
request.end data, "utf8"
makeRequest url, method, headers, data

# Called when we switch to a new page with the URL of the old page.
pageChanged = (old)=>
Expand Down
74 changes: 40 additions & 34 deletions lib/zombie/xhr.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -49,52 +49,58 @@ XMLHttpRequest = (browser, window)->
headers = {}
@setRequestHandler = (header, value)-> headers[header.toString().toLowerCase()] = value.toString()
# Allow calling send method.
@send = (body)->
@send = (data)->
# Aborting request in progress.
@abort = ->
aborted = true
@_error = new core.DOMException(core.ABORT_ERR, "Request aborted")
stateChanged 4
reset()

client = http.createClient(url.port, url.hostname)
if body && method != "GET" && method != "HEAD"
if data && method != "GET" && method != "HEAD"
headers["content-type"] ||= "text/plain;charset=UTF-8"
headers["content-length"] = data.length
else
body = ""
data = ""
headers["cookie"] = cookies._header(url)
window.request { url: URL.format(url), method: method, headers: headers, body: body }, (done)=>
request = client.request(method, url.pathname, headers)
request.end body, "utf8"
request.on "response", (response)=>
response.setEncoding "utf8"
# At this state, allow retrieving of headers and status code.
@getResponseHeader = (header)-> response.headers[header.toLowerCase()]
@getAllResponseHeader = -> response.headers
@__defineGetter__ "status", -> response.statusCode
@__defineGetter__ "statusText", -> XMLHttpRequest.STATUS[response.statusCode]
stateChanged 2
body = ""
response.on "data", (chunk)=>
return response.destroy() if aborted
body += chunk
stateChanged 3
response.on "end", (chunk)=>
return response.destroy() if aborted
@__defineGetter__ "responseText", -> body
@__defineGetter__ "responseXML", -> # not implemented
browser.response = [response.statusCode, response.headers, body]
cookies._update url, response.headers["set-cookie"]
makeRequest = (url, method, headers, data)=>
window.request { url: URL.format(url), method: method, headers: headers, body: data }, (done)=>
client = http.createClient(url.port, url.hostname)
request = client.request(method, url.pathname, headers)
request.end data, "utf8"
request.on "response", (response)=>
response.setEncoding "utf8"
# At this state, allow retrieving of headers and status code.
@getResponseHeader = (header)-> response.headers[header.toLowerCase()]
@getAllResponseHeader = -> response.headers
@__defineGetter__ "status", -> response.statusCode
@__defineGetter__ "statusText", -> XMLHttpRequest.STATUS[response.statusCode]
stateChanged 2
body = ""
response.on "data", (chunk)=>
return response.destroy() if aborted
body += chunk
stateChanged 3
response.on "end", (chunk)=>
return response.destroy() if aborted
cookies._update url, response.headers["set-cookie"]
done null, { status: response.statusCode, headers: response.headers, body: body }
switch response.statusCode
when 301, 302, 303, 307
makeRequest URL.parse(URL.resolve(url, response.headers["location"])) , "GET", headers
else
@__defineGetter__ "responseText", -> body
@__defineGetter__ "responseXML", -> # not implemented
stateChanged 4

client.on "error", (err)=>
console.error "XHR error", err
done err
@_error = new core.DOMException(core.NETWORK_ERR, err.message)
stateChanged 4
done null, { status: response.statusCode, headers: response.headers, body: body }
reset()
makeRequest url, method, headers, data

client.on "error", (err)=>
console.error "XHR error", err
done err
@_error = new core.DOMException(core.NETWORK_ERR, err.message)
stateChanged 4
reset()

# Calling open at this point aborts the ongoing request, resets the
# state and starts a new request going
@open = (method, url, async, user, password)->
Expand Down
7 changes: 7 additions & 0 deletions spec/history-spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ jsdom = require("jsdom")

brains.get "/boo", (req, res)->
res.send "<html><title>Eeek!</title></html>"
brains.get "/redirect", (req, res)->
res.redirect "/"


vows.describe("History").addBatch(
Expand Down Expand Up @@ -127,4 +129,9 @@ vows.describe("History").addBatch(
"should not add page to history": (browser)-> assert.length browser.window.history, 1
"should not change location URL": (browser)-> assert.equal browser.location, "http://localhost:3003/"
"should reload document": (browser)-> assert.match browser.html(), /Tap, Tap/

"redirect":
zombie.wants "http://localhost:3003/redirect"
"should redirect to final destination": (browser)-> assert.equal browser.location, "http://localhost:3003/"
"should not add location in history": (browser)-> assert.length browser.window.history, 1
).export(module)
21 changes: 20 additions & 1 deletion spec/xhr-spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,22 @@ brains.get "/xhr", (req, res)->
"""
brains.get "/backend", (req, res)->
res.cookie "xml", "lol", "Path": "/"
res.send req.cookies["xhr"]
res.send req.cookies["xhr"] || ""

brains.get "/xhr/redirect", (req, res)->
res.cookie "xhr", "yes", "Path": "/"
res.send """
<html>
<head><script src="/jquery.js"></script></head>
<body>
<script>
$.get("/backend/redirect", function(response) { window.response = response });
</script>
</body>
</html>
"""
brains.get "/backend/redirect", (req, res)->
res.redirect "/backend"


vows.describe("XMLHttpRequest").addBatch(
Expand All @@ -30,4 +45,8 @@ vows.describe("XMLHttpRequest").addBatch(
"receive cookies":
zombie.wants "http://localhost:3003/xhr"
"should process cookies in XHR response": (browser)-> assert.equal browser.cookies.get("xml"), "lol"

"redirect":
zombie.wants "http://localhost:3003/xhr/redirect"
"should send cookies in XHR response": (browser)-> assert.equal browser.window.response, "yes"
).export(module)

0 comments on commit 85633a8

Please sign in to comment.