Firefox-Marionette

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

     - Adding --issuer-organization, --trusted-only and --profile-name options to ./ca-bundle-for-firefox
     - Hiding bookmarks toolbar

1.28  Mon Jun 13 11:54 2022
     - Adding ./setup-for-firefox-marionette-build.sh to remove the snap firefox for Ubuntu 22.04 TLS.

1.27  Sun May 08 21:38 2022
     - Fixing missing META information

1.26  Sun May 01 06:31 2022
     - Adding support for about:config prefs.js dynamic changes

1.25  Mon Apr 25 09:32 2022
     - Adding X11 Forwarding and support for ssh jump hosts
     - Reduce network and disk load during 'make test'
     - Improving test suite (coverage now > 90%)

1.24  Fri Apr  8 19:32 2022
     - Fixes for CPAN Testers results
     - Adding visible support for remote Firefox instances on linux (via xvfb-run)
     - Test suite changes for darwin

lib/Firefox/Marionette.pm  view on Meta::CPAN


sub languages {
    my ( $self, @new_languages ) = @_;
    my $pref_name = 'intl.accept_languages';
    my $script =
'return navigator.languages || branch.getComplexValue(arguments[0], Components.interfaces.nsIPrefLocalizedString).data.split(/,\s*/)';
    my $old           = $self->_context('chrome');
    my @old_languages = @{
        $self->script(
            $self->_compress_script(
                $self->_prefs_interface_preamble() . $script
            ),
            args => [$pref_name]
        )
    };
    $self->_context($old);
    if ( scalar @new_languages ) {
        $self->set_pref( $pref_name, join q[, ], @new_languages );
    }
    return @old_languages;
}

lib/Firefox/Marionette.pm  view on Meta::CPAN

									"speed": response["coords"]["speed"],
									}; }).catch((err) => { throw err.message });
})();
_JS_
    if ( ( defined $result ) && ( !ref $result ) ) {
        Firefox::Marionette::Exception->throw("javascript error: $result");
    }
    return $result;
}

sub _prefs_interface_preamble {
    my ($self) = @_;
    return <<'_JS_';    # modules/libpref/nsIPrefService.idl
let prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
let branch = prefs.getBranch("");
_JS_
}

sub get_pref {
    my ( $self, $name ) = @_;
    my $script = <<'_JS_';
let result = [ null ];
switch (branch.getPrefType(arguments[0])) {
  case branch.PREF_STRING:
    result = [ branch.getStringPref ? branch.getStringPref(arguments[0]) : branch.getComplexValue(arguments[0], Components.interfaces.nsISupportsString).data, 'string' ];

lib/Firefox/Marionette.pm  view on Meta::CPAN

    break;
  case branch.PREF_BOOL:
    result = [ branch.getBoolPref(arguments[0]), 'boolean' ];
}
return result;
_JS_
    my $old = $self->_context('chrome');
    my ( $result, $type ) = @{
        $self->script(
            $self->_compress_script(
                $self->_prefs_interface_preamble() . $script
            ),
            args => [$name]
        )
    };
    $self->_context($old);
    if ($type) {
        if ( $type eq 'integer' ) {
            $result += 0;
        }
    }

lib/Firefox/Marionette.pm  view on Meta::CPAN

      branch.setStringPref(arguments[0], arguments[1]);
    } else {
      let newString = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
      newString.data = arguments[1];
      branch.setComplexValue(arguments[0], Components.interfaces.nsISupportsString, newString);
    }
}
_JS_
    my $old = $self->_context('chrome');
    $self->script(
        $self->_compress_script( $self->_prefs_interface_preamble() . $script ),
        args => [ $name, $value ]
    );
    $self->_context($old);
    return $self;
}

sub _clear_data_service_interface_preamble {
    my ($self) = @_;
    return <<'_JS_';    # toolkit/components/cleardata/nsIClearDataService.idl
let clearDataService = Components.classes["@mozilla.org/clear-data-service;1"].getService(Components.interfaces.nsIClearDataService);

lib/Firefox/Marionette.pm  view on Meta::CPAN

    return $self;
}

sub clear_pref {
    my ( $self, $name ) = @_;
    my $script = <<'_JS_';
branch.clearUserPref(arguments[0]);
_JS_
    my $old = $self->_context('chrome');
    $self->script(
        $self->_compress_script( $self->_prefs_interface_preamble() . $script ),
        args => [$name]
    );
    $self->_context($old);
    return $self;
}

