Index: index.php
===================================================================
RCS file: /cvs/drupal/drupal/index.php,v
retrieving revision 1.94
diff -u -r1.94 index.php
--- index.php	26 Dec 2007 08:46:48 -0000	1.94
+++ index.php	10 Mar 2008 21:20:44 -0000
@@ -32,8 +32,7 @@
   }
 }
 elseif (isset($return)) {
-  // Print any value (including an empty string) except NULL or undefined:
-  print theme('page', $return);
+  print render('page', $return);
 }
 
 drupal_page_footer();
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.267
diff -u -r1.267 form.inc
--- includes/form.inc	12 Feb 2008 13:52:32 -0000	1.267
+++ includes/form.inc	10 Mar 2008 21:20:45 -0000
@@ -433,7 +433,9 @@
       // however, we'll skip this and let the calling function examine
       // the resulting $form_state bundle itself.
       if (!$form['#programmed'] && empty($form_state['rebuild']) && empty($form_state['storage'])) {
-        drupal_redirect_form($form, $form_state['redirect']);
+        global $request_mode;
+        // TODO - more comments here, and update the above comments.
+        module_invoke_all('after_process_form', $request_mode, $form, $form_state);
       }
     }
   }
@@ -606,8 +608,13 @@
  * @param $redirect
  *   An optional value containing the destination path to redirect
  *   to if none is specified by the form.
+ * @param $do_goto
+ *   Boolean flag.  
+ *   If true, call to drupal_goto to do the redirect.
+ *   If false, return the url of the redirect, but don't go there.  This allows
+ *   for alternal flows of control, such as AJAX or webservices.
  */
-function drupal_redirect_form($form, $redirect = NULL) {
+function drupal_redirect_form($form, $redirect = NULL, $do_goto = TRUE) {
   $goto = NULL;
   if (isset($redirect)) {
     $goto = $redirect;
@@ -615,17 +622,31 @@
   if ($goto !== FALSE && isset($form['#redirect'])) {
     $goto = $form['#redirect'];
   }
-  if (!isset($goto) || ($goto !== FALSE)) {
-    if (isset($goto)) {
-      if (is_array($goto)) {
-        call_user_func_array('drupal_goto', $goto);
+  if (!isset($goto) || ($goto !== FALSE)) { 
+    // If $do_goto is set, call drupal_goto and do the redirect & exit.        
+    if ($do_goto) {
+      if (isset($goto)) {
+        if (is_array($goto)) {
+          call_user_func_array('drupal_goto', $goto);
+        }
+        else {
+          drupal_goto($goto);
+        }
       }
-      else {
-        drupal_goto($goto);
+      drupal_goto($_GET['q']);
+    }
+    else { // If $do_goto is false, just calculate and return the next url.
+      if (isset($goto)) {
+        if (is_array($goto)) {
+          return call_user_func_array('drupal_get_goto_url', $goto);
+        }
+        else {
+          return drupal_get_goto_url($goto);
+        }
       }
+      return drupal_get_goto_url($_GET['q']);
     }
-    drupal_goto($_GET['q']);
-  }
+  }  
 }
 
 /**
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.206
diff -u -r1.206 bootstrap.inc
--- includes/bootstrap.inc	10 Jan 2008 22:47:17 -0000	1.206
+++ includes/bootstrap.inc	10 Mar 2008 21:20:44 -0000
@@ -271,7 +271,7 @@
  * session name correctly.
  */
 function conf_init() {
-  global $base_url, $base_path, $base_root;
+  global $base_url, $base_path, $base_root, $render_mode, $request_mode;
 
   // Export the following settings.php variables to the global namespace
   global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access;
@@ -337,6 +337,10 @@
     ini_set('session.cookie_domain', $cookie_domain);
   }
   session_name('SESS'. md5($session_name));
+    
+  // TODO - add comments to describe what these globals control.
+  $render_mode = isset($_SERVER['HTTP_X_DRUPAL_RENDER_MODE']) ? $_SERVER['HTTP_X_DRUPAL_RENDER_MODE'] : 'xhtml/plain';
+  $request_mode = isset($_SERVER['HTTP_X_REQUESTED_WITH']) ? $_SERVER['HTTP_X_REQUESTED_WITH'] : 'standard';  
 }
 
 /**
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.759
diff -u -r1.759 common.inc
--- includes/common.inc	20 Feb 2008 13:38:32 -0000	1.759
+++ includes/common.inc	10 Mar 2008 21:20:44 -0000
@@ -293,14 +293,14 @@
  *   supported.
  * @see drupal_get_destination()
  */
-function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
-
+// TODO - update comments to reflect breaking drupal_goto into two functions.
+function drupal_get_goto_url($path = '', $query = NULL, $fragment = NULL) {
   if (isset($_REQUEST['destination'])) {
     extract(parse_url(urldecode($_REQUEST['destination'])));
   }
   else if (isset($_REQUEST['edit']['destination'])) {
     extract(parse_url(urldecode($_REQUEST['edit']['destination'])));
-  }
+  } 
 
   $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE));
   // Remove newlines from the URL to avoid header injection attacks.
@@ -311,19 +311,25 @@
   if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
     module_invoke_all('exit', $url);
   }
+  return $url;
+}
 
