diff --git a/core/lib/Drupal/Core/Form/FormAjaxResponseBuilder.php b/core/lib/Drupal/Core/Form/FormAjaxResponseBuilder.php
index 4b826ff..2ed1526 100644
--- a/core/lib/Drupal/Core/Form/FormAjaxResponseBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormAjaxResponseBuilder.php
@@ -72,7 +72,7 @@ public function buildResponse(Request $request, array $form, FormStateInterface
     if (empty($callback) || !is_callable($callback)) {
       throw new HttpException(500, 'The specified #ajax callback is empty or not callable.');
     }
-    $result = call_user_func_array($callback, [&$form, &$form_state]);
+    $result = call_user_func_array($callback, [&$form, &$form_state, $request]);
 
     // If the callback is an #ajax callback, the result is a render array, and
     // we need to turn it into an AJAX response, so that we can add any commands
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 3cb1c8d..0309dc2 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -328,20 +328,6 @@
       }
       else if (this.element && element.form) {
         this.url = this.$form.attr('action');
-
-        // @todo If there's a file input on this form, then jQuery will submit
-        //   the AJAX response with a hidden Iframe rather than the XHR object.
-        //   If the response to the submission is an HTTP redirect, then the
-        //   Iframe will follow it, but the server won't content negotiate it
-        //   correctly, because there won't be an ajax_iframe_upload POST
-        //   variable. Until we figure out a work around to this problem, we
-        //   prevent AJAX-enabling elements that submit to the same URL as the
-        //   form when there's a file input. For example, this means the Delete
-        //   button on the edit form of an Article node doesn't open its
-        //   confirmation form in a dialog.
-        if (this.$form.find(':file').length) {
-          return;
-        }
       }
     }
 
@@ -421,7 +407,8 @@
     else {
       ajax.options.url += '&';
     }