sub _is_chrome_user_agent {
    my ( $self, $user_agent ) = @_;
    if ( $user_agent =~ /Chrome/smx ) {
        return 1;

lib/Firefox/Marionette.pm  view on Meta::CPAN

                $self->{_ssh_local_directory}   = $ssh_local_directory;
                $self->{_root_directory}        = $proxy->{ssh}->{root};
                $self->{_remote_root_directory} = $proxy->{ssh}->{root};

                if ( defined $proxy->{ssh}->{tmp} ) {
                    $self->{_original_remote_tmp_directory} =
                      $proxy->{ssh}->{tmp};
                }
                $self->{profile_path} =
                  $self->_remote_catfile( $self->{_root_directory},
                    'profile', 'prefs.js' );
                my $local_scp_directory =
                  File::Spec->catdir( $self->ssh_local_directory(), 'scp' );
                $self->{_local_scp_get_directory} =
                  File::Spec->catdir( $local_scp_directory, 'get' );
                $self->{_scp_get_file_index} =
                  $self->_get_max_scp_file_index(
                    $self->{_local_scp_get_directory} );

                $self->{_local_scp_put_directory} =
                  File::Spec->catdir( $local_scp_directory, 'put' );

lib/Firefox/Marionette.pm  view on Meta::CPAN

            push @{ $self->{mime_types} }, $mime_type;
            $known_mime_types{$mime_type} = 1;
        }
    }
    return;
}

sub _check_for_existing_local_firefox_process {
    my ($self) = @_;
    my $profile_path =
      File::Spec->catfile( $self->{_profile_directory}, 'prefs.js' );
    my $profile_handle = FileHandle->new($profile_path);
    my $port;
    if ($profile_handle) {
        while ( my $line = <$profile_handle> ) {
            if ( $line =~ /^user_pref[(]"marionette[.]port",[ ](\d+)[)];$/smx )
            {
                ($port) = ($1);
            }
        }
    }

lib/Firefox/Marionette.pm  view on Meta::CPAN

        "Failed to close directory '$temp_directory':$EXTENDED_OS_ERROR");
    return $alive_pid;
}

sub _setup_profile {
    my ($self) = @_;
    if ( $self->{profile_name} ) {
        $self->{_profile_directory} =
          Firefox::Marionette::Profile->directory( $self->{profile_name} );
        $self->{profile_path} =
          File::Spec->catfile( $self->{_profile_directory}, 'prefs.js' );
    }
    else {
        $self->{_profile_directory} =
          File::Spec->catfile( $self->{_root_directory}, 'profile' );
        $self->{_download_directory} =
          File::Spec->catfile( $self->{_root_directory}, 'downloads' );
        $self->{profile_path} =
          File::Spec->catfile( $self->{_profile_directory}, 'prefs.js' );
    }
    return;
}

sub _reconnect {
    my ( $self, %parameters ) = @_;
    if ( $parameters{profile_name} ) {
        $self->{profile_name} = $parameters{profile_name};
    }
    $self->{_reconnected} = 1;

lib/Firefox/Marionette.pm  view on Meta::CPAN

          _MILLISECONDS_IN_ONE_SECOND(),
        implicit  => $timeouts->implicit(),
        page_load => $timeouts->page_load()
    );
    $self->timeouts($update_timeouts);
    my $old = $self->_context('chrome');

    # toolkit/mozapps/update/nsIUpdateService.idl
    my $update_parameters = $self->script(
        $self->_compress_script(
            $self->_prefs_interface_preamble() . <<'_JS_' ) );
