diff --git a/includes/webform.report.inc b/includes/webform.report.inc
index 1117b8f..c10d5eb 100644
--- a/includes/webform.report.inc
+++ b/includes/webform.report.inc
@@ -312,6 +312,7 @@ function webform_results_download_form($form, &$form_state, $node) {
   module_load_include('inc', 'webform', 'includes/webform.components');
 
   $form = array();
+  $form['#attributes']['target'] = '_blank';
 
   $form['node'] = array(
     '#type' => 'value',
@@ -527,16 +528,7 @@ function webform_results_download_form_submit(&$form, &$form_state) {
     $options['sids'] = webform_download_sids($form_state['values']['node']->nid, $form_state['values']['range']);
   }
 
-  $export_info = webform_results_export($form_state['values']['node'], $form_state['values']['format'], $options);
-
-  // If webform result file should be downloaded, send the file to the browser,
-  // otherwise save information about the created file in $form_state.
-  if ($options['download']) {
-    webform_results_download($form_state['values']['node'], $export_info);
-  }
-  else {
-    $form_state['export_info'] = $export_info;
-  }
+  webform_results_export($form_state['values']['node'], $form_state['values']['format'], $options);
 }
 
 /**
@@ -751,10 +743,48 @@ function webform_results_export($node, $format = 'delimited', $options = array()
   if (!empty($options['sids'])){
     $filters['sid'] = $options['sids'];
   }
-  $submissions = webform_get_submissions($filters);
+
+  $batch = array(
+    'title' => t('Webform submission export'),
+    'init_message' => t('Preparing submissions for download'),
+    'progress_message' => t('Generating submission export file …'),
+    'finished' => 'webform_results_export_batch_finish',
+    'file' => drupal_get_path('module', 'webform') . '/includes/webform.report.inc',
+  );
+  $batch['operations'][] = array(
+    'webform_results_export_batch',
+    array($filters, $options, $submission_information, $node, $exporter, $file_name),
+  );
+  batch_set($batch);
+}
+
+function webform_results_export_batch($filters, $options, $submission_information, $node, $exporter, $file_name, &$context) {
+  module_load_include('inc', 'webform', 'includes/webform.components');
+  $handle = @fopen($file_name, 'a'); // The @ suppresses errors.
+
+  // Prepare pager
+  if (!isset($context['sandbox']['pager'])) {
+    $context['sandbox']['pager'] = array(
+      'page' => 0,
+      'size' => webform_variable_get('webform_export_batch_size'),
+      'element' => PagerDefault::$maxElement,
+    );
+  }
+  $pager = &$context['sandbox']['pager'];
+  $row_count = $pager['page'] * $pager['size'];
+
+  // Set current page
+  $pages = isset($_GET['page']) ? explode(',', $_GET['page']) : array();
+  for ($page = count($pages)-1; $page <= $pager['element']; $page++) {
+    $pages[] = '';
+  }
+  $pages[$pager['element']] = $pager['page']++;
+  $_GET['page'] = implode(',', $pages);
+
+  $submissions = webform_get_submissions($filters, NULL, $pager['size']);
+  $pager['total'] = $GLOBALS['pager_total'][$pager['element']];
 
   // Generate a row for each submission.
-  $row_count = 0;
   $sid = 0;
   foreach ($submissions as $sid => $submission) {
     $row_count++;
@@ -807,9 +837,6 @@ function webform_results_export($node, $format = 'delimited', $options = array()
     $data = $exporter->add_row($handle, $row);
   }
 
-  // Add the closing bytes.
-  $exporter->eof($handle);
-
   // Close the file.
   @fclose($handle);
 
@@ -818,8 +845,23 @@ function webform_results_export($node, $format = 'delimited', $options = array()
   $export_info['exporter'] = $exporter;
   $export_info['row_count'] = $row_count;
   $export_info['last_sid'] = $sid;
+  $export_info['node'] = $node;
+
+  $context['message'] = t('Exported @count form submissions.', array('@count' => $row_count));
+  $context['results']['export_info'] = $export_info;
+  if ($pager['page'] < $pager['total']) {
+    $context['finished'] = (float) $pager['page'] / $pager['total'];
+  }
+}
+
+function webform_results_export_batch_finish($success, $results, $operations) {
+  $handle = @fopen($results['export_info']['file_name'], 'a'); // The @ suppresses errors.
+  // Add the closing bytes.
+  $results['export_info']['exporter']->eof($handle);
 
-  return $export_info;
+  // Close the file.
+  @fclose($handle);
+  webform_results_download($results['export_info']['node'], $results['export_info']);
 }
 
 /**
@@ -836,6 +878,8 @@ function webform_results_download($node, $export_info) {
   // $exporter, $file_name, $row_count
   $export_name = _webform_safe_name($node->title);
   $export_info['exporter']->set_headers($export_name);
+  drupal_add_http_header('Content-Length', filesize($export_info['file_name']));
+  while (@ob_end_flush());
   @readfile($export_info['file_name']);  // The @ makes it silent.
   @unlink($export_info['file_name']);  // Clean up, the @ makes it silent.
 
diff --git a/webform.module b/webform.module
index 235d43b..925713c 100644
--- a/webform.module
+++ b/webform.module
@@ -3175,6 +3175,9 @@ function webform_variable_get($variable) {
     case 'webform_allowed_tags':
       $result = variable_get('webform_allowed_tags', array('a', 'em', 'strong', 'code', 'img'));
       break;
+    case 'webform_export_batch_size':
+      $result = variable_get('webform_export_batch_size', 1000);
+      break;
     case 'webform_default_from_name':
       $result = variable_get('webform_default_from_name', variable_get('site_name', ''));
       break;
