poltergeist 0.7.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  module Capybara::Poltergeist
2
2
  class Client
3
3
  PHANTOMJS_SCRIPT = File.expand_path('../client/compiled/main.js', __FILE__)
4
- PHANTOMJS_VERSION = '1.6.0'
4
+ PHANTOMJS_VERSION = '1.7.0'
5
5
  PHANTOMJS_NAME = 'phantomjs'
6
6
 
7
7
  def self.start(*args)
@@ -61,11 +61,11 @@ module Capybara::Poltergeist
61
61
  def check_phantomjs_version
62
62
  return if @phantomjs_version_checked
63
63
 
64
- version = `#{path} --version`.chomp
64
+ version = `#{path} --version` rescue nil
65
65
 
66
- if $? != 0
66
+ if version.nil? || $? != 0
67
67
  raise PhantomJSFailed.new($?)
68
- elsif version < PHANTOMJS_VERSION
68
+ elsif version.chomp < PHANTOMJS_VERSION
69
69
  raise PhantomJSTooOld.new(version)
70
70
  end
71
71
 
@@ -4,8 +4,6 @@ class PoltergeistAgent
4
4
  constructor: ->
5
5
  @elements = []
6
6
  @nodes = {}
7
- @windows = []
8
- this.pushWindow(window)
9
7
 
10
8
  externalCall: (name, args) ->
11
9
  try
@@ -14,39 +12,23 @@ class PoltergeistAgent
14
12
  { error: { message: error.toString(), stack: error.stack } }
15
13
 
16
14
  @stringify: (object) ->
17
- JSON.stringify object, (key, value) ->
18
- if Array.isArray(this[key])
19
- return this[key]
15
+ try
16
+ JSON.stringify object, (key, value) ->
17
+ if Array.isArray(this[key])
18
+ return this[key]
19
+ else
20
+ return value
21
+ catch error
22
+ if error instanceof TypeError
23
+ '"(cyclic structure)"'
20
24
  else
21
- return value
22
-
23
- pushWindow: (new_window) ->
24
- @windows.push(new_window)
25
-
26
- @window = new_window
27
- @document = @window.document
28
-
29
- null
30
-
31
- popWindow: ->
32
- @windows.pop()
33
-
34
- @window = @windows[@windows.length - 1]
35
- @document = @window.document
36
-
37
- null
38
-
39
- pushFrame: (id) ->
40
- this.pushWindow @document.getElementById(id).contentWindow
41
-
42
- popFrame: ->
43
- this.popWindow()
25
+ throw error
44
26
 
45
27
  currentUrl: ->
46
28
  window.location.toString()
47
29
 
48
- find: (selector, within = @document) ->
49
- results = @document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
30
+ find: (selector, within = document) ->
31
+ results = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
50
32
  ids = []
51
33
 
52
34
  for i in [0...results.snapshotLength]
@@ -59,8 +41,8 @@ class PoltergeistAgent
59
41
  @elements.length - 1
60
42
 
61
43
  documentSize: ->
62
- height: @document.documentElement.scrollHeight,
63
- width: @document.documentElement.scrollWidth
44
+ height: document.documentElement.scrollHeight,
45
+ width: document.documentElement.scrollWidth
64
46
 
65
47
  get: (id) ->
66
48
  @nodes[id] or= new PoltergeistAgent.Node(this, @elements[id])
@@ -91,7 +73,7 @@ class PoltergeistAgent.Node
91
73
  isObsolete: ->
92
74
  obsolete = (element) =>
93
75
  if element.parentNode?
94
- if element.parentNode == @agent.document
76
+ if element.parentNode == document
95
77
  false
96
78
  else
97
79
  obsolete element.parentNode
@@ -104,51 +86,15 @@ class PoltergeistAgent.Node
104
86
  event.initEvent('change', true, false)
105
87
  @element.dispatchEvent(event)
106
88
 
107
- input: ->
108
- event = document.createEvent('HTMLEvents')
109
- event.initEvent('input', true, false)
110
- @element.dispatchEvent(event)
111
-
112
- keyupdowned: (eventName, keyCode) ->
113
- event = document.createEvent('UIEvents')
114
- event.initEvent(eventName, true, true)
115
- event.keyCode = keyCode
116
- event.which = keyCode
117
- event.charCode = 0
118
- @element.dispatchEvent(event)
119
-
120
- keypressed: (altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) ->
121
- event = document.createEvent('UIEvents')
122
- event.initEvent('keypress', true, true)
123
- event.window = @agent.window
124
- event.altKey = altKey
125
- event.ctrlKey = ctrlKey
126
- event.shiftKey = shiftKey
127
- event.metaKey = metaKey
128
- event.keyCode = keyCode
129
- event.charCode = charCode
130
- event.which = keyCode
131
- @element.dispatchEvent(event)
132
-
133
89
  insideBody: ->