let disabledForTesting = branch.getBoolPref("app.update.disabledForTesting");
branch.setBoolPref("app.update.disabledForTesting", false);
let updateManager = new Promise((resolve, reject) => {
  var updateStatus = {};
  if ("@mozilla.org/updates/update-manager;1" in Components.classes) {
    let PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
    let PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
      Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
    }
    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
      Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
    }
    let updateService = Components.classes["@mozilla.org/updates/update-service;1"].getService(Components.interfaces.nsIApplicationUpdateService);
    let latestUpdate = null;
    if (!updateService.canCheckForUpdates) {
      updateStatus["updateStatusCode"] = 'CANNOT_CHECK_FOR_UPDATES';
      reject(updateStatus);
    }
    if (!updateService.canApplyUpdates) {
      updateStatus["updateStatusCode"] = 'CANNOT_APPLY_UPDATES';
      reject(updateStatus);

lib/Firefox/Marionette.pm  view on Meta::CPAN

        else {
            $profile_ini_directory =
              $self->_remote_catfile( '.mozilla', 'firefox' );
        }
        my $profile_ini_path =
          $self->_remote_catfile( $profile_ini_directory, 'profiles.ini' );
        my $handle = $self->_get_file_via_scp( { ignore_exit_status => 1 },
            $profile_ini_path, 'profiles.ini file' )
          or Firefox::Marionette::Exception->throw( 'Failed to find the file '
              . $self->_ssh_address()
              . ":$profile_ini_path which would indicate where the prefs.js file for the '$profile_name' is stored"
          );
        my $config = Config::INI::Reader->read_handle($handle);
        $profile_directory = $self->_remote_catfile(
            Firefox::Marionette::Profile->directory(
                $profile_name,          $config,
                $profile_ini_directory, $self->_ssh_address()
            )
        );
    }
    return $profile_directory;

lib/Firefox/Marionette.pm  view on Meta::CPAN

            '-profile',    $self->_restart_profile_directory(),
            '--no-remote', '--new-instance'
          );
    }
    elsif ( $parameters{profile_name} ) {
        $self->{profile_name} = $parameters{profile_name};
        if ( $self->_ssh() ) {
            $self->{_profile_directory} =
              $self->_get_remote_profile_directory( $parameters{profile_name} );
            $self->{profile_path} =
              $self->_remote_catfile( $self->{_profile_directory}, 'prefs.js' );
        }
        else {
            $self->{_profile_directory} =
              Firefox::Marionette::Profile->directory(
                $parameters{profile_name} );
            $self->{profile_path} =
              File::Spec->catfile( $self->{_profile_directory}, 'prefs.js' );
        }
        push @arguments, ( '-P', $self->{profile_name} );
    }
    else {
        my $profile_directory =
          $self->_setup_new_profile( $parameters{profile}, %parameters );
        if ( $self->_ssh() ) {
            if ( $self->_remote_uname() eq 'cygwin' ) {
                $profile_directory =
                  $self->_execute_via_ssh( {}, 'cygpath', '-s', '-m',

lib/Firefox/Marionette.pm  view on Meta::CPAN

            "Failed to create directory $tmp_directory:$EXTENDED_OS_ERROR");
    }
    return;
}

sub _new_profile_path {
    my ($self) = @_;
    my $profile_path;
    if ( $self->_ssh() ) {
        $profile_path =
          $self->_remote_catfile( $self->{_profile_directory}, 'prefs.js' );
    }
    else {
        $profile_path =
          File::Spec->catfile( $self->{_profile_directory}, 'prefs.js' );
    }
    return $profile_path;
}

sub _setup_new_profile {
    my ( $self, $profile, %parameters ) = @_;
    $self->_setup_profile_directories($profile);
    $self->{profile_path} = $self->_new_profile_path();
    if ($profile) {
        if ( !$profile->download_directory() ) {

lib/Firefox/Marionette/Profile.pm  view on Meta::CPAN

        if ( defined $config->{$key}->{Name} ) {
            push @names, $config->{$key}->{Name};
        }
    }
    return @names;
}

sub path {
    my ( $class, $name ) = @_;
    if ( my $profile_directory = $class->directory($name) ) {
        return File::Spec->catfile( $profile_directory, 'prefs.js' );
    }
    return;
}

sub _parse_config_for_path {
    my ( $class, $name, $config, $profile_ini_directory ) = @_;
    my @path;
    my $first_key;
    foreach my $key ( sort { $a cmp $b } keys %{$config} ) {
        if ( ( !defined $first_key ) && ( defined $config->{$key}->{Name} ) ) {

lib/Firefox/Marionette/Profile.pm  view on Meta::CPAN

        $profile->set_value( 'privacy.fingerprintingProtection', 'true', 0 );
        $profile->set_value( 'privacy.fingerprintingProtection.pbmode',
            'true', 0 );
        $profile->set_value( 'privacy.trackingprotection.enabled', 'true', 0 );
        $profile->set_value(
            'privacy.trackingprotection.fingerprinting.enabled',
            'true', 0 );
        $profile->set_value( 'privacy.trackingprotection.pbmode.enabled',
            'false', 0 );
        $profile->set_value( 'profile.enable_profile_migration', 'false', 0 );
        $profile->set_value( 'services.sync.prefs.sync.browser.search.update',
            'false', 0 );
        $profile->set_value(
'services.sync.prefs.sync.privacy.trackingprotection.cryptomining.enabled',
            'false', 0
        );
        $profile->set_value(
            'services.sync.prefs.sync.privacy.trackingprotection.enabled',
            'false', 0 );
        $profile->set_value(
'services.sync.prefs.sync.privacy.trackingprotection.fingerprinting.enabled',
            'false', 0
        );
        $profile->set_value(
'services.sync.prefs.sync.privacy.trackingprotection.pbmode.enabled',
            'false', 0
        );
        $profile->set_value( 'signon.rememberSignons', 'false', 0 );
        $profile->set_value( 'signon.management.page.breach-alerts.enabled',
            'false', 0 );
        $profile->set_value( 'toolkit.telemetry.archive.enabled', 'false', 0 );
        $profile->set_value( 'toolkit.telemetry.enabled',         'false', 0 );
        $profile->set_value( 'toolkit.telemetry.rejected',        'true',  0 );
        $profile->set_value( 'toolkit.telemetry.server',          q[],     1 );
        $profile->set_value( 'toolkit.telemetry.unified',         'false', 0 );

lib/Firefox/Marionette/Profile.pm  view on Meta::CPAN

    }
    return $self;

}

1;    # Magic true value required at end of module
__END__

=head1 NAME

Firefox::Marionette::Profile - Represents a prefs.js Firefox Profile

=head1 VERSION

Version 1.70

=head1 SYNOPSIS

    use Firefox::Marionette();
    use v5.10;

lib/Firefox/Marionette/Profile.pm  view on Meta::CPAN


        # OR start a new browser with a copy of a specific existing profile

        $profile = Firefox::Marionette::Profile->existing($profile_name);
        $firefox = Firefox::Marionette->new(profile => $profile);
        $firefox->quit();
    }

=head1 DESCRIPTION

This module handles the implementation of a C<prefs.js> Firefox Profile

=head1 CONSTANTS

=head2 ANY_PORT

returns the port number for Firefox to listen on any port (0).

=head1 SUBROUTINES/METHODS

=head2 new

lib/Firefox/Marionette/Profile.pm  view on Meta::CPAN

=head2 names

returns a list of existing profile names that this module can discover on the filesystem.

=head2 default_name

returns the default profile name.

=head2 directory

accepts a profile name and returns the directory path that contains the C<prefs.js> file.

=head2 download_directory

accepts a directory path that will contain downloaded files.  Returns the previous value for download directory.

=head2 existing

accepts a profile name and returns a L<profile|Firefox::Marionette::Profile> object for that specified profile name.

=head2 parse

accepts a path as the parameter.  This path should be to a C<prefs.js> file.  Parses the file and returns it as a L<profile|Firefox::Marionette::Profile>.

=head2 parse_by_handle

accepts a filehandle as the parameter to a C<prefs.js> file.  Parses the file and returns it as a L<profile|Firefox::Marionette::Profile>.

=head2 path

accepts a profile name and returns the corresponding path to the C<prefs.js> file.

=head2 profile_ini_directory

returns the base directory for profiles.

=head2 save

accepts a path as the parameter.  Saves the current profile to this location.

=head2 as_string

lib/Firefox/Marionette/Profile.pm  view on Meta::CPAN

=item C<< Failed to rename '%s' to '%s':%s >>
 
The module was unable to rename the named file to the second file.  Something is seriously wrong with your environment.

=item C<< Failed to open '%s' for reading:%s >>
 
The module was unable to open the named file.  Maybe your disk is full or the file permissions need to be changed?

=item C<< Failed to parse line '%s' >>
 
The module was unable to parse the line for a Firefox prefs.js configuration.  This is probably a bug in this module's logic.  Please report as described in the BUGS AND LIMITATIONS section below.

=back

=head1 CONFIGURATION AND ENVIRONMENT

Firefox::Marionette::Profile requires no configuration files or environment variables.

=head1 DEPENDENCIES

Firefox::Marionette::Profile requires the following non-core Perl modules

lib/Waterfox/Marionette/Profile.pm  view on Meta::CPAN

    }

    return $profile;
}

