 core/lib/Drupal/Core/Cache/CacheableMetadata.php   | 69 ++++++++++++++++++++
 .../EntityReferenceFormatterBase.php               |  8 +--
 core/lib/Drupal/Core/Render/BubbleableMetadata.php | 63 ++----------------
 core/lib/Drupal/Core/Render/Renderer.php           |  5 +-
 .../EntityReferenceFormatterTest.php               |  8 +--
 .../modules/views/src/Plugin/views/field/Field.php |  6 +-
 .../Tests/Core/Cache/CacheableMetadataTest.php     | 74 ++++++++++++++++++++++
 .../Tests/Core/Render/BubbleableMetadataTest.php   | 35 ----------
 .../Drupal/Tests/Core/Render/RendererTest.php      |  8 ---
 9 files changed, 160 insertions(+), 116 deletions(-)

diff --git a/core/lib/Drupal/Core/Cache/CacheableMetadata.php b/core/lib/Drupal/Core/Cache/CacheableMetadata.php
index 04088b4..d21bd65 100644
--- a/core/lib/Drupal/Core/Cache/CacheableMetadata.php
+++ b/core/lib/Drupal/Core/Cache/CacheableMetadata.php
@@ -129,4 +129,73 @@ public function setCacheMaxAge($max_age) {
     return $this;
   }
 