+function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
+  $url = drupal_get_goto_url($path, $query, $fragment);
+  
   // Even though session_write_close() is registered as a shutdown function, we
   // need all session data written to the database before redirecting.
   session_write_close();
-
+   
   header('Location: '. $url, TRUE, $http_response_code);
-
+  
   // The "Location" header sends a redirect status code to the HTTP daemon. In
   // some cases this can be wrong, so we make sure none of the code below the
   // drupal_goto() call gets executed upon redirection.
   exit();
 }
 
+
 /**
  * Generates a site off-line message.
  */
@@ -2659,6 +2665,44 @@
   }
 }
 
+/**
+ * Abstract the Drupal rendering system.
+ * Allow renderers other than the theme system to produce content output.
+ * Example of use: render('page', $content);
+ * 
+ * @param 
+ *   Array starting with the element being rendered (ex: 'page'),
+ *   followed by the content arguments. 
+ *
+ * @return 
+ *   Rendered content.
+ */
+function render() {
+  global $render_mode;
+  $args = func_get_args();
+  
+//  TODO - cache the renderer dispatch table.
+//  static $renderers;
+//  if (!$renderers) {
+//    // look in the cache
+//    //$renderers = cache_get('renderers');
+//    if (!$renderers) {
+//      $renderers = module_invoke_all('renderers'); // Build the renderer dispatch table
+//      cache_set('renderers', $renderers);
+//    }
+//  }
+
+  $renderers = module_invoke_all('renderers'); // Build the renderer dispatch table
+  $renderer = $renderers[$render_mode];
+  if ($renderer) {
+    return call_user_func_array($renderer, $args);  
+  }
+  else {
+    drupal_set_message(t('Attempting to use an unsupported render mode %render_mode.', array('%render_mode' => $render_mode)), 'error');
+    $args = func_get_args();
+    return call_user_func_array('theme', $args);
+  }
+}
 
 /**
  * Renders HTML given a structured array tree.
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.592
diff -u -r1.592 system.module
--- modules/system/system.module	20 Feb 2008 13:46:41 -0000	1.592
+++ modules/system/system.module	10 Mar 2008 21:20:45 -0000
@@ -1887,6 +1887,77 @@
 }
 
 /**
+ * Implements hook_renderers.
+ * Provide default system renderers.
+ */
+function system_renderers() {
+  return array(
+    'xhtml/plain' => 'system_render_default',
+    'json/popup' => 'system_render_json_popup'
+  );
+}
+
+/**
+ * Defualt Drupal renderer.
+ * Use the theme system to render to xhtml, for consumption by browsers.
+ *
+ * @return 
+ *   Themed content.
+ */
+function system_render_default() { 
+  $args = func_get_args();
+  return call_user_func_array('theme', $args);
+}
+
+/**
+ * Render the page as JSON, for consumption by AJAX calls.
+ * Optimized for AJAX popups.
+ *
+ * @return 
+ *   JSON object with metadata and themed page content.
+ */
+function system_render_json_popup() {
+  $args = func_get_args();
+  $hook = array_shift($args);
+  switch ($hook) {
+    case 'page': // render('page', $content);
+      return drupal_json(array(
+        'status' => 'ok',
+        'title' => drupal_get_title(),
+        'messages' => theme('status_messages'),
+        'path' => $_GET['q'],
+        'content' => $args[0],
+      ));
+    default: 
+      return drupal_json(array(
+        'status' => 'error',
+        'messages' => "No way to render '$hook' in mode 'json/popup'",
+      ));
+  }
+}
+
+/**
+ * Implements hook_after_process_form
+ */
+function system_after_process_form($request_mode, $form, $form_state) {
+  switch ($request_mode) {
+    case 'XMLHttpRequest':
+      // Request comes from jQuery AJAX call. 
+      // Return status, next page url and form state as JSON.
+      $url = drupal_redirect_form($form, $form_state['redirect'], FALSE);
+      print drupal_json(array(
+        'status' => 'redirect',
+        'path' => $url,
+        'form_state' => $form_state,
+      ));
+      exit;      
+    case 'standard':
+      // Default Drupal behavior, redirect to the next page.
+      drupal_redirect_form($form, $form_state['redirect']);
+  }
+}
+
+/**
  * Format the Powered by Drupal text.
  *
  * @ingroup themeable
