File Coverage

blib/lib/WWW/Scripter/Plugin/JavaScript.pm
Criterion Covered Total %
statement 102 105 97.1
branch 20 24 83.3
condition 9 17 52.9
subroutine 21 22 95.4
pod 8 9 88.8
total 160 177 90.4


line stmt bran cond sub pod time code
1             package WWW::Scripter::Plugin::JavaScript;
2              
3 5     5   227797 use strict; # :-(
  5         12  
  5         185  
4 5     5   27 use warnings; # :-(
  5         9  
  5         173  
5              
6 5     5   904 use Encode 'decode_utf8';
  5         11454  
  5         395  
7 5     5   997 use LWP'UserAgent 5.815;
  5         58299  
  5         161  
8 5     5   36 use Scalar::Util qw'weaken';
  5         11  
  5         340  
9 5     5   34 use URI::Escape 'uri_unescape';
  5         9  
  5         246  
10 5     5   5238 use Hash::Util::FieldHash::Compat 'fieldhash';
  5         5036  
  5         41  
11 5     5   1719 use WWW::Scripter 0.022; # screen
  5         311635  
  5         1043  
12              
13             our $VERSION = '0.008';
14              
15             # Attribute constants (array indices)
16             sub mech() { 0 }
17             sub jsbe() { 1 } # JavaScript back-end (field hash of objects, keyed
18             sub benm() { 2 } # Back-end name # by document)
19             sub init_cb() { 3 } # callback routine that's called whenever a new js
20             # environment is created
21             sub alert() { 4 }
22             sub confirm() { 5 }
23             sub prompt() { 6 }
24             sub cb() { 7 } # class bindings
25             sub tmout() { 8 } # timeouts
26             sub f() { 9 } # functions
27             sub g() { 10 } # guard objects for back ends, to destroy
28             # them forcibly
29              
30 5     5   30 {no warnings; no strict;
  5     5   8  
  5         180  
  5         23  
  5         8  
  5         7749  
31             undef *$_ for qw/mech jsbe benm init_cb g cb
32             f alert confirm prompt tmout/} # These are PRIVATE constants!
33              
34             sub init {
35              
36 10     10 1 51413 my ($package, $mech) = @_;
37              
38 10         51 my $self = bless [$mech], $package;
39 10         70 weaken $self->[mech];
40              
41 10         58 $mech->script_handler( default => $self );
42 10         200 $mech->script_handler(
43             qr/(?:^|\/)(?:x-)?(?:ecma|j(?:ava)?)script[\d.]*\z/i => $self
44             );
45              
46             $mech->set_my_handler(request_preprepare => sub {
47 2     2   15012 my($request,$mech) = @_;
48 2         10 $self->eval(
49 2         7 $mech, decode_utf8 uri_unescape opaque {uri $request}
50             );
51 2 50       222 $@ and $mech->warn($@);
52 2         30 WWW'Scripter'abort;
53 10         264 }, m_scheme => 'javascript');
54              
55             # stop closures from preventing destruction
56 10         937 weaken $mech;
57 10         18 my $life_raft = $self;
58 10         30 weaken $self;
59              
60 10         42 $self;
61             }
62              
63             sub options {
64 6     6 0 38 my $self = shift;
65 6         24 my %opts = @_;
66              
67 6         14 my $w;
68 6         23 for(keys %opts) {
69 6 100       30 if($_ eq 'engine') {
    50          
70 4 50 33     41 if($self->[jsbe] &&
71             $self->[benm] ne $opts{$_}
72             ) {
73 0         0 $self->[mech]->die(
74             "Can't set JavaScript engine to " .
75             "'$opts{$_}' since $self->[benm] is " .
76             "already loaded.");;
77             }
78 4         24 $self->[benm] = $opts{$_};;
79             }
80             elsif($_ eq 'init') {
81 2         13 $self->[init_cb] = $opts{$_};
82             }
83             else {
84 0         0 $self->[mech]->die(
85             "JavaScript plugin: Unrecognized option '$_'"
86             );
87             }
88             }
89             }
90              
91             sub eval {
92 70     70 1 491207 my($plugin,$mech,$code,$url,$line,$inline) = @_;
93              
94 70 100       435 if(
95             $code =~ s/^(\s*)\s*\z//;
101            
102 70         289 my $be = $plugin->back_end($mech);
103              
104 70         670 $be->eval($code, $url, $line);
105             }
106              
107             sub event2sub {
108 6     6 1 107381 my($self,$mech,$elem,undef,$code,$url,$line) = @_;
109              
110 6         35 $self->
111             back_end($mech)->event2sub($code,$elem,$url,$line);
112             }
113              
114             # We have to associate each JS environment with a response object. While
115             # writing this logic, I initially tried to use the document, but not all
116             # URLs have documents (e.g., plain text files).
117             sub back_end {
118 111     111 1 986 my $self = shift;
119 111 50       471 ref $_[0] or require Carp, Carp'cluck();
120 111         977 my $res = (my $w = shift)->res;
121 111 100 66     1665 return $self->[jsbe]{$res}
122             if ($self->[jsbe] ||= &fieldhash({}))->{$res};
123            
124 26 100       384 if(!$self->[benm]) {
125             # When wspjssm is stable enough, these lines can be uncommented:
126             # # try this one first, since it's faster:
127             # eval{require WWW::Scripter::Plugin::JavaScript::SpiderMonkey};
128             # if($@) {
129             require
130 6         1017 WWW::Scripter::Plugin::JavaScript::JE;
131 6         23 $self->[benm] = 'JE'
132             # }
133             # else { $self->[benm] = 'SpiderMonkey' };
134             }
135             else {
136 20         2980 require "WWW/Scripter/Plugin/JavaScript/" .
137             "$$self[benm].pm";
138             }
139              
140 26   66     311 ($self->[g] ||= &fieldhash({}))->{$res}
141             = new WWW'Scripter'Plugin'JavaScript'Guard
142             my $back_end = $self->[jsbe]{$res}
143             = "WWW::Scripter::Plugin::JavaScript::$$self[benm]" -> new( $w );
144 26         523 require HTML::DOM::Interface;
145 26         156 require CSS::DOM::Interface;
146 26         87 for ($back_end) {
147 26         227 for my $class_info( $self->[mech]->class_info ) {
148 104         998 $_->bind_classes($class_info) ;
149             }
150 26 100       124 for my $__(@{$self->[cb]||[]}){
  26         12425  
151 17         89 $_->bind_classes($__)
152             }
153 26 100       75 for my $__(@{$self->[f]||[]}){
  26         195  
154 51         861 $_->new_function(@$__)
155             }
156             } # for $back_end;
157 26   100     293 { ($self->[init_cb]||next)->($w); }
  26         125  
158 26         1916 weaken $self; # closures
159 26         163 return $back_end;
160             }
161              
162             sub bind_classes {
163 5     5 1 37626 my $plugin = shift;
164 5         19 push @{$plugin->[cb]}, $_[0];
  5         21  
165 5 100       63 if($plugin->[jsbe]) {
166 3         47 $_ && $_->bind_classes($_[0])
167 3   33     7 for values %{ $plugin->[jsbe] };
168             }
169             }
170              
171 4     4 1 2722 sub set { shift->back_end( shift )->set(@_) }
172              
173             sub new_function {
174 8     8 1 176142 my $plugin = shift;
175 8         25 push @{$plugin->[f]}, \@_;
  8         36  
176 8 100       57 if($plugin->[jsbe]) {
177 2         46 $_ && $_->new_function(@_)
178 2   33     7 for values %{ $plugin->[jsbe] };
179             }
180             }
181              
182              
183             # ~~~ This is experimental. The purposed for this is that code that relies
184             # on a particular version of a JS back end can check to see which back
185             # end is being used before doing Foo->VERSION($bar). The problem with
186             # it is that it returns nothing unless the JS environment has already
187             # been loaded. If we have it start the JS engine, we may load it and
188             # then not use it.
189 0     0 1 0 sub engine { shift->[benm] }
190              
191              
192             package WWW::Scripter::Plugin::JavaScript::Guard;
193              
194 26     26   305 sub new { bless \(my $object = pop) }
195 13     13   192982 DESTROY { eval { ${$_[0]}->destroy } }
  13         352  
  13         129  
196              
197              
198             # ------------------ DOCS --------------------#
199              
200             1;
201              
202              
203             =head1 NAME
204              
205             WWW::Scripter::Plugin::JavaScript - JavaScript plugin for WWW::Scripter
206              
207             =head1 VERSION
208              
209             Version 0.008 (alpha)
210              
211             =head1 SYNOPSIS
212              
213             use WWW::Scripter;
214             $w = new WWW::Scripter;
215            
216             $w->use_plugin('JavaScript');
217             $w->get('http://www.cpan.org/');
218             $w->get('javascript:alert("Hello!")'); # prints Hello!
219            
220             $w->use_plugin(JavaScript =>
221             engine => 'SpiderMonkey',
222             init => \&init, # initialisation function
223             ); # for the JS environment
224            
225             =head1 DESCRIPTION
226              
227             This module is a plugin for L that provides JavaScript
228             capabilities (who would have guessed?).
229              
230             To load the plugin, just use L's C method:
231              
232             $w = new WWW::Scripter;
233             $w->use_plugin('JavaScript');
234              
235             You can pass options to the plugin via the C method. It takes
236             hash-style arguments and they are as follows:
237              
238             =over 4
239              
240             =item engine
241              
242             Which JavaScript back end to use. Currently, the only two back ends
243             available are L, a pure-Perl JavaScript interpreter, and
244             L (that back end is bundled
245             separately). The SpiderMonkey back end is just a proof-of-concept as of
246             July, 2010, but may become the default in a future version. JE is now the
247             default.
248              
249             If this option is
250             not specified, either JE or SpiderMonkey will be used, whichever is
251             available. It is possible to
252             write one's own bindings for a particular JavaScript engine. See below,
253             under L.
254              
255             =item init
256              
257             Pass to this option a reference to a subroutine and it will be run every
258             time a new JavaScript environment is initialised. This happens after the
259             functions above have been created. The first argument will
260             be the WWW::Scripter object. You can use this, for instance,
261             to make your
262             own functions available to JavaScript.
263              
264             =back
265              
266             =head1 METHODS
267              
268             L's C method will return a plugin object. The
269             same object can be retrieved via C<< $w->plugin('JavaScript') >> after the
270             plugin is loaded. The same plugin object is used for every page and frame,
271             and for every new window derived from the WWW::Scripter object. The
272             following methods can be called on that object:
273              
274             =over 4
275              
276             =item eval
277              
278             This evaluates the JavaScript code passed to it. The WWW::Scripter object
279             is the first argument; the string of code the second. You can optionally
280             pass
281             two more arguments: the file name or URL, and the first line number.
282              
283             This method sets C<$@> and returns C if there is an error.
284              
285             =item set
286              
287             Sets the named variable to the value given. The first argument is the
288             WWW::Scripter object. The last argument is the value. The intervening
289             arguments are the names of properties, so if you want to assign to a
290             property of a property ... of a global property, you can pass each property
291             name separately like this:
292              
293             $w->plugin('JavaScript')->set(
294             $w, 'document', 'location', 'href' => 'http://www.perl.org/'
295             );
296              
297             =item new_function
298              
299             This creates a new global JavaScript function out of a coderef. This
300             function is added to every JavaScript environment the plugin has access to. Pass the WWW::Scripter object as the first argument, the
301             name as
302             the second and the code ref as the third.
303              
304             =item bind_classes
305              
306             Instead of using this method, you might consider L's
307             C method, which is more general-purpose (it applies also to
308             whatever other scripting languages might be available).
309              
310             With this you can bind Perl classes to JavaScript, so that JavaScript can
311             handle objects of those classes. These class bindings will persist from one
312             page to the next.
313              
314             You should pass a hash ref that has the
315             structure described in L, except that this method
316             also accepts a C<< _constructor >> hash element, which should be set to the
317             name of the method to be called when the constructor function is called
318             within JavaScript; e.g., C<< _constructor => 'new' >>.
319              
320             =item back_end
321              
322             This returns the back end corresponding to the WWW::Scripter object passed
323             to it, creating it if necessary. This is intended mostly for back ends
324             themselves to use, for accessing frames, etc.
325              
326             =back
327              
328             =head1 FEATURES AVAILABLE TO JAVASCRIPT
329              
330             The members of the HTML DOM that are available depend on the versions of
331             L and L installed. See L and
332             L.
333              
334             For a list of the properties of the window object, see
335             L.
336              
337             =head1 BACK ENDS
338              
339             A back end has to be in the WWW::Scripter::Plugin::JavaScript:: name
340             space. It will be Cd by this plugin implicitly when its name is
341             passed to the C option.
342              
343             The following methods must be implemented:
344              
345             =head2 Class methods
346              
347             =over 4
348              
349             =item new
350              
351             This method is passed a window (L)
352             object.
353              
354             It has to create a JavaScript environment, in which the global object
355             delegates to the window object for the members listed in
356             L| WWW::Scripter::WindowInterface/THE C<%WindowInterface> HASH>.
357              
358             When the window object or its frames collection (WWW::Scripter::Frames
359             object) is passed to the JavaScript
360             environment, the global
361             object must be returned instead.
362              
363             This method can optionally create C, C and C
364             properties
365             that refer to the global object, but this is not necessary. It might make
366             things a little more efficient.
367              
368             Finally, it has to return an object that implements the interface below.
369              
370             The back end has to do some magic to make sure that, when the global object
371             is passed to another JS environment, references to it automatically point
372             to a new global object when the user (or calling code) browses to another
373             page.
374              
375             For instance, it could wrap up the global object in a proxy object
376             that delegates to whichever global object corresponds to the document.
377              
378             =back
379              
380             =head2 Object Methods
381              
382             =over 4
383              
384             =item eval
385              
386             This should accept up to three arguments: a string of code, the file name
387             or URL, and the first line number.
388              
389             It must set C<$@> and return C if there is an error.
390              
391             =item new_function
392              
393             =item set
394              
395             =item bind_classes
396              
397             These correspond to those
398             listed above for
399             the plugin object. Unlike the above, though, this C is not passed a
400             window as its first argument. Also, C and C are
401             only expected to act on a single JavaScript environment. The plugin's own
402             methods of the same names make sure every JavaScript environment's methods
403             are called.
404              
405             C must also accept a third argument, indicating the return
406             type. This (when specified) will be the name of a JavaScript function that
407             does the type conversion. Only 'Number' is used right now.
408             This requirement may be removed before version 1.
409              
410             =item event2sub ($code, $elem, $url, $first_line)
411              
412             This method needs to turn the
413             event handler code in C<$code> into an object with a C method
414             and then return it. That object's C
415             method will be
416             called with the event target and the event
417             object as its two arguments. Its return
418             value, if
419             defined, will be used to determine whether the event's C
420             method is called.
421              
422             The function's scope must contain the following objects: the global object,
423             the document, the element's form (if there is one) and the element itself.
424              
425             If the C<$code> could not be compiled, this method must set C<$@> and
426             return C, just like C.
427              
428             =item define_setter
429              
430             This will be called
431             with a list of property names representing the 'path' to the property. The
432             last argument will be a coderef that must be called with the value assigned
433             to the property.
434              
435             B This is actually not used right now. The requirement for this may
436             be removed some time before version 1.
437              
438             =head1 PREREQUISITES
439              
440             perl 5.8.4 or higher
441              
442             HTML::DOM 0.032 or higher
443              
444             JE 0.056 or later (when the SpiderMonkey binding is stable enough it will
445             become optional)
446              
447             CSS::DOM
448              
449             WWW::Scripter 0.022 or higher
450              
451             URI
452              
453             Hash::Util::FieldHash::Compat
454              
455             LWP 5.815 or higher
456              
457             =head1 BUGS
458              
459             =for comment
460             (See also L.)
461              
462             There is currently no system in place for preventing pages from different
463             sites from communicating with each other.
464              
465             To report bugs, please e-mail the author.
466              
467             =head1 AUTHOR & COPYRIGHT
468              
469             Copyright (C) 2009-11 Father Chrysostomos
470             join '.', reverse org => 'cpan' >>E
471              
472             This program is free software; you may redistribute it and/or modify
473             it under the same terms as perl.
474              
475             =head1 SEE ALSO
476              
477             =over 4
478              
479             =item -
480              
481             L
482              
483             =item -
484              
485             L
486              
487             =item -
488              
489             L
490              
491             =item -
492              
493             L
494              
495             =item -
496              
497             L
498              
499             =item -
500              
501             L
502              
503             =item -
504              
505             L (the original version of this module)
506              
507             =back