The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Firefox::Application::API40;
use strict;
use parent 'Firefox::Application';
use vars qw($VERSION %addon_types);
use MozRepl::RemoteObject qw(as_list);
$VERSION = '0.73';

=head1 NAME

Firefox::Application::API40 - API wrapper for Firefox 4+

=head1 SYNOPSIS

    use Firefox::Application;
    my $ff = Firefox::Application->new(
        # Force the Firefox 4 API
        api => 'Firefox::Application::API40',
    );

=head1 METHODS

=head2 C<< $api->updateitems( %args ) >>

  for my $item ($api->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. Under Firefox 4,
can be restricted by the C<type> option.

=over 4

=item * C<type> - type of items to fetch

C<ANY> - fetch any item

C<ADDON> - fetch extensions

C<LOCALE> - fetch locales

C<THEME> - fetch themes

=back

This method is asynchronous in Firefox 4, but is run
synchronously by Firefox::Application, polling every 0.1s.
Currently, no special support for AnyEvent is implemented.

=cut

%addon_types = (
    ADDON => 'extension',
    THEME => 'theme',
);

sub updateitems {
    my ($self, %options) = @_;
    my $repl = delete $options{ repl } || $self->repl;
    my $type = $options{type} || $options{ ANY };
    
    my $done;
    my @res;
    my $cb = sub {
        my ($addons) = @_;
        $done++; # This should be $cv->send ...
        @res = @$addons;
    };
    
    my $addons_js = $repl->declare(sprintf( <<'JS', $type), 'list');
    function(types,cb) {
        Components.utils.import("resource://gre/modules/AddonManager.jsm");
        AddonManager.getAddonsByTypes(types, cb);
        return 1;
   };
JS
    $addons_js->([$type], $cb);
    while (! $done) {
        # AnyEvent!
        $self->repl->poll;
    };
    @res
};

=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 C<Addon>s.
See <https://developer.mozilla.org/en/Addons/Add-on_Manager/Addon>
depending.

=cut

sub addons {
    my $self = shift;
    $self->updateitems(type => 'extension', @_);
};

sub themes {
    my $self = shift;
    $self->updateitems(type => 'theme', @_);
};

sub locales {
    my $self = shift;
    $self->updateitems(type => 'locale', @_);
};

=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.

The Firefox 4 API is asynchronous. The method is forced
into a synchronous call here.

=cut

sub addTab {
    my ($self, %options) = @_;
    my $repl = $options{ repl } || $self->repl;

    my $tab = $self->browser( $repl )->addTab();

    if (not exists $options{ autoclose } or $options{ autoclose }) {
        $self->autoclose_tab($tab)
    };
    
    $tab
};

sub closeTab {
    my ($self,$tab,$repl) = @_;
    $repl ||= $self->repl;
    my $close_tab = $repl->declare(<<'JS');
function(tab) {
          if(tab.collapsed) { return };
          var be = Components.classes["@mozilla.org/appshell/window-mediator;1"]
	                     .getService(Components.interfaces.nsIWindowMediator)
	                     .getEnumerator("navigator:browser");
	  while (be.hasMoreElements()) {
	    var browserWin = be.getNext();
	    var tabbrowser = browserWin.gBrowser;
	    if( tabbrowser ) {
	      for( var i=0; i< tabbrowser.tabs.length; i++) {
	          if( tabbrowser.tabs.item( i ) === tab ) {
                      tabbrowser.removeTab(tab);
                      break;
                  };
              };
	    };
          };
}
JS
    return $close_tab->($tab);
}

sub autoclose_tab {
    my ($self,$tab,$close) = @_;
    $close = 1
        if( 2 == @_ );
    my $release = join "\n",
          # Find the window our tab lives in
          q<if(!self.collapsed){>,
              q<var be = Components.classes["@mozilla.org/appshell/window-mediator;1"]>,
                                 q<.getService(Components.interfaces.nsIWindowMediator)>,
                                 q<.getEnumerator("navigator:browser");>,
              q<while (be.hasMoreElements()) {>,
                q<var browserWin = be.getNext();>,
                q<var tabbrowser = browserWin.gBrowser;>,
                q<if( tabbrowser ) {>,
                  q!for( var i=0; i< tabbrowser.tabs.length; i++) {!,
                      q<if( tabbrowser.tabs.item( i ) === self ) {>,
                          q<tabbrowser.removeTab(self);>,
                          q<break;>,
                      q<};>,
                  q<};>,
                q<};>,
              q<};>,
        q<};>,
    ;
    if( $close ) {
        $tab->__release_action($release);
    } else {
        $tab->__release_action('');
    };
};
=head2 C<< $ff->selectedTab( %options ) >>

    my $curr = $ff->selectedTab();

Sets the currently active tab.

=cut

sub selectedTab {
    my ($self,%options) = @_;
    my $repl = delete $options{ repl } || $self->repl;
    return $self->browser( $repl )->{selectedTab};
}

sub openTabs {
    my ($self,$repl) = @_;
    $repl ||= $self->repl;
    my $open_tabs = $repl->declare(<<'JS', 'list');
function() {
    var idx = 0;
    var tabs = [];
    
    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                       .getService(Components.interfaces.nsIWindowMediator);
    var en = wm.getEnumerator('navigator:browser');
    while( en.hasMoreElements() ) {
        var win= en.getNext();
        var tabbrowser = win.gBrowser;
        var numTabs = tabbrowser.browsers.length;
        for (var index = 0; index < numTabs; index++) {
            var tab = tabbrowser.tabContainer.childNodes[index];
            var d = tab.linkedBrowser.contentWindow.document;
            tabs.push({
                location: d.location.href,
                document: d,
                title:    d.title,
                "id":     d.id,
                index:    idx++,
                panel:    tab.linkedPanel,
                tab:      tab,
            });
        };
    };

    return tabs;
}
JS
    $open_tabs->();
}

=head2 C<< $api->element_query( \@elements, \%attributes ) >>

    my $query = $element_query(['input', 'select', 'textarea'],
                               { name => 'foo' });

Returns the XPath query that searches for all elements with C<tagName>s
in C<@elements> having the attributes C<%attributes>. The C<@elements>
will form an C<or> condition, while the attributes will form an C<and>
condition.

=cut

sub element_query {
    my ($self, $elements, $attributes) = @_;
        my $query = 
            './/*[(' . 
                join( ' or ',
                    map {
                        sprintf qq{local-name(.)="%s"}, lc $_
                    } @$elements
                )
            . ') and '
            . join( " and ",
                map { sprintf q{@%s="%s"}, $_, $attributes->{$_} }
                  sort keys(%$attributes)
            )
            . ']';
};

1;

=head1 AUTHOR

Max Maischein C<corion@cpan.org>

=head1 COPYRIGHT (c)

Copyright 2009-2012 by Max Maischein C<corion@cpan.org>.

=head1 LICENSE

This module is released under the same terms as Perl itself.

=cut