Index: includes/ajax.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/ajax.inc,v
retrieving revision 1.40
diff -u -p -r1.40 ajax.inc
--- includes/ajax.inc	17 Dec 2010 01:03:58 -0000	1.40
+++ includes/ajax.inc	22 Dec 2010 03:21:21 -0000
@@ -407,19 +407,49 @@ function ajax_base_page_theme() {
 /**
  * Package and send the result of a page callback to the browser as an AJAX response.
  *
+ * This function is the equivalent of drupal_deliver_html_page(), but for AJAX
+ * requests. Like that function, it:
+ * - Adds needed HTTP headers.
+ * - Prints rendered output.
+ * - Performs end-of-request tasks.
+ *
  * @param $page_callback_result
  *   The result of a page callback. Can be one of:
  *   - NULL: to indicate no content.
  *   - An integer menu status constant: to indicate an error condition.
  *   - A string of HTML content.
  *   - A renderable array of content.
+ *
+ * @see drupal_deliver_html_page()
  */
 function ajax_deliver($page_callback_result) {
-  $commands = array();
-  $header = TRUE;
+  // Emit a Content-Type HTTP header if none has been added by the page callback
+  // or by a wrapping delivery callback.
+  if (is_null(drupal_get_http_header('Content-Type'))) {
+    // The standard header for JSON is application/json.
+    // @see http://www.ietf.org/rfc/rfc4627.txt?number=4627
+    // However, browsers do not allow JavaScript to read the contents of a
+    // user's local files. To work around that, jQuery submits forms containing
+    // a file input element to an IFRAME, instead of using XHR.
+    // @see http://malsup.com/jquery/form/#file-upload
+    // When Internet Explorer receives application/json content in an IFRAME, it
+    // treats it as a file download and prompts the user to save it. To prevent
+    // that, we return the content as text/plain. But only for POST requests,
+    // since jQuery should always use XHR for GET requests and the incorrect
+    // mime type should not end up in page or proxy server caches.
+    // @see http://drupal.org/node/995854
+    $iframe_upload = !isset($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest';
+    if ($iframe_upload && $_SERVER['REQUEST_METHOD'] == 'POST') {
+      drupal_add_http_header('Content-Type', 'text/plain; charset=utf-8');
+    }
+    else {
+      drupal_add_http_header('Content-Type', 'application/json; charset=utf-8');
+    }
+  }
 
   // Normalize whatever was returned by the page callback to an AJAX commands
   // array.
+  $commands = array();
   if (!isset($page_callback_result)) {
     // Simply delivering an empty commands array is sufficient. This results
     // in the AJAX request being completed, but nothing being done to the page.
@@ -444,7 +474,6 @@ function ajax_deliver($page_callback_res
     // Complex AJAX callbacks can return a result that contains an error message
     // or a specific set of commands to send to the browser.
     $page_callback_result += element_info('ajax');
-    $header = $page_callback_result['#header'];
     $error = $page_callback_result['#error'];
     if (isset($error) && $error !== FALSE) {
       if ((empty($error) || $error === TRUE)) {
@@ -470,24 +499,10 @@ function ajax_deliver($page_callback_res
     $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
   }
 
-  // This function needs to do the same thing that drupal_deliver_html_page()
-  // does: add any needed http headers, print rendered output, and perform
-  // end-of-request tasks. By default, $header=TRUE, and we add a
-  // 'text/javascript' header. The page callback can override $header by
-  // returning an 'ajax' element with a #header property. This can be set to
-  // FALSE to prevent the 'text/javascript' header from being output, necessary
-  // when outputting to an IFRAME. This can also be set to 'multipart', in which
-  // case, we don't output JSON, but JSON content wrapped in a textarea, making
-  // a 'text/javascript' header incorrect.
-  if ($header && $header !== 'multipart') {
-    drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');
-  }
-  $output = ajax_render($commands);
-  if ($header === 'multipart') {
-    // jQuery file uploads: http://malsup.com/jquery/form/#code-samples
-    $output = '<textarea>' . $output . '</textarea>';
-  }
-  print $output;
+  // Unlike the recommendation in http://malsup.com/jquery/form/#file-upload,
+  // we do not have to wrap the JSON string in a TEXTAREA, because
+  // drupal_json_encode() returns an HTML-safe JSON string.
+  print ajax_render($commands);
   ajax_footer();
 }
 
Index: misc/ajax.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/ajax.js,v
retrieving revision 1.34
diff -u -p -r1.34 ajax.js
--- misc/ajax.js	21 Dec 2010 04:21:16 -0000	1.34
+++ misc/ajax.js	22 Dec 2010 03:34:50 -0000
@@ -152,9 +152,9 @@ Drupal.ajax = function (base, element, e
       ajax.ajaxing = true;
       return ajax.beforeSubmit(form_values, element_settings, options);
     },
-    beforeSend: function (xmlhttprequest) {
+    beforeSend: function (xmlhttprequest, options) {
       ajax.ajaxing = true;
-      return ajax.beforeSend(xmlhttprequest, ajax.options);
+      return ajax.beforeSend(xmlhttprequest, options);
     },
     success: function (response, status) {
       // Sanity check for browser support (object expected).
@@ -318,7 +318,20 @@ Drupal.ajax.prototype.beforeSubmit = fun
  * Prepare the AJAX request before it is sent.
  */
 Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) {
-  // Disable the element that received the change.
+  // Disable the element that received the change to prevent user interface
+  // interaction while the AJAX request is in progress. ajax.ajaxing prevents
+  // the element from triggering a new request, but does not prevent the user
+  // from changing its value.
+  // Forms without file inputs are already serialized before this function is
+  // called. Forms with file inputs use an IFRAME to perform a POST request
+  // similar to a browser, so disabled elements are not contained in the
+  // submitted values. Therefore, we manually add the element's value to
+  // options.extraData.
+  var v = $.fieldValue(this.element);
+  if (v !== null) {
+    options.extraData = options.extraData || {};
+    options.extraData[this.element.name] = v;
+  }
   $(this.element).addClass('progress-disabled').attr('disabled', true);
 
   // Insert progressbar or throbber.
Index: modules/file/file.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.module,v
retrieving revision 1.48
diff -u -p -r1.48 file.module
--- modules/file/file.module	11 Dec 2010 01:32:20 -0000	1.48
+++ modules/file/file.module	22 Dec 2010 03:01:53 -0000
@@ -237,7 +237,7 @@ function file_ajax_upload() {
     drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
     $commands = array();
     $commands[] = ajax_command_replace(NULL, theme('status_messages'));
-    return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
+    return array('#type' => 'ajax', '#commands' => $commands);
   }
 
   list($form, $form_state) = ajax_get_form();
@@ -247,7 +247,7 @@ function file_ajax_upload() {
     drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
     $commands = array();
     $commands[] = ajax_command_replace(NULL, theme('status_messages'));
-    return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
+    return array('#type' => 'ajax', '#commands' => $commands);
   }
 
   // Get the current element and count the number of files.
@@ -280,7 +280,7 @@ function file_ajax_upload() {
 
   $commands = array();
   $commands[] = ajax_command_replace(NULL, $output, $settings);
-  return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
+  return array('#type' => 'ajax', '#commands' => $commands);
 }
 
 /**
