Skip to content

Commit

Permalink
Feeble attempt to fix window == this issue, and breaking iframes.
Browse files Browse the repository at this point in the history
  • Loading branch information
assaf committed Feb 18, 2011
1 parent 5b3db2c commit f3f8517
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 57 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,14 @@
zombie.js-changelog(7) -- Changelog
===================================

### Version 0.9.1 2011-02-17

Some internal changes to history. Breaks iframe.

289 Tests
3.3 sec to complete


### Version 0.9.0 2011-02-17

New isolated contexts for executing JavaScript. This solves a long
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -22,7 +22,8 @@
"spec",
"src",
"TODO.md",
"xpath"
"xpath",
"wscript"
],
"main": "lib/zombie/index.js",
"scripts": {
Expand Down
3 changes: 2 additions & 1 deletion spec/browser-spec.coffee
Expand Up @@ -372,6 +372,7 @@ vows.describe("Browser").addBatch(
forked.window.history.back()
assert.equal "http://localhost:3003/living", forked.location.href

###
"iframes":
zombie.wants "http://localhost:3003/iframe"
"should load": (browser)->
Expand All @@ -389,6 +390,6 @@ vows.describe("Browser").addBatch(
browser.wait -> callback null, browser
"should still reference the parent": (browser)->
assert.ok browser.window == browser.querySelector("iframe").window.parent

###

).export(module)
34 changes: 17 additions & 17 deletions spec/history-spec.coffee
Expand Up @@ -3,12 +3,12 @@ require("./helpers")
jsdom = require("jsdom")


brains.get "/boo", (req, res)->
brains.get "/history/boo", (req, res)->
response = if req.query.redirected then "Redirected" else "Eeek!"
res.send "<html><title>#{response}</title></html>"
brains.get "/redirect", (req, res)->
res.redirect "/boo?redirected=true"
brains.get "/redirect_back", (req, res)->
brains.get "/history/redirect", (req, res)->
res.redirect "/history/boo?redirected=true"
brains.get "/history/redirect_back", (req, res)->
res.redirect req.headers['referer']


Expand Down Expand Up @@ -87,20 +87,20 @@ vows.describe("History").addBatch(
"change location":
zombie.wants "http://localhost:3003/"
topic: (browser)->
browser.window.location = "http://localhost:3003/boo"
browser.window.location = "http://localhost:3003/history/boo"
browser.window.document.addEventListener "DOMContentLoaded", => @callback null, browser
return
"should add page to history": (browser)-> assert.length browser.window.history, 2
"should change location URL": (browser)-> assert.equal browser.location, "http://localhost:3003/boo"
"should change location URL": (browser)-> assert.equal browser.location, "http://localhost:3003/history/boo"
"should load document": (browser)-> assert.match browser.html(), /Eeek!/
"change pathname":
zombie.wants "http://localhost:3003/"
topic: (browser)->
browser.window.location.pathname = "/boo"
browser.window.location.pathname = "/history/boo"
browser.window.document.addEventListener "DOMContentLoaded", => @callback null, browser
return
"should add page to history": (browser)-> assert.length browser.window.history, 2
"should change location URL": (browser)-> assert.equal browser.location, "http://localhost:3003/boo"
"should change location URL": (browser)-> assert.equal browser.location, "http://localhost:3003/history/boo"
"should load document": (browser)-> assert.match browser.html(), /Eeek!/
"change hash":
zombie.wants "http://localhost:3003/"
Expand All @@ -116,22 +116,22 @@ vows.describe("History").addBatch(
zombie.wants "http://localhost:3003/"
topic: (browser)->
@window = browser.window
browser.window.location.assign "http://localhost:3003/boo"
browser.window.location.assign "http://localhost:3003/history/boo"
browser.document.addEventListener "DOMContentLoaded", => @callback null, browser
return
"should add page to history": (browser)-> assert.length browser.window.history, 2
"should change location URL": (browser)-> assert.equal browser.location, "http://localhost:3003/boo"
"should change location URL": (browser)-> assert.equal browser.location, "http://localhost:3003/history/boo"
"should load document": (browser)-> assert.match browser.html(), /Eeek!/
"should load document in new window": (browser)-> assert.ok browser.window != @window
"replace":
zombie.wants "http://localhost:3003/"
topic: (browser)->
@window = browser.window
browser.window.location.replace "http://localhost:3003/boo"
browser.window.location.replace "http://localhost:3003/history/boo"
browser.window.document.addEventListener "DOMContentLoaded", => @callback null, browser
return
"should not add page to history": (browser)-> assert.length browser.window.history, 1
"should change location URL": (browser)-> assert.equal browser.location, "http://localhost:3003/boo"
"should change location URL": (browser)-> assert.equal browser.location, "http://localhost:3003/history/boo"
"should load document": (browser)-> assert.match browser.html(), /Eeek!/
"should load document in new window": (browser)-> assert.ok browser.window != @window
"reload":
Expand All @@ -158,19 +158,19 @@ vows.describe("History").addBatch(
"should include hash": (location)-> assert.equal location.hash, ""

"redirect":
zombie.wants "http://localhost:3003/redirect"
"should redirect to final destination": (browser)-> assert.equal browser.location, "http://localhost:3003/boo?redirected=true"
zombie.wants "http://localhost:3003/history/redirect"
"should redirect to final destination": (browser)-> assert.equal browser.location, "http://localhost:3003/history/boo?redirected=true"
"should pass query parameter": (browser)-> assert.equal browser.text("title"), "Redirected"
"should not add location in history": (browser)-> assert.length browser.window.history, 1
"should indicate last request followed a redirect": (browser)-> assert.ok browser.redirected

"redirect back":
zombie.wants "http://localhost:3003/boo"
zombie.wants "http://localhost:3003/history/boo"
topic: (browser)->
browser.visit "http://localhost:3003/redirect_back"
browser.visit "http://localhost:3003/history/redirect_back"
browser.window.document.addEventListener "DOMContentLoaded", => @callback null, browser
return
"should redirect to the previous path": (browser)-> assert.equal browser.location.href, "http://localhost:3003/boo"
"should redirect to the previous path": (browser)-> assert.equal browser.location.href, "http://localhost:3003/history/boo"
"should pass query parameter": (browser)-> assert.match browser.text("title"), /Eeek!/
"should not add location in history": (browser)-> assert.length browser.window.history, 2
"should indicate last request followed a redirect": (browser)-> assert.ok browser.redirected
Expand Down
8 changes: 8 additions & 0 deletions spec/script-spec.coffee
Expand Up @@ -10,6 +10,8 @@ brains.get "/script/context", (req, res)-> res.send """
})</script>
"""

brains.get "/script/window", (req, res)-> res.send "<script>document.title = [window == this, this == window.window].join(',')</script>"

brains.get "/script/order", (req, res)-> res.send """
<html>
<head>
Expand Down Expand Up @@ -126,6 +128,12 @@ vows.describe("Scripts").addBatch(
zombie.wants "http://localhost:3003/script/context"
"should be shared by all scripts": (browser)-> assert.equal browser.text("title"), "4"

###
"script window":
zombie.wants "http://localhost:3003/script/window"
"should be the same as this and top": (browser)-> assert.equal browser.text("title"), "true,true"
###

"script order":
zombie.wants "http://localhost:3003/script/order"
"should run scripts in order regardless of source": (browser)-> assert.equal browser.text("title"), "ZeroOneTwo"
Expand Down
15 changes: 9 additions & 6 deletions src/zombie/browser.coffee
Expand Up @@ -88,12 +88,17 @@ class Browser extends require("events").EventEmitter
this.open = (features = {})->
features.interactive ?= true

history = features.history || new History
history = features.history || new History(this)

newWindow = jsdom.createWindow(html)
# Add context for evaluating scripts.
newWindow._evalContext = new WindowContext(newWindow)
newWindow._evaluate = (code, filename)-> newWindow._evalContext.evaluate(code, filename)
#context = new WindowContext(jsdom.createWindow(html))
#newWindow = context.global
#newWindow._evaluate = (code, filename)-> context.evaluate(code, filename)
#newWindow._evaluate "this.window = this"

newWindow = jsdom.createWindow(html)
context = new WindowContext(newWindow)
newWindow._evaluate = (code, filename)-> context.evaluate(code, filename)

# Switch to the newly created window if it's interactive.
# Examples of non-interactive windows are frames.
Expand All @@ -115,8 +120,6 @@ class Browser extends require("events").EventEmitter
newWindow.JSON = JSON
# Default onerror handler.
newWindow.onerror = (event)=> @emit "error", event.error || new Error("Error loading script")
# TODO: Fix
newWindow.Image = ->

return newWindow

Expand Down
33 changes: 12 additions & 21 deletions src/zombie/history.coffee
Expand Up @@ -29,14 +29,11 @@ class Entry
#
# Represents window.history.
class History
constructor: ->
constructor: (browser)->
# History is a stack of Entry objects.
stack = []
index = -1

window = null
browser = null

# Called when we switch to a new page with the URL of the old page.
pageChanged = (was)=>
url = stack[index]?.url
Expand All @@ -45,9 +42,9 @@ class History
resource url
else if was.hash != url.hash
# Hash changed. Do not reload page, but do send hashchange
evt = browser.document.createEvent("HTMLEvents")
evt = browser.window.document.createEvent("HTMLEvents")
evt.initEvent "hashchange", true, false
window.dispatchEvent evt
browser.window.dispatchEvent evt
else
# Load new page for now (but later on use caching).
resource url
Expand All @@ -61,10 +58,8 @@ class History
# If the browser has a new window, use it. If a document was already
# loaded into that window it would have state information we don't want
# (e.g. window.$) so open a new window.
if window.document
browser.open
history: this
interactive: window.parent == window
if browser.window.document
browser.open history: this, interactive: browser.window.parent == browser.window

# Create new DOM Level 3 document, add features (load external
# resources, etc) and associate it with current document. From this
Expand All @@ -84,12 +79,12 @@ class History
options.features.FetchExternalResources.push "iframe"
document = jsdom.jsdom(false, jsdom.level3, options)
document.fixQueue()
window.document = document
browser.window.document = document

headers = if headers then JSON.parse(JSON.stringify(headers)) else {}
referer = stack[index-1]?.url
headers["referer"] = referer.href if referer?
window.resources.request method, url, data, headers, (error, response)=>
browser.window.resources.request method, url, data, headers, (error, response)=>
if error
event = document.createEvent("HTMLEvents")
event.initEvent "error", true, false
Expand Down Expand Up @@ -120,9 +115,9 @@ class History
if new_index != index && entry = stack[new_index]
index = new_index
if entry.pop
if browser.document
if browser.window.document
# Created with pushState/replaceState, send popstate event
evt = browser.document.createEvent("HTMLEvents")
evt = browser.window.document.createEvent("HTMLEvents")
evt.initEvent "popstate", false, false
evt.state = entry.state
browser.window.dispatchEvent evt
Expand Down Expand Up @@ -179,13 +174,9 @@ class History

# Add Location/History to window.
this.extend = (new_window)->
window = new_window
browser = window.browser

window.__defineGetter__ "history", => this
window.__defineGetter__ "location", => stack[index]?.location || new Location(this, {})
window.__defineSetter__ "location", (url)=>
@_assign URL.resolve(stack[index]?.url, url)
new_window.__defineGetter__ "history", => this
new_window.__defineGetter__ "location", => stack[index]?.location || new Location(this, {})
new_window.__defineSetter__ "location", (url)=> @_assign URL.resolve(stack[index]?.url, url)

# Used to dump state to console (debuggin)
this.dump = ->
Expand Down
35 changes: 24 additions & 11 deletions src/zombie/windowcontext.cc
Expand Up @@ -97,6 +97,7 @@ class WindowContext: ObjectWrap {

Local<ObjectTemplate> instance_t = t->InstanceTemplate();
instance_t->SetAccessor(String::New("global"), GetGlobal);
instance_t->SetAccessor(String::New("g"), GetG);
NODE_SET_PROTOTYPE_METHOD(s_ct, "evaluate", Evaluate);

target->Set(String::NewSymbol("WindowContext"), s_ct->GetFunction());
Expand All @@ -107,7 +108,7 @@ class WindowContext: ObjectWrap {
WindowContext(Handle<Object> global) {
Handle<ObjectTemplate> tmpl = ObjectTemplate::New();
this->global = Persistent<Object>::New(global);
tmpl->SetNamedPropertyHandler(GetProperty, SetProperty, NULL, DeleteProperty, EnumerateProperties, this->global);
tmpl->SetNamedPropertyHandler(GetProperty, SetProperty, QueryProperty, DeleteProperty, EnumerateProperties, this->global);
context = Context::New(NULL, tmpl);

// Copy primitivies in context.
Expand Down Expand Up @@ -141,6 +142,11 @@ class WindowContext: ObjectWrap {
return wc->global;
}

static Handle<Value> GetG(Local<String> name, const AccessorInfo& info) {
WindowContext* wc = ObjectWrap::Unwrap<WindowContext>(info.This());
return wc->context->Global();
}

// Evaluate expression or function in this context. First argument is either
// a function or a script (String). In the later case, second argument
// specifies filename.
Expand All @@ -160,37 +166,43 @@ class WindowContext: ObjectWrap {
result = script->Run();
}
wc->context->Exit();
HandleScope scope;
return scope.Close(result);
return result;
}

// Returns the value of a property from the global scope.
static Handle<Value> GetProperty(Local<String> name, const AccessorInfo &info) {
HandleScope scope;
Local<Object> self = Local<Object>::Cast(info.Data());
Handle<Value> result = self->Get(name);
return scope.Close(result);
return self->Get(name);
}

// Sets the value of a property in the global scope.
static Handle<Value> SetProperty(Local<String> name, Local<Value> value, const AccessorInfo &info) {
Local<Object> self = Local<Object>::Cast(info.Data());
self->Set(name, value);
self->Set(name, Persistent<Value>::New(value));
return value;
}

// Deletes a property from the global scope.
static Handle<Boolean> DeleteProperty(Local<String> name, const AccessorInfo &info) {
HandleScope scope;
Local<Object> self = Local<Object>::Cast(info.Data());
Handle<Boolean> result = Boolean::New(self->Delete(name));
return result;
Persistent<Value> value = (Persistent<Value>) self->Get(name);
bool deleted = self->Delete(name);
if (deleted)
value.Dispose();
return scope.Close(Boolean::New(deleted));
}

// Enumerate all named properties in the global scope.
static Handle<Array> EnumerateProperties(const AccessorInfo &info) {
HandleScope scope;
Local<Object> self = Local<Object>::Cast(info.Data());
Handle<Array> names = self->GetPropertyNames();
return names;
return scope.Close(self->GetPropertyNames());
}

static Handle<Integer> QueryProperty(Local<String> name, const AccessorInfo &info) {
HandleScope scope;
return scope.Close(Integer::New(None));
}

};
Expand All @@ -207,6 +219,7 @@ SetPrimitive *WindowContext::primitives[] = {
new SetPrimitive("String", "''.constructor"),
new SetPrimitive("Date"),
new SetPrimitive("Error"),
new SetPrimitive("Image", "{}", false),
new SetPrimitive("Math"),
new SetPrimitive("decodeURI"),
new SetPrimitive("decodeURIComponent"),
Expand Down

0 comments on commit f3f8517

Please sign in to comment.