poltergeist 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,6 +5,8 @@ class Poltergeist.Browser
5
5
  @state = 'default'
6
6
  @page_stack = []
7
7
  @page_id = 0
8
+ @js_errors = true
9
+ @_debug = false
8
10
 
9
11
  this.resetPage()
10
12
 
@@ -17,18 +19,18 @@ class Poltergeist.Browser
17
19
  @page.setViewportSize(width: @width, height: @height)
18
20
 
19
21
  @page.onLoadStarted = =>
20
- @state = 'loading' if @state == 'clicked'
22
+ this.setState 'loading' if @state == 'clicked'
21
23
 
22
24
  @page.onNavigationRequested = (url, navigation) =>
23
- @state = 'loading' if @state == 'clicked' && navigation == 'FormSubmitted'
25
+ this.setState 'loading' if @state == 'clicked' && navigation == 'FormSubmitted'
24
26
 
25
27
  @page.onLoadFinished = (status) =>
26
28
  if @state == 'loading'
27
29
  this.sendResponse(status: status, click: @last_click)
28
- @state = 'default'
30
+ this.setState 'default'
29
31
  else if @state == 'awaiting_frame_load'
30
32
  this.sendResponse(true)
31
- @state = 'default'
33
+ this.setState 'default'
32
34
 
33
35
  @page.onInitialized = =>
34
36
  @page_id += 1
@@ -36,23 +38,36 @@ class Poltergeist.Browser
36
38
  @page.onPageCreated = (sub_page) =>
37
39
  if @state == 'awaiting_sub_page'
38
40
  name = @page_name
39
- @state = 'default'
40
41
  @page_name = null
41
42
 
43
+ this.setState 'default'
44
+
42
45
  # At this point subpage isn't fully initialized, so we can't check
43
46
  # its name. Instead, we just schedule another attempt to push the
44
47
  # window.
45
48
  setTimeout((=> this.push_window(name)), 0)
46
49
 
50
+ debug: (message) ->
51
+ if @_debug
52
+ console.log "poltergeist [#{new Date().getTime()}] #{message}"
53
+
54
+ setState: (state) ->
55
+ this.debug "state #{@state} -> #{state}"
56
+ @state = state
57
+
47
58
  sendResponse: (response) ->
48
59
  errors = @page.errors()
60
+ @page.clearErrors()
49
61
 
50
- if errors.length > 0
51
- @page.clearErrors()
62
+ if errors.length > 0 && @js_errors
52
63
  @owner.sendError(new Poltergeist.JavascriptError(errors))
53
64
  else
54
65
  @owner.sendResponse(response)
55
66
 
67
+ add_extension: (extension) ->
68
+ @page.injectExtension extension
69
+ this.sendResponse 'success'
70
+
56
71
  node: (page_id, id) ->
57
72
  if page_id == @page_id
58
73
  @page.get(id)
@@ -60,14 +75,15 @@ class Poltergeist.Browser
60
75
  throw new Poltergeist.ObsoleteNode
61
76
 
62
77
  visit: (url) ->
63
- @state = 'loading'
78
+ this.setState 'loading'
79
+
64
80
  prev_url = @page.currentUrl()
65
81
 
66
82
  @page.open(url)
67
83
 
68
84
  if /#/.test(url) && prev_url.split('#')[0] == url.split('#')[0]
69
85
  # hashchange occurred, so there will be no onLoadFinished
70
- @state = 'default'
86
+ this.setState 'default'
71
87
  this.sendResponse 'success'
72
88
 
73
89
  current_url: ->
@@ -105,11 +121,11 @@ class Poltergeist.Browser
105
121
  # so we have to add an attribute to the element to identify it, then remove it
106
122
  # afterwards.
107
123
  select_file: (page_id, id, value) ->
108
- node = this.node(page_id, id)
124
+ node = this.node(page_id, id)
109
125
 
110
- node.setAttribute('_poltergeist_selected', '')
126
+ @page.beforeUpload(node.id)
111
127
  @page.uploadFile('[_poltergeist_selected]', value)
112
- node.removeAttribute('_poltergeist_selected')
128
+ @page.afterUpload(node.id)
113
129
 
