File Coverage

blib/lib/MozRepl/RemoteObject.pm
Criterion Covered Total %
statement 73 417 17.5
branch 6 118 5.0
condition 11 63 17.4
subroutine 18 71 25.3
pod 9 25 36.0
total 117 694 16.8


line stmt bran cond sub pod time code
1             package MozRepl::RemoteObject;
2 27     27   415335 use strict;
  27         51  
  27         1024  
3 27     27   107 use Exporter 'import';
  27         32  
  27         682  
4 27     27   17191 use JSON;
  27         298769  
  27         116  
5 27     27   17429 use Encode qw(decode);
  27         212288  
  27         2080  
6 27     27   163 use Carp qw(croak);
  27         38  
  27         1254  
7 27     27   113 use Scalar::Util qw(refaddr weaken);
  27         35  
  27         2188  
8              
9             =head1 NAME
10              
11             MozRepl::RemoteObject - treat Javascript objects as Perl objects
12              
13             =head1 SYNOPSIS
14              
15             #!perl -w
16             use strict;
17             use MozRepl::RemoteObject;
18            
19             # use $ENV{MOZREPL} or localhost:4242
20             my $repl = MozRepl::RemoteObject->install_bridge();
21            
22             # get our root object:
23             my $tab = $repl->expr(<
24             window.getBrowser().addTab()
25             JS
26              
27             # Now use the object:
28             my $body = $tab->{linkedBrowser}
29             ->{contentWindow}
30             ->{document}
31             ->{body}
32             ;
33             $body->{innerHTML} = "

Hello from MozRepl::RemoteObject

";
34              
35             $body->{innerHTML} =~ '/Hello from/'
36             and print "We stored the HTML";
37              
38             $tab->{linkedBrowser}->loadURI('http://corion.net/');
39              
40             =cut
41              
42 27     27   118 use vars qw[$VERSION $objBridge @CARP_NOT @EXPORT_OK $WARN_ON_LEAKS];
  27         32  
  27         63026  
43             $VERSION = '0.39';
44              
45             @EXPORT_OK=qw[as_list];
46             @CARP_NOT = (qw[MozRepl::RemoteObject::Instance
47             MozRepl::RemoteObject::TiedHash
48             MozRepl::RemoteObject::TiedArray
49             ]);
50              
51             # This should go into __setup__ and attach itself to $repl as .link()
52             $objBridge = <
53             (function(repl){
54             repl.link = function(obj) {
55             // These values should go into a closure instead of attaching to the repl
56             if (! repl.linkedVars) {
57             repl.linkedVars = {};
58             repl.linkedIdNext = 1;
59             };
60            
61             if (obj) {
62             repl.linkedVars[ repl.linkedIdNext ] = obj;
63             return repl.linkedIdNext++;
64             } else {
65             return undefined
66             }
67             };
68              
69             repl.getLink = function(id) {
70             return repl.linkedVars[ id ];
71             };
72              
73             repl.breakLink = function() {
74             var l = arguments.length;
75             for(i=0;i
76             delete repl.linkedVars[ arguments[i] ];
77             };
78             };
79              
80             repl.purgeLinks = function() {
81             repl.linkedVars = {};
82             repl.linkedIdNext = 1;
83             };
84              
85             repl.JSON_ok = function(val,context) {
86             return JSON.stringify({
87             "status":"ok",
88             "result": repl.wrapResults(val,context)
89             });
90             };
91              
92             repl.getAttr = function(id,attr) {
93             var v = repl.getLink(id)[attr];
94             return v
95             };
96              
97             repl.wrapValue = function(v,context) {
98             var payload;
99             if (context == "list") {
100             // The caller wants a lists instead of an array ref
101             // alert("Returning list " + v.length);
102             var r = [];
103             for (var i=0;i
104             r.push(repl.wrapValue(v[i]));
105             };
106             payload = { "result":r, "type":"list" };
107             } else if (v instanceof String
108             || typeof(v) == "string"
109             || v instanceof Number
110             || typeof(v) == "number"
111             || v instanceof Boolean
112             || typeof(v) == "boolean"
113             ) {
114             payload = {"result":v, "type": null }
115             } else {
116             payload = {"result":repl.link(v),"type": typeof(v) }
117             };
118             return payload
119             }
120              
121             var eventQueue = [];
122             repl.wrapResults = function(v,context) {
123             var payload = repl.wrapValue(v,context);
124             if (eventQueue.length) {
125             payload.events = eventQueue;
126             eventQueue = [];
127             };
128             return payload;
129             };
130              
131             repl.dive = function(id,elts) {
132             var obj = repl.getLink(id);
133             var last = "";
134             for (var idx=0;idx
135             var e = elts[idx];
136             // because "in" doesn't seem to look at inherited properties??
137             if (e in obj || obj[e]) {
138             last = e;
139             obj = obj[ e ];
140             } else {
141             throw "Cannot dive: " + last + "." + e + " is empty.";
142             };
143             };
144             return obj
145             };
146              
147             repl.callThis = function(id,args) {
148             var obj = repl.getLink(id);
149             var res = obj.apply(obj, args);
150             return res
151             };
152              
153             repl.callMethod = function(id,fn,args) {
154             var obj = repl.getLink(id);
155             var f = obj[fn];
156             if (! f) {
157             throw "Object has no function " + fn;
158             }
159             return f.apply(obj, args);
160             };
161              
162              
163             repl.makeCatchEvent = function(myid) {
164             var id = myid;
165             return function() {
166             var myargs = arguments;
167             eventQueue.push({
168             cbid : id,
169             ts : Number(new Date()),
170             args : repl.link(myargs)
171             });
172             };
173             };
174              
175             repl.q = function (queue) {
176             try {
177             eval(queue);
178             } catch(e) {
179             // Silently eat those errors
180             // alert("Error in queue: " + e.message + "["+queue+"]");
181             };
182             };
183              
184             repl.ejs = function (js,context) {
185             try {
186             var res = eval(js);
187             return repl.JSON_ok(res,context);
188             } catch(e) {
189             //for (var x in e) { alert(x)};
190             return JSON.stringify({
191             "status":"error",
192             "name": e.name,
193             "message": e.message ? e.message : e,
194             //"line":e.lineNumber,
195             "command":js
196             });
197             };
198             };
199              
200             // This should return links to all installed functions
201             // so we can get rid of nasty details of ->expr()
202             // return repl.wrapResults({});
203             })([% rn %]);
204             JS
205              
206             # Take a JSON response and convert it to a Perl data structure
207             sub to_perl {
208 0     0 0 0 my ($self,$js) = @_;
209 0         0 local $_ = $js;
210             #s/^(\.+\>\s*)+//; # remove Mozrepl continuation prompts
211 0         0 s/^"//;
212 0         0 s/"$//;
213            
214 0 0       0 if (/^(\.+>\s*)+/) {
215             # This should now be eliminated!
216 0         0 die "Continuation prompt found in [$_]";
217             }
218            
219             #warn $js;
220             # reraise JS errors from perspective of caller
221 0 0       0 if (/^!!!\s+(.*)$/m) {
222 0         0 croak "MozRepl::RemoteObject: $1";
223             };
224            
225 0 0       0 if (! /\S/) {
226             # We got an empty string back from the REPL ...
227 0         0 warn "Got empty string from REPL";
228 0         0 return;
229             }
230              
231             # In the case that we don't have a unicode string
232             # already, decode the string from UTF-8
233 0         0 $js = decode('UTF-8', $_);
234             #warn "[[$_]]";
235 0         0 my $res;
236 0         0 local $@;
237 0         0 my $json = $self->json;
238 0 0       0 if (! eval {
239            
240 0         0 $res = $json->decode($js);
241             #use Data::Dumper;
242             #warn Dumper $res;
243 0         0 1
244             }) {
245 0         0 my $err = $@;
246 0         0 my $offset;
247 0 0       0 if ($err =~ /character offset (\d+)\b/) {
248 0         0 $offset = $1
249             };
250 0         0 $offset -= 10;
251 0 0       0 $offset = 0 if $offset < 0;
252 0         0 warn sprintf "(Sub)string is [%s]", substr($js,$offset,20);
253 0         0 die $@
254             };
255 0         0 $res
256             };
257              
258             # Unwrap the result, will in the future also be used
259             # to handle async events
260             sub unwrap_json_result {
261 0     0 0 0 my ($self,$data) = @_;
262 0 0       0 if (my $events = delete $data->{events}) {
263 0         0 my @ev = @$events;
264 0         0 for my $ev (@ev) {
265 0         0 $self->{stats}->{callback}++;
266 0         0 ($ev->{args}) = $self->link_ids($ev->{args});
267 0         0 $self->dispatch_callback($ev);
268 0         0 undef $ev; # release the memory early!
269             };
270             };
271 0   0     0 my $t = $data->{type} || '';
272 0 0       0 if ($t eq 'list') {
    0          
273 0 0       0 return map {
274 0         0 $_->{type}
275             ? $self->link_ids( $_->{result} )
276             : $_->{result}
277 0         0 } @{ $data->{result} };
278             } elsif ($data->{type}) {
279 0         0 return ($self->link_ids( $data->{result} ))[0]
280             } else {
281 0         0 return $data->{result}
282             };
283             };
284              
285             # Call JS and return the unwrapped result
286             sub unjson {
287 0     0 0 0 my ($self,$js,$context) = @_;
288 0         0 my $data = $self->js_call_to_perl_struct($js,$context);
289 0         0 return $self->unwrap_json_result($data);
290             };
291              
292             =head1 BRIDGE SETUP
293              
294             =head2 C<< MozRepl::RemoteObject->install_bridge %options >>
295              
296             Installs the Javascript C<< <-> >> Perl bridge. If you pass in
297             an existing L instance, it must have L
298             loaded if you're running on a browser without native JSON support.
299              
300             If C is not passed in, C<$ENV{MOZREPL}> will be used
301             to find the ip address and portnumber to connect to. If C<$ENV{MOZREPL}>
302             is not set, the default of C will be used.
303              
304             If C is not a reference, it will be used instead of C<$ENV{MOZREPL}>.
305              
306             To replace the default JSON parser, you can pass it in using the C
307             option.
308              
309             =over 4
310              
311             =item *
312              
313             C - a premade L instance to use, or alternatively a
314             connection string to use
315              
316             =item *
317              
318             C - whether to queue destructors until the next command. This
319             reduces the latency and amount of queries sent via L by half,
320             at the cost of a bit delayed release of objects on the remote side. The
321             release commands get queued until the next "real" command gets sent
322             through L.
323              
324             =item *
325              
326             C - the command line to launch the program that runs C.
327              
328             =back
329              
330             =head3 Connect to a different machine
331              
332             If you want to connect to a Firefox instance on a different machine,
333             call C<< ->install_bridge >> as follows:
334              
335             MozRepl::RemoteObject->install_bridge(
336             repl => "$remote_machine:4242"
337             );
338              
339             =head3 Using an existing MozRepl
340              
341             If you want to pass in a preconfigured L object,
342             call C<< ->install_bridge >> as follows:
343              
344             my $repl = MozRepl->new;
345             $repl->setup({
346             log => [qw/ error info /],
347             plugins => { plugins => [qw[ JSON2 ]] },
348             });
349             my $bridge = MozRepl::RemoteObject->install_bridge(repl => $repl);
350              
351             =head3 Launch a mozrepl program if it's not found running
352              
353             If you want to launch Firefox if it's not already running,
354             call C<< ->install_bridge >> as follows:
355              
356             MozRepl::RemoteObject->install_bridge(
357             launch => 'iceweasel' # that program must be in the path
358             );
359              
360             =head3 Using a custom command line
361              
362             By default the launched program will be launched with the C<-repl>
363             command line switch to start up C. If you need to provide
364             the full command line, pass an array reference to the
365             C option:
366              
367             MozRepl::RemoteObject->install_bridge(
368             launch => ['iceweasel','-repl','666']
369             );
370              
371             =head3 Using a custom Mozrepl class
372              
373             By default, any class named in C<$ENV{MOZREPL}> will get loaded and used
374             as the MozRepl backend. That value will get untainted!
375             If you want to prevent C<$ENV{MOZREPL}>
376             from getting used, pass an explicit class name using the C
377             option.
378              
379             MozRepl::RemoteObject->install_bridge(
380             repl_class => 'MozRepl::AnyEvent',
381             );
382              
383             =head3 Preventing/forcing native JSON
384              
385             The Javascript part of MozRepl::RemoteObject will try to detect whether
386             to use the native Mozilla C object or whether to supply its own
387             JSON encoder from L. To prevent the autodetection,
388             pass the C option:
389              
390             js_JSON => 'native', # force to use the native JSON object
391              
392             js_JSON => '', # force the json2.js encoder
393              
394             The autodetection detects whether the connection has a native JSON
395             encoder and whether it properly transports UTF-8.
396              
397             =cut
398              
399             sub require_module($) {
400 25     25 0 47 local $_ = shift;
401 25         111 s{::|'}{/}g;
402 25         10912 require "$_.pm"; # dies if the file is not found
403             };
404              
405             sub install_bridge {
406 25     25 1 1498 my ($package, %options) = @_;
407 25   33     194 $options{ repl } ||= $ENV{MOZREPL};
408 25   100     240 my $repl_class = delete $options{ repl_class } || $ENV{MOZREPL_CLASS} || 'MozRepl';
409             # Untaint repl class
410 25 100       122 $repl_class =~ /^((?:\w+::)+\w+)$/
411             and $repl_class = $1;
412 25   50     135 $options{ constants } ||= {};
413 25   50     135 $options{ log } ||= [qw/ error/];
414 25   50     106 $options{ queue } ||= [];
415 25   50     118 $options{ bufsize } ||= 10_240_000;
416 25   100     101 $options{ use_queue } ||= 0; # > 0 means enqueue
417             # mozrepl
418             # / Net::Telnet don't like too large commands
419 25   50     108 $options{ max_queue_size } ||= 1000;
420            
421 25   50     112 $options{ command_sep } ||= "\n--end-remote-input\n";
422              
423 25 50       78 if (! ref $options{repl}) { # we have host:port
424 25         33 my @host_port;
425 25 50       79 if (defined $options{repl}) {
426 0 0       0 $options{repl} =~ /^(.*):(\d+)$/
427             or croak "Couldn't find host:port from [$options{repl}].";
428 0 0       0 push @host_port, host => $1
429             if defined $1;
430 0 0       0 push @host_port, port => $2
431             if defined $2;
432             };
433 25         71 require_module $repl_class;
434 24         473652 $options{repl} = $repl_class->new();
435             RETRY: {
436 24         68118 my $ok = eval {
  24         55  
437             $options{repl}->setup({
438             client => {
439             @host_port,
440             extra_client_args => {
441             binmode => 1,
442             }
443             },
444             log => $options{ log },
445 24         231 plugins => { plugins => [] },
446             });
447            
448 0 0       0 if (my $bufsize = delete $options{ bufsize }) {
449 0 0       0 if ($options{ repl }->can('client')) {
450 0         0 $options{ repl }->client->telnet->max_buffer_length($bufsize);
451             };
452             };
453              
454 0         0 1;
455             };
456 24 50       1133430 if (! $ok ) {
457 24 50       111 if( $options{ launch }) {
458 0         0 require IPC::Run;
459 0         0 my $cmd = delete $options{ launch };
460 0 0       0 if (! ref $cmd) {
461 0         0 $cmd = [$cmd,'-repl']
462             };
463 0         0 IPC::Run::start($cmd);
464 0         0 sleep 2; # to give the process a chance to launch
465             redo RETRY
466 0         0 } else {
467 24         1110 die "Failed to connect to @host_port, $@";
468             }
469             }
470             };
471             };
472            
473 0 0         if(! exists $options{ js_JSON }) {
474             # Autodetect whether we need the custom JSON serializer
475            
476             # It's required on Firefox 3.0 only
477             my $capabilities = $options{ repl }->execute(
478 0           join "",
479             # Extract version
480             'Components.classes["@mozilla.org/xre/app-info;1"].',
481             'getService(Components.interfaces.nsIXULAppInfo).version+"!"',
482             # Native JSON object available?
483             q{+eval("var r;try{r=JSON.stringify('\u30BD');}catch(e){r=''};r")},
484             # UTF-8 transport detection
485             '+"!\u30BD"',
486             ";\n"
487             );
488 0           $capabilities =~ s/^"(.*)"\s*$/$1/;
489 0           $capabilities =~ s/^"//;
490 0           $capabilities =~ s/"$//;
491             #warn "Capabilities: [$capabilities]";
492 0           my ($version, $have_native, $unicode) = split /!/, $capabilities;
493            
494             #warn $unicode;
495             #warn sprintf "%02x",$_ for map{ord} split //, $unicode;
496 0 0         if ($have_native eq '') {
497 0   0       $options{ js_JSON } ||= "json2; No native JSON object found ($version)";
498             };
499 0 0 0       if( lc $have_native eq lc q{"\u30bd"} # values get escaped
500             or $have_native eq qq{"\x{E3}\x{82}\x{BD}"} # values get encoded as UTF-8
501             ) {
502             # so we can transport unicode properly
503 0   0       $options{ js_JSON } ||= 'native';
504             } else {
505 0   0       $options{ js_JSON } ||= "json2; Transport not UTF-8-safe";
506             };
507             };
508            
509 0 0         if ($options{ js_JSON } ne 'native') {
510             # send our own JSON encoder
511             #warn "Installing custom JSON encoder ($options{ native_JSON })";
512 0           require MozRepl::Plugin::JSON2;
513            
514 0           my $json2 = MozRepl::Plugin::JSON2->new()->process('setup');
515 0           $options{ repl }->execute($json2);
516            
517             # Now, immediately check whether our transport is UTF-8 safe:
518             my $utf8 = $options{ repl }->execute(
519 0           q{JSON.stringify('\u30BD')}.";\n"
520             );
521 0           $utf8 =~ s/\s*$//;
522 0 0         lc $utf8 eq lc q{""\u30bd""}
523             or warn "Transport still not UTF-8 safe: [$utf8].\nDo you have mozrepl 1.1.0 or later installed?";
524             };
525            
526 0           my $rn = $options{repl}->repl;
527 0   0       $options{ json } ||= JSON->new->allow_nonref->ascii; # We talk ASCII
528             # Is this still true? It seems to be even when we find an UTF-8 safe
529             # transport above. This needs some investigation.
530            
531             # Switch the Perl-repl to multiline input mode
532             # Well, better use a custom interactor and pass JSON messages that
533             # are self-delimited and contain no newlines. Newline for a new message.
534            
535             # Switch the JS-repl to multiline input mode
536 0           $options{repl}->execute("$rn.setenv('inputMode','multiline');undefined;\n");
537              
538             # Load the JS side of the JS <-> Perl bridge
539 0           my $c = $objBridge; # make a copy
540 0           $c =~ s/\[%\s+rn\s+%\]/$rn/g; # cheap templating
541             #warn $c;
542            
543 0           $package->execute_command($c, %options);
544            
545 0           $options{ functions } = {}; # cache
546 0           $options{ constants } = {}; # cache
547 0           $options{ callbacks } = {}; # active callbacks
548              
549 0           bless \%options, $package;
550             };
551              
552             sub execute_command {
553 0     0 0   my ($self, $command, %options) = @_;
554 0   0       $options{ repl } ||= $self->repl;
555             $options{ command_sep } ||= $self->command_sep
556 0 0 0       unless exists $options{ command_sep };
557 0           $command =~ s/\s+$//;
558 0           $command .= $options{ command_sep };
559 0           $options{repl}->execute($command);
560             };
561              
562             =head2 C<< $bridge->expr( $js, $context ) >>
563              
564             Runs the Javascript passed in through C< $js > and links
565             the returned result to a Perl object or a plain
566             value, depending on the type of the Javascript result.
567              
568             This is how you get at the initial Javascript object
569             in the object forest.
570              
571             my $window = $bridge->expr('window');
572             print $window->{title};
573            
574             You can also create Javascript functions and use them from Perl:
575              
576             my $add = $bridge->expr(<
577             function (a,b) { return a+b }
578             JS
579             print $add->(2,3);
580             # prints 5
581              
582             The C parameter allows you to specify that you
583             expect a Javascript array and want it to be returned
584             as list. To do that, specify C<'list'> as the C<$context> parameter:
585              
586             for ($bridge->expr(<
587             [1,2,3,4]
588             JS
589              
590             This is slightly more efficient than passing back an array reference
591             and then fetching all elements.
592              
593             =cut
594              
595             # This is used by ->declare() so can't use it itself
596             sub expr {
597 0     0 1   my ($self,$js,$context) = @_;
598 0           return $self->unjson($js,$context);
599             }
600              
601             # the queue stuff is left undocumented because it's
602             # not necessarily useful. The destructors use it to
603             # bundle the destruction of objects when run through
604             # ->queued()
605             sub exprq {
606 0     0 0   my ($self,$js) = @_;
607 0 0         if (defined wantarray) {
608 0           croak "->exprq cannot return a result yet";
609             };
610 0 0         if ($self->{use_queue}) {
611             # can we fake up a result here? Maybe hand out a fictional
612             # object id and tell the JS to construct an object here,
613             # just in case we need it?
614             # later
615 0           push @{ $self->{queue} }, $js;
  0            
616 0 0         if (@{ $self->{queue} } > $self->{ max_queue_size }) {
  0            
617             # flush queue
618 0           $self->poll;
619             };
620             } else {
621 0           $self->js_call_to_perl_struct($js);
622             # but we're not really interested in the result
623             };
624             }
625              
626             =head2 C<< as_list( $array ) >>
627              
628             for $_ in (as_list $array) {
629             print $_->{innerHTML},"\n";
630             };
631              
632             Efficiently fetches all elements from C< @$array >. This is
633             functionally equivalent to writing
634              
635             @$array
636              
637             except that it involves much less roundtrips between Javascript
638             and Perl. If you find yourself using this, consider
639             declaring a Javascript function with C context
640             by using C<< ->declare >> instead.
641              
642             =cut
643              
644             sub as_list {
645 0     0 1   my ($array) = @_;
646 0           my $repl = $array->bridge;
647 0           my $as_array = $repl->declare(<<'JS','list');
648             function(a){return a}
649             JS
650 0           $as_array->($array)
651             };
652              
653             sub queued {
654 0     0 0   my ($self,$cb) = @_;
655 0 0         if (defined wantarray) {
656 0           croak "->queued cannot return a result yet";
657             };
658 0           $self->{use_queue}++;
659 0           $cb->();
660             # ideally, we would gather the results here and
661             # also return those, if wanted.
662 0 0         if (--$self->{use_queue} == 0) {
663             # flush the queue
664             #my $js = join "//\n;//\n", @{ $self->queue };
665 0 0         my $js = join "\n", map { /;$/? $_ : "$_;" } @{ $self->queue };
  0            
  0            
666             # we don't want a result here!
667             # This is where we would do ->execute_async on AnyEvent
668 0           $self->execute_command($js);
669 0           @{ $self->queue } = ();
  0            
670             };
671             };
672              
673             sub DESTROY {
674 0     0     my ($self) = @_;
675 0           local $@;
676             #warn "Repl cleaning up";
677 0           delete @{$self}{ qw( constants functions callbacks )};
  0            
678 0 0 0       if ($self->{use_queue} and $self->queue and @{ $self->queue }) {
  0   0        
679 0           $self->poll;
680             };
681             #warn "Repl cleaned up";
682             };
683              
684             =head2 C<< $bridge->declare( $js, $context ) >>
685              
686             Shortcut to declare anonymous JS functions
687             that will be cached in the bridge. This
688             allows you to use anonymous functions
689             in an efficient manner from your modules
690             while keeping the serialization features
691             of MozRepl::RemoteObject:
692              
693             my $js = <<'JS';
694             function(a,b) {
695             return a+b
696             }
697             JS
698             my $fn = $self->bridge->declare($js);
699             $fn->($a,$b);
700              
701             The function C<$fn> will remain declared
702             on the Javascript side
703             until the bridge is torn down.
704              
705             If you expect an array to be returned and want the array
706             to be fetched as list, pass C<'list'> as the C<$context>.
707             This is slightly more efficient than passing an array reference
708             to Perl and fetching the single elements from Perl.
709              
710             =cut
711              
712             sub declare {
713 0     0 1   my ($self,$js,$context) = @_;
714 0 0         if (! $self->{functions}->{$js}) {
715 0           $self->{functions}->{$js} = $self->expr("var f=$js;\n;f");
716             # Weaken the backlink of the function
717 0           my $res = $self->{functions}->{$js};
718 0           my $ref = ref $res;
719 0           bless $res, "$ref\::HashAccess";
720 0           weaken $res->{bridge};
721 0           $res->{return_context} = $context;
722 0           bless $res => $ref;
723             };
724 0           $self->{functions}->{$js}
725             };
726              
727             sub link_ids {
728 0     0 0   my $self = shift;
729 0 0         map {
730 0           $_ ? MozRepl::RemoteObject::Instance->new( $self, $_ )
731             : undef
732             } @_
733             }
734              
735             =head2 C<< $bridge->constant( $NAME ) >>
736              
737             my $i = $bridge->constant( 'Components.interfaces.nsIWebProgressListener.STATE_STOP' );
738              
739             Fetches and caches a Javascript constant. If you use this to fetch
740             and cache Javascript objects, this will create memory leaks, as these objects
741             will not get released.
742              
743             =cut
744              
745             sub constant {
746 0     0 1   my ($self, $name) = @_;
747 0 0         if (! exists $self->{constants}->{$name}) {
748 0           $self->{constants}->{$name} = $self->expr($name);
749 0 0         if (ref $self->{constants}->{$name}) {
750             #warn "*** $name is an object.";
751             # Need to weaken the backlink of the constant-object
752 0           my $res = $self->{constants}->{$name};
753 0           my $ref = ref $res;
754 0           bless $res, "$ref\::HashAccess";
755 0           weaken $res->{bridge};
756 0           bless $res => $ref;
757             };
758             };
759 0           $self->{constants}->{ $name }
760             };
761              
762             =head2 C<< $bridge->appinfo() >>
763              
764             Returns the C object
765             so you can inspect what application
766             the bridge is connected to:
767              
768             my $info = $bridge->appinfo();
769             print $info->{name}, "\n";
770             print $info->{version}, "\n";
771             print $info->{ID}, "\n";
772              
773             =cut
774              
775             sub appinfo {
776 0     0 1   $_[0]->expr(<<'JS');
777             Components.classes["@mozilla.org/xre/app-info;1"]
778             .getService(Components.interfaces.nsIXULAppInfo);
779             JS
780             };
781              
782             =head2 C<< $bridge->js_call_to_perl_struct( $js, $context ) >>
783              
784             Takes a scalar with JS code, executes it, and returns
785             the result as a Perl structure.
786              
787             This will not (yet?) cope with objects on the remote side, so you
788             will need to make sure to call C<< $rn.link() >> on all objects
789             that are to persist across the bridge.
790              
791             This is a very low level method. You are better advised to use
792             C<< $bridge->expr() >> as that will know
793             to properly wrap objects but leave other values alone.
794              
795             C<$context> is passed through and tells the Javascript side
796             whether to return arrays as objects or as lists. Pass
797             C if you want a list of results instead of a reference
798             to a Javascript C object.
799              
800             =cut
801              
802             sub repl_API {
803 0     0 0   my ($self,$call,@args) = @_;
804 0           return sprintf q<%s.%s(%s);>, $self->repl->repl, $call, join ",", map { $self->json->encode($_) } @args;
  0            
805             };
806              
807             sub js_call_to_perl_struct {
808 0     0 1   my ($self,$js,$context) = @_;
809 0   0       $context ||= '';
810 0           $self->{stats}->{roundtrip}++;
811 0           my $repl = $self->repl;
812 0 0         if (! $repl) {
813             # Likely during global destruction
814             return
815 0           };
816 0 0         my $queue = join '',
817 0           map( { /;$/? $_ : "$_;" } map { s/\s*$//; $_ } @{ $self->queue });
  0            
  0            
  0            
818            
819 0           @{ $self->queue } = ();
  0            
820              
821             #warn "<<$js>>";
822 0           my @js;
823 0 0         if ($queue) {
824 0           push @js, $self->repl_API('q', $queue);
825             };
826 0           push @js, $self->repl_API('ejs', $js, $context );
827 0           $js = join ";", @js;
828            
829 0 0         if (defined wantarray) {
830             #warn $js;
831             # When going async, we would want to turn this into a callback
832 0           my $res = $self->execute_command($js);
833 0           $res =~ s/^(?:\.+\>\s+)+//g;
834 0           while ($res !~ /\S/) {
835             # Gobble up continuation prompts
836 0           warn "No result yet from repl";
837 0           $res = $self->execute_command(";"); # no-op
838 0           $res =~ s/^(?:\.+\>\s+)+//g;
839             };
840 0           my $d = $self->to_perl($res);
841 0 0         if ($d->{status} eq 'ok') {
842 0           return $d->{result}
843             } else {
844 27     27   165 no warnings 'uninitialized';
  27         48  
  27         12890  
845 0           croak ((ref $self).": $d->{name}: $d->{message}");
846             };
847             } else {
848             #warn "Executing $js";
849             # When going async, we would want to turn this into a callback
850             # This produces additional, bogus prompts...
851 0           $self->execute_command($js);
852             ()
853 0           };
854             };
855              
856 0     0 0   sub repl {$_[0]->{repl}};
857 0     0 0   sub command_sep {$_[0]->{command_sep}};
858 0     0 0   sub json {$_[0]->{json}};
859 0 0   0 0   sub name {$_[0]->{repl}?$_[0]->{repl}->repl:undef};
860 0     0 0   sub queue {$_[0]->{queue}};
861              
862             sub make_callback {
863 0     0 0   my ($self,$cb) = @_;
864 0           my $cbid = refaddr $cb;
865 0           my $makeCatchEvent = $self->declare(<<'JS');
866             function(repl,id) {
867             return repl.makeCatchEvent(id);
868             };
869             JS
870 0           my $res = $makeCatchEvent->($self,$cbid);
871 0 0         croak "Couldn't create a callback"
872             if (! $res);
873              
874             # Need to weaken the backlink of the constant-object
875 0           my $ref = ref $res;
876 0           bless $res, "$ref\::HashAccess";
877 0           weaken $res->{bridge};
878 0           bless $res => $ref;
879            
880 0           $self->{callbacks}->{$cbid} = {
881             callback => $cb, jsproxy => $res, where => [caller(1)],
882             };
883 0           $res
884             };
885              
886             sub dispatch_callback {
887 0     0 0   my ($self,$info) = @_;
888 0           my $cbid = $info->{cbid};
889 0 0         if (! $cbid) {
890 0           croak "Unknown callback fired with values @{ $info->{ args }}";
  0            
891             };
892 0 0 0       if (exists $self->{callbacks}->{$cbid} and my $cb = $self->{callbacks}->{$cbid}->{callback}) {
893             # Replace with goto &$cb ?
894 0           my @args = as_list $info->{args};
895 0           $cb->(@args);
896             } else {
897             #warn "Unknown callback id $cbid (created in @{$self->{removed_callbacks}->{$cbid}->{where}})";
898             }
899             };
900              
901             =head2 C<< $bridge->remove_callback( $callback ) >>
902              
903             my $onload = sub {
904             ...
905             };
906             $js_object->{ onload } = $onload;
907             $bridge->remove_callback( $onload )
908              
909             If you want to remove a callback that you instated,
910             this is the way.
911              
912             This will release the resources associated with the callback
913             on both sides of the bridge.
914              
915             =cut
916              
917             sub remove_callback {
918 0     0 1   my ($self,@callbacks) = @_;
919 0           for my $cb (@callbacks) {
920 0           my $cbid = refaddr $cb;
921 0           $self->{removed_callbacks}->{$cbid} = $self->{callbacks}->{$cbid}->{where};
922 0           delete $self->{callbacks}->{$cbid};
923             # and if you don't have memory cycles, all will be fine
924             };
925             };
926              
927             =head2 C<< $bridge->poll >>
928              
929             A crude no-op that can be used to just look if new events have arrived.
930              
931             =cut
932              
933             sub poll {
934 0     0 1   $_[0]->expr('1==1');
935             };
936              
937             package # hide from CPAN
938             MozRepl::RemoteObject::Instance;
939 27     27   140 use strict;
  27         38  
  27         865  
940 27     27   378 use Carp qw(croak);
  27         42  
  27         1366  
941 27     27   134 use Scalar::Util qw(blessed refaddr);
  27         52  
  27         1124  
942 27     27   11435 use MozRepl::RemoteObject::Methods;
  27         43  
  27         734  
943 27     27   122 use vars qw(@CARP_NOT);
  27         33  
  27         1922  
944             @CARP_NOT = 'MozRepl::RemoteObject::Methods';
945              
946             use overload '%{}' => 'MozRepl::RemoteObject::Methods::as_hash',
947             '@{}' => 'MozRepl::RemoteObject::Methods::as_array',
948             '&{}' => 'MozRepl::RemoteObject::Methods::as_code',
949             '==' => 'MozRepl::RemoteObject::Methods::object_identity',
950 27     27   117 '""' => sub { overload::StrVal $_[0] };
  27     0   35  
  27         209  
  0         0  
951              
952             #sub TO_JSON {
953             # sprintf "%s.getLink(%d)", $_[0]->bridge->name, $_[0]->__id
954             #};
955              
956             =head1 HASH access
957              
958             All MozRepl::RemoteObject objects implement
959             transparent hash access through overloading, which means
960             that accessing C<< $document->{body} >> will return
961             the wrapped C<< document.body >> object.
962              
963             This is usually what you want when working with Javascript
964             objects from Perl.
965              
966             Setting hash keys will try to set the respective property
967             in the Javascript object, but always as a string value,
968             numerical values are not supported.
969              
970             =head1 ARRAY access
971              
972             Accessing an object as an array will mainly work. For
973             determining the C, it is assumed that the
974             object has a C<.length> method. If the method has
975             a different name, you will have to access the object
976             as a hash with the index as the key.
977              
978             Note that C expects the underlying object
979             to have a C<.push()> Javascript method, and C
980             gets mapped to the C<.pop()> Javascript method.
981              
982             =cut
983              
984             =head1 OBJECT IDENTITY
985              
986             Object identity is currently implemented by
987             overloading the C<==> operator.
988             Two objects are considered identical
989             if the javascript C<===> operator
990             returns true.
991              
992             my $obj_a = MozRepl::RemoteObject->expr('window.document');
993             print $obj_a->__id(),"\n"; # 42
994             my $obj_b = MozRepl::RemoteObject->expr('window.document');
995             print $obj_b->__id(), "\n"; #43
996             print $obj_a == $obj_b; # true
997              
998             =head1 CALLING METHODS
999              
1000             Calling methods on a Javascript object is supported.
1001              
1002             All arguments will be autoquoted if they contain anything
1003             other than ASCII digits (C<< [0-9] >>). There currently
1004             is no way to specify that you want an all-digit parameter
1005             to be put in between double quotes.
1006              
1007             Passing MozRepl::RemoteObject objects as parameters in Perl
1008             passes the proxied Javascript object as parameter to the Javascript method.
1009              
1010             As in Javascript, functions are first class objects, the following
1011             two methods of calling a function are equivalent:
1012              
1013             $window->loadURI('http://search.cpan.org/');
1014            
1015             $window->{loadURI}->('http://search.cpan.org/');
1016              
1017             =cut
1018              
1019             sub AUTOLOAD {
1020 0     0     my $fn = $MozRepl::RemoteObject::Instance::AUTOLOAD;
1021 0           $fn =~ s/.*:://;
1022 0           my $self = shift;
1023 0           return $self->MozRepl::RemoteObject::Methods::invoke($fn,@_)
1024             }
1025              
1026             =head1 EVENTS / CALLBACKS
1027              
1028             This module also implements a rudimentary asynchronous
1029             event dispatch mechanism. Basically, it allows you
1030             to write code like this and it will work:
1031            
1032             $window->addEventListener('load', sub {
1033             my ($event) = @_;
1034             print "I got a " . $event->{type} . " event\n";
1035             print "on " . $event->{originalTarget};
1036             });
1037             # do other things...
1038              
1039             Note that you cannot block the execution of Javascript that way.
1040             The Javascript code has long continued running when you receive
1041             the event.
1042              
1043             Currently, only busy-waiting is implemented and there is no
1044             way yet for Javascript to tell Perl it has something to say.
1045             So in absence of a real mainloop, you have to call
1046              
1047             $repl->poll;
1048              
1049             from time to time to look for new events. Note that I
1050             call to Javascript will carry all events back to Perl and trigger
1051             the handlers there, so you only need to use poll if no other
1052             activity happens.
1053              
1054              
1055             In the long run,
1056             a move to L would make more sense, but currently,
1057             MozRepl::RemoteObject is still under heavy development on
1058             many fronts so that has been postponed.
1059              
1060             =head1 OBJECT METHODS
1061              
1062             These methods are considered to be internal. You usually
1063             do not want to call them from your code. They are
1064             documented here for the rare case you might need to use them directly
1065             instead of treating the objects as Perl structures. The
1066             official way to access these functions is by using
1067             L instead.
1068              
1069             =head2 C<< $obj->__invoke(METHOD, ARGS) >>
1070              
1071             The C<< ->__invoke() >> object method is an alternate way to
1072             invoke Javascript methods. It is normally equivalent to
1073             C<< $obj->$method(@ARGS) >>. This function must be used if the
1074             METHOD name contains characters not valid in a Perl variable name
1075             (like foreign language characters).
1076             To invoke a Javascript objects native C<< __invoke >> method (if such a
1077             thing exists), please use:
1078              
1079             $object->MozRepl::RemoteObject::Methods::invoke::invoke('__invoke', @args);
1080              
1081             The same method can be used to call the Javascript functions with the
1082             same name as other convenience methods implemented
1083             by this package:
1084              
1085             __attr
1086             __setAttr
1087             __xpath
1088             __click
1089             ...
1090              
1091             =cut
1092              
1093             *__invoke = \&MozRepl::RemoteObject::Methods::invoke;
1094              
1095             =head2 C<< $obj->__transform_arguments(@args) >>
1096              
1097             This method transforms the passed in arguments to their JSON string
1098             representations.
1099              
1100             Things that match C< /^(?:[1-9][0-9]*|0+)$/ > get passed through.
1101            
1102             MozRepl::RemoteObject::Instance instances
1103             are transformed into strings that resolve to their
1104             Javascript global variables. Use the C<< ->expr >> method
1105             to get an object representing these.
1106            
1107             It's also impossible to pass a negative or fractional number
1108             as a number through to Javascript, or to pass digits as a Javascript string.
1109              
1110             =cut
1111            
1112             *__transform_arguments = \&MozRepl::RemoteObject::Methods::transform_arguments;
1113              
1114             =head2 C<< $obj->__id >>
1115              
1116             Readonly accessor for the internal object id
1117             that connects the Javascript object to the
1118             Perl object.
1119              
1120             =cut
1121              
1122             *__id = \&MozRepl::RemoteObject::Methods::id;
1123              
1124             =head2 C<< $obj->__on_destroy >>
1125              
1126             Accessor for the callback
1127             that gets invoked from C<< DESTROY >>.
1128              
1129             =cut
1130              
1131             *__on_destroy = \&MozRepl::RemoteObject::Methods::on_destroy;
1132              
1133             =head2 C<< $obj->bridge >>
1134              
1135             Readonly accessor for the bridge
1136             that connects the Javascript object to the
1137             Perl object.
1138              
1139             =cut
1140              
1141             *bridge = \&MozRepl::RemoteObject::Methods::bridge;
1142              
1143             =head2 C<< $obj->__release_action >>
1144              
1145             Accessor for Javascript code that gets executed
1146             when the Perl object gets released.
1147              
1148             =cut
1149              
1150             sub __release_action {
1151 0     0     my $class = ref $_[0];
1152 0           bless $_[0], "$class\::HashAccess";
1153 0 0         if (2 == @_) {
1154 0           $_[0]->{release_action} = $_[1];
1155             };
1156 0           my $release_action = $_[0]->{release_action};
1157 0           bless $_[0], $class;
1158 0           $release_action
1159             };
1160              
1161             sub DESTROY {
1162 0     0     my $self = shift;
1163 0           local $@;
1164 0           my $id = $self->__id();
1165 0 0         return unless $self->__id();
1166 0           my $release_action;
1167 0 0 0       if ($release_action = ($self->__release_action || '')) {
1168 0           $release_action =~ s/\s+$//mg;
1169 0           $release_action = join '',
1170             'var self = repl.getLink(id);',
1171             $release_action,
1172             ';self = null;',
1173             ;
1174             };
1175 0 0         if (my $on_destroy = $self->__on_destroy) {
1176             #warn "Calling on_destroy";
1177 0           $on_destroy->($self);
1178             };
1179 0 0         if ($self->bridge) { # not always there during global destruction
1180 0           my $rn = $self->bridge->name;
1181 0 0         if ($rn) { # not always there during global destruction
1182             # we don't want a result here!
1183 0           $self->bridge->exprq(<
1184             (function(repl,id){${release_action}repl.breakLink(id)})($rn,$id)
1185             JS
1186             } else {
1187 0           warn "Repl '$rn' has gone away already";
1188             };
1189 0           1
1190             } else {
1191 0 0         if ($MozRepl::RemoteObject::WARN_ON_LEAKS) {
1192 0           warn "Can't release JS part of object $self / $id ($release_action)";
1193             };
1194             };
1195             }
1196              
1197             =head2 C<< $obj->__attr( $attribute ) >>
1198              
1199             Read-only accessor to read the property
1200             of a Javascript object.
1201              
1202             $obj->__attr('foo')
1203            
1204             is identical to
1205              
1206             $obj->{foo}
1207              
1208             =cut
1209              
1210             sub __attr {
1211 0     0     my ($self,$attr,$context) = @_;
1212 0 0         my $id = MozRepl::RemoteObject::Methods::id($self)
1213             or die "No id given";
1214            
1215 0           my $bridge = MozRepl::RemoteObject::Methods::bridge($self);
1216 0           $bridge->{stats}->{fetch}++;
1217 0           my $rn = $bridge->name;
1218 0           my $json = $bridge->json;
1219 0           $attr = $json->encode($attr);
1220 0           return $bridge->unjson(<
1221             $rn.getAttr($id,$attr)
1222             JS
1223             }
1224              
1225             =head2 C<< $obj->__setAttr( $attribute, $value ) >>
1226              
1227             Write accessor to set a property of a Javascript
1228             object.
1229              
1230             $obj->__setAttr('foo', 'bar')
1231            
1232             is identical to
1233              
1234             $obj->{foo} = 'bar'
1235              
1236             =cut
1237              
1238             sub __setAttr {
1239 0     0     my ($self,$attr,$value) = @_;
1240 0 0         my $id = MozRepl::RemoteObject::Methods::id($self)
1241             or die "No id given";
1242 0           my $bridge = $self->bridge;
1243 0           $bridge->{stats}->{store}++;
1244 0           my $rn = $bridge->name;
1245 0           my $json = $bridge->json;
1246 0           $attr = $json->encode($attr);
1247 0           ($value) = $self->__transform_arguments($value);
1248 0           $self->bridge->js_call_to_perl_struct(<
1249             $rn.getLink($id)[$attr]=$value
1250             JS
1251             }
1252              
1253             =head2 C<< $obj->__dive( @PATH ) >>
1254              
1255             B - this method will vanish somewhere after 0.23.
1256             Use L instead.
1257              
1258             Convenience method to quickly dive down a property chain.
1259              
1260             If any element on the path is missing, the method dies
1261             with the error message which element was not found.
1262              
1263             This method is faster than descending through the object
1264             forest with Perl, but otherwise identical.
1265              
1266             my $obj = $tab->{linkedBrowser}
1267             ->{contentWindow}
1268             ->{document}
1269             ->{body}
1270              
1271             my $obj = $tab->__dive(qw(linkedBrowser contentWindow document body));
1272              
1273             =cut
1274              
1275             *__dive = \&MozRepl::RemoteObject::Methods::dive;
1276              
1277             =head2 C<< $obj->__keys() >>
1278              
1279             Please use instead:
1280              
1281             keys %$obj
1282              
1283             The function returns the names of all properties
1284             of the javascript object as a list, just like the C
1285             Perl function.
1286              
1287             $obj->__keys()
1288              
1289             is identical to
1290              
1291             keys %$obj
1292              
1293             =cut
1294              
1295             sub __keys { # or rather, __properties
1296 0     0     my ($self,$attr) = @_;
1297 0 0         die unless $self;
1298            
1299             # We do not want to rely on the object actually supporting
1300             # .hasOwnProperty, so we support both, it having .hasOwnProperty
1301             # and using Object.hasOwnProperty
1302 0           my $getKeys = $self->bridge->declare(<<'JS', 'list');
1303             function(obj){
1304             var res = [];
1305             var hop = // obj.hasOwnProperty
1306             Object.hasOwnProperty
1307             ;
1308             for (var el in obj) {
1309             if (hop.apply(obj, [el])){
1310             res.push(el);
1311             };
1312             }
1313             return res
1314             }
1315             JS
1316 0           return $getKeys->($self)
1317             }
1318              
1319             =head2 C<< $obj->__values() >>
1320              
1321             Please use instead:
1322              
1323             values %$obj
1324              
1325             Returns the values of all properties
1326             as a list.
1327              
1328             $obj->values()
1329            
1330             is identical to
1331              
1332             values %$obj
1333              
1334             =cut
1335              
1336             sub __values { # or rather, __properties
1337 0     0     my ($self,$attr) = @_;
1338 0 0         die unless $self;
1339 0           my $getValues = $self->bridge->declare(<<'JS','list');
1340             function(obj){
1341             var res = [];
1342             for (var el in obj) {
1343             res.push(obj[el]);
1344             }
1345             return res
1346             }
1347             JS
1348 0           return $getValues->($self);
1349             }
1350              
1351             =head2 C<< $obj->__xpath( $query [, $ref ] ) >>
1352              
1353             B - this method will vanish somewhere after 0.23.
1354             Use L instead:
1355              
1356             $obj->MozRepl::RemoteObject::Methods::xpath( $query )
1357              
1358             Executes an XPath query and returns the node
1359             snapshot result as a list.
1360              
1361             This is a convenience method that should only be called
1362             on HTMLdocument nodes.
1363              
1364             The optional C<$ref> parameter can be a DOM node relative to which a
1365             relative XPath expression will be evaluated. It defaults to C.
1366              
1367             The optional C<$cont> parameter can be a Javascript function that
1368             will get applied to every result. This can be used to directly map
1369             each DOM node in the XPath result to an attribute. For example
1370             for efficiently fetching the text value of an XPath query resulting in
1371             textnodes, the two snippets are equivalent, but the latter executes
1372             less roundtrips between Perl and Javascript:
1373              
1374             my @text = map { $_->{nodeValue} }
1375             $obj->MozRepl::RemoteObject::Methods::xpath( '//p/text()' )
1376              
1377              
1378             my $fetch_nodeValue = $bridge->declare(<
1379             function (e){ return e.nodeValue }
1380             JS
1381             my @text = map { $_->{nodeValue} }
1382             $obj->MozRepl::RemoteObject::Methods::xpath( '//p/text()', undef, $fetch_nodeValue )
1383              
1384             =cut
1385              
1386             *__xpath = \&MozRepl::RemoteObject::Methods::xpath;
1387              
1388             =head2 C<< $obj->__click >>
1389              
1390             Sends a Javascript C event to the object.
1391              
1392             This is a convenience method that should only be called
1393             on HTMLdocument nodes or their children.
1394              
1395             =cut
1396              
1397             sub __click {
1398 0     0     my ($self, @args) = @_; # $self is a HTMLdocument or a descendant!
1399 0           $self->__event('click', @args);
1400             }
1401              
1402             =head2 C<< $obj->__change >>
1403              
1404             Sends a Javascript C event to the object.
1405              
1406             This is a convenience method that should only be called
1407             on HTMLdocument nodes or their children.
1408              
1409             =cut
1410              
1411             sub __change {
1412 0     0     my ($self) = @_; # $self is a HTMLdocument or a descendant!
1413 0           $self->__event('change');
1414             }
1415              
1416             =head2 C<< $obj->__event TYPE >>
1417              
1418             Sends a Javascript event of type C to the object.
1419              
1420             This is a convenience method that should only be called
1421             on HTMLdocument nodes or their children.
1422              
1423             =head3 Send a C, C and C event to an element
1424              
1425             The following code simulates the events sent by the
1426             user entering a value into a field:
1427              
1428             $elt->__event('focus');
1429             $elt->{value} = 'Hello';
1430             $elt->__event('change');
1431             $elt->__event('blur');
1432            
1433             =cut
1434              
1435             sub __event {
1436 0     0     my ($self,$type,@args) = @_;
1437 0           my $fn;
1438 0 0         if ($type eq 'click') {
1439 0           $fn = $self->bridge->declare(<<'JS');
1440             function(target,name,x,y) {
1441             if(!x) x= 0;
1442             if(!y) y= 0;
1443             var r= target.getBoundingClientRect();
1444             x+= r.left;
1445             y+= r.top;
1446             var d= target.ownerDocument;
1447             var container= d.defaultView || window;
1448             var event = d.createEvent('MouseEvents');
1449             event.initMouseEvent(name, true, true, container,
1450             null, 0, 0, x, y, false, false, false,
1451             false, 0, null);
1452             target.dispatchEvent(event);
1453             }
1454             JS
1455             } else {
1456 0           $fn = $self->bridge->declare(<<'JS');
1457             function(target,name) {
1458             var event = target.ownerDocument.createEvent('Events');
1459             event.initEvent(name, true, true);
1460             target.dispatchEvent(event);
1461             }
1462             JS
1463             };
1464 0           $fn->($self,$type,@args);
1465             };
1466              
1467             =head2 C<< MozRepl::RemoteObject::Instance->new( $bridge, $ID, $onDestroy ) >>
1468              
1469             This creates a new Perl object that's linked to the
1470             Javascript object C. You usually do not call this
1471             directly but use C<< $bridge->link_ids @IDs >>
1472             to wrap a list of Javascript ids with Perl objects.
1473              
1474             The C<$onDestroy> parameter should contain a Javascript
1475             string that will be executed when the Perl object is
1476             released.
1477             The Javascript string is executed in its own scope
1478             container with the following variables defined:
1479              
1480             =over 4
1481              
1482             =item *
1483              
1484             C - the linked object
1485              
1486             =item *
1487              
1488             C - the numerical Javascript object id of this object
1489              
1490             =item *
1491              
1492             C - the L Javascript C object
1493              
1494             =back
1495              
1496             This method is useful if you want to automatically
1497             close tabs or release other resources
1498             when your Perl program exits.
1499              
1500             =cut
1501              
1502             sub new {
1503 0     0     my ($package,$bridge, $id,$release_action) = @_;
1504             #warn "Created object $id";
1505 0           my $self = {
1506             id => $id,
1507             bridge => $bridge,
1508             release_action => $release_action,
1509             stats => {
1510             roundtrip => 0,
1511             fetch => 0,
1512             store => 0,
1513             callback => 0,
1514             },
1515             };
1516 0   0       bless $self, ref $package || $package;
1517             };
1518              
1519             package # don't index this on CPAN
1520             MozRepl::RemoteObject::TiedHash;
1521 27     27   26734 use strict;
  27         41  
  27         9278  
1522              
1523             sub TIEHASH {
1524 0     0     my ($package,$impl) = @_;
1525 0           my $tied = { impl => $impl };
1526 0           bless $tied, $package;
1527             };
1528              
1529             sub FETCH {
1530 0     0     my ($tied,$k) = @_;
1531 0           my $obj = $tied->{impl};
1532 0           $obj->__attr($k)
1533             };
1534              
1535             sub STORE {
1536 0     0     my ($tied,$k,$val) = @_;
1537 0           my $obj = $tied->{impl};
1538 0           $obj->__setAttr($k,$val);
1539             () # force __setAttr to return nothing
1540 0           };
1541              
1542             sub FIRSTKEY {
1543 0     0     my ($tied) = @_;
1544 0           my $obj = $tied->{impl};
1545 0   0       $tied->{__keys} ||= [$tied->{impl}->__keys()];
1546 0           $tied->{__keyidx} = 0;
1547 0           $tied->{__keys}->[ $tied->{__keyidx}++ ];
1548             };
1549              
1550             sub NEXTKEY {
1551 0     0     my ($tied,$lastkey) = @_;
1552 0           my $obj = $tied->{impl};
1553 0           $tied->{__keys}->[ $tied->{__keyidx}++ ];
1554             };
1555              
1556             sub EXISTS {
1557 0     0     my ($tied,$key) = @_;
1558 0           my $obj = $tied->{impl};
1559 0           my $exists = $obj->bridge->declare(<<'JS');
1560             function(elt,prop) {
1561             return (prop in elt && elt.hasOwnProperty(prop))
1562             }
1563             JS
1564 0           $exists->($obj,$key);
1565             }
1566              
1567             sub DELETE {
1568 0     0     my ($tied,$key) = @_;
1569 0           my $obj = $tied->{impl};
1570 0           my $delete = $obj->bridge->declare(<<'JS');
1571             function(elt,prop) {
1572             var r=elt[prop];
1573             delete elt[prop];
1574             return r
1575             }
1576             JS
1577 0           $delete->($obj,$key);
1578             }
1579              
1580             sub CLEAR {
1581 0     0     my ($tied,$key) = @_;
1582 0           my $obj = $tied->{impl};
1583 0           my $clear = $obj->bridge->declare(<<'JS');
1584             function(obj) {
1585             var del = [];
1586             for (var prop in obj) {
1587             if (obj.hasOwnProperty(prop)) {
1588             del.push(prop);
1589             };
1590             };
1591             for (var i=0;i
1592             delete obj[del[i]]
1593             };
1594             return del
1595             }
1596             JS
1597 0           $clear->($obj);
1598             };
1599              
1600             1;
1601              
1602             package # don't index this on CPAN
1603             MozRepl::RemoteObject::TiedArray;
1604 27     27   124 use strict;
  27         33  
  27         8986  
1605              
1606             sub TIEARRAY {
1607 0     0     my ($package,$impl) = @_;
1608 0           my $tied = { impl => $impl };
1609 0           bless $tied, $package;
1610             };
1611              
1612             sub FETCHSIZE {
1613 0     0     my ($tied) = @_;
1614 0           my $obj = $tied->{impl};
1615 0           $obj->{length};
1616             }
1617              
1618             sub FETCH {
1619 0     0     my ($tied,$k) = @_;
1620 0           my $obj = $tied->{impl};
1621 0           $obj->__attr($k)
1622             };
1623              
1624             sub STORE {
1625 0     0     my ($tied,$k,$val) = @_;
1626 0           my $obj = $tied->{impl};
1627 0           $obj->__setAttr($k,$val);
1628 0           (); # force void context on __setAttr
1629             };
1630              
1631             sub PUSH {
1632 0     0     my $tied = shift;
1633 0           my $obj = $tied->{impl};
1634 0           for (@_) {
1635 0           $obj->push($_);
1636             };
1637             };
1638              
1639             sub POP {
1640 0     0     my $tied = shift;
1641 0           my $obj = $tied->{impl};
1642 0           $obj->pop();
1643             };
1644              
1645             sub SPLICE {
1646 0     0     my ($tied,$from,$count) = (shift,shift,shift);
1647 0           my $obj = $tied->{impl};
1648 0   0       $from ||= 0;
1649 0   0       $count ||= $obj->{length};
1650 0           MozRepl::RemoteObject::as_list $obj->splice($from,$count,@_);
1651             };
1652              
1653             sub CLEAR {
1654 0     0     my $tied = shift;
1655 0           my $obj = $tied->{impl};
1656 0           $obj->splice(0,$obj->{length});
1657             ()
1658 0           };
1659              
1660 0     0     sub EXTEND {
1661             # we acknowledge the advice
1662             };
1663              
1664             1;
1665              
1666             __END__