why pre_get_posts works fine in post type archive, but not in search result list?

If searching via WordPress ?s=keyword (not WooComm’s Product Search), post_type isn’t set or used, so the following would return false:

if( isset($query->query_vars['post_type']) && $query->query_vars['post_type'] == 'product' ) {

You can explore what $query->query_vars contains on your Products page vs your Products Search page by just printing it and killing the page:

function my_pre_get_posts( $query ) {
  echo "<pre>".print_r($query->query_vars,true)."</pre>";
  die;
  //...

If you are using WooComm’s search (and the query is like ?s=keyword&post_type=product) and WooComm does have post_type set, try firing your action with a later priority:

add_action('pre_get_posts', 'my_pre_get_posts',999);