114
130
  this.sendResponse(true)
115
131
 
@@ -132,7 +148,7 @@ class Poltergeist.Browser
132
148
  push_frame: (name) ->
133
149
  if @page.pushFrame(name)
134
150
  if @page.currentUrl() == 'about:blank'
135
- @state = 'awaiting_frame_load'
151
+ this.setState 'awaiting_frame_load'
136
152
  else
137
153
  this.sendResponse(true)
138
154
  else
@@ -158,29 +174,36 @@ class Poltergeist.Browser
158
174
  this.sendResponse(true)
159
175
  else
160
176
  @page_name = name
161
- @state = 'awaiting_sub_page'
177
+ this.setState 'awaiting_sub_page'
162
178
 
163
179
  pop_window: ->
164
180
  prev_page = @page_stack.pop()
165
181
  @page = prev_page if prev_page
166
182
  this.sendResponse(true)
167
183
 
168
- click: (page_id, id) ->
184
+ click: (page_id, id, event = 'click') ->
169
185
  # Get the node before changing state, in case there is an exception
170
186
  node = this.node(page_id, id)
171
187
 
172
188
  # If the click event triggers onNavigationRequested, we will transition to the 'loading'
173
189
  # state and wait for onLoadFinished before sending a response.
174
- @state = 'clicked'
190
+ this.setState 'clicked'
175
191
 
176
- @last_click = node.click()
192
+ @last_click = node.click(event)
177
193
 
178
194
  setTimeout =>
179
195
  if @state != 'loading'
180
- @state = 'default'
196
+ this.setState 'default'
181
197
  this.sendResponse(@last_click)
182
198
  , 5
183
199
 
200
+ double_click: (page_id, id) ->
201
+ this.click page_id, id, 'doubleclick'
202
+
203
+ click_coordinates: (x, y) ->
204
+ @page.sendEvent('click', x, y)
205
+ this.sendResponse({ click: { x: x, y: y } })
206
+
184
207
  drag: (page_id, id, other_id) ->
185
208
  this.node(page_id, id).dragTo this.node(page_id, other_id)
186
209
  this.sendResponse(true)
@@ -241,6 +264,14 @@ class Poltergeist.Browser
241
264
  @page.deleteCookie(name)
242
265
  this.sendResponse(true)
243
266
 
267
+ set_js_errors: (value) ->
268
+ @js_errors = value
269
+ this.sendResponse(true)
270
+
271
+ set_debug: (value) ->
272
+ @_debug = value
273
+ this.sendResponse(true)
274
+
244
275
  exit: ->
245
276
  phantom.exit()
246
277
 
@@ -83,6 +83,14 @@ PoltergeistAgent = (function() {
83
83
  return node[name].apply(node, args);
84
84
  };
85
85
 
86
+ PoltergeistAgent.prototype.beforeUpload = function(id) {
87
+ return this.get(id).setAttribute('_poltergeist_selected', '');
88
+ };
89
+
90
+ PoltergeistAgent.prototype.afterUpload = function(id) {
91
+ return this.get(id).removeAttribute('_poltergeist_selected');
92
+ };
93
+
86
94
  return PoltergeistAgent;
87
95
 
88
96
  })();
@@ -223,20 +231,38 @@ PoltergeistAgent.Node = (function() {
223
231
  }
224
232
  };
225
233
 
