File Coverage

blib/lib/MozRepl/RemoteObject.pm
Criterion Covered Total %
statement 73 419 17.4
branch 6 120 5.0
condition 10 63 15.8
subroutine 18 71 25.3
pod 9 25 36.0
total 116 698 16.6


line stmt bran cond sub pod time code
1             package MozRepl::RemoteObject;
2 26     26   1225819 use strict;
  26         63  
  26         725  
3 26     26   135 use Exporter 'import';
  26         47  
  26         697  
4 26     26   9792 use JSON;
  26         224968  
  26         132  
5 26     26   11090 use Encode qw(decode);
  26         191523  
  26         1758  
6 26     26   195 use Carp qw(croak);
  26         51  
  26         1080  
7 26     26   140 use Scalar::Util qw(refaddr weaken);
  26         46  
  26         1364  
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('https://corion.net/');
39              
40             =cut
41              
42 26     26   142 use vars qw[$VERSION $objBridge @CARP_NOT @EXPORT_OK $WARN_ON_LEAKS];
  26         48  
  26         59828  
43             $VERSION = '0.40';
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             return map {
274             $_->{type}
275             ? $self->link_ids( $_->{result} )
276             : $_->{result}
277 0 0       0 } @{ $data->{result} };
  0         0  
  0         0  
