Index: includes/ajax.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/ajax.inc,v
retrieving revision 1.32
diff -u -p -r1.32 ajax.inc
--- includes/ajax.inc	13 Sep 2010 01:09:25 -0000	1.32
+++ includes/ajax.inc	25 Sep 2010 18:22:03 -0000
@@ -26,14 +26,34 @@
  * also return a richer set of @link ajax_commands AJAX framework commands @endlink.
  *
  * Standard form handling is as follows:
- *   - A form element has a #ajax member.
+ *   - A form element has a #ajax property that includes #ajax['callback'] and
+ *     omits #ajax['path']. See below about using #ajax['path'] to implement
+ *     advanced use-cases that require something other than standard form
+ *     handling.
  *   - On the specified element, AJAX processing is triggered by a change to
  *     that element.
- *   - The form is submitted and rebuilt.
- *   - The function named by #ajax['callback'] is called, which returns content
- *     or an array of AJAX framework commands.
- *   - The content returned by the callback replaces the div on the page
- *     referenced by #ajax['wrapper'].
+ *   - The browser submits an HTTP POST request to the 'system/ajax' Drupal
+ *     path.
+ *   - The menu page callback for 'system/ajax', ajax_form_callback(), calls
+ *     drupal_process_form() to process the form submission and rebuild the
+ *     form if necessary. The form is processed in much the same way as if it
+ *     were submitted without AJAX, with the same #process functions and
+ *     validation and submission handlers called in either case, making it easy
+ *     to create AJAX-enabled forms that degrade gracefully when JavaScript is
+ *     disabled.
+ *   - After form processing is complete, ajax_form_callback() calls the
+ *     function named by #ajax['callback'], which returns the form element that
+ *     has been updated and needs to be returned to the browser, or
+ *     alternatively, an array of custom AJAX commands.
+ *   - The page delivery callback for 'system/ajax', ajax_deliver(), renders the
+ *     element returned by #ajax['callback'], and returns the JSON string
+ *     created by ajax_render() to the browser.
+ *   - The browser unserializes the returned JSON string into an array of
+ *     command objects and executes each command, resulting in the old page
+ *     content within and including the HTML element specified by
+ *     #ajax['wrapper'] being replaced by the new content returned by
+ *     #ajax['callback'], using a JavaScript animation effect specified by
+ *     #ajax['effect'].
  *
  * A simple example of basic AJAX use from the
  * @link http://drupal.org/project/examples Examples module @endlink follows:
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.495
diff -u -p -r1.495 form.inc
--- includes/form.inc	24 Sep 2010 21:36:22 -0000	1.495
+++ includes/form.inc	25 Sep 2010 18:36:37 -0000
@@ -219,11 +219,13 @@ function drupal_get_form($form_id) {
  *     request (so a browser refresh does not re-submit the form). However, if
  *     'rebuild' has been set to TRUE, then a new copy of the form is
  *     immediately built and sent to the browser; instead of a redirect. This is
- *     used for multi-step forms, such as wizards and confirmation forms. Also,
- *     if a form validation handler has set 'rebuild' to TRUE and a validation
- *     error occurred, then the form is rebuilt prior to being returned,
- *     enabling form elements to be altered, as appropriate to the particular
- *     validation error.
+ *     used for multi-step forms, such as wizards and confirmation forms.
+ *     Normally, $form_state['rebuild'] is set by a submit handler, since it is
+ *     usually logic within a submit handler that determines whether a form is
+ *     done or requires another step. However, a validation handler may already
+ *     set $form_state['rebuild'] to cause the form processing to bypass submit
+ *     handlers and rebuild the form instead, even if there are no validation
+ *     errors.
  *   - input: An array of input that corresponds to $_POST or $_GET, depending
  *     on the 'method' chosen (see below).
  *   - method: The HTTP form method to use for finding the input for this form.
@@ -362,6 +364,7 @@ function form_state_defaults() {
     'build_info' => array('args' => array()),
     'temporary' => array(),
     'submitted' => FALSE,
+    'executed' => FALSE,
     'programmed' => FALSE,
     'cache'=> FALSE,
     'method' => 'post',
@@ -526,6 +529,7 @@ function form_state_keys_no_cache() {
     'method',
     'submit_handlers',
     'submitted',
+    'executed',
     'validate_handlers',
     'values',
   );
@@ -804,23 +808,39 @@ function drupal_process_form($form_id, &
     if (!empty($form_state['programmed'])) {
       return;
     }
-  }
 
-  // If $form_state['rebuild'] has been set and input has been processed without
-  // validation errors, we're in a multi-step workflow that is not yet complete.
-  // We need to construct a new $form based on the changes made to $form_state
-  // during this request.
-  if ($form_state['rebuild'] && $form_state['process_input'] && !form_get_errors()) {
-    $form = drupal_rebuild_form($form_id, $form_state, $form);
+    // If $form_state['rebuild'] has been set and input has been processed
+    // without validation errors, we are in a multi-step workflow that is not
+    // yet complete. A new $form needs to be constructed based on the changes
+    // made to $form_state during this request. Normally, a submit handler sets
+    // $form_state['rebuild'] if a fully executed form requires another step.
+    // However, for forms that have not been fully executed (e.g., AJAX
+    // submissions triggered by non-buttons), there is no submit handler to set
+    // $form_state['rebuild']. It would not make sense to redisplay the
+    // identical form without an error for the user to correct, so we also
+    // rebuild error-free non-executed forms, regardless of
+    // $form_state['rebuild'].
+    // @todo D8: Simplify this logic; considering AJAX and non-HTML front-ends,
+    //   along with element-level #submit properties, it makes no sense to have
+    //   divergent form execution based on whether the triggering element has
+    //   #executes_submit_callback set to TRUE.
+    if (($form_state['rebuild'] || !$form_state['executed']) && !form_get_errors()) {
+      // Form building functions (e.g., _form_builder_handle_input_element())
+      // may use $form_state['rebuild'] to determine if they are running in the
+      // context of a rebuild, so ensure it is set.
+      $form_state['rebuild'] = TRUE;
+      $form = drupal_rebuild_form($form_id, $form_state, $form);
+    }
   }
+
   // After processing the form, the form builder or a #process callback may
   // have set $form_state['cache'] to indicate that the form and form state
   // shall be cached. But the form may only be cached if the 'no_cache' property
   // is not set to TRUE. Only cache $form as it was prior to form_builder(),
   // because form_builder() must run for each request to accomodate new user
-  // input. We do not cache here for forms that have been rebuilt, because
-  // drupal_rebuild_form() takes care of that.
-  elseif ($form_state['cache'] && empty($form_state['no_cache'])) {
+  // input. Rebuilt forms are not cached here, because drupal_rebuild_form()
+  // already takes care of that.
+  if (!$form_state['rebuild'] && $form_state['cache'] && empty($form_state['no_cache'])) {
     form_set_cache($form['#build_id'], $unprocessed_form, $form_state);
   }
 }
Index: modules/book/book.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.admin.inc,v
retrieving revision 1.36
diff -u -p -r1.36 book.admin.inc
--- modules/book/book.admin.inc	26 Jun 2010 21:32:20 -0000	1.36
+++ modules/book/book.admin.inc	25 Sep 2010 18:22:03 -0000
@@ -91,7 +91,6 @@ function book_admin_edit($form, $form_st
 function book_admin_edit_validate($form, &$form_state) {
   if ($form_state['values']['tree_hash'] != $form_state['values']['tree_current_hash']) {
     form_set_error('', t('This book has been modified by another user, the changes could not be saved.'));
-    $form_state['rebuild'] = TRUE;
   }
 }
 
Index: modules/image/image.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/image/image.admin.inc,v
retrieving revision 1.23
diff -u -p -r1.23 image.admin.inc
--- modules/image/image.admin.inc	24 Sep 2010 21:36:22 -0000	1.23
+++ modules/image/image.admin.inc	25 Sep 2010 18:22:03 -0000
@@ -166,7 +166,6 @@ function image_style_form($form, &$form_
 function image_style_form_add_validate($form, &$form_state) {
   if (!$form_state['values']['new']) {
     form_error($form['effects']['new']['new'], t('Select an effect to add.'));
-    $form_state['rebuild'] = TRUE;
   }
 }
 
Index: modules/simpletest/tests/form.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v
retrieving revision 1.68
diff -u -p -r1.68 form.test
--- modules/simpletest/tests/form.test	24 Sep 2010 21:36:22 -0000	1.68
+++ modules/simpletest/tests/form.test	25 Sep 2010 18:33:18 -0000
@@ -788,24 +780,21 @@ class FormsFormStorageTestCase extends D
     $this->assertText('Form constructions: 1');
 
     $edit = array('title' => 'new', 'value' => 'value_is_set');
-    // Reload the form, but don't rebuild.
-    $this->drupalPost(NULL, $edit, 'Reload');
-    $this->assertText('Form constructions: 2');
 
-    // Now use form rebuilding triggered by a submit button.
+    // Use form rebuilding triggered by a submit button.
     $this->drupalPost(NULL, $edit, 'Continue submit');
+    $this->assertText('Form constructions: 2');
     $this->assertText('Form constructions: 3');
-    $this->assertText('Form constructions: 4');
 
     // Reset the form to the values of the storage, using a form rebuild
     // triggered by button of type button.
     $this->drupalPost(NULL, array('title' => 'changed'), 'Reset');
     $this->assertFieldByName('title', 'new', 'Values have been resetted.');
     // After rebuilding, the form has been cached.
-    $this->assertText('Form constructions: 5');
+    $this->assertText('Form constructions: 4');
 
     $this->drupalPost(NULL, $edit, 'Save');
-    $this->assertText('Form constructions: 5');
+    $this->assertText('Form constructions: 4');
     $this->assertText('Title: new', t('The form storage has stored the values.'));
   }
 
@@ -817,11 +806,8 @@ class FormsFormStorageTestCase extends D
     $this->assertText('Form constructions: 1');
 
     $edit = array('title' => 'new', 'value' => 'value_is_set');
-    // Reload the form, but don't rebuild.
-    $this->drupalPost(NULL, $edit, 'Reload');
-    $this->assertNoText('Form constructions');
 
-    // Now use form rebuilding triggered by a submit button.
+    // Use form rebuilding triggered by a submit button.
     $this->drupalPost(NULL, $edit, 'Continue submit');
     $this->assertText('Form constructions: 2');
 
Index: modules/simpletest/tests/form_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v
retrieving revision 1.49
diff -u -p -r1.49 form_test.module
--- modules/simpletest/tests/form_test.module	24 Sep 2010 21:36:22 -0000	1.49
+++ modules/simpletest/tests/form_test.module	25 Sep 2010 18:22:03 -0000
@@ -522,16 +522,10 @@ function form_test_storage_form($form, &
     '#default_value' => $form_state['storage']['thing']['value'],
     '#element_validate' => array('form_test_storage_element_validate_value_cached'),
   );
-  $form['button'] = array(
-    '#type' => 'button',
-    '#value' => 'Reload',
-    // Reload the form (don't rebuild), thus we start at the initial step again.
-  );
   $form['continue_button'] = array(
     '#type' => 'button',
     '#value' => 'Reset',
     // Rebuilds the form without keeping the values.
-    '#validate' => array('form_storage_test_form_continue_validate'),
   );
   $form['continue_submit'] = array(
     '#type' => 'submit',
@@ -577,15 +571,6 @@ function form_storage_test_form_continue
 }
 
 /**
- * Form validation handler, which doesn't preserve the values but rebuilds the
- * form. We cannot use a submit handler here, as buttons of type button don't
- * submit the form.
- */
-function form_storage_test_form_continue_validate($form, &$form_state) {
-  $form_state['rebuild'] = TRUE;
-}
-
-/**
  * Form submit handler to finish multi-step form.
  */
 function form_test_storage_form_submit($form, &$form_state) {
Index: modules/taxonomy/taxonomy.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.admin.inc,v
retrieving revision 1.107
diff -u -p -r1.107 taxonomy.admin.inc
--- modules/taxonomy/taxonomy.admin.inc	31 Jul 2010 13:01:50 -0000	1.107
+++ modules/taxonomy/taxonomy.admin.inc	25 Sep 2010 18:22:03 -0000
@@ -868,8 +868,6 @@ function taxonomy_form_term_submit($form
 
   $form_state['values']['tid'] = $term->tid;
   $form_state['tid'] = $term->tid;
-  // Do not rebuild here. The term is saved by now and the form should clear.
-  $form_state['rebuild'] = FALSE;
 }
 
 /**
