Firefox-Marionette

 view release on metacpan or  search on metacpan

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

}

sub BY_ID {
    Carp::carp(
'**** DEPRECATED METHOD - using find(..., BY_ID()) HAS BEEN REPLACED BY find_id ****'
    );
    return 'id';
}

sub BY_NAME {
    Carp::carp(
'**** DEPRECATED METHOD - using find(..., BY_NAME()) HAS BEEN REPLACED BY find_name ****'
    );
    return 'name';
}

sub BY_TAG {
    Carp::carp(
'**** DEPRECATED METHOD - using find(..., BY_TAG()) HAS BEEN REPLACED BY find_tag ****'
    );
    return 'tag name';
}

sub BY_CLASS {
    Carp::carp(
'**** DEPRECATED METHOD - using find(..., BY_CLASS()) HAS BEEN REPLACED BY find_class ****'
    );
    return 'class name';
}

sub BY_SELECTOR {
    Carp::carp(
'**** DEPRECATED METHOD - using find(..., BY_SELECTOR()) HAS BEEN REPLACED BY find_selector ****'
    );
    return 'css selector';
}

sub BY_LINK {
    Carp::carp(
'**** DEPRECATED METHOD - using find(..., BY_LINK()) HAS BEEN REPLACED BY find_link ****'
    );
    return 'link text';
}

sub BY_PARTIAL {
    Carp::carp(
'**** DEPRECATED METHOD - using find(..., BY_PARTIAL()) HAS BEEN REPLACED BY find_partial ****'
    );
    return 'partial link text';
}

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;
}

sub _setup_trackable {
    my ( $self, $trackable ) = @_;
    my $value = $trackable ? 0 : 1;
    $self->set_pref( 'privacy.fingerprintingProtection',        $value );
    $self->set_pref( 'privacy.fingerprintingProtection.pbmode', $value );
    return $self;
}

sub _setup_geo {
    my ( $self, $geo ) = @_;
    $self->set_pref( 'geo.enabled',                   1 );
    $self->set_pref( 'geo.provider.use_geoclue',      0 );
    $self->set_pref( 'geo.provider.use_corelocation', 0 );
    $self->set_pref( 'geo.provider.testing',          1 );
    $self->set_pref( 'geo.prompt.testing',            1 );
    $self->set_pref( 'geo.prompt.testing.allow',      1 );
    $self->set_pref( 'geo.security.allowinsecure',    1 );
    $self->set_pref( 'geo.wifi.scan',                 1 );
    $self->set_pref( 'permissions.default.geo',       1 );

    if ( ref $geo ) {
        if ( ( Scalar::Util::blessed($geo) ) && ( $geo->isa('URI') ) ) {
            $self->geo( $self->json($geo) );
        }
        else {
            $self->geo($geo);
        }
    }
    elsif ( $geo =~ /^(?:data|http)/smx ) {
        $self->geo( $self->json($geo) );
    }
    return $self;
}

sub tz {
    my ( $self, $timezone ) = @_;
    require Firefox::Marionette::Extension::Timezone;
    my %parameters = ( timezone => $timezone );
    $self->script(
        $self->_compress_script(
            Firefox::Marionette::Extension::Timezone->timezone_contents(
                %parameters)
        )
    );
    if ( $self->{timezone_extension} ) {
        $self->uninstall( delete $self->{timezone_extension} );
    }
    my $zip = Firefox::Marionette::Extension::Timezone->new(%parameters);
    $self->{timezone_extension} =

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

}

sub geo {
    my ( $self, @parameters ) = @_;

    my $location;
    if ( scalar @parameters ) {
        $location = Firefox::Marionette::GeoLocation->new(@parameters);
    }
    if ( defined $location ) {
        $self->set_pref( 'geo.provider.network.url',
            q[data:application/json,]
              . JSON->new()->convert_blessed()->encode($location) );
        $self->set_pref( 'geo.wifi.uri',
            q[data:application/json,]
              . JSON->new()->convert_blessed()->encode($location) );
        if ( my $ipgeolocation_timezone = $location->tz() ) {
            $self->tz($ipgeolocation_timezone);
        }
        return $self;
    }
    if ( my $geo_location = $self->_get_geolocation() ) {
        my $new_location = Firefox::Marionette::GeoLocation->new($geo_location);
        return $new_location;
    }
    return;
}

