poltergeist 1.5.1 → 1.6.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.
@@ -2,67 +2,46 @@ class Poltergeist.Browser
2
2
  constructor: (@owner, width, height) ->
3
3
  @width = width || 1024
4
4
  @height = height || 768
5
- @state = 'default'
6
- @page_stack = []
7
- @page_id = 0
5
+ @pages = []
8
6
  @js_errors = true
9
7
  @_debug = false
8
+ @_counter = 0
10
9
 
11
10
  this.resetPage()
12
11
 
13
12
  resetPage: ->
13
+ [@_counter, @pages] = [0, []]
14
+
14
15
  if @page?
15
- @page.release()
16
+ unless @page.closed
17
+ @page.clearLocalStorage() if @page.currentUrl() != 'about:blank'
18
+ @page.release()
16
19
  phantom.clearCookies()
17
20
 
18
- @page = new Poltergeist.WebPage
21
+ @page = @currentPage = new Poltergeist.WebPage
19
22
  @page.setViewportSize(width: @width, height: @height)
23
+ @page.handle = "#{@_counter++}"
24
+ @pages.push(@page)
20
25
 
21
- @page.onLoadStarted = =>
22
- this.setState 'loading' if @state == 'mouse_event'
23
-
24
- @page.onNavigationRequested = (url, navigation) =>
25
- this.setState 'loading' if @state == 'mouse_event' && navigation == 'FormSubmitted'
26
-
27
- @page.onLoadFinished = (status) =>
28
- if @state == 'loading'
29
- this.sendResponse(status: status, position: @last_mouse_event)
30
- this.setState 'default'
31
- else if @state == 'awaiting_frame_load'
32
- this.sendResponse(true)
33
- this.setState 'default'
34
-
35
- @page.onInitialized = =>
36
- @page_id += 1
26
+ @page.onPageCreated = (newPage) =>
27
+ page = new Poltergeist.WebPage(newPage)
28
+ page.handle = "#{@_counter++}"
29
+ @pages.push(page)
37
30
 
38
- @page.onPageCreated = (sub_page) =>
39
- if @state == 'awaiting_sub_page'
40
- name = @page_name
41
- @page_name = null
42
-
43
- this.setState 'default'
44
-
45
- # At this point subpage isn't fully initialized, so we can't check
46
- # its name. Instead, we just schedule another attempt to push the
47
- # window.
48
- setTimeout((=> this.push_window(name)), 0)
31
+ getPageByHandle: (handle) ->
32
+ @pages.filter((p) -> !p.closed && p.handle == handle)[0]
49
33
 
50
34
  runCommand: (name, args) ->
51
- this.setState "default"
35
+ @currentPage.state = 'default'
52
36
  this[name].apply(this, args)
53
37
 
54
38
  debug: (message) ->
55
39
  if @_debug
56
40
  console.log "poltergeist [#{new Date().getTime()}] #{message}"
57
41
 
58
- setState: (state) ->
59
- return if @state == state
60
- this.debug "state #{@state} -> #{state}"
61
- @state = state
62
-
63
42
  sendResponse: (response) ->
64
- errors = @page.errors()
65
- @page.clearErrors()
43
+ errors = @currentPage.errors
44
+ @currentPage.clearErrors()
66
45
 
67
46
  if errors.length > 0 && @js_errors
68
47
  @owner.sendError(new Poltergeist.JavascriptError(errors))
@@ -70,47 +49,53 @@ class Poltergeist.Browser
70
49
  @owner.sendResponse(response)
71
50
 
72
51
  add_extension: (extension) ->
73
- @page.injectExtension extension
52
+ @currentPage.injectExtension extension
74
53
  this.sendResponse 'success'
75
54
 
76
55
  node: (page_id, id) ->
77
- if page_id == @page_id
78
- @page.get(id)
56
+ if @currentPage.id == page_id
57
+ @currentPage.get(id)
79
58
  else
80
59
  throw new Poltergeist.ObsoleteNode
81
60
 
82
61
  visit: (url) ->
83
- this.setState 'loading'
62
+ @currentPage.state = 'loading'
84
63
 
