diff --git a/core/modules/media/config/install/core.entity_view_mode.media.full.yml b/core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml
similarity index 60%
copy from core/modules/media/config/install/core.entity_view_mode.media.full.yml
copy to core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml
index dfdbb3a..e082019 100644
--- a/core/modules/media/config/install/core.entity_view_mode.media.full.yml
+++ b/core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml
@@ -1,9 +1,9 @@
 langcode: en
-status: false
+status: true
 dependencies:
   module:
     - media
-id: media.full
-label: 'Full content'
+id: media.add_inline
+label: 'File Upload'
 targetEntityType: media
 cache: true
diff --git a/core/modules/media/config/install/core.entity_view_mode.media.full.yml b/core/modules/media/config/optional/core.entity_view_mode.media.full.yml
similarity index 100%
rename from core/modules/media/config/install/core.entity_view_mode.media.full.yml
rename to core/modules/media/config/optional/core.entity_view_mode.media.full.yml
diff --git a/core/modules/media/src/Element/MediaManagedFile.php b/core/modules/media/src/Element/MediaManagedFile.php
new file mode 100644
index 0000000..07b5148
--- /dev/null
+++ b/core/modules/media/src/Element/MediaManagedFile.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\media\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\file\Element\ManagedFile;
+use Drupal\media\Entity\Media;
+
+/**
+ * @FormElement("media_managed_file")
+ */
+class MediaManagedFile extends ManagedFile {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
+    if (!empty($element['#value']['target_id'])) {
+      // We're editing an entity with a previously-saved Media entity
+      // reference(s). In order not to confuse ManagedFile, load those entities'
+      // source fids and store them where it expects them.
+      $media_entity = Media::load($element['#value']['target_id']);
+      // @TODO: Could $media_entity be empty here in any scenario?
+      $media_source_field = $media_entity->getSource()->getConfiguration()['source_field'];
+      $file_value = $media_entity->get($media_source_field)->getValue();
+      // @TODO: Should / could we assume here that this will always have only
+      // 1 value?
+      foreach ($file_value as $value) {
+        $element['#value']['fids'][] = $value['target_id'];
+      }
+    }
+    $element = parent::processManagedFile($element, $form_state, $complete_form);
+
+    // We don't want to show the file-upload field for values that already have
+    // a file.
+    if (!empty($element['#files'])) {
+      $element['upload']['#access'] = FALSE;
+    }
+
+    $mids = isset($element['#value']['mids']) ? $element['#value']['mids'] : [];
+    $element['mids'] = [
+      '#type' => 'hidden',
+      '#value' => $mids,
+    ];
+
+    return $element;
+  }
+
+}
diff --git a/core/modules/media/src/Entity/Media.php b/core/modules/media/src/Entity/Media.php
index 49f7283..2c6862f 100644
--- a/core/modules/media/src/Entity/Media.php
+++ b/core/modules/media/src/Entity/Media.php
@@ -39,6 +39,7 @@
  *       "edit" = "Drupal\media\MediaForm",
  *       "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
  *     },
