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.49';
3             # ABSTRACT: Use custom profiles with Selenium::Remote::Driver
4             # TODO: convert this to Moo!
5              
6 4     4   1137 use strict;
  4         9  
  4         122  
7 4     4   21 use warnings;
  4         11  
  4         109  
8              
9 4     4   728 use Archive::Zip qw( :ERROR_CODES );
  4         85724  
  4         490  
10 4     4   32 use Carp qw(croak);
  4         18  
  4         193  
11 4     4   32 use Cwd qw(abs_path);
  4         8  
  4         199  
12 4     4   27 use File::Copy qw(copy);
  4         10  
  4         219  
13 4     4   25 use File::Temp;
  4         16  
  4         328  
14 4     4   30 use File::Basename qw(dirname);
  4         14  
  4         273  
15 4     4   2155 use IO::Uncompress::Unzip 2.030 qw($UnzipError);
  4         158256  
  4         411  
16 4     4   679 use JSON qw(decode_json);
  4         10184  
  4         52  
17 4     4   966 use MIME::Base64;
  4         750  
  4         282  
18 4     4   32 use Scalar::Util qw(blessed looks_like_number);
  4         10  
  4         200  
19 4     4   3504 use XML::Simple;
  4         40020  
  4         28  
20              
21              
22              
23             sub new {
24 6     6 1 12534 my $class = shift;
25 6         23 my %args = @_;
26              
27 6         13 my $profile_dir;
28 6 100 66     46 if ( $args{profile_dir} && -d $args{profile_dir} ) {
29 1         6 $profile_dir = $args{profile_dir};
30             }
31             else {
32 5         50 $profile_dir = File::Temp->newdir();
33             }
34              
35             # TODO: accept user prefs, boolean prefs, and extensions in
36             # constructor
37 6         2735 my $self = {
38             profile_dir => $profile_dir,
39             user_prefs => {},
40             extensions => []
41             };
42 6 50       51 bless $self, $class or die "Can't bless $class: $!";
43              
44 6         29 return $self;
45             }
46              
47              
48             sub set_preference {
49 5     5 1 675 my ( $self, %prefs ) = @_;
50              
51 5         30 foreach ( keys %prefs ) {
52 9         19 my $value = $prefs{$_};
53 9         26 my $clean_value = '';
54              
55 9 100 100     48 if ( JSON::is_bool($value) ) {
    100          
56 2         15 $self->set_boolean_preference( $_, $value );
57 2         21 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         46 $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         34 $self->{user_prefs}->{$_} = $clean_value;
73             }
74             }
75              
76              
77             sub set_boolean_preference {
78 3     3 1 607 my ( $self, %prefs ) = @_;
79              
80 3         10 foreach ( keys %prefs ) {
81 4         8 my $value = $prefs{$_};
82              
83 4 100       15 $self->{user_prefs}->{$_} = $value ? 'true' : 'false';
84             }
85             }
86              
87              
88             sub get_preference {
89 15     15 1 1988 my ( $self, $pref ) = @_;
90              
91 15         63 return $self->{user_prefs}->{$pref};
92             }
93              
94              
95             sub add_extension {
96 4     4 1 1387 my ( $self, $xpi ) = @_;
97              
98 4 100       431 croak 'File not found: ' . $xpi unless -e $xpi;
99 3         161 my $xpi_abs_path = abs_path($xpi);
100 3 100       113 croak '$xpi_abs_path: extensions must be in .xpi format'
101             unless $xpi_abs_path =~ /\.xpi$/;
102              
103 2         9 push( @{ $self->{extensions} }, $xpi_abs_path );
  2         10  
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         12 %{$current_user_prefs},
  1         6  
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         5 $self->_add_webdriver_xpi;
123             }
124              
125 1         4 return $self;
126             }
127              
128              
129              
130             sub _add_webdriver_xpi {
131 1     1   7 my ($self) = @_;
132              
133 1         206 my $this_dir = dirname( abs_path(__FILE__) );
134 1         12 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   705 my $self = shift;
148              
149             # The remote webdriver accepts the Firefox profile as a base64
150             # encoded zip file
151 2         14 $self->_layout_on_disk();
152              
153 1         13 my $zip = Archive::Zip->new();
154 1         58 $zip->addTree( $self->{profile_dir} );
155              
156 1         9039 my $string = "";
157 1     1   115 open( my $fh, ">", \$string );
  1         26  
  1         12  
  1         32  
158 1         1514 binmode($fh);
159 1 50       21 unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
160 0         0 die 'write error';
161             }
162              
163 1         4307 return encode_base64( $string, '' );
164             }
165              
166             sub _layout_on_disk {
167 3     3   449 my $self = shift;
168              
169 3         13 $self->_write_preferences();
170 3         18 $self->_install_extensions();
171              
172 2         7 return $self->{profile_dir};
173             }
174              
175             sub _write_preferences {
176 3     3   6 my $self = shift;
177              
178 3         17 my $userjs = $self->{profile_dir} . "/user.js";
179 3 50       277 open( my $fh, ">>", $userjs )
180             or die "Cannot open $userjs for writing preferences: $!";
181              
182 3         13 foreach ( keys %{ $self->{user_prefs} } ) {
  3         18  
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   9 my $self = shift;
192 3         26 my $extension_dir = $self->{profile_dir} . "/extensions/";
193 3 50       232 mkdir $extension_dir unless -d $extension_dir;
194              
195             # TODO: handle extensions that need to be unpacked
196 3         11 foreach my $xpi ( @{ $self->{extensions} } ) {
  3         18  
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         25 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   6 my ( $self, $xpi ) = @_;
215              
216 1 50       11 my $unzipped = IO::Uncompress::Unzip->new($xpi)
217             or die "Cannot unzip $xpi: $UnzipError";
218              
219 1         2199 my $install_rdf = '';
220 1         26 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         426 'Invalid Firefox extension: could not find install.rdf in the .XPI at: '
233             . $xpi;
234             }
235              
236             1;
237              
238             __END__