+  /**
+   * Merges the values of another CacheableMetadata object with this one.
+   *
+   * @param \Drupal\Core\Cache\CacheableMetadata $other
+   *   The other CacheableMetadata object.
+   *
+   * @return static
+   *   A new CacheableMetadata object, with the merged data.
+   */
+  public function merge(CacheableMetadata $other) {
+    $result = new static();
+    $result->contexts = Cache::mergeContexts($this->contexts, $other->contexts);
+    $result->tags = Cache::mergeTags($this->tags, $other->tags);
+    $result->maxAge = Cache::mergeMaxAges($this->maxAge, $other->maxAge);
+    return $result;
+  }
+
+  /**
+   * Applies the values of this CacheableMetadata object to a render array.
+   *
+   * @param array &$build
+   *   A render array.
+   */
+  public function applyTo(array &$build) {
+    $build['#cache']['contexts'] = $this->contexts;
+    $build['#cache']['tags'] = $this->tags;
+    $build['#cache']['max-age'] = $this->maxAge;
+  }
+
+  /**
+   * Creates a CacheableMetadata object with values taken from a render array.
+   *
+   * @param array $build
+   *   A render array.
+   *
+   * @return static
+   */
+  public static function createFromRenderArray(array $build) {
+    $meta = new static();
+    $meta->contexts = (isset($build['#cache']['contexts'])) ? $build['#cache']['contexts'] : [];
+    $meta->tags = (isset($build['#cache']['tags'])) ? $build['#cache']['tags'] : [];
+    $meta->maxAge = (isset($build['#cache']['max-age'])) ? $build['#cache']['max-age'] : Cache::PERMANENT;
+    return $meta;
+  }
+
+  /**
+   * Creates a CacheableMetadata object from a depended object.
+   *
+   * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $object
+   *   The object whose cacheability metadata to retrieve.
+   *
+   * @return static
+   */
+  public static function createFromObject($object) {
+    if ($object instanceof CacheableDependencyInterface) {
+      $meta = new static();
+      $meta->contexts = $object->getCacheContexts();
+      $meta->tags = $object->getCacheTags();
+      $meta->maxAge = $object->getCacheMaxAge();
+      return $meta;
+    }
+
+    // Objects that don't implement CacheableDependencyInterface must be assumed
+    // to be uncacheable, so set max-age 0.
+    $meta = new static();
+    $meta->maxAge = 0;
+    return $meta;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
index 351d51c..b9a339f 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
@@ -7,12 +7,12 @@
 
 namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
 
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 use Drupal\Core\Field\FormatterBase;
-use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\TypedData\TranslatableInterface;
 
 /**
@@ -58,7 +58,7 @@ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $item
 
         $access = $this->checkAccess($entity);
         // Add the access result's cacheability, ::view() needs it.
-        $item->_accessCacheability = BubbleableMetadata::createFromObject($access);
+        $item->_accessCacheability = CacheableMetadata::createFromObject($access);
         if ($access->isAllowed()) {
           // Add the referring item, in case the formatter needs it.
           $entity->_referringItem = $items[$delta];
@@ -79,7 +79,7 @@ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $item
   public function view(FieldItemListInterface $items) {
     $elements = parent::view($items);
 
-    $field_level_access_cacheability = new BubbleableMetadata();
+    $field_level_access_cacheability = new CacheableMetadata();
 
     // Try to map the cacheability of the access result that was set at
     // _accessCacheability in getEntitiesToView() to the corresponding render
@@ -90,7 +90,7 @@ public function view(FieldItemListInterface $items) {
       // prepareView().
       if (!empty($item->_accessCacheability)) {
         if (isset($elements[$delta])) {
-          BubbleableMetadata::createFromRenderArray($elements[$delta])
+          CacheableMetadata::createFromRenderArray($elements[$delta])
             ->merge($item->_accessCacheability)
             ->applyTo($elements[$delta]);
         }
diff --git a/core/lib/Drupal/Core/Render/BubbleableMetadata.php b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
index b51c055..b231d0f 100644
--- a/core/lib/Drupal/Core/Render/BubbleableMetadata.php
+++ b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
@@ -20,27 +20,6 @@
 class BubbleableMetadata extends CacheableMetadata {
 
   /**
-   * Cache contexts.
-   *
-   * @var string[]
-   */
-  protected $contexts = [];
-
-  /**
-   * Cache tags.
-   *
-   * @var string[]
-   */
-  protected $tags = [];
-
-  /**
-   * Cache max-age.
-   *
-   * @var int
-   */
-  protected $maxAge = Cache::PERMANENT;
-
-  /**
    * Attached assets.
    *
    * @var string[][]
@@ -57,7 +36,7 @@ class BubbleableMetadata extends CacheableMetadata {
   /**
    * Merges the values of another bubbleable metadata object with this one.
    *
-   * @param \Drupal\Core\Render\BubbleableMetadata $other
+   * @param \Drupal\Core\Render\CacheableMetadata $other
    *   The other bubbleable metadata object.
    * @return static
    *   A new bubbleable metadata object, with the merged data.
@@ -67,11 +46,8 @@ class BubbleableMetadata extends CacheableMetadata {
    *       drupal_merge_attached() no longer is a procedural function and remove
    *       the '@codeCoverageIgnore' annotation.
    */
-  public function merge(BubbleableMetadata $other) {
-    $result = new BubbleableMetadata();
-    $result->contexts = Cache::mergeContexts($this->contexts, $other->contexts);
-    $result->tags = Cache::mergeTags($this->tags, $other->tags);
-    $result->maxAge = Cache::mergeMaxAges($this->maxAge, $other->maxAge);
+  public function merge(CacheableMetadata $other) {
+    $result = parent::merge($other);
     $result->attached = \Drupal::service('renderer')->mergeAttachments($this->attached, $other->attached);
     $result->postRenderCache = NestedArray::mergeDeep($this->postRenderCache, $other->postRenderCache);
     return $result;
@@ -84,9 +60,7 @@ public function merge(BubbleableMetadata $other) {
    *   A render array.
    */
   public function applyTo(array &$build) {
-    $build['#cache']['contexts'] = $this->contexts;
-    $build['#cache']['tags'] = $this->tags;
-    $build['#cache']['max-age'] = $this->maxAge;
+    parent::applyTo($build);
     $build['#attached'] = $this->attached;
     $build['#post_render_cache'] = $this->postRenderCache;
   }
@@ -100,40 +74,13 @@ public function applyTo(array &$build) {
    * @return static
    */
   public static function createFromRenderArray(array $build) {
-    $meta = new static();
-    $meta->contexts = (isset($build['#cache']['contexts'])) ? $build['#cache']['contexts'] : [];
-    $meta->tags = (isset($build['#cache']['tags'])) ? $build['#cache']['tags'] : [];
-    $meta->maxAge = (isset($build['#cache']['max-age'])) ? $build['#cache']['max-age'] : Cache::PERMANENT;
+    $meta = parent::createFromRenderArray($build);
     $meta->attached = (isset($build['#attached'])) ? $build['#attached'] : [];
     $meta->postRenderCache = (isset($build['#post_render_cache'])) ? $build['#post_render_cache'] : [];
     return $meta;
   }
 
   /**
-   * Creates a bubbleable metadata object from a depended object.
-   *
-   * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $object
-   *   The object whose cacheability metadata to retrieve.
-   *
-   * @return static
-   */
-  public static function createFromObject($object) {
-    if ($object instanceof CacheableDependencyInterface) {
-      $meta = new static();
-      $meta->contexts = $object->getCacheContexts();
-      $meta->tags = $object->getCacheTags();
-      $meta->maxAge = $object->getCacheMaxAge();
-      return $meta;
-    }
-
-    // Objects that don't implement CacheableDependencyInterface must be assumed
-    // to be uncacheable, so set max-age 0.
-    $meta = new static();
-    $meta->maxAge = 0;
-    return $meta;
-  }
-
-  /**
    * Gets assets.
    *
    * @return array
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 1fd14ee..44c79e8 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -11,6 +11,7 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Cache\CacheContextsManager;
 use Drupal\Core\Cache\CacheFactoryInterface;
 use Drupal\Core\Controller\ControllerResolverInterface;
@@ -800,8 +801,8 @@ public function mergeBubbleableMetadata(array $a, array $b) {
    * {@inheritdoc}
    */
   public function addDependency(array &$elements, $dependency) {
-    $meta_a = BubbleableMetadata::createFromRenderArray($elements);
-    $meta_b = BubbleableMetadata::createFromObject($dependency);
+    $meta_a = CacheableMetadata::createFromRenderArray($elements);
+    $meta_b = CacheableMetadata::createFromObject($dependency);
     $meta_a->merge($meta_b)->applyTo($elements);
   }
 
diff --git a/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php b/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php
index 7bd22f7..548ae64 100644
--- a/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php
+++ b/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php
@@ -8,9 +8,9 @@
 namespace Drupal\field\Tests\EntityReference;
 
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\entity_reference\Tests\EntityReferenceTestTrait;
-use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\filter\Entity\FilterFormat;
 use Drupal\system\Tests\Entity\EntityUnitTestBase;
@@ -236,11 +236,9 @@ public function testLabelFormatter() {
         ],
         'tags' => $this->referencedEntity->getCacheTags(),
       ),
-      '#attached' => [],
-      '#post_render_cache' => [],
     );
     $this->assertEqual(drupal_render($build[0]), drupal_render($expected_item_1), sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
-    $this->assertEqual(BubbleableMetadata::createFromRenderArray($build[0]), BubbleableMetadata::createFromRenderArray($expected_item_1));
+    $this->assertEqual(CacheableMetadata::createFromRenderArray($build[0]), CacheableMetadata::createFromRenderArray($expected_item_1));
 
     // The second referenced entity is "autocreated", therefore not saved and
     // lacking any URL info.
@@ -253,8 +251,6 @@ public function testLabelFormatter() {
         'tags' => $this->unsavedReferencedEntity->getCacheTags(),
         'max-age' => Cache::PERMANENT,
       ),
-      '#attached' => [],
-      '#post_render_cache' => [],
     );
     $this->assertEqual($build[1], $expected_item_2, sprintf('The render array returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
 
diff --git a/core/modules/views/src/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php
index 439d835..5013b21 100644
--- a/core/modules/views/src/Plugin/views/field/Field.php
+++ b/core/modules/views/src/Plugin/views/field/Field.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\Xss as CoreXss;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
@@ -18,7 +19,6 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
-use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -742,8 +742,8 @@ public function getItems(ResultRow $values) {
       // Merge the cacheability metadata of the top-level render array into
       // each child because they will most likely be rendered individually.
       if (isset($render_array['#cache'])) {
-        BubbleableMetadata::createFromRenderArray($render_array)
-          ->merge(BubbleableMetadata::createFromRenderArray($items[$delta]['rendered']))
+        CacheableMetadata::createFromRenderArray($render_array)
+          ->merge(CacheableMetadata::createFromRenderArray($items[$delta]['rendered']))
           ->applyTo($items[$delta]['rendered']);
       }
       // Add the raw field items (for use in tokens).
diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php
index 7918d60..3a3856c 100644
--- a/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\Tests\Core\Cache;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Tests\Core\Render\TestCacheableDependency;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Core\Render\Element;
 
@@ -69,4 +71,76 @@ public function providerSetCacheMaxAge() {
      [8.0, TRUE]
    ];
   }
+
+  /**
+   * @covers ::createFromRenderArray
+   * @dataProvider providerTestCreateFromRenderArray
+   */
+  public function testCreateFromRenderArray(array $render_array, CacheableMetadata $expected) {
+    $this->assertEquals($expected, CacheableMetadata::createFromRenderArray($render_array));
+  }
+
+  /**
+   * Provides test data for createFromRenderArray().
+   *
+   * @return array
+   */
+  public function providerTestCreateFromRenderArray() {
+    $data = [];
+
+    $empty_metadata = new CacheableMetadata();
+    $nonempty_metadata = new CacheableMetadata();
+    $nonempty_metadata->setCacheContexts(['qux'])
+      ->setCacheTags(['foo:bar']);
+
+    $empty_render_array = [];
+    $nonempty_render_array = [
+      '#cache' => [
+        'contexts' => ['qux'],
+        'tags' => ['foo:bar'],
+        'max-age' => Cache::PERMANENT,
+      ],
+    ];
+
+    $data[] = [$empty_render_array, $empty_metadata];
+    $data[] = [$nonempty_render_array, $nonempty_metadata];
+
+    return $data;
+  }
+
+  /**
+   * @covers ::createFromObject
+   * @dataProvider providerTestCreateFromObject
+   */
+  public function testCreateFromObject($object, CacheableMetadata $expected) {
+    $this->assertEquals($expected, CacheableMetadata::createFromObject($object));
+  }
+
+  /**
+   * Provides test data for createFromObject().
+   *
+   * @return array
+   */
+  public function providerTestCreateFromObject() {
+    $data = [];
+
+    $empty_metadata = new CacheableMetadata();
+    $nonempty_metadata = new CacheableMetadata();
+    $nonempty_metadata->setCacheContexts(['qux'])
+      ->setCacheTags(['foo:bar'])
+      ->setCacheMaxAge(600);
+    $uncacheable_metadata = new CacheableMetadata();
+    $uncacheable_metadata->setCacheMaxAge(0);
+
+    $empty_cacheable_object = new TestCacheableDependency([], [], Cache::PERMANENT);
+    $nonempty_cacheable_object = new TestCacheableDependency(['qux'], ['foo:bar'], 600);
+    $uncacheable_object = new \stdClass();
+
+    $data[] = [$empty_cacheable_object, $empty_metadata];
+    $data[] = [$nonempty_cacheable_object, $nonempty_metadata];
+    $data[] = [$uncacheable_object, $uncacheable_metadata];
+
+    return $data;
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
index 721d992..4c25860 100644
--- a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
@@ -132,39 +132,4 @@ public function providerTestCreateFromRenderArray() {
     return $data;
   }
 
-  /**
-   * @covers ::createFromObject
-   * @dataProvider providerTestCreateFromObject
-   */
-  public function testCreateFromObject($object, BubbleableMetadata $expected) {
-    $this->assertEquals($expected, BubbleableMetadata::createFromObject($object));
-  }
-
-  /**
-   * Provides test data for createFromObject().
-   *
-   * @return array
-   */
-  public function providerTestCreateFromObject() {
-    $data = [];
-
-    $empty_metadata = new BubbleableMetadata();
-    $nonempty_metadata = new BubbleableMetadata();
-    $nonempty_metadata->setCacheContexts(['qux'])
-      ->setCacheTags(['foo:bar'])
-      ->setCacheMaxAge(600);
-    $uncacheable_metadata = new BubbleableMetadata();
-    $uncacheable_metadata->setCacheMaxAge(0);
-
-    $empty_cacheable_object = new TestCacheableDependency([], [], Cache::PERMANENT);
-    $nonempty_cacheable_object = new TestCacheableDependency(['qux'], ['foo:bar'], 600);
-    $uncacheable_object = new \stdClass();
-
-    $data[] = [$empty_cacheable_object, $empty_metadata];
-    $data[] = [$nonempty_cacheable_object, $nonempty_metadata];
-    $data[] = [$uncacheable_object, $uncacheable_metadata];
-
-    return $data;
-  }
-
 }
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 56d9db1..bbea4b9 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -643,8 +643,6 @@ public function providerTestAddDependency() {
             'tags' => [],
             'max-age' => Cache::PERMANENT,
           ],
-          '#attached' => [],
-          '#post_render_cache' => [],
         ],
       ],
       // Empty render array, some cacheability.
@@ -657,8 +655,6 @@ public function providerTestAddDependency() {
             'tags' => ['foo'],
             'max-age' => Cache::PERMANENT,
           ],
-          '#attached' => [],
-          '#post_render_cache' => [],
         ],
       ],
       // Cacheable render array, some cacheability.
@@ -677,8 +673,6 @@ public function providerTestAddDependency() {
             'tags' => ['bar', 'foo'],
             'max-age' => 600,
           ],
-          '#attached' => [],
-          '#post_render_cache' => [],
         ],
       ],
       // Cacheable render array, no cacheability.
@@ -697,8 +691,6 @@ public function providerTestAddDependency() {
             'tags' => ['bar'],
             'max-age' => 0,
           ],
-          '#attached' => [],
-          '#post_render_cache' => [],
         ],
       ],
     ];