85
64
  # Prevent firing `page.onInitialized` event twice. Calling currentUrl
86
65
  # method before page is actually opened fires this event for the first time.
87
66
  # The second time will be in the right place after `page.open`
88
- prev_url = if @page.source() is null then 'about:blank' else @page.currentUrl()
67
+ prevUrl = if @currentPage.source is null then 'about:blank' else @currentPage.currentUrl()
89
68
 
90
- @page.open(url)
69
+ @currentPage.open(url)
91
70
 
92
- if /#/.test(url) && prev_url.split('#')[0] == url.split('#')[0]
93
- # hashchange occurred, so there will be no onLoadFinished
94
- this.setState 'default'
95
- this.sendResponse 'success'
71
+ if /#/.test(url) && prevUrl.split('#')[0] == url.split('#')[0]
72
+ # Hash change occurred, so there will be no onLoadFinished
73
+ @currentPage.state = 'default'
74
+ this.sendResponse(status: 'success')
75
+ else
76
+ @currentPage.waitState 'default', =>
77
+ if @currentPage.statusCode == null && @currentPage.status == 'fail'
78
+ @owner.sendError(new Poltergeist.StatusFailError)
79
+ else
80
+ this.sendResponse(status: @currentPage.status)
96
81
 
97
82
  current_url: ->
98
- this.sendResponse @page.currentUrl()
83
+ this.sendResponse @currentPage.currentUrl()
99
84
 
100
85
  status_code: ->
101
- this.sendResponse @page.statusCode()
86
+ this.sendResponse @currentPage.statusCode
102
87
 
103
88
  body: ->
104
- this.sendResponse @page.content()
89
+ this.sendResponse @currentPage.content()
105
90
 
106
91
  source: ->
107
- this.sendResponse @page.source()
92
+ this.sendResponse @currentPage.source
108
93
 
109
94
  title: ->
110
- this.sendResponse @page.title()
95
+ this.sendResponse @currentPage.title()
111
96
 
112
97
  find: (method, selector) ->
113
- this.sendResponse(page_id: @page_id, ids: @page.find(method, selector))
98
+ this.sendResponse(page_id: @currentPage.id, ids: @currentPage.find(method, selector))
114
99
 
115
100
  find_within: (page_id, id, method, selector) ->
116
101
  this.sendResponse this.node(page_id, id).find(method, selector)
@@ -127,6 +112,12 @@ class Poltergeist.Browser
127
112
  attribute: (page_id, id, name) ->
128
113
  this.sendResponse this.node(page_id, id).getAttribute(name)
129
114
 
115
+ attributes: (page_id, id, name) ->
116
+ this.sendResponse this.node(page_id, id).getAttributes()
117
+
118
+ parents: (page_id, id) ->
119
+ this.sendResponse this.node(page_id, id).parentIds()
120
+
130
121
  value: (page_id, id) ->
131
122
  this.sendResponse this.node(page_id, id).value()
132
123
 
@@ -140,9 +131,9 @@ class Poltergeist.Browser
140
131
  select_file: (page_id, id, value) ->
141
132
  node = this.node(page_id, id)
142
133
 
143
- @page.beforeUpload(node.id)
144
- @page.uploadFile('[_poltergeist_selected]', value)
145
- @page.afterUpload(node.id)
134
+ @currentPage.beforeUpload(node.id)
135
+ @currentPage.uploadFile('[_poltergeist_selected]', value)
136
+ @currentPage.afterUpload(node.id)
146
137
 
147
138
  this.sendResponse(true)
148
139
 
@@ -159,16 +150,23 @@ class Poltergeist.Browser
159
150
  this.sendResponse this.node(page_id, id).isDisabled()
160
151
 
161
152
  evaluate: (script) ->
162
- this.sendResponse @page.evaluate("function() { return #{script} }")
153
+ this.sendResponse @currentPage.evaluate("function() { return #{script} }")
163
154
 
164
155
  execute: (script) ->
165
- @page.execute("function() { #{script} }")
156
+ @currentPage.execute("function() { #{script} }")
166
157
  this.sendResponse(true)
