Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.594
diff -u -r1.594 system.module
--- modules/system/system.module	1 Apr 2008 20:05:15 -0000	1.594
+++ modules/system/system.module	1 Apr 2008 22:41:25 -0000
@@ -2004,6 +2004,63 @@
 }
 
 /**
+ * Implements hook_theme_modes.
+ * 
+ * Provide a rendering mode for JSON based popup dialogs.
+ */
+function system_theme_modes() {
+  return array(
+    'json/popup' => 'system_json_popup_render'
+  );
+}
+
+/**
+ * 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_json_popup_render() {
+  $args = func_get_args();
+  $hook = array_shift($args);
+  switch ($hook) {
+    case 'page':
+      return drupal_json(array(
+        'status' => 'ok',
+        'title' => drupal_get_title(),
+        'messages' => theme('status_messages'),
+        'path' => $_GET['q'],
+        'content' => $args[0],
+      ));
+    default:
+      array_unshift($args, $hook);
+      return call_user_func_array('theme_render', $args);
+  }
+}
+
+/**
+ * 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
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	1 Apr 2008 22:41:18 -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/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.268
diff -u -r1.268 form.inc
--- includes/form.inc	15 Mar 2008 11:02:47 -0000	1.268
+++ includes/form.inc	1 Apr 2008 22:41:21 -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/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.418
diff -u -r1.418 theme.inc
--- includes/theme.inc	25 Mar 2008 14:10:01 -0000	1.418
+++ includes/theme.inc	1 Apr 2008 22:41:22 -0000
@@ -463,7 +463,7 @@
 }
 
 /**
- * Generate the themed output.
+ * Generate themed output for the current rendering mode.
  *
  * All requests for theme hooks must go through this function. It examines
  * the request and routes it to the appropriate theme function. The theme
@@ -547,9 +547,41 @@
  * @param ...
  *   Additional arguments to pass along to the theme function.
  * @return
- *   An HTML string that generates the themed output.
+ *   An string that generates the themed output according to the current render
+ *   mode. HTML is the default output.
  */
 function theme() {
+  global $render_mode;
+  $args = func_get_args();
+
+  // Check if we're using a custom render mode.
+  if ($render_mode != 'xhtml/plain') {
+    static $theme_renderers;
+    if (!isset($theme_renderers)) {
+      $theme_renderers = module_invoke_all('theme_modes');
+    }
+    $theme_renderer = isset($theme_renderers[$render_mode]) ? $theme_renderers[$render_mode] : NULL;
+
+    // If a different theme rendering system is available, call it instead of the default.
+    if (function_exists($theme_renderer)) {
+      return call_user_func_array($theme_renderer, $args);  
+    }
+  }
+
+  // If not using a custom render mode, use the default renderer.
+  return call_user_func_array('theme_render', $args);
+}
+
+/**
+ * The default theme mode renderer. Generates the HTML output for most requests.
+ * 
+ * Invokes the theme system and generates the page layout when making a normal
+ * page request. This function is called by theme() when using the default
+ * rendering mode.
+ * 
+ * @see theme()
+ */
+function theme_render() {
   $args = func_get_args();
   $hook = array_shift($args);
 
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.762
diff -u -r1.762 common.inc
--- includes/common.inc	31 Mar 2008 20:50:05 -0000	1.762
+++ includes/common.inc	1 Apr 2008 22:41:20 -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.
  */
