Index: index.php
===================================================================
RCS file: /cvs/drupal/drupal/index.php,v
retrieving revision 1.98
diff -u -p -r1.98 index.php
--- index.php	8 Feb 2009 20:27:51 -0000	1.98
+++ index.php	9 Oct 2009 18:07:26 -0000
@@ -19,25 +19,4 @@ define('DRUPAL_ROOT', getcwd());
 
 require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
-$return = menu_execute_active_handler();
-
-// Menu status constants are integers; page content is a string or array.
-if (is_int($return)) {
-  switch ($return) {
-    case MENU_NOT_FOUND:
-      drupal_not_found();
-      break;
-    case MENU_ACCESS_DENIED:
-      drupal_access_denied();
-      break;
-    case MENU_SITE_OFFLINE:
-      drupal_site_offline();
-      break;
-  }
-}
-elseif (isset($return)) {
-  // Print anything besides a menu constant, assuming it's not NULL or undefined.
-  print drupal_render_page($return);
-}
-
-drupal_page_footer();
+drupal_deliver(menu_execute_active_handler());
Index: includes/ajax.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/ajax.inc,v
retrieving revision 1.13
diff -u -p -r1.13 ajax.inc
--- includes/ajax.inc	5 Oct 2009 02:16:32 -0000	1.13
+++ includes/ajax.inc	9 Oct 2009 18:07:26 -0000
@@ -291,30 +291,58 @@ function ajax_form_callback() {
     $callback = $triggering_element['#ajax']['callback'];
   }
   if (!empty($callback) && function_exists($callback)) {
-    $html = $callback($form, $form_state);
+    return $callback($form, $form_state);
+  }
+}
 