134
- @element == @agent.document.body ||
135
- @agent.document.evaluate('ancestor::body', @element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue
90
+ @element == document.body ||
91
+ document.evaluate('ancestor::body', @element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue
136
92
 
137
93
  text: ->
138
- return '' unless this.isVisible()
139
-
140
- if this.insideBody()
141
- el = @element
94
+ if @element.tagName == 'TEXTAREA'
95
+ @element.textContent
142
96
  else
143
- el = @agent.document.body
144
-
145
- results = @agent.document.evaluate('.//text()[not(ancestor::script)]', el, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
146
- text = ''
147
-
148
- for i in [0...results.snapshotLength]
149
- node = results.snapshotItem(i)
150
- text += node.textContent if this.isVisible(node.parentNode)
151
- text
97
+ @element.innerText
152
98
 
153
99
  getAttribute: (name) ->
154
100
  if name == 'checked' || name == 'selected'
@@ -165,25 +111,6 @@ class PoltergeistAgent.Node
165
111
  else
166
112
  @element.value
167
113
 
168
- set: (value) ->
169
- if (@element.maxLength >= 0)
170
- value = value.substr(0, @element.maxLength)
171
-
172
- @element.value = ''
173
- this.trigger('focus')
174
-
175
- for char in value
176
- @element.value += char
177
-
178
- keyCode = this.characterToKeyCode(char)
179
- this.keyupdowned('keydown', keyCode)
180
- this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0))
181
- this.keyupdowned('keyup', keyCode)
182
-
183
- this.changed()
184
- this.input()
185
- this.trigger('blur')
186
-
187
114
  isMultiple: ->
188
115
  @element.multiple
189
116
 
@@ -207,7 +134,7 @@ class PoltergeistAgent.Node
207
134
  isVisible: (element) ->
208
135
  element = @element unless element
209
136
 
210
- if @agent.window.getComputedStyle(element).display == 'none'
137
+ if window.getComputedStyle(element).display == 'none'
211
138
  false
212
139
  else if element.parentElement
213
140
  this.isVisible element.parentElement
@@ -216,6 +143,7 @@ class PoltergeistAgent.Node
216
143
 
217
144
  position: ->
218
145
  rect = @element.getClientRects()[0]
146
+ throw new PoltergeistAgent.ObsoleteNode unless rect
219
147
 
220
148
  {
221
149
  top: rect.top,
@@ -230,7 +158,7 @@ class PoltergeistAgent.Node
230
158
  if Node.EVENTS.MOUSE.indexOf(name) != -1
231
159
  event = document.createEvent('MouseEvent')
232
160
  event.initMouseEvent(
233
- name, true, true, @agent.window, 0, 0, 0, 0, 0,
161
+ name, true, true, window, 0, 0, 0, 0, 0,
234
162
  false, false, false, false, 0, null
235
163
  )
236
164
  else if Node.EVENTS.FOCUS.indexOf(name) != -1
@@ -241,6 +169,13 @@ class PoltergeistAgent.Node
241
169
 
242
170
  @element.dispatchEvent(event)
243
171
 
172
+ focusAndHighlight: ->
173
+ @element.focus()
174
+ @element.select()
175
+
176
+ blur: ->
177
+ @element.blur()
178
+
244
179
  clickTest: (x, y) ->
245
180
  el = origEl = document.elementFromPoint(x, y)
246
181
 
@@ -260,45 +195,6 @@ class PoltergeistAgent.Node
260
195
  selector += ".#{className}"
261
196
  selector
262
197
 
263
- characterToKeyCode: (character) ->
264
- code = character.toUpperCase().charCodeAt(0)
265
- specialKeys =
266
- 96: 192 #`
267
- 45: 189 #-
268
- 61: 187 #=
269
- 91: 219 #[
270
- 93: 221 #]
271
- 92: 220 #\
272
- 59: 186 #;
273
- 39: 222 #'
274
- 44: 188 #,
275
- 46: 190 #.
276
- 47: 191 #/
277
- 127: 46 #delete
278
- 126: 192 #~
279
- 33: 49 #!
280
- 64: 50 #@
281
- 35: 51 ##
282
- 36: 52 #$
283
- 37: 53 #%
284
- 94: 54 #^
285
- 38: 55 #&
286
- 42: 56 #*
287
- 40: 57 #(
288
- 41: 48 #)
289
- 95: 189 #_
290
- 43: 187 #+
291
- 123: 219 #{
292
- 125: 221 #}
293
- 124: 220 #|
294
- 58: 186 #:
295
- 34: 222 #"
296
- 60: 188 #<
297
- 62: 190 #>
298
- 63: 191 #?
299
-
300
- specialKeys[code] || code
301
-
302
198
  isDOMEqual: (other_id) ->
303
199
  @element == @agent.get(other_id).element
304
200
 
@@ -1,15 +1,20 @@
1
1
  class Poltergeist.Browser
2
2
  constructor: (@owner, width, height) ->
3
- @width = width || 1024
4
- @height = height || 768
5
- @state = 'default'
6
- @page_id = 0
3
+ @width = width || 1024
4
+ @height = height || 768
5
+ @state = 'default'
6
+ @page_stack = []
7
+ @page_id = 0
7
8
 
8
9
  this.resetPage()
9
10
 
10
11
  resetPage: ->
11
- @page.release() if @page?
12
- @page = new Poltergeist.WebPage(@width, @height)
12
+ if @page?
13
+ @page.release()
14
+ phantom.clearCookies()
15
+
16
+ @page = new Poltergeist.WebPage
17
+ @page.setViewportSize(width: @width, height: @height)
13
18
 
14
19
  @page.onLoadStarted = =>
15
20
  @state = 'loading' if @state == 'clicked'
@@ -19,18 +24,32 @@ class Poltergeist.Browser
19
24
 
20
25
  @page.onLoadFinished = (status) =>
21
26
  if @state == 'loading'
22
- this.sendResponse(status)
27
+ this.sendResponse(status: status, click: @last_click)
28
+ @state = 'default'
29
+ else if @state == 'awaiting_frame_load'
30
+ this.sendResponse(true)
23
31
  @state = 'default'
24
32
 
25
33
  @page.onInitialized = =>
26
34
  @page_id += 1
27
35
 
36
+ @page.onPageCreated = (sub_page) =>
37
+ if @state == 'awaiting_sub_page'
38
+ name = @page_name
39
+ @state = 'default'
40
+ @page_name = null
41
+
42
+ # At this point subpage isn't fully initialized, so we can't check
43
+ # its name. Instead, we just schedule another attempt to push the
44
+ # window.
45
+ setTimeout((=> this.push_window(name)), 0)
46
+
28
47
  sendResponse: (response) ->
29
48
  errors = @page.errors()
30
49
 
31
50
  if errors.length > 0
32
51
  @page.clearErrors()
33
- throw new Poltergeist.JavascriptError(errors)
52
+ @owner.sendError(new Poltergeist.JavascriptError(errors))
34
53
  else
35
54
  @owner.sendResponse(response)
36
55
 
@@ -40,9 +59,16 @@ class Poltergeist.Browser
40
59
  else
41
60
  throw new Poltergeist.ObsoleteNode
42
61
 
43
- visit: (url, headers) ->
44
- @state = 'loading'
45
- @page.open(url, operation: "get", headers: headers)
62
+ visit: (url) ->
63
+ @state = 'loading'
64
+ prev_url = @page.currentUrl()
65
+
66
+ @page.open(url)
67
+
68
+ if /#/.test(url) && prev_url.split('#')[0] == url.split('#')[0]
69
+ # hashchange occurred, so there will be no onLoadFinished
70
+ @state = 'default'
71
+ this.sendResponse 'success'
46
72
 
47
73
  current_url: ->
48
74
  this.sendResponse @page.currentUrl()
@@ -103,12 +129,40 @@ class Poltergeist.Browser
103
129
  @page.execute("function() { #{script} }")
104
130
  this.sendResponse(true)
105
131
 
106
- push_frame: (id) ->
107
- @page.pushFrame(id)
108
- this.sendResponse(true)
132
+ push_frame: (name) ->
133
+ if @page.pushFrame(name)
134
+ if @page.currentUrl() == 'about:blank'
135
+ @state = 'awaiting_frame_load'
136
+ else
137
+ this.sendResponse(true)
138
+ else
139
+ # There's currently no PhantomJS callback available for frame creation,
140
+ # so we have to poll
141
+ setTimeout((=> this.push_frame(name)), 50)
109
142
 
110
143
  pop_frame: ->
111
- @page.popFrame()
144
+ this.sendResponse(@page.popFrame())
145
+
146
+ push_window: (name) ->
147
+ sub_page = @page.getPage(name)
148
+
149
+ if sub_page
150
+ if sub_page.currentUrl() == 'about:blank'
151
+ sub_page.onLoadFinished = =>
152
+ sub_page.onLoadFinished = null
153
+ this.push_window(name)
154
+ else
155
+ @page_stack.push(@page)
156
+ @page = sub_page
157
+ @page_id += 1
158
+ this.sendResponse(true)
159
+ else
160
+ @page_name = name
161
+ @state = 'awaiting_sub_page'
162
+
163
+ pop_window: ->
164
+ prev_page = @page_stack.pop()
165
+ @page = prev_page if prev_page
112
166
  this.sendResponse(true)
113
167
 
114
168
  click: (page_id, id) ->
@@ -119,11 +173,13 @@ class Poltergeist.Browser
119
173
  # state and wait for onLoadFinished before sending a response.
120
174
  @state = 'clicked'
121
175
 
122
- node.click()
176
+ @last_click = node.click()
123
177
 
124
- if @state != 'loading'
125
- @state = 'default'
126
- this.sendResponse(true)
178
+ setTimeout =>
179
+ if @state != 'loading'
180
+ @state = 'default'
181
+ this.sendResponse(@last_click)
182
+ , 5
127
183
 
128
184
  drag: (page_id, id, other_id) ->
129
185
  this.node(page_id, id).dragTo this.node(page_id, other_id)
@@ -163,6 +219,28 @@ class Poltergeist.Browser
163
219
  network_traffic: ->
164
220
  this.sendResponse(@page.networkTraffic())
165
221
 
222
+ set_headers: (headers) ->
223
+ # Workaround for https://code.google.com/p/phantomjs/issues/detail?id=745
224
+ @page.setUserAgent(headers['User-Agent']) if headers['User-Agent']
225
+ @page.setCustomHeaders(headers)
226
+ this.sendResponse(true)
227
+
228
+ response_headers: ->
229
+ this.sendResponse(@page.responseHeaders())
230
+
231
+ cookies: ->
232
+ this.sendResponse(@page.cookies())
233
+
234
+ # We're using phantom.addCookie so that cookies can be set
235
+ # before the first page load has taken place.
236
+ set_cookie: (cookie) ->
237
+ phantom.addCookie(cookie)
238
+ this.sendResponse(true)
239
+
240
+ remove_cookie: (name) ->
241
+ @page.deleteCookie(name)
242
+ this.sendResponse(true)
243
+
166
244
  exit: ->
167
245
  phantom.exit()
168
246
 
@@ -5,8 +5,6 @@ PoltergeistAgent = (function() {
5
5
  function PoltergeistAgent() {
6
6
  this.elements = [];
7
7
  this.nodes = {};
8
- this.windows = [];
9
- this.pushWindow(window);
10
8
  }
11
9
 
12
10
  PoltergeistAgent.prototype.externalCall = function(name, args) {
@@ -25,35 +23,21 @@ PoltergeistAgent = (function() {
25
23
  };
26
24
 
27
25
  PoltergeistAgent.stringify = function(object) {
28
- return JSON.stringify(object, function(key, value) {
29
- if (Array.isArray(this[key])) {
30
- return this[key];
26
+ try {
27
+ return JSON.stringify(object, function(key, value) {
28
+ if (Array.isArray(this[key])) {
29
+ return this[key];
30
+ } else {
31
+ return value;
32
+ }
33
+ });
34
+ } catch (error) {
35
+ if (error instanceof TypeError) {
36
+ return '"(cyclic structure)"';
31
37
  } else {
32
- return value;
38
+ throw error;
33
39
  }
34
- });
35
- };
36
-
37
- PoltergeistAgent.prototype.pushWindow = function(new_window) {
38
- this.windows.push(new_window);
39
- this.window = new_window;
40
- this.document = this.window.document;
41
- return null;
42
- };
43
-
44
- PoltergeistAgent.prototype.popWindow = function() {
45
- this.windows.pop();
46
- this.window = this.windows[this.windows.length - 1];
47
- this.document = this.window.document;
48
- return null;
49
- };
50
-
51
- PoltergeistAgent.prototype.pushFrame = function(id) {
52
- return this.pushWindow(this.document.getElementById(id).contentWindow);
53
- };
54
-
55
- PoltergeistAgent.prototype.popFrame = function() {
56
- return this.popWindow();
40
+ }
57
41
  };
58
42
 
59
43
  PoltergeistAgent.prototype.currentUrl = function() {
@@ -63,9 +47,9 @@ PoltergeistAgent = (function() {
63
47
  PoltergeistAgent.prototype.find = function(selector, within) {
64
48
  var i, ids, results, _i, _ref;
65
49
  if (within == null) {
66
- within = this.document;
50
+ within = document;
67
51
  }
68
- results = this.document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
52
+ results = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
69
53
  ids = [];
70
54
  for (i = _i = 0, _ref = results.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
71
55
  ids.push(this.register(results.snapshotItem(i)));
@@ -80,8 +64,8 @@ PoltergeistAgent = (function() {
80
64
 
81
65
  PoltergeistAgent.prototype.documentSize = function() {
82
66
  return {
83
- height: this.document.documentElement.scrollHeight,
84
- width: this.document.documentElement.scrollWidth
67
+ height: document.documentElement.scrollHeight,
68
+ width: document.documentElement.scrollWidth
85
69
  };
86
70
  };
87
71
 
@@ -140,7 +124,7 @@ PoltergeistAgent.Node = (function() {
140
124
  _this = this;
141
125
  obsolete = function(element) {
142
126
  if (element.parentNode != null) {
143
- if (element.parentNode === _this.agent.document) {
127
+ if (element.parentNode === document) {
144
128
  return false;
145
129
  } else {
146
130
  return obsolete(element.parentNode);
@@ -159,61 +143,16 @@ PoltergeistAgent.Node = (function() {
159
143
  return this.element.dispatchEvent(event);
160
144
  };
161
145
 
162
- Node.prototype.input = function() {
163
- var event;
164
- event = document.createEvent('HTMLEvents');
165
- event.initEvent('input', true, false);
166
- return this.element.dispatchEvent(event);
167
- };
168
-
169
- Node.prototype.keyupdowned = function(eventName, keyCode) {
170
- var event;
171
- event = document.createEvent('UIEvents');
172
- event.initEvent(eventName, true, true);
173
- event.keyCode = keyCode;
174
- event.which = keyCode;
175
- event.charCode = 0;
176
- return this.element.dispatchEvent(event);
177
- };
178
-
179
- Node.prototype.keypressed = function(altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
180
- var event;
181
- event = document.createEvent('UIEvents');
182
- event.initEvent('keypress', true, true);
183
- event.window = this.agent.window;
184
- event.altKey = altKey;
185
- event.ctrlKey = ctrlKey;
186
- event.shiftKey = shiftKey;
187
- event.metaKey = metaKey;
188
- event.keyCode = keyCode;
189
- event.charCode = charCode;
190
- event.which = keyCode;
191
- return this.element.dispatchEvent(event);
192
- };
193
-
194
146
  Node.prototype.insideBody = function() {
195
- return this.element === this.agent.document.body || this.agent.document.evaluate('ancestor::body', this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
147
+ return this.element === document.body || document.evaluate('ancestor::body', this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
196
148
  };
197
149
 
198
150
  Node.prototype.text = function() {
199
- var el, i, node, results, text, _i, _ref;
200
- if (!this.isVisible()) {
201
- return '';
202
- }
203
- if (this.insideBody()) {
204
- el = this.element;
151
+ if (this.element.tagName === 'TEXTAREA') {
152
+ return this.element.textContent;
205
153
  } else {
206
- el = this.agent.document.body;
154
+ return this.element.innerText;
207
155
  }
208
- results = this.agent.document.evaluate('.//text()[not(ancestor::script)]', el, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
209
- text = '';
210
- for (i = _i = 0, _ref = results.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
211
- node = results.snapshotItem(i);
212
- if (this.isVisible(node.parentNode)) {
213
- text += node.textContent;
214
- }
215
- }
216
- return text;
217
156
  };
218
157
 
219
158
  Node.prototype.getAttribute = function(name) {
@@ -245,26 +184,6 @@ PoltergeistAgent.Node = (function() {
245
184
  }
246
185
  };
247
186
 
248
- Node.prototype.set = function(value) {
249
- var char, keyCode, _i, _len;
250
- if (this.element.maxLength >= 0) {
251
- value = value.substr(0, this.element.maxLength);
252
- }
253
- this.element.value = '';
254
- this.trigger('focus');
255
- for (_i = 0, _len = value.length; _i < _len; _i++) {
256
- char = value[_i];
257
- this.element.value += char;
258
- keyCode = this.characterToKeyCode(char);
259
- this.keyupdowned('keydown', keyCode);
260
- this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
261
- this.keyupdowned('keyup', keyCode);
262
- }
263
- this.changed();
264
- this.input();
265
- return this.trigger('blur');
266
- };
267
-
268
187
  Node.prototype.isMultiple = function() {
269
188
  return this.element.multiple;
270
189
  };
@@ -295,7 +214,7 @@ PoltergeistAgent.Node = (function() {
295
214
  if (!element) {
296
215
  element = this.element;
297
216
  }
298
- if (this.agent.window.getComputedStyle(element).display === 'none') {
217
+ if (window.getComputedStyle(element).display === 'none') {
299
218
  return false;
300
219
  } else if (element.parentElement) {
301
220
  return this.isVisible(element.parentElement);
@@ -307,6 +226,9 @@ PoltergeistAgent.Node = (function() {
307
226
  Node.prototype.position = function() {
308
227
  var rect;
309
228
  rect = this.element.getClientRects()[0];
229
+ if (!rect) {
230
+ throw new PoltergeistAgent.ObsoleteNode;
231
+ }
310
232
  return {
311
233
  top: rect.top,
312
234
  right: rect.right,
@@ -321,7 +243,7 @@ PoltergeistAgent.Node = (function() {
321
243
  var event;
322
244
  if (Node.EVENTS.MOUSE.indexOf(name) !== -1) {
323
245
  event = document.createEvent('MouseEvent');
324
- event.initMouseEvent(name, true, true, this.agent.window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
246
+ event.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
325
247
  } else if (Node.EVENTS.FOCUS.indexOf(name) !== -1) {
326
248
  event = document.createEvent('HTMLEvents');
327
249
  event.initEvent(name, true, true);
@@ -331,6 +253,15 @@ PoltergeistAgent.Node = (function() {
331
253
  return this.element.dispatchEvent(event);
332
254
  };
333
255
 
256
+ Node.prototype.focusAndHighlight = function() {
257
+ this.element.focus();
258
+ return this.element.select();
259
+ };
260
+
261
+ Node.prototype.blur = function() {
262
+ return this.element.blur();
263
+ };
264
+
334
265
  Node.prototype.clickTest = function(x, y) {
335
266
  var el, origEl;
336
267
  el = origEl = document.elementFromPoint(x, y);
@@ -364,47 +295,6 @@ PoltergeistAgent.Node = (function() {
364
295
  return selector;
365
296
  };
366
297
 
367
- Node.prototype.characterToKeyCode = function(character) {
368
- var code, specialKeys;
369
- code = character.toUpperCase().charCodeAt(0);
370
- specialKeys = {
371
- 96: 192,
372
- 45: 189,
373
- 61: 187,
374
- 91: 219,
375
- 93: 221,
376
- 92: 220,
377
- 59: 186,
378
- 39: 222,
379
- 44: 188,
380
- 46: 190,
381
- 47: 191,
382
- 127: 46,
383
- 126: 192,
384
- 33: 49,
385
- 64: 50,
386
- 35: 51,
387
- 36: 52,
388
- 37: 53,
389
- 94: 54,
390
- 38: 55,
391
- 42: 56,
392
- 40: 57,
393
- 41: 48,
394
- 95: 189,
395
- 43: 187,
396
- 123: 219,
397
- 125: 221,
398
- 124: 220,
399
- 58: 186,
400
- 34: 222,
401
- 60: 188,
402
- 62: 190,
403
- 63: 191
404
- };
405
- return specialKeys[code] || code;
406
- };
407
-
408
298
  Node.prototype.isDOMEqual = function(other_id) {
409
299
  return this.element === this.agent.get(other_id).element;
410
300
  };