234
+ Node.prototype.frameOffset = function() {
235
+ var offset, rect, win;
236
+ win = window;
237
+ offset = {
238
+ top: 0,
239
+ left: 0
240
+ };
241
+ while (win.frameElement) {
242
+ rect = window.frameElement.getClientRects()[0];
243
+ win = win.parent;
244
+ offset.top += rect.top;
245
+ offset.left += rect.left;
246
+ }
247
+ return offset;
248
+ };
249
+
226
250
  Node.prototype.position = function() {
227
- var rect;
251
+ var frameOffset, pos, rect;
228
252
  rect = this.element.getClientRects()[0];
229
253
  if (!rect) {
230
254
  throw new PoltergeistAgent.ObsoleteNode;
231
255
  }
232
- return {
233
- top: rect.top,
234
- right: rect.right,
235
- left: rect.left,
236
- bottom: rect.bottom,
256
+ frameOffset = this.frameOffset();
257
+ pos = {
258
+ top: rect.top + frameOffset.top,
259
+ right: rect.right + frameOffset.left,
260
+ left: rect.left + frameOffset.left,
261
+ bottom: rect.bottom + frameOffset.top,
237
262
  width: rect.width,
238
263
  height: rect.height
239
264
  };
265
+ return pos;
240
266
  };
241
267
 
242
268
  Node.prototype.trigger = function(name) {
@@ -263,7 +289,10 @@ PoltergeistAgent.Node = (function() {
263
289
  };
264
290
 
265
291
  Node.prototype.clickTest = function(x, y) {
266
- var el, origEl;
292
+ var el, frameOffset, origEl;
293
+ frameOffset = this.frameOffset();
294
+ x -= frameOffset.left;
295
+ y -= frameOffset.top;
267
296
  el = origEl = document.elementFromPoint(x, y);
268
297
  while (el) {
269
298
  if (el === this.element) {
@@ -8,6 +8,8 @@ Poltergeist.Browser = (function() {
8
8
  this.state = 'default';
9
9
  this.page_stack = [];
10
10
  this.page_id = 0;
11
+ this.js_errors = true;
12
+ this._debug = false;
11
13
  this.resetPage();
12
14
  }
13
15
 
@@ -24,12 +26,12 @@ Poltergeist.Browser = (function() {
24
26
  });
25
27
  this.page.onLoadStarted = function() {
26
28
  if (_this.state === 'clicked') {
27
- return _this.state = 'loading';
29
+ return _this.setState('loading');
28
30
  }
29
31
  };
30
32
  this.page.onNavigationRequested = function(url, navigation) {
31
33
  if (_this.state === 'clicked' && navigation === 'FormSubmitted') {
32
- return _this.state = 'loading';
34
+ return _this.setState('loading');
33
35
  }
34
36
  };
35
37
  this.page.onLoadFinished = function(status) {
@@ -38,10 +40,10 @@ Poltergeist.Browser = (function() {
38
40
  status: status,
39
41
  click: _this.last_click
40
42
  });
41
- return _this.state = 'default';
43
+ return _this.setState('default');
42
44
  } else if (_this.state === 'awaiting_frame_load') {
43
45
  _this.sendResponse(true);
44
- return _this.state = 'default';
46
+ return _this.setState('default');
45
47
  }
46
48
  };
47
49
  this.page.onInitialized = function() {
@@ -51,8 +53,8 @@ Poltergeist.Browser = (function() {
51
53
  var name;
52
54
  if (_this.state === 'awaiting_sub_page') {
53
55
  name = _this.page_name;
54
- _this.state = 'default';
55
56
  _this.page_name = null;
57
+ _this.setState('default');
56
58
  return setTimeout((function() {
57
59
  return _this.push_window(name);
58
60
  }), 0);
@@ -60,17 +62,33 @@ Poltergeist.Browser = (function() {
60
62
  };
61
63
  };
62
64
 
65
+ Browser.prototype.debug = function(message) {
66
+ if (this._debug) {
67
+ return console.log("poltergeist [" + (new Date().getTime()) + "] " + message);
68
+ }
69
+ };
70
+
71
+ Browser.prototype.setState = function(state) {
72
+ this.debug("state " + this.state + " -> " + state);
73
+ return this.state = state;
74
+ };
75
+
63
76
  Browser.prototype.sendResponse = function(response) {
64
77
  var errors;
65
78
  errors = this.page.errors();
66
- if (errors.length > 0) {
67
- this.page.clearErrors();
79
+ this.page.clearErrors();
80
+ if (errors.length > 0 && this.js_errors) {
68
81
  return this.owner.sendError(new Poltergeist.JavascriptError(errors));
69
82
  } else {
70
83
  return this.owner.sendResponse(response);
71
84
  }
72
85
  };
73
86
 
87
+ Browser.prototype.add_extension = function(extension) {
88
+ this.page.injectExtension(extension);
89
+ return this.sendResponse('success');
90
+ };
91
+
74
92
  Browser.prototype.node = function(page_id, id) {
75
93
  if (page_id === this.page_id) {
76
94
  return this.page.get(id);
@@ -81,11 +99,11 @@ Poltergeist.Browser = (function() {
81
99
 
82
100
  Browser.prototype.visit = function(url) {
83
101
  var prev_url;
84
- this.state = 'loading';
102
+ this.setState('loading');
85
103
  prev_url = this.page.currentUrl();
86
104
  this.page.open(url);
87
105
  if (/#/.test(url) && prev_url.split('#')[0] === url.split('#')[0]) {
88
- this.state = 'default';
106
+ this.setState('default');
89
107
  return this.sendResponse('success');
90
108
  }
91
109
  };
@@ -137,9 +155,9 @@ Poltergeist.Browser = (function() {
137
155
  Browser.prototype.select_file = function(page_id, id, value) {
138
156
  var node;
139
157
  node = this.node(page_id, id);
140
- node.setAttribute('_poltergeist_selected', '');
158
+ this.page.beforeUpload(node.id);
141
159
  this.page.uploadFile('[_poltergeist_selected]', value);
142
- node.removeAttribute('_poltergeist_selected');
160
+ this.page.afterUpload(node.id);
143
161
  return this.sendResponse(true);
144
162
  };
145
163
 
@@ -168,7 +186,7 @@ Poltergeist.Browser = (function() {
168
186
  var _this = this;
169
187
  if (this.page.pushFrame(name)) {
170
188
  if (this.page.currentUrl() === 'about:blank') {
171
- return this.state = 'awaiting_frame_load';
189
+ return this.setState('awaiting_frame_load');
172
190
  } else {
173
191
  return this.sendResponse(true);
174
192
  }
@@ -201,7 +219,7 @@ Poltergeist.Browser = (function() {
201
219
  }
202
220
  } else {
203
221
  this.page_name = name;
204
- return this.state = 'awaiting_sub_page';
222
+ return this.setState('awaiting_sub_page');
205
223
  }
206
224
  };
207
225
 
@@ -214,20 +232,37 @@ Poltergeist.Browser = (function() {
214
232
  return this.sendResponse(true);
215
233
  };
216
234
 
217
- Browser.prototype.click = function(page_id, id) {
235
+ Browser.prototype.click = function(page_id, id, event) {
218
236
  var node,
219
237
  _this = this;
238
+ if (event == null) {
239
+ event = 'click';
240
+ }
220
241
  node = this.node(page_id, id);
221
- this.state = 'clicked';
222
- this.last_click = node.click();
242
+ this.setState('clicked');
243
+ this.last_click = node.click(event);
223
244
  return setTimeout(function() {
224
245
  if (_this.state !== 'loading') {
225
- _this.state = 'default';
246
+ _this.setState('default');
226
247
  return _this.sendResponse(_this.last_click);
227
248
  }
228
249
  }, 5);
229
250
  };
230
251
 
252
+ Browser.prototype.double_click = function(page_id, id) {
253
+ return this.click(page_id, id, 'doubleclick');
254
+ };
255
+
256
+ Browser.prototype.click_coordinates = function(x, y) {
257
+ this.page.sendEvent('click', x, y);
258
+ return this.sendResponse({
259
+ click: {
260
+ x: x,
261
+ y: y
262
+ }
263
+ });
264
+ };
265
+
231
266
  Browser.prototype.drag = function(page_id, id, other_id) {
232
267
  this.node(page_id, id).dragTo(this.node(page_id, other_id));
233
268
  return this.sendResponse(true);
@@ -318,6 +353,16 @@ Poltergeist.Browser = (function() {
318
353
  return this.sendResponse(true);
319
354
  };
320
355
 
356
+ Browser.prototype.set_js_errors = function(value) {
357
+ this.js_errors = value;
358
+ return this.sendResponse(true);
359
+ };
360
+
361
+ Browser.prototype.set_debug = function(value) {
362
+ this._debug = value;
363
+ return this.sendResponse(true);
364
+ };
365
+
321
366
  Browser.prototype.exit = function() {
322
367
  return phantom.exit();
323
368
  };
@@ -41,13 +41,16 @@ Poltergeist.Node = (function() {
41
41
  };
42
42
  };
43
43
 
44
- Node.prototype.click = function() {
44
+ Node.prototype.click = function(event) {
45
45
  var pos, test;
46
+ if (event == null) {
47
+ event = 'click';
48
+ }
46
49
  this.scrollIntoView();
47
50
  pos = this.clickPosition();
48
51
  test = this.clickTest(pos.x, pos.y);
49
52
  if (test.status === 'success') {
50
- this.page.mouseEvent('click', pos.x, pos.y);
53
+ this.page.mouseEvent(event, pos.x, pos.y);
51
54
  return pos;
52
55
  } else {
53
56
  throw new Poltergeist.ClickFailed(test.selector, pos);
@@ -8,7 +8,9 @@ Poltergeist.WebPage = (function() {
8
8
 
9
9
  WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render'];
10
10
 
11
- WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize'];
11
+ WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload'];
12
+
13
+ WebPage.EXTENSIONS = [];
12
14
 
13
15
  function WebPage(_native) {
14
16
  var callback, _i, _len, _ref;
@@ -24,7 +26,6 @@ Poltergeist.WebPage = (function() {
24
26
  callback = _ref[_i];
25
27
  this.bindCallback(callback);
26
28
  }
27
- this.injectAgent();
28
29
  }
29
30
 
30
31
  _ref = WebPage.COMMANDS;
@@ -61,13 +62,26 @@ Poltergeist.WebPage = (function() {
61
62
  };
62
63
 
63
64
  WebPage.prototype.injectAgent = function() {
65
+ var extension, _k, _len2, _ref2, _results;
64
66
  if (this["native"].evaluate(function() {
65
67
  return typeof __poltergeist;
66
68
  }) === "undefined") {
67
- return this["native"].injectJs("" + phantom.libraryPath + "/agent.js");
69
+ this["native"].injectJs("" + phantom.libraryPath + "/agent.js");
70
+ _ref2 = WebPage.EXTENSIONS;
71
+ _results = [];
72
+ for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
73
+ extension = _ref2[_k];
74
+ _results.push(this["native"].injectJs(extension));
75
+ }
76
+ return _results;
68
77
  }
69
78
  };
70
79
 
80
+ WebPage.prototype.injectExtension = function(file) {
81
+ WebPage.EXTENSIONS.push(file);
82
+ return this["native"].injectJs(file);
83
+ };
84
+
71
85
  WebPage.prototype.onConsoleMessageNative = function(message) {
72
86
  if (message === '__DOMContentLoaded') {
73
87
  this._source = this["native"].content;
@@ -204,11 +218,12 @@ Poltergeist.WebPage = (function() {
204
218
  };
205
219
 
206
220
  WebPage.prototype.pushFrame = function(name) {
207
- var res;
208
- this.frames.push(name);
209
- res = this["native"].switchToFrame(name);
210
- this.injectAgent();
211
- return res;
221
+ if (this["native"].switchToFrame(name)) {
222
+ this.frames.push(name);
223
+ return true;
224
+ } else {
225
+ return false;
226
+ }
212
227
  };
213
228
 
214
229
  WebPage.prototype.popFrame = function() {
@@ -277,6 +292,7 @@ Poltergeist.WebPage = (function() {
277
292
  WebPage.prototype.evaluate = function() {
278
293
  var args, fn;
279
294
  fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
295
+ this.injectAgent();
280
296
  return JSON.parse(this["native"].evaluate("function() { return PoltergeistAgent.stringify(" + (this.stringifyCall(fn, args)) + ") }"));
281
297
  };
282
298
 
@@ -317,7 +333,7 @@ Poltergeist.WebPage = (function() {
317
333
  if (result.error.message === 'PoltergeistAgent.ObsoleteNode') {
318
334
  throw new Poltergeist.ObsoleteNode;
319
335
  } else {
320
- throw new Poltergeist.JavascriptError([result.error]);
336
+ throw new Poltergeist.BrowserError(result.error.message, result.error.stack);
321
337
  }
322
338
  } else {
323
339
  return result.value;