-    ajax.options.url += Drupal.ajax.WRAPPER_FORMAT + '=drupal_' + (element_settings.dialogType || 'ajax');
+    // @todo Passing both always is kinda redicilous!
+    ajax.options.url += Drupal.ajax.AJAX_FORM_REQUEST + '=1&' + Drupal.ajax.WRAPPER_FORMAT + '=drupal_' + (element_settings.dialogType || 'ajax');
 
     // Bind the ajaxSubmit function to the element event.
     $(ajax.element).on(element_settings.event, function (event) {
@@ -446,6 +433,11 @@
   };
 
   /**
+   * Request key for a form that was triggered via AJAX.
+   */
+   Drupal.ajax.AJAX_FORM_REQUEST = 'ajax_form';
+
+  /**
    * URL query attribute to indicate the wrapper used to render a request.
    *
    * The wrapper format determines how the HTML is wrapped, for example in a
diff --git a/core/modules/file/file.routing.yml b/core/modules/file/file.routing.yml
index d9d4efa..b7d2e6a 100644
--- a/core/modules/file/file.routing.yml
+++ b/core/modules/file/file.routing.yml
@@ -1,12 +1,3 @@
-file.ajax_upload:
-  path: '/file/ajax'
-  defaults:
-    _controller: '\Drupal\file\Controller\FileWidgetAjaxController::upload'
-  options:
-    _theme: ajax_base_page
-  requirements:
-    _permission: 'access content'
-
 file.ajax_progress:
   path: '/file/progress'
   defaults:
diff --git a/core/modules/file/src/Controller/FileWidgetAjaxController.php b/core/modules/file/src/Controller/FileWidgetAjaxController.php
index 40785a8..fb36e65 100644
--- a/core/modules/file/src/Controller/FileWidgetAjaxController.php
+++ b/core/modules/file/src/Controller/FileWidgetAjaxController.php
@@ -7,18 +7,7 @@
 
 namespace Drupal\file\Controller;
 
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Ajax\AjaxResponse;
-use Drupal\Core\Ajax\ReplaceCommand;
-use Drupal\Core\Form\FormAjaxResponseBuilderInterface;
-use Drupal\Core\Form\FormBuilderInterface;
-use Drupal\Core\Render\RendererInterface;
-use Drupal\system\Controller\FormAjaxController;
-use Psr\Log\LoggerInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\JsonResponse;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
 
 /**
  * Defines a controller to respond to file widget AJAX requests.
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index 4bfd6ca..5ac2c94 100644
--- a/core/modules/file/src/Element/ManagedFile.php
+++ b/core/modules/file/src/Element/ManagedFile.php
@@ -7,10 +7,14 @@
 
 namespace Drupal\file\Element;
 
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\ReplaceCommand;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element\FormElement;
 use Drupal\Core\Url;
 use Drupal\file\Entity\File;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Provides an AJAX/progress aware widget for uploading and saving a file.
@@ -124,6 +128,63 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
   }
 
   /**
+   * #ajax callback for managed_file upload forms.
+   *
+   * This ajax callback takes care about ensuring that there is no broken
+   * request due to too big files, as well as adding a class to the response
+   * that a new file got uploaded.
+   *
+   * @param array $form
+   *   The build form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   The ajax response of the ajax upload.
+   */
+  public static function uploadAjaxCallback(&$form, FormStateInterface &$form_state, Request $request) {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
+    $form_parents = explode('/', $request->query->get('element_parents'));
+    $form_build_id = $request->query->get('form_build_id');
+    $request_form_build_id = $request->request->get('form_build_id');
+
+    if (empty($request_form_build_id) || $form_build_id !== $request_form_build_id) {
+      // Invalid request.
+      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');
+      $response = new AjaxResponse();
+      $status_messages = array('#type' => 'status_messages');
+      return $response->addCommand(new ReplaceCommand(NULL, $renderer->renderRoot($status_messages)));
+    }
+
+    // Retrieve the element to be rendered.
+    $form = NestedArray::getValue($form, $form_parents);
+
+
+    // Add the special Ajax class if a new file was added.
+    $current_file_count = $form_state->get('file_upload_delta_initial');
+    if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
+      $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
+    }
+    // Otherwise just add the new content class on a placeholder.
+    else {
+      $form['#suffix'] .= '<span class="ajax-new-content"></span>';
+    }
+
+    $status_messages = array('#type' => 'status_messages');
+    $form['#prefix'] .= $renderer->renderRoot($status_messages);
+    $output = $renderer->renderRoot($form);
+
+    $response = new AjaxResponse();
+    $response->setAttachments($form['#attached']);
+
+    return $response->addCommand(new ReplaceCommand(NULL, $output));
+  }
+
+  /**
    * Render API callback: Expands the managed_file element type.
    *
    * Expands the file type to include Upload and Remove buttons, as well as
@@ -145,8 +206,7 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
     $element['#tree'] = TRUE;
 
     $ajax_settings = [
-      // @todo Remove this in https://www.drupal.org/node/2500527.
-      'url' => Url::fromRoute('file.ajax_upload'),
+      'callback' => [get_called_class(), 'uploadAjaxCallback'],
       'options' => [
         'query' => [
           'element_parents' => implode('/', $element['#array_parents']),
diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
index da48f74..08ea7b3 100644
--- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
+++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
@@ -407,7 +407,6 @@ public static function process($element, FormStateInterface $form_state, $form)
     // file, the entire group of file fields is updated together.
     if ($element['#cardinality'] != 1) {
       $parents = array_slice($element['#array_parents'], 0, -1);
-      $new_url = Url::fromRoute('file.ajax_upload');
       $new_options = array(
         'query' => array(
           'element_parents' => implode('/', $parents),
@@ -418,7 +417,6 @@ public static function process($element, FormStateInterface $form_state, $form)
       $new_wrapper = $field_element['#id'] . '-ajax-wrapper';
       foreach (Element::children($element) as $key) {
         if (isset($element[$key]['#ajax'])) {
-          $element[$key]['#ajax']['url'] = $new_url->setOptions($new_options);
           $element[$key]['#ajax']['options'] = $new_options;
           $element[$key]['#ajax']['wrapper'] = $new_wrapper;
         }
@@ -451,6 +449,19 @@ public static function processMultiple($element, FormStateInterface $form_state,
     $element_children = Element::children($element, TRUE);
     $count = count($element_children);
 
+    // Count the amount of already uploaded files, in order to display new
+    // items in \Drupal\file\Element\ManagedFile::uploadAjaxCallback.
+    if (!$form_state->isRebuilding()) {
+      $count_items_before = 0;
+      foreach ($element_children as $children) {
+        if (!empty($element[$children]['#default_value']['fids'])) {
+          $count_items_before++;
+        }
+      }
+
+      $form_state->set('file_upload_delta_initial', $count_items_before);
+    }
+
     foreach ($element_children as $delta => $key) {
       if ($key != $element['#file_upload_delta']) {
         $description = static::getDescriptionFromElement($element[$key]);
