diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 0b07d8e..baf169d 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -14,7 +14,10 @@
  * @see batch_get()
  */
 
-use \Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
  * Loads a batch from the database.
@@ -27,12 +30,13 @@ use \Symfony\Component\HttpFoundation\JsonResponse;
  *   An array representing the batch, or FALSE if no batch was found.
  */
 function batch_load($id) {
-  $batch = db_query("SELECT batch FROM {batch} WHERE bid = :bid AND token = :token", array(
+  $result = db_query('SELECT batch, token FROM {batch} WHERE bid = :bid', array(
     ':bid' => $id,
-    ':token' => drupal_get_token($id),
   ))->fetchField();
-  if ($batch) {
-    return unserialize($batch);
+  if ($result) {
+    $batch = unserialize($result->batch);
+    $batch['token'] = $result->token;
+    return $batch;
   }
   return FALSE;
 }
@@ -46,7 +50,7 @@ function _batch_page() {
   $batch = &batch_get();
 
   if (!isset($_REQUEST['id'])) {
-    return FALSE;
+    throw new NotFoundHttpException();
   }
 
   // Retrieve the current state of the batch.
@@ -56,6 +60,9 @@ function _batch_page() {
       drupal_set_message(t('No active batch.'), 'error');
       drupal_goto();
     }
+    if (!isset($batch['token']) || $batch['token'] !== drupal_get_token($_REQUEST['id'])) {
+      throw new AccessDeniedHttpException();
+    }
   }
 
   // Register database update for the end of processing.
@@ -96,6 +103,26 @@ function _batch_page() {
 }
 
 /**
+ * Delivery callback for batch requests.
+ *
+ * @see drupal_deliver_page()
+ * @see drupal_deliver_html_page()
+ */
+function batch_deliver_page($page_callback_result) {
+  if ($page_callback_result instanceof Response) {
+    return $page_callback_result;
+  }
+  elseif (isset($page_callback_result) && (is_string($page_callback_result) || is_array($page_callback_result))) {
+    // Force a page without blocks or messages to display a list of collected
+    // messages later.
+    drupal_set_page_content($page_callback_result);
+    $page = element_info('page');
+    $page['#show_messages'] = FALSE;
+    drupal_deliver_html_page($page);
+  }
+}
+
+/**
  * Initializes the batch processing.
  *
  * JavaScript-enabled clients are identified by the 'has_js' cookie set in
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index f11f15b..0b5d451 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -2298,29 +2298,6 @@ function system_php() {
 }
 
 /**
- * Default page callback for batches.
- */
-function system_batch_page() {
-  require_once DRUPAL_ROOT . '/core/includes/batch.inc';
-  $output = _batch_page();
-
-  if ($output === FALSE) {
-    throw new AccessDeniedHttpException();
-  }
-  elseif ($output instanceof Response) {
-    return $output;
-  }
-  elseif (isset($output)) {
-    // Force a page without blocks or messages to
-    // display a list of collected messages later.
-    drupal_set_page_content($output);
-    $page = element_info('page');
-    $page['#show_messages'] = FALSE;
-    return $page;
-  }
-}
-
-/**
  * Returns HTML for an administrative block for display.
  *
  * @param $variables
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index a76e3ae..bfa8a90 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1116,11 +1116,17 @@ function system_menu() {
 
   // Default page for batch operations.
   $items['batch'] = array(
-    'page callback' => 'system_batch_page',
+    'page callback' => '_batch_page',
+     // Depending on the client configuration, the _batch_page() callback
+     // returns either an HTML page or plain JSON. Using the regular
+     // drupal_deliver_html_page() callback would break with modules using
+     // hook_page_delivery_callback_alter() and expecting an HTML page.
+    'delivery callback' => 'batch_deliver_page',
     'access callback' => TRUE,
     'theme callback' => '_system_batch_theme',
     'type' => MENU_CALLBACK,
-    'file' => 'system.admin.inc',
+    'file path' => 'core/includes',
+    'file' => 'batch.inc',
   );
 
   // Localize date formats.