1;    # Magic true value required at end of module
__END__

=head1 NAME

Waterfox::Marionette::Profile - Represents a prefs.js Waterfox Profile

=head1 VERSION

Version 1.70

=head1 SYNOPSIS

    use Waterfox::Marionette();
    use v5.10;

lib/Waterfox/Marionette/Profile.pm  view on Meta::CPAN


        # OR start a new browser with a copy of a specific existing profile

        $profile = Waterfox::Marionette::Profile->existing($profile_name);
        $firefox = Waterox::Marionette->new(profile => $profile);
        $firefox->quit();
    }

=head1 DESCRIPTION

This module handles the implementation of a C<prefs.js> Waterfox Profile.  This module inherits from L<Firefox::Marionette::Profile|Firefox::Marionette::Profile>.

=head1 SUBROUTINES/METHODS

For a full list of methods available, see L<Firefox::Marionette::Profile|Firefox::Marionette::Profile#SUBROUTINES/METHODS>

=head2 new

returns a new L<profile|Waterfox::Marionette::Profile>.

=head2 profile_ini_directory

ssh-auth-cmd-marionette  view on Meta::CPAN

    %options = _validate_parameters(%options);
    my ( $allowed_binary_directories_regex,
        $allowed_binary_paths_regex, $allowed_binary_regex )
      = _filesystem_regexes(%options);
    my $sub_directory_regex = qr/(?:profile|downloads|tmp|addons|certs)/smx;
    my $profile_names       = q[(?:] . (
        join q[|],
        map { quotemeta } (
            qw(
              bookmarks.html
              prefs.js
              mimeTypes.rdf
              search.json.mozlz4
            )
        )
    ) . q[)];
    my $profile_file_regex    = qr/profile\/$profile_names/smx;
    my $file_regex            = qr/[+\w\-()]{1,255}(?:[.][+\w\-()]{1,255})*/smx;
    my $downloads_regex       = qr/downloads\/$file_regex/smx;
    my $addons_regex          = qr/(?:addons|profile)\/$file_regex/smx;
    my $ca_name_regex         = qr/Firefox::Marionette[ ]Root[ ]CA/smx;

