File Coverage

blib/lib/Selenium/Firefox/Profile.pm
Criterion Covered Total %
statement 115 129 89.1
branch 18 30 60.0
condition 5 6 83.3
subroutine 26 27 96.3
pod 7 7 100.0
total 171 199 85.9


line stmt bran cond sub pod time code
1             package Selenium::Firefox::Profile;
2             $Selenium::Firefox::Profile::VERSION = '1.48';
3             # ABSTRACT: Use custom profiles with Selenium::Remote::Driver
4             # TODO: convert this to Moo!
5              
6 4     4   1084 use strict;
  4         9  
  4         116  
7 4     4   37 use warnings;
  4         22  
  4         116  
8              
9 4     4   708 use Archive::Zip qw( :ERROR_CODES );
  4         83850  
  4         560  
10 4     4   34 use Carp qw(croak);
  4         13  
  4         204  
11 4     4   27 use Cwd qw(abs_path);
  4         9  
  4         226  
12 4     4   25 use File::Copy qw(copy);
  4         23  
  4         240  
13 4     4   27 use File::Temp;
  4         29  
  4         387  
14 4     4   38 use File::Basename qw(dirname);
  4         9  
  4         267  
15 4     4   2201 use IO::Uncompress::Unzip 2.030 qw($UnzipError);
  4         157481  
  4         474  
16 4     4   714 use JSON qw(decode_json);
  4         10321  
  4         45  
17 4     4   997 use MIME::Base64;
  4         592  
  4         269  
18 4     4   24 use Scalar::Util qw(blessed looks_like_number);
  4         18  
  4         196  
19 4     4   3618 use XML::Simple;
  4         37969  
  4         27  
