Skip to content
Browse files

Copy browser history when forking.

  • Loading branch information...
1 parent a742001 commit a2b92c9e3f2fb7c7b784dd64c7d9c8201eee661c @assaf assaf committed Feb 1, 2011
Showing with 143 additions and 34 deletions.
  1. +2 −2 CHANGELOG.md
  2. +30 −0 doc/API.md
  3. +7 −0 spec/browser-spec.coffee
  4. +36 −9 src/zombie/browser.coffee
  5. +25 −14 src/zombie/cookies.coffee
  6. +16 −0 src/zombie/history.coffee
  7. +27 −9 src/zombie/storage.coffee
View
4 CHANGELOG.md
@@ -20,8 +20,8 @@ time it takes to fire up Node, Cake, etc, so the reported time is
approximately a second smaller than the previously reported time for
0.8.11. All other things being equal.
- 285 Tests
- 3.4 sec to complete
+ 292 Tests
+ 3.7 sec to complete
### Version 0.8.11 2011-01-25
View
30 doc/API.md
@@ -352,6 +352,21 @@ value)`, `remove(name)` and `dump()`.
The `set` method accepts a third argument which may include the options
`expires`, `maxAge` and `secure`.
+### browser.loadCookies(String)
+
+Load cookies from a text string (e.g. previously created using
+`browser.saveCookies`.
+
+### browser.loadHistory(String)
+
+Load history from a text string (e.g. previously created using
+`browser.saveHistory`.
+
+### browser.loadStorage(String)
+
+Load local/session stroage from a text string (e.g. previously created
+using `browser.saveStorage`.
+
### browser.localStorage(host) : Storage
Returns local Storage based on the document origin (hostname/port).
@@ -364,6 +379,21 @@ The `Storage` object has the methods `key(index)`, `getItem(name)`,
`setItem(name, value)`, `removeItem(name)`, `clear()` and `dump`. It
also has the read-only property `length`.
+### browser.saveCookies() : String
+
+Save cookies to a text string. You can use this to load them back later
+on using `browser.loadCookies`.
+
+### browser.saveHistory() : String
+
+Save history to a text string. You can use this to load the data
+later on using `browser.loadHistory`.
+
+### browser.saveStorage() : String
+
+Save local/session storage to a text string. You can use this to load
+the data later on using `browser.loadStorage`.
+
### browser.sessionStorage(host) : Storage
Returns session Storage based on the document origin (hostname/port).
View
7 spec/browser-spec.coffee
@@ -352,5 +352,12 @@ vows.describe("Browser").addBatch(
assert.equal browser.sessionStorage("www.localhost").getItem("baz"), "value"
assert.equal forked.localStorage("www.localhost").getItem("foo"), "bar"
assert.equal forked.sessionStorage("www.localhost").getItem("baz"), "qux"
+ "should have independent history": ([forked, browser])->
+ assert.equal "http://localhost:3003/living", browser.location.href
+ assert.equal "http://localhost:3003/dead", forked.location.href
+ "should clone history from source": ([forked, browser])->
+ assert.equal "http://localhost:3003/dead", forked.location.href
+ forked.window.history.back()
+ assert.equal "http://localhost:3003/living", forked.location.href
).export(module)
View
45 src/zombie/browser.coffee
@@ -63,22 +63,17 @@ class Browser extends require("events").EventEmitter
else
throw "I don't recognize the option #{k}"
- # Fork
- # ----
-
# ### browser.fork() => Browser
#
# Return a new browser with a snapshot of this browser's data.
# Any changes to the forked browser's state do not affect this browser.
this.fork = ->
forked = new Browser()
- forked.importCookies(cookies.dump())
- forked.importStorage(storage.dump())
+ forked.loadCookies this.saveCookies()
+ forked.loadStorage this.saveStorage()
+ forked.loadHistory this.saveHistory()
return forked
- this.importCookies = (serialized) ->
- cookies.from(serialized)
- this.importStorage = (serialized) ->
- storage.from(serialized)
+
# Windows
# -------
@@ -355,6 +350,17 @@ class Browser extends require("events").EventEmitter
else
callback new Error("No link matching '#{selector}'")
+ # ### browser.saveHistory() => String
+ #
+ # Save history to a text string. You can use this to load the data
+ # later on using `browser.loadHistory`.
+ this.saveHistory = -> history.save()
+ # ### browser.loadHistory(String)
+ #
+ # Load history from a text string (e.g. previously created using
+ # `browser.saveHistory`.
+ this.loadHistory = (serialized)-> history.load serialized
+
# Forms
# -----
@@ -585,6 +591,17 @@ class Browser extends require("events").EventEmitter
#
# Returns all the cookies for this domain/path. Path defaults to "/".
this.cookies = (domain, path)-> cookies.access(domain, path)
+ # ### browser.saveCookies() => String
+ #
+ # Save cookies to a text string. You can use this to load them back
+ # later on using `browser.loadCookies`.
+ this.saveCookies = -> cookies.save()
+ # ### browser.loadCookies(String)
+ #
+ # Load cookies from a text string (e.g. previously created using
+ # `browser.saveCookies`.
+ this.loadCookies = (serialized)-> cookies.load serialized
+
# ### brower.localStorage(host) => Storage
#
# Returns local Storage based on the document origin (hostname/port). This
@@ -595,6 +612,16 @@ class Browser extends require("events").EventEmitter
# Returns session Storage based on the document origin (hostname/port). This
# is the same storage area you can access from any document of that origin.
this.sessionStorage = (host)-> storage.session(host)
+ # ### browser.saveStorage() => String
+ #
+ # Save local/session storage to a text string. You can use this to
+ # load the data later on using `browser.loadStorage`.
+ this.saveStorage = -> storage.save()
+ # ### browser.loadStorage(String)
+ #
+ # Load local/session stroage from a text string (e.g. previously
+ # created using `browser.saveStorage`.
+ this.loadStorage = (serialized)-> storage.load serialized
# Scripts
View
39 src/zombie/cookies.coffee
@@ -11,8 +11,8 @@ serialize = (browser, domain, path, name, cookie)->
str = str + "; secure" if cookie.secure
str
-# Unserialize a cookie
-unserialize = (serialized)->
+# Deserialize a cookie
+deserialize = (serialized)->
fields = serialized.split(/;+/)
first = fields[0].trim()
[name, value] = first.split(/\=/, 2)
@@ -129,8 +129,8 @@ class Cookies
# Handle case where we get array of headers.
serialized = serialized.join(",") if serialized.constructor == Array
for cookie in serialized.split(/,(?=[^;,]*=)|,$/)
- unserialized = unserialize(cookie)
- @set(unserialized.name, unserialized.value, unserialized)
+ cookie = deserialize(cookie)
+ @set(cookie.name, cookie.value, cookie)
#### cookies(host, path).addHeader(headers)
#
@@ -174,17 +174,28 @@ exports.use = (browser)->
# Add cookies accessor to window: documents need this.
extend = (window)->
window.__defineGetter__ "cookies", -> access(@location.hostname, @location.pathname)
+
+ # Used to dump state to console (debuggin)
dump = ->
- dump = []
+ serialized = []
+ for domain, in_domain of cookies
+ for path, in_path of in_domain
+ for name, cookie of in_path
+ serialized.push serialize(browser, domain, path, name, cookie)
+ serialized.join("\n")
+ # browser.saveCookies uses this
+ save = ->
+ serialized = ["# Saved on #{new Date().toISOString()}"]
for domain, in_domain of cookies
for path, in_path of in_domain
for name, cookie of in_path
- dump.push serialize(browser, domain, path, name, cookie)
- dump
- # Import cookies from a dump
- from = (serialized)->
- for cookie in serialized
- unserialized = unserialize(cookie)
- (new Cookies(browser, cookies, unserialized.domain, unserialized.path)).set(unserialized.name, unserialized.value, unserialized)
-
- return access: access, extend: extend, dump: dump, from: from
+ serialized.push serialize(browser, domain, path, name, cookie)
+ serialized.join("\n")
+ # browser.loadCookies uses this
+ load = (serialized)->
+ for cookie in serialized.split(/\n+/)
+ continue if cookie[0] == "#"
+ cookie = deserialize(cookie)
+ new Cookies(browser, cookies, cookie.domain, cookie.path).set(cookie.name, cookie.value, cookie)
+
+ return access: access, extend: extend, save: save, load: load
View
16 src/zombie/history.coffee
@@ -177,6 +177,7 @@ class History
window.__defineSetter__ "location", (url)=>
@_assign URL.resolve(stack[index]?.url, url)
+ # Used to dump state to console (debuggin)
this.dump = ->
dump = []
for i, entry of stack
@@ -187,6 +188,21 @@ class History
dump.push line
dump
+ # browser.saveHistory uses this
+ this.save = ->
+ serialized = []
+ for i, entry of stack
+ line = URL.format(entry.url)
+ line += " #{JSON.stringify(entry.state)}" if entry.pop
+ serialized.push line
+ serialized.join("\n")
+ # browser.loadHistory uses this
+ this.load = (serialized) ->
+ for line in serialized.split(/\n+/)
+ [url, state] = line.split(/\s/)
+ options = state && { state: JSON.parse(state), title: null, pop: true }
+ stack[++index] = new Entry(this, url, state)
+
# ## window.location
#
View
36 src/zombie/storage.coffee
@@ -120,26 +120,44 @@ class Storages
@document._sessionStorage ||= browser.sessionStorage(@location.host)
window.__defineGetter__ "localStorage", ->
@document._localStorage ||= browser.localStorage(@location.host)
+
+ # Used to dump state to console (debuggin)
this.dump = ->
- dump = []
+ serialized = []
+ for domain, area of localAreas
+ pairs = area.pairs
+ serialized.push "#{domain} local:"
+ for pair in pairs
+ serialized.push " #{pair[0]} = #{pair[1]}"
+ for domain, area of sessionAreas
+ pairs = area.pairs
+ serialized.push "#{domain} session:"
+ for pair in pairs
+ serialized.push " #{pair[0]} = #{pair[1]}"
+ serialized.join("\n")
+ # browser.saveStorage uses this
+ this.save = ->
+ serialized = ["# Saved on #{new Date().toISOString()}"]
for domain, area of localAreas
pairs = area.pairs
if pairs.length > 0
- dump.push "#{domain} local:"
+ serialized.push "#{domain} local:"
for pair in pairs
- dump.push " #{pair[0]} = #{pair[1]}"
+ serialized.push " #{escape pair[0]} = #{escape pair[1]}"
for domain, area of sessionAreas
pairs = area.pairs
if pairs.length > 0
- dump.push "#{domain} session:"
+ serialized.push "#{domain} session:"
for pair in pairs
- dump.push " #{pair[0]} = #{pair[1]}"
- dump
- this.from = (serialized) ->
- for item in serialized
+ serialized.push " #{escape pair[0]} = #{escape pair[1]}"
+ serialized.join("\n")
+ # browser.loadStorage uses this
+ this.load = (serialized) ->
+ for item in serialized.split(/\n+/)
+ continue if item[0] == "#"
if (item[0] == " ")
[key, value] = item.split("=")
- storage.setItem key.trim(), value.trim() if storage
+ storage.setItem unescape(key.trim()), unescape(value.trim()) if storage
else
[domain, type] = item.split(" ")
if (type == "local:")

0 comments on commit a2b92c9

Please sign in to comment.
Something went wrong with that request. Please try again.