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	15 Oct 2009 04:41:40 -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();
+menu_execute_active_handler();
Index: includes/ajax.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/ajax.inc,v
retrieving revision 1.14
diff -u -p -r1.14 ajax.inc
--- includes/ajax.inc	13 Oct 2009 21:16:42 -0000	1.14
+++ includes/ajax.inc	15 Oct 2009 04:41:40 -0000
@@ -291,30 +291,56 @@ 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 $page_callback_result
+ *   The result of a page callback. Can be NULL, an integer menu status, a
+ *   string of html, or a renderable array.
+ */
+function ajax_deliver($page_callback_result) {
+  $commands = array();
+  if (!isset($page_callback_result)) {
+    // 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($page_callback_result)) {
+    switch ($page_callback_result) {
+      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($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax_commands')) {
+    // Complex AJAX callbacks can return a result that contains a specific
+    // set of commands to send to the browser. 
+    if (isset($page_callback_result['#ajax_commands'])) {
+      $commands = $page_callback_result['#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($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result);
+    $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.1017
diff -u -p -r1.1017 common.inc
--- includes/common.inc	13 Oct 2009 21:16:42 -0000	1.1017
+++ includes/common.inc	15 Oct 2009 04:41:40 -0000
@@ -665,75 +665,45 @@ function drupal_goto($path = '', array $
 }
 
 /**
- * Generates a site offline message.
+ * Deliver a "site is under maintenance" message to the browser.
+ *
+ * Whenever possible, write page callback functions to return MENU_SITE_OFFLINE
+ * instead of calling drupal_site_offline(). However, until Drupal implements
+ * exception handling, if you're writing a function that is called by a
+ * page callback function, and returning MENU_SITE_OFFLINE from your function
+ * would not chain up back to menu_execute_active_handler(), then calling
+ * drupal_site_offline() is appropriate.
  */
 function drupal_site_offline() {
-  drupal_maintenance_theme();
-  drupal_add_http_header('503 Service unavailable');
-  drupal_set_title(t('Site under maintenance'));
-  print theme('maintenance_page', array('content' => 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')))))));
+  drupal_deliver_page(MENU_SITE_OFFLINE);
 }
 
 /**
- * Generates a 404 error if the request can not be handled.
+ * Deliver a "page not found" error to the browser.
+ *
+ * Whenever possible, write page callback functions to return MENU_NOT_FOUND
+ * instead of calling drupal_not_found(). However, until Drupal implements
+ * exception handling, if you're writing a function that is called by a
+ * page callback function, and returning MENU_NOT_FOUND from your function
+ * would not chain up back to menu_execute_active_handler(), then calling
+ * drupal_not_found() is appropriate.
  */
 function drupal_not_found() {
-  drupal_add_http_header('404 Not Found');
-
-  watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
-
-  // Keep old path for reference, and to allow forms to redirect to it.
-  if (!isset($_GET['destination'])) {
-    $_GET['destination'] = $_GET['q'];
-  }
-
-  $path = drupal_get_normal_path(variable_get('site_404', ''));
-  if ($path && $path != $_GET['q']) {
-    // Custom 404 handler. Set the active item in case there are tabs to
-    // display, or other dependencies on the path.
-    menu_set_active_item($path);
-    $return = menu_execute_active_handler($path);
-  }
-
-  if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
-    // Standard 404 handler.
-    drupal_set_title(t('Page not found'));
-    $return = t('The requested page could not be found.');
-  }
-
-  drupal_set_page_content($return);
-  $page = element_info('page');
-  print drupal_render_page($page);
+  drupal_deliver_page(MENU_NOT_FOUND);
 }
 
 /**
- * Generates a 403 error if the request is not allowed.
+ * Deliver a "access denied" error to the browser.
+ *
+ * Whenever possible, write page callback functions to return MENU_ACCESS_DENIED
+ * instead of calling drupal_access_denied(). However, until Drupal implements
+ * exception handling, if you're writing a function that is called by a
+ * page callback function, and returning MENU_ACCESS_DENIED from your function
+ * would not chain up back to menu_execute_active_handler(), then calling
+ * drupal_access_denied() is appropriate.
  */
 function drupal_access_denied() {
-  drupal_add_http_header('403 Forbidden');
-  watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
-
-  // Keep old path for reference, and to allow forms to redirect to it.
-  if (!isset($_GET['destination'])) {
-    $_GET['destination'] = $_GET['q'];
-  }
-
-  $path = drupal_get_normal_path(variable_get('site_403', ''));
-  if ($path && $path != $_GET['q']) {
-    // Custom 403 handler. Set the active item in case there are tabs to
-    // display or other dependencies on the path.
-    menu_set_active_item($path);
-    $return = menu_execute_active_handler($path);
-  }
-
-  if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
-    // Standard 403 handler.
-    drupal_set_title(t('Access denied'));
-    $return = t('You are not authorized to access this page.');
-  }
-
-  print drupal_render_page($return);
+  drupal_deliver_page(MENU_ACCESS_DENIED);
 }
 
 /**
@@ -2592,6 +2562,155 @@ function l($text, $path, array $options 
 }
 
 /**
+ * Deliver a page callback result to the browser in the format appropriate.
+ *
+ * This function is most commonly called by menu_execute_active_handler(), but
+ * can also be called by error conditions such as drupal_not_found(),
+ * drupal_access_denied(), and drupal_site_offline().
+ *
+ * When a user requests a page, index.php calls menu_execute_active_handler()
+ * which calls the 'page callback' function registered in hook_menu(). The page
+ * callback function can return NULL (to indicate no content), an integer
+ * menu status constant to indicate an error condition, a string of HTML
+ * content, or a renderable array of content. It is recommended wherever
+ * possible to return a renderable array rather than a string of HTML, because
+ * doing so gives modules more flexibility in customizing the final result.
+ *
+ * When the page callback returns its constructed content to
+ * menu_execute_active_handler(), this functions gets called. The purpose of
+ * this function is to determine the most appropriate 'delivery callback'
+ * function to route the contents to. The delivery callback function then
+ * sends the content to the browser in the needed format.
+ *
+ * For example, the same page callback function can be used for an HTML
+ * version of the page and an AJAX version of the page. The page callback
+ * function just needs to decide what content is to be returned and the
+ * delivery callback function will send it as an HTML page or an AJAX
+ * response, as appropriate.
+ *
+ * In order for page callbacks to be reusable in different delivery formats,
+ * they should not issue any "print" or "echo" statements, but instead just
+ * return content. 
+ *
+ * @param $page_callback_result
+ *   The result of a page callback. Can be NULL, an integer menu status, a
+ *   string of html, or a renderable array.
+ * @param $default_delivery_callback
+ *   (Optional) If given, it is the name of a delivery function most likely
+ *   to be appropriate for the page request. If not given, it is determined
+ *   from the menu router information of the current page. In either case,
+ *   modules have a final chance to alter which delivery function is called. 
+ *
+ * @see menu_execute_active_handler()
+ * @see hook_menu()
+ */
+function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL) {
+  if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) {
+    drupal_alter('menu_active_handler', $router_item);
+    $default_delivery_callback = $router_item['delivery_callback'];
+  }
+  $delivery_callback = !empty($default_delivery_callback) ? $default_delivery_callback : 'drupal_deliver_html_page';
+  // Give modules a final chance to alter the delivery callback used. This is
+  // for modules that need to decide which delivery callback to use based on
+  // information made available during page callback execution and for pages
+  // without router items.
+  drupal_alter('page_delivery_callback', $delivery_callback);
+  if (function_exists($delivery_callback)) {
+    $delivery_callback($page_callback_result);
+  }
+}
+
+/**
+ * Package and send the result of a page callback to the browser as a normal
+ * HTML page.
+ *
+ * @param $page_callback_result
+ *   The result of a page callback. Can be NULL, an integer menu status, a
+ *   string of html, or a renderable array.
+ * 
+ * @see drupal_deliver_page
+ */
+function drupal_deliver_html_page($page_callback_result) {
+  // Menu status constants are integers; page content is a string or array.
+  if (is_int($page_callback_result)) {
+    // @todo: Break these up into separate functions?
+    switch ($page_callback_result) {   
+      case MENU_NOT_FOUND:
+        // Print a 404 page.
+        drupal_add_http_header('404 Not Found');
+
+        watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+        // Keep old path for reference, and to allow forms to redirect to it.
+        if (!isset($_GET['destination'])) {
+          $_GET['destination'] = $_GET['q'];
+        }
+
+        $path = drupal_get_normal_path(variable_get('site_404', ''));
+        if ($path && $path != $_GET['q']) {
+          // Custom 404 handler. Set the active item in case there are tabs to
+          // display, or other dependencies on the path.
+          menu_set_active_item($path);
+          $return = menu_execute_active_handler($path, FALSE);
+        }
+
+        if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+          // Standard 404 handler.
+          drupal_set_title(t('Page not found'));
+          $return = t('The requested page could not be found.');
+        }
+
+        drupal_set_page_content($return);
+        $page = element_info('page');
+        print drupal_render_page($page);
+        break;
+      case MENU_ACCESS_DENIED:
+        // Print a 403 page.
+        drupal_add_http_header('403 Forbidden');
+        watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+        // Keep old path for reference, and to allow forms to redirect to it.
+        if (!isset($_GET['destination'])) {
+          $_GET['destination'] = $_GET['q'];
+        }
+
+        $path = drupal_get_normal_path(variable_get('site_403', ''));
+        if ($path && $path != $_GET['q']) {
+          // Custom 403 handler. Set the active item in case there are tabs to
+          // display or other dependencies on the path.
+          menu_set_active_item($path);
+          $return = menu_execute_active_handler($path, FALSE);
+        }
+
+        if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+          // Standard 403 handler.
+          drupal_set_title(t('Access denied'));
+          $return = t('You are not authorized to access this page.');
+        }
+
+        print drupal_render_page($return);
+        break;
+      case MENU_SITE_OFFLINE:
+        // Print a 503 page.
+        drupal_maintenance_theme();
+        drupal_add_http_header('503 Service unavailable');
+        drupal_set_title(t('Site under maintenance'));
+        print theme('maintenance_page', array('content' => 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;
+    }
+  }
+  elseif (isset($page_callback_result)) {
+    // Print anything besides a menu constant, assuming it's not NULL or
+    // undefined.
+    print drupal_render_page($page_callback_result);
+  }
+
+  // Perform end-of-request tasks.
+  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.352
diff -u -p -r1.352 menu.inc
--- includes/menu.inc	11 Oct 2009 19:39:30 -0000	1.352
+++ includes/menu.inc	15 Oct 2009 04:41:41 -0000
@@ -401,29 +401,52 @@ function menu_get_item($path = NULL, $ro
 }
 
 /**
- * Execute the page callback associated with the current path
+ * Execute the page callback associated with the current path.
+ * 
+ * @param $path
+ *   The drupal path whose handler is to be be executed. If set to NULL, then
+ *   the current path is used.
+ * @param $deliver
+ *   A boolean. If set to TRUE, the page is sent to the browser using the
+ *   appropriate delivery callback. If set to FALSE, the result of the page
+ *   callback is simply returned to the client code.
  */
-function menu_execute_active_handler($path = NULL) {
+function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
   if (_menu_site_is_offline()) {
-    return MENU_SITE_OFFLINE;
+    $page_callback_result = MENU_SITE_OFFLINE;
   }
-  // Rebuild if we know it's needed, or if the menu masks are missing which
-  // occurs rarely, likely due to a race condition of multiple rebuilds.
-  if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
-    menu_rebuild();
-  }
-  if ($router_item = menu_get_item($path)) {
-    if ($router_item['access']) {
-      if ($router_item['file']) {
-        require_once DRUPAL_ROOT . '/' . $router_item['file'];
+  else {
+    // Rebuild if we know it's needed, or if the menu masks are missing which
+    // occurs rarely, likely due to a race condition of multiple rebuilds.
+    if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
+      menu_rebuild();
+    }
+    if ($router_item = menu_get_item($path)) {
+      drupal_alter('menu_active_handler', $router_item, $path);
+      if ($router_item['access']) {
+        if ($router_item['file']) {
+          require_once DRUPAL_ROOT . '/' . $router_item['file'];
+        }
+        $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
+      }
+      else {
+        $page_callback_result = MENU_ACCESS_DENIED;
       }
-      return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
     }
     else {
-      return MENU_ACCESS_DENIED;
+      $page_callback_result = MENU_NOT_FOUND;
     }
   }
-  return MENU_NOT_FOUND;
+
+  // Deliver the result of the page callback to the browser, or if requested,
+  // return it raw, so calling code can do more processing.
+  if ($deliver) {
+    $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL;
+    drupal_deliver_page($page_callback_result, $default_delivery_callback);
+  }
+  else {
+    return $page_callback_result;
+  }
 }
 
 /**
@@ -935,6 +958,7 @@ function menu_tree_all_data($menu_name, 
         'access_arguments',
         'page_callback',
         'page_arguments',
+        'delivery_callback',
         'title',
         'title_callback',
         'title_arguments',
@@ -1118,6 +1142,7 @@ function menu_tree_page_data($menu_name,
           'access_arguments',
           'page_callback',
           'page_arguments',
+          'delivery_callback',
           'title',
           'title_callback',
           'title_arguments',
@@ -2833,6 +2858,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'];
@@ -2858,6 +2887,7 @@ function _menu_router_build($callbacks) 
       'access callback' => '',
       'page arguments' => array(),
       'page callback' => '',
+      'delivery callback' => '',
       'block callback' => '',
       'title arguments' => array(),
       'title callback' => 't',
@@ -2905,6 +2935,7 @@ function _menu_router_save($menu, $masks
       'access_arguments',
       'page_callback',
       'page_arguments',
+      'delivery_callback',
       'fit',
       'number_parts',
       'tab_parent',
@@ -2932,6 +2963,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.518
diff -u -p -r1.518 book.module
--- modules/book/book.module	11 Oct 2009 03:07:17 -0000	1.518
+++ modules/book/book.module	15 Oct 2009 04:41:41 -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.18
diff -u -p -r1.18 book.pages.inc
--- modules/book/book.pages.inc	11 Oct 2009 03:07:17 -0000	1.18
+++ modules/book/book.pages.inc	15 Oct 2009 04:41:41 -0000
@@ -255,5 +255,8 @@ function book_form_update() {
     $commands[] = ajax_command_replace(NULL, drupal_render($form['book']['plid']));
   }
 
-  ajax_render($commands);
+  // @todo: We could and should just return $form['book']['plid'] and skip the
+  // ajax_command_replace() above. But for now, this provides a test case of
+  // returning an AJAX commands array.
+  return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
 }
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.784
diff -u -p -r1.784 comment.module
--- modules/comment/comment.module	13 Oct 2009 05:17:15 -0000	1.784
+++ modules/comment/comment.module	15 Oct 2009 04:41:41 -0000
@@ -364,7 +364,7 @@ function comment_permalink($comment) {
     drupal_add_link(array('rel' => 'canonical', 'href' => url('node/' . $node->nid)));
 
     // Return the node view, this will show the correct comment in context.
-    return menu_execute_active_handler('node/' . $node->nid);
+    return menu_execute_active_handler('node/' . $node->nid, FALSE);
   }
   drupal_not_found();
 }
Index: modules/file/file.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.module,v
retrieving revision 1.7
diff -u -p -r1.7 file.module
--- modules/file/file.module	12 Oct 2009 05:22:57 -0000	1.7
+++ modules/file/file.module	15 Oct 2009 04:41:41 -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	15 Oct 2009 04:41:41 -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.16
diff -u -p -r1.16 menu.api.php
--- modules/menu/menu.api.php	11 Oct 2009 06:05:53 -0000	1.16
+++ modules/menu/menu.api.php	15 Oct 2009 04:41:41 -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_html_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.317
diff -u -p -r1.317 poll.module
--- modules/poll/poll.module	11 Oct 2009 03:07:19 -0000	1.317
+++ modules/poll/poll.module	15 Oct 2009 04:41:41 -0000
@@ -382,9 +382,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.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.87
diff -u -p -r1.87 system.api.php
--- modules/system/system.api.php	14 Oct 2009 10:56:35 -0000	1.87
+++ modules/system/system.api.php	15 Oct 2009 04:41:41 -0000
@@ -2507,5 +2507,77 @@ function hook_date_formats_alter(&$forma
 }
 
 /**
+ * Alters the router item for the active menu handler.
+ * 
+ * Called by menu_execute_active_handler() to allow modules to alter the
+ * information that will be used to handle the page request.
+ * 
+ * @param $router_item
+ *   An array with the following keys:
+ *   - access: Boolean. Whether the user is allowed to see this page.
+ *   - file: A path to a file to include prior to invoking the page callback.
+ *   - page_callback: The function to call to build the page content.
+ *   - page_arguments: Arguments to pass to the page callback.
+ *   - delivery_callback: The function to call to deliver the result of the
+ *     page callback to the browser.
+ * @param $path
+ *   The drupal path that was used for retrieving the router item.
+ *
+ * @see menu_execute_active_handler()
+ * @see hook_menu()
+ */
+function hook_menu_active_handler_alter(&$router_item, $path = NULL) {
+  // Turn off access for all pages for all users.
+  $router_item['access'] = FALSE;
+}
+
+/**
+ * Alters the delivery callback used to send the result of the page callback
+ * to the browser.
+ *
+ * Called by drupal_deliver() to allow modules to alter how the
+ * page is delivered to the browser.
+ *
+ * This hook is intended for altering the delivery callback based on
+ * information unrelated to the path of the page accessed. For example,
+ * it can be used to set the delivery callback based on a HTTP request
+ * header (as shown in the code sample). To specify a delivery callback
+ * based on path information, use hook_menu() or
+ * hook_menu_active_handler_alter().
+ *
+ * This hook can also be used as an API function that can be used to explicitly
+ * set the delivery callback from some other function. For example, for a module
+ * named MODULE:
+ * @code
+ * function MODULE_page_delivery_callback_alter(&$callback, $set = FALSE) {
+ *   static $stored_callback;
+ *   if ($set) {
+ *     $stored_callback = $callback;
+ *   }
+ *   elseif (isset($stored_callback)) {
+ *     $callback = $stored_callback;
+ *   }
+ * }
+ * function SOMEWHERE_ELSE() {
+ *   $desired_delivery_callback = 'foo';
+ *   MODULE_page_delivery_callback_alter($desired_delivery_callback, TRUE);
+ * }
+ * @endcode
+ *
+ * @param $callback
+ *   The name of a function.
+ *
+ * @see drupal_deliver()
+ */
+function hook_page_delivery_callback_alter(&$callback) {
+  // jQuery sets a HTTP_X_REQUESTED_WITH header of 'XMLHttpRequest'.
+  // If a page would normally be delivered as an html page, and it is called
+  // from jQuery, deliver it instead as an AJAX response.
+  if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' && $callback == 'drupal_deliver_html_page') {
+    $callback = 'ajax_deliver';
+  }
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.399
diff -u -p -r1.399 system.install
--- modules/system/system.install	14 Oct 2009 10:56:35 -0000	1.399
+++ modules/system/system.install	15 Oct 2009 04:41:42 -0000
@@ -1005,6 +1005,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',
@@ -2726,6 +2733,14 @@ function system_update_7040() {
 }
 
 /**
+ * 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_7041() {
+  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.811
diff -u -p -r1.811 system.module
--- modules/system/system.module	14 Oct 2009 20:42:47 -0000	1.811
+++ modules/system/system.module	15 Oct 2009 04:41:42 -0000
@@ -307,6 +307,21 @@ function system_element_info() {
     '#attributes' => array(),
     '#items' => array(),
   );
+  $types['ajax_commands'] = array(
+    '#ajax_commands' => array(),
+    // AJAX commands are used by AJAX callbacks to return a set of javascript
+    // commands for the browser to execute. When using the ajax_deliver()
+    // delivery callback, these get rendered by ajax_render(). It's possible
+    // (though not necessarily useful) to create a page callback that returns
+    // AJAX commands that are delivered to the browser using
+    // drupal_deliver_html_page(). In this case, this element would be rendered
+    // using #theme and #theme_wrappers functions as is the case with other
+    // renderable elements. By default, we don't want AJAX commands being
+    // rendered in the context of a normal HTML page, so we don't provide
+    // defaults for #theme or #theme_wrappers. However, modules can set these
+    // properties if they would like (for example, to provide a debugging page
+    // that displays rather than executes AJAX commands).
+  );
 
   // Input elements.
   $types['submit'] = array(
@@ -492,6 +507,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',
@@ -1745,7 +1761,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));
@@ -2461,7 +2477,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);
Index: modules/user/user.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.pages.inc,v
retrieving revision 1.58
diff -u -p -r1.58 user.pages.inc
--- modules/user/user.pages.inc	10 Oct 2009 16:48:39 -0000	1.58
+++ modules/user/user.pages.inc	15 Oct 2009 04:41:42 -0000
@@ -499,7 +499,7 @@ function user_page() {
   global $user;
   if ($user->uid) {
     menu_set_active_item('user/' . $user->uid);
-    return menu_execute_active_handler();
+    return menu_execute_active_handler(NULL, FALSE);
   }
   else {
     return drupal_get_form('user_login');