278             } elsif ($data->{type}) {
279 0         0 return ($self->link_ids( $data->{result} ))[0]
280             } else {
281             return $data->{result}
282 0         0 };
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 51 local $_ = shift;
401 25         108 s{::|'}{/}g;
402 25         5991 require "$_.pm"; # dies if the file is not found
403             };
404              
405             sub install_bridge {
406 25     25 1 3336 my ($package, %options) = @_;
407 25   33     209 $options{ repl } ||= $ENV{MOZREPL};
408 25   50     188 my $repl_class = delete $options{ repl_class } || $ENV{MOZREPL_CLASS} || 'MozRepl';
409             # Untaint repl class
410 25 100       110 $repl_class =~ /^((?:\w+::)+\w+)$/
411             and $repl_class = $1;
412 25   50     176 $options{ constants } ||= {};
413 25   50     147 $options{ log } ||= [qw/ error/];
414 25   50     118 $options{ queue } ||= [];
415 25   50     156 $options{ bufsize } ||= 10_240_000;
416 25   100     100 $options{ use_queue } ||= 0; # > 0 means enqueue
417             # mozrepl
418             # / Net::Telnet don't like too large commands
419 25   50     112 $options{ max_queue_size } ||= 1000;
420              
421 25   50     128 $options{ command_sep } ||= "\n--end-remote-input\n";
422              
423 25 50       77 if (! ref $options{repl}) { # we have host:port
424 25         43 my @host_port;
425 25 50       81 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         87 require_module $repl_class;
434 24         385262 $options{repl} = $repl_class->new();
435             RETRY: {
436 24         54637 my $ok = eval {
  24         60  
437             $options{repl}->setup({
438             client => {
439             @host_port,
440             extra_client_args => {
441             binmode => 1,
442             }
443             },
444             log => $options{ log },
445 24         215 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       964490 if (! $ok ) {
457 24 50       113 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         831 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             map {
730 0 0         $_ ? MozRepl::RemoteObject::Instance->new( $self, $_ )
  0            
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             my $queue = join '',
817 0 0         map( { /;$/? $_ : "$_;" } map { s/\s*$//; $_ } @{ $self->queue });
  0            
  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           my $i=0;
835 0           while ($res !~ /\S/) {
836             # Gobble up continuation prompts
837 0           warn "No result yet from repl";
838 0           $res = $self->execute_command(";"); # no-op
839 0           $res =~ s/^(?:\.+\>\s+)+//g;
840 0           $i++;
841 0 0         last if ($i == 25);
842             };
843 0           my $d = $self->to_perl($res);
844 0 0         if ($d->{status} eq 'ok') {
845             return $d->{result}
846 0           } else {
847 26     26   214 no warnings 'uninitialized';
  26         56  
  26         12221  
848 0           croak ((ref $self).": $d->{name}: $d->{message}");
849             };
850             } else {
851             #warn "Executing $js";
852             # When going async, we would want to turn this into a callback
853             # This produces additional, bogus prompts...
854 0           $self->execute_command($js);
855             ()
856 0           };
857             };
858              
859 0     0 0   sub repl {$_[0]->{repl}};
860 0     0 0   sub command_sep {$_[0]->{command_sep}};
861 0     0 0   sub json {$_[0]->{json}};
862 0 0   0 0   sub name {$_[0]->{repl}?$_[0]->{repl}->repl:undef};
863 0     0 0   sub queue {$_[0]->{queue}};
864              
865             sub make_callback {
866 0     0 0   my ($self,$cb) = @_;
867 0           my $cbid = refaddr $cb;
868 0           my $makeCatchEvent = $self->declare(<<'JS');
869             function(repl,id) {
870             return repl.makeCatchEvent(id);
871             };
872             JS
873 0           my $res = $makeCatchEvent->($self,$cbid);
874 0 0         croak "Couldn't create a callback"
875             if (! $res);
876              
877             # Need to weaken the backlink of the constant-object
878 0           my $ref = ref $res;
879 0           bless $res, "$ref\::HashAccess";
880 0           weaken $res->{bridge};
881 0           bless $res => $ref;
882              
883 0           $self->{callbacks}->{$cbid} = {
884             callback => $cb, jsproxy => $res, where => [caller(1)],
885             };
886 0           $res
887             };
888              
889             sub dispatch_callback {
890 0     0 0   my ($self,$info) = @_;
891 0           my $cbid = $info->{cbid};
892 0 0         if (! $cbid) {
893 0           croak "Unknown callback fired with values @{ $info->{ args }}";
  0            
894             };
895 0 0 0       if (exists $self->{callbacks}->{$cbid} and my $cb = $self->{callbacks}->{$cbid}->{callback}) {
896             # Replace with goto &$cb ?
897 0           my @args = as_list $info->{args};
898 0           $cb->(@args);
899             } else {
900             #warn "Unknown callback id $cbid (created in @{$self->{removed_callbacks}->{$cbid}->{where}})";
901             }
902             };
903              
904             =head2 C<< $bridge->remove_callback( $callback ) >>
905              
906             my $onload = sub {
907             ...
908             };
909             $js_object->{ onload } = $onload;
910             $bridge->remove_callback( $onload )
911              
912             If you want to remove a callback that you instated,
913             this is the way.
914              
915             This will release the resources associated with the callback
916             on both sides of the bridge.
917              
918             =cut
919              
920             sub remove_callback {
921 0     0 1   my ($self,@callbacks) = @_;
922 0           for my $cb (@callbacks) {
923 0           my $cbid = refaddr $cb;
924 0           $self->{removed_callbacks}->{$cbid} = $self->{callbacks}->{$cbid}->{where};
925 0           delete $self->{callbacks}->{$cbid};
926             # and if you don't have memory cycles, all will be fine
927             };
928             };
929              
930             =head2 C<< $bridge->poll >>
931              
932             A crude no-op that can be used to just look if new events have arrived.
933              
934             =cut
935              
936             sub poll {
937 0     0 1   $_[0]->expr('1==1');
938             };
939              
940             package # hide from CPAN
941             MozRepl::RemoteObject::Instance;
942 26     26   178 use strict;
  26         56  
  26         643  
943 26     26   359 use Carp qw(croak);
  26         52  
  26         1166  
944 26     26   146 use Scalar::Util qw(blessed refaddr);
  26         48  
  26         1064  
945 26     26   8216 use MozRepl::RemoteObject::Methods;
  26         66  
  26         770  
946 26     26   147 use vars qw(@CARP_NOT);
  26         46  
  26         1853  
947             @CARP_NOT = 'MozRepl::RemoteObject::Methods';
948              
949             use overload '%{}' => 'MozRepl::RemoteObject::Methods::as_hash',
950             '@{}' => 'MozRepl::RemoteObject::Methods::as_array',
951             '&{}' => 'MozRepl::RemoteObject::Methods::as_code',
952             '==' => 'MozRepl::RemoteObject::Methods::object_identity',
953 26     26   143 '""' => sub { overload::StrVal $_[0] };
  26     0   44  
  26         200  
  0         0  
954              
955             #sub TO_JSON {
956             # sprintf "%s.getLink(%d)", $_[0]->bridge->name, $_[0]->__id
957             #};
958              
959             =head1 HASH access
960              
961             All MozRepl::RemoteObject objects implement
962             transparent hash access through overloading, which means
963             that accessing C<< $document->{body} >> will return
964             the wrapped C<< document.body >> object.
965              
966             This is usually what you want when working with Javascript
967             objects from Perl.
968              
969             Setting hash keys will try to set the respective property
970             in the Javascript object, but always as a string value,
971             numerical values are not supported.
972              
973             =head1 ARRAY access
974              
975             Accessing an object as an array will mainly work. For
976             determining the C, it is assumed that the
977             object has a C<.length> method. If the method has
978             a different name, you will have to access the object
979             as a hash with the index as the key.
980              
981             Note that C expects the underlying object
982             to have a C<.push()> Javascript method, and C
983             gets mapped to the C<.pop()> Javascript method.
984              
985             =cut
986              
987             =head1 OBJECT IDENTITY
988              
989             Object identity is currently implemented by
990             overloading the C<==> operator.
991             Two objects are considered identical
992             if the javascript C<===> operator
993             returns true.
994              
995             my $obj_a = MozRepl::RemoteObject->expr('window.document');
996             print $obj_a->__id(),"\n"; # 42
997             my $obj_b = MozRepl::RemoteObject->expr('window.document');
998             print $obj_b->__id(), "\n"; #43
999             print $obj_a == $obj_b; # true
1000              
1001             =head1 CALLING METHODS
1002              
1003             Calling methods on a Javascript object is supported.
1004              
1005             All arguments will be autoquoted if they contain anything
1006             other than ASCII digits (C<< [0-9] >>). There currently
1007             is no way to specify that you want an all-digit parameter
1008             to be put in between double quotes.
1009              
1010             Passing MozRepl::RemoteObject objects as parameters in Perl
1011             passes the proxied Javascript object as parameter to the Javascript method.
1012              
1013             As in Javascript, functions are first class objects, the following
1014             two methods of calling a function are equivalent:
1015              
1016             $window->loadURI('http://search.cpan.org/');
1017              
1018             $window->{loadURI}->('http://search.cpan.org/');
1019              
1020             =cut
1021              
1022             sub AUTOLOAD {
1023 0     0     my $fn = $MozRepl::RemoteObject::Instance::AUTOLOAD;
1024 0           $fn =~ s/.*:://;
1025 0           my $self = shift;
1026 0           return $self->MozRepl::RemoteObject::Methods::invoke($fn,@_)
1027             }
1028              
1029             =head1 EVENTS / CALLBACKS
1030              
1031             This module also implements a rudimentary asynchronous
1032             event dispatch mechanism. Basically, it allows you
1033             to write code like this and it will work:
1034              
1035             $window->addEventListener('load', sub {
1036             my ($event) = @_;
1037             print "I got a " . $event->{type} . " event\n";
1038             print "on " . $event->{originalTarget};
1039             });
1040             # do other things...
1041              
1042             Note that you cannot block the execution of Javascript that way.
1043             The Javascript code has long continued running when you receive
1044             the event.
1045              
1046             Currently, only busy-waiting is implemented and there is no
1047             way yet for Javascript to tell Perl it has something to say.
1048             So in absence of a real mainloop, you have to call
1049              
1050             $repl->poll;
1051              
1052             from time to time to look for new events. Note that I
1053             call to Javascript will carry all events back to Perl and trigger
1054             the handlers there, so you only need to use poll if no other
1055             activity happens.
1056              
1057              
1058             In the long run,
1059             a move to L would make more sense, but currently,
1060             MozRepl::RemoteObject is still under heavy development on
1061             many fronts so that has been postponed.
1062              
1063             =head1 OBJECT METHODS
1064              
1065             These methods are considered to be internal. You usually
1066             do not want to call them from your code. They are
1067             documented here for the rare case you might need to use them directly
1068             instead of treating the objects as Perl structures. The
1069             official way to access these functions is by using
1070             L instead.
1071              
1072             =head2 C<< $obj->__invoke(METHOD, ARGS) >>
1073              
1074             The C<< ->__invoke() >> object method is an alternate way to
1075             invoke Javascript methods. It is normally equivalent to
1076             C<< $obj->$method(@ARGS) >>. This function must be used if the
1077             METHOD name contains characters not valid in a Perl variable name
1078             (like foreign language characters).
1079             To invoke a Javascript objects native C<< __invoke >> method (if such a
1080             thing exists), please use:
1081              
1082             $object->MozRepl::RemoteObject::Methods::invoke::invoke('__invoke', @args);
1083              
1084             The same method can be used to call the Javascript functions with the
1085             same name as other convenience methods implemented
1086             by this package:
1087              
1088             __attr
1089             __setAttr
1090             __xpath
1091             __click
1092             ...
1093              
1094             =cut
1095              
1096             *__invoke = \&MozRepl::RemoteObject::Methods::invoke;
1097              
1098             =head2 C<< $obj->__transform_arguments(@args) >>
1099              
1100             This method transforms the passed in arguments to their JSON string
1101             representations.
1102              
1103             Things that match C< /^(?:[1-9][0-9]*|0+)$/ > get passed through.
1104              
1105             MozRepl::RemoteObject::Instance instances
1106             are transformed into strings that resolve to their
1107             Javascript global variables. Use the C<< ->expr >> method
1108             to get an object representing these.
1109              
1110             It's also impossible to pass a negative or fractional number
1111             as a number through to Javascript, or to pass digits as a Javascript string.
1112              
1113             =cut
1114              
1115             *__transform_arguments = \&MozRepl::RemoteObject::Methods::transform_arguments;
1116              
1117             =head2 C<< $obj->__id >>
1118              
1119             Readonly accessor for the internal object id
1120             that connects the Javascript object to the
1121             Perl object.
1122              
1123             =cut
1124              
1125             *__id = \&MozRepl::RemoteObject::Methods::id;
1126              
1127             =head2 C<< $obj->__on_destroy >>
1128              
1129             Accessor for the callback
1130             that gets invoked from C<< DESTROY >>.
1131              
1132             =cut
1133              
1134             *__on_destroy = \&MozRepl::RemoteObject::Methods::on_destroy;
1135              
1136             =head2 C<< $obj->bridge >>
1137              
1138             Readonly accessor for the bridge
1139             that connects the Javascript object to the
1140             Perl object.
1141              
1142             =cut
1143              
1144             *bridge =
1145             *bridge =
1146             \&MozRepl::RemoteObject::Methods::bridge;
1147              
1148             =head2 C<< $obj->__release_action >>
1149              
1150             Accessor for Javascript code that gets executed
1151             when the Perl object gets released.
1152              
1153             =cut
1154              
1155             sub __release_action {
1156 0     0     my $class = ref $_[0];
1157 0           bless $_[0], "$class\::HashAccess";
1158 0 0         if (2 == @_) {
1159 0           $_[0]->{release_action} = $_[1];
1160             };
1161 0           my $release_action = $_[0]->{release_action};
1162 0           bless $_[0], $class;
1163 0           $release_action
1164             };
1165              
1166             sub DESTROY {
1167 0     0     my $self = shift;
1168 0           local $@;
1169 0           my $id = $self->__id();
1170 0 0         return unless $self->__id();
1171 0           my $release_action;
1172 0 0 0       if ($release_action = ($self->__release_action || '')) {
1173 0           $release_action =~ s/\s+$//mg;
1174 0           $release_action = join '',
1175             'var self = repl.getLink(id);',
1176             $release_action,
1177             ';self = null;',
1178             ;
1179             };
1180 0 0         if (my $on_destroy = $self->__on_destroy) {
1181             #warn "Calling on_destroy";
1182 0           $on_destroy->($self);
1183             };
1184 0 0         if ($self->bridge) { # not always there during global destruction
1185 0           my $rn = $self->bridge->name;
1186 0 0         if ($rn) { # not always there during global destruction
1187             # we don't want a result here!
1188 0           $self->bridge->exprq(<
1189             (function(repl,id){${release_action}repl.breakLink(id)})($rn,$id)
1190             JS
1191             } else {
1192 0           warn "Repl '$rn' has gone away already";
1193             };
1194 0           1
1195             } else {
1196 0 0         if ($MozRepl::RemoteObject::WARN_ON_LEAKS) {
1197 0           warn "Can't release JS part of object $self / $id ($release_action)";
1198             };
1199             };
1200             }
1201              
1202             =head2 C<< $obj->__attr( $attribute ) >>
1203              
1204             Read-only accessor to read the property
1205             of a Javascript object.
1206              
1207             $obj->__attr('foo')
1208              
1209             is identical to
1210              
1211             $obj->{foo}
1212              
1213             =cut
1214              
1215             sub __attr {
1216 0     0     my ($self,$attr,$context) = @_;
1217 0 0         my $id = MozRepl::RemoteObject::Methods::id($self)
1218             or die "No id given";
1219              
1220 0           my $bridge = MozRepl::RemoteObject::Methods::bridge($self);
1221 0           $bridge->{stats}->{fetch}++;
1222 0           my $rn = $bridge->name;
1223 0           my $json = $bridge->json;
1224 0           $attr = $json->encode($attr);
1225 0           return $bridge->unjson(<
1226             $rn.getAttr($id,$attr)
1227             JS
1228             }
1229              
1230             =head2 C<< $obj->__setAttr( $attribute, $value ) >>
1231              
1232             Write accessor to set a property of a Javascript
1233             object.
1234              
1235             $obj->__setAttr('foo', 'bar')
1236              
1237             is identical to
1238              
1239             $obj->{foo} = 'bar'
1240              
1241             =cut
1242              
1243             sub __setAttr {
1244 0     0     my ($self,$attr,$value) = @_;
1245 0 0         my $id = MozRepl::RemoteObject::Methods::id($self)
1246             or die "No id given";
1247 0           my $bridge = $self->bridge;
1248 0           $bridge->{stats}->{store}++;
1249 0           my $rn = $bridge->name;
1250 0           my $json = $bridge->json;
1251 0           $attr = $json->encode($attr);
1252 0           ($value) = $self->__transform_arguments($value);
1253 0           $self->bridge->js_call_to_perl_struct(<
1254             $rn.getLink($id)[$attr]=$value
1255             JS
1256             }
1257              
1258             =head2 C<< $obj->__dive( @PATH ) >>
1259              
1260             B - this method will vanish somewhere after 0.23.
1261             Use L instead.
1262              
1263             Convenience method to quickly dive down a property chain.
1264              
1265             If any element on the path is missing, the method dies
1266             with the error message which element was not found.
1267              
1268             This method is faster than descending through the object
1269             forest with Perl, but otherwise identical.
1270              
1271             my $obj = $tab->{linkedBrowser}
1272             ->{contentWindow}
1273             ->{document}
1274             ->{body}
1275              
1276             my $obj = $tab->__dive(qw(linkedBrowser contentWindow document body));
1277              
1278             =cut
1279              
1280             *__dive = \&MozRepl::RemoteObject::Methods::dive;
1281              
1282             =head2 C<< $obj->__keys() >>
1283              
1284             Please use instead:
1285              
1286             keys %$obj
1287              
1288             The function returns the names of all properties
1289             of the javascript object as a list, just like the C
1290             Perl function.
1291              
1292             $obj->__keys()
1293              
1294             is identical to
1295              
1296             keys %$obj
1297              
1298             =cut
1299              
1300             sub __keys { # or rather, __properties
1301 0     0     my ($self,$attr) = @_;
1302 0 0         die unless $self;
1303              
1304             # We do not want to rely on the object actually supporting
1305             # .hasOwnProperty, so we support both, it having .hasOwnProperty
1306             # and using Object.hasOwnProperty
1307 0           my $getKeys = $self->bridge->declare(<<'JS', 'list');
1308             function(obj){
1309             var res = [];
1310             var hop = // obj.hasOwnProperty
1311             Object.hasOwnProperty
1312             ;
1313             for (var el in obj) {
1314             if (hop.apply(obj, [el])){
1315             res.push(el);
1316             };
1317             }
1318             return res
1319             }
1320             JS
1321 0           return $getKeys->($self)
1322             }
1323              
1324             =head2 C<< $obj->__values() >>
1325              
1326             Please use instead:
1327              
1328             values %$obj
1329              
1330             Returns the values of all properties
1331             as a list.
1332              
1333             $obj->values()
1334              
1335             is identical to
1336              
1337             values %$obj
1338              
1339             =cut
1340              
1341             sub __values { # or rather, __properties
1342 0     0     my ($self,$attr) = @_;
1343 0 0         die unless $self;
1344 0           my $getValues = $self->bridge->declare(<<'JS','list');
1345             function(obj){
1346             var res = [];
1347             for (var el in obj) {
1348             res.push(obj[el]);
1349             }
1350             return res
1351             }
1352             JS
1353 0           return $getValues->($self);
1354             }
1355              
1356             =head2 C<< $obj->__xpath( $query [, $ref ] ) >>
1357              
1358             B - this method will vanish somewhere after 0.23.
1359             Use L instead:
1360              
1361             $obj->MozRepl::RemoteObject::Methods::xpath( $query )
1362              
1363             Executes an XPath query and returns the node
1364             snapshot result as a list.
1365              
1366             This is a convenience method that should only be called
1367             on HTMLdocument nodes.
1368              
1369             The optional C<$ref> parameter can be a DOM node relative to which a
1370             relative XPath expression will be evaluated. It defaults to C.
1371              
1372             The optional C<$cont> parameter can be a Javascript function that
1373             will get applied to every result. This can be used to directly map
1374             each DOM node in the XPath result to an attribute. For example
1375             for efficiently fetching the text value of an XPath query resulting in
1376             textnodes, the two snippets are equivalent, but the latter executes
1377             less roundtrips between Perl and Javascript:
1378              
1379             my @text = map { $_->{nodeValue} }
1380             $obj->MozRepl::RemoteObject::Methods::xpath( '//p/text()' )
1381              
1382              
1383             my $fetch_nodeValue = $bridge->declare(<
1384             function (e){ return e.nodeValue }
1385             JS
1386             my @text = map { $_->{nodeValue} }
1387             $obj->MozRepl::RemoteObject::Methods::xpath( '//p/text()', undef, $fetch_nodeValue )
1388              
1389             =cut
1390              
1391             *__xpath = \&MozRepl::RemoteObject::Methods::xpath;
1392              
1393             =head2 C<< $obj->__click >>
1394              
1395             Sends a Javascript C event to the object.
1396              
1397             This is a convenience method that should only be called
1398             on HTMLdocument nodes or their children.
1399              
1400             =cut
1401              
1402             sub __click {
1403 0     0     my ($self, @args) = @_; # $self is a HTMLdocument or a descendant!
1404 0           $self->__event('click', @args);
1405             }
1406              
1407             =head2 C<< $obj->__change >>
1408              
1409             Sends a Javascript C event to the object.
1410              
1411             This is a convenience method that should only be called
1412             on HTMLdocument nodes or their children.
1413              
1414             =cut
1415              
1416             sub __change {
1417 0     0     my ($self) = @_; # $self is a HTMLdocument or a descendant!
1418 0           $self->__event('change');
1419             }
1420              
1421             =head2 C<< $obj->__event TYPE >>
1422              
1423             Sends a Javascript event of type C to the object.
1424              
1425             This is a convenience method that should only be called
1426             on HTMLdocument nodes or their children.
1427              
1428             =head3 Send a C, C and C event to an element
1429              
1430             The following code simulates the events sent by the
1431             user entering a value into a field:
1432              
1433             $elt->__event('focus');
1434             $elt->{value} = 'Hello';
1435             $elt->__event('change');
1436             $elt->__event('blur');
1437              
1438             =cut
1439              
1440             sub __event {
1441 0     0     my ($self,$type,@args) = @_;
1442 0           my $fn;
1443 0 0         if ($type eq 'click') {
1444 0           $fn = $self->bridge->declare(<<'JS');
1445             function(target,name,x,y) {
1446             if(!x) x= 0;
1447             if(!y) y= 0;
1448             var r= target.getBoundingClientRect();
1449             x+= r.left;
1450             y+= r.top;
1451             var d= target.ownerDocument;
1452             var container= d.defaultView || window;
1453             var event = d.createEvent('MouseEvents');
1454             event.initMouseEvent(name, true, true, container,
1455             null, 0, 0, x, y, false, false, false,
1456             false, 0, null);
1457             target.dispatchEvent(event);
1458             }
1459             JS
1460             } else {
1461 0           $fn = $self->bridge->declare(<<'JS');
1462             function(target,name) {
1463             var event = target.ownerDocument.createEvent('Events');
1464             event.initEvent(name, true, true);
1465             target.dispatchEvent(event);
1466             }
1467             JS
1468             };
1469 0           $fn->($self,$type,@args);
1470             };
1471              
1472             =head2 C<< MozRepl::RemoteObject::Instance->new( $bridge, $ID, $onDestroy ) >>
1473              
1474             This creates a new Perl object that's linked to the
1475             Javascript object C. You usually do not call this
1476             directly but use C<< $bridge->link_ids @IDs >>
1477             to wrap a list of Javascript ids with Perl objects.
1478              
1479             The C<$onDestroy> parameter should contain a Javascript
1480             string that will be executed when the Perl object is
1481             released.
1482             The Javascript string is executed in its own scope
1483             container with the following variables defined:
1484              
1485             =over 4
1486              
1487             =item *
1488              
1489             C - the linked object
1490              
1491             =item *
1492              
1493             C - the numerical Javascript object id of this object
1494              
1495             =item *
1496              
1497             C - the L Javascript C object
1498              
1499             =back
1500              
1501             This method is useful if you want to automatically
1502             close tabs or release other resources
1503             when your Perl program exits.
1504              
1505             =cut
1506              
1507             sub new {
1508 0     0     my ($package,$bridge, $id,$release_action) = @_;
1509             #warn "Created object $id";
1510 0           my $self = {
1511             id => $id,
1512             bridge => $bridge,
1513             release_action => $release_action,
1514             stats => {
1515             roundtrip => 0,
1516             fetch => 0,
1517             store => 0,
1518             callback => 0,
1519             },
1520             };
1521 0   0       bless $self, ref $package || $package;
1522             };
1523              
1524             package # don't index this on CPAN
1525             MozRepl::RemoteObject::TiedHash;
1526 26     26   24858 use strict;
  26         53  
  26         8616  
1527              
1528             sub TIEHASH {
1529 0     0     my ($package,$impl) = @_;
1530 0           my $tied = { impl => $impl };
1531 0           bless $tied, $package;
1532             };
1533              
1534             sub FETCH {
1535 0     0     my ($tied,$k) = @_;
1536 0           my $obj = $tied->{impl};
1537 0           $obj->__attr($k)
1538             };
1539              
1540             sub STORE {
1541 0     0     my ($tied,$k,$val) = @_;
1542 0           my $obj = $tied->{impl};
1543 0           $obj->__setAttr($k,$val);
1544             () # force __setAttr to return nothing
1545 0           };
1546              
1547             sub FIRSTKEY {
1548 0     0     my ($tied) = @_;
1549 0           my $obj = $tied->{impl};
1550 0   0       $tied->{__keys} ||= [$tied->{impl}->__keys()];
1551 0           $tied->{__keyidx} = 0;
1552 0           $tied->{__keys}->[ $tied->{__keyidx}++ ];
1553             };
1554              
1555             sub NEXTKEY {
1556 0     0     my ($tied,$lastkey) = @_;
1557 0           my $obj = $tied->{impl};
1558 0           $tied->{__keys}->[ $tied->{__keyidx}++ ];
1559             };
1560              
1561             sub EXISTS {
1562 0     0     my ($tied,$key) = @_;
1563 0           my $obj = $tied->{impl};
1564 0           my $exists = $obj->bridge->declare(<<'JS');
1565             function(elt,prop) {
1566             return (prop in elt && elt.hasOwnProperty(prop))
1567             }
1568             JS
1569 0           $exists->($obj,$key);
1570             }
1571              
1572             sub DELETE {
1573 0     0     my ($tied,$key) = @_;
1574 0           my $obj = $tied->{impl};
1575 0           my $delete = $obj->bridge->declare(<<'JS');
1576             function(elt,prop) {
1577             var r=elt[prop];
1578             delete elt[prop];
1579             return r
1580             }
1581             JS
1582 0           $delete->($obj,$key);
1583             }
1584              
1585             sub CLEAR {
1586 0     0     my ($tied,$key) = @_;
1587 0           my $obj = $tied->{impl};
1588 0           my $clear = $obj->bridge->declare(<<'JS');
1589             function(obj) {
1590             var del = [];
1591             for (var prop in obj) {
1592             if (obj.hasOwnProperty(prop)) {
1593             del.push(prop);
1594             };
1595             };
1596             for (var i=0;i
1597             delete obj[del[i]]
1598             };
1599             return del
1600             }
1601             JS
1602 0           $clear->($obj);
1603             };
1604              
1605             1;
1606              
1607             package # don't index this on CPAN
1608             MozRepl::RemoteObject::TiedArray;
1609 26     26   164 use strict;
  26         47  
  26         7338  
1610              
1611             sub TIEARRAY {
1612 0     0     my ($package,$impl) = @_;
1613 0           my $tied = { impl => $impl };
1614 0           bless $tied, $package;
1615             };
1616              
1617             sub FETCHSIZE {
1618 0     0     my ($tied) = @_;
1619 0           my $obj = $tied->{impl};
1620 0           $obj->{length};
1621             }
1622              
1623             sub FETCH {
1624 0     0     my ($tied,$k) = @_;
1625 0           my $obj = $tied->{impl};
1626 0           $obj->__attr($k)
1627             };
1628              
1629             sub STORE {
1630 0     0     my ($tied,$k,$val) = @_;
1631 0           my $obj = $tied->{impl};
1632 0           $obj->__setAttr($k,$val);
1633 0           (); # force void context on __setAttr
1634             };
1635              
1636             sub PUSH {
1637 0     0     my $tied = shift;
1638 0           my $obj = $tied->{impl};
1639 0           for (@_) {
1640 0           $obj->push($_);
1641             };
1642             };
1643              
1644             sub POP {
1645 0     0     my $tied = shift;
1646 0           my $obj = $tied->{impl};
1647 0           $obj->pop();
1648             };
1649              
1650             sub SPLICE {
1651 0     0     my ($tied,$from,$count) = (shift,shift,shift);
1652 0           my $obj = $tied->{impl};
1653 0   0       $from ||= 0;
1654 0   0       $count ||= $obj->{length};
1655 0           MozRepl::RemoteObject::as_list $obj->splice($from,$count,@_);
1656             };
1657              
1658             sub CLEAR {
1659 0     0     my $tied = shift;
1660 0           my $obj = $tied->{impl};
1661 0           $obj->splice(0,$obj->{length});
1662             ()
1663 0           };
1664              
1665       0     sub EXTEND {
1666             # we acknowledge the advice
1667             };
1668              
1669             1;
1670              
1671             __END__