sub _get_geolocation {
    my ($self) = @_;

    my $result = $self->script( $self->_compress_script(<<'_JS_') );
return (async function() {
  function getGeo() {
    return new Promise((resolve, reject) => {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(resolve, reject, { maximumAge: 0, enableHighAccuracy: true });
      } else {
        reject("navigator.geolocation is unavailable");
      }
    })
  };
  return await getGeo().then((response) => { let d = new Date(); return {
									"timezone_offset": d.getTimezoneOffset(),
									"latitude": response["coords"]["latitude"],
									"longitude": response["coords"]["longitude"],
									"altitude": response["coords"]["altitude"],
									"accuracy": response["coords"]["accuracy"],
									"altitudeAccuracy": response["coords"]["altitudeAccuracy"],
									"heading": response["coords"]["heading"],
									"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' ];
    break;
  case branch.PREF_INT:
    result = [ branch.getIntPref(arguments[0]), 'integer' ];
    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;
        }
    }
    return $result;
}

sub set_pref {
    my ( $self, $name, $value ) = @_;
    my $script = <<'_JS_';
switch (branch.getPrefType(arguments[0])) {
  case branch.PREF_INT:
    branch.setIntPref(arguments[0], arguments[1]);
    break;
  case branch.PREF_BOOL:
    branch.setBoolPref(arguments[0], arguments[1] ? true : false);
    break;
  case branch.PREF_STRING:
  default:
    if (branch.setStringPref) {
      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);
_JS_
}

sub cache_keys {
    my ($self) = @_;
    my @names;
    foreach my $name (@Firefox::Marionette::Cache::EXPORT_OK) {
        if ( defined $self->check_cache_key($name) ) {
            push @names, $name;
        }
    }
    return @names;
}

sub check_cache_key {
    my ( $self, $name ) = @_;
    my $class = ref $self;
    defined $name
      or Firefox::Marionette::Exception->throw(
        "$class->check_cache_value() must be passed an argument.");
    $name =~ /^[[:upper:]_]+$/smx
      or Firefox::Marionette::Exception->throw(
"$class->check_cache_key() must be passed an argument consisting of uppercase characters and underscores."
      );
    my $script = <<"_JS_";
if (typeof clearDataService.$name === undefined) {
  return;
} else {
  return clearDataService.$name;
}
_JS_
    my $old    = $self->_context('chrome');
    my $result = $self->script(
        $self->_compress_script(
            $self->_clear_data_service_interface_preamble() . $script
        )
    );
    $self->_context($old);
    return $result;
}

sub clear_cache {
    my ( $self, $flags ) = @_;
    $flags = defined $flags ? $flags : Firefox::Marionette::Cache::CLEAR_ALL();
    my $script = <<'_JS_';
let argument_flags = arguments[0];
let clearCache = function(flags) {
  return new Promise((resolve) => {
    clearDataService.deleteData(flags, function() { resolve(); });
  })};
let result = (async function() {
  let awaitResult = await clearCache(argument_flags);
  return awaitResult;
})();
return arguments[0];
_JS_
    my $old    = $self->_context('chrome');
    my $result = $self->script(
        $self->_compress_script(
            $self->_clear_data_service_interface_preamble() . $script
        ),
        args => [$flags]
    );
    $self->_context($old);
    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;
    }
    return;
}

sub _is_safari_user_agent {
    my ( $self, $user_agent ) = @_;
    if ( $user_agent =~ /Safari/smx ) {
        return 1;
    }
    return;
}

sub _is_safari_and_iphone_user_agent {
    my ( $self, $user_agent ) = @_;
    if ( $user_agent =~ /iPhone/smx ) {
        return 1;
    }
    return;
}

sub _is_trident_user_agent {
    my ( $self, $user_agent ) = @_;
    if ( $user_agent =~ /Trident/smx ) {
        return 1;
    }
    return;
}

sub _parse_user_agent {
    my ( $self, $user_agent ) = @_;
    my ( $app_version, $platform, $product, $product_sub, $vendor, $vendor_sub,
        $oscpu );

    # https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent#value
    if ( !defined $user_agent ) {
        $user_agent = $self->_original_agent();
    }
    if (
        $user_agent =~ m{^
                                        [^\/]+\/ # appCodeName
                                        ((5[.]0[ ][(][^; ]+)[^;]*;[ ] # appVersion
                                        ([^;)]+)[;)] # platform
					.*)
				$}smx
      )
    {
        ( my $webkit_app, $app_version, $platform ) = ( $1, $2, $3 );
        $app_version .= q[)];
        ( $vendor, $vendor_sub, $oscpu ) = ( q[], q[], $platform );
        $product     = 'Gecko';

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

    };
    my $temp_directory = File::Spec->tmpdir();
    my $temp_handle    = DirHandle->new($temp_directory)
      or Firefox::Marionette::Exception->throw(
        "Failed to open directory '$temp_directory':$EXTENDED_OS_ERROR");
  POSSIBLE_REMOTE_PROXY:
    while ( my $tainted_entry = $temp_handle->read() ) {
        next if ( $tainted_entry eq File::Spec->curdir() );
        next if ( $tainted_entry eq File::Spec->updir() );
        if ( $tainted_entry =~ /^($proxy_name_regex)$/smx ) {
            my ($untainted_entry) = ($1);
            my $ssh_local_directory =
              File::Spec->catfile( $temp_directory, $untainted_entry );
            if (
                my $proxy = $self->_matching_remote_proxy(
                    $ssh_local_directory, $search_local_proxy
                )
              )
            {
                $self->{_ssh} = {
                    port => $port,
                    host => $host,
                    user => $user,
                    pid  => $proxy->{ssh}->{pid},
                };
                if (   ( defined $proxy->{firefox} )
                    && ( defined $proxy->{firefox}->{pid} ) )
                {
                    $self->{_firefox_pid} = $proxy->{firefox}->{pid};
                }
                if (   ( defined $proxy->{xvfb} )
                    && ( defined $proxy->{xvfb}->{pid} ) )
                {
                    $self->{_xvfb_pid} = $proxy->{xvfb}->{pid};
                }
                if ( ( $OSNAME eq 'MSWin32' ) || ( $OSNAME eq 'cygwin' ) ) {
                    $self->{_ssh}->{use_control_path} = 0;
                    $self->{_ssh}->{use_unix_sockets} = 0;
                }
                else {
                    $self->{_ssh}->{use_control_path} = 1;
                    $self->{_ssh}->{use_unix_sockets} = 1;
                    $self->{_ssh}->{control_path} =
                      File::Spec->catfile( $ssh_local_directory,
                        'control.sock' );
                }
                $self->{_remote_uname}     = $proxy->{ssh}->{uname};
                $self->{marionette_binary} = $proxy->{ssh}->{binary};
                $self->{_initial_version}  = $proxy->{firefox}->{version};
                $self->_initialise_version();
                $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' );
                $self->{_scp_put_file_index} =
                  $self->_get_max_scp_file_index(
                    $self->{_local_scp_put_directory} );
                last POSSIBLE_REMOTE_PROXY;
            }
        }
    }
    closedir $temp_handle
      or Firefox::Marionette::Exception->throw(
        "Failed to close directory '$temp_directory':$EXTENDED_OS_ERROR");
    if ( $self->_ssh() ) {
    }
    else {
        Firefox::Marionette::Exception->throw(
            "Failed to detect existing local ssh tunnel to $user\@$host");
    }
    return;
}

