 core/lib/Drupal/Core/Access/AccessResult.php       |   9 +-
 core/lib/Drupal/Core/Cache/Cache.php               |  30 +++
 core/lib/Drupal/Core/Render/BubbleableMetadata.php | 212 ++++++++++++++++++---
 .../Core/Render/MainContent/HtmlRenderer.php       |  10 +-
 core/lib/Drupal/Core/Render/Renderer.php           |   6 +
 core/modules/block/src/BlockViewBuilder.php        |   2 +
 .../block/src/Tests/BlockViewBuilderTest.php       |   2 +-
 core/modules/filter/src/Element/ProcessedText.php  |   2 +-
 core/modules/filter/src/FilterProcessResult.php    | 212 ++-------------------
 .../Tests/Core/Render/BubbleableMetadataTest.php   |  15 +-
 .../Drupal/Tests/Core/Render/RendererTest.php      |   2 +
 11 files changed, 265 insertions(+), 237 deletions(-)

diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php
index d515933..db78d89 100644
--- a/core/lib/Drupal/Core/Access/AccessResult.php
+++ b/core/lib/Drupal/Core/Access/AccessResult.php
@@ -466,14 +466,7 @@ public function inheritCacheability(AccessResultInterface $other) {
       $this->setCacheable($other->isCacheable());
       $this->addCacheContexts($other->getCacheContexts());
       $this->addCacheTags($other->getCacheTags());
-      // Use the lowest max-age.
-      if ($this->getCacheMaxAge() === Cache::PERMANENT) {
-        // The other max-age is either lower or equal.
-        $this->setCacheMaxAge($other->getCacheMaxAge());
-      }
-      else {
-        $this->setCacheMaxAge(min($this->getCacheMaxAge(), $other->getCacheMaxAge()));
-      }
+      $this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge()));
     }
     // If any of the access results don't provide cacheability metadata, then
     // we cannot cache the combined access result, for we may not make
diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php
index 91cc4a8..ff24324 100644
--- a/core/lib/Drupal/Core/Cache/Cache.php
+++ b/core/lib/Drupal/Core/Cache/Cache.php
@@ -71,6 +71,36 @@ public static function mergeTags() {
   }
 
   /**
+   * Merges max-age values (expressed in seconds), finds the lowest max-age.
+   *
+   * Ensures infinite max-age (Cache::PERMANENT) is taken into account.
+   *
+   * @param int …
+   *   Max-age values.
+   *
+   * @return int
+   *   The minimum max-age value.
+   */
+  public static function mergeMaxAges() {
+    $max_ages = func_get_args();
+
+    // Filter out all max-age values set to cache permanently.
+    if (in_array(Cache::PERMANENT, $max_ages)) {
+      $max_ages = array_filter($max_ages, function ($max_age) {
+        return $max_age !== Cache::PERMANENT;
+      });
+
+      // If nothing is left, then all max-age values were set to cache
+      // permanently, and then that is the result.
+      if (empty($max_ages)) {
+        return Cache::PERMANENT;
+      }
+    }
+
+    return min($max_ages);
+  }
+
+  /**
    * Validates an array of cache tags.
    *
    * Can be called before using cache tags in operations, to ensure validity.
diff --git a/core/lib/Drupal/Core/Render/BubbleableMetadata.php b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
index fa2edf0..d822d82 100644
--- a/core/lib/Drupal/Core/Render/BubbleableMetadata.php
+++ b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
@@ -22,47 +22,35 @@ class BubbleableMetadata {
    *
    * @var string[]
    */
-  protected $contexts;
+  protected $contexts = [];
 
   /**
    * Cache tags.
    *
    * @var string[]
    */
-  protected $tags;
+  protected $tags = [];
 
   /**
-   * Attached assets.
+   * Cache max-age.
    *
-   * @var string[][]
+   * @var int
    */
-  protected $attached;
+  protected $maxAge = Cache::PERMANENT;
 
   /**
-   * #post_render_cache metadata.
+   * Attached assets.
    *
-   * @var array[]
+   * @var string[][]
    */