20              
21              
22              
23             sub new {
24 6     6 1 12340 my $class = shift;
25 6         41 my %args = @_;
26              
27 6         16 my $profile_dir;
28 6 100 66     49 if ( $args{profile_dir} && -d $args{profile_dir} ) {
29 1         7 $profile_dir = $args{profile_dir};
30             }
31             else {
32 5         43 $profile_dir = File::Temp->newdir();
33             }
34              
35             # TODO: accept user prefs, boolean prefs, and extensions in
36             # constructor
37 6         2806 my $self = {
38             profile_dir => $profile_dir,
39             user_prefs => {},
40             extensions => []
41             };
42 6 50       44 bless $self, $class or die "Can't bless $class: $!";
43              
44 6         38 return $self;
45             }
46              
47              
48             sub set_preference {
49 5     5 1 648 my ( $self, %prefs ) = @_;
50              
51 5         27 foreach ( keys %prefs ) {
52 9         19 my $value = $prefs{$_};
53 9         27 my $clean_value = '';
54              
55 9 100 100     43 if ( JSON::is_bool($value) ) {
    100          
56 2         15 $self->set_boolean_preference( $_, $value );
57 2         22 next;
58             }
59             elsif ( $value =~ /^(['"]).*\1$/ or looks_like_number($value) ) {
60              
61             # plain integers: 0, 1, 32768, or integers wrapped in strings:
62             # "0", "1", "20140204". in either case, there's nothing for us
63             # to do.
64 3         37 $clean_value = $value;
65             }
66             else {
67             # otherwise it's hopefully a string that we'll need to
68             # quote on our own
69 4         100 $clean_value = '"' . $value . '"';
70             }
71              
72 7         29 $self->{user_prefs}->{$_} = $clean_value;
73             }
74             }
75              
76              
77             sub set_boolean_preference {
78 3     3 1 608 my ( $self, %prefs ) = @_;
79              
80 3         10 foreach ( keys %prefs ) {
81 4         8 my $value = $prefs{$_};
82              
83 4 100       14 $self->{user_prefs}->{$_} = $value ? 'true' : 'false';
84             }
85             }
86              
87              
88             sub get_preference {
89 15     15 1 1880 my ( $self, $pref ) = @_;
90              
91 15         67 return $self->{user_prefs}->{$pref};
92             }
93              
94              
95             sub add_extension {
96 4     4 1 1435 my ( $self, $xpi ) = @_;
97              
98 4 100       459 croak 'File not found: ' . $xpi unless -e $xpi;
99 3         169 my $xpi_abs_path = abs_path($xpi);
100 3 100       123 croak '$xpi_abs_path: extensions must be in .xpi format'
101             unless $xpi_abs_path =~ /\.xpi$/;
102              
103 2         17 push( @{ $self->{extensions} }, $xpi_abs_path );
  2         10  
104             }
105              
106              
107             sub add_webdriver {
108 1     1 1 10 my ( $self, $port, $is_marionette ) = @_;
109              
110 1         6 my $current_user_prefs = $self->{user_prefs};
111              
112             $self->set_preference(
113             # having the user prefs here allows them to overwrite the
114             # mutable loaded prefs
115 1         2 %{$current_user_prefs},
  1         13  
116              
117             # but the frozen ones cannot be overwritten
118             'webdriver_firefox_port' => $port
119             );
120              
121 1 50       6 if ( !$is_marionette ) {
122 1         16 $self->_add_webdriver_xpi;
123             }
124              
125 1         9 return $self;
126             }
127              
128              
129              
130             sub _add_webdriver_xpi {
131 1     1   13 my ($self) = @_;
132              
133 1         249 my $this_dir = dirname( abs_path(__FILE__) );
134 1         6 my $webdriver_extension = $this_dir . '/webdriver.xpi';
135              
136 1         10 $self->add_extension($webdriver_extension);
137             }
138              
139              
140             sub add_marionette {
141 0     0 1 0 my ( $self, $port ) = @_;
142 0 0       0 return if !$port;
143 0         0 $self->set_preference( 'marionette.defaultPrefs.port', $port );
144             }
145              
146             sub _encode {
147 2     2   695 my $self = shift;
148              
149             # The remote webdriver accepts the Firefox profile as a base64
150             # encoded zip file
151 2         8 $self->_layout_on_disk();
152              
153 1         11 my $zip = Archive::Zip->new();
154 1         61 $zip->addTree( $self->{profile_dir} );
155              
156 1         9161 my $string = "";
157 1     1   99 open( my $fh, ">", \$string );
  1         28  
  1         6  
  1         28  
158 1         1235 binmode($fh);
159 1 50       16 unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
160 0         0 die 'write error';
161             }
162              
163 1         3846 return encode_base64( $string, '' );
164             }
165              
166             sub _layout_on_disk {
167 3     3   462 my $self = shift;
168              
169 3         16 $self->_write_preferences();
170 3         19 $self->_install_extensions();
171              
172 2         8 return $self->{profile_dir};
173             }
174              
175             sub _write_preferences {
176 3     3   9 my $self = shift;
177              
178 3         25 my $userjs = $self->{profile_dir} . "/user.js";
179 3 50       328 open( my $fh, ">>", $userjs )
180             or die "Cannot open $userjs for writing preferences: $!";
181              
182 3         12 foreach ( keys %{ $self->{user_prefs} } ) {
  3         19  
183 7         18 print $fh 'user_pref("'
184             . $_ . '", '
185             . $self->get_preference($_) . ');' . "\n";
186             }
187 3         94 close($fh);
188             }
189              
190             sub _install_extensions {
191 3     3   16 my $self = shift;
192 3         18 my $extension_dir = $self->{profile_dir} . "/extensions/";
193 3 50       261 mkdir $extension_dir unless -d $extension_dir;
194              
195             # TODO: handle extensions that need to be unpacked
196 3         12 foreach my $xpi ( @{ $self->{extensions} } ) {
  3         13  
197              
198             # For Firefox to recognize the extension, we have to put the
199             # .xpi in the /extensions/ folder and change the filename to
200             # its id, which is found in the install.rdf in the root of the
201             # zip.
202              
203 1         20 my $rdf_string = $self->_extract_install_rdf($xpi);
204 0         0 my $rdf = XMLin($rdf_string);
205 0         0 my $name = $rdf->{Description}->{'em:id'};
206              
207 0         0 my $xpi_dest = $extension_dir . $name . ".xpi";
208 0 0       0 copy( $xpi, $xpi_dest )
209             or croak "Error copying $_ to $xpi_dest : $!";
210             }
211             }
212              
213             sub _extract_install_rdf {
214 1     1   5 my ( $self, $xpi ) = @_;
215              
216 1 50       15 my $unzipped = IO::Uncompress::Unzip->new($xpi)
217             or die "Cannot unzip $xpi: $UnzipError";
218              
219 1         2012 my $install_rdf = '';
220 1         36 while ( $unzipped->nextStream ) {
221 0         0 my $filename = $unzipped->getHeaderInfo->{Name};
222 0 0       0 if ( $filename eq 'install.rdf' ) {
223 0         0 my $buffer;
224 0         0 while ( ( my $status = $unzipped->read($buffer) ) > 0 ) {
225 0         0 $install_rdf .= $buffer;
226             }
227 0         0 return $install_rdf;
228             }
229             }
230              
231             croak
232 1         495 'Invalid Firefox extension: could not find install.rdf in the .XPI at: '
233             . $xpi;
234             }
235              
236             1;
237              
238             __END__