MozRepl::RemoteObject - treat Javascript objects as Perl objects
#!perl -w use strict; use MozRepl::RemoteObject; # use $ENV{MOZREPL} or localhost:4242 my $repl = MozRepl::RemoteObject->install_bridge(); # get our root object: my $tab = $repl->expr(<<JS); window.getBrowser().addTab() JS # Now use the object: my $body = $tab->{linkedBrowser} ->{contentWindow} ->{document} ->{body} ; $body->{innerHTML} = "<h1>Hello from MozRepl::RemoteObject</h1>"; $body->{innerHTML} =~ '/Hello from/' and print "We stored the HTML"; $tab->{linkedBrowser}->loadURI('http://corion.net/');
MozRepl::RemoteObject->install_bridge %options
Installs the Javascript <-> Perl bridge. If you pass in an existing MozRepl instance, it must have MozRepl::Plugin::JSON2 loaded.
<->
If repl is not passed in, $ENV{MOZREPL} will be used to find the ip address and portnumber to connect to. If $ENV{MOZREPL} is not set, the default of localhost:4242 will be used.
repl
$ENV{MOZREPL}
localhost:4242
If repl is not a reference, it will be used instead of $ENV{MOZREPL}.
To replace the default JSON parser, you can pass it in using the json option.
json
repl - a premade MozRepl instance to use, or alternatively a connection string to use
use_queue - whether to queue destructors until the next command. This reduces the latency and amount of queries sent via MozRepl by half, at the cost of a bit delayed release of objects on the remote side. The release commands get queued until the next "real" command gets sent through MozRepl.
use_queue
launch - the command line to launch the program that runs mozrepl.
launch
mozrepl
If you want to connect to a Firefox instance on a different machine, call ->install_bridge as follows:
->install_bridge
MozRepl::RemoteObject->install_bridge( repl => "$remote_machine:4242" );
If you want to pass in a preconfigured MozRepl object, call ->install_bridge as follows:
my $repl = MozRepl->new; $repl->setup({ log => [qw/ error info /], plugins => { plugins => [qw[ JSON2 ]] }, }); my $bridge = MozRepl::RemoteObject->install_bridge(repl => $repl);
If you want to launch Firefox if it's not already running, call ->install_bridge as follows:
MozRepl::RemoteObject->install_bridge( launch => 'iceweasel' # that program must be in the path );
By default the launched program will be launched with the -repl command line switch to start up mozrepl. If you need to provide the full command line, pass an array reference to the launch option:
-repl
MozRepl::RemoteObject->install_bridge( launch => ['iceweasel','-repl','666'] );
$bridge->expr( $js, $context )
Runs the Javascript passed in through $js and links the returned result to a Perl object or a plain value, depending on the type of the Javascript result.
$js
This is how you get at the initial Javascript object in the object forest.
my $window = $bridge->expr('window'); print $window->{title};
You can also create Javascript functions and use them from Perl:
my $add = $bridge->expr(<<JS); function (a,b) { return a+b } JS print $add->(2,3);
The context parameter allows you to specify that you expect a Javascript array and want it to be returned as list. To do that, specify 'list' as the $context parameter:
context
'list'
$context
for ($bridge->expr(<<JS,'list')) { print $_ }; [1,2,3,4] JS
as_list( $array )
for $_ in (as_list $array) { print $_->{innerHTML},"\n"; };
Efficiently fetches all elements from @$array . This is functionally equivalent to writing
@$array
except that it involves much less roundtrips between Javascript and Perl.
$bridge->declare( $js, $context )
Shortcut to declare anonymous JS functions that will be cached in the bridge. This allows you to use anonymous functions in an efficient manner from your modules while keeping the serialization features of MozRepl::RemoteObject:
my $js = <<'JS'; function(a,b) { return a+b } JS my $fn = $self->bridge->declare($js); $fn->($a,$b);
The function $fn will remain declared on the Javascript side until the bridge is torn down.
$fn
If you expect an array to be returned and want the array to be fetched as list, pass 'list' as the $context.
$bridge->appinfo()
Returns the nsIXULAppInfo object so you can inspect what application the bridge is connected to:
nsIXULAppInfo
my $info = $bridge->appinfo(); print $info->{name}, "\n"; print $info->{version}, "\n"; print $info->{ID}, "\n";
$bridge->js_call_to_perl_struct $js
Takes a scalar with JS code, executes it, and returns the result as a Perl structure.
This will not (yet?) cope with objects on the remote side, so you will need to make sure to call $rn.link() on all objects that are to persist across the bridge.
$rn.link()
This is a very low level method. You are better advised to use $bridge->expr() as that will know to properly wrap objects but leave other values alone.
$bridge->expr()
$bridge->remove_callback $callback
If you want to remove a callback that you instated, this is the way.
This will release the resources associated with the callback on both sides of the bridge.
$bridge->poll
A crude no-op that can be used to just look if new events have arrived.
All MozRepl::RemoteObject objects implement transparent hash access through overloading, which means that accessing $document->{body} will return the wrapped document.body object.
$document->{body}
document.body
This is usually what you want when working with Javascript objects from Perl.
Setting hash keys will try to set the respective property in the Javascript object, but always as a string value, numerical values are not supported.
Accessing an object as an array will mainly work. For determining the length, it is assumed that the object has a .length method. If the method has a different name, you will have to access the object as a hash with the index as the key.
length
.length
Note that push expects the underlying object to have a .push() Javascript method, and pop gets mapped to the .pop() Javascript method.
push
.push()
pop
.pop()
Object identity is currently implemented by overloading the == operator. Two objects are considered identical if the javascript === operator returns true.
==
===
my $obj_a = MozRepl::RemoteObject->expr('window.document'); print $obj_a->__id(),"\n"; # 42 my $obj_b = MozRepl::RemoteObject->expr('window.document'); print $obj_b->__id(), "\n"; #43 print $obj_a == $obj_b; # true
Calling methods on a Javascript object is supported.
All arguments will be autoquoted if they contain anything other than ASCII digits ([0-9]). There currently is no way to specify that you want an all-digit parameter to be put in between double quotes.
[0-9]
Passing MozRepl::RemoteObject objects as parameters in Perl passes the proxied Javascript object as parameter to the Javascript method.
As in Javascript, functions are first class objects, the following two methods of calling a function are equivalent:
$window->loadURI('http://search.cpan.org/'); $window->{loadURI}->('http://search.cpan.org/');
This module also implements a rudimentary asynchronous event dispatch mechanism. Basically, it allows you to write code like this and it will work:
$window->addEventListener('load', sub { my ($event) = @_; print "I got a " . $event->{type} . " event\n"; print "on " . $event->{originalTarget}; }); # do other things...
Note that you cannot block the execution of Javascript that way. The Javascript code has long continued running when you receive the event.
Currently, only busy-waiting is implemented and there is no way yet for Javascript to tell Perl it has something to say. So in absence of a real mainloop, you have to call
$repl->poll;
from time to time to look for new events. Note that any call to Javascript will carry all events back to Perl and trigger the handlers there, so you only need to use poll if no other activity happens.
In the long run, a move to AnyEvent would make more sense, but currently, MozRepl::RemoteObject is still under heavy development on many fronts so that has been postponed.
$obj->__invoke(METHOD, ARGS)
The ->__invoke() object method is an alternate way to invoke Javascript methods. It is normally equivalent to $obj->$method(@ARGS). This function must be used if the METHOD name contains characters not valid in a Perl variable name (like foreign language characters). To invoke a Javascript objects native __invoke method (if such a thing exists), please use:
->__invoke()
$obj->$method(@ARGS)
__invoke
$object->__invoke('__invoke', @args);
The same method can be used to call the Javascript functions with the same name as other convenience methods implemented by this package:
__attr __setAttr __xpath __click ...
$obj->__transform_arguments(@args)
This method transforms the passed in arguments to their JSON string representations.
Things that match /^(?:[1-9][0-9]*|0+)$/ get passed through.
/^(?:[1-9][0-9]*|0+)$/
MozRepl::RemoteObject::Instance instances are transformed into strings that resolve to their Javascript global variables. Use the ->expr method to get an object representing these.
->expr
It's also impossible to pass a negative or fractional number as a number through to Javascript, or to pass digits as a Javascript string.
$obj->__id
Readonly accessor for the internal object id that connects the Javascript object to the Perl object.
$obj->__on_destroy
Accessor for the callback that gets invoked from DESTROY.
DESTROY
$obj->bridge
Readonly accessor for the bridge that connects the Javascript object to the Perl object.
$obj->__release_action
Accessor for Javascript code that gets executed when the Perl object gets released.
$obj->__attr( $attribute )
Read-only accessor to read the property of a Javascript object.
$obj->__attr('foo')
is identical to
$obj->{foo}
$obj->__setAttr( $attribute, $value )
Write accessor to set a property of a Javascript object.
$obj->__setAttr('foo', 'bar')
$obj->{foo} = 'bar'
$obj->__dive( @PATH )
Convenience method to quickly dive down a property chain.
If any element on the path is missing, the method dies with the error message which element was not found.
This method is faster than descending through the object forest with Perl, but otherwise identical.
my $obj = $tab->{linkedBrowser} ->{contentWindow} ->{document} ->{body} my $obj = $tab->__dive(qw(linkedBrowser contentWindow document body));
$obj->__keys()
Returns the names of all properties of the javascript object as a list.
keys %$obj
$obj->__values()
Returns the values of all properties as a list.
$obj->values()
values %$obj
$obj->__xpath( $query [, $ref ] )
Executes an XPath query and returns the node snapshot result as a list.
This is a convenience method that should only be called on HTMLdocument nodes.
$obj->__click
Sends a Javascript click event to the object.
click
This is a convenience method that should only be called on HTMLdocument nodes or their children.
$obj->__change
Sends a Javascript change event to the object.
change
$obj->__event TYPE
Sends a Javascript event of type TYPE to the object.
TYPE
focus
blur
The following code simulates the events sent by the user entering a value into a field:
$elt->__event('focus'); $elt->{value} = 'Hello'; $elt->__event('change'); $elt->__event('blur');
MozRepl::RemoteObject::Instance->new( $bridge, $ID, $onDestroy )
This creates a new Perl object that's linked to the Javascript object ID. You usually do not call this directly but use $bridge->link_ids @IDs to wrap a list of Javascript ids with Perl objects.
ID
$bridge->link_ids @IDs
The onDestroy parameter should contain a Javascript string that will be executed when the Perl object is released. The Javascript string is executed in its own scope container with the following variables defined:
onDestroy
self - the linked object
self
id - the numerical Javascript object id of this object
id
repl - the MozRepl Javascript repl object
This method is useful if you want to automatically close tabs or release other resources when your Perl program exits.
$obj->__as_hash()
$obj->__as_array()
$obj->__as_code()
Returns a reference to a hash/array/coderef. This is used by overload. Don't use these directly.
The communication with the MozRepl plugin is done through 7bit safe ASCII. The received bytes are supposed to be UTF-8, but this seems not always to be the case, so the JSON encoder on the Javascript side also uses a 7bit safe encoding.
Currently there is no way to specify a different encoding on the fly. You have to replace or reconfigure the JSON object in the constructor.
For tests that connect to the outside world, check/ask whether we're allowed to. If running automated, skip.
Think more about how to handle object identity. Should Scalar::Util::refaddr return true whenever the Javascript === operator returns true?
Scalar::Util::refaddr
Also see http://perlmonks.org/?node_id=802912
Consider whether MozRepl actually always delivers UTF-8 as output.
Properly encode all output that gets send towards MozRepl into the proper encoding.
Can we find a sensible implementation of string overloading for JS objects? Should it be the respective JS object type?
Add truely lazy objects that don't allocate their JS counterparts until an __attr() is requested or a method call is made.
__attr()
This is an optimization and hence gets postponed.
Potentially do away with attaching to the repl object and keep all elements as anonymous functions referenced only by Perl variables.
This would have the advantage of centralizing the value wrapping/unwrapping in one place, __invoke, and possibly also in __as_code. It would also keep the precompiled JS around instead of recompiling it on every access.
__as_code
repl.wrapResults would have to be handed around in an interesting manner then though.
repl.wrapResults
Add proper event wrappers and find a mechanism to send such events.
Having __click() is less than desireable. Maybe blindly adding the click() method is preferrable.
__click()
click()
Implement fetching of more than one property at once through __attr()
Implement automatic reblessing of JS objects into Perl objects based on a typemap instead of blessing everything into MozRepl::RemoteObject::Instance.
Find out how to make MozRepl actively send responses instead of polling for changes.
This would lead to implementing a full two-way message bus.
repl.print() can create arbitrary output, but Net::Telnet is not prepared to consume it.
repl.print()
On the Javascript side, yield can be used to implement continuations in a way that could maybe allow us to "suspend" the currently executing Javascript callback to introduce synchronous callbacks from Javascript into Perl.
yield
Consider using/supporting AnyEvent for better compatibility with other mainloops.
Consider implementing a mozrepl "interactor" to remove the prompting of mozrepl alltogether. Interactors only exist in the development releases of mozrepl.
Should I make room for promises as well?
my ($foo,$bar); $bridge->transaction(sub { $foo = $obj->promise; $bar = $obj2->promise; });
The JS could instantiate another level of proxy objects that would have to get filled by a batch of JS statements sent from Perl to fill in all those promises.
$bridge->promise( 'window' ) could return sub { $bridge->expr('window') }
but that wouldn't allow for coalescing these promises into Javascript.
Create synchronous Javascript callbacks by blocking the current FireFox thread. This shouldn't block the rest of FireFox:
/** * Netscape compatible WaitForDelay function. * You can use it as an alternative to Thread.Sleep() in any major programming language * that support it while JavaScript it self doesn't have any built-in function to do such a thing. * parameters: * (Number) delay in millisecond */ function nsWaitForDelay(delay) { /** * Just uncomment this code if you're building an extention for Firefox. * Since FF3, we'll have to ask for user permission to execute XPCOM objects. */ // netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); // Get the current thread. var thread = Components.classes["@mozilla.org/thread-manager;1"].getService(Components.interfaces.nsIThreadManager).currentThread; // Create an inner property to be used later as a notifier. this.delayed = true; /* Call JavaScript setTimeout function * to execute this.delayed = false * after it finish. */ setTimeout("this.delayed = false;", delay); /** * Keep looping until this.delayed = false */ while (this.delayed) { /** * This code will not freeze your browser as it's documented in here: * https://developer.mozilla.org/en/Code_snippets/Threads#Waiting_for_a_background_task_to_complete */ thread.processNextEvent(true); } }
Win32::OLE for another implementation of proxy objects
http://wiki.github.com/bard/mozrepl - the MozRepl FireFox plugin homepage
The public repository of this module is http://github.com/Corion/mozrepl-remoteobject.
Max Maischein corion@cpan.org
corion@cpan.org
Copyright 2009-2010 by Max Maischein corion@cpan.org.
This module is released under the same terms as Perl itself.
To install MozRepl::RemoteObject, copy and paste the appropriate command in to your terminal.
cpanm
cpanm MozRepl::RemoteObject
CPAN shell
perl -MCPAN -e shell install MozRepl::RemoteObject
For more information on module installation, please visit the detailed CPAN module installation guide.