diff --git a/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php b/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php
index 01e6ce8..f6a412a 100644
--- a/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Session\PermissionsHashGeneratorInterface;
 
@@ -51,4 +52,17 @@ public function getContext() {
     return 'ph.' . $this->permissionsHashGenerator->generate($this->user);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    $cacheable_metadata = new CacheableMetadata();
+    $tags = [];
+    foreach ($this->user->getRoles() as $rid) {
+      $tags[] = "config:user.role.$rid";
+    }
+
+    return $cacheable_metadata->setCacheTags($tags);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextInterface.php b/core/lib/Drupal/Core/Cache/Context/CacheContextInterface.php
index ada3174..0365661 100644
--- a/core/lib/Drupal/Core/Cache/Context/CacheContextInterface.php
+++ b/core/lib/Drupal/Core/Cache/Context/CacheContextInterface.php
@@ -31,4 +31,15 @@ public static function getLabel();
    */
   public function getContext();
 
+  /**
+   * Gets the cacheable metadata for the context based on the parameter value.
+   *
+   * If the cache context is being optimized away, cacheable metadata provided
+   * by this method will be bubbled up.
+   *
+   * @return \Drupal\Core\Cache\CacheableMetadata|NULL
+   *   A cacheable metadata object or NULL.
+   */
+  public function getCacheableMetadata();
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextKeys.php b/core/lib/Drupal/Core/Cache/Context/CacheContextKeys.php
new file mode 100644
index 0000000..ed3dd1c
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/Context/CacheContextKeys.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\Context\CacheContextKeys.
+ */
+
+namespace Drupal\Core\Cache\Context;
+
+use Drupal\Core\Cache\CacheableMetadata;
+
+/**
+ * A value object to store cache context keys with its cacheable metadata.
+ */
+class CacheContextKeys {
+
+  /**
+   * The cache context keys.
+   *
+   * @var string[]
+   */
+  protected $keys;
+
+  /**
+   * The cacheable metadata associated for the optimized cache contexts.
+   *
+   * @var \Drupal\Core\Cache\CacheableMetadata
+   */
+  protected $cacheableMetadata;
+
+  /**
+   * Constructs cache context keys value object.
+   *
+   * @param string[] $keys
+   *   The cache context keys.
+   * @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
+   *   The cacheable metadata associated for the optimized cache contexts.
+   */
+  public function __construct(array $keys, CacheableMetadata $cacheable_metadata) {
+    $this->keys = $keys;
+    $this->cacheableMetadata = $cacheable_metadata;
+  }
+
+  /**
+   * Gets the cache context keys.
+   *
+   * @return string[]
+   */
+  public function getKeys() {
+    return $this->keys;
+  }
+
+  /**
+   * Gets the cacheable metadata associated for the optimized cache contexts.
+   *
+   * @return \Drupal\Core\Cache\CacheableMetadata
+   */
+  public function getCacheableMetadata() {
+    return $this->cacheableMetadata;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php
index 27b012a..4614fb6 100644
--- a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php
+++ b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Cache\Context;
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Cache\CacheableMetadata;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -99,23 +100,40 @@ public function getLabels($include_calculated_cache_contexts = FALSE) {
    * @param string[] $context_tokens
    *   An array of cache context tokens.
    *
-   * @return string[]
-   *   The array of corresponding cache keys.
+   * @return \Drupal\Core\Cache\Context\CacheContextKeys
    *
    * @throws \InvalidArgumentException
    */
   public function convertTokensToKeys(array $context_tokens) {
-    $context_tokens = $this->optimizeTokens($context_tokens);
-    sort($context_tokens);
+    $cacheable_metadata = new CacheableMetadata();
+    $optimized_tokens = $this->optimizeTokens($context_tokens);
+    // Iterate through cache contexts that has been optimized away and get their
+    // cacheable metadata.
+    if ($cacheable_metadata) {
+      foreach (static::parseTokens(array_diff($context_tokens, $optimized_tokens)) as $context_token) {
+        list($context_id, $parameter) = $context_token;
+        if (!in_array($context_id, $this->contexts)) {
+          throw new \InvalidArgumentException(SafeMarkup::format('"@context" is not a valid cache context ID.', ['@context' => $context_id]));
+        }
+        $context = $this->getService($context_id);
+        // To make it possible to return NULL instead of CacheableMetadata.
+        if ($metadata = $context->getCacheableMetadata($parameter)) {
+          $cacheable_metadata = $cacheable_metadata->merge($metadata);
+        }
+      }
+    }
+
+    sort($optimized_tokens);
     $keys = [];
-    foreach (static::parseTokens($context_tokens) as $context) {
+    foreach (static::parseTokens($optimized_tokens) as $context) {
       list($context_id, $parameter) = $context;
       if (!in_array($context_id, $this->contexts)) {
         throw new \InvalidArgumentException(SafeMarkup::format('"@context" is not a valid cache context ID.', ['@context' => $context_id]));
       }
       $keys[] = $this->getService($context_id)->getContext($parameter);
     }
-    return $keys;
+
+    return new CacheContextKeys($keys, $cacheable_metadata);
   }
 
   /**
@@ -129,6 +147,9 @@ public function convertTokensToKeys(array $context_tokens) {
    * possible of a set of cache context tokens, that still captures the entire
    * universe of variations.
    *
+   * If cache context is being optimized away, it is able to set cacheable
+   * metadata for itself which will be bubbled up.
+   *
    * E.g. when caching per user ('user'), also caching per role ('user.roles')
    * is meaningless because "per role" is implied by "per user".
    *
diff --git a/core/lib/Drupal/Core/Cache/Context/CalculatedCacheContextInterface.php b/core/lib/Drupal/Core/Cache/Context/CalculatedCacheContextInterface.php
index bb2f291..fcf3714 100644
--- a/core/lib/Drupal/Core/Cache/Context/CalculatedCacheContextInterface.php
+++ b/core/lib/Drupal/Core/Cache/Context/CalculatedCacheContextInterface.php
@@ -37,4 +37,18 @@ public static function getLabel();
    */
   public function getContext($parameter = NULL);
 
+  /**
+   * Gets the cacheable metadata for the context based on the parameter value.
+   *
+   * If the cache context is being optimized away, cacheable metadata provided
+   * by this method will be bubbled up.
+   *
+   * @param mixed $parameter
+   *   The parameter to get context values for.
+
+   * @return \Drupal\Core\Cache\CacheableMetadata|NULL
+   *   A cacheable metadata object or NULL.
+   */
+  public function getCacheableMetadata($parameter = NULL);
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/CookiesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/CookiesCacheContext.php
index 534d10c..961209d 100644
--- a/core/lib/Drupal/Core/Cache/Context/CookiesCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/CookiesCacheContext.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
+
 /**
  * Defines the CookiesCacheContext service, for "per cookie" caching.
  *
@@ -35,4 +37,11 @@ public function getContext($cookie = NULL) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php b/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php
index 8286b36..7cb14f2 100644
--- a/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php
@@ -35,4 +35,11 @@ public function getContext($header = NULL) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata($header = NULL) {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/IpCacheContext.php b/core/lib/Drupal/Core/Cache/Context/IpCacheContext.php
index 9cc6713..a006153 100644
--- a/core/lib/Drupal/Core/Cache/Context/IpCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/IpCacheContext.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
+
 /**
  * Defines the IpCacheContext service, for "per IP address" caching.
  *
@@ -28,4 +30,11 @@ public function getContext() {
     return $this->requestStack->getCurrentRequest()->getClientIp();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/IsSuperUserCacheContext.php b/core/lib/Drupal/Core/Cache/Context/IsSuperUserCacheContext.php
index 1f30cf1..0288e11 100644
--- a/core/lib/Drupal/Core/Cache/Context/IsSuperUserCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/IsSuperUserCacheContext.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
+
 /**
  * Defines the IsSuperUserCacheContext service, for "super user or not" caching.
  *
@@ -28,4 +30,11 @@ public function getContext() {
     return ((int) $this->user->id()) === 1 ? '1' : '0';
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php
index 2bf9a2f..db46ddb 100644
--- a/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Language\LanguageManagerInterface;
 
 /**
@@ -74,4 +75,11 @@ public function getContext($type = NULL) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata($type = NULL) {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/MenuActiveTrailsCacheContext.php b/core/lib/Drupal/Core/Cache/Context/MenuActiveTrailsCacheContext.php
index 40cc077..b69e705 100644
--- a/core/lib/Drupal/Core/Cache/Context/MenuActiveTrailsCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/MenuActiveTrailsCacheContext.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
 use Symfony\Component\DependencyInjection\ContainerAware;
 
 /**
@@ -33,4 +34,11 @@ public function getContext($menu_name = NULL) {
     return 'menu_trail.' . $menu_name . '|' . implode('|', $active_trail);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata($menu_name = NULL) {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php b/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php
index f53d464..ef6b394 100644
--- a/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
+
 /**
  * Defines a cache context for "per page in a pager" caching.
  *
@@ -38,4 +40,11 @@ public function getContext($pager_id = NULL) {
     return 'pager.' . $pager_id . '.' . pager_find_page($pager_id);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata($pager_id = NULL) {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php b/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php
index faceb07..44cd1b9 100644
--- a/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
+
 /**
  * Defines the QueryArgsCacheContext service, for "per query args" caching.
  *
@@ -35,4 +37,11 @@ public function getContext($query_arg = NULL) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata($query_arg = NULL) {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/RequestStackCacheContextBase.php b/core/lib/Drupal/Core/Cache/Context/RequestStackCacheContextBase.php
index 2a8f22d..14c3787 100644
--- a/core/lib/Drupal/Core/Cache/Context/RequestStackCacheContextBase.php
+++ b/core/lib/Drupal/Core/Cache/Context/RequestStackCacheContextBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
 use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
@@ -31,4 +32,11 @@ public function __construct(RequestStack $request_stack) {
     $this->requestStack = $request_stack;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/RouteCacheContext.php b/core/lib/Drupal/Core/Cache/Context/RouteCacheContext.php
index bb4dd8d..70a20bb 100644
--- a/core/lib/Drupal/Core/Cache/Context/RouteCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/RouteCacheContext.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
@@ -47,4 +48,11 @@ public function getContext() {
     return $this->routeMatch->getRouteName() . hash('sha256', serialize($this->routeMatch->getRawParameters()->all()));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;z
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/ThemeCacheContext.php b/core/lib/Drupal/Core/Cache/Context/ThemeCacheContext.php
index 0a66878..eccf859 100644
--- a/core/lib/Drupal/Core/Cache/Context/ThemeCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/ThemeCacheContext.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Cache\Context;
 
-use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Theme\ThemeManagerInterface;
 
 /**
@@ -48,4 +48,11 @@ public function getContext() {
     return $this->themeManager->getActiveTheme()->getName() ?: 'stark';
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php b/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php
index 9b22e0e..2ca91af 100644
--- a/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\CacheableMetadata;
+
 /**
  * Defines the TimeZoneCacheContext service, for "per time zone" caching.
  *
@@ -32,4 +34,11 @@ public function getContext() {
     return date_default_timezone_get();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/UserCacheContext.php b/core/lib/Drupal/Core/Cache/Context/UserCacheContext.php
index d7b8051..82f7c99 100644
--- a/core/lib/Drupal/Core/Cache/Context/UserCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/UserCacheContext.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Cache\Context;
 
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Session\AccountInterface;
 
 /**
@@ -40,4 +42,11 @@ public function getContext() {
     return "u." . $this->user->id();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php
index e4bb71a..9d96407 100644
--- a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php
@@ -6,6 +6,7 @@
  */
 
 namespace Drupal\Core\Cache\Context;
+use Drupal\Core\Cache\CacheableMetadata;
 
 /**
  * Defines the UserRolesCacheContext service, for "per role" caching.
@@ -17,7 +18,7 @@
  * Calculated cache context ID: 'user.roles:%role', e.g. 'user.roles:anonymous'
  * (to vary by the presence/absence of a specific role).
  */
-class UserRolesCacheContext extends UserCacheContext implements CalculatedCacheContextInterface{
+class UserRolesCacheContext extends UserCacheContext implements CalculatedCacheContextInterface {
 
   /**
    * {@inheritdoc}
@@ -45,4 +46,11 @@ public function getContext($role = NULL) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata($role = NULL) {
+    return NULL;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Render/RenderCache.php b/core/lib/Drupal/Core/Render/RenderCache.php
index 1a8339b..4a5c333 100644
--- a/core/lib/Drupal/Core/Render/RenderCache.php
+++ b/core/lib/Drupal/Core/Render/RenderCache.php
@@ -295,7 +295,7 @@ protected function maxAgeToExpire($max_age) {
    * @return string
    *   The cache ID string, or FALSE if the element may not be cached.
    */
-  protected function createCacheID(array $elements) {
+  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;
@@ -305,7 +305,10 @@ protected function createCacheID(array $elements) {
       $cid_parts = $elements['#cache']['keys'];
       if (!empty($elements['#cache']['contexts'])) {
         $contexts = $this->cacheContextsManager->convertTokensToKeys($elements['#cache']['contexts']);
-        $cid_parts = array_merge($cid_parts, $contexts);
+        $cid_parts = array_merge($cid_parts, $contexts->getKeys());
+        CacheableMetadata::createFromRenderArray($elements)
+          ->merge($contexts->getCacheableMetadata())
+          ->applyTo($elements);
       }
       return implode(':', $cid_parts);
     }
diff --git a/core/modules/book/src/Cache/BookNavigationCacheContext.php b/core/modules/book/src/Cache/BookNavigationCacheContext.php
index 04a09e8..439e0fb 100644
--- a/core/modules/book/src/Cache/BookNavigationCacheContext.php
+++ b/core/modules/book/src/Cache/BookNavigationCacheContext.php
@@ -70,4 +70,11 @@ public function getContext() {
     return 'book.' . implode('|', $active_trail);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php
index 8e7bf27..0d6a77c 100644
--- a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php
+++ b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\comment\Tests;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Session\UserSession;
 use Drupal\comment\CommentInterface;
 use Drupal\system\Tests\Entity\EntityUnitTestBase;
@@ -69,7 +70,8 @@ public function testCacheTags() {
       ->getViewBuilder('entity_test')
       ->view($commented_entity);
     $renderer->renderRoot($build);
-    $expected_cache_tags = array(
+    $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($build['#cache']->getKeys())->getCacheableMetadata()->getCacheTags();
+    $expected_cache_tags = Cache::mergeTags($cache_context_tags, [
       'entity_test_view',
       'entity_test:'  . $commented_entity->id(),
       'comment_list',
@@ -78,9 +80,9 @@ public function testCacheTags() {
       'config:field.field.entity_test.entity_test.comment',
       'config:field.storage.comment.comment_body',
       'config:user.settings',
-    );
+    ]);
     sort($expected_cache_tags);
-    $this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags before it has comments.');
+    $this->assertEqual($build['#cache']['tags'], $expected_cache_tags);
 
     // Create a comment on that entity. Comment loading requires that the uid
     // also exists in the {users} table.
@@ -111,7 +113,8 @@ public function testCacheTags() {
       ->getViewBuilder('entity_test')
       ->view($commented_entity);
     $renderer->renderRoot($build);
-    $expected_cache_tags = array(
+    $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($build['#cache']->getKeys())->getCacheableMetadata()->getCacheTags();
+    $expected_cache_tags = Cache::mergeTags($cache_context_tags, [
       'entity_test_view',
       'entity_test:' . $commented_entity->id(),
       'comment_list',
@@ -125,9 +128,9 @@ public function testCacheTags() {
       'config:field.field.entity_test.entity_test.comment',
       'config:field.storage.comment.comment_body',
       'config:user.settings',
-    );
+    ]);
     sort($expected_cache_tags);
-    $this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags when it has comments.');
+    $this->assertEqual($build['#cache']['tags'], $expected_cache_tags);
   }
 
 }
diff --git a/core/modules/comment/src/Tests/CommentRssTest.php b/core/modules/comment/src/Tests/CommentRssTest.php
index b82da48..aed6be8 100644
--- a/core/modules/comment/src/Tests/CommentRssTest.php
+++ b/core/modules/comment/src/Tests/CommentRssTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\comment\Tests;
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 
@@ -52,16 +53,22 @@ function testCommentRss() {
     $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName());
     $this->drupalGet('rss.xml');
 
-    $this->assertCacheTags([
-      'config:views.view.frontpage', 'node:1', 'node_list', 'node_view', 'user:3',
-    ]);
-    $this->assertCacheContexts([
+    $cache_contexts = [
       'languages:language_interface',
       'theme',
       'user.node_grants:view',
       'user.permissions',
       'timezone',
-    ]);
+    ];
+    $this->assertCacheContexts($cache_contexts);
+
+    $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheableMetadata()->getCacheTags();
+    $this->assertCacheTags(Cache::mergeTags($cache_context_tags, [
+      'config:views.view.frontpage',
+      'node:1', 'node_list',
+      'node_view',
+      'user:3',
+    ]));
 
     $raw = '<comments>' . $this->node->url('canonical', array('fragment' => 'comments', 'absolute' => TRUE)) . '</comments>';
     $this->assertRaw($raw, 'Comments as part of RSS feed.');
diff --git a/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
index 912309f..7232771 100644
--- a/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
+++ b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\node\Cache;
 
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Cache\Context\CalculatedCacheContextInterface;
 use Drupal\Core\Cache\Context\UserCacheContext;
 
@@ -82,4 +83,11 @@ protected function checkNodeGrants($operation) {
     return $operation . '.' . implode(';', $grants_context_parts);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata($operation = NULL) {
+    return new CacheableMetadata();
+  }
+
 }
diff --git a/core/modules/node/src/Tests/Views/FrontPageTest.php b/core/modules/node/src/Tests/Views/FrontPageTest.php
index b3ab9c9..5d36780 100644
--- a/core/modules/node/src/Tests/Views/FrontPageTest.php
+++ b/core/modules/node/src/Tests/Views/FrontPageTest.php
@@ -251,6 +251,8 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
       'url.query_args.pagers:0',
     ];
 
+    $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheableMetadata()->getCacheTags();
+
     // Test before there are any nodes.
     $empty_node_listing_cache_tags = [
       'config:views.view.frontpage',
@@ -260,12 +262,12 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
       $view,
       $empty_node_listing_cache_tags,
       $do_assert_views_caches,
-      $empty_node_listing_cache_tags
+      Cache::mergeTags($empty_node_listing_cache_tags, $cache_context_tags)
     );
     $this->assertPageCacheContextsAndTags(
       Url::fromRoute('view.frontpage.page_1'),
       $cache_contexts,
-      Cache::mergeTags($empty_node_listing_cache_tags, ['rendered', 'config:user.role.anonymous'])
+      Cache::mergeTags($empty_node_listing_cache_tags, $cache_context_tags, ['rendered', 'config:user.role.anonymous'])
     );
 
     // Create some nodes on the frontpage view. Add more than 10 nodes in order
@@ -307,12 +309,17 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
       'node:14',
       'node:15',
     ];
-    $first_page_output_cache_tags = Cache::mergeTags($first_page_result_cache_tags, [
-      'config:filter.format.plain_text',
-      'node_view',
-      'user_view',
-      'user:0',
-    ]);
+    $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheableMetadata()->getCacheTags();
+    $first_page_output_cache_tags = Cache::mergeTags(
+      $first_page_result_cache_tags,
+      $cache_context_tags,
+      [
+        'config:filter.format.plain_text',
+        'node_view',
+        'user_view',
+        'user:0',
+      ]
+    );
     $view->setDisplay('page_1');
     $view->setCurrentPage(0);
     $this->assertViewsCacheTags(
diff --git a/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php b/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php
new file mode 100644
index 0000000..9c29913
--- /dev/null
+++ b/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Cache\CacheContextOptimizationTest.
+ */
+
+namespace Drupal\system\Tests\Cache;
+
+use Drupal\simpletest\KernelTestBase;
+use Drupal\simpletest\UserCreationTrait;
+use Drupal\user\Entity\Role;
+
+/**
+ * Tests the cache context optimization.
+ *
+ * @group Render
+ */
+class CacheContextOptimizationTest extends KernelTestBase {
+
+  use UserCreationTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var string[]
+   */
+  public static $modules = ['user', 'system'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installEntitySchema('user');
+    $this->installConfig(['user']);
+    $this->installSchema('system', ['sequences']);
+  }
+
+  /**
+   * Ensures that 'user.permissions' cache context is able to define cache tags.
+   */
+  public function testUserPermissionCacheContextOptimization() {
+    $user1 = $this->createUser();
+    $this->assertEqual($user1->id(), 1);
+
+    $authenticated_user = $this->createUser(['administer permissions']);
+    $role = $authenticated_user->getRoles()[1];
+
+    $test_element = [
+      '#cache' => [
+        'keys' => ['test'],
+        'contexts' => ['user', 'user.permissions'],
+      ],
+    ];
+    \Drupal::service('account_switcher')->switchTo($authenticated_user);
+    $element = $test_element;
+    $element['#markup'] = 'content for authenticated users';
+    $output = \Drupal::service('renderer')->renderRoot($element);
+    $this->assertEqual($output, 'content for authenticated users');
+
+    // Verify that the render caching is working so that other tests can be
+    // trusted.
+    $element = $test_element;
+    $element['#markup'] = 'this should not be visible';
+    $output = \Drupal::service('renderer')->renderRoot($element);
+    $this->assertEqual($output, 'content for authenticated users');
+
+    // Even though the cache contexts have been optimized to only include 'user'
+    // cache context, the element should have been changed because
+    // 'user.permissions' cache context defined a cache tags for permission
+    // change, which should have bubbled up for the element when it was optimized
+    // away.
+    Role::load($role)
+      ->revokePermission('administer permissions')
+      ->save();
+    $element = $test_element;
+    $element['#markup'] = 'this should be visible';
+    $output = \Drupal::service('renderer')->renderRoot($element);
+    $this->assertEqual($output, 'this should be visible');
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
index 0b365cb..a43b7d2 100644
--- a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
+++ b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
@@ -349,6 +349,9 @@ public function testReferencedEntity() {
         ->getCacheTags();
     }
 
+    $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($entity_cache_contexts)->getCacheableMetadata();
+    $cache_context_tags = $context_metadata->getCacheTags();
+
     // Generate the cache tags for the (non) referencing entities.
     $referencing_entity_cache_tags = Cache::mergeTags(
       $this->referencingEntity->getCacheTags(),
@@ -357,6 +360,7 @@ public function testReferencedEntity() {
       $this->entity->getCacheTags(),
       $this->getAdditionalCacheTagsForEntity($this->entity),
       $view_cache_tag,
+      $cache_context_tags,
       ['rendered']
     );
     $non_referencing_entity_cache_tags = Cache::mergeTags(
@@ -389,7 +393,10 @@ public function testReferencedEntity() {
     $access_cache_contexts = $this->getAccessCacheContextsForEntity($this->entity);
     $redirected_cid = NULL;
     if (count($access_cache_contexts)) {
-      $redirected_cid = $this->createCacheId($cache_keys, Cache::mergeContexts($entity_cache_contexts, $this->getAdditionalCacheContextsForEntity($this->referencingEntity), $access_cache_contexts));
+      $cache_contexts = Cache::mergeContexts($entity_cache_contexts, $this->getAdditionalCacheContextsForEntity($this->referencingEntity), $access_cache_contexts);
+      $redirected_cid = $this->createCacheId($cache_keys, $cache_contexts);
+      $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheableMetadata();
+      $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $context_metadata->getCacheTags());
     }
     $this->verifyRenderCache($cid, $referencing_entity_cache_tags, $redirected_cid);
 
@@ -653,7 +660,7 @@ protected function createCacheId(array $keys, array $contexts) {
     $cid_parts = $keys;
 
     $contexts = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($contexts);
-    $cid_parts = array_merge($cid_parts, $contexts);
+    $cid_parts = array_merge($cid_parts, $contexts->getKeys());
 
     return implode(':', $cid_parts);
   }
diff --git a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
index a7aa63d..b160081 100644
--- a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
@@ -65,7 +65,7 @@ public function testEntityViewBuilderCache() {
     // Get a fully built entity view render array.
     $entity_test->save();
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme']));
+    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])->getKeys());
     $cid = implode(':', $cid_parts);
     $bin = $build['#cache']['bin'];
 
@@ -117,7 +117,7 @@ public function testEntityViewBuilderCacheWithReferences() {
 
     // Get a fully built entity view render array for the referenced entity.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test_reference, 'full');
-    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme']));
+    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])->getKeys());
     $cid_reference = implode(':', $cid_parts);
     $bin_reference = $build['#cache']['bin'];
 
@@ -136,7 +136,7 @@ public function testEntityViewBuilderCacheWithReferences() {
 
     // Get a fully built entity view render array.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme']));
+    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])->getKeys());
     $cid = implode(':', $cid_parts);
     $bin = $build['#cache']['bin'];
 
diff --git a/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php b/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php
index 0b4b360..f2e3bb2 100644
--- a/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php
+++ b/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php
@@ -34,4 +34,11 @@ public function getContext() {
     return '299792458';
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php
index 004603f..7f03636 100644
--- a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php
+++ b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php
@@ -69,7 +69,7 @@ public function queryParameters() {
    * #pre_render callback for #type => pager that shows the pager cache context.
    */
   public static function showPagerCacheContext(array $pager) {
-    drupal_set_message(\Drupal::service('cache_contexts_manager')->convertTokensToKeys(['url.query_args.pagers:' . $pager['#element']])[0]);
+    drupal_set_message(\Drupal::service('cache_contexts_manager')->convertTokensToKeys(['url.query_args.pagers:' . $pager['#element']])->getKeys()[0]);
     return $pager;
   }
 
diff --git a/core/modules/views/tests/modules/views_test_data/src/Cache/ViewsTestCacheContext.php b/core/modules/views/tests/modules/views_test_data/src/Cache/ViewsTestCacheContext.php
index 25be646..88d7559 100644
--- a/core/modules/views/tests/modules/views_test_data/src/Cache/ViewsTestCacheContext.php
+++ b/core/modules/views/tests/modules/views_test_data/src/Cache/ViewsTestCacheContext.php
@@ -30,4 +30,11 @@ public function getContext() {
     return \Drupal::state()->get('views_test_cache_context', 'George');
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php b/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php
index 99a32b3..c6c06a5 100644
--- a/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php
@@ -95,7 +95,7 @@ public function testConvertTokensToKeys() {
       'baz.cnenzrgreO',
       'bar',
     ];
-    $this->assertEquals($expected, $new_keys);
+    $this->assertEquals($expected, $new_keys->getKeys());
   }
 
   /**
@@ -234,6 +234,13 @@ public function getContext() {
     return 'bar';
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return NULL;
+  }
+
 }
 
 /**
@@ -258,4 +265,11 @@ public function getContext($parameter = NULL) {
     return 'baz.' . str_rot13($parameter);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata($parameter = NULL) {
+    return NULL;
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php
index 7388c61..d753c5d 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php
@@ -8,6 +8,8 @@
 namespace Drupal\Tests\Core\Render;
 
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\Context\CacheContextKeys;
 use Drupal\Core\Cache\MemoryBackend;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Render\Element;
@@ -124,7 +126,7 @@ protected function setUp() {
               $keys[] = $context_id;
           }
         }
-        return $keys;
+        return new CacheContextKeys($keys, new CacheableMetadata());
       });
     $this->renderCache = new RenderCache($this->requestStack, $this->cacheFactory, $this->cacheContextsManager);
     $this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo, $this->renderCache, $this->rendererConfig);
