line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojo::Phantom; |
2
|
|
|
|
|
|
|
|
3
|
5
|
|
|
5
|
|
391220
|
use Mojo::Base -base; |
|
5
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
42
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
6
|
|
|
|
|
|
|
$VERSION = eval $VERSION; |
7
|
|
|
|
|
|
|
|
8
|
5
|
|
|
5
|
|
3091
|
use Mojo::Phantom::Process; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
43
|
|
9
|
|
|
|
|
|
|
|
10
|
5
|
|
|
5
|
|
4221
|
use File::Temp (); |
|
5
|
|
|
|
|
46519
|
|
|
5
|
|
|
|
|
154
|
|
11
|
5
|
|
|
5
|
|
40
|
use Mojo::Util; |
|
5
|
|
|
|
|
6
|
|
|
5
|
|
|
|
|
258
|
|
12
|
5
|
|
|
5
|
|
26
|
use Mojo::JSON; |
|
5
|
|
|
|
|
14
|
|
|
5
|
|
|
|
|
154
|
|
13
|
5
|
|
|
5
|
|
25
|
use Mojo::Template; |
|
5
|
|
|
|
|
6
|
|
|
5
|
|
|
|
|
56
|
|
14
|
5
|
|
|
5
|
|
116
|
use Mojo::URL; |
|
5
|
|
|
|
|
6
|
|
|
5
|
|
|
|
|
36
|
|
15
|
5
|
|
|
5
|
|
2510
|
use JavaScript::Value::Escape; |
|
5
|
|
|
|
|
2609
|
|
|
5
|
|
|
|
|
317
|
|
16
|
|
|
|
|
|
|
|
17
|
5
|
|
|
5
|
|
27
|
use constant DEBUG => $ENV{MOJO_PHANTOM_DEBUG}; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
3411
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
has base => sub { Mojo::URL->new }; |
20
|
|
|
|
|
|
|
has bind => sub { {} }; |
21
|
|
|
|
|
|
|
has cookies => sub { [] }; |
22
|
|
|
|
|
|
|
has package => 'main'; |
23
|
|
|
|
|
|
|
has 'setup'; |
24
|
|
|
|
|
|
|
has sep => '--MOJO_PHANTOM_MSG--'; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
has template => <<'TEMPLATE'; |
27
|
|
|
|
|
|
|
% my ($self, $url, $js) = @_; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
// Setup perl function |
30
|
|
|
|
|
|
|
function perl() { |
31
|
|
|
|
|
|
|
var system = require('system'); |
32
|
|
|
|
|
|
|
var args = Array.prototype.slice.call(arguments); |
33
|
|
|
|
|
|
|
system.stdout.writeLine(JSON.stringify(args)); |
34
|
|
|
|
|
|
|
system.stdout.writeLine('<%== $self->sep %>'); |
35
|
|
|
|
|
|
|
system.stdout.flush(); |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
// Setup error handling |
39
|
|
|
|
|
|
|
var onError = function(msg, trace) { |
40
|
|
|
|
|
|
|
var msgStack = ['PHANTOM ERROR: ' + msg]; |
41
|
|
|
|
|
|
|
if (trace && trace.length) { |
42
|
|
|
|
|
|
|
msgStack.push('TRACE:'); |
43
|
|
|
|
|
|
|
trace.forEach(function(t) { |
44
|
|
|
|
|
|
|
msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : '')); |
45
|
|
|
|
|
|
|
}); |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
//phantom.exit isn't exitting immediately, so let Perl kill us |
49
|
|
|
|
|
|
|
perl('CORE::die', msgStack.join('\n')); |
50
|
|
|
|
|
|
|
//phantom.exit(1); |
51
|
|
|
|
|
|
|
}; |
52
|
|
|
|
|
|
|
phantom.onError = onError; |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
// Setup bound functions |
55
|
|
|
|
|
|
|
% my $bind = $self->bind || {}; |
56
|
|
|
|
|
|
|
% foreach my $func (keys %$bind) { |
57
|
|
|
|
|
|
|
% my $target = $bind->{$func} || $func; |
58
|
|
|
|
|
|
|
perl.<%= $func %> = function() { |
59
|
|
|
|
|
|
|
perl.apply(this, ['<%== $target %>'].concat(Array.prototype.slice.call(arguments))); |
60
|
|
|
|
|
|
|
}; |
61
|
|
|
|
|
|
|
% } |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
// Setup Cookies |
64
|
|
|
|
|
|
|
% foreach my $cookie (@{ $self->cookies }) { |
65
|
|
|
|
|
|
|
% my $name = $cookie->name; |
66
|
|
|
|
|
|
|
phantom.addCookie({ |
67
|
|
|
|
|
|
|
name: '<%== $name %>', |
68
|
|
|
|
|
|
|
value: '<%== $cookie->value %>', |
69
|
|
|
|
|
|
|
domain: '<%== $cookie->domain || $self->base->host %>', |
70
|
|
|
|
|
|
|
}) || perl.diag('Failed to import cookie <%== $name %>'); |
71
|
|
|
|
|
|
|
% } |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
// Requst page and inject user-provided javascript |
74
|
|
|
|
|
|
|
var page = require('webpage').create(); |
75
|
|
|
|
|
|
|
page.onError = onError; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
// Additional setup |
78
|
|
|
|
|
|
|
<%= $self->setup || '' %>; |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
page.open('<%== $url %>', function(status) { |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
<%= $js %>; |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
phantom.exit(); |
85
|
|
|
|
|
|
|
}); |
86
|
|
|
|
|
|
|
TEMPLATE |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
sub execute_file { |
89
|
5
|
|
|
5
|
1
|
17
|
my ($self, $file, $cb) = @_; |
90
|
|
|
|
|
|
|
# note that $file might be an object that needs to have a strong reference |
91
|
|
|
|
|
|
|
|
92
|
5
|
|
|
|
|
79
|
my $proc = Mojo::Phantom::Process->new; |
93
|
|
|
|
|
|
|
|
94
|
5
|
|
|
|
|
54
|
my $sep = $self->sep; |
95
|
5
|
|
|
|
|
47
|
my $package = $self->package; |
96
|
5
|
|
|
|
|
25
|
my $buffer = ''; |
97
|
5
|
|
|
|
|
8
|
my $error; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
$proc->on(read => sub { |
100
|
0
|
|
|
0
|
|
0
|
my ($proc, $bytes) = @_; |
101
|
0
|
|
|
|
|
0
|
warn "\nPerl <<<< Phantom: $bytes\n" if DEBUG; |
102
|
0
|
|
|
|
|
0
|
$buffer .= $bytes; |
103
|
0
|
|
|
|
|
0
|
while ($buffer =~ s/^(.*)\n$sep\n//) { |
104
|
0
|
|
|
|
|
0
|
my ($function, @args) = @{ Mojo::JSON::decode_json $1 }; |
|
0
|
|
|
|
|
0
|
|
105
|
0
|
|
|
|
|
0
|
eval "package $package; no strict 'refs'; &{\$function}(\@args)"; |
106
|
0
|
0
|
|
|
|
0
|
if ($@) { $error = $@; return $proc->kill } |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
107
|
|
|
|
|
|
|
} |
108
|
5
|
|
|
|
|
71
|
}); |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
$proc->on(close => sub { |
111
|
0
|
|
|
0
|
|
0
|
my ($proc) = @_; |
112
|
0
|
|
|
|
|
0
|
undef $file; |
113
|
0
|
0
|
0
|
|
|
0
|
$self->$cb($error || $proc->error, $proc->exit_status) if $cb; |
114
|
5
|
|
|
|
|
86
|
}); |
115
|
|
|
|
|
|
|
|
116
|
5
|
|
|
|
|
45
|
return $proc->start($file); |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
sub execute_url { |
120
|
5
|
|
|
5
|
1
|
3528
|
my ($self, $url, $js, $cb) = @_; |
121
|
|
|
|
|
|
|
|
122
|
5
|
|
|
|
|
56
|
$js = Mojo::Template |
123
|
|
|
|
|
|
|
->new(escape => \&javascript_value_escape) |
124
|
|
|
|
|
|
|
->render($self->template, $self, $url, $js); |
125
|
|
|
|
|
|
|
|
126
|
5
|
50
|
|
|
|
21872
|
die $js if ref $js; # Mojo::Template returns Mojo::Exception objects on failure |
127
|
|
|
|
|
|
|
|
128
|
5
|
|
|
|
|
11
|
warn "\nPerl >>>> Phantom:\n$js\n" if DEBUG; |
129
|
5
|
|
|
|
|
69
|
my $tmp = File::Temp->new(SUFFIX => '.js'); |
130
|
5
|
|
|
|
|
3609
|
Mojo::Util::spurt($js => "$tmp"); |
131
|
|
|
|
|
|
|
|
132
|
5
|
|
|
|
|
287287
|
return $self->execute_file($tmp, $cb); |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
1; |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=head1 NAME |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
Mojo::Phantom - Interact with your client side code via PhantomJS |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=head1 SYNOPSIS |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
use Mojolicious::Lite; |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
use Test::More; |
147
|
|
|
|
|
|
|
use Test::Mojo::WithRoles qw/Phantom/; |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
any '/' => 'index'; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
my $t = Test::Mojo::WithRoles->new; |
152
|
|
|
|
|
|
|
$t->phantom_ok('/' => <<'JS'); |
153
|
|
|
|
|
|
|
var text = page.evaluate(function(){ |
154
|
|
|
|
|
|
|
return document.getElementById('name').innerHTML; |
155
|
|
|
|
|
|
|
}); |
156
|
|
|
|
|
|
|
perl.is(text, 'Bender', 'name changed after loading'); |
157
|
|
|
|
|
|
|
JS |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
done_testing; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
__DATA__ |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
@@ index.html.ep |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
Leela |
170
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=head1 DESCRIPTION |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
L is the transport backbone for L. |
179
|
|
|
|
|
|
|
Currently it is used to evaluate javascript tests using PhantomJS, though more is possible. |
180
|
|
|
|
|
|
|
Please note that this class is not yet as stable as the public api for the test role. |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
L inherits the attributes from L and implements the following new ones. |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=head2 base |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
An instance of L used to make relative urls absolute. |
189
|
|
|
|
|
|
|
This is used, for example, in setting cookies |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=head2 bind |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
A hash reference used to bind JS methods and Perl functions. |
194
|
|
|
|
|
|
|
Keys are methods to be created in the C object in javascript. |
195
|
|
|
|
|
|
|
Values are functions for those methods to invoke when the message is received by the Perl process. |
196
|
|
|
|
|
|
|
The functions may be relative to the L or are absolute if they contain C<::>. |
197
|
|
|
|
|
|
|
If the function is false, then the key is used as the function name. |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=head2 cookies |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
An array reference containing L objects. |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=head2 package |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
The package for binding relative function names. |
206
|
|
|
|
|
|
|
Defaults to C |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head2 setup |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
An additional string of javascript which is executed after the page object is created but before the url is opened. |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=head2 sep |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
A string used to separate messages from the JS side. |
215
|
|
|
|
|
|
|
Defaults to C<--MOJO_PHANTOM_MSG-->. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=head2 template |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
A string which is used to build a L object. |
220
|
|
|
|
|
|
|
It takes as its arguments the instance, a target url, and a string of javascript to be evaluated. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
The default handles much of what this module does, you should be very sure of why you need to change this before doing so. |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=head1 METHODS |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
L inherits all methods from L and implements the following new ones. |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=head2 execute_file |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
A lower level function which handles the message passing etc. |
231
|
|
|
|
|
|
|
You probably want L. |
232
|
|
|
|
|
|
|
Takes a file path to start C with and a callback. |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
Returns a pre-initialized instance of L. |
235
|
|
|
|
|
|
|
The end user likely does not need to worry about this object, though it might be useful if the process needs to be killed or the stream timeout needs to be lengthened. |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=head2 execute_url |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
Builds the template for PhantomJS to execute and starts it. |
240
|
|
|
|
|
|
|
Takes a target url, a string of javascript to be executed in the context that the template provides and a callback. |
241
|
|
|
|
|
|
|
By default this is the page context. |
242
|
|
|
|
|
|
|
The return value is the same as L. |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=head1 SOURCE REPOSITORY |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
L |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
=head1 AUTHOR |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
Joel Berger, Ejoel.a.berger@gmail.comE |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
Copyright (C) 2015 by Joel Berger |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
257
|
|
|
|
|
|
|
it under the same terms as Perl itself. |