167
158
 
159
+ frameUrl: (frame_name) ->
160
+ @currentPage.frameUrl(frame_name)
161
+
168
162
  push_frame: (name, timeout = new Date().getTime() + 2000) ->
169
- if @page.pushFrame(name)
170
- if @page.currentUrl() == 'about:blank'
171
- this.setState 'awaiting_frame_load'
163
+ if @frameUrl(name) in @currentPage.blockedUrls()
164
+ this.sendResponse(true)
165
+ else if @currentPage.pushFrame(name)
166
+ if @currentPage.currentUrl() == 'about:blank'
167
+ @currentPage.state = 'awaiting_frame_load'
168
+ @currentPage.waitState 'default', =>
169
+ this.sendResponse(true)
172
170
  else
173
171
  this.sendResponse(true)
174
172
  else
@@ -177,53 +175,72 @@ class Poltergeist.Browser
177
175
  else
178
176
  @owner.sendError(new Poltergeist.FrameNotFound(name))
179
177
 
180
- pages: ->
181
- this.sendResponse(@page.pages())
182
-
183
178
  pop_frame: ->
184
- this.sendResponse(@page.popFrame())
179
+ this.sendResponse(@currentPage.popFrame())
185
180
 
186
- push_window: (name) ->
187
- sub_page = @page.getPage(name)
181
+ window_handles: ->
182
+ handles = @pages.filter((p) -> !p.closed).map((p) -> p.handle)
183
+ this.sendResponse(handles)
188
184
 
189
- if sub_page
190
- if sub_page.currentUrl() == 'about:blank'
191
- sub_page.onLoadFinished = =>
192
- sub_page.onLoadFinished = null
193
- this.push_window(name)
185
+ window_handle: (name = null) ->
186
+ handle = if name
187
+ page = @pages.filter((p) -> !p.closed && p.windowName() == name)[0]
188
+ if page then page.handle else null
189
+ else
190
+ @currentPage.handle
191
+
192
+ this.sendResponse(handle)
193
+
194
+ switch_to_window: (handle) ->
195
+ page = @getPageByHandle(handle)
196
+ if page
197
+ if page != @currentPage
198
+ page.waitState 'default', =>
199
+ @currentPage = page
200
+ this.sendResponse(true)
194
201
  else
195
- @page_stack.push(@page)
196
- @page = sub_page
197
- @page_id += 1
198
202
  this.sendResponse(true)
199
203
  else
200
- @page_name = name
201
- this.setState 'awaiting_sub_page'
204
+ throw new Poltergeist.NoSuchWindowError
202
205
 
203
- pop_window: ->
204
- prev_page = @page_stack.pop()
205
- @page = prev_page if prev_page
206
+ open_new_window: ->
207
+ this.execute 'window.open()'
206
208
  this.sendResponse(true)
207
209
 
210
+ close_window: (handle) ->
211
+ page = @getPageByHandle(handle)
212
+ if page
213
+ page.release()
214
+ this.sendResponse(true)
215
+ else
216
+ this.sendResponse(false)
217
+
208
218
  mouse_event: (page_id, id, name) ->
209
219
  # Get the node before changing state, in case there is an exception
210
220
  node = this.node(page_id, id)
211
221
 
212
222
  # If the event triggers onNavigationRequested, we will transition to the 'loading'
213
223
  # state and wait for onLoadFinished before sending a response.
214
- this.setState 'mouse_event'
224
+ @currentPage.state = 'mouse_event'
215
225
 
216
226
  @last_mouse_event = node.mouseEvent(name)
217
227
 
218
228
  setTimeout =>
219
- if @state != 'loading'
220
- this.setState 'default'
221
- this.sendResponse(@last_mouse_event)
229
+ # If the state is still the same then navigation event won't happen
230
+ if @currentPage.state == 'mouse_event'
231
+ @currentPage.state = 'default'
232
+ this.sendResponse(position: @last_mouse_event)
233
+ else
234
+ @currentPage.waitState 'default', =>
235
+ this.sendResponse(position: @last_mouse_event)
222
236
  , 5
