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.47';
3             # ABSTRACT: Use custom profiles with Selenium::Remote::Driver
4             # TODO: convert this to Moo!
5              
6 4     4   917 use strict;
  4         7  
  4         100  
7 4     4   18 use warnings;
  4         9  
  4         103  
8              
9 4     4   624 use Archive::Zip qw( :ERROR_CODES );
  4         77604  
  4         434  
10 4     4   28 use Carp qw(croak);
  4         6  
  4         185  
11 4     4   21 use Cwd qw(abs_path);
  4         9  
  4         179  
12 4     4   22 use File::Copy qw(copy);
  4         5  
  4         198  
13 4     4   22 use File::Temp;
  4         7  
  4         270  
14 4     4   23 use File::Basename qw(dirname);
  4         7  
  4         231  
15 4     4   1772 use IO::Uncompress::Unzip 2.030 qw($UnzipError);
  4         133524  
  4         415  
16 4     4   757 use JSON qw(decode_json);
  4         9694  
  4         51  
17 4     4   996 use MIME::Base64;
  4         723  
  4         271  
18 4     4   24 use Scalar::Util qw(blessed looks_like_number);
  4         7  
  4         184  
19 4     4   2916 use XML::Simple;
  4         31132  
  4         27  
20              
21              
22              
23             sub new {
24 6     6 1 9363 my $class = shift;
25 6         22 my %args = @_;
26              
27 6         9 my $profile_dir;
28 6 100 66     44 if ( $args{profile_dir} && -d $args{profile_dir} ) {
29 1         5 $profile_dir = $args{profile_dir};
30             }
31             else {
32 5         32 $profile_dir = File::Temp->newdir();
33             }
34              
35             # TODO: accept user prefs, boolean prefs, and extensions in
36             # constructor
37 6         2290 my $self = {
38             profile_dir => $profile_dir,
39             user_prefs => {},
40             extensions => []
41             };
42 6 50       37 bless $self, $class or die "Can't bless $class: $!";
43              
44 6         21 return $self;
45             }
46              
47              
48             sub set_preference {
49 5     5 1 425 my ( $self, %prefs ) = @_;
50              
51 5         17 foreach ( keys %prefs ) {
52 9         18 my $value = $prefs{$_};
53 9         16 my $clean_value = '';
54              
55 9 100 100     31 if ( JSON::is_bool($value) ) {
    100          
56 2         12 $self->set_boolean_preference( $_, $value );
57 2         18 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         27 $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         88 $clean_value = '"' . $value . '"';
70             }
71              
72 7         25 $self->{user_prefs}->{$_} = $clean_value;
73             }
74             }
75              
76              
77             sub set_boolean_preference {
78 3     3 1 391 my ( $self, %prefs ) = @_;
79              
80 3         7 foreach ( keys %prefs ) {
81 4         5 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 1210 my ( $self, $pref ) = @_;
90              
91 15         54 return $self->{user_prefs}->{$pref};
92             }
93              
94              
95             sub add_extension {
96 4     4 1 941 my ( $self, $xpi ) = @_;
97              
98 4 100       419 croak 'File not found: ' . $xpi unless -e $xpi;
99 3         145 my $xpi_abs_path = abs_path($xpi);
100 3 100       86 croak '$xpi_abs_path: extensions must be in .xpi format'
101             unless $xpi_abs_path =~ /\.xpi$/;
102              
103 2         5 push( @{ $self->{extensions} }, $xpi_abs_path );
  2         12  
104             }
105              
106              
107             sub add_webdriver {
108 1     1 1 15 my ( $self, $port, $is_marionette ) = @_;
109              
110 1         4 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         9  
116              
117             # but the frozen ones cannot be overwritten
118             'webdriver_firefox_port' => $port
119             );
120              
121 1 50       8 if ( !$is_marionette ) {
122 1         10 $self->_add_webdriver_xpi;
123             }
124              
125 1         4 return $self;
126             }
127              
128              
129              
130             sub _add_webdriver_xpi {
131 1     1   4 my ($self) = @_;
132              
133 1         191 my $this_dir = dirname( abs_path(__FILE__) );
134 1         4 my $webdriver_extension = $this_dir . '/webdriver.xpi';
135              
136 1         4 $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   430 my $self = shift;
148              
149             # The remote webdriver accepts the Firefox profile as a base64
150             # encoded zip file
151 2         11 $self->_layout_on_disk();
152              
153 1         17 my $zip = Archive::Zip->new();
154 1         52 $zip->addTree( $self->{profile_dir} );
155              
156 1         6299 my $string = "";
157 1     1   85 open( my $fh, ">", \$string );
  1         25  
  1         5  
  1         24  
158 1         1144 binmode($fh);
159 1 50       18 unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
160 0         0 die 'write error';
161             }
162              
163 1         3509 return encode_base64( $string, '' );
164             }
165              
166             sub _layout_on_disk {
167 3     3   261 my $self = shift;
168              
169 3         13 $self->_write_preferences();
170 3         16 $self->_install_extensions();
171              
172 2         6 return $self->{profile_dir};
173             }
174              
175             sub _write_preferences {
176 3     3   5 my $self = shift;
177              
178 3         10 my $userjs = $self->{profile_dir} . "/user.js";
179 3 50       210 open( my $fh, ">>", $userjs )
180             or die "Cannot open $userjs for writing preferences: $!";
181              
182 3         12 foreach ( keys %{ $self->{user_prefs} } ) {
  3         14  
183 7         17 print $fh 'user_pref("'
184             . $_ . '", '
185             . $self->get_preference($_) . ');' . "\n";
186             }
187 3         79 close($fh);
188             }
189              
190             sub _install_extensions {
191 3     3   7 my $self = shift;
192 3         15 my $extension_dir = $self->{profile_dir} . "/extensions/";
193 3 50       176 mkdir $extension_dir unless -d $extension_dir;
194              
195             # TODO: handle extensions that need to be unpacked
196 3         10 foreach my $xpi ( @{ $self->{extensions} } ) {
  3         16  
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         11 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   7 my ( $self, $xpi ) = @_;
215              
216 1 50       14 my $unzipped = IO::Uncompress::Unzip->new($xpi)
217             or die "Cannot unzip $xpi: $UnzipError";
218              
219 1         1562 my $install_rdf = '';
220 1         19 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         364 'Invalid Firefox extension: could not find install.rdf in the .XPI at: '
233             . $xpi;
234             }
235              
236             1;
237              
238             __END__