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	23 Sep 2010 02:13:00 -0000
@@ -26,14 +26,33 @@
  * 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 processes 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.
+ *   - 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.494
diff -u -p -r1.494 form.inc
--- includes/form.inc	22 Sep 2010 03:34:57 -0000	1.494
+++ includes/form.inc	23 Sep 2010 02:13:00 -0000
@@ -219,11 +219,15 @@ 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.
+ *     Usually, $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. A validation handler may set
+ *     $form_state['rebuild'] to cause the form processing to bypass the submit
+ *     handlers and rebuild the form instead. However, there is no use-case for
+ *     this in Drupal core, because a form is only rebuilt if it is free of
+ *     validation errors, and it is rare for a validation handler to want to
+ *     prevent submit handler execution in the absence of a validation error.
  *   - 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.
@@ -410,6 +414,13 @@ function form_state_defaults() {
  * @see ajax_form_callback()
  */
 function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {
+  // It's possible for drupal_rebuild_form() to be called without
+  // $form_state['rebuild'] set to TRUE (see drupal_process_form()). Form
+  // building functions (e.g., _form_builder_handle_input_element()) may use
+  // this variable to determine if they are running in the context of a rebuild,
+  // so ensure it is set.
+  $form_state['rebuild'] = TRUE;
+
   $form = drupal_retrieve_form($form_id, $form_state);
 
   // If only parts of the form will be returned to the browser (e.g. AJAX or
@@ -526,6 +537,7 @@ function form_state_keys_no_cache() {
     'method',
     'submit_handlers',
     'submitted',
+    'executed',
     'validate_handlers',
     'values',
   );
@@ -809,8 +821,21 @@ function drupal_process_form($form_id, &
   // 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()) {
+  // during this request. It is usually the submit handler's responsibility to
+  // set $form_state['rebuild'] if the form requires another step, and for fully
+  // executed forms, we do not want to assume a rebuild is needed without it
+  // being explicitly requested. However, for forms that aren't fully executed
+  // (for example, submissions triggered by elements of the 'button' type rather
+  // than the 'submit' type, and for AJAX submissions triggered by non-buttons),
+  // there is no opportunity for a submit handler to set $form_state['rebuild'],
+  // but it makes no sense to redisplay the exact same form without an error for
+  // the user to correct, so we also rebuild error-free non-executed forms
+  // regardless of $form_state['rebuild'].
+  // @todo For Drupal 8, simplify this logic. Considering AJAX and non-HTML
+  //   front-ends, along with element-level #submit properties, it might not
+  //   make sense any more to have divergent form execution based on whether the
+  //   triggering element has #executes_submit_callback set to TRUE.
+  if (($form_state['rebuild'] || empty($form_state['executed'])) && $form_state['process_input'] && !form_get_errors()) {
     $form = drupal_rebuild_form($form_id, $form_state, $form);
   }
   // After processing the form, the form builder or a #process callback may
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	23 Sep 2010 02:13:00 -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.22
diff -u -p -r1.22 image.admin.inc
--- modules/image/image.admin.inc	22 Sep 2010 03:24:09 -0000	1.22
+++ modules/image/image.admin.inc	23 Sep 2010 02:13:00 -0000
@@ -164,7 +164,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.67
diff -u -p -r1.67 form.test
--- modules/simpletest/tests/form.test	19 Sep 2010 18:39:18 -0000	1.67
+++ modules/simpletest/tests/form.test	23 Sep 2010 02:13:01 -0000
@@ -727,24 +727,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.'));
   }
 
@@ -756,11 +753,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.48
diff -u -p -r1.48 form_test.module
--- modules/simpletest/tests/form_test.module	27 Aug 2010 11:54:32 -0000	1.48
+++ modules/simpletest/tests/form_test.module	23 Sep 2010 02:13:01 -0000
@@ -516,16 +516,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',
@@ -571,15 +565,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	23 Sep 2010 02:13:01 -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;
 }
 
 /**
