diff --git a/includes/ajax.inc b/includes/ajax.inc
index f059209bce..09b5fa0943 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -384,7 +384,16 @@ function ajax_get_form() {
 function ajax_form_callback() {
   list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
   drupal_process_form($form['#form_id'], $form, $form_state);
+  return ajax_form_return_commands($form, $form_state, $commands);
+}
 
+/**
+ * Return a set of AJAX commands for updating a form.
+ *
+ * @see ajax_form_callback()
+ * @see drupal_get_form()
+ */
+function ajax_form_return_commands($form, $form_state, $commands = array()) {
   // We need to return the part of the form (or some other content) that needs
   // to be re-rendered so the browser can update the page with changed content.
   // Since this is the generic menu callback used by many Ajax elements, it is
@@ -642,7 +651,7 @@ function ajax_footer() {
  */
 function ajax_process_form($element, &$form_state) {
   $element = ajax_pre_render_element($element);
-  if (!empty($element['#ajax_processed'])) {
+  if (!empty($element['#ajax_processed']) && !isset($element['#ajax']['no_form_cache'])) {
     $form_state['cache'] = TRUE;
   }
   return $element;
@@ -738,10 +747,23 @@ function ajax_pre_render_element($element) {
 
     $settings = $element['#ajax'];
 
+    $path = 'system/ajax';
+    $options = array();
+
+    $enabled_callbacks = variable_get('drupal_ajax_no_form_cache_enabled_callbacks', array());
+    if (isset($element['#ajax']['callback']) && isset($enabled_callbacks[$element['#ajax']['callback']]) && !isset($element['#ajax']['path'])) {
+      $query_parameters = drupal_get_query_parameters($_GET, array('q'));
+
+      $path = $_GET['q'];
+      $options['query'] = $query_parameters;
+
+      $element['#ajax']['no_form_cache'] = TRUE;
+    }
+
     // Assign default settings.
     $settings += array(
-      'path' => 'system/ajax',
-      'options' => array(),
+      'path' => $path,
+      'options' => $options,
     );
 
     // @todo Legacy support. Remove in Drupal 8.
diff --git a/includes/form.inc b/includes/form.inc
index 6c33de7f96..f64f133977 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -128,7 +128,36 @@ function drupal_get_form($form_id) {
   array_shift($args);
   $form_state['build_info']['args'] = $args;
 
-  return drupal_build_form($form_id, $form_state);
+  // Disable redirection if the request was made via AJAX.
+  $ajax_post = isset($_POST['ajax_page_state']) && $_POST['form_id'] === $form_id;
+  if ($ajax_post) {
+    $form_state['no_redirect'] = TRUE;
+    $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
+    $form_state['rebuild_info']['copy']['#action'] = TRUE;
+  }
+
+  $form = drupal_build_form($form_id, $form_state);
+
+  // In the event that this form was submitted via AJAX, we need to return a
+  // set of AJAX commands to update the original form. Generate the JSON
+  // response and end the request.
+  if ($ajax_post && $form_state['process_input']) {
+    $commands = array();
+    if (isset($form['#build_id_old']) && $form['#build_id_old'] !== $form['#build_id']) {
+      $commands[] = ajax_command_update_build_id($form);
+    }
+    $commands = ajax_form_return_commands($form, $form_state, $commands);
+
+    $status = ob_get_status();
+    if (!empty($status)) {
+      ob_clean();
+    }
+
+    ajax_deliver($commands);
+    exit();
+  }
+
+  return $form;
 }
 
 /**
@@ -409,6 +438,7 @@ function form_state_defaults() {
     'build_info' => array(
       'args' => array(),
       'files' => array(),
+      'from_cache' => FALSE,
     ),
     'temporary' => array(),
     'submitted' => FALSE,
@@ -464,6 +494,15 @@ function form_state_defaults() {
 function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {
   $form = drupal_retrieve_form($form_id, $form_state);
 
+  $copy_build_id = !empty($form_state['rebuild_info']['copy']['#build_id']);
+
+  // If this form was not built from cache but needs to maintain the same build
+  // ID, copy the value from input onto the old form.
+  if (empty($form_state['build_info']['from_cache']) && isset($form_state['input']['form_build_id'])) {
+    $old_form['#build_id'] = $form_state['input']['form_build_id'];
+    $copy_build_id = FALSE;
+  }
+
   // If only parts of the form will be returned to the browser (e.g., Ajax or
   // RIA clients), or if the form already had a new build ID regenerated when it
   // was retrieved from the form cache, reuse the existing #build_id.
@@ -471,7 +510,7 @@ function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {
   // build's data in the form cache; also allowing the user to go back to an
   // earlier build, make changes, and re-submit.
   // @see drupal_prepare_form()
-  $enforce_old_build_id = isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id']);
+  $enforce_old_build_id = isset($old_form['#build_id']) && $copy_build_id;
   $old_form_is_mutable_copy = isset($old_form['#build_id_old']);
   if ($enforce_old_build_id || $old_form_is_mutable_copy) {
     $form['#build_id'] = $old_form['#build_id'];
@@ -524,6 +563,9 @@ function form_get_cache($form_build_id, &$form_state) {
         // Re-populate $form_state for subsequent rebuilds.
         $form_state = $cached->data + $form_state;
 
+        // Indicate this build was loaded from the cache, not a fresh build.
+        $form_state['build_info']['from_cache'] = TRUE;
+
         // If the original form is contained in include files, load the files.
         // @see form_load_include()
         $form_state['build_info'] += array('files' => array());
diff --git a/modules/simpletest/tests/ajax.test b/modules/simpletest/tests/ajax.test
index afe02306bd..81afed38f8 100644
--- a/modules/simpletest/tests/ajax.test
+++ b/modules/simpletest/tests/ajax.test
@@ -583,6 +583,71 @@ class AJAXFormPageCacheTestCase extends AJAXTestCase {
   }
 }
 
+/**
+ * Test Ajax forms when form cache is disabled for the ajax form.
+ */
+class AJAXFormNoCacheTestCase extends AJAXTestCase {
+  protected $profile = 'testing';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'AJAX forms with form cache disabled',
+      'description' => 'Tests that AJAX forms can have form cache disabled.',
+      'group' => 'AJAX',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+
+    variable_set('drupal_ajax_no_form_cache_enabled_callbacks', array(
+      'ajax_forms_test_simple_form_select_callback' => TRUE,
+      'ajax_forms_test_simple_form_checkbox_callback' => TRUE,
+    ));
+  }
+
+  /**
+   * Return the build id of the current form.
+   */
+  protected function getFormBuildId() {
+    $build_id_fields = $this->xpath('//input[@name="form_build_id"]');
+    $this->assertEqual(count($build_id_fields), 1, 'One form build id field on the page');
+    return (string) $build_id_fields[0]['value'];
+  }
+
+  /**
+   * Check for a given form build id in cache_form.
+   */
+  protected function getFormFromCache($form_build_id) {
+    return (bool) cache_get('form_' . $form_build_id, 'cache_form');
+  }
+
+  /**
+   * Create a simple ajax form, then validate form cache is disabled.
+   */
+  public function testSimpleAJAXFormValue() {
+    $this->drupalGet('ajax_forms_test_no_form_cache');
+    $build_id_initial = $this->getFormBuildId();
+
+    $edit = array('select' => 'green');
+    $commands = $this->drupalPostAJAX(NULL, $edit, 'select');
+    $build_id_first_ajax = $this->getFormBuildId();
+    $this->assertNotEqual($build_id_initial, $build_id_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission');
+    $expected = array(
+      'command' => 'updateBuildId',
+      'old' => $build_id_initial,
+      'new' => $build_id_first_ajax,
+    );
+    $this->assertCommand($commands, $expected, 'Build id change command issued on first AJAX submission');
+    $this->assertFalse($this->getFormFromCache($build_id_initial), 'AJAX form was not retrieved from cache_form.');
+
+    // Now disable all callbacks, allowing the ajax form to be cached.
+    variable_set('drupal_ajax_no_form_cache_enabled_callbacks', array());
+    $this->drupalGet('ajax_forms_test_no_form_cache');
+    $build_id = $this->getFormBuildId();
+    $this->assertTrue($this->getFormFromCache($build_id), 'AJAX form was successfully retrieved from cache_form.');
+  }
+}
 
 /**
  * Miscellaneous Ajax tests using ajax_test module.
diff --git a/modules/simpletest/tests/ajax_forms_test.module b/modules/simpletest/tests/ajax_forms_test.module
index de2fa0ba84..9e02556feb 100644
--- a/modules/simpletest/tests/ajax_forms_test.module
+++ b/modules/simpletest/tests/ajax_forms_test.module
@@ -16,6 +16,12 @@ function ajax_forms_test_menu() {
     'page arguments' => array('ajax_forms_test_simple_form'),
     'access callback' => TRUE,
   );
+  $items['ajax_forms_test_no_form_cache'] = array(
+    'title' => 'AJAX forms no form cache test',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ajax_forms_test_no_form_cache'),
+    'access callback' => TRUE,
+  );
   $items['ajax_forms_test_ajax_commands_form'] = array(
     'title' => 'AJAX forms AJAX commands test',
     'page callback' => 'drupal_get_form',
@@ -70,6 +76,37 @@ function ajax_forms_test_simple_form($form, &$form_state) {
   return $form;
 }
 
+/**
+ * A basic form used to test form_state['values'] during callback.
+ */
+function ajax_forms_test_no_form_cache($form, &$form_state) {
+  $form = array();
+  $form['select'] = array(
+    '#type' => 'select',
+    '#options' => array(
+      'red' => 'red',
+      'green' => 'green',
+      'blue' => 'blue'),
+    '#ajax' => array(
+      'callback' => 'ajax_forms_test_simple_form_select_callback',
+    ),
+    '#suffix' => '<div id="ajax_selected_color">No color yet selected</div>',
+  );
+
+  $form['checkbox'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Test checkbox'),
+    '#ajax' => array(
+      'callback' => 'ajax_forms_test_simple_form_checkbox_callback',
+    ),
+    '#suffix' => '<div id="ajax_checkbox_value">No action yet</div>',
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('submit'),
+  );
+  return $form;
+}
 /**
  * Ajax callback triggered by select.
  */
