diff --git a/.gitignore b/.gitignore index ea0b1c5c6..77f5bd934 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ man7 node_modules build .lock-wscript +tags diff --git a/CHANGELOG.md b/CHANGELOG.md index db2889286..38b8c7a33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ zombie.js-changelog(7) -- Changelog ### Version 0.8.14 Pending +New isolated contexts for executing JavaScript. + + 293 Tests + 4.4 sec to complete + ### Version 0.8.13 2011-02-11 diff --git a/Cakefile b/Cakefile index bcc1a35b4..7df35e102 100644 --- a/Cakefile +++ b/Cakefile @@ -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 clean build", callback task "build", "Compile CoffeeScript to JavaScript", -> build onerror task "watch", "Continously compile CoffeeScript to JavaScript", -> diff --git a/TODO.md b/TODO.md index e584f0087..6f81b227a 100644 --- a/TODO.md +++ b/TODO.md @@ -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. diff --git a/src/zombie/browser.coffee b/src/zombie/browser.coffee index 44119fd54..a2b11d72d 100644 --- a/src/zombie/browser.coffee +++ b/src/zombie/browser.coffee @@ -5,7 +5,8 @@ require "./jsdom_patches" require "./forms" require "./xpath" History = require("./history").History - +require.paths.push "../../build/default" +WindowContext = require("../../build/default/windowcontext").WindowContext # Use the browser to open up new windows and load documents. @@ -78,10 +79,6 @@ class Browser extends require("events").EventEmitter # Windows # ------- - require.paths.push "build/default" - WindowContext = require("windowcontext").WindowContext - - window = null # ### browser.open() => Window # @@ -94,36 +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) - global = newWindow._evalContext.global - global.decodeURI = decodeURI - global.decodeURIComponent = decodeURIComponent - global.encodeURI = encodeURI - global.encodeURIComponent = encodeURIComponent - global.escape = escape - global.eval = eval - global.isFinite = isFinite - global.isNaN = isNaN - global.parseFloat = parseFloat - global.parseInt = parseInt - global.unescape = unescape - global.Array = [].constructor - global.Boolean = Boolean - global.Date = Date - global.Error = Error - global.Function = Function - global.Math = Math - global.Number = Number - global.Object = {}.constructor - global.RegExp = //.constructor - global.String = newWindow._evalContext.evaluate("''.constructor") - - newWindow._evaluate = (code, filename)-> - try - newWindow._evalContext.evaluate(code, filename) - catch ex - console.log ex.message[0..500] - console.log ex.stack + 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. diff --git a/src/zombie/windowcontext.cc b/src/zombie/windowcontext.cc index 4273f6830..e9b2fdc86 100644 --- a/src/zombie/windowcontext.cc +++ b/src/zombie/windowcontext.cc @@ -4,6 +4,8 @@ using namespace node; using namespace v8; +// Isolated V8 Context/global scope for evaluating JavaScript, with access to +// all window methods/properties. class WindowContext: ObjectWrap { private: @@ -35,6 +37,27 @@ class WindowContext: ObjectWrap { this->global = Persistent::New(global); tmpl->SetNamedPropertyHandler(GetProperty, SetProperty, NULL, DeleteProperty, EnumerateProperties, this->global); context = Context::New(NULL, tmpl); + + // Define constructor functions for primitives. We need to do this within + // the context, so "foo".constructor == String + context->Enter(); + global->Set(String::New("Array"), Script::New(String::New("[].constructor"))->Run()); + global->Set(String::New("Boolean"), Script::New(String::New("true.constructor"))->Run()); + global->Set(String::New("Function"), Script::New(String::New("(function() {}).constructor"))->Run()); + global->Set(String::New("Number"), Script::New(String::New("(1).constructor"))->Run()); + global->Set(String::New("Object"), Script::New(String::New("({}).constructor"))->Run()); + global->Set(String::New("RegExp"), Script::New(String::New("/./.constructor"))->Run()); + global->Set(String::New("String"), Script::New(String::New("''.constructor"))->Run()); + context->Exit(); + // FIX ME: Define constructors for non-primitive types. We copy the + // constructor from Zombie's context, which is not a good thing. + const char* const list[] = { "Date", "Error", "Math", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", + "escape", "eval", "isFinite", "isNaN", "parseFloat", "parseInt", "unescape" }; + const size_t len = sizeof(list) / sizeof(list[0]); + for (int i = len ; i-- >0 ; ) { + Handle str = String::New(list[i]); + global->Set(str, Script::New(str)->Run()); + } } ~WindowContext() {