Index: index.php
===================================================================
RCS file: /cvs/drupal/drupal/index.php,v
retrieving revision 1.93
diff -u -p -r1.93 index.php
--- index.php	6 Apr 2007 13:27:20 -0000	1.93
+++ index.php	20 May 2007 23:28:04 -0000
@@ -12,25 +12,5 @@
 require_once './includes/bootstrap.inc';
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 
-$return = menu_execute_active_handler();
-
-// Menu status constants are integers; page content is a string.
-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 any value (including an empty string) except NULL or undefined:
-  print theme('page', $return);
-}
-
-drupal_page_footer();
+drupal_page_content();
+drupal_page_footer();
\ No newline at end of file
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.641
diff -u -p -r1.641 common.inc
--- includes/common.inc	15 May 2007 20:19:47 -0000	1.641
+++ includes/common.inc	20 May 2007 23:28:10 -0000
@@ -322,8 +322,8 @@ function drupal_goto($path = '', $query 
 function drupal_site_offline() {
   drupal_set_header('HTTP/1.1 503 Service unavailable');
   drupal_set_title(t('Site off-line'));
-  print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message',
-    t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
+  return array('maintenance_page', filter_xss_admin(variable_get('site_offline_message',
+    t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))), NULL);
 }
 
 /**
@@ -353,8 +353,8 @@ function drupal_not_found() {
     drupal_set_title(t('Page not found'));
     $return = '';
   }
-  // To conserve CPU and bandwidth, omit the blocks
-  print theme('page', $return, FALSE);
+
+  return array('page', $return, FALSE);
 }
 
 /**
@@ -383,7 +383,7 @@ function drupal_access_denied() {
     drupal_set_title(t('Access denied'));
     $return = t('You are not authorized to access this page.');
   }
-  print theme('page', $return);
+  return array('page', $return, FALSE);
 }
 
 /**
@@ -1335,6 +1335,78 @@ function l($text, $path, $options = arra
 }
 
 /**
+ * Return an array representing the rendered page for use e.g. in Javascript
+ * applications.
+ *
+ * @return
+ *   An array of page elements.
+ */
+function drupal_page($content) {
+  $return = array();
+  switch ($content) {
+    case MENU_NOT_FOUND:
+    case MENU_ACCESS_DENIED:
+    case MENU_SITE_OFFLINE:
+      $return['status'] = FALSE;
+      $return['page'] = $content;
+      break;
+    default:
+      init_theme();
+      $hooks = theme_get_registry();
+      if (!isset($hooks['page'])) {
+        $return['status'] = FALSE;
+        break;
+      }
+      $return['status'] = TRUE;
+      $info = $hooks['page'];
+      $variables = array('content' => $content);
+      if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) {
+        // This construct ensures that we can keep a reference through
+        // call_user_func_array.
+        $args = array(&$variables, $hook);
+        foreach ($info['preprocess functions'] as $preprocess_function) {
+          if (function_exists($preprocess_function)) {
+            call_user_func_array($preprocess_function, $args);
+          }
+        }
+      }
+      $return['page'] = $variables;
+      break;
+  }
+  return $return;
+}
+
+/**
+ * Call the active menu handler and output the result.
+ */
+function drupal_page_content() {
+  $return = menu_execute_active_handler();
+
+  // Menu status constants are integers; page content is a string.
+  if (is_int($return)) {
+    switch ($return) {
+      case MENU_NOT_FOUND:
+        list($hook, $content, $blocks) = drupal_not_found();
+        break;
+      case MENU_ACCESS_DENIED:
+        list($hook, $content, $blocks) = drupal_access_denied();
+        break;
+      case MENU_SITE_OFFLINE:
+        list($hook, $content, $blocks) = drupal_site_offline();
+        break;
+    }
+  }
+  // Print any value (including an empty string) except NULL or undefined:
+  elseif (isset($return)) {
+    list($hook, $content, $blocks) = array('page', $return, TRUE);
+    $return = TRUE;
+  }
+  if (isset($hook)) {
+    print theme($hook, $content, $blocks, TRUE, $return);
+  }
+}
+
+/**
  * Perform end-of-request tasks.
  *
  * This function sets the page cache if appropriate, and allows modules to
@@ -1349,6 +1421,24 @@ function drupal_page_footer() {
 }
 
 /**
+ * Enable modules to override the standard theme renderer for a theme
+ * call.
+ *
+ * Modules invoking hook_renderer() are called in order of weight,
+ * starting with the highest, and only the first response is accepted.
+ */
+function drupal_set_renderer(&$render_function, $hook, &$variables) {
+  foreach (array_reverse(module_implements('renderer')) as $module) {
+    // Construction needed to pass by reference.
+    $args = array($hook, &$variables);
+    if ($function = call_user_func_array($module .'_renderer', $args)) {
+      $render_function = $function;
+      return;
+    }
+  }
+}
+
+/**
  * Form an associative array from a linear array.
  *
  * This function walks through the provided array and constructs an associative
@@ -1787,6 +1877,21 @@ function drupal_to_js($var) {
 }
 
 /**
+ * Render function to output themed data in JSON format.
+ *
+ * The theme system passes two arguments, a template file path and the
+ * content to be rendered. Here we need only the second.
+ */
+function drupal_render_js() {
+  $args = func_get_args();
+  print drupal_to_js(array(
+    'status' => TRUE,
+    'content' => array_pop($args),
+  ));
+  exit();
+}
+
+/**
  * Wrapper around urlencode() which avoids Apache quirks.
  *
  * Should be used when placing arbitrary data in an URL. Note that Drupal paths
@@ -2332,7 +2437,7 @@ function drupal_common_themes() {
       'arguments' => array('text' => NULL)
     ),
     'page' => array(
-      'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE),
+      'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE, 'menu_result' => TRUE),
       'file' => 'page',
     ),
     'maintenance_page' => array(
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.355
diff -u -p -r1.355 theme.inc
--- includes/theme.inc	6 May 2007 05:47:51 -0000	1.355
+++ includes/theme.inc	20 May 2007 23:28:14 -0000
@@ -472,6 +472,9 @@ function theme() {
       }
       include_once($function_file);
     }
+    // Enable the redirection of output into formats other than XHTML,
+    // e.g. XML, JSON.
+    drupal_set_renderer($info['function'], $hook, $args);
     $output = call_user_func_array($info['function'], $args);
   }
   else {
@@ -541,6 +544,9 @@ function theme() {
         $template_file = $hooks[$hook]['path'] .'/'. $template_file;
       }
     }
+    // Enable the redirection of output into formats other than XHTML,
+    // e.g. XML, JSON.
+    drupal_set_renderer($render_function, $hook, $variables);
     $output = $render_function($template_file, $variables);
   }
   // restore path_to_theme()
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.478
diff -u -p -r1.478 system.module
--- modules/system/system.module	17 May 2007 07:28:42 -0000	1.478
+++ modules/system/system.module	20 May 2007 23:28:21 -0000
@@ -341,6 +341,34 @@ function system_menu() {
   return $items;
 }
 
+/**
+ * Implementation of hook_renderer().
+ *
+ * If a page or item has been requested via jQuery, return it in JSON format.
+ */
+function system_renderer($hook, &$variables) {
+  global $theme_key;
+
+  // jQuery sets a HTTP_X_REQUESTED_WITH header of 'XMLHttpRequest'.
+  if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
+    // If this is a full page request that has reached the rendering stage,
+    // return it in JSON.
+    if ($hook == 'page') {
+      // Strip out all but a subset of page properties.
+      // This prevents security issues involved with passing unthemed
+      // data to the client and minimizes transfer bandwidth.
+      $use_variables = array('content', 'tabs', 'title', 'show_blocks', 'show_messages', 'menu_result', 'sidebar_left', 'sidebar_right', 'help', 'messages', 'mission', 'primary_links', 'secondary_links');
+      $use_variables = array_merge($use_variables, array_keys(system_region_list($theme_key)));
+      foreach (array_keys($variables) as $key) {
+        if (!in_array($key, $use_variables)) {
+          unset($variables[$key]);
+        }
+      }
+      return 'drupal_render_js';
+    }
+  }
+}
+
 function system_init() {
   // Use the administrative theme if the user is looking at a page in the admin/* path.
   if (arg(0) == 'admin') {