223
237
 
224
238
  click: (page_id, id) ->
225
239
  this.mouse_event page_id, id, 'click'
226
240
 
241
+ right_click: (page_id, id) ->
242
+ this.mouse_event page_id, id, 'rightclick'
243
+
227
244
  double_click: (page_id, id) ->
228
245
  this.mouse_event page_id, id, 'doubleclick'
229
246
 
@@ -231,8 +248,8 @@ class Poltergeist.Browser
231
248
  this.mouse_event page_id, id, 'mousemove'
232
249
 
233
250
  click_coordinates: (x, y) ->
234
- @page.sendEvent('click', x, y)
235
- this.sendResponse({ click: { x: x, y: y } })
251
+ @currentPage.sendEvent('click', x, y)
252
+ this.sendResponse(click: { x: x, y: y })
236
253
 
237
254
  drag: (page_id, id, other_id) ->
238
255
  this.node(page_id, id).dragTo this.node(page_id, other_id)
@@ -250,84 +267,92 @@ class Poltergeist.Browser
250
267
  this.sendResponse(true)
251
268
 
252
269
  scroll_to: (left, top) ->
253
- @page.setScrollPosition(left: left, top: top)
270
+ @currentPage.setScrollPosition(left: left, top: top)
254
271
  this.sendResponse(true)
255
272
 
256
273
  send_keys: (page_id, id, keys) ->
274
+ target = this.node(page_id, id)
275
+
257
276
  # Programmatically generated focus doesn't work for `sendKeys`.
258
277
  # That's why we need something more realistic like user behavior.
259
- this.node(page_id, id).mouseEvent('click')
278
+ if !target.containsSelection()
279
+ target.mouseEvent('click')
280
+
260
281
  for sequence in keys
261
- key = if sequence.key? then @page.native.event.key[sequence.key] else sequence
262
- @page.sendEvent('keypress', key)
282
+ key = if sequence.key? then @currentPage.keyCode(sequence.key) else sequence
283
+ @currentPage.sendEvent('keypress', key)
263
284
  this.sendResponse(true)
264
285
 
265
286
  render_base64: (format, full, selector = null)->
266
287
  this.set_clip_rect(full, selector)
267
- encoded_image = @page.renderBase64(format)
288
+ encoded_image = @currentPage.renderBase64(format)
268
289
  this.sendResponse(encoded_image)
269
290
 
270
291
  render: (path, full, selector = null) ->
271
292
  dimensions = this.set_clip_rect(full, selector)
272
- @page.setScrollPosition(left: 0, top: 0)
273
- @page.render(path)
274
- @page.setScrollPosition(left: dimensions.left, top: dimensions.top)
293
+ @currentPage.setScrollPosition(left: 0, top: 0)
294
+ @currentPage.render(path)
295
+ @currentPage.setScrollPosition(left: dimensions.left, top: dimensions.top)
275
296
  this.sendResponse(true)
276
297
 
277
298
  set_clip_rect: (full, selector) ->
278
- dimensions = @page.validatedDimensions()
299
+ dimensions = @currentPage.validatedDimensions()
279
300
  [document, viewport] = [dimensions.document, dimensions.viewport]
280
301
 
281
302
  rect = if full
282
303
  left: 0, top: 0, width: document.width, height: document.height
283
304
  else
284
305
  if selector?
285
- @page.elementBounds(selector)
306
+ @currentPage.elementBounds(selector)
286
307
  else
287
308
  left: 0, top: 0, width: viewport.width, height: viewport.height
288
309
 
289
- @page.setClipRect(rect)
310
+ @currentPage.setClipRect(rect)
290
311
  dimensions
291
312
 
292
313
  set_paper_size: (size) ->
293
- @page.setPaperSize(size)
314
+ @currentPage.setPaperSize(size)
315
+ this.sendResponse(true)
316
+
317
+ set_zoom_factor: (zoom_factor) ->
318
+ @currentPage.setZoomFactor(zoom_factor)
294
319
  this.sendResponse(true)
295
320
 
296
321
  resize: (width, height) ->