-  protected $postRenderCache;
+  protected $attached = [];
 
   /**
-   * Constructs a BubbleableMetadata value object.
+   * #post_render_cache metadata.
    *
-   * @param string[] $contexts
-   *   An array of cache contexts.
-   * @param string[] $tags
-   *   An array of cache tags.
-   * @param array $attached
-   *   An array of attached assets.
-   * @param array $post_render_cache
-   *   An array of #post_render_cache metadata.
+   * @var array[]
    */
-  public function __construct(array $contexts = [], array $tags = [], array $attached = [], array $post_render_cache = []) {
-    $this->contexts = $contexts;
-    $this->tags = $tags;
-    $this->attached = $attached;
-    $this->postRenderCache = $post_render_cache;
-  }
+  protected $postRenderCache = [];
 
   /**
    * Merges the values of another bubbleable metadata object with this one.
@@ -81,6 +69,7 @@ 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);
     $result->attached = Renderer::mergeAttachments($this->attached, $other->attached);
     $result->postRenderCache = NestedArray::mergeDeep($this->postRenderCache, $other->postRenderCache);
     return $result;
@@ -95,6 +84,7 @@ public function merge(BubbleableMetadata $other) {
   public function applyTo(array &$build) {
     $build['#cache']['contexts'] = $this->contexts;
     $build['#cache']['tags'] = $this->tags;
+    $build['#cache']['max-age'] = $this->maxAge;
     $build['#attached'] = $this->attached;
     $build['#post_render_cache'] = $this->postRenderCache;
   }
@@ -111,9 +101,185 @@ 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->attached = (isset($build['#attached'])) ? $build['#attached'] : [];
     $meta->postRenderCache = (isset($build['#post_render_cache'])) ? $build['#post_render_cache'] : [];
     return $meta;
   }
 
+  /**
+   * Gets cache tags.
+   *
+   * @return string[]
+   */
+  public function getCacheTags() {
+    return $this->tags;
+  }
+
+  /**
+   * Adds cache tags.
+   *
+   * @param string[] $cache_tags
+   *   The cache tags to be added.
+   *
+   * @return $this
+   */
+  public function addCacheTags(array $cache_tags) {
+    $this->tags = Cache::mergeTags($this->tags, $cache_tags);
+    return $this;
+  }
+
+  /**
+   * Sets cache tags.
+   *
+   * @param string[] $cache_tags
+   *   The cache tags to be associated.
+   *
+   * @return $this
+   */
+  public function setCacheTags(array $cache_tags) {
+    $this->tags = $cache_tags;
+    return $this;
+  }
+
+  /**
+   * Gets cache contexts.
+   *
+   * @return string[]
+   */
+  public function getCacheContexts() {
+    return $this->contexts;
+  }
+
+  /**
+   * Adds cache contexts.
+   *
+   * @param string[] $cache_contexts
+   *   The cache contexts to be added.
+   *
+   * @return $this
+   */
+  public function addCacheContexts(array $cache_contexts) {
+    $this->contexts = Cache::mergeContexts($this->contexts, $cache_contexts);
+    return $this;
+  }
+
+  /**
+   * Sets cache contexts.
+   *
+   * @param string[] $cache_contexts
+   *   The cache contexts to be associated.
+   *
+   * @return $this
+   */
+  public function setCacheContexts(array $cache_contexts) {
+    $this->contexts = $cache_contexts;
+    return $this;
+  }
+
+  /**
+   * Gets the maximum age (in seconds).
+   *
+   * @return int
+   */
+  public function getCacheMaxAge() {
+    return $this->maxAge;
+  }
+
+  /**
+   * Sets the maximum age (in seconds).
+   *
+   * Defaults to Cache::PERMANENT
+   *
+   * @param int $max_age
+   *   The max age to associate.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function setCacheMaxAge($max_age) {
+    if (!is_int($max_age)) {
+      throw new \InvalidArgumentException('$max_age must be an integer');
+    }
+
+    $this->maxAge = $max_age;
+    return $this;
+  }
+
+  /**
+   * Gets assets.
+   *
+   * @return array
+   */
+  public function getAssets() {
+    return $this->attached;
+  }
+
+  /**
+   * Adds assets.
+   *
+   * @param array $assets
+   *   The associated assets to be attached.
+   *
+   * @return $this
+   */
+  public function addAssets(array $assets) {
+    $this->attached = NestedArray::mergeDeep($this->attached, $assets);
+    return $this;
+  }
+
+  /**
+   * Sets assets.
+   *
+   * @param array $assets
+   *   The associated assets to be attached.
+   *
+   * @return $this
+   */
+  public function setAssets(array $assets) {
+    $this->attached = $assets;
+    return $this;
+  }
+
+  /**
+   * Gets #post_render_cache callbacks.
+   *
+   * @return array
+   */
+  public function getPostRenderCacheCallbacks() {
+    return $this->postRenderCache;
+  }
+
+  /**
+   * Adds #post_render_cache callbacks.
+   *
+   * @param string $callback
+   *   The #post_render_cache callback that will replace the placeholder with
+   *   its eventual markup.
+   * @param array $context
+   *   An array providing context for the #post_render_cache callback.
+   *
+   * @see \Drupal\Core\Render\RendererInterface::generateCachePlaceholder()
+   *
+   * @return $this
+   */
+  public function addPostRenderCacheCallback($callback, array $context) {
+    $this->postRenderCache[$callback][] = $context;
+    return $this;
+  }
+
+  /**
+   * Sets #post_render_cache callbacks.
+   *
+   * @param array $post_render_cache_callbacks
+   *   The associated #post_render_cache callbacks to be executed.
+   *
+   * @return $this
+   */
+  public function setPostRenderCacheCallbacks(array $post_render_cache_callbacks) {
+    $this->postRenderCache = $post_render_cache_callbacks;
+    return $this;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
index ee2a42e..5378d7c 100644
--- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -135,21 +135,29 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
     // entire render cache, regardless of the cache bin.
     $cache_contexts = [];
     $cache_tags = ['rendered'];
+    $cache_max_age = Cache::PERMANENT;
     foreach (['page_top', 'page', 'page_bottom'] as $region) {
       if (isset($html[$region])) {
         $cache_contexts = Cache::mergeContexts($cache_contexts, $html[$region]['#cache']['contexts']);
         $cache_tags = Cache::mergeTags($cache_tags, $html[$region]['#cache']['tags']);
+        $cache_max_age = Cache::mergeMaxAges($cache_max_age, $html[$region]['#cache']['max-age']);
       }
     }
 
     // Set the generator in the HTTP header.
     list($version) = explode('.', \Drupal::VERSION, 2);
 
-    return new Response($content, 200,[
+    $response = new Response($content, 200,[
       'X-Drupal-Cache-Tags' => implode(' ', $cache_tags),
       'X-Drupal-Cache-Contexts' => implode(' ', $cache_contexts),
       'X-Generator' => 'Drupal ' . $version . ' (http://drupal.org)'
     ]);
+    // If an explicit non-infinite max-age is specified by a part of the page,
+    // respect that by applying it to the response's headers.
+    if ($cache_max_age !== Cache::PERMANENT) {
+      $response->setMaxAge($cache_max_age);
+    }
+    return $response;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 189c2de..a3f72a8 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -218,6 +218,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     // Defaults for bubbleable rendering metadata.
     $elements['#cache']['contexts'] = isset($elements['#cache']['contexts']) ? $elements['#cache']['contexts'] : array();
     $elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
+    $elements['#cache']['max-age'] = isset($elements['#cache']['max-age']) ? $elements['#cache']['max-age'] : Cache::PERMANENT;
     $elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array();
     $elements['#post_render_cache'] = isset($elements['#post_render_cache']) ? $elements['#post_render_cache'] : array();
 
@@ -728,6 +729,11 @@ protected function cacheSet(array &$elements, $pre_bubbling_cid) {
    *   The cache ID string, or FALSE if the element may not be cached.
    */
   protected function createCacheID(array $elements) {
+    // If the maximum age is zero, then caching is effectively prohibited.
+    if (isset($elements['#cache']['max-age']) && $elements['#cache']['max-age'] === 0) {
+      return FALSE;
+    }
+
     if (isset($elements['#cache']['cid'])) {
       return $elements['#cache']['cid'];
     }
diff --git a/core/modules/block/src/BlockViewBuilder.php b/core/modules/block/src/BlockViewBuilder.php
index e56b24d..8430393 100644
--- a/core/modules/block/src/BlockViewBuilder.php
+++ b/core/modules/block/src/BlockViewBuilder.php
@@ -76,6 +76,8 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
         $plugin->getCacheTags() // Block plugin cache tags.
       );
 
+      $build[$entity_id]['#cache']['max-age'] = $plugin->getCacheMaxAge();
+
       if ($plugin->isCacheable()) {
         $build[$entity_id]['#pre_render'][] = array($this, 'buildBlock');
         // Generic cache keys, with the block plugin's custom keys appended.
diff --git a/core/modules/block/src/Tests/BlockViewBuilderTest.php b/core/modules/block/src/Tests/BlockViewBuilderTest.php
index 506ce51..d4f99f3 100644
--- a/core/modules/block/src/Tests/BlockViewBuilderTest.php
+++ b/core/modules/block/src/Tests/BlockViewBuilderTest.php
@@ -151,7 +151,7 @@ protected function verifyRenderCacheHandling() {
 
     // Test that entities with caching disabled do not generate a cache entry.
     $build = $this->getBlockRenderArray();
-    $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'The render array element of uncacheable blocks is not cached, but does have cache tags set.');
+    $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags', 'max-age'), 'The render array element of uncacheable blocks is not cached, but does have cache tags & max-age set.');
 
     // Enable block caching.
     $this->setBlockCacheConfig(array(
diff --git a/core/modules/filter/src/Element/ProcessedText.php b/core/modules/filter/src/Element/ProcessedText.php
index 52f0649..d007b5f 100644
--- a/core/modules/filter/src/Element/ProcessedText.php
+++ b/core/modules/filter/src/Element/ProcessedText.php
@@ -113,7 +113,7 @@ public static function preRenderText($element) {
     foreach ($filters as $filter) {
       if ($filter_must_be_applied($filter)) {
         $result = $filter->process($text, $langcode);
-        $metadata = $metadata->merge($result->getBubbleableMetadata());
+        $metadata = $metadata->merge($result);
         $text = $result->getProcessedText();
       }
     }
diff --git a/core/modules/filter/src/FilterProcessResult.php b/core/modules/filter/src/FilterProcessResult.php
index e8487d1..67e49fa 100644
--- a/core/modules/filter/src/FilterProcessResult.php
+++ b/core/modules/filter/src/FilterProcessResult.php
@@ -21,7 +21,10 @@
  * 2. declare cache tags that the filtered text depends upon, so when either of
  *   those cache tags is invalidated, the filtered text should also be
  *   invalidated;
- * 3. apply uncacheable filtering, for example because it differs per user.
+ * 3. declare cache context to vary by, e.g. 'language' to do language-specific
+ *    filtering.
+ * 4. declare a maximum age for the filtered text
+ * 5. apply uncacheable filtering, for example because it differs per user.
  *
  * In case a filter needs one or more of these advanced use cases, it can use
  * the additional methods available.
@@ -49,14 +52,20 @@
  *     ),
  *   ));
  *
+ *   // Associate cache contexts to vary by.
+ *   $result->setCacheContexts(['language']);
+ *
  *   // Associate cache tags to be invalidated by.
  *   $result->setCacheTags($node->getCacheTags());
  *
+ *   // Associate a maximum age.
+ *   $result->setCacheMaxAge(300); // 5 minutes.
+ *
  *   return $result;
  * }
  * @endcode
  */
-class FilterProcessResult {
+class FilterProcessResult extends BubbleableMetadata {
 
   /**
    * The processed text.
@@ -68,40 +77,6 @@ class FilterProcessResult {
   protected $processedText;
 
   /**
-   * An array of associated assets to be attached.
-   *
-   * @see drupal_process_attached()
-   *
-   * @var array
-   */
-  protected $assets;
-
-  /**
-   * The attached cache tags.
-   *
-   * @see drupal_render_collect_cache_tags()
-   *
-   * @var array
-   */
-  protected $cacheTags;
-
-  /**
-   * The associated cache contexts.
-   *
-   * @var string[]
-   */
-  protected $cacheContexts;
-
-  /**
-   * The associated #post_render_cache callbacks.
-   *
-   * @see _drupal_render_process_post_render_cache()
-   *
-   * @var array
-   */
-  protected $postRenderCacheCallbacks;
-
-  /**
    * Constructs a FilterProcessResult object.
    *
    * @param string $processed_text
@@ -109,11 +84,6 @@ class FilterProcessResult {
    */
   public function __construct($processed_text) {
     $this->processedText = $processed_text;
-
-    $this->assets = array();
-    $this->cacheTags = array();
-    $this->cacheContexts = array();
-    $this->postRenderCacheCallbacks = array();
   }
 
   /**
@@ -146,164 +116,4 @@ public function setProcessedText($processed_text) {
     $this->processedText = $processed_text;
     return $this;
   }
-
-  /**
-   * Gets cache tags associated with the processed text.
-   *
-   * @return array
-   */
-  public function getCacheTags() {
-    return $this->cacheTags;
-  }
-
-  /**
-   * Adds cache tags associated with the processed text.
-   *
-   * @param array $cache_tags
-   *   The cache tags to be added.
-   *
-   * @return $this
-   */
-  public function addCacheTags(array $cache_tags) {
-    $this->cacheTags = Cache::mergeTags($this->cacheTags, $cache_tags);
-    return $this;
-  }
-
-  /**
-   * Sets cache tags associated with the processed text.
-   *
-   * @param array $cache_tags
-   *   The cache tags to be associated.
-   *
-   * @return $this
-   */
-  public function setCacheTags(array $cache_tags) {
-    $this->cacheTags = $cache_tags;
-    return $this;
-  }
-
-  /**
-   * Gets cache contexts associated with the processed text.
-   *
-   * @return string[]
-   */
-  public function getCacheContexts() {
-    return $this->cacheContexts;
-  }
-
-  /**
-   * Adds cache contexts associated with the processed text.
-   *
-   * @param string[] $cache_contexts
-   *   The cache contexts to be added.
-   *
-   * @return $this
-   */
-  public function addCacheContexts(array $cache_contexts) {
-    $this->cacheContexts = Cache::mergeContexts($this->cacheContexts, $cache_contexts);
-    return $this;
-  }
-
-  /**
-   * Sets cache contexts associated with the processed text.
-   *
-   * @param string[] $cache_contexts
-   *   The cache contexts to be associated.
-   *
-   * @return $this
-   */
-  public function setCacheContexts(array $cache_contexts) {
-    $this->cacheContexts = $cache_contexts;
-    return $this;
-  }
-
-  /**
-   * Gets assets associated with the processed text.
-   *
-   * @return array
-   */
-  public function getAssets() {
-    return $this->assets;
-  }
-
-  /**
-   * Adds assets associated with the processed text.
-   *
-   * @param array $assets
-   *   The associated assets to be attached.
-   *
-   * @return $this
-   */
-  public function addAssets(array $assets) {
-    $this->assets = NestedArray::mergeDeep($this->assets, $assets);
-    return $this;
-  }
-
-  /**
-   * Sets assets associated with the processed text.
-   *
-   * @param array $assets
-   *   The associated assets to be attached.
-   *
-   * @return $this
-   */
-  public function setAssets(array $assets) {
-    $this->assets = $assets;
-    return $this;
-  }
-
-  /**
-   * Gets #post_render_cache callbacks associated with the processed text.
-   *
-   * @return array
-   */
-  public function getPostRenderCacheCallbacks() {
-    return $this->postRenderCacheCallbacks;
-  }
-
-  /**
-   * Adds #post_render_cache callbacks associated with the processed text.
-   *
-   * @param string $callback
-   *   The #post_render_cache callback that will replace the placeholder with
-   *   its eventual markup.
-   * @param array $context
-   *   An array providing context for the #post_render_cache callback.
-   *
-   * @see drupal_render_cache_generate_placeholder()
-   *
-   * @return $this
-   */
-  public function addPostRenderCacheCallback($callback, array $context) {
-    $this->postRenderCacheCallbacks[$callback][] = $context;
-    return $this;
-  }
-
-  /**
-   * Sets #post_render_cache callbacks associated with the processed text.
-   *
-   * @param array $post_render_cache_callbacks
-   *   The associated #post_render_cache callbacks to be executed.
-   *
-   * @return $this
-   */
-  public function setPostRenderCacheCallbacks(array $post_render_cache_callbacks) {
-    $this->postRenderCacheCallbacks = $post_render_cache_callbacks;
-    return $this;
-  }
-
-  /**
-   * Returns the attached asset libraries, etc. as a bubbleable metadata object.
-   *
-   * @return \Drupal\Core\Render\BubbleableMetadata
-   */
-  public function getBubbleableMetadata() {
-    return new BubbleableMetadata(
-      $this->getCacheContexts(),
-      $this->getCacheTags(),
-      $this->getAssets(),
-      $this->getPostRenderCacheCallbacks()
-    );
-  }
-
 }
diff --git a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
index ea336da..f6c4cfb 100644
--- a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Render;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Core\Render\Element;
@@ -35,13 +36,17 @@ public function providerTestApplyTo() {
     $data = [];
 
     $empty_metadata = new BubbleableMetadata();
-    $nonempty_metadata = new BubbleableMetadata(['qux'], ['foo:bar'], ['settings' => ['foo' => 'bar']]);
+    $nonempty_metadata = new BubbleableMetadata();
+    $nonempty_metadata->setCacheContexts(['qux'])
+      ->setCacheTags(['foo:bar'])
+      ->setAssets(['settings' => ['foo' => 'bar']]);
 
     $empty_render_array = [];
     $nonempty_render_array = [
       '#cache' => [
         'contexts' => ['qux'],
         'tags' => ['llamas:are:awesome:but:kittens:too'],
+        'max-age' => Cache::PERMANENT,
       ],
       '#attached' => [
         'library' => [
@@ -56,6 +61,7 @@ public function providerTestApplyTo() {
       '#cache' => [
         'contexts' => [],
         'tags' => [],
+        'max-age' => Cache::PERMANENT,
       ],
       '#attached' => [],
       '#post_render_cache' => [],
@@ -66,6 +72,7 @@ public function providerTestApplyTo() {
       '#cache' => [
         'contexts' => ['qux'],
         'tags' => ['foo:bar'],
+        'max-age' => Cache::PERMANENT,
       ],
       '#attached' => [
         'settings' => [
@@ -97,13 +104,17 @@ public function providerTestCreateFromRenderArray() {
     $data = [];
 
     $empty_metadata = new BubbleableMetadata();
-    $nonempty_metadata = new BubbleableMetadata(['qux'], ['foo:bar'], ['settings' => ['foo' => 'bar']]);
+    $nonempty_metadata = new BubbleableMetadata();
+    $nonempty_metadata->setCacheContexts(['qux'])
+      ->setCacheTags(['foo:bar'])
+      ->setAssets(['settings' => ['foo' => 'bar']]);
 
     $empty_render_array = [];
     $nonempty_render_array = [
       '#cache' => [
         'contexts' => ['qux'],
         'tags' => ['foo:bar'],
+        'max-age' => Cache::PERMANENT,
       ],
       '#attached' => [
         'settings' => [
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 455834e..d705ede 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Render;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Template\Attribute;
 
@@ -20,6 +21,7 @@ class RendererTest extends RendererTestBase {
     '#cache' => [
       'contexts' => [],
       'tags' => [],
+      'max-age' => Cache::PERMANENT,
     ],
     '#attached' => [],
     '#post_render_cache' => [],
