poltergeist 1.0.3 → 1.1.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.
@@ -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;