297
- @page.setViewportSize(width: width, height: height)
322
+ @currentPage.setViewportSize(width: width, height: height)
298
323
  this.sendResponse(true)
299
324
 
300
325
  network_traffic: ->
301
- this.sendResponse(@page.networkTraffic())
326
+ this.sendResponse(@currentPage.networkTraffic())
302
327
 
303
328
  clear_network_traffic: ->
304
- @page.clearNetworkTraffic()
329
+ @currentPage.clearNetworkTraffic()
305
330
  this.sendResponse(true)
306
331
 
307
332
  get_headers: ->
308
- this.sendResponse(@page.getCustomHeaders())
333
+ this.sendResponse(@currentPage.getCustomHeaders())
309
334
 
310
335
  set_headers: (headers) ->
311
336
  # Workaround for https://code.google.com/p/phantomjs/issues/detail?id=745
312
- @page.setUserAgent(headers['User-Agent']) if headers['User-Agent']
313
- @page.setCustomHeaders(headers)
337
+ @currentPage.setUserAgent(headers['User-Agent']) if headers['User-Agent']
338
+ @currentPage.setCustomHeaders(headers)
314
339
  this.sendResponse(true)
315
340
 
316
341
  add_headers: (headers) ->
317
- allHeaders = @page.getCustomHeaders()
342
+ allHeaders = @currentPage.getCustomHeaders()
318
343
  for name, value of headers
319
344
  allHeaders[name] = value
320
345
  this.set_headers(allHeaders)
321
346
 
322
347
  add_header: (header, permanent) ->
323
- @page.addTempHeader(header) unless permanent
348
+ @currentPage.addTempHeader(header) unless permanent
324
349
  this.add_headers(header)
325
350
 
326
351
  response_headers: ->
327
- this.sendResponse(@page.responseHeaders())
352
+ this.sendResponse(@currentPage.responseHeaders())
328
353
 
329
354
  cookies: ->
330
- this.sendResponse(@page.cookies())
355
+ this.sendResponse(@currentPage.cookies())
331
356
 
332
357
  # We're using phantom.addCookie so that cookies can be set
333
358
  # before the first page load has taken place.
@@ -336,7 +361,11 @@ class Poltergeist.Browser
336
361
  this.sendResponse(true)
337
362
 
338
363
  remove_cookie: (name) ->
339
- @page.deleteCookie(name)
364
+ @currentPage.deleteCookie(name)
365
+ this.sendResponse(true)
366
+
367
+ clear_cookies: () ->
368
+ phantom.clearCookies()
340
369
  this.sendResponse(true)
341
370
 
342
371
  cookies_enabled: (flag) ->
@@ -344,7 +373,7 @@ class Poltergeist.Browser
344
373
  this.sendResponse(true)
345
374
 
346
375
  set_http_auth: (user, password) ->
347
- @page.setHttpAuth(user, password)
376
+ @currentPage.setHttpAuth(user, password)
348
377
  this.sendResponse(true)
349
378
 
350
379
  set_js_errors: (value) ->
@@ -366,9 +395,23 @@ class Poltergeist.Browser
366
395
  throw new Error('zomg')
367
396
 
368
397
  go_back: ->
369
- this.page.goBack() if this.page.canGoBack
370
- this.sendResponse(true)
398
+ if @currentPage.canGoBack
399
+ @currentPage.state = 'loading'
400
+ @currentPage.goBack()
401
+ @currentPage.waitState 'default', =>
402
+ this.sendResponse(true)
403
+ else
404
+ this.sendResponse(false)
371
405
 
372
406
  go_forward: ->
373
- this.page.goForward() if this.page.canGoForward
374
- this.sendResponse(true)
407
+ if @currentPage.canGoForward
408
+ @currentPage.state = 'loading'
409
+ @currentPage.goForward()
410
+ @currentPage.waitState 'default', =>
411
+ this.sendResponse(true)
412
+ else
413
+ this.sendResponse(false)
414
+
415
+ set_url_blacklist: ->
416
+ @currentPage.urlBlacklist = Array.prototype.slice.call(arguments)
417
+ @sendResponse(true)