diff --git a/core/lib/Drupal/Core/ContentNegotiation.php b/core/lib/Drupal/Core/ContentNegotiation.php
index f1a33d724d232b5e1d4dbdf0465c4ac4b9cb3348..ebab1a82da229a37608ce127d458290239e65aeb 100644
--- a/core/lib/Drupal/Core/ContentNegotiation.php
+++ b/core/lib/Drupal/Core/ContentNegotiation.php
@@ -12,10 +12,18 @@ class ContentNegotiation {
 
   public function getContentType(Request $request) {
     $acceptable_content_types = $request->getAcceptableContentTypes();
-    if (in_array('application/json', $request->getAcceptableContentTypes())) {
+    if ($request->isXmlHttpRequest()) {
+      if ($request->get('ajax_iframe_upload', FALSE)) {
+        return 'iframeupload';
+      }
+      else {
+        return 'ajax';
+      }
+    }
+    elseif (in_array('application/json', $request->getAcceptableContentTypes())) {
       return 'json';
     }
-    if(in_array('text/html', $acceptable_content_types) || in_array('*/*', $acceptable_content_types)) {
+    elseif(in_array('text/html', $acceptable_content_types) || in_array('*/*', $acceptable_content_types)) {
       return 'html';
     }
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
index 8ade061551a526456eaa1e30c9b13165d7063263..2f22f58bf9d75c4d50ba5e8f47d0c1cb6843815b 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -37,6 +37,27 @@ protected function createJsonResponse() {
     return $response;
   }
 
+  protected function createAjaxResponse(GetResponseEvent $event) {
+    $response = new Response();
+    $response->headers->set('Content-Type', 'application/json; charset=utf-8');
+
+    return $response;
+  }
+
+  protected function createIframeUploadResponse(GetResponseEvent $event) {
+    $response = new Response();
+
+    // Browsers do not allow JavaScript to read the contents of a user's local
+    // files. To work around that, the jQuery Form plugin submits forms containing
+    // a file input element to an IFRAME, instead of using XHR. Browsers do not
+    // normally expect JSON strings as content within an IFRAME, so the response
+    // must be customized accordingly.
+    // @see http://malsup.com/jquery/form/#file-upload
+    // @see Drupal.ajax.prototype.beforeSend()
+    $response->headers->set('Content-Type', 'text/html; charset=utf-8');
+
+    return $response;
+  }
 
   /**
    * Processes a successful controller into an HTTP 200 response.
@@ -75,6 +96,42 @@ public function onJson(GetResponseEvent $event) {
     return $response;
   }
 
+  public function onAjax(GetResponseEvent $event) {
+    $page_callback_result = $event->getControllerResult();
+
+    // Construct the response content from the page callback result.
+    $commands = ajax_prepare_response($page_callback_result);
+    $json = ajax_render($commands);
+
+    // Build the actual response object.
+    $response = $this->createAjaxResponse($event);
+    $response->setContent($json);
+
+    return $response;
+  }
+
+  public function onIframeUpload(GetResponseEvent $event) {
+    $page_callback_result = $event->getControllerResult();
+
+    // Construct the response content from the page callback result.
+    $commands = ajax_prepare_response($page_callback_result);
+    $json = ajax_render($commands);
+
+    // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
+    // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into
+    // links. This corrupts the JSON response. Protect the integrity of the
+    // JSON data by making it the value of a textarea.
+    // @see http://malsup.com/jquery/form/#file-upload
+    // @see http://drupal.org/node/1009382
+    $json = '<textarea>' . $json . '</textarea>';
+
+    // Build the actual response object.
+    $response = $this->createIframeUploadResponse($event);
+    $response->setContent($json);
+
+    return $response;
+  }
+
   /**
    * Processes a successful controller into an HTTP 200 response.
    *
diff --git a/core/modules/simpletest/drupal_web_test_case.php b/core/modules/simpletest/drupal_web_test_case.php
index ded4ad0da0cdcb3ba71ff8725cf1a4057ac09138..3a6e541ed98930cc7bfa20727861a6fd55340f51 100644
--- a/core/modules/simpletest/drupal_web_test_case.php
+++ b/core/modules/simpletest/drupal_web_test_case.php
@@ -1834,6 +1834,7 @@ protected function drupalGet($path, array $options = array(), array $headers = a
    * Retrieve a Drupal path or an absolute path and JSON decode the result.
    */
   protected function drupalGetAJAX($path, array $options = array(), array $headers = array()) {
+    $headers[] = 'X-Requested-With: XMLHttpRequest';
     return drupal_json_decode($this->drupalGet($path, $options, $headers));
   }
 
@@ -2041,6 +2042,7 @@ protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path
     }
     $content = $this->content;
     $drupal_settings = $this->drupalSettings;
+    $headers[] = 'X-Requested-With: XMLHttpRequest';
 
     // Get the Ajax settings bound to the triggering element.
     if (!isset($ajax_settings)) {