diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml
index d5d8e5c7a7..18a8f7bfa5 100644
--- a/core/modules/media/config/schema/media.schema.yml
+++ b/core/modules/media/config/schema/media.schema.yml
@@ -59,6 +59,9 @@ field.formatter.settings.oembed:
     max_height:
       type: integer
       label: 'Maximum height'
+    scale:
+      type: boolean
+      label: 'Scale content to fit the container'
 
 field.widget.settings.oembed_textfield:
   type: field.widget.settings.string_textfield
diff --git a/core/modules/media/css/oembed.formatter.css b/core/modules/media/css/oembed.formatter.css
new file mode 100644
index 0000000000..caf220e444
--- /dev/null
+++ b/core/modules/media/css/oembed.formatter.css
@@ -0,0 +1,3 @@
+.media-oembed-content {
+  max-width: 100%;
+}
diff --git a/core/modules/media/css/oembed.frame.css b/core/modules/media/css/oembed.frame.css
new file mode 100644
index 0000000000..15deb11676
--- /dev/null
+++ b/core/modules/media/css/oembed.frame.css
@@ -0,0 +1,10 @@
+iframe {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  margin: 0;
+  width: 100%;
+  height: 100%;
+}
diff --git a/core/modules/media/media.libraries.yml b/core/modules/media/media.libraries.yml
index 72496a3233..ecad094b03 100644
--- a/core/modules/media/media.libraries.yml
+++ b/core/modules/media/media.libraries.yml
@@ -11,3 +11,15 @@ type_form:
     js/type_form.js: {}
   dependencies:
     - core/drupal.form
+
+oembed.formatter:
+  version VERSION
+  css:
+    component:
+      css/oembed.formatter.css: {}
+
+oembed.frame:
+  version: VERSION
+  css:
+    component:
+      css/oembed.frame.css: {}
diff --git a/core/modules/media/media.module b/core/modules/media/media.module
index f877a47025..48ef64777f 100644
--- a/core/modules/media/media.module
+++ b/core/modules/media/media.module
@@ -77,6 +77,7 @@ function media_theme() {
     'media_oembed_iframe' => [
       'variables' => [
         'media' => NULL,
+        'placeholder_token' => '',
       ],
     ],
   ];
diff --git a/core/modules/media/src/Controller/OEmbedIframeController.php b/core/modules/media/src/Controller/OEmbedIframeController.php
index 7a103d4c6f..497cb4004d 100644
--- a/core/modules/media/src/Controller/OEmbedIframeController.php
+++ b/core/modules/media/src/Controller/OEmbedIframeController.php
@@ -4,8 +4,8 @@
 
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Cache\CacheableMetadata;
-use Drupal\Core\Cache\CacheableResponse;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Render\HtmlResponse;
 use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Url;
