package Firefox::Application; use strict; use MozRepl::RemoteObject (); use URI (); use Carp qw(carp croak); use vars qw'$VERSION'; $VERSION = '0.65'; =head1 NAME Firefox::Application - inspect and automate the Firefox UI =head1 SYNOPSIS use Firefox::Application; my $ff = Firefox::Application->new(); This module will let you automate Firefox through the Mozrepl plugin. You need to have installed that plugin in your Firefox. For more examples see L. =head1 METHODS =head2 C<< Firefox::Application->new( %args ) >> use Firefox::Application; my $ff = Firefox::Application->new(); Creates a new instance and connects it to Firefox. Note that Firefox must have the C extension installed and enabled. The following options are recognized: =over 4 =item * C - name of the program to launch if we can't connect to it on the first try. =item * C - array reference to log levels, passed through to L =item * C - L buffer size, if the default of 1MB is not enough =item * C - a premade L instance or a connection string suitable for initializing one. =item * C - whether to enable L command queueing =item * C - class for the API wrapper You almost never want to use this parameter, as Firefox::Application asks Firefox about its version. =back =cut sub new { my ($class, %args) = @_; my $loglevel = delete $args{ log } || [qw[ error ]]; my $use_queue = exists $args{ use_queue } ? delete $args{ use_queue } : 1; my $api = delete $args{ api }; if (! ref $args{ repl }) { my @passthrough = qw(repl js_JSON launch); my %options = map { exists $args{ $_ } ? ($_ => delete $args{ $_ }) : () } @passthrough; $args{ repl } = MozRepl::RemoteObject->install_bridge( log => $loglevel, use_queue => $use_queue, bufsize => delete $args{ bufsize }, %options, ); }; # Now install the proper API if (! $api) { my $info = $args{ repl }->appinfo; my $v = $info->{version}; $v =~ s!^(\d+.\d+).*!$1! or $v = '3.0'; # Wild guess if ($v >= 4) { $api = 'Firefox::Application::API40'; } elsif ($v >= 3.6) { $api = 'Firefox::Application::API36'; } else { $api = 'Firefox::Application::API35'; }; }; MozRepl::RemoteObject::require_module( $api ); bless \%args, $api; }; sub DESTROY { my ($self) = @_; local $@; #warn "App cleaning up"; if (my $repl = delete $self->{ repl }) { %$self = (); # wipe out all references we keep # but keep $repl alive until we can dispose of it # as the last thing, now: $repl = undef; }; #warn "App cleaned up"; } =head2 C<< $ff->repl >> my ($value,$type) = $ff->repl->expr('2+2'); Gets the L instance that is used. =cut sub repl { $_[0]->{repl} }; =head1 APPLICATION INFORMATION =cut =head2 C<< $ff->appinfo >> my $info = $ff->appinfo; print 'ID : ', $info->{ID}; print 'name : ', $info->{name}; print 'version : ', $info->{version}; Returns information about Firefox. =cut sub appinfo { $_[0]->repl->appinfo }; =head2 C<< $ff->addons( %args ) >> for my $addon ($ff->addons) { print sprintf "Name: %s\n", $addon->{name}; print sprintf "Version: %s\n", $addon->{version}; print sprintf "GUID: %s\n", $addon->{id}; }; Returns the list of installed addons as Cs (FF 3.5+) or Cs (FF4+). See L or L, depending on your Firefox version. =head2 C<< $ff->locales( %args ) >> for my $locale ($ff->locales) { print sprintf "Name: %s\n", $locale->{name}; print sprintf "Version: %s\n", $locale->{version}; print sprintf "GUID: %s\n", $locale->{id}; }; Returns the list of installed locales as Cs. =head2 C<< $ff->themes( %args ) >> for my $theme ($ff->themes) { print sprintf "Name: %s\n", $theme->{name}; print sprintf "Version: %s\n", $theme->{version}; print sprintf "GUID: %s\n", $theme->{id}; }; Returns the list of installed locales as Cs. =head2 C<< $ff->updateitems( %args ) >> for my $item ($ff->updateitems) { print sprintf "Name: %s\n", $item->{name}; print sprintf "Version: %s\n", $item->{version}; print sprintf "GUID: %s\n", $item->{id}; }; Returns the list of updateable items. The type of item can be restricted by the C option. =over 4 =item * C - type of items to fetch C - fetch any item C - fetch add-ons C - fetch locales C - fetch themes =back =cut sub profileService { my ($self) = @_; my $profileService = $self->repl->declare(<<'JS')->(); function () { return Components.classes["@mozilla.org/toolkit/profile-service;1"] .createInstance(Components.interfaces.nsIToolkitProfileService); } JS } =head2 C<< $ff->current_profile >> print $ff->current_profile->{name}, "\n"; Returns currently selected profile as C. See L. =cut sub current_profile { my ($self) = @_; $self->profileService->{selectedProfile} } =head2 C<< $ff->find_profile( $name ) >> print $ff->find_profile("")->{localDir}, "\n"; Returns the profile given its name. Dies if the profile cannot be found. =cut sub find_profile { my ($self,$name) = @_; $self->profileService->getProfileByName($name); } =head2 C<< $ff->profiles >> for ($ff->profiles) { print "Profile: ", $_->{name}, "\n"; } Lists the installed profiles as Cs. See L. =cut sub profiles { my ($self) = @_; my $getProfiles = $self->repl->declare(<<'JS', 'list'); function () { var toolkitProfileService = Components.classes["@mozilla.org/toolkit/profile-service;1"] .createInstance(Components.interfaces.nsIToolkitProfileService); var res = new Array; var i = toolkitProfileService.profiles; while( i.hasMoreElements() ) { res.push( i.getNext() ); }; return res } JS $getProfiles->() } =head1 UI METHODS =head2 C<< $ff->addTab( %options ) >> my $new = $ff->addTab(); Creates a new tab and returns it. The tab will be automatically closed upon program exit. If you want the tab to remain open, pass a false value to the the C< autoclose > option. The recognized options are: =over 4 =item * C - the repl to use. By default it will use C<< $ff->repl >>. =item * C - whether to automatically close the tab at program exit. Default is to close the tab. =back =cut =head2 C<< $ff->selectedTab( %options ) >> my $curr = $ff->selectedTab(); Sets the currently active tab. =cut =head2 C<< $ff->closeTab( $tab [,$repl] ) >> $ff->closeTab( $tab ); Close the given tab. =cut =head2 C<< $ff->openTabs( [$repl] ) >> my @tab_info = $ff->openTabs(); print "$_->{title}, $_->{location}, \n" for @tab_info; Returns a list of information about the currently open tabs. =cut =head2 C<< $ff->activateTab( [ $tab [, $repl ]] ) >> $ff->activateTab( $mytab ); # bring to foreground Activates the tab passed in. =cut sub activateTab { my ($self, $tab, $repl ) = @_; $repl ||= $self->repl; croak "No tab given" unless $tab; $self->browser( $repl )->{tabContainer}->{selectedItem} = $tab; }; =head2 C<< $ff->browser( [$repl] ) >> my $b = $ff->browser(); Returns the current Firefox browser instance, or opens a new browser window if none is available, and returns its browser instance. If you need to call this as a class method, pass in the L bridge to use. =cut sub browser { my ($self,$repl) = @_; $repl ||= $self->repl; return $repl->declare(<<'JS')->(); function() { var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); var win = wm.getMostRecentWindow('navigator:browser'); if (! win) { // No browser windows are open, so open a new one. win = window.open('about:blank'); }; return win.gBrowser // return win.getBrowser() } JS }; =head2 C<< $ff->set_tab_content( $tab, $html [,$repl] ) >> $ff->set_tab_content('

