poltergeist 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)