+ *     "add_inline" = "Drupal\media\Form\MediaInlineForm",
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
  *     "views_data" = "Drupal\media\MediaViewsData",
  *     "route_provider" = {
diff --git a/core/modules/media/src/Form/MediaInlineForm.php b/core/modules/media/src/Form/MediaInlineForm.php
new file mode 100644
index 0000000..8ac4d8c
--- /dev/null
+++ b/core/modules/media/src/Form/MediaInlineForm.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Drupal\media\Form;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\media\MediaInlineFormInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Media entity inline form handler.
+ */
+class MediaInlineForm implements MediaInlineFormInterface {
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity type managed by this handler.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected $entityType;
+
+  /**
+   * Module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs the inline entity form controller.
+   *
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   The entity field manager.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   */
+  public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, EntityTypeInterface $entity_type) {
+    $this->entityFieldManager = $entity_field_manager;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->moduleHandler = $module_handler;
+    $this->entityType = $entity_type;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $container->get('entity_field.manager'),
+      $container->get('entity_type.manager'),
+      $container->get('module_handler'),
+      $entity_type
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function entityForm(array $entity_form, FormStateInterface $form_state) {
+    // Build the media entity form.
+    // @TODO Why shouldn't we assume this is a MediaInterface instead?
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $entity_form['#entity'];
+    $form_display = EntityFormDisplay::collectRenderDisplay($entity, $entity_form['#form_mode']);
+    $form_display->buildForm($entity, $entity_form, $form_state);
+    $entity_form['#weight'] = 100;
+
+    // In the service of keeping this form as user-friendly as possible in the
+    // context of a parent entity form, only show required fields.
+    // @TODO Inject the service.
+    /** @var \Drupal\Core\Entity\EntityFieldManager $entity_field_manager */
+    $entity_field_manager = \Drupal::service('entity_field.manager');
+    $field_definitions = $entity_field_manager->getFieldDefinitions('media', 'file');
+    /** @var \Drupal\Core\Field\BaseFieldDefinition $field_definition */
+    foreach ($field_definitions as $field_definition) {
+      $field_name = $field_definition->getName();
+      if (!$field_definition->isRequired()) {
+        $entity_form[$field_name]['#access'] = FALSE;
+      }
+      else {
+        // @TODO: Why is this here?
+        $entity_form[$field_name]['#element_validate'] = [];
+      }
+
+    }
+
+    // We've already set the just-uploaded file as the value of the source
+    // field, so hide that too.
+    $source_field = $entity->getSource()
+      ->getSourceFieldDefinition($entity->bundle->entity)
+      ->getName();
+    $entity_form[$source_field]['#access'] = FALSE;
+
+    // Inline entities inherit the parent language, so hide translation-related
+    // fields as well.
+    $langcode_key = $this->entityType->getKey('langcode');
+    if ($langcode_key && isset($entity_form[$langcode_key])) {
+      $entity_form[$langcode_key]['#access'] = FALSE;
+    }
+    if (!empty($entity_form['#translating'])) {
+      // Hide the non-translatable fields.
+      foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
+        if (isset($entity_form[$field_name]) && $field_name != $langcode_key) {
+          $entity_form[$field_name]['#access'] = $definition->isTranslatable();
+        }
+      }
+    }
+
+
+    return $entity_form;
+  }
+
+}
diff --git a/core/modules/media/src/MediaInlineFormInterface.php b/core/modules/media/src/MediaInlineFormInterface.php
new file mode 100644
index 0000000..d624e66
--- /dev/null
+++ b/core/modules/media/src/MediaInlineFormInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\media;
+
+use Drupal\Core\Entity\EntityHandlerInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Defines the interface for Media inline form handlers.
+ */
+interface MediaInlineFormInterface extends EntityHandlerInterface {
+
+  /**
+   * Builds the entity form.
+   *
+   * @param array $entity_form
+   *   The entity form, containing the following basic properties:
+   *   - #entity: The entity for the current entity form.
+   *   - #form_mode: The form mode used to display the entity form.
+   *   - #parents: Identifies the position of the entity form in the overall
+   *     parent form, and identifies the location where the field values are
+   *     placed within $form_state->getValues().
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state of the parent form.
+   */
+  public function entityForm(array $entity_form, FormStateInterface $form_state);
+
+}
diff --git a/core/modules/media/src/Plugin/Field/FieldWidget/MediaFileWidget.php b/core/modules/media/src/Plugin/Field/FieldWidget/MediaFileWidget.php
new file mode 100644
index 0000000..e103467
--- /dev/null
+++ b/core/modules/media/src/Plugin/Field/FieldWidget/MediaFileWidget.php
@@ -0,0 +1,327 @@
+<?php
+
+namespace Drupal\media\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\file\Entity\File;
+use Drupal\file\Plugin\Field\FieldWidget;
+use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
+use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
+use Drupal\file\Plugin\Field\FieldType\FileItem;
+use Drupal\media\Entity\Media;
+use Drupal\media\Entity\MediaType;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Link;
+
+/**
+ * Plugin implementation of the 'media_file' widget.
+ *
+ * @FieldWidget(
+ *   id = "media_file",
+ *   label = @Translation("File"),
+ *   field_types = {
+ *     "entity_reference"
+ *   }
+ * )
+ */
+class MediaFileWidget extends FileWidget {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Uses nearly the same code that FileWidget does here, except that we have to
+   * dig down through the target Media entity to *its* file field's upload
+   * location and upload validators.
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    $field_settings = $this->getFieldSettings();
+
+    // The field settings include defaults for the field type. However, this
+    // widget is a base class for other widgets (e.g., ImageWidget) that may act
+    // on field types without these expected settings.
+    $field_settings += [
+      'display_default' => NULL,
+      'display_field' => NULL,
+      'description_field' => NULL,
+    ];
+
+    $defaults = [
+      'mids' => [],
+      'fids' => [],
+      'display' => (bool) $field_settings['display_default'],
+      'description' => '',
+    ];
+
+    $target_bundle = array_shift($this->fieldDefinition->getSetting('handler_settings')['target_bundles']);
+    /** @var \Drupal\media\Entity\MediaType $media_type */
+    $media_type = MediaType::load($target_bundle);
+    $source = $media_type->getSource();
+    $source_data_definition = FieldItemDataDefinition::create($source->getSourceFieldDefinition($media_type));
+    $file_item = new FileItem($source_data_definition);
+
+    $element_info = $this->elementInfo->getInfo('media_managed_file');
+    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
+
+    $element += [
+      '#type' => 'media_managed_file',
+      '#upload_location' => $file_item->getUploadLocation(),
+      '#upload_validators' => $file_item->getUploadValidators(),
+      '#value_callback' => [get_class($this), 'value'],
+      '#process' => array_merge($element_info['#process'], [[get_class($this), 'process']]),
+      '#progress_indicator' => $this->getSetting('progress_indicator'),
+      // Allows this field to return an array instead of a single value.
+      '#extended' => TRUE,
+      // Add properties needed by value() and process() methods.
+      '#field_name' => $this->fieldDefinition->getName(),
+      // This is actually talking about the entity type ON WHICH this field is
+      // placed, not the entity type TO WHICH it can refer. In fact, it seems
+      // only to be used, unnecessarily, on FileWidget::validateMultipleCount().
+      '#entity_type' => $items->getEntity()->getEntityTypeId(),
+      '#display_field' => (bool) $field_settings['display_field'],
+      '#display_default' => $field_settings['display_default'],
+      '#description_field' => $field_settings['description_field'],
+      '#cardinality' => $cardinality,
+    ];
+
+    $element['#weight'] = $delta;
+
+    // Save mid, the target_id value that this field ultimately cares about.
+    if (!isset($items[$delta]->mids) && isset($items[$delta]->target_id)) {
+      $items[$delta]->mids = [$items[$delta]->target_id];
+    }
+    $element['#default_value'] = $items[$delta]->getValue() + $defaults;
+
+    $default_fids = $element['#extended'] ? $element['#default_value']['fids'] : $element['#default_value'];
+    if (empty($default_fids)) {
+      $file_upload_help = [
+        '#theme' => 'file_upload_help',
+        '#description' => $element['#description'],
+        '#upload_validators' => $element['#upload_validators'],
+        '#cardinality' => $cardinality,
+      ];
+      // @TODO: Inject the service.
+      $element['#description'] = \Drupal::service('renderer')->renderPlain($file_upload_help);
+      $element['#multiple'] = $cardinality != 1 ? TRUE : FALSE;
+      if ($cardinality != 1 && $cardinality != -1) {
+        $element['#element_validate'][] = [get_class($this), 'validateMultipleCount'];
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
+    // Override FileWidget::massageFormValues() in order to reference Media
+    // entities instead of files.
+    $new_values = [];
+    foreach ($values as &$value) {
+      foreach ($value['mids'] as $mid) {
+        $new_value = $value;
+        $new_value['target_id'] = $mid;
+        unset($new_value['mids']);
+        $new_values[] = $new_value;
+      }
+    }
+
+    return $new_values;
+  }
+
+  /**
+   * // @TODO update the docblock, the inherited one doesn't make sense anymore.
+   */
+  public static function value($element, $input, FormStateInterface $form_state) {
+    $return = parent::value($element, $input, $form_state);
+
+    // When the form has just been submitted with file(s) being uploaded,
+    // $input['mids'] should be empty. FileWidget::value() calls
+    // ManagedFile::valueCallback(), which populates $return['fids']. If there's
+    // info in there AND we're not doing the subsequent form rebuild, it's time
+    // to make the media.
+    if (empty($input['mids']) && !empty($return['fids']) && !$form_state->isRebuilding()) {
+      foreach ($return['fids'] as $fid) {
+        $file = File::load($fid);
+        /** @var \Drupal\media\MediaInterface $media_entity */
+        $media_entity = Media::create([
+          // @TODO: Shouldn't media entities automatically deal with empty names?
+          'name' => $file->getFilename(),
+          'bundle' => 'file', // TODO: generalize (note that we have to apply this to MediaInlineFileWidgetTest::setUp() too, in createdMediaType() call)
+          // @TODO: Inject the service.
+          'uid' => \Drupal::currentUser()->id(),
+          // TODO: figure out langcode in this context.
+          // 'langcode' => !empty($element['#langcode']) ? $element['#langcode'] : LanguageInterface::LANGCODE_DEFAULT,
+        ]);
+        $media_entity->setPublished();
+        $source_field = $media_entity->getSource()
+          ->getSourceFieldDefinition($media_entity->bundle->entity)
+          ->getName();
+        $media_entity->set($source_field, $fid);
+        // @TODO: Figure out how to handle (if we want to handle it) what to do
+        // with media entities created by an "abandoned" upload (e.g. when the
+        // user uploads the file, then does not save the host form).
+        $media_entity->save();
+        $return['mids'][] = $media_entity->id();
+      }
+    }
+    elseif (!empty($input['mids'])) {
+      $mid = $input['mids'];
+      $media_fields_key = 'media_' . $mid;
+
+      if (!empty($input[$media_fields_key])) {
+        $media_entity = Media::load($mid);
+        foreach ($input[$media_fields_key] as $field_name => $field_value) {
+          $media_entity->set($field_name, $field_value);
+        }
+        $media_entity->save();
+      }
+      else {
+        // Warn process() that this is a preexisting value.
+        $return['media_already_saved'] = TRUE;
+      }
+      $return['mids'] = [$mid];
+    }
+
+    return $return;
+  }
+
+  /**
+   * // @TODO update the docblock, the inherited one doesn't make sense anymore.
+   */
+  public static function process($element, FormStateInterface $form_state, $form) {
+    $element = parent::process($element, $form_state, $form);
+    $mid = FALSE;
+    $show_form = FALSE;
+
+    if (!empty($element['#value']['target_id'])) {
+      // This is an existing entity ref and we're loading the edit page the
+      // first time. Just show the icon and name.
+      $mid = $element['#value']['target_id'];
+    }
+    elseif (!empty($element['#value']['mids']) && count($element['#value']['mids']) === 1) {
+      // If $element['mids']['#value'] has multiple values, that means this is
+      // the first pass after someone uploaded several files; ignore. If it has
+      // only ONE, then this media entity was just created and we want to force
+      // the user to fill out all the new entity's required fields. I.e., show
+      // the form, as well as the standard icon and name.
+      $mid = $element['#value']['mids'][0];
+
+      if (empty($element['#value']['media_already_saved'])) {
+        $show_form = TRUE;
+      }
+    }
+
+    if ($mid) {
+      // First, get rid of the file link that FileManaged has created, and
+      // replace with a media-entity link.
+      $fid = $element['#value']['fids'][0];
+      unset($element['file_' . $fid]);
+      // Under some circumstances this button said "remove selected," but since
+      // we just removed the checkbox this button should instead always just say
+      // "remove."
+      $element['remove_button']['#value'] = t('Remove');
+
+      // Now show the media icon and name.
+      $form_element_name = 'media_' . $mid;
+      $form_element_parents = array_merge($element['#parents'], [$form_element_name]);
+      $media_entity = Media::load($mid);
+
+      $element[$form_element_name]['media_icon'] = [
+        '#theme' => 'image_style',
+        '#style_name' => 'thumbnail',
+        '#uri' => $media_entity->getSource()->getMetadata($media_entity, 'thumbnail_uri'),
+        '#weight' => -20,
+      ];
+      $link_build = Link::createFromRoute($media_entity->getName(), 'entity.media.canonical', ['media' => $mid])->toRenderable();
+      $element[$form_element_name]['media_name'] = $link_build + ['#weight' => -10];
+
+      if ($show_form) {
+        // Create and add the media entity form.
+        $form_mode = 'add_inline';
+        $media_form = [
+          '#entity' => $media_entity,
+          '#form_mode' => $form_mode,
+          '#parents' => $form_element_parents,
+        ];
+        // @TODO: Inject the service.
+        $inline_form_handler = \Drupal::entityTypeManager()
+          ->getHandler('media', $form_mode);
+        $media_form = $inline_form_handler->entityForm($media_form, $form_state);
+
+        // @TODO Investigate the possibility of wrapping the media form elements
+        // in a container, to avoid inadvertently overrides during this merge.
+        $element[$form_element_name] += $media_form;
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * Form submission handler for upload/remove button of formElement().
+   *
+   * Overrides FileWidget::submit(), performing the exact same functionality,
+   * only maintaining awareness of mids as well as fids.
+   *
+   * @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit()
+   * @see file_managed_file_submit()
+   */
+  public static function submit($form, FormStateInterface $form_state) {
+    // During the form rebuild, formElement() will create field item widget
+    // elements using re-indexed deltas, so clear out FormState::$input to
+    // avoid a mismatch between old and new deltas. The rebuilt elements will
+    // have #default_value set appropriately for the current state of the field,
+    // so nothing is lost in doing this.
+    $button = $form_state->getTriggeringElement();
+    $parents = array_slice($button['#parents'], 0, -2);
+    NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
+
+    // Go one level up in the form, to the widgets container.
+    $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
+    $field_name = $element['#field_name'];
+    $parents = $element['#field_parents'];
+
+    $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -2));
+    foreach ($submitted_values as $delta => $submitted_value) {
+      if (empty($submitted_value['fids'])) {
+        unset($submitted_values[$delta]);
+      }
+    }
+
+    // If there are more files uploaded via the same widget, we have to separate
+    // them, as we display each file in its own widget.
+    $new_values = [];
+    foreach ($submitted_values as $submitted_value) {
+      if (is_array($submitted_value['fids']) && is_array($submitted_value['mids'])) {
+        foreach ($submitted_value['fids'] as $delta => $fid) {
+          $new_value = $submitted_value;
+          $new_value['fids'] = [$fid];
+          $new_value['mids'] = [$submitted_value['mids'][$delta]];
+          $new_values[] = $new_value;
+        }
+      }
+    }
+
+    // Re-index deltas after removing empty items.
+    $submitted_values = array_values($new_values);
+
+    // Update form_state values.
+    NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -2), $submitted_values);
+
+    // Update items.
+    $field_state = static::getWidgetState($parents, $field_name, $form_state);
+    $field_state['items'] = $submitted_values;
+    static::setWidgetState($parents, $field_name, $form_state, $field_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function isApplicable(FieldDefinitionInterface $field_definition) {
+    return parent::isApplicable($field_definition);
+    // @TODO Restrict this to only fields targeting a single media type.
+  }
+}
diff --git a/core/modules/media/tests/fixtures/example_1.pdf b/core/modules/media/tests/fixtures/example_1.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/core/modules/media/tests/fixtures/example_2.pdf b/core/modules/media/tests/fixtures/example_2.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php
new file mode 100644
index 0000000..22fdc2e
--- /dev/null
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php
@@ -0,0 +1,202 @@
+<?php
+
+namespace Drupal\Tests\media\FunctionalJavascript;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\media\MediaInterface;
+
+/**
+ * @group media
+ */
+class MediaInlineFileWidgetTest extends MediaJavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['node'];
+
+  protected $strictConfigSchema = FALSE;
+
+  /**
+   * The bundle name of the test node.
+   *
+   * @var string
+   */
+  protected $nodeTypeId;
+
+  /**
+   * The bundle name of the test media.
+   *
+   * @var string
+   */
+  protected $mediaTypeId;
+
+  /**
+   * An indexed array of valid paths for test files.
+   *
+   * @var array
+   */
+  protected $testFilePaths;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $media_type = $this->createMediaType(['bundle' => 'file'], 'file');
+    $this->mediaTypeId = $media_type->id();
+    // Create two test fields on this media type, one of them required.
+    $media_required_field_storage = FieldStorageConfig::create([
+      'type' => 'string',
+      'entity_type' => 'media',
+      'field_name' => 'field_media_required',
+      'settings' => [
+        'max_length' => 255,
+      ],
+    ]);
+    $media_required_field_storage->save();
+    FieldConfig::create([
+      'field_storage' => $media_required_field_storage,
+      'bundle' => $this->mediaTypeId,
+      'required' => TRUE,
+    ])->save();
+    entity_get_form_display('media', $media_type->id(), 'add_inline')
+      ->setComponent('field_media_required', ['type' => 'string_textfield'])
+      ->save();
+    $media_nonrequired_field_storage = FieldStorageConfig::create([
+      'type' => 'string',
+      'entity_type' => 'media',
+      'field_name' => 'field_media_nonrequired',
+      'settings' => [
+        'max_length' => 255,
+      ],
+    ]);
+    $media_nonrequired_field_storage->save();
+    FieldConfig::create([
+      'field_storage' => $media_nonrequired_field_storage,
+      'bundle' => $this->mediaTypeId,
+      'required' => FALSE,
+    ])->save();
+    // Deliberately add it to the form, to test that even then we don't show
+    // this field when embedding the form inside the widget.
+    entity_get_form_display('media', $this->mediaTypeId, 'add_inline')
+      ->setComponent('field_media_nonrequired', ['type' => 'string_textfield'])
+      ->save();
+    entity_get_display('media', $this->mediaTypeId, 'default')
+      ->setComponent('field_media_file', [
+        'type' => 'file_default',
+        'label' => 'hidden',
+        'settings' => [],
+      ])
+      ->setComponent('field_media_required', [
+        'type' => 'string',
+        'label' => 'hidden',
+        'settings' => [],
+      ])
+      ->setComponent('field_media_nonrequired', [
+        'type' => 'string',
+        'label' => 'hidden',
+        'settings' => [],
+      ])
+      ->save();
+
+    $node_type = $this->drupalCreateContentType();
+    $this->nodeTypeId = $node_type->id();
+    $field_storage = FieldStorageConfig::create([
+      'type' => 'entity_reference',
+      'cardinality' => -1,
+      'entity_type' => 'node',
+      'field_name' => 'field_media_reference',
+      'settings' => [
+        'target_type' => 'media',
+      ],
+    ]);
+    $field_storage->save();
+    FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => $this->nodeTypeId,
+      'settings' => [
+        'handler_settings' => [
+          'target_bundles' => [
+            $this->mediaTypeId => $this->mediaTypeId,
+          ],
+        ],
+      ],
+    ])->save();
+    entity_get_form_display('node', $this->nodeTypeId, 'default')
+      ->setComponent('field_media_reference', ['type' => 'media_file'])
+      ->save();
+    entity_get_display('node', $this->nodeTypeId, 'default')
+      ->setComponent('field_media_reference', [
+        'type' => 'entity_reference_entity_view',
+        'label' => 'hidden',
+        'settings' => [
+          'view_mode' => 'full',
+        ],
+      ])->save();
+
+    // Create a couple of files for testing.
+    for ($i = 0; $i < 2; $i++) {
+      $test_filename = $this->randomMachineName() . '.txt';
+      $test_filepath = 'public://' . $test_filename;
+      file_put_contents($test_filepath, $this->randomMachineName());
+      $this->testFilePaths[$i] = \Drupal::service('file_system')->realpath($test_filepath);
+    }
+
+  }
+
+  /**
+   * Tests the file widget behavior..
+   */
+  public function testInlineFileWidget() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalGet('/node/add/' . $this->nodeTypeId);
+
+    // Ensure the media field is present, but no required fields exist in there.
+    $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]');
+    $assert_session->elementNotExists('css', '.required', $media_field);
+
+    // Upload a file and see that new required fields appeared.
+    $first_upload_element_id = 'edit-field-media-reference-0-upload';
+    $page->attachFileToField($first_upload_element_id, $this->testFilePaths[0]);
+    $result = $assert_session->waitForButton('Remove');
+    $this->assertNotEmpty($result);
+    $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]');
+    // The name field should be present and required.
+    $assert_session->elementExists('css', '.field--name-name input.required', $media_field);
+    // The additional field sould be present and required.
+    $assert_session->elementExists('css', '.field--name-field-media-required input.required', $media_field);
+    // The non-required field should not be there.
+    $assert_session->elementNotExists('css', '.field--name-field-media-nonrequired input', $media_field);
+
+    // By now the media entity should already exist, check that.
+    $media_id = $this->container->get('entity.query')->get('media')
+      ->sort('mid', 'DESC')
+      ->execute();
+    $media_id = reset($media_id);
+    /** @var MediaInterface $media */
+    $media = $this->container->get('entity_type.manager')->getStorage('media')->loadUnchanged($media_id);
+    $this->assertTrue($media->isPublished());
+
+    // Save the node and check page elements correspond to what is expected.
+    $node_title = 'Host Node 1';
+    $page->fillField('Title', $node_title);
+    $required_text = $this->randomMachineName();
+    $page->fillField('field_media_required', $required_text);
+    $page->pressButton('Save');
+    // The node has been correctly saved.
+    $assert_session->pageTextContains("{$this->nodeTypeId} $node_title has been created");
+    // We have a reference media container.
+    $media_field = $assert_session->elementExists('css', '.field--name-field-media-reference');
+    // We have a file link inside that container.
+    $assert_session->elementExists('css', '.field--name-field-media-file a', $media_field);
+    // The required field is present.
+    $required_element = $assert_session->elementExists('css', '.field--name-field-media-required', $media_field);
+    // The required field text is what we expect.
+    $this->assertEquals($required_text, $required_element->getText());
+  }
+
+}