-    // If the returned value is a string, assume it is HTML, add the status
-    // messages, and create a command object to return automatically. We want
-    // the status messages inside the new wrapper, so that they get replaced
-    // on subsequent AJAX calls for the same wrapper.
-    if (is_string($html)) {
-      $commands = array();
-      $commands[] = ajax_command_replace(NULL, $html);
-      $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
+/**
+ * Package and send the result of a page callback to the browser as an AJAX
+ * response.
+ *
+ * @param $payload
+ *   The result of a page callback. Can be NULL, an integer menu status, a
+ *   string of html, or a renderable array.
+ * @param $path
+ *   A drupal path that can be used to affect delivery details.
+ */
+function ajax_deliver($payload, $path = NULL) {
+  $commands = array();
+  if (!isset($payload)) {
+    // Simply delivering an empty commands array is sufficient. This results
+    // in the AJAX request being completed, but nothing being done to the page.
+  }
+  elseif (is_int($payload)) {
+    switch ($payload) {
+      case MENU_NOT_FOUND:
+        $commands[] = ajax_command_alert(t('The requested page could not be found.'));
+        break;
+      case MENU_ACCESS_DENIED:
+        $commands[] = ajax_command_alert(t('You are not authorized to access this page.'));
+        break;
+      case MENU_SITE_OFFLINE:
+        $commands[] = ajax_command_alert(filter_xss_admin(variable_get('maintenance_mode_message',
+          t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
+        break;
     }
-    // Otherwise, $html is supposed to be an array of commands, suitable for
-    // Drupal.ajax, so we pass it on as is. In this situation, the callback is
-    // doing something fancy, so let it decide how to handle status messages
-    // without second guessing it.
-    else {
-      $commands = $html;
+  }
+  elseif (is_array($payload) && isset($payload['#type']) && ($payload['#type'] == 'ajax_commands')) {
+    // Complex AJAX callbacks can return a payload that contains a specific
+    // set of commands to send to the browser. 
+    if (isset($payload['#ajax_commands'])) {
+      $commands = $payload['#ajax_commands'];
     }
-
-    ajax_render($commands);
   }
-
-  // Return a 'do nothing' command if there was no callback.
-  ajax_render(array());
+  else {
+    // Like normal page callbacks, simple AJAX callbacks can return html
+    // content, as a string or renderable array, to replace what was previously
+    // there in the wrapper. In this case, in addition to the content, we want
+    // to add the status messages, but inside the new wrapper, so that they get
+    // replaced on subsequent AJAX calls for the same wrapper.
+    $html = is_string($payload) ? $payload : drupal_render($payload);
+    $commands[] = ajax_command_replace(NULL, $html);
+    $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
+  }
+  ajax_render($commands);
 }
 
 /**
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1011
diff -u -p -r1.1011 common.inc
--- includes/common.inc	9 Oct 2009 16:33:13 -0000	1.1011
+++ includes/common.inc	9 Oct 2009 18:07:26 -0000
@@ -2591,6 +2591,59 @@ function l($text, $path, array $options 
 }
 
 /**
+ * Package and send the result of a page callback to the browser.
+ * 
+ * This function is the generic one called by index.php. It determines which
+ * specific delivery callback to use and routes to that.
+ *
+ * @param $payload
+ *   The result of a page callback. Can be NULL, an integer menu status, a
+ *   string of html, or a renderable array.
+ * @param $path
+ *   A drupal path that determines which specific delivery callback to use.
+ */
+function drupal_deliver($payload, $path = NULL) {
+  $router_item = menu_get_item($path);
+  $callback = ($router_item && !empty($router_item['delivery_callback'])) ? $router_item['delivery_callback'] : 'drupal_deliver_page';
+  if (function_exists($callback)) {
+    $callback($payload, $path);
+  }
+}
+
+/**
+ * Package and send the result of a page callback to the browser as a normal
+ * HTML page.
+ *
+ * @param $payload
+ *   The result of a page callback. Can be NULL, an integer menu status, a
+ *   string of html, or a renderable array.
+ * @param $path
+ *   A drupal path that can be used to affect delivery details.
+ */
+function drupal_deliver_page($payload, $path = NULL) {
+  // Menu status constants are integers; page content is a string or array.
+  if (is_int($payload)) {
+    switch ($payload) {
+      case MENU_NOT_FOUND:
+        drupal_not_found();
+        break;
+      case MENU_ACCESS_DENIED:
+        drupal_access_denied();
+        break;
+      case MENU_SITE_OFFLINE:
+        drupal_site_offline();
+        break;
+    }
+  }
+  elseif (isset($payload)) {
+    // Print anything besides a menu constant, assuming it's not NULL or undefined.
+    print drupal_render_page($payload);
+  }
+
+  drupal_page_footer();
+}
+
+/**
  * Perform end-of-request tasks.
  *
  * This function sets the page cache if appropriate, and allows modules to
Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.350
diff -u -p -r1.350 menu.inc
--- includes/menu.inc	9 Oct 2009 08:02:24 -0000	1.350
+++ includes/menu.inc	9 Oct 2009 18:07:27 -0000
@@ -935,6 +935,7 @@ function menu_tree_all_data($menu_name, 
         'access_arguments',
         'page_callback',
         'page_arguments',
+        'delivery_callback',
         'title',
         'title_callback',
         'title_arguments',
@@ -1118,6 +1119,7 @@ function menu_tree_page_data($menu_name,
           'access_arguments',
           'page_callback',
           'page_arguments',
+          'delivery_callback',
           'title',
           'title_callback',
           'title_arguments',
@@ -2809,6 +2811,10 @@ function _menu_router_build($callbacks) 
             $item['file path'] = $parent['file path'];
           }
         }
+        // Same for delivery callbacks.
+        if (!isset($item['delivery callback']) && isset($parent['delivery callback'])) {
+          $item['delivery callback'] = $parent['delivery callback'];
+        }
         // Same for theme callbacks.
         if (!isset($item['theme callback']) && isset($parent['theme callback'])) {
           $item['theme callback'] = $parent['theme callback'];
@@ -2834,6 +2840,7 @@ function _menu_router_build($callbacks) 
       'access callback' => '',
       'page arguments' => array(),
       'page callback' => '',
+      'delivery callback' => '',
       'block callback' => '',
       'title arguments' => array(),
       'title callback' => 't',
@@ -2881,6 +2888,7 @@ function _menu_router_save($menu, $masks
       'access_arguments',
       'page_callback',
       'page_arguments',
+      'delivery_callback',
       'fit',
       'number_parts',
       'tab_parent',
@@ -2908,6 +2916,7 @@ function _menu_router_save($menu, $masks
       'access_arguments' => serialize($item['access arguments']),
       'page_callback' => $item['page callback'],
       'page_arguments' => serialize($item['page arguments']),
+      'delivery_callback' => $item['delivery callback'],
       'fit' => $item['_fit'],
       'number_parts' => $item['_number_parts'],
       'tab_parent' => $item['tab_parent'],
Index: modules/book/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.module,v
retrieving revision 1.517
diff -u -p -r1.517 book.module
--- modules/book/book.module	9 Oct 2009 00:59:55 -0000	1.517
+++ modules/book/book.module	9 Oct 2009 18:07:27 -0000
@@ -165,6 +165,7 @@ function book_menu() {
   );
   $items['book/js/form'] = array(
     'page callback' => 'book_form_update',
+    'delivery callback' => 'ajax_deliver',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
     'file' => 'book.pages.inc',
@@ -1214,7 +1215,7 @@ function book_menu_subtree_data($link) {
       $menu_router_alias = $query->join('menu_router', 'm', 'm.path = ml.router_path');
       $book_alias = $query->join('book', 'b', 'ml.mlid = b.mlid');
       $query->fields($book_alias);
-      $query->fields($menu_router_alias, array('load_functions', 'to_arg_functions', 'access_callback', 'access_arguments', 'page_callback', 'page_arguments', 'title', 'title_callback', 'title_arguments', 'type'));
+      $query->fields($menu_router_alias, array('load_functions', 'to_arg_functions', 'access_callback', 'access_arguments', 'page_callback', 'page_arguments', 'delivery_callback', 'title', 'title_callback', 'title_arguments', 'type'));
       $query->fields('ml');
       $query->condition('menu_name', $link['menu_name']);
       for ($i = 1; $i <= MENU_MAX_DEPTH && $link["p$i"]; ++$i) {
Index: modules/book/book.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.pages.inc,v
retrieving revision 1.17
diff -u -p -r1.17 book.pages.inc
--- modules/book/book.pages.inc	9 Oct 2009 00:59:55 -0000	1.17
+++ modules/book/book.pages.inc	9 Oct 2009 18:07:27 -0000
@@ -255,5 +255,5 @@ function book_form_update() {
     $commands[] = ajax_command_replace(NULL, drupal_render($form['book']['plid']));
   }
 
-  ajax_render($commands);
+  return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
 }
Index: modules/file/file.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.module,v
retrieving revision 1.6
diff -u -p -r1.6 file.module
--- modules/file/file.module	9 Oct 2009 07:31:38 -0000	1.6
+++ modules/file/file.module	9 Oct 2009 18:07:28 -0000
@@ -17,11 +17,13 @@ function file_menu() {
 
   $items['file/ajax'] = array(
     'page callback' => 'file_ajax_upload',
+    'delivery callback' => 'ajax_deliver',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
   $items['file/progress'] = array(
     'page callback' => 'file_ajax_progress',
+    'delivery callback' => 'ajax_deliver',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
Index: modules/menu/menu.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.admin.inc,v
retrieving revision 1.64
diff -u -p -r1.64 menu.admin.inc
--- modules/menu/menu.admin.inc	9 Oct 2009 17:16:46 -0000	1.64
+++ modules/menu/menu.admin.inc	9 Oct 2009 18:07:28 -0000
@@ -44,7 +44,7 @@ function menu_overview_form($form, &$for
   global $menu_admin;
   $form['#attached']['css'] = array(drupal_get_path('module', 'menu') . '/menu.css');
   $sql = "
-    SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
+    SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.delivery_callback, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
     FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
     WHERE ml.menu_name = :menu
     ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
Index: modules/menu/menu.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.api.php,v
retrieving revision 1.15
diff -u -p -r1.15 menu.api.php
--- modules/menu/menu.api.php	9 Oct 2009 08:02:24 -0000	1.15
+++ modules/menu/menu.api.php	9 Oct 2009 18:07:28 -0000
@@ -163,6 +163,10 @@
  *     instead.
  *   - "page arguments": An array of arguments to pass to the page callback
  *     function, with path component substitution as described above.
+ *   - "delivery callback": The function to call to package the result of the 
+ *     page callback function and send it to the browser. Defaults to
+ *     drupal_deliver_page() unless a value is inherited from a parent menu
+ *     item.
  *   - "access callback": A function returning a boolean value that determines
  *     whether the user has access rights to this menu item. Defaults to
  *     user_access() unless a value is inherited from a parent menu item.
Index: modules/poll/poll.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v
retrieving revision 1.316
diff -u -p -r1.316 poll.module
--- modules/poll/poll.module	9 Oct 2009 01:00:01 -0000	1.316
+++ modules/poll/poll.module	9 Oct 2009 18:07:28 -0000
@@ -390,9 +390,7 @@ function _poll_choice_form($key, $chid =
  * Menu callback for AHAH additions. Render the new poll choices.
  */
 function poll_choice_js($form, $form_state) {
-  $choice_form = $form['choice_wrapper']['choice'];
-
-  return drupal_render($choice_form);
+  return $form['choice_wrapper']['choice'];
 }
 
 /**
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.395
diff -u -p -r1.395 system.install
--- modules/system/system.install	5 Oct 2009 04:34:04 -0000	1.395
+++ modules/system/system.install	9 Oct 2009 18:07:29 -0000
@@ -919,6 +919,13 @@ function system_schema() {
         'type' => 'text',
         'not null' => FALSE,
       ),
+      'delivery_callback' => array(
+        'description' => 'The name of the function that sends the result of the page_callback function to the browser.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
       'fit' => array(
         'description' => 'A numeric representation of how specific the path is.',
         'type' => 'int',
@@ -2584,6 +2591,14 @@ function system_update_7039() {
 }
 
 /**
+ * Adds 'delivery_callback' field to the {menu_router} table to allow a custom
+ * function to be used for final page rendering and sending to browser.
+ */
+function system_update_7040() {
+  db_add_field('menu_router', 'delivery_callback', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.803
diff -u -p -r1.803 system.module
--- modules/system/system.module	9 Oct 2009 07:48:06 -0000	1.803
+++ modules/system/system.module	9 Oct 2009 18:07:29 -0000
@@ -303,6 +303,9 @@ function system_element_info() {
     '#attributes' => array(),
     '#items' => array(),
   );
+  $types['ajax_commands'] = array(
+    '#ajax_commands' => array(),
+  );
 
   // Input elements.
   $types['submit'] = array(
@@ -488,6 +491,7 @@ function system_menu() {
   $items['system/ajax'] = array(
     'title' => 'AHAH callback',
     'page callback' => 'ajax_form_callback',
+    'delivery callback' => 'ajax_deliver',
     'access callback' => TRUE,
     'type' => MENU_CALLBACK,
     'file path' => 'includes',
@@ -1667,7 +1671,7 @@ function system_admin_menu_block($item) 
   $default_task = NULL;
   $has_subitems = FALSE;
   $result = db_query("
-    SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.theme_callback, m.theme_arguments, m.type, m.description, m.path, m.weight as router_weight, ml.*
+    SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.delivery_callback, m.title, m.title_callback, m.title_arguments, m.theme_callback, m.theme_arguments, m.type, m.description, m.path, m.weight as router_weight, ml.*
     FROM {menu_router} m 
     LEFT JOIN {menu_links} ml ON m.path = ml.router_path
     WHERE (ml.plid = :plid AND ml.menu_name = :name AND hidden = 0) OR (m.tab_parent = :path AND m.type IN (:local_task, :default_task))", array(':plid' => $item['mlid'], ':name' => $item['menu_name'], ':path' => $item['path'], ':local_task' => MENU_LOCAL_TASK, ':default_task' => MENU_DEFAULT_LOCAL_TASK), array('fetch' => PDO::FETCH_ASSOC));
@@ -2355,7 +2359,7 @@ function system_get_module_admin_tasks($
 
   if (empty($items)) {
     $result = db_query("
-       SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.theme_callback, m.theme_arguments, m.type, ml.*
+       SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.delivery_callback, m.title, m.title_callback, m.title_arguments, m.theme_callback, m.theme_arguments, m.type, ml.*
        FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.link_path LIKE 'admin/%' AND hidden >= 0 AND module = 'system' AND m.number_parts > 2", array(), array('fetch' => PDO::FETCH_ASSOC));
     foreach ($result as $item) {
       _menu_link_translate($item);
