diff --git includes/ajax.inc includes/ajax.inc
index c4d67bc..8694232 100644
--- includes/ajax.inc
+++ includes/ajax.inc
@@ -237,7 +237,7 @@ function ajax_get_form() {
  * which contains the array #parents of the element in the form structure.
  * This additional field allows ajax_form_callback() to determine which
  * element triggered the action, as non-submit form elements do not
- * provide this information in $form_state['clicked_button'], which can
+ * provide this information in $form_state['internal']['clicked_button'], which can
  * also be used to determine triggering element, but only submit-type
  * form elements.
  *
@@ -283,7 +283,7 @@ function ajax_form_callback() {
     }
   }
   if (empty($triggering_element)) {
-    $triggering_element = $form_state['clicked_button'];
+    $triggering_element = $form_state['internal']['clicked_button'];
   }
   // Now that we have the element, get a callback if there is one.
   if (!empty($triggering_element)) {
diff --git includes/authorize.inc includes/authorize.inc
index 82526bd..e32139f 100644
--- includes/authorize.inc
+++ includes/authorize.inc
@@ -149,7 +149,7 @@ function authorize_filetransfer_form_validate($form, &$form_state) {
  */
 function authorize_filetransfer_form_submit($form, &$form_state) {
   global $base_url;
-  switch ($form_state['clicked_button']['#name']) {
+  switch ($form_state['internal']['clicked_button']['#name']) {
     case 'process_updates':
 
       // Save the connection settings to the DB.
diff --git includes/form.inc includes/form.inc
index 680c6ed..ad9077e 100644
--- includes/form.inc
+++ includes/form.inc
@@ -162,7 +162,7 @@ function drupal_build_form($form_id, &$form_state) {
   $form_state += form_state_defaults();
 
   if (!isset($form_state['input'])) {
-    $form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST;
+    $form_state['input'] = $form_state['internal']['method'] == 'get' ? $_GET : $_POST;
   }
 
   if (isset($_SESSION['batch_form_state'])) {
@@ -204,7 +204,7 @@ function drupal_build_form($form_id, &$form_state) {
       $form['#build_id'] = $form_build_id;
 
       // Fix the form method, if it is 'get' in $form_state, but not in $form.
-      if ($form_state['method'] == 'get' && !isset($form['#method'])) {
+      if ($form_state['internal']['method'] == 'get' && !isset($form['#method'])) {
         $form['#method'] = 'get';
       }
 
@@ -249,7 +249,7 @@ function drupal_build_form($form_id, &$form_state) {
   // workflow is not complete. We need to construct a fresh copy of the form,
   // passing in the latest $form_state in addition to any other variables passed
   // into drupal_get_form().
-  if ($form_state['rebuild'] && $form_state['submitted'] && !form_get_errors()) {
+  if ($form_state['rebuild'] && $form_state['internal']['submitted'] && !form_get_errors()) {
     $form = drupal_rebuild_form($form_id, $form_state);
   }
 
@@ -270,16 +270,16 @@ function drupal_build_form($form_id, &$form_state) {
  */
 function form_state_defaults() {
   return array(
-    'args' => array(),
     'rebuild' => FALSE,
     'redirect' => NULL,
     'build_info' => array(),
-    'storage' => array(),
-    'submitted' => FALSE,
-    'programmed' => FALSE,
     'cache'=> FALSE,
-    'method' => 'post',
-    'groups' => array(),
+    'internal' => array(
+      'method' => 'post',
+      'groups' => array(),
+      'submitted' => FALSE,
+      'programmed' => FALSE,
+    ),
   );
 }
 
@@ -291,7 +291,7 @@ function form_state_defaults() {
  * button is pressed: get the form from the cache, run drupal_process_form over
  * it and then if it needs rebuild, run drupal_rebuild_form over it. Then send
  * back a part of the returned form.
- * $form_state['clicked_button']['#array_parents'] will help you to find which
+ * $form_state['internal']['clicked_button']['#array_parents'] will help you to find which
  * part.
  *
  * @param $form_id
@@ -302,8 +302,7 @@ function form_state_defaults() {
  *   different $form_id values to the proper form constructor function. Examples
  *   may be found in node_forms(), search_forms(), and user_forms().
  * @param $form_state
- *   A keyed array containing the current state of the form. Most
- *   important is the $form_state['storage'] collection.
+ *   A keyed array containing the current state of the form.
  * @param $form_build_id
  *   If the AHAH callback calling this function only alters part of the form,
  *   then pass in the existing form_build_id so we can re-cache with the same
@@ -337,7 +336,7 @@ function drupal_rebuild_form($form_id, &$form_state, $form_build_id = NULL) {
 
   // Clear out all group associations as these might be different when
   // re-rendering the form.
-  $form_state['groups'] = array();
+  $form_state['internal']['groups'] = array();
 
   // Do not call drupal_process_form(), since it would prevent the rebuilt form
   // to submit.
@@ -356,8 +355,7 @@ function form_get_cache($form_build_id, &$form_state) {
     if ((isset($form['#cache_token']) && drupal_valid_token($form['#cache_token'])) || (!isset($form['#cache_token']) && !$user->uid)) {
       if ($cached = cache_get('form_state_' . $form_build_id, 'cache_form')) {
         // Re-populate $form_state for subsequent rebuilds.
-        $form_state['build_info'] = $cached->data['build_info'];
-        $form_state['storage'] = $cached->data['storage'];
+        $form_state = $cached->data + $form_state;
 
         // If the original form is contained in an include file, load the file.
         // @see drupal_build_form()
@@ -386,11 +384,7 @@ function form_set_cache($form_build_id, $form, $form_state) {
   }
 
   // Cache form state.
-  if (!empty($form_state['build_info']) || !empty($form_state['storage'])) {
-    $data = array(
-      'build_info' => $form_state['build_info'],
-      'storage' => $form_state['storage'],
-    );
+  if ($data = array_diff_key($form_state, array_flip(array('rebuild', 'redirect', 'no_redirect', 'input', 'values', 'cache', 'internal')))) {
     cache_set('form_state_' . $form_build_id, $data, 'cache_form', REQUEST_TIME + $expire);
   }
 }
@@ -450,14 +444,14 @@ function drupal_form_submit($form_id, &$form_state) {
     array_shift($args);
     $form_state['build_info']['args'] = $args;
   }
+  // Merge in default values.
+  $form_state += form_state_defaults();
 
   $form = drupal_retrieve_form($form_id, $form_state);
   $form_state['input'] = $form_state['values'];
-  $form_state['programmed'] = TRUE;
+  $form_state['internal']['programmed'] = TRUE;
   // Programmed forms are always submitted.
-  $form_state['submitted'] = TRUE;
-  // Merge in default values.
-  $form_state += form_state_defaults();
+  $form_state['internal']['submitted'] = TRUE;
 
   drupal_prepare_form($form_id, $form, $form_state);
   drupal_process_form($form_id, $form, $form_state);
@@ -569,7 +563,7 @@ function drupal_process_form($form_id, &$form, &$form_state) {
   $form_state['values'] = array();
 
   // With $_GET, these forms are always submitted if requested.
-  if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) {
+  if ($form_state['internal']['method'] == 'get' && !empty($form_state['always_process'])) {
     if (!isset($form_state['input']['form_build_id'])) {
       $form_state['input']['form_build_id'] = $form['#build_id'];
     }
@@ -585,7 +579,7 @@ function drupal_process_form($form_id, &$form, &$form_state) {
   $form = form_builder($form_id, $form, $form_state);
 
   // Only process the input if we have a correct form submission.
-  if ($form_state['process_input']) {
+  if ($form_state['internal']['process_input']) {
     drupal_validate_form($form_id, $form, $form_state);
 
     // drupal_html_id() maintains a cache of element IDs it has seen,
@@ -595,7 +589,7 @@ function drupal_process_form($form_id, &$form, &$form_state) {
     // browser don't increment all the element IDs needlessly.
     drupal_static_reset('drupal_html_id');
 
-    if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild']) {
+    if ($form_state['internal']['submitted'] && !form_get_errors() && !$form_state['rebuild']) {
       // Execute form submit handlers.
       form_execute_handlers('submit', $form, $form_state);
 
@@ -616,7 +610,7 @@ function drupal_process_form($form_id, &$form, &$form_state) {
         // late execution of submit handlers and post-batch redirection.
         $batch['form'] = $form;
         $batch['form_state'] = $form_state;
-        $batch['progressive'] = !$form_state['programmed'];
+        $batch['progressive'] = !$form_state['internal']['programmed'];
         batch_process();
         // Execution continues only for programmatic forms.
         // For 'regular' forms, we get redirected to the batch processing
@@ -651,7 +645,7 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
   global $user;
 
   $form['#type'] = 'form';
-  $form_state['programmed'] = isset($form_state['programmed']) ? $form_state['programmed'] : FALSE;
+  $form_state['internal']['programmed'] = isset($form_state['internal']['programmed']) ? $form_state['internal']['programmed'] : FALSE;
 
   if (isset($form['#build_id'])) {
     $form['form_build_id'] = array(
@@ -667,14 +661,14 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
   // requested previously by the user and protects against cross site request
   // forgeries.
   if (isset($form['#token'])) {
-    if ($form['#token'] === FALSE || $user->uid == 0 || $form_state['programmed']) {
+    if ($form['#token'] === FALSE || $user->uid == 0 || $form_state['internal']['programmed']) {
       unset($form['#token']);
     }
     else {
       $form['form_token'] = array('#type' => 'token', '#default_value' => drupal_get_token($form['#token']));
     }
   }
-  elseif (isset($user->uid) && $user->uid && !$form_state['programmed']) {
+  elseif (isset($user->uid) && $user->uid && !$form_state['internal']['programmed']) {
     $form['#token'] = $form_id;
     $form['form_token'] = array(
       '#id' => drupal_html_id('edit-' . $form_id . '-form-token'),
@@ -788,7 +782,7 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
  *   defines $form_state['no_redirect'] when building a form in an AJAX
  *   callback to prevent any redirection. $form_state['no_redirect'] should NOT
  *   be altered by form builder functions or form validation/submit handlers.
- * - If $form_state['programmed'] is TRUE, the form submission was usually
+ * - If $form_state['internal']['programmed'] is TRUE, the form submission was usually
  *   invoked via drupal_form_submit(), so any redirection would break the script
  *   that invoked drupal_form_submit().
  * - If $form_state['rebuild'] is TRUE, the form needs to be rebuilt without
@@ -802,7 +796,7 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
  */
 function drupal_redirect_form($form_state) {
   // Skip redirection for form submissions invoked via drupal_form_submit().
-  if (!empty($form_state['programmed'])) {
+  if (!empty($form_state['internal']['programmed'])) {
     return;
   }
   // Skip redirection if rebuild is activated.
@@ -938,8 +932,8 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) {
 function form_execute_handlers($type, &$form, &$form_state) {
   $return = FALSE;
   // If there was a button pressed, use its handlers.
-  if (isset($form_state[$type . '_handlers'])) {
-    $handlers = $form_state[$type . '_handlers'];
+  if (isset($form_state['internal'][$type . '_handlers'])) {
+    $handlers = $form_state['internal'][$type . '_handlers'];
   }
   // Otherwise, check for a form-level handler.
   elseif (isset($form['#' . $type])) {
@@ -1083,11 +1077,11 @@ function form_builder($form_id, $element, &$form_state) {
     // Set a flag if we have a correct form submission. This is always TRUE for
     // programmed forms coming from drupal_form_submit(), or if the form_id coming
     // from the POST data is set and matches the current form_id.
-    if ($form_state['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) {
-      $form_state['process_input'] = TRUE;
+    if ($form_state['internal']['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) {
+      $form_state['internal']['process_input'] = TRUE;
     }
     else {
-      $form_state['process_input'] = FALSE;
+      $form_state['internal']['process_input'] = FALSE;
     }
   }
 
@@ -1208,7 +1202,7 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
   if (!isset($element['#value']) && !array_key_exists('#value', $element)) {
     $value_callback = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value';
 
-    if ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))) {
+    if ($form_state['internal']['programmed'] || ($form_state['internal']['process_input'] && (!isset($element['#access']) || $element['#access']))) {
       // Get the input for the current element. NULL values in the input need to
       // be explicitly distinguished from missing input. (see below)
       $input = $form_state['input'];
@@ -1232,7 +1226,7 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
       // for the latter, if required. Programmatically submitted forms can
       // submit explicit NULL values when calling drupal_form_submit(), so we do
       // not modify $form_state['input'] for them.
-      if (!$input_exists && !$form_state['rebuild'] && !$form_state['programmed']) {
+      if (!$input_exists && !$form_state['rebuild'] && !$form_state['internal']['programmed']) {
         // We leverage the internal logic of form_set_value() to change the
         // input values by passing $form_state['input'] instead of the usual
         // $form_state['values']. In effect, this adds the necessary parent keys
@@ -1278,22 +1272,22 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
     // First, accumulate a collection of buttons, divided into two bins:
     // those that execute full submit callbacks and those that only validate.
     $button_type = $element['#executes_submit_callback'] ? 'submit' : 'button';
-    $form_state['buttons'][$button_type][] = $element;
+    $form_state['internal']['buttons'][$button_type][] = $element;
 
     if (_form_button_was_clicked($element, $form_state)) {
-      $form_state['submitted'] = $form_state['submitted'] || $element['#executes_submit_callback'];
+      $form_state['internal']['submitted'] = $form_state['internal']['submitted'] || $element['#executes_submit_callback'];
 
       // In most cases, we want to use form_set_value() to manipulate
       // the global variables. In this special case, we want to make sure that
       // the value of this element is listed in $form_variables under 'op'.
       $form_state['values'][$element['#name']] = $element['#value'];
-      $form_state['clicked_button'] = $element;
+      $form_state['internal']['clicked_button'] = $element;
 
       if (isset($element['#validate'])) {
-        $form_state['validate_handlers'] = $element['#validate'];
+        $form_state['internal']['validate_handlers'] = $element['#validate'];
       }
       if (isset($element['#submit'])) {
-        $form_state['submit_handlers'] = $element['#submit'];
+        $form_state['internal']['submit_handlers'] = $element['#submit'];
       }
     }
   }
@@ -1344,16 +1338,16 @@ function _form_builder_ie_cleanup($form, &$form_state) {
     // If we haven't recognized a submission yet, and there's a single
     // submit button, we know that we've hit the right conditions. Grab
     // the first one and treat it as the clicked button.
-    if (empty($form_state['submitted']) && !empty($form_state['buttons']['submit']) && empty($form_state['buttons']['button'])) {
-      $button = $form_state['buttons']['submit'][0];
+    if (empty($form_state['internal']['submitted']) && !empty($form_state['internal']['buttons']['submit']) && empty($form_state['internal']['buttons']['button'])) {
+      $button = $form_state['internal']['buttons']['submit'][0];
 
       // Set up all the $form_state information that would have been
       // populated had the button been recognized earlier.
-      $form_state['submitted'] = TRUE;
-      $form_state['submit_handlers'] = empty($button['#submit']) ? NULL : $button['#submit'];
-      $form_state['validate_handlers'] = empty($button['#validate']) ? NULL : $button['#validate'];
+      $form_state['internal']['submitted'] = TRUE;
+      $form_state['internal']['submit_handlers'] = empty($button['#submit']) ? NULL : $button['#submit'];
+      $form_state['internal']['validate_handlers'] = empty($button['#validate']) ? NULL : $button['#validate'];
       $form_state['values'][$button['#name']] = $button['#value'];
-      $form_state['clicked_button'] = $button;
+      $form_state['internal']['clicked_button'] = $button;
     }
   }
 }
@@ -1383,7 +1377,7 @@ function form_state_values_clean(&$form_state) {
   // Remove button values.
   // form_builder() collects all button elements in a form, keyed by button
   // type. We remove the button value separately for each button element.
-  foreach ($form_state['buttons'] as $button_type => $buttons) {
+  foreach ($form_state['internal']['buttons'] as $button_type => $buttons) {
     foreach ($buttons as $button) {
       // Remove this button's value from the submitted form values by finding
       // the value corresponding to this button.
@@ -2458,7 +2452,7 @@ function form_process_fieldset(&$element, &$form_state) {
   // Add this fieldset to a group if one is set and if it's not being
   // added to itself.
   if (isset($element['#group']) && $element['#group'] != $parents) {
-    if (isset($form_state['groups'][$element['#group']]) && !empty($form_state['groups'][$element['#group']]['#group_exists'])) {
+    if (isset($form_state['internal']['groups'][$element['#group']]) && !empty($form_state['internal']['groups'][$element['#group']]['#group_exists'])) {
       // Trick drupal_render() into believing this has already been output.
       // The group widget will rerender this later. This only happens when the
       // group is actually defined ('#group_exists' is TRUE). This prevents
@@ -2473,21 +2467,21 @@ function form_process_fieldset(&$element, &$form_state) {
 
     // Store a reference to this fieldset for the vertical tabs processing
     // function.
-    $form_state['groups'][$element['#group']][] = &$element;
+    $form_state['internal']['groups'][$element['#group']][] = &$element;
   }
 
   // Each fieldset can be a group itself and gets a reference to all
   // elements in its group.
-  $form_state['groups'][$parents]['#group_exists'] = TRUE;
+  $form_state['internal']['groups'][$parents]['#group_exists'] = TRUE;
   // There might already be elements associated with this group. Since the
   // group did not exist yet at the time they were added to this group, they
   // couldn't set #printed to TRUE (see above). We now know that this group
   // does in fact exist and set #printed to TRUE to prevent rendering in the
   // original context.
-  foreach (element_children($form_state['groups'][$parents]) as $key) {
-    $form_state['groups'][$parents][$key]['#printed'] = TRUE;
+  foreach (element_children($form_state['internal']['groups'][$parents]) as $key) {
+    $form_state['internal']['groups'][$parents][$key]['#printed'] = TRUE;
   }
-  $element['#group_members'] = &$form_state['groups'][$parents];
+  $element['#group_members'] = &$form_state['internal']['groups'][$parents];
 
   // Contains form element summary functionalities.
   $element['#attached']['js']['misc/form.js'] = array('weight' => JS_LIBRARY + 1);
diff --git modules/field/field.form.inc modules/field/field.form.inc
index 345bef1..f7e62fc 100644
--- modules/field/field.form.inc
+++ modules/field/field.form.inc
@@ -208,7 +208,7 @@ function field_multiple_value_form($field, $instance, $langcode, $items, &$form,
         '#max_delta' => $max,
       );
       // Add 'add more' button, if not working with a programmed form.
-      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form_state['programmed'])) {
+      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form_state['internal']['programmed'])) {
         $field_elements['add_more'] = array(
           '#type' => 'submit',
           '#name' => $field_name . '_add_more',
@@ -351,8 +351,8 @@ function field_add_more_submit($form, &$form_state) {
     $entity = $form['#builder_function']($form, $form_state);
 
     // Make the changes we want to the form state.
-    $field_name = $form_state['clicked_button']['#field_name'];
-    $langcode = $form_state['clicked_button']['#language'];
+    $field_name = $form_state['internal']['clicked_button']['#field_name'];
+    $langcode = $form_state['internal']['clicked_button']['#language'];
     if ($form_state['values'][$field_name . '_add_more']) {
       $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name][$langcode]);
     }
@@ -364,7 +364,7 @@ function field_add_more_submit($form, &$form_state) {
  */
 function field_add_more_js($form, $form_state) {
   // Retrieve field information.
-  $field_name = $form_state['clicked_button']['#field_name'];
+  $field_name = $form_state['internal']['clicked_button']['#field_name'];
   $field = $form['#fields'][$field_name]['field'];
   if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
     ajax_render(array());
diff --git modules/field_ui/field_ui.module modules/field_ui/field_ui.module
index ccf1cb4..374db43 100644
--- modules/field_ui/field_ui.module
+++ modules/field_ui/field_ui.module
@@ -325,7 +325,7 @@ function field_ui_form_node_type_form_alter(&$form, $form_state) {
  * Redirect to manage fields form.
  */
 function field_ui_form_node_type_form_submit($form, &$form_state) {
-  if ($form_state['clicked_button']['#parents'][0] === 'save_continue') {
+  if ($form_state['internal']['clicked_button']['#parents'][0] === 'save_continue') {
     $form_state['redirect'] = _field_ui_bundle_admin_path('node', $form_state['values']['type']) .'/fields';
   }
 }
diff --git modules/file/file.module modules/file/file.module
index 11183ac..e1a240d 100644
--- modules/file/file.module
+++ modules/file/file.module
@@ -361,7 +361,7 @@ function file_managed_file_process($element, &$form_state, $form) {
   // Because the output of this field changes depending on the button clicked,
   // we need to ask FAPI immediately if the remove button was clicked.
   // It's not good that we call this private function, but
-  // $form_state['clicked_button'] is only available after this #process
+  // $form_state['internal']['clicked_button'] is only available after this #process
   // callback is finished.
   if (_form_button_was_clicked($element['remove_button'], $form_state)) {
     // If it's a temporary file we can safely remove it immediately, otherwise
@@ -509,7 +509,7 @@ function file_managed_file_validate(&$element, &$form_state) {
   // If referencing an existing file, only allow if there are existing
   // references. This prevents unmanaged files from being deleted if this
   // item were to be deleted.
-  $clicked_button = end($form_state['clicked_button']['#parents']);
+  $clicked_button = end($form_state['internal']['clicked_button']['#parents']);
   if ($clicked_button != 'remove_button' && !empty($element['fid']['#value'])) {
     if ($file = file_load($element['fid']['#value'])) {
       if ($file->status == FILE_STATUS_PERMANENT) {
diff --git modules/node/node.pages.inc modules/node/node.pages.inc
index b4e7775..d02de5f 100644
--- modules/node/node.pages.inc
+++ modules/node/node.pages.inc
@@ -450,7 +450,7 @@ function node_form_submit($form, &$form_state) {
 function node_form_submit_build_node($form, &$form_state) {
   // Unset any button-level handlers, execute all the form-level submit
   // functions to process the form values into an updated node.
-  unset($form_state['submit_handlers']);
+  unset($form_state['internal']['submit_handlers']);
   form_execute_handlers('submit', $form, $form_state);
   $node = node_submit((object)$form_state['values']);
 
diff --git modules/search/search.admin.inc modules/search/search.admin.inc
index 45be2bf..1a0795f 100644
--- modules/search/search.admin.inc
+++ modules/search/search.admin.inc
@@ -143,7 +143,7 @@ function search_admin_settings_submit($form, &$form_state) {
   }
   $current_modules = variable_get('search_active_modules', array('node', 'user'));
   // Check whether we are resetting the values.
-  if ($form_state['clicked_button']['#value'] == t('Reset to defaults')) {
+  if ($form_state['internal']['clicked_button']['#value'] == t('Reset to defaults')) {
     $new_modules = array('node', 'user');
   }
   else {
diff --git modules/simpletest/tests/form.test modules/simpletest/tests/form.test
index c1ab839..25874e7 100644
--- modules/simpletest/tests/form.test
+++ modules/simpletest/tests/form.test
@@ -80,7 +80,7 @@ class FormsTestCase extends DrupalWebTestCase {
           $form_state['values'][$element] = $empty;
           $form_state['input'] = $form_state['values'];
           $form_state['input']['form_id'] = $form_id;
-          $form_state['method'] = 'post';
+          $form_state['internal']['method'] = 'post';
           drupal_prepare_form($form_id, $form, $form_state);
           drupal_process_form($form_id, $form, $form_state);
           $errors = form_get_errors();
@@ -660,3 +660,30 @@ class FormsRebuildTestCase extends DrupalWebTestCase {
     $this->assertFieldById('edit-text-2', 'DEFAULT 2', t('A newly added textfield was initialized with its default value.'));
   }
 }
+
+/**
+ * Tests a form using form storage to pass data from the constructor to a
+ * submit handler. The data has to persist even when caching gets activated,
+ * what may happen when a modules alter the form and adds #ajax properties.
+ */
+class FormTestStoragePersist extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Form State Persistence',
+      'description' => 'Makes sure the form state populated during construction is available to handlers even when caching is active.',
+      'group' => 'Form API',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('form_test');
+  }
+
+  function testFormStoragePersist() {
+    $this->drupalPost('form-test/storage-persist', array(), t('Submit'));
+    // The submit handler outputs the value in $form_state, assert it's there.
+    $this->assertText('test');
+  }
+}
+
+
diff --git modules/simpletest/tests/form_test.module modules/simpletest/tests/form_test.module
index cdb6118..45a2c1f 100644
--- modules/simpletest/tests/form_test.module
+++ modules/simpletest/tests/form_test.module
@@ -94,6 +94,14 @@ function form_test_menu() {
     'type' => MENU_CALLBACK,
   );
 
+  $items['form-test/storage-persist'] = array(
+    'title' => t('Form test'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('form_test_storage_persist'),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+  
   return $items;
 }
 
@@ -678,3 +686,36 @@ function form_test_form_rebuild_preserve_values_form_submit($form, &$form_state)
   // Finish the workflow. Do not rebuild.
   drupal_set_message(t('Form values: %values', array('%values' => var_export($form_state['values'], TRUE))));
 }
+
+/**
+ * Form constructor for testing form storage persistence.
+ */
+function form_test_storage_persist($form, &$form_state) {
+  $form['title'] = array(
+    '#type' => 'textfield',
+    '#title' => 'title',
+    '#default_value' => 'DEFAULT',
+    '#required' => TRUE,
+  );
+  $form_state['value'] = 'test';
+  
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Submit')
+  );
+  return $form;
+}
+
+function form_test_storage_persist_submit($form, &$form_state) {
+  drupal_set_message($form_state['value']);
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Implement hook_form_FORM_ID_alter().
+ */
+function form_test_form_form_test_storage_persist_alter(&$form, &$form_state) {
+  // Simulate a form alter implementation inserting form elements that enable
+  // caching of the form, e.g. elements having #ajax.
+  $form_state['cache'] = TRUE;
+}
diff --git modules/system/system.admin.inc modules/system/system.admin.inc
index 350972d..5f613d0 100644
--- modules/system/system.admin.inc
+++ modules/system/system.admin.inc
@@ -1179,7 +1179,7 @@ function system_modules_uninstall($form, $form_state = NULL) {
   include_once DRUPAL_ROOT . '/includes/install.inc';
 
   // Display the confirm form if any modules have been submitted.
-  if (!empty($form_state) && $confirm_form = system_modules_uninstall_confirm_form($form_state['storage'])) {
+  if (!empty($form_state['storage']) && $confirm_form = system_modules_uninstall_confirm_form($form_state['storage'])) {
     return $confirm_form;
   }
 
diff --git modules/taxonomy/taxonomy.admin.inc modules/taxonomy/taxonomy.admin.inc
index 448e84d..bea048f 100644
--- modules/taxonomy/taxonomy.admin.inc
+++ modules/taxonomy/taxonomy.admin.inc
@@ -173,7 +173,7 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) {
  * @see taxonomy_form_vocabulary()
  */
 function taxonomy_form_vocabulary_validate($form, &$form_state) {
-  if ($form_state['clicked_button']['#value'] != t('Delete') && isset($form_state['values']['machine_name'])) {
+  if ($form_state['internal']['clicked_button']['#value'] != t('Delete') && isset($form_state['values']['machine_name'])) {
 
     // Restrict machine names to appropriate characters.
     $machine_name = $form_state['values']['machine_name'];
@@ -200,7 +200,7 @@ function taxonomy_form_vocabulary_validate($form, &$form_state) {
  */
 function taxonomy_form_vocabulary_submit($form, &$form_state) {
   $old_vocabulary = $form['#vocabulary'];
-  if ($form_state['clicked_button']['#value'] == t('Delete')) {
+  if ($form_state['internal']['clicked_button']['#value'] == t('Delete')) {
     // Rebuild the form to confirm vocabulary deletion.
     $form_state['rebuild'] = TRUE;
     $form_state['confirm_delete'] = TRUE;
@@ -414,7 +414,7 @@ function taxonomy_overview_terms($form, &$form_state, $vocabulary) {
  * @see taxonomy_overview_terms()
  */
 function taxonomy_overview_terms_submit($form, &$form_state) {
-  if ($form_state['clicked_button']['#value'] == t('Reset to alphabetical')) {
+  if ($form_state['internal']['clicked_button']['#value'] == t('Reset to alphabetical')) {
     // Execute the reset action.
     if ($form_state['values']['reset_alphabetical'] === TRUE) {
       return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
@@ -756,7 +756,7 @@ function taxonomy_form_term_validate($form, &$form_state) {
  * @see taxonomy_form_term()
  */
 function taxonomy_form_term_submit($form, &$form_state) {
-  if ($form_state['clicked_button']['#value'] == t('Delete')) {
+  if ($form_state['internal']['clicked_button']['#value'] == t('Delete')) {
     // Execute the term deletion.
     if ($form_state['values']['delete'] === TRUE) {
       return taxonomy_term_confirm_delete_submit($form, $form_state);
@@ -767,7 +767,7 @@ function taxonomy_form_term_submit($form, &$form_state) {
     return;
   }
   // Rebuild the form to confirm enabling multiple parents.
-  elseif ($form_state['clicked_button']['#value'] == t('Save') && count($form_state['values']['parent']) > 1 && $form['#vocabulary']->hierarchy < 2) {
+  elseif ($form_state['internal']['clicked_button']['#value'] == t('Save') && count($form_state['values']['parent']) > 1 && $form['#vocabulary']->hierarchy < 2) {
     $form_state['rebuild'] = TRUE;
     $form_state['confirm_parents'] = TRUE;
     return;
diff --git modules/user/user.module modules/user/user.module
index 6d341f7..f54fe76 100644
--- modules/user/user.module
+++ modules/user/user.module
@@ -2579,7 +2579,7 @@ function user_user_operations($form = array(), $form_state = array()) {
 
   // If the form has been posted, we need to insert the proper data for
   // role editing if necessary.
-  if (!empty($form_state['submitted'])) {
+  if (!empty($form_state['internal']['submitted'])) {
     $operation_rid = explode('-', $form_state['values']['operation']);
     $operation = $operation_rid[0];
     if ($operation == 'add_role' || $operation == 'remove_role') {
