File Coverage

blib/lib/WWW/Scripter/Plugin/JavaScript/SpiderMonkey.pm
Criterion Covered Total %
statement 17 19 89.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 24 26 92.3


line stmt bran cond sub pod time code
1 1     1   97579 BEGIN { require 5.006 }
2              
3             package WWW::Scripter::Plugin::JavaScript::SpiderMonkey;
4              
5             use#
6 1     1   8 strict; use#
  1         7  
  1         33  
7 1     1   4 warnings;
  1         2  
  1         75  
8              
9 1     1   5 use Carp 'croak';
  1         1  
  1         102  
10 1     1   4 use Hash::Util::FieldHash::Compat 'fieldhash';
  1         2  
  1         12  
11 1     1   58 use HTML::DOM::Interface ':all'; # for the constants
  1         1  
  1         263  
12 1     1   785 use JavaScript 1.12; # PerlSub type
  0            
  0            
13             use Scalar::Util qw'weaken blessed ';
14             use WWW'Scripter'Plugin'JavaScript 0.005; # back_end
15              
16             our $VERSION = '0.003';
17              
18             no constant 1.03 ();
19             use constant::lexical {
20             wndw => 0,
21             cntx => 1,
22             setr => 2,
23             exst => 3,
24             hash => 4, # whether a particular package needs a hash wrapper
25             isam => 5,
26             wrap => 6, # hash wrappers
27             defs => 7,
28             defg => 8,
29             defm => 9,
30             getr =>10,
31             };
32              
33             my $rt;
34              
35             fieldhash my %destructibles;
36              
37             sub new {
38             $rt ||= new JavaScript::Runtime;
39              
40             my $class = shift;
41             my $self = bless[], $class;
42             $self->[wndw] = my $parathi = shift,
43             $self->[cntx] = my $cx = $rt->create_context;
44             $self->[hash] = {};
45              
46             # Weaken the reference to the WWW::Scripter object. Otherwise we
47             # have a reference loop:
48             # window -> js plugin -> sm back end -> window
49             weaken $parathi;
50              
51             # cache $self so we can purge it in an END block
52             weaken(my $weak_self = $self);
53             $destructibles{$self} = \$weak_self;
54              
55             my @wrappers;
56             @wrappers[BOOL,STR,OBJ] = @{ $cx->eval(' 0,function() {
57             // for speed:
58             frames = self = window = this
59             return [
60             function(func_name) {
61             var f = this[func_name]
62             func_name = function() {
63             return Boolean(
64             f.apply(this, arguments)
65             )
66             }
67             },
68             function(func_name) {
69             var f = this[func_name]
70             func_name = function() {
71             var r = f.apply(this, arguments)
72             return r === null || r === void 0
73             ? null : Object(r)
74             }
75             },
76             function(func_name) {
77             var f = this[func_name]
78             func_name = function() {
79             var r = f.apply(this, arguments)
80             return r === null || r === void 0
81             ? null : ""+r
82             }
83             },
84             ]
85             }() ') };
86            
87            
88             my $i = \%WWW'Scripter'WindowInterface;
89             my %methods;
90             @methods{ grep !/^_/ && $$i{$_} & METHOD, =>=> keys %$i } = ();
91             for(keys %methods) {
92             my $method = $_;
93             my $type = $$i{$_}&TYPE;
94             if($type == NUM) {
95             $cx->bind_function($_ => sub {
96             0+$parathi->$method(@_);
97             });
98             }
99             else {
100             $cx->bind_function($_ => sub {
101             $parathi->$method(@_);
102             });
103             $wrappers[$type]($_);
104             }
105              
106             }
107              
108             my $fetch = $cx->eval('
109             0,function(p,f){__defineGetter__(p, function(){return f()})}
110             ');
111             my $store = $cx->eval('
112             0,function(p,f){__defineSetter__(p, function(v){f(v)})}
113             ');
114             weaken(my $cself = $self); # for closures (not foreclosures)
115             # ~~~ We still need to deal with type conversion.
116             my %props;
117             @props{ grep !/^_/ && !($$i{$_}&METHOD) =>=> keys %$i } = ();
118             for(keys %props) {
119             my $name = $_;
120             next if $name =~ /^(?:frames|window|self)\z/; # for
121             my $type = $$i{$_}&TYPE; # efficiency
122             &$store($_ => sub {
123             #my $self = shift;
124             #$self->_cast(
125             # scalar
126             $self->[wndw]->$name,
127             # $types[$type&TYPE]
128             #);
129             });
130             unless($type & READONLY) {
131             &$fetch( $_ => sub {
132             #my $self = shift;
133             #$self->_cast(
134             # scalar
135             my $ret = $cself->[wndw]->$name;
136             exists $cself->[hash]{ref $ret}
137             ? $cself->hash_wrapper($ret)
138             : $ret;
139             # $types[$type&TYPE]
140             #);
141             } );
142             }
143             }
144              
145             $self
146             }
147              
148             END { # Empty any $selves *before* global destruction, to ensure that any
149             for(values %destructibles) { # SM objects we reference go away before the
150             # This line causes a crash in perl 5.8.8. It seems # runtime is freed.
151             # that 5.8.8 has some bug in av_clear in that it can end
152             # up trying to write to the xpvav struct after the array has
153             # been freed. Since, when the array is freed, the sv_any pointer
154             # (which usually points to the xpvav struct) points to another freed
155             # sv, it causes a crash if that sv is used again. Or something like that.
156             # I never did finish getting to the bottom of it.
157             #@$$_ = ();
158             undef $_ for @$$_;
159             }
160             }
161              
162             sub eval {
163             my ($self,$code,$url,$line) = @_;
164             defined $line and substr $code, 0, 0 =>= "\n" x ($line-1);
165             $self->[cntx]->eval($code,$url)
166             }
167              
168             sub set {
169             croak "Not enough arguments for W:M:P:JS:SM->set" unless @_ > 2;
170              
171             my $self = shift;
172             my @args = @_;
173             if(my $h = $self->[hash]) {
174             for(@args){
175             defined blessed $_ or next;
176             exists $$h{ref $_} and $_ = $self->hash_wrapper($_),
177             }
178             }
179             ( $$self[setr] ||= $self->[cntx]->eval('0,function() {
180             var a = arguments;
181             var $obj = this;
182             var $val = a[a.length-1];
183             var $prop = a[a.length-2];
184             for (var i = 0; i < a.length-2; ++i) {
185             var $_ = a[i]
186             $_ in $obj || ($obj[$_] = {});
187             $obj = $obj[$_];
188             }
189             $obj[$prop] = $val;
190             }') )
191             ->(@args);
192             return;
193             }
194              
195             sub bind_classes {
196             # ~~~ We still need to deal with type conversion and read-only props.
197             my($self, $classes) = @_;
198             weaken(my $cself = $self); # self for closures
199             my $cx = $self->[cntx];
200             my $exists = $self->[exst] ||= $cx->eval('0,function(prop) {
201             return prop in this
202             }');
203             my @defer;
204             my $isa_maker = $self->[isam] ||= $cx->eval('
205             0,function(class,super) {
206             this[class].__proto__ = this[super]
207             }
208             ');
209             my $define_setter = $self->[defs] ||= $cx->eval('
210             0,function(class,prop,sub) {
211             this[class].prototype.__defineSetter__(
212             prop,
213             function(v) {
214             sub(this, v)
215             }
216             )
217             }
218             ');
219             my $define_string_getter = $self->[defg] ||= $cx->eval('
220             0,function(class,prop,sub) {
221             this[class].prototype.__defineGetter__(
222             prop,
223             function() {
224             var ret = sub(this)
225             return(
226             typeof ret == "undefined" ? null : String(ret)
227             );
228             }
229             )
230             }
231             ');
232             my $define_string_meth = $self->[defm] ||= $cx->eval('
233             0,function(class,prop,sub) {
234             this[class].prototype[prop] = function() {
235             var ret = sub.apply(this,arguments);
236             return(
237             typeof ret == "undefined" ? null : String(ret)
238             );
239             }
240             }
241             ');
242            
243              
244             for (grep /::/, keys %$classes) {
245             my $i = $$classes{$$classes{$_}}; # interface info
246             if($$i{_hash} || $$i{_array}) { # **Shudder!**
247             my %props;
248             my %methods;
249             {
250             my $i = $i;
251             while() {
252             $props{$_} = undef
253             for grep !/^_/ && !($$i{$_} & METHOD),keys %$i;
254             $methods{$_} = undef
255             for grep !/^_/ && $$i{$_} & METHOD, keys %$i;
256             exists $$i{_isa} || last;
257             $i = $$classes{$$i{_isa}};
258             }
259             }
260             $self->[hash]{$_} = [
261             @$i{'_array','_hash'},\%props,\%methods
262             ];
263             }
264             else {
265             my @props = grep !/^_/ && !($$i{$_} & METHOD), keys %$i;
266             my @str_props;
267             my @str_meths;
268             $cx->bind_class(
269             package => $_,
270             name => $$classes{$_},
271             methods => { map {
272             if(($$i{$_} & TYPE) == STR) {
273             push @str_meths, $_;
274             ()
275             }
276             else {
277             my $method = $_;
278             $_ => sub {
279             my $self = shift;
280             my $ret = $self->$method(@_);
281             exists $cself->[hash]{ref $ret}
282             ? $cself->hash_wrapper($ret)
283             : $ret
284             }
285             }
286             } grep !/^_/ && $$i{$_} & METHOD, keys %$i },
287             properties => { map {
288             if(($$i{$_} & TYPE) == STR) {
289             push @str_props, $_;
290             ()
291             }
292             else {
293             my $prop = $_;
294             $_ => [
295             sub {
296             my $self = shift;
297             my $ret = $self->$prop;
298             exists $cself->[hash]{ref $ret}
299             ? $cself->hash_wrapper($ret)
300             : $ret
301             },
302             sub {
303             # my $self = shift;
304             # my $ret = $self->$prop(@_);
305             # return;
306             },
307             ]
308             }
309             } @props },
310             exists $$i{_constructor}
311             ? (constructor => $$i{_constructor})
312             : (flags => JS_CLASS_NO_INSTANCE),
313             );
314             for my $p(@props) {
315             &$define_setter($$classes{$_}, $p, sub {
316             shift->$p(@_); return
317             });
318             }
319             for my $p(@str_props) {
320             &$define_string_getter($$classes{$_}, $p, sub {
321             shift->$p(@_);
322             });
323             }
324             for my $p(@str_meths) {
325             &$define_string_meth($$classes{$_}, $p, sub {
326             shift->$p(@_);
327             });
328             }
329             }
330              
331             if(exists $$i{_constants}){
332             my $p = $_;
333             for(@{$$i{_constants}}){
334             /([^:]+\z)/;
335             $self->set($$classes{$p}, $1, eval);
336             }
337             }
338            
339             if (exists $$i{_isa}) {
340             if(!&$exists($$i{_isa})) {
341             push @defer, [$$classes{$_}, $$i{_isa}]
342             } else {
343             $isa_maker->($$classes{$_}, $$i{_isa});
344             }
345             }
346             }
347             while(@defer) {
348             my @copy = @defer;
349             @defer = ();
350             for (@copy) {
351             if(&$exists($$_[1])) { # $$_[1] == superclass
352             $isa_maker->(@$_);
353             }
354             else {
355             push @defer, $_;
356             }
357             }
358             }
359              
360             return;
361             }
362              
363             sub event2sub {
364             my ($self, $code, $elem, $url, $line) = @_;
365              
366             # We create a function with a specific scope chain by generating
367             # and calling code like this:
368             # (function() {
369             # with(arguments[0])with(arguments[1])with(arguments[2])
370             # return function() { ... }
371             # })
372              
373             # The global object is automatically in the scope, so we don’t need
374             # to add it explicitly.
375             my @scope = (
376             $elem->can('form') ? $elem->form : (),
377             $elem
378             );
379              
380             # We need the line break after $code, because there may be a sin-
381             # gle-line comment at the end, and no line break. ("foo //bar"
382             # would fail without this, because the closing }}) would be com-
383             # mented out too.)
384             ($self->[cntx]->eval(
385             "\n" x($line-1) . "(function(){"
386             . (join '', map "with(arguments[$_])", 0..$#scope)
387             . "return function() { $code\n } })",
388             $url
389             )||return) -> ( @scope );
390             }
391              
392             sub new_function {
393             my($self, $name, $sub) = @_;
394             $self->set($name,$sub);
395             return;
396             }
397              
398             sub hash_wrapper {
399             my $self = shift;
400             my $w = $self->[wrap] ||= &fieldhash({});
401             my $obj = shift;
402             $w->{$obj} ||= do {
403             my $wrapper = new JavaScript::PerlHash;
404             # WWW::Scripter is the special case
405             if(ref $obj eq 'WWW::Scripter') {
406             tie
407             %{get_ref $wrapper},
408             __PACKAGE__.'::WindowProxy',
409             $obj;
410             }
411             else {
412             my $binding_info = $self->[hash]{ref $obj};
413             tie
414             %{$wrapper->get_ref},
415             __PACKAGE__.'::Hash',
416             $obj, @$binding_info, $self;
417             }
418             $wrapper;
419             }
420             }
421              
422             sub _hash_classes { shift->[hash] }
423              
424              
425             package WWW::Scripter::Plugin::JavaScript::SpiderMonkey::WindowProxy;
426             # Is this package name long enough?
427              
428             sub TIEHASH {
429             # Slot 0 is the WWW::Scripter object. Slot 1 is used to catch the
430             # fetching function.
431             bless [pop], shift;
432             }
433              
434             sub STORE {
435             my $w = ${;shift}[0];
436             $w->plugin("JavaScript")->back_end($w)->set(shift, shift);
437             }
438              
439             sub CLEAR{}
440              
441             sub FETCH {
442             my $self = shift;
443             my $w = $$self[0];
444             (
445             $$self[1]
446             ||= $w->plugin("JavaScript")->back_end($w)->eval(
447             '0,function(k){ return this[k] }'
448             )
449             )->(shift)
450             }
451              
452              
453             package WWW::Scripter::Plugin::JavaScript::SpiderMonkey::Hash;
454              
455             use constant::lexical {
456             obje => 0, arry => 1, hash => 2, prop => 3, meth => 4, jsbe => 5,
457             };
458              
459             sub TIEHASH {
460             # args: 0) object to wrap
461             # 1) array?
462             # 2) hash?
463             # 3) { props }
464             # 4) { methods }
465             # 5) JavaScript back end (wspjssm object)
466             my $ret = bless \@_, shift;
467             # warn "wrapping up a " . ref($obj) . " object with props [ @{$ret->[prop]} ]";
468             Scalar::Util'weaken($ret->[jsbe]);
469             $ret;
470             }
471              
472             sub STORE {
473             my $self = shift;
474             my $name = shift;
475             exists $self->[prop]{$name} and $self->[obje]->$name(shift), return;
476             exists $self->[meth]{$name} and return;
477             $self->[arry] && $name =~ /^(?:0|[1-9]\d*)\z/ && $name < 4294967295
478             ? $self->[obje][$name]=shift
479             :($self->[obje]{$name}=shift);
480             }
481              
482             sub CLEAR{}
483              
484             sub FETCH {
485             my $self = shift;
486             my $name = shift;
487             my $ret =
488             exists $self->[prop]{$name} ? $self->[obje]->$name :
489             exists $self->[meth]{$name} ? return sub { $self->[obje]->$name(@_) } :
490             $self->[arry] && $name =~ /^(?:0|[1-9]\d*)\z/ && $name < 4294967295
491             ? $self->[obje][$name]
492             : $self->[obje]{$name};
493             exists $self->[jsbe]->_hash_classes->{ref $ret}
494             ? $self->[jsbe]->hash_wrapper($ret)
495             : $ret;
496             }
497              
498              
499             exit exit exit exit exit exit exit exit exit exit exit exit exit return 1;
500              
501             # ------------------ DOCS --------------------#
502              
503              
504              
505             =head1 NAME
506              
507             WWW::Scripter::Plugin::JavaScript::SpiderMonkey - SpiderMonkey backend for wspjs
508              
509             =head1 VERSION
510              
511             0.003 (alpha)
512              
513             =head1 SYNOPSIS
514              
515             use WWW::Scripter;
516            
517             my $w = new WWW::Scripter;
518             $w->use_plugin('JavaScript', engine => 'SpiderMonkey');
519            
520             $w->get("http://...");
521             # etc.
522              
523             =head1 DESCRIPTION
524              
525             This little module is a bit of duct tape to connect the JavaScript plugin
526             for L to the SpiderMonkey JavaScript engine via
527             L. Don't use this module
528             directly. For usage, see
529             L.
530              
531             =head1 BUGS
532              
533             There are too many to list! This thing is currently very unstable, to put
534             it mildly.
535              
536             If you find any bugs, please report them via L
537             or
538             L (long e-mail
539             address, isn't it?).
540              
541             =head1 SINE QUIBUS NON
542              
543             perl 5.8.3 or higher (5.8.6 or higher recommended)
544              
545             HTML::DOM 0.008 or later
546              
547             JavaScript.pm 1.12 or later
548              
549             Hash::Util::FieldHash::Compat
550              
551             constant::lexical
552              
553             =head1 AUTHOR & COPYRIGHT
554              
555             Copyright (C) 2010-11, Father Chrysostomos (org.cpan@sprout backwards)
556              
557             This program is free software; you may redistribute it, modify it or
558             both under the same terms as perl.
559              
560             =head1 SEE ALSO
561              
562             =over 4
563              
564             =item -
565              
566             L
567              
568             =item -
569              
570             L