ssh-auth-cmd-marionette  view on Meta::CPAN

      qr/(?:[ ]\-height[ ]\d{1,8})?/smx,
      qr/(?:[ ]\-\-jsconsole)?/smx,
      qr/(?:[ ]\-MOZ_LOG=[[:alnum:],:]+)?/smx,
      qr/(?:[ ]-safe\-mode)?/smx,
      qr/(?:[ ]\-headless)?/smx,
      qr/[ ](?:\-profile[ ]$root_dir_regex\/profile|\-P[ ][[:alnum:]]+)/smx,
      qr/(?:[ ]\-\-no\-remote)?/smx,
      qr/(?:[ ]\-\-new\-instance)?/smx,
      qr/(?:[ ]\-\-devtools)?/smx,
      qr/(?:[ ]\-\-kiosk)?/smx;
    my $prefs_grep_patterns_regex = join q[],
      qr/\-e[ ]marionette[ ]/smx,
      qr/\-e[ ]security[ ]/smx;
    my @darwin_regexes;

    if ( $OSNAME eq 'darwin' ) {
        my $plist_prefix_regex =
          _get_plist_prefix_regex( @{ $options{'allow-binary'} } );
        @darwin_regexes = (
            qr/ls[ ]-1[ ]"$allowed_binary_regex"/smx,
qr/plutil[ ]-convert[ ]json[ ]-o[ ]-[ ]"(?:$plist_prefix_regex)\/Info[.]plist"/smx,

ssh-auth-cmd-marionette  view on Meta::CPAN

qr/scp(?:[ ]\-v)?[ ]\-p[ ]\-[tf][ ]"?$root_dir_regex\/$downloads_regex"?/smx,
      qr/scp[ ]\-p[ ]\-[tf][ ]"?$profiles_ini_regex"?/smx,
      qr/kill[ ]\-0[ ]\d{1,8}/smx,
      qr/which[ ]$allowed_binary_regex/smx,
      qr/readlink[ ]\-f[ ]$allowed_binary_paths_regex/smx,
qr/rm[ ]\-Rf[ ]$root_dir_regex(?:[ ]$quoted_tmp_directory\/Temp\-[\d\-a-f]{1,255})*/smx,
qr/ls[ ]-1[ ]"$allowed_binary_directories_regex(?:\/updates(?:\/\d+)?)?"/smx,
      qr/ls[ ]-1[ ]"$root_dir_regex\/downloads"/smx,
      qr/certutil$certutil_arguments_regex/smx,
      qr/(?:$xvfb_regex)?"$allowed_binary_regex"$firefox_arguments_regex/smx,
      qr/grep[ ]$prefs_grep_patterns_regex$profile_path_regex\/prefs[.]js/smx;

    my $user_name = getpwuid $EFFECTIVE_USER_ID;
    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ m/^($allowed_commands_regex)$/smx ) {
        my ($command_and_arguments) = ($1);
        if ( $options{'force-binary'} ) {
            $command_and_arguments =~
              s/^"$allowed_binary_regex"/"$options{'force-binary'}"/smx;
        }
        Sys::Syslog::openlog( $ident, 'cons', $options{facility} );
        Sys::Syslog::syslog( Sys::Syslog::LOG_INFO(),

t/stub.pl  view on Meta::CPAN

	my $browser_version = "112.0.2";
	if ($options{version}) {
                $| = 1;
		print "Mozilla Firefox $browser_version\n";
		exit 0;
	}
	socket my $server, Socket::PF_INET(), Socket::SOCK_STREAM(), 0 or die "Failed to create a socket:$!";
	bind $server, Socket::sockaddr_in( 0, Socket::INADDR_LOOPBACK() ) or die "Failed to bind socket:$!";
	listen $server, Socket::SOMAXCONN() or die "Failed to listen:$!";
	my $port = ( Socket::sockaddr_in( getsockname $server ) )[0];
	my $prefs_path = File::Spec->catfile($options{profile}, 'prefs.js');
	my $old_prefs_handle = FileHandle->new($prefs_path, Fcntl::O_RDONLY()) or die "Failed to open $prefs_path for reading:$!";
	my $new_prefs_path = File::Spec->catfile($options{profile}, 'prefs.new');
	my $new_prefs_handle = FileHandle->new($new_prefs_path, Fcntl::O_CREAT() | Fcntl::O_EXCL() | Fcntl::O_WRONLY(), Fcntl::S_IRUSR() | Fcntl::S_IWUSR()) or die "Failed to open $new_prefs_path for writing:$!";
	while(my $line = <$old_prefs_handle>) {
		if ($line =~ /^user_pref\("marionette.port",[ ]0\);/smx) {
			print {$new_prefs_handle} qq[user_pref("marionette.port", $port);\n] or die "Failed to write to $new_prefs_path:$!";	
		} else {
			print {$new_prefs_handle} $line or die "Failed to write to $new_prefs_path:$!";	
		}
	}
	close $new_prefs_handle or die "Failed to close $new_prefs_path:$!";
	close $old_prefs_handle or die "Failed to close $prefs_path:$!";
	rename $new_prefs_path, $prefs_path or die "Failed to rename $new_prefs_path to $prefs_path:$!";
	my $paddr = accept(my $client, $server);
	my $old = select $client; $| = 1; select $old;
	syswrite $client, qq[50:{"applicationType":"gecko","marionetteProtocol":3}] or die "Failed to write to socket:$!";
	my $request = _get_request($client);
	my $platform = $^O;
	my $headless = $options{headless} ? 'true' : 'false';
	my $response_type = 1;
	my $profile_path = $options{profile};
	$profile_path =~ s/\\/\\\\/smxg;
	my $capabilities = qq([1,1,null,{"sessionId":"5a5f9a08-0faa-4794-aa85-ee85980ce422","capabilities":{"browserName":"firefox","browserVersion":"$browser_version","platformName":"$platform","acceptInsecureCerts":false,"pageLoadStrategy":"normal","setWi...



( run in 1.610 second using v1.01-cache-2.11-cpan-0bb4e1dffa6 )