sub ssh_local_directory {
    my ($self) = @_;
    return $self->{_ssh_local_directory};
}

sub _setup_ssh {
    my ( $self, $host, $port, $user, $reconnect ) = @_;
    if ($reconnect) {
        $self->_setup_ssh_with_reconnect( $host, $port, $user );
    }
    else {
        my $ssh_local_directory = File::Temp->newdir(
            CLEANUP  => 0,
            TEMPLATE => File::Spec->catdir(
                File::Spec->tmpdir(), 'perl_ff_m_XXXXXXXXXXX'
            )
          )
          or Firefox::Marionette::Exception->throw(
            "Failed to create temporary directory:$EXTENDED_OS_ERROR");
        $self->{_ssh_local_directory} = $ssh_local_directory->dirname();
        my $local_scp_directory =
          File::Spec->catdir( $self->ssh_local_directory(), 'scp' );
        mkdir $local_scp_directory, Fcntl::S_IRWXU()
          or Firefox::Marionette::Exception->throw(
            "Failed to create directory $local_scp_directory:$EXTENDED_OS_ERROR"
          );
        $self->{_local_scp_get_directory} =
          File::Spec->catdir( $local_scp_directory, 'get' );
        mkdir $self->{_local_scp_get_directory}, Fcntl::S_IRWXU()
          or Firefox::Marionette::Exception->throw(
"Failed to create directory $self->{_local_scp_get_directory}:$EXTENDED_OS_ERROR"

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

    my ( $self, %parameters ) = @_;
    if ( defined $parameters{har} ) {
        $self->{_har} = $parameters{har};
        require Firefox::Marionette::Extension::HarExportTrigger;
    }
    if ( $parameters{stealth} ) {
        $self->{stealth} = 1;
        require Firefox::Marionette::Extension::Stealth;
    }
    return;
}

sub _determine_mime_types {
    my ( $self, %parameters ) = @_;
    $self->{mime_types} = [
        qw(
          application/x-gzip
          application/gzip
          application/zip
          application/pdf
          application/octet-stream
          application/msword
          application/vnd.openxmlformats-officedocument.wordprocessingml.document
          application/vnd.openxmlformats-officedocument.wordprocessingml.template
          application/vnd.ms-word.document.macroEnabled.12
          application/vnd.ms-word.template.macroEnabled.12
          application/vnd.ms-excel
          application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
          application/vnd.openxmlformats-officedocument.spreadsheetml.template
          application/vnd.ms-excel.sheet.macroEnabled.12
          application/vnd.ms-excel.template.macroEnabled.12
          application/vnd.ms-excel.addin.macroEnabled.12
          application/vnd.ms-excel.sheet.binary.macroEnabled.12
          application/vnd.ms-powerpoint
          application/vnd.openxmlformats-officedocument.presentationml.presentation
          application/vnd.openxmlformats-officedocument.presentationml.template
          application/vnd.openxmlformats-officedocument.presentationml.slideshow
          application/vnd.ms-powerpoint.addin.macroEnabled.12
          application/vnd.ms-powerpoint.presentation.macroEnabled.12
          application/vnd.ms-powerpoint.template.macroEnabled.12
          application/vnd.ms-powerpoint.slideshow.macroEnabled.12
          application/vnd.ms-access
        )
    ];
    my %known_mime_types;
    foreach my $mime_type ( @{ $self->{mime_types} } ) {
        $known_mime_types{$mime_type} = 1;
    }
    foreach my $mime_type ( @{ $parameters{mime_types} } ) {
        if ( !$known_mime_types{$mime_type} ) {
            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);
            }
        }
    }
    return $port || _DEFAULT_PORT();
}