@@ -121,10 +121,11 @@ public function render(Request $request) {
     $url = $request->query->get('url');
     $max_width = $request->query->getInt('max_width', NULL);
     $max_height = $request->query->getInt('max_height', NULL);
+    $scale = $request->query->getBoolean('scale', TRUE);
 
     // Hash the URL and max dimensions, and ensure it is equal to the hash
     // parameter passed in the query string.
-    $hash = $this->iFrameUrlHelper->getHash($url, $max_width, $max_height);
+    $hash = $this->iFrameUrlHelper->getHash($url, $max_width, $max_height, $scale);
     if (!Crypt::hashEquals($hash, $request->query->get('hash', ''))) {
       throw new AccessDeniedHttpException('This resource is not available');
     }
@@ -132,13 +133,15 @@ public function render(Request $request) {
     // Return a response instead of a render array so that the frame content
     // will not have all the blocks and page elements normally rendered by
     // Drupal.
-    $response = new CacheableResponse();
+    $response = new HtmlResponse();
     $response->addCacheableDependency(Url::createFromRequest($request));
 
     try {
       $resource_url = $this->urlResolver->getResourceUrl($url, $max_width, $max_height);
       $resource = $this->resourceFetcher->fetchResource($resource_url);
 
+      $placeholder_token = Crypt::randomBytesBase64(55);
+
       // Render the content in a new render context so that the cacheability
       // metadata of the rendered HTML will be captured correctly.
       $element = [
@@ -153,12 +156,22 @@ public function render(Request $request) {
           // \Drupal\Core\Render\MainContent\HtmlRenderer::renderResponse().
           'tags' => ['rendered'],
         ],
+        '#attached' => [
+          'html_response_attachment_placeholders' => [
+            'styles' => '<css-placeholder token="' . $placeholder_token . '">',
+          ],
+        ],
+        '#placeholder_token' => $placeholder_token,
       ];
+      if ($scale) {
+        $element['#attached']['library'][] = 'media/oembed.frame';
+      }
       $content = $this->renderer->executeInRenderContext(new RenderContext(), function () use ($resource, $element) {
         return $this->renderer->render($element);
       });
       $response
         ->setContent($content)
+        ->setAttachments($element['#attached'])
         ->addCacheableDependency($resource)
         ->addCacheableDependency(CacheableMetadata::createFromRenderArray($element));
     }
diff --git a/core/modules/media/src/IFrameUrlHelper.php b/core/modules/media/src/IFrameUrlHelper.php
index 5da09593de..2dc1dcd41f 100644
--- a/core/modules/media/src/IFrameUrlHelper.php
+++ b/core/modules/media/src/IFrameUrlHelper.php
@@ -52,12 +52,14 @@ public function __construct(RequestContext $request_context, PrivateKey $private
    *   (optional) The maximum width of the resource.
    * @param int $max_height
    *   (optional) The maximum height of the resource.
+   * @param bool $scale
+   *   (optional) Whether oEmbed content may scale to fit the container.
    *
    * @return string
    *   The hashed URL.
    */
-  public function getHash($url, $max_width = NULL, $max_height = NULL) {
-    return Crypt::hmacBase64("$url:$max_width:$max_height", $this->privateKey->get() . Settings::getHashSalt());
+  public function getHash($url, $max_width = NULL, $max_height = NULL, $scale = TRUE) {
+    return Crypt::hmacBase64("$url:$max_width:$max_height:$scale", $this->privateKey->get() . Settings::getHashSalt());
   }
 
   /**
diff --git a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
index 443df18098..5e9644cb36 100644
--- a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
+++ b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
@@ -150,6 +150,7 @@ public static function defaultSettings() {
     return [
       'max_width' => 0,
       'max_height' => 0,
+      'scale' => TRUE,
     ] + parent::defaultSettings();
   }
 
@@ -160,6 +161,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
     $element = [];
     $max_width = $this->getSetting('max_width');
     $max_height = $this->getSetting('max_height');
+    $scale = $this->getSetting('scale');
 
     foreach ($items as $delta => $item) {
       $main_property = $item->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName();
@@ -199,7 +201,8 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
             'url' => $value,
             'max_width' => $max_width,
             'max_height' => $max_height,
-            'hash' => $this->iFrameUrlHelper->getHash($value, $max_width, $max_height),
+            'scale' => $scale,
+            'hash' => $this->iFrameUrlHelper->getHash($value, $max_width, $max_height, $scale),
           ],
         ]);
 
@@ -220,8 +223,12 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
             'allowtransparency' => TRUE,
             'width' => $max_width ?: $resource->getWidth(),
             'height' => $max_height ?: $resource->getHeight(),
+            'class' => ['media-oembed-content'],
           ],
         ];
+        if ($scale) {
+          $element[$delta]['#attached']['library'][] = 'media/oembed.formatter';
+        }
 
         CacheableMetadata::createFromObject($resource)
           ->addCacheTags($this->config->getCacheTags())
@@ -254,6 +261,11 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
         '#field_suffix' => $this->t('pixels'),
         '#min' => 0,
       ],
+      'scale' => [
+        '#type' => 'checkbox',
+        '#title' => $this->t('Scale content to fit the container'),
+        '#default_value' => $this->getSetting('scale'),
+      ],
     ];
   }
 
@@ -278,6 +290,10 @@ public function settingsSummary() {
         '%max_height' => $this->getSetting('max_height'),
       ]);
     }
+
+    if ($this->getSetting('scale')) {
+      $summary[] = $this->t('Scale content to fit the container');
+    }
     return $summary;
   }
 
diff --git a/core/modules/media/templates/media-oembed-iframe.html.twig b/core/modules/media/templates/media-oembed-iframe.html.twig
index 96de5dfbad..985b4dd7a9 100644
--- a/core/modules/media/templates/media-oembed-iframe.html.twig
+++ b/core/modules/media/templates/media-oembed-iframe.html.twig
@@ -8,6 +8,9 @@
 #}
 <!DOCTYPE html>
 <html>
+  <head>
+    <css-placeholder token="{{ placeholder_token }}">
+  </head>
   <body style="margin: 0">
     {{ media|raw }}
   </body>
diff --git a/core/modules/media/tests/fixtures/oembed/video_vimeo.json b/core/modules/media/tests/fixtures/oembed/video_vimeo.json
index fac8a0d7b6..1549d2df88 100644
--- a/core/modules/media/tests/fixtures/oembed/video_vimeo.json
+++ b/core/modules/media/tests/fixtures/oembed/video_vimeo.json
@@ -6,7 +6,7 @@
   "title": "Drupal Rap Video - Schipulcon09",
   "author_name": "Tendenci - The Open Source AMS",
   "author_url": "https:\/\/vimeo.com\/schipul",
-  "html": "<h1>By the power of Greyskull, Vimeo works!</h1>",
+  "html": "<iframe width=\"480\">By the power of Greyskull, Vimeo works!</iframe>",
   "width": 480,
   "height": 360,
   "description": "Special thanks to Tendenci, formerly Schipul for sponsoring this video with training, equipment and time. The open source way. All creative however was self directed by the individuals - A. Hughes (www.schipul.com\/ahughes) featuring QCait (www.schipul.com\/qcait) - Hands On Drupal\n\nDrupal is a free software package that allows an individual or a community of users to easily publish, manage and organize a wide variety of content on a website.\n\nNeed a little Drupal help or just want to geek out with us?  Visit our www.schipul.com\/drupal for more info - we'd love to connect!\n\nGo here for Drupal Common Terms and Suggested Modules : http:\/\/schipul.com\/en\/helpfiles\/v\/229",
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaSourceOEmbedVideoTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceOEmbedVideoTest.php
index cb7ae2ac5c..b62c8111ce 100644
--- a/core/modules/media/tests/src/FunctionalJavascript/MediaSourceOEmbedVideoTest.php
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceOEmbedVideoTest.php
@@ -91,6 +91,18 @@ public function testMediaOEmbedVideoSource() {
     $assert_session->selectExists('field_map[author_name]')->setValue('field_string_author_name');
     $assert_session->buttonExists('Save')->press();
 
+    // Configure the iframe to be narrower than the actual video, so we can
+    // verify that the video scales correctly.
+    $display = entity_get_display('media', $media_type_id, 'default');
+    $this->assertFalse($display->isNew());
+    $component = $display->getComponent('field_media_oembed_video');
+    $this->assertInternalType('array', $component);
+    $component['settings']['max_width'] = 240;
+    // Ensure that scaling is enabled by default.
+    $this->assertTrue($component['settings']['scale']);
+    $display->setComponent('field_media_oembed_video', $component);
+    $this->assertSame(SAVED_UPDATED, $display->save());
+
     $this->hijackProviderEndpoints();
     $video_url = 'https://vimeo.com/7073899';
     ResourceController::setResourceUrl($video_url, $this->getFixturesDirectory() . '/video_vimeo.json');
@@ -114,14 +126,22 @@ public function testMediaOEmbedVideoSource() {
 
     // Ensure the iframe exists and that its src attribute contains a coherent
     // URL with the query parameters we expect.
-    $iframe_url = $assert_session->elementExists('css', 'iframe')->getAttribute('src');
-    $iframe_url = parse_url($iframe_url);
+    $iframe = $assert_session->elementExists('css', 'iframe');
+    $iframe_url = parse_url($iframe->getAttribute('src'));
     $this->assertStringEndsWith('/media/oembed', $iframe_url['path']);
     $this->assertNotEmpty($iframe_url['query']);
     $query = [];
     parse_str($iframe_url['query'], $query);
     $this->assertSame($video_url, $query['url']);
     $this->assertNotEmpty($query['hash']);
+    // Ensure that the outer iframe's width respects the formatter settings.
+    $this->assertSame('240', $iframe->getAttribute('width'));
+    // Check the inner iframe to make sure that CSS has been applied to scale it
+    // correctly, regardless of whatever its width attribute may be (the fixture
+    // hard-codes it to 480).
+    $inner_frame = 'frames[0].document.querySelector("iframe")';
+    $this->assertSame('480', $session->evaluateScript("$inner_frame.getAttribute('width')"));
+    $this->assertLessThanOrEqual(240, $session->evaluateScript("$inner_frame.clientWidth"));
 
     // Make sure the thumbnail is displayed from uploaded image.
     $assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', '/oembed_thumbnails/' . basename($thumbnail));
diff --git a/core/profiles/demo_umami/config/install/core.entity_view_display.media.remote_video.default.yml b/core/profiles/demo_umami/config/install/core.entity_view_display.media.remote_video.default.yml
index 6894add2a9..5b23af1355 100644
--- a/core/profiles/demo_umami/config/install/core.entity_view_display.media.remote_video.default.yml
+++ b/core/profiles/demo_umami/config/install/core.entity_view_display.media.remote_video.default.yml
@@ -18,6 +18,7 @@ content:
     settings:
       max_width: 0
       max_height: 0
+      scale: true
     third_party_settings: {  }
     region: content
 hidden:
diff --git a/core/profiles/standard/config/optional/core.entity_view_display.media.remote_video.default.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.remote_video.default.yml
index cc8c1388c6..d269e958f2 100644
--- a/core/profiles/standard/config/optional/core.entity_view_display.media.remote_video.default.yml
+++ b/core/profiles/standard/config/optional/core.entity_view_display.media.remote_video.default.yml
@@ -18,6 +18,7 @@ content:
     settings:
       max_width: 0
       max_height: 0
+      scale: true
     third_party_settings: {  }
     region: content
 hidden:
diff --git a/core/themes/stable/css/media/oembed.formatter.css b/core/themes/stable/css/media/oembed.formatter.css
new file mode 100644
index 0000000000..caf220e444
--- /dev/null
+++ b/core/themes/stable/css/media/oembed.formatter.css
@@ -0,0 +1,3 @@
+.media-oembed-content {
+  max-width: 100%;
+}
diff --git a/core/themes/stable/css/media/oembed.frame.css b/core/themes/stable/css/media/oembed.frame.css
new file mode 100644
index 0000000000..15deb11676
--- /dev/null
+++ b/core/themes/stable/css/media/oembed.frame.css
@@ -0,0 +1,10 @@
+iframe {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  margin: 0;
+  width: 100%;
+  height: 100%;
+}
diff --git a/core/themes/stable/stable.info.yml b/core/themes/stable/stable.info.yml
index bf07d21456..8d774ce235 100644
--- a/core/themes/stable/stable.info.yml
+++ b/core/themes/stable/stable.info.yml
@@ -139,6 +139,15 @@ libraries-override:
       component:
         css/locale.admin.css: css/locale/locale.admin.css
 
+  media/oembed.formatter:
+    css:
+      component:
+        css/oembed.formatter.css: css/media/oembed.formatter.css
+  media/oembed.frame:
+    css:
+      component:
+        css/oembed.frame.css: css/media/oembed.frame.css
+
   menu_ui/drupal.menu_ui.adminforms:
     css:
       theme:
diff --git a/core/themes/stable/templates/content/media-oembed-iframe.html.twig b/core/themes/stable/templates/content/media-oembed-iframe.html.twig
index 96de5dfbad..985b4dd7a9 100644
--- a/core/themes/stable/templates/content/media-oembed-iframe.html.twig
+++ b/core/themes/stable/templates/content/media-oembed-iframe.html.twig
@@ -8,6 +8,9 @@
 #}
 <!DOCTYPE html>
 <html>
+  <head>
+    <css-placeholder token="{{ placeholder_token }}">
+  </head>
   <body style="margin: 0">
     {{ media|raw }}
   </body>
