Permalink
Browse files

Merge branch 'new-context'

Conflicts:
	CHANGELOG.md
  • Loading branch information...
2 parents 03bcd66 + 9c61377 commit e405a06df900420f848bdfd4c3f78a71b835a0da @assaf committed Feb 17, 2011
View
@@ -5,3 +5,6 @@ html
lib
man7
node_modules
+build
+.lock-wscript
+tags
View
@@ -3,6 +3,16 @@ zombie.js-changelog(7) -- Changelog
### Version 0.8.14 Pending
+New isolated contexts for executing JavaScript.
+
+Fixed error handling on timeout/XHR scripts, these now generate an
+`onerror` event.
+
+Eventloop is now associated with window instead of browser.
+
+ 293 Tests
+ 3.4 sec to complete
+
### Version 0.8.13 2011-02-11
View
@@ -52,7 +52,9 @@ task "install", "Install Zombie in your local repository", ->
build = (callback)->
log "Compiling CoffeeScript to JavaScript ...", green
exec "rm -rf lib && coffee -c -l -b -o lib src", (err, stdout)->
- callback err
+ onerror err
+ log "Compiling native extension ...", green
+ exec "node-waf configure build", callback
task "build", "Compile CoffeeScript to JavaScript", -> build onerror
task "watch", "Continously compile CoffeeScript to JavaScript", ->
View
@@ -9,13 +9,6 @@ zombie.js-todo(7) -- Wishlist
* Make sure `DOMContentLoaded` event fires after all stylesheets
are loaded
-* New script context
- * The execution context for all scripts on the page is the `Window`
- object itself
- * Node's `runInContext` accepts a sandbox, then creates an actual V8
- context by copying properties to/from, which breaks asynchronous
- scripts (timer, XHR, etc) which run in the contex, not the sandbox
-
* Navigation: Browser.open/close should work as a pair; look into supporting
window.open; fire unload event when navigating away from page.
View
@@ -25,6 +25,7 @@
"xpath"
],
"scripts": {
+ "install": "node-waf configure build",
"build": "cake build",
"test": "cake test"
},
View
@@ -31,9 +31,9 @@ brains.use express.cookieDecoder()
brains.get "/", (req, res)->
res.send "<html><title>Tap, Tap</title></html>"
brains.get "/jquery.js", (req, res)->
- fs.readFile "#{__dirname}/.scripts/jquery.js", (err, data)-> res.send data
+ fs.readFile "#{__dirname}/scripts/jquery.js", (err, data)-> res.send data
brains.get "/sammy.js", (req, res)->
- fs.readFile "#{__dirname}/.scripts/sammy.js", (err, data)->
+ fs.readFile "#{__dirname}/scripts/sammy.js", (err, data)->
# Prevent sammy from polluting the output. Comment this if you need its
# messages for debugging.
data = data + ";window.Sammy.log = function() {}"
@@ -2,9 +2,12 @@ require "./helpers"
{ vows: vows, assert: assert, zombie: zombie, brains: brains } = require("vows")
brains.get "/script/context", (req, res)-> res.send """
- <script>var foo = 1;</script>
- <script>foo = foo + 1;</script>
- <script>document.title = foo;</script>
+ <script>var foo = 1</script>
+ <script>window.foo = foo + 1</script>
+ <script>document.title = this.foo</script>
+ <script>setTimeout(function() {
+ document.title = foo + window.foo
+ })</script>
"""
brains.get "/script/order", (req, res)-> res.send """
@@ -121,7 +124,7 @@ brains.get "/app.js", (req, res)-> res.send """
vows.describe("Scripts").addBatch(
"script context":
zombie.wants "http://localhost:3003/script/context"
- "should be shared by all scripts": (browser)-> assert.equal browser.text("title"), "2"
+ "should be shared by all scripts": (browser)-> assert.equal browser.text("title"), "4"
"script order":
zombie.wants "http://localhost:3003/script/order"
File renamed without changes.
File renamed without changes.
@@ -5,7 +5,9 @@ require "./jsdom_patches"
require "./forms"
require "./xpath"
History = require("./history").History
-
+EventLoop = require("./eventloop").EventLoop
+require.paths.push "../../build/default"
+WindowContext = require("../../build/default/windowcontext").WindowContext
# Use the browser to open up new windows and load documents.
@@ -16,7 +18,6 @@ class Browser extends require("events").EventEmitter
cache = require("./cache").use(this)
cookies = require("./cookies").use(this)
storage = require("./storage").use(this)
- eventloop = require("./eventloop").use(this)
interact = require("./interact").use(this)
xhr = require("./xhr").use(cache)
resources = require("./resources")
@@ -90,6 +91,9 @@ class Browser extends require("events").EventEmitter
history = features.history || new History
newWindow = jsdom.createWindow(html)
+ # Add context for evaluating scripts.
+ newWindow._evalContext = new WindowContext(newWindow)
+ newWindow._evaluate = (code, filename)-> newWindow._evalContext.evaluate(code, filename)
# Switch to the newly created window if it's interactive.
# Examples of non-interactive windows are frames.
@@ -103,7 +107,7 @@ class Browser extends require("events").EventEmitter
resources.extend newWindow
cookies.extend newWindow
storage.extend newWindow
- eventloop.extend newWindow
+ newWindow._eventloop = new EventLoop(newWindow)
history.extend newWindow
interact.extend newWindow
xhr.extend newWindow
@@ -156,7 +160,7 @@ class Browser extends require("events").EventEmitter
callback null, this
@on "error", onerror
@on "done", ondone
- eventloop.wait window, terminate
+ window._eventloop.wait window, terminate
return
# ### browser.fire(name, target, callback?)
@@ -648,27 +652,7 @@ class Browser extends require("events").EventEmitter
# You can also use this to evaluate a function in the context of the
# window: for timers and asynchronous callbacks (e.g. XHR).
this.evaluate = (code, filename)->
- if typeof code == "function"
- code.apply window
- else
- # Unfortunately, using the same context in multiple scripts
- # doesn't agree with jQuery, Sammy and other scripts I tested,
- # so each script gets a new context.
- context = vm.Script.createContext(window)
-
- # But we need to carry global variables from one script to the
- # next, so we're going to store them in window._vars and add them
- # back to the new context.
- if window._vars
- context[v[0]] = v[1] for v in @window._vars
- script = new vm.Script(code, filename || "eval")
- try
- return script.runInContext(context)
- catch ex
- this.log ex.stack.split("\n").slice(0,2)
- throw ex
- finally
- window._vars = ([n,v] for n, v of context).filter((v)-> !window[v[0]])
+ this.window._evaluate code, filename
# Interaction
@@ -771,7 +755,7 @@ class Browser extends require("events").EventEmitter
console.log "History:\n#{indent window.history.dump()}"
console.log "Cookies:\n#{indent cookies.dump()}"
console.log "Storage:\n#{indent storage.dump()}"
- console.log "Eventloop:\n#{indent eventloop.dump()}"
+ console.log "Eventloop:\n#{indent window._eventloop.dump()}"
if @document
html = @document.outerHTML
html = html.slice(0, 497) + "..." if html.length > 497
@@ -3,21 +3,26 @@ URL = require("url")
# Handles the Window event loop, timers and pending requests.
class EventLoop
- constructor: (browser)->
+ constructor: (window)->
timers = {}
lastHandle = 0
# ### window.setTimeout(fn, delay) => Number
#
# Implements window.setTimeout using event queue
- this.setTimeout = (fn, delay)->
+ window.setTimeout = (fn, delay)->
timer =
- when: browser.clock + delay
+ when: window.browser.clock + delay
timeout: true
fire: =>
- browser.log "Firing timeout #{handle}, delay: #{delay}"
+ window.browser.log "Firing timeout #{handle}, delay: #{delay}"
try
- browser.evaluate fn
+ window._evaluate fn
+ catch error
+ evt = window.document.createEvent("HTMLEvents")
+ evt.initEvent "error", true, false
+ evt.error = error
+ window.dispatchEvent evt
finally
delete timers[handle]
handle = ++lastHandle
@@ -27,28 +32,33 @@ class EventLoop
# ### window.setInterval(fn, delay) => Number
#
# Implements window.setInterval using event queue
- this.setInterval = (fn, delay)->
+ window.setInterval = (fn, delay)->
timer =
- when: browser.clock + delay
+ when: window.browser.clock + delay
interval: true
fire: =>
- browser.log "Firing interval #{handle}, interval: #{delay}"
+ window.browser.log "Firing interval #{handle}, interval: #{delay}"
try
- browser.evaluate fn
+ window._evaluate fn
+ catch error
+ evt = window.document.createEvent("HTMLEvents")
+ evt.initEvent "error", true, false
+ evt.error = error
+ window.dispatchEvent evt
finally
- timer.when = browser.clock + delay
+ timer.when = window.browser.clock + delay
handle = ++lastHandle
timers[handle] = timer
handle
# ### window.clearTimeout(timeout)
#
# Implements window.clearTimeout using event queue
- this.clearTimeout = (handle)-> delete timers[handle] if timers[handle]?.timeout
+ window.clearTimeout = (handle)-> delete timers[handle] if timers[handle]?.timeout
# ### window.clearInterval(interval)
#
# Implements window.clearInterval using event queue
- this.clearInterval = (handle)-> delete timers[handle] if timers[handle]?.interval
+ window.clearInterval = (handle)-> delete timers[handle] if timers[handle]?.interval
# Size of processing queue (number of ongoing tasks).
processing = 0
@@ -90,7 +100,7 @@ class EventLoop
if earliest
intervals = false
event = ->
- browser.clock = earliest.when if browser.clock < earliest.when
+ window.browser.clock = earliest.when if window.browser.clock < earliest.when
earliest.fire()
if event
try
@@ -103,31 +113,24 @@ class EventLoop
done = true if terminate.call(window) == false
if done
process.nextTick ->
- browser.emit "done", browser
+ window.browser.emit "done", window.browser
callback null, window if callback
else
@wait window, terminate, callback, intervals
catch err
- browser.emit "error", err
+ window.browser.emit "error", err
callback err, window if callback
else if processing > 0
waiting.push => @wait window, terminate, callback, intervals
else
- browser.emit "done", browser
+ window.browser.emit "done", window.browser
callback null, window if callback
- this.extend = (window)=>
- for fn in ["setTimeout", "setInterval", "clearTimeout", "clearInterval"]
- window[fn] = this[fn]
- window.perform = this.perform
- window.wait = (terminate, callback)=> this.wait(window, terminate, callback)
- window.request = this.request
-
this.dump = ()->
- [ "The time: #{browser.clock}",
+ [ "The time: #{window.browser.clock}",
"Timers: #{Object.keys(timers).length}",
"Processing: #{processing}",
"Waiting: #{waiting.length}" ]
-exports.use = (browser)->
- return new EventLoop(browser)
+
+exports.EventLoop = EventLoop
@@ -54,7 +54,7 @@ core.languageProcessors =
window = document.parentWindow
window.browser.log -> "Running script from #{filename}" if filename
try
- window.browser.evaluate code, filename
+ window._evaluate code, filename
catch error
event = document.createEvent("HTMLEvents")
event.initEvent "error", true, false
@@ -83,7 +83,7 @@ core.Document.prototype._elementBuilders["script"] = (doc, s)->
code = event.target.nodeValue
if code.trim().length > 0
filename = @ownerDocument.URL
- @ownerDocument.parentWindow.perform (done)=>
+ @ownerDocument.parentWindow._eventloop.perform (done)=>
loaded = (code, filename)=>
if core.languageProcessors[@language] && code == @text
core.languageProcessors[@language](this, code, filename)
@@ -121,7 +121,7 @@ class Resources extends Array
#
# The callback is called with error and response (see `HTTPResponse`).
this.request = (method, url, data, headers, callback)->
- window.perform (done)->
+ window._eventloop.perform (done)->
makeRequest method, url, data, headers, null, (error, response)->
done()
callback error, response
Oops, something went wrong.

0 comments on commit e405a06

Please sign in to comment.