Hello

'); This is a more general method that allows you to replace the HTML of an arbitrary tab, and not only the tab that WWW::Mechanize::Firefox is associated with. It has the flaw of not waiting until the tab has loaded. =cut sub set_tab_content { my ($self, $tab, $content, $repl) = @_; my $url = URI->new('data:'); $url->media_type("text/html"); $url->data($content); $tab ||= $self->tab; $repl ||= $self->repl; $tab->{linkedBrowser}->loadURI("".$url); }; =head2 C<< $ff->quit( %options ) >> $ff->quit( restart => 1 ); # restart $ff->quit(); # quit Quits or restarts the application =cut sub quit { my ($self, %options) = @_; my $repl = $options{ repl } || $self->repl; my $flags = $options{ restart } ? 0x13 # force-quit : 0x03 # force-quit + restart ; my $get_startup = $repl->declare(<<'JS'); function() { return Components.classes["@mozilla.org/toolkit/app-startup;1"] .getService(Components.interfaces.nsIAppStartup); } JS my $s = $get_startup->(); $s->quit($flags); }; =head1 TODO =over 4 =item * Consider how to roll L into this module for convenient / versatile launching of Firefox =back =head1 AUTHOR Max Maischein C =head1 COPYRIGHT (c) Copyright 2009-2012 by Max Maischein C. =head1 LICENSE This module is released under the same terms as Perl itself. =cut 1;