diff --git a/core/includes/form.inc b/core/includes/form.inc
index 8170820..91ffdb8 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -462,6 +462,21 @@ function template_preprocess_form_element(&$variables) {
   $variables['label'] = array('#theme' => 'form_element_label');
   $variables['label'] += array_intersect_key($element, array_flip(array('#id', '#required', '#title', '#title_display')));
 
+  // The element may have specified its label should refer to one of its
+  // children, or to a fixed string, rather than the default of itself.
+  if(!empty($element['#for'])) {
+    if(is_array($element['#for'])) {
+      // if #for is an array, it is interpreted as a reference to the #id of a
+      // child of $element.
+      $for_element = NestedArray::getValue($element, $element['#for']);
+      if(!empty($for_element['#id'])) {
+        $variables['label']['#for'] = $for_element['#id'];
+      }
+    } else {
+      $variables['label']['#for'] = $element['#for'];
+    }
+  }
+
   $variables['children'] = $element['#children'];
 }
 
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 64e01e6..1e305dd 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -24,6 +24,7 @@
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\HttpFoundation\Response;
+use Zend\Stdlib\ArrayUtils;
 
 /**
  * Provides form building and processing.
@@ -783,12 +784,15 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
       $element['#defaults_loaded'] = TRUE;
     }
     // Assign basic defaults common for all form elements.
-    $element += array(
-      '#required' => FALSE,
-      '#attributes' => array(),
-      '#title_display' => 'before',
-      '#description_display' => 'after',
-      '#errors' => NULL,
+
+    $element = ArrayUtils::merge(array(
+        '#required' => FALSE,
+        '#attributes' => array(),
+        '#title_display' => 'before',
+        '#description_display' => 'after',
+        '#errors' => NULL,
+      ),
+      $element
     );
 
     // Special handling if we're on the top level form element.
diff --git a/core/lib/Drupal/Core/Render/Element/File.php b/core/lib/Drupal/Core/Render/Element/File.php
index b8d5263..e18564d 100644
--- a/core/lib/Drupal/Core/Render/Element/File.php
+++ b/core/lib/Drupal/Core/Render/Element/File.php
@@ -49,7 +49,7 @@ public function getInfo() {
    */
   public static function processFile(&$element, FormStateInterface $form_state, &$complete_form) {
     if ($element['#multiple']) {
-      $element['#attributes'] = array('multiple' => 'multiple');
+      $element['#attributes'] += array('multiple' => 'multiple');
       $element['#name'] .= '[]';
     }
     return $element;
diff --git a/core/modules/file/file.js b/core/modules/file/file.js
index 5a4aea8..30e2d45 100644
--- a/core/modules/file/file.js
+++ b/core/modules/file/file.js
@@ -17,35 +17,21 @@
    * @type {Drupal~behavior}
    */
   Drupal.behaviors.fileValidateAutoAttach = {
-    attach: function (context, settings) {
+    attach: function (context) {
       var $context = $(context);
-      var elements;
-
-      function initFileValidation(selector) {
-        $context.find(selector)
-          .once('fileValidate')
-          .on('change.fileValidate', {extensions: elements[selector]}, Drupal.file.validateExtension);
-      }
 
-      if (settings.file && settings.file.elements) {
-        elements = settings.file.elements;
-        Object.keys(elements).forEach(initFileValidation);
-      }
+      $context.find('.js-form-managed-file .js-form-file')
+        .once('fileValidate')
+        .on('change.fileValidate', Drupal.file.validateExtension);
     },
     detach: function (context, settings, trigger) {
       var $context = $(context);
-      var elements;
-
-      function removeFileValidation(selector) {
-        $context.find(selector)
+      
+      if (trigger === 'unload') {
+        $context.find('.js-form-managed-file .js-form-file')
           .removeOnce('fileValidate')
           .off('change.fileValidate', Drupal.file.validateExtension);
       }
-
-      if (trigger === 'unload' && settings.file && settings.file.elements) {
-        elements = settings.file.elements;
-        Object.keys(elements).forEach(removeFileValidation);
-      }
     }
   };
 
@@ -117,10 +103,10 @@
       $('.file-upload-js-error').remove();
 
       // Add client side validation for the input[type=file].
-      var extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
-      if (extensionPattern.length > 1 && this.value.length > 0) {
-        var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'gi');
-        if (!acceptableMatch.test(this.value)) {
+      var extensionPattern = $(event.target).attr('data-drupal-validate-extensions').replace(/,|\s+/g, '|');
+      if (extensionPattern.length > 1 && event.target.value.length > 0) {
+        var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'gi');        
+        if (!acceptableMatch.test(event.target.value)) {
           var error = Drupal.t("The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.", {
             // According to the specifications of HTML5, a file upload control
             // should not reveal the real local path to the file that a user
@@ -129,7 +115,7 @@
             // confusion by leaving the user thinking perhaps Drupal could not
             // find the file because it messed up the file path. To avoid this
             // confusion, therefore, we strip out the bogus fakepath string.
-            '%filename': this.value.replace('C:\\fakepath\\', ''),
+            '%filename': event.target.value.replace('C:\\fakepath\\', ''),
             '%extensions': extensionPattern.replace(/\|/g, ', ')
           });
           $(this).closest('div.js-form-managed-file').prepend('<div class="messages messages--error file-upload-js-error" aria-live="polite">' + error + '</div>');
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index c2fa103..666c322 100644
--- a/core/modules/file/src/Element/ManagedFile.php
+++ b/core/modules/file/src/Element/ManagedFile.php
@@ -275,9 +275,15 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
     }
 
     // The file upload field itself.
+    $validate_attrs = [];
+    if(!empty($element['#upload_validators']['file_validate_extensions'])) {
+      $validate_attrs += ['data-drupal-validate-extensions' => implode(' ', $element['#upload_validators']['file_validate_extensions'])];
+    }
+    
     $element['upload'] = [
       '#name' => 'files[' . $parents_prefix . ']',
       '#type' => 'file',
+      '#attributes' => $validate_attrs,
       '#title' => t('Choose a file'),
       '#title_display' => 'invisible',
       '#size' => $element['#size'],
@@ -304,16 +310,11 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
         }
       }
     }
-
-    // Add the extension list to the page as JavaScript settings.
-    if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
-      $extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0])));
-      $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $element['#id']] = $extension_list;
-    }
-
-    // Let #id point to the file element, so the field label's 'for' corresponds
-    // with it.
-    $element['#id'] = &$element['upload']['#id'];
+    
+    // Let #for reference the file element by its array index. This causes
+    // the label for the overall manged file form element to reference the 
+    // nested file input, see form.inc's template_preprocess_form_element.
+    $element['#for'] = array('upload');
 
     // Prefix and suffix used for Ajax replacement.
     $element['#prefix'] = '<div id="' . $ajax_wrapper_id . '">';