sub _reconnected {
    my ($self) = @_;
    return $self->{_reconnected};
}

sub _check_reconnecting_firefox_process_is_alive {
    my ( $self, $pid ) = @_;
    if ( $OSNAME eq 'MSWin32' ) {
        if (
            Win32::Process::Open(
                my $process, $pid, _WIN32_PROCESS_INHERIT_FLAGS()
            )
          )
        {
            $self->{_win32_firefox_process} = $process;
            return $pid;
        }
    }
    elsif ( kill 0, $pid ) {
        return $pid;
    }
    return;
}

sub _get_local_name_regex {
    my ($self) = @_;
    my $local_name_regex = qr/firefox_marionette_local_/smx;
    if ( $self->{reconnect_index} ) {
        my $quoted_index = quotemeta $self->{reconnect_index};
        $local_name_regex = qr/${local_name_regex}${quoted_index}\-/smx;
    }
    $local_name_regex = qr/${local_name_regex}\w+/smx;
    return $local_name_regex;
}

sub _get_local_reconnect_pid {
    my ($self)         = @_;
    my $temp_directory = File::Spec->tmpdir();
    my $temp_handle    = DirHandle->new($temp_directory)
      or Firefox::Marionette::Exception->throw(
        "Failed to open directory '$temp_directory':$EXTENDED_OS_ERROR");
    my $alive_pid;
    my $local_name_regex = $self->_get_local_name_regex();

  TEMP_DIR_LISTING: while ( my $tainted_entry = $temp_handle->read() ) {
        next if ( $tainted_entry eq File::Spec->curdir() );
        next if ( $tainted_entry eq File::Spec->updir() );
        if ( $tainted_entry =~ /^($local_name_regex)$/smx ) {
            my ($untainted_entry) = ($1);
            my $possible_root_directory =
              File::Spec->catfile( $temp_directory, $untainted_entry );
            my $local_proxy = $self->_read_possible_proxy_path(
                File::Spec->catfile( $possible_root_directory, 'reconnect' ) );
            if (   ( defined $local_proxy->{firefox} )
                && ( defined $local_proxy->{firefox}->{binary} ) )
            {
                if ( $self->_binary() ne $local_proxy->{firefox}->{binary} ) {
                    next TEMP_DIR_LISTING;
                }
            }
            elsif ( $self->_binary() ) {
                next TEMP_DIR_LISTING;
            }
            if (   ( defined $local_proxy->{firefox} )
                && ( $local_proxy->{firefox}->{pid} ) )
            {
                if (
                    my $check_pid =
                    $self->_check_reconnecting_firefox_process_is_alive(
                        $local_proxy->{firefox}->{pid}
                    )
                  )
                {
                    $alive_pid = $check_pid;
                }
                else {
                    next TEMP_DIR_LISTING;
                }
            }
            else {
                next TEMP_DIR_LISTING;
            }
            if (   ( defined $local_proxy->{xvfb} )
                && ( defined $local_proxy->{xvfb}->{pid} )
                && ( kill 0, $local_proxy->{xvfb}->{pid} ) )
            {
                $self->{_xvfb_pid} = $local_proxy->{xvfb}->{pid};
            }
            $self->{_initial_version} = $local_proxy->{firefox}->{version};
            $self->{_root_directory}  = $possible_root_directory;
            $self->_setup_profile();
        }
    }
    closedir $temp_handle
      or Firefox::Marionette::Exception->throw(
        "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;
    if ( my $ssh = $self->_ssh() ) {
        if ( my $pid = $self->_firefox_pid() ) {
            if ( $self->_remote_process_running($pid) ) {
                $self->{_firefox_pid} = $pid;
            }
        }
    }
    else {
        if ( my $pid = $self->_get_local_reconnect_pid() ) {
            if (
                ( kill 0, $pid )
                && ( my $port =
                    $self->_check_for_existing_local_firefox_process() )
              )
            {
                $self->{_firefox_pid} = $pid;
            }

        }
    }
    my ( $host, $user );
    if ( my $ssh = $self->_ssh() ) {
        $host = $self->_ssh()->{host};
        $user = $self->_ssh()->{user};
    }
    elsif (( $OSNAME eq 'MSWin32' )
        || ( $OSNAME eq 'cygwin' ) )
    {
        $user = Win32::LoginName();
        $host = 'localhost';
    }
    else {
        $user = getpwuid $EFFECTIVE_USER_ID;
        $host = 'localhost';
    }
    my $quoted_user = defined $user ? quotemeta $user : q[];
    if ( $self->_ssh() ) {
        $self->_initialise_remote_uname();
    }
    $self->_check_visible(%parameters);
    my $port = $self->_get_marionette_port();
    defined $port
      or Firefox::Marionette::Exception->throw(
        "Existing firefox process could not be found at $user\@$host");
    my $socket;
    socket $socket,
      $self->_using_unix_sockets_for_ssh_connection()
      ? Socket::PF_UNIX()
      : Socket::PF_INET(), Socket::SOCK_STREAM(), 0
      or Firefox::Marionette::Exception->throw(

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

    my $socket = $self->_setup_local_connection_to_firefox(@arguments);
    my $session_id;
    ( $session_id, $capabilities ) =
      $self->_initial_socket_setup( $socket, $capabilities );
    $self->_check_protocol_version_and_pid( $session_id, $capabilities );
    $self->_post_launch_checks_and_setup($timeouts);
    return $self;
}

sub _reset_marionette_port {
    my ($self) = @_;
    my $handle;
    if ( $self->_ssh() ) {
        $handle =
          $self->_get_file_via_scp( {}, $self->{profile_path}, 'profile path' );
    }
    else {
        $handle = FileHandle->new( $self->{profile_path}, Fcntl::O_RDONLY() )
          or Firefox::Marionette::Exception->throw(
"Failed to open '$self->{profile_path}' for reading:$EXTENDED_OS_ERROR"
          );
    }
    my $profile = Firefox::Marionette::Profile->parse_by_handle($handle);
    close $handle
      or Firefox::Marionette::Exception->throw(
        "Failed to close '$self->{profile_path}':$EXTENDED_OS_ERROR");
    if ( $self->_is_auto_listen_okay() ) {
        $profile->set_value( 'marionette.port',
            Firefox::Marionette::Profile::ANY_PORT() );
    }
    else {
        my $port = $self->_get_empty_port();
        $profile->set_value( 'marionette.defaultPrefs.port', $port );
        $profile->set_value( 'marionette.port',              $port );
    }
    if ( $self->_ssh() ) {
        $self->_save_profile_via_ssh($profile);
    }
    else {
        $profile->save( $self->{profile_path} );
    }
    return;
}

sub update {
    my ( $self, $update_timeout ) = @_;
    my $timeouts        = $self->timeouts();
    my $script_timeout  = $timeouts->script();
    my $update_timeouts = Firefox::Marionette::Timeouts->new(
        script => ( $update_timeout || _DEFAULT_UPDATE_TIMEOUT() ) *
          _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);
    }
    if (updateService.canUsuallyStageUpdates) {
      if (!updateService.canStageUpdates) {
        updateStatus["updateStatusCode"] = 'CANNOT_STAGE_UPDATES';
        reject(updateStatus);
      }
    }
    if ((updateService.isOtherInstanceHandlingUpdates) && (updateService.isOtherInstanceHandlingUpdates())) {
      updateStatus["updateStatusCode"] = 'ANOTHER_INSTANCE_IS_HANDLING_UPDATES';
      reject(updateStatus);
    }
    let updateChecker = Components.classes["@mozilla.org/updates/update-checker;1"].createInstance(Components.interfaces.nsIUpdateChecker);
    if (updateChecker.stopCurrentCheck) {
      updateChecker.stopCurrentCheck();
    }
    let updateServiceListener = {
      onCheckComplete: (request, updates) => {
        latestUpdate = updateService.selectUpdate(updates, true);
        updateStatus["numberOfUpdates"] = updates.length;
        if (latestUpdate === null) {
          updateStatus["updateStatusCode"] = 'NO_UPDATES_AVAILABLE';
          reject(updateStatus);
        } else {
          for (key in latestUpdate) {
            if (typeof latestUpdate[key] !== 'function') {
              updateStatus[key] = latestUpdate[key];
            }
          }
          let result = updateService.downloadUpdate(latestUpdate, false);
          let updateProcessor = Components.classes["@mozilla.org/updates/update-processor;1"].createInstance(Components.interfaces.nsIUpdateProcessor);
          if (updateProcessor.fixUpdateDirectoryPermissions) {
            updateProcessor.fixUpdateDirectoryPermissions(true);
          }
          updateProcessor.processUpdate(latestUpdate);

          let previousState = null;
          function nowPending() {
            if ((latestUpdate.state) && ((previousState == null) || (previousState != latestUpdate.state))) {
              console.log("Update status is now " + latestUpdate.state);
            }
            previousState = latestUpdate.state;
            updateStatus["state"] = latestUpdate.state;
            updateStatus["statusText"] = latestUpdate.statusText;
            if ((latestUpdate.state == 'pending') || (latestUpdate.state == 'pending-service')) {
              updateStatus["updateStatusCode"] = 'PENDING_UPDATE';
              resolve(updateStatus);
            } else {
              setTimeout(function() { nowPending() }, 500);
            }
          }

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

    my $profile_directory = $self->{_profile_directory};
    if ( $self->_ssh() ) {
        if ( $self->_remote_uname() eq 'cygwin' ) {
            $profile_directory =
              $self->_execute_via_ssh( {}, 'cygpath', '-s', '-m',
                $profile_directory );
            chomp $profile_directory;
        }
    }
    elsif ( $OSNAME eq 'cygwin' ) {
        $profile_directory =
          $self->execute( 'cygpath', '-s', '-m', $profile_directory );
    }
    return $profile_directory;
}

sub _get_remote_profile_directory {
    my ( $self, $profile_name ) = @_;
    my $profile_directory;
    if (   ( $self->_remote_uname() eq 'cygwin' )
        || ( $self->_remote_uname() eq 'MSWin32' ) )
    {
        my $appdata_directory =
          $self->_get_remote_environment_variable_via_ssh('APPDATA');
        if ( $self->_remote_uname() eq 'cygwin' ) {
            $appdata_directory =~ s/\\/\//smxg;
            $appdata_directory =
              $self->_execute_via_ssh( {}, 'cygpath', '-u',
                $appdata_directory );
            chomp $appdata_directory;
        }
        my $profile_ini_directory =
          $self->_remote_catfile( $appdata_directory, 'Mozilla', 'Firefox' );
        my $profile_ini_path =
          $self->_remote_catfile( $profile_ini_directory, 'profiles.ini' );
        my $handle = $self->_get_file_via_scp( {}, $profile_ini_path,
            'profiles.ini file' );
        my $config = Config::INI::Reader->read_handle($handle);
        $profile_directory = $self->_remote_catfile(
            Firefox::Marionette::Profile->directory(
                $profile_name, $config, $profile_ini_directory
            )
        );
    }
    else {
        my $profile_ini_directory;
        if ( $self->_remote_uname() eq 'darwin' ) {
            $profile_ini_directory = $self->_remote_catfile( 'Library',
                'Application Support', 'Firefox' );
        }
        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;
}

sub _setup_arguments {
    my ( $self, %parameters ) = @_;
    my @arguments = qw(-marionette);
    if ( ( defined $self->{debug} ) && ( $self->{debug} !~ /^[01]$/smx ) ) {
        push @arguments, '-MOZ_LOG=' . $self->{debug};
    }
    if ( $self->{system_access} ) {
        push @arguments, '-remote-allow-system-access';
    }
    if ( defined $self->{window_width} ) {
        push @arguments, '-width', $self->{window_width};
    }
    if ( defined $self->{window_height} ) {
        push @arguments, '-height', $self->{window_height};
    }
    if ( defined $self->{console} ) {
        push @arguments, '--jsconsole';
    }
    push @arguments, $self->_check_addons(%parameters);
    push @arguments, $self->_check_visible(%parameters);
    push @arguments, $self->_profile_arguments(%parameters);
    if ( ( $self->{_har} ) || ( $parameters{devtools} ) ) {
        push @arguments, '--devtools';
    }
    if ( $parameters{kiosk} ) {
        push @arguments, '--kiosk';
    }
    return @arguments;
}

sub _profile_arguments {
    my ( $self, %parameters ) = @_;
    my @arguments;
    if ( $parameters{restart} ) {
        push @arguments,
          (
            '-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',
                    $profile_directory );
                chomp $profile_directory;
            }
        }
        elsif ( $OSNAME eq 'cygwin' ) {
            $profile_directory =
              $self->execute( 'cygpath', '-s', '-m', $profile_directory );
        }
        my $mime_types_content = $self->_mime_types_content();
        if ( $self->_ssh() ) {
            $self->_write_mime_types_via_ssh($mime_types_content);
        }
        else {
            my $path =
              File::Spec->catfile( $profile_directory, 'mimeTypes.rdf' );
            my $handle = FileHandle->new(
                $path,
                Fcntl::O_WRONLY() | Fcntl::O_CREAT() | Fcntl::O_EXCL(),
                Fcntl::S_IRUSR() | Fcntl::S_IWUSR()
              )
              or Firefox::Marionette::Exception->throw(
                "Failed to open '$path' for writing:$EXTENDED_OS_ERROR");
            print {$handle} $mime_types_content
              or Firefox::Marionette::Exception->throw(
                "Failed to write to '$path':$EXTENDED_OS_ERROR");
            close $handle
              or Firefox::Marionette::Exception->throw(
                "Failed to close '$path':$EXTENDED_OS_ERROR");
        }
        push @arguments,
          ( '-profile', $profile_directory, '--no-remote', '--new-instance' );
    }
    return @arguments;
}

sub _mime_types_content {
    my ($self) = @_;
    my $mime_types_content = <<'_RDF_';
<?xml version="1.0"?>
<RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#"
         xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <RDF:Seq RDF:about="urn:mimetypes:root">
_RDF_
    foreach my $mime_type ( @{ $self->{mime_types} } ) {
        $mime_types_content .= <<'_RDF_';
    <RDF:li RDF:resource="urn:mimetype:$mime_type"/>
_RDF_
    }
    $mime_types_content .= <<'_RDF_';
  </RDF:Seq>

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

    }
    if ( defined $self->{_firefox_pid} ) {
        $local_proxy->{firefox}->{pid}     = $self->{_firefox_pid};
        $local_proxy->{firefox}->{binary}  = $self->_binary();
        $local_proxy->{firefox}->{version} = $self->{_initial_version};
    }
    print {$local_proxy_handle} JSON::encode_json($local_proxy)
      or Firefox::Marionette::Exception->throw(
        "Failed to write to $local_proxy_path:$EXTENDED_OS_ERROR");
    close $local_proxy_handle
      or Firefox::Marionette::Exception->throw(
        "Failed to close '$local_proxy_path':$EXTENDED_OS_ERROR");
    return;
}

sub _setup_profile_directories {
    my ( $self, $profile ) = @_;
    if ( ($profile) && ( $profile->download_directory() ) ) {
        if ( $self->_ssh() ) {
            $self->{_root_directory} = $self->_get_remote_root_directory();
        }
    }
    elsif ( my $ssh = $self->_ssh() ) {
        $self->{_root_directory} = $self->_get_remote_root_directory();
        $self->_write_local_proxy($ssh);
        $self->{_profile_directory} = $self->_make_remote_directory(
            $self->_remote_catfile( $self->{_root_directory}, 'profile' ) );
        $self->{_download_directory} = $self->_make_remote_directory(
            $self->_remote_catfile( $self->{_root_directory}, 'downloads' ) );
        $self->{_remote_tmp_directory} = $self->_make_remote_directory(
            $self->_remote_catfile( $self->{_root_directory}, 'tmp' ) );
    }
    else {
        my $root_directory = $self->_root_directory();
        my $profile_directory =
          File::Spec->catdir( $root_directory, 'profile' );
        mkdir $profile_directory, Fcntl::S_IRWXU()
          or Firefox::Marionette::Exception->throw(
            "Failed to create directory $profile_directory:$EXTENDED_OS_ERROR");
        $self->{_profile_directory} = $profile_directory;
        my $download_directory =
          File::Spec->catdir( $root_directory, 'downloads' );
        mkdir $download_directory, Fcntl::S_IRWXU()
          or Firefox::Marionette::Exception->throw(
            "Failed to create directory $download_directory:$EXTENDED_OS_ERROR"
          );
        $self->{_download_directory} = $download_directory;
        my $tmp_directory = $self->_local_firefox_tmp_directory();
        mkdir $tmp_directory, Fcntl::S_IRWXU()
          or Firefox::Marionette::Exception->throw(
            "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() ) {
            my $download_directory = $self->{_download_directory};
            if ( $self->_ssh() ) {
                if ( $self->_remote_uname() eq 'cygwin' ) {
                    $download_directory =
                      $self->_execute_via_ssh( {}, 'cygpath', '-s', '-w',
                        $download_directory );
                    chomp $download_directory;
                }
            }
            elsif ( $OSNAME eq 'cygwin' ) {
                $download_directory =
                  $self->execute( 'cygpath', '-s', '-w', $download_directory );
            }
            $profile->download_directory($download_directory);
        }
    }
    else {
        my %profile_parameters = ();
        foreach my $profile_key (qw(chatty seer nightly)) {
            if ( $parameters{$profile_key} ) {
                $profile_parameters{$profile_key} = 1;
            }
        }
        if ( $self->{waterfox} ) {
            $profile = Waterfox::Marionette::Profile->new(%profile_parameters);
        }
        else {
            $profile = Firefox::Marionette::Profile->new(%profile_parameters);
        }
        my $download_directory = $self->{_download_directory};
        my $bookmarks_path     = $self->_setup_empty_bookmarks();
        $self->_setup_search_json_mozlz4();
        if (   ( $self->_remote_uname() )
            && ( $self->_remote_uname() eq 'cygwin' ) )
        {
            $download_directory =
              $self->_execute_via_ssh( {}, 'cygpath', '-s', '-w',
                $download_directory );
            chomp $download_directory;
        }
        $profile->download_directory($download_directory);
        $profile->set_value( 'browser.bookmarks.file', $bookmarks_path, 1 );
        if (
            !$self->_is_firefox_major_version_at_least(
                _MIN_VERSION_FOR_LINUX_SANDBOX()
            )
          )
        {
            $profile->set_value( 'security.sandbox.content.level', 0, 0 )
              ; # https://wiki.mozilla.org/Security/Sandbox#Customization_Settings



( run in 2.466 seconds using v1.01-cache-2.11-cpan-5fbc6bb55f2 )