 core/core.services.yml                             | 99 +++++++++++++++++-----
 core/lib/Drupal/Core/Cache/CacheContextsPass.php   | 14 +++
 .../Core/Cache/CalculatedCacheContextInterface.php |  9 +-
 core/lib/Drupal/Core/Cache/CookiesCacheContext.php | 34 ++++++++
 core/lib/Drupal/Core/Cache/FormatCacheContext.php  | 29 +++++++
 core/lib/Drupal/Core/Cache/HeadersCacheContext.php | 34 ++++++++
 core/lib/Drupal/Core/Cache/HostCacheContext.php    | 33 ++++++++
 core/lib/Drupal/Core/Cache/IpCacheContext.php      | 29 +++++++
 .../Drupal/Core/Cache/IsSuperUserCacheContext.php  | 29 +++++++
 ...ontext.php => MenuActiveTrailsCacheContext.php} |  8 +-
 ...agerCacheContext.php => PagersCacheContext.php} | 10 ++-
 .../Drupal/Core/Cache/QueryArgsCacheContext.php    | 40 +++++++++
 .../Core/Cache/RequestStackCacheContextBase.php    | 38 +++++++++
 core/lib/Drupal/Core/Cache/RouteCacheContext.php   | 48 +++++++++++
 .../Drupal/Core/Cache/RouteNameCacheContext.php    | 28 ++++++
 core/lib/Drupal/Core/Cache/UrlCacheContext.php     | 21 +----
 .../Drupal/Core}/Cache/UserCacheContext.php        |  5 +-
 .../Drupal/Core}/Cache/UserRolesCacheContext.php   | 28 +++---
 .../Field/FieldFormatter/TimestampFormatter.php    |  2 +-
 .../Drupal/Core/Menu/MenuActiveTrailInterface.php  |  5 +-
 core/modules/book/book.services.yml                |  2 +-
 .../book/src/Plugin/Block/BookNavigationBlock.php  |  2 +-
 core/modules/comment/comment.module                |  2 +-
 .../FieldFormatter/DateTimeDefaultFormatter.php    |  2 +-
 .../FieldFormatter/DateTimePlainFormatter.php      |  2 +-
 core/modules/node/node.services.yml                |  4 +-
 ...ontext.php => NodeAccessGrantsCacheContext.php} | 79 +++++++++--------
 core/modules/node/src/NodeGrantDatabaseStorage.php |  9 +-
 ...st.php => NodeAccessGrantsCacheContextTest.php} | 40 ++++-----
 core/modules/node/src/Tests/NodeCacheTagsTest.php  |  2 +-
 .../system/src/Plugin/Block/SystemMenuBlock.php    |  2 +-
 .../Tests/Cache/PageCacheTagsIntegrationTest.php   | 10 +--
 core/modules/user/user.services.yml                | 10 ---
 .../Drupal/Tests/Core/Cache/CacheContextsTest.php  |  2 +-
 34 files changed, 553 insertions(+), 158 deletions(-)

diff --git a/core/core.services.yml b/core/core.services.yml
index cf9082c..4b197f5 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -6,23 +6,85 @@ parameters:
   factory.keyvalue.expirable:
     default: keyvalue.expirable.database
 services:
-  cache_factory:
-    class: Drupal\Core\Cache\CacheFactory
-    arguments: ['@settings', '%cache_default_bin_backends%']
-    calls:
-      - [setContainer, ['@service_container']]
-  cache_contexts:
-    class: Drupal\Core\Cache\CacheContexts
-    arguments: ['@service_container', '%cache_contexts%' ]
+  # Simple cache contexts, directly derived from the request context.
+  cache_context.ip:
+    class: Drupal\Core\Cache\IpCacheContext
+    arguments: ['@request_stack']
+    tags:
+      - { name: cache.context }
+  cache_context.headers:
+    class: Drupal\Core\Cache\HeadersCacheContext
+    arguments: ['@request_stack']
+    tags:
+      - { name: cache.context }
+  cache_context.cookies:
+    class: Drupal\Core\Cache\CookiesCacheContext
+    arguments: ['@request_stack']
+    tags:
+      - { name: cache.context }
   cache_context.url:
     class: Drupal\Core\Cache\UrlCacheContext
     arguments: ['@request_stack']
     tags:
-      - { name: cache.context}
-  cache_context.pager:
-    class: Drupal\Core\Cache\PagerCacheContext
+      - { name: cache.context }
+  cache_context.url.host:
+    class: Drupal\Core\Cache\HostCacheContext
+    arguments: ['@request_stack']
+    tags:
+      - { name: cache.context }
+  cache_context.url.query_args:
+    class: Drupal\Core\Cache\QueryArgsCacheContext
+    arguments: ['@request_stack']
+    tags:
+      - { name: cache.context }
+  cache_context.url.pager:
+    class: Drupal\Core\Cache\PagersCacheContext
+    arguments: ['@request_stack']
+    tags:
+      - { name: cache.context }
+  cache_context.url.route:
+    class: Drupal\Core\Cache\RouteCacheContext
+    arguments: ['@current_route_match']
+    tags:
+      - { name: cache.context }
+  cache_context.url.route.name:
+    class: Drupal\Core\Cache\RouteNameCacheContext
+    arguments: ['@current_route_match']
+    tags:
+      - { name: cache.context }
+  cache_context.url.route.format:
+    class: Drupal\Core\Cache\FormatCacheContext
     arguments: ['@request_stack']
     tags:
+      - { name: cache.context }
+  cache_context.url.route.menu_active_trails:
+    class: Drupal\Core\Cache\MenuActiveTrailsCacheContext
+    calls:
+      - [setContainer, ['@service_container']]
+    tags:
+      - { name: cache.context }
+
+  # Complex cache contexts, that may be calculated from a combination of
+  # multiple aspects of the request context plus additional logic. Hence they
+  # are their own roots.
+  cache_context.user:
+    class: Drupal\Core\Cache\UserCacheContext
+    arguments: ['@current_user']
+    tags:
+      - { name: cache.context}
+  cache_context.user.roles:
+    class: Drupal\Core\Cache\UserRolesCacheContext
+    arguments: ['@current_user']
+    tags:
+      - { name: cache.context}
+  cache_context.user.is_super_user:
+    class: Drupal\Core\Cache\IsSuperUserCacheContext
+    arguments: ['@current_user']
+    tags:
+      - { name: cache.context}
+  cache_context.user.timezone:
+    class: Drupal\Core\Cache\TimeZoneCacheContext
+    tags:
       - { name: cache.context}
   cache_context.language:
     class: Drupal\Core\Cache\LanguageCacheContext
@@ -34,16 +96,15 @@ services:
     arguments: ['@theme.manager']
     tags:
       - { name: cache.context}
-  cache_context.timezone:
-    class: Drupal\Core\Cache\TimeZoneCacheContext
-    tags:
-      - { name: cache.context}
-  cache_context.menu.active_trail:
-    class: Drupal\Core\Cache\MenuActiveTrailCacheContext
+
+  cache_factory:
+    class: Drupal\Core\Cache\CacheFactory
+    arguments: ['@settings', '%cache_default_bin_backends%']
     calls:
       - [setContainer, ['@service_container']]
-    tags:
-      - { name: cache.context}
+  cache_contexts:
+    class: Drupal\Core\Cache\CacheContexts
+    arguments: ['@service_container', '%cache_contexts%' ]
   cache_tags.invalidator:
     parent: container.trait
     class: Drupal\Core\Cache\CacheTagsInvalidator
diff --git a/core/lib/Drupal/Core/Cache/CacheContextsPass.php b/core/lib/Drupal/Core/Cache/CacheContextsPass.php
index b1cb74e..576b2b5 100644
--- a/core/lib/Drupal/Core/Cache/CacheContextsPass.php
+++ b/core/lib/Drupal/Core/Cache/CacheContextsPass.php
@@ -28,6 +28,20 @@ public function process(ContainerBuilder $container) {
       }
       $cache_contexts[] = substr($id, 14);
     }
+
+    // Validate.
+    sort($cache_contexts);
+    foreach ($cache_contexts as $id) {
+      // Validate the hierarchy of non-root-level cache contexts.
+      if (strpos($id, '.') !== FALSE) {
+        $parent = substr($id, 0, strrpos($id, '.'));
+        if (!in_array($parent, $cache_contexts)) {
+          throw new \InvalidArgumentException(sprintf('The service "%s" has an invalid service ID: the period indicates the hierarchy of cache contexts, therefore "%s" is considered the parent cache context, but no cache context service with that name was found.', $id, $parent));
+        }
+      }
+    }
+
+
     $container->setParameter('cache_contexts', $cache_contexts);
   }
 
diff --git a/core/lib/Drupal/Core/Cache/CalculatedCacheContextInterface.php b/core/lib/Drupal/Core/Cache/CalculatedCacheContextInterface.php
index 795a512..38114c3 100644
--- a/core/lib/Drupal/Core/Cache/CalculatedCacheContextInterface.php
+++ b/core/lib/Drupal/Core/Cache/CalculatedCacheContextInterface.php
@@ -28,12 +28,13 @@ public static function getLabel();
    * A cache context service's name is used as a token (placeholder) cache key,
    * and is then replaced with the string returned by this method.
    *
-   * @param string $parameter
-   *   The parameter.
+   * @param string|null $parameter
+   *   The parameter, or NULL to indicate all possible parameter values.
    *
    * @return string
-   *   The string representation of the cache context.
+   *   The string representation of the cache context. When $parameter is NULL,
+   *   a value representing all possible parameters must be generated.
    */
-  public function getContext($parameter);
+  public function getContext($parameter = NULL);
 
 }
diff --git a/core/lib/Drupal/Core/Cache/CookiesCacheContext.php b/core/lib/Drupal/Core/Cache/CookiesCacheContext.php
new file mode 100644
index 0000000..6760b1b
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CookiesCacheContext.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\CookiesCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the CookiesCacheContext service, for "per cookie" caching.
+ */
+class CookiesCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('HTTP cookies');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext($cookie = NULL) {
+    if ($cookie === NULL) {
+      return $this->requestStack->getCurrentRequest()->cookies->all();
+    }
+    else {
+      return $this->requestStack->getCurrentRequest()->cookies->get($cookie);
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/FormatCacheContext.php b/core/lib/Drupal/Core/Cache/FormatCacheContext.php
new file mode 100644
index 0000000..2dbf8ec
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/FormatCacheContext.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\FormatCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the FormatCacheContext service, for "per format" caching.
+ */
+class FormatCacheContext extends RequestStackCacheContextBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Format');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    return $this->requestStack->getCurrentRequest()->getRequestFormat();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/HeadersCacheContext.php b/core/lib/Drupal/Core/Cache/HeadersCacheContext.php
new file mode 100644
index 0000000..e2b8932
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/HeadersCacheContext.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\HeadersCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the HeadersCacheContext service, for "per header" caching.
+ */
+class HeadersCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('HTTP headers');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext($header = NULL) {
+    if ($header === NULL) {
+      return $this->requestStack->getCurrentRequest()->headers->all();
+    }
+    else {
+      return $this->requestStack->getCurrentRequest()->headers->get($header);
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/HostCacheContext.php b/core/lib/Drupal/Core/Cache/HostCacheContext.php
new file mode 100644
index 0000000..8a3af00
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/HostCacheContext.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\HostCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the HostCacheContext service, for "per host" caching.
+ *
+ * A "host" is defined as the combination of URI scheme, domain name and port.
+ *
+ * @see Symfony\Component\HttpFoundation::getSchemeAndHttpHost()
+ */
+class HostCacheContext extends RequestStackCacheContextBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Host');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    return $this->requestStack->getCurrentRequest()->getSchemeAndHttpHost();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/IpCacheContext.php b/core/lib/Drupal/Core/Cache/IpCacheContext.php
new file mode 100644
index 0000000..c0c2e08
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/IpCacheContext.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\IpCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the IpCacheContext service, for "per IP address" caching.
+ */
+class IpCacheContext extends RequestStackCacheContextBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('IP address');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    return $this->requestStack->getCurrentRequest()->getClientIp();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/IsSuperUserCacheContext.php b/core/lib/Drupal/Core/Cache/IsSuperUserCacheContext.php
new file mode 100644
index 0000000..23d9a9a
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/IsSuperUserCacheContext.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\IsSuperUserCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the IsSuperUserCacheContext service, for "super user or not" caching.
+ */
+class IsSuperUserCacheContext extends UserCacheContext {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Is super user');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    return $this->user->id() === 1;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/MenuActiveTrailCacheContext.php b/core/lib/Drupal/Core/Cache/MenuActiveTrailsCacheContext.php
similarity index 68%
rename from core/lib/Drupal/Core/Cache/MenuActiveTrailCacheContext.php
rename to core/lib/Drupal/Core/Cache/MenuActiveTrailsCacheContext.php
index 7c41bee..3b43178 100644
--- a/core/lib/Drupal/Core/Cache/MenuActiveTrailCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/MenuActiveTrailsCacheContext.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\Core\Cache\MenuActiveTrailCacheContext.
+ * Contains \Drupal\Core\Cache\MenuActiveTrailsCacheContext.
  */
 
 namespace Drupal\Core\Cache;
@@ -10,12 +10,12 @@
 use Symfony\Component\DependencyInjection\ContainerAware;
 
 /**
- * Defines the MenuActiveTrailCacheContext service.
+ * Defines the MenuActiveTrailsCacheContext service.
  *
  * This class is container-aware to avoid initializing the 'menu.active_trail'
  * service (and its dependencies) when it is not necessary.
  */
-class MenuActiveTrailCacheContext extends ContainerAware implements CalculatedCacheContextInterface {
+class MenuActiveTrailsCacheContext extends ContainerAware implements CalculatedCacheContextInterface {
 
   /**
    * {@inheritdoc}
@@ -27,7 +27,7 @@ public static function getLabel() {
   /**
    * {@inheritdoc}
    */
-  public function getContext($menu_name) {
+  public function getContext($menu_name = NULL) {
     $active_trail = $this->container->get('menu.active_trail')
       ->getActiveTrailIds($menu_name);
     return 'menu_trail.' . $menu_name . '|' . implode('|', $active_trail);
diff --git a/core/lib/Drupal/Core/Cache/PagerCacheContext.php b/core/lib/Drupal/Core/Cache/PagersCacheContext.php
similarity index 49%
rename from core/lib/Drupal/Core/Cache/PagerCacheContext.php
rename to core/lib/Drupal/Core/Cache/PagersCacheContext.php
index a4c308c..d1fcba2 100644
--- a/core/lib/Drupal/Core/Cache/PagerCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/PagersCacheContext.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\Core\Cache\PagerCacheContext.
+ * Contains \Drupal\Core\Cache\PagersCacheContext.
  */
 
 namespace Drupal\Core\Cache;
@@ -10,7 +10,7 @@
 /**
  * Defines a cache context for "per page in a pager" caching.
  */
-class PagerCacheContext implements CalculatedCacheContextInterface {
+class PagersCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
 
   /**
    * {@inheritdoc}
@@ -22,7 +22,11 @@ public static function getLabel() {
   /**
    * {@inheritdoc}
    */
-  public function getContext($pager_id) {
+  public function getContext($pager_id = NULL) {
+    if ($pager_id === NULL) {
+      return 'pager' . $this->requestStack->getCurrentRequest()->query->get('page', '');
+    }
+
     return 'pager.' . $pager_id . '.' . pager_find_page($pager_id);
   }
 
diff --git a/core/lib/Drupal/Core/Cache/QueryArgsCacheContext.php b/core/lib/Drupal/Core/Cache/QueryArgsCacheContext.php
new file mode 100644
index 0000000..7b5e01d
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/QueryArgsCacheContext.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\QueryArgsCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the QueryArgsCacheContext service, for "per host" caching.
+ *
+ * A "host" is defined as the combination of URI scheme, domain name and port.
+ *
+ * @see Symfony\Component\HttpFoundation::getSchemeAndHttpHost()
+ */
+class QueryArgsCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Query arguments');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext($query_arg = NULL) {
+    if ($query_arg === NULL) {
+      return $this->requestStack->getCurrentRequest()->getQueryString();
+    }
+    else {
+      $query_args = [];
+      parse_str($this->requestStack->getCurrentRequest()->getQueryString(), $query_args);
+      return $query_args[$query_arg];
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/RequestStackCacheContextBase.php b/core/lib/Drupal/Core/Cache/RequestStackCacheContextBase.php
new file mode 100644
index 0000000..b75f3d7
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/RequestStackCacheContextBase.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\RequestStackCacheContextBase.
+ */
+
+namespace Drupal\Core\Cache;
+
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Defines the HostCacheContext service, for "per host" caching.
+ *
+ * A "host" is defined as the combination of URI scheme, domain name and port.
+ *
+ * @see Symfony\Component\HttpFoundation::getSchemeAndHttpHost()
+ */
+abstract class RequestStackCacheContextBase implements CacheContextInterface {
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Constructs a new RequestStackCacheContextBase class.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   */
+  public function __construct(RequestStack $request_stack) {
+    $this->requestStack = $request_stack;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/RouteCacheContext.php b/core/lib/Drupal/Core/Cache/RouteCacheContext.php
new file mode 100644
index 0000000..f944f12
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/RouteCacheContext.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\RouteCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+use Drupal\Core\Routing\RouteMatchInterface;
+
+/**
+ * Defines the RouteCacheContext service, for "per route" caching.
+ */
+class RouteCacheContext {
+
+  /**
+   * The route match.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * Constructs a new RouteCacheContext class.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   */
+  public function __construct(RouteMatchInterface $route_match) {
+    $this->routeMatch = $route_match;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Route');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    return $this->routeMatch->getRouteName() . serialize($this->routeMatch->getRawParameters()->all());
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/RouteNameCacheContext.php b/core/lib/Drupal/Core/Cache/RouteNameCacheContext.php
new file mode 100644
index 0000000..7a429d4
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/RouteNameCacheContext.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\RouteNameCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the RouteCacheContext service, for "per route name" caching.
+ */
+class RouteNameCacheContext extends RouteCacheContext {
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Route name');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    return $this->routeMatch->getRouteName();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/UrlCacheContext.php b/core/lib/Drupal/Core/Cache/UrlCacheContext.php
index 308d182..1381093 100644
--- a/core/lib/Drupal/Core/Cache/UrlCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/UrlCacheContext.php
@@ -7,29 +7,10 @@
 
 namespace Drupal\Core\Cache;
 
-use Symfony\Component\HttpFoundation\RequestStack;
-
 /**
  * Defines the UrlCacheContext service, for "per page" caching.
  */
-class UrlCacheContext implements CacheContextInterface {
-
-  /**
-   * The request stack.
-   *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
-   */
-  protected $requestStack;
-
-  /**
-   * Constructs a new UrlCacheContext service.
-   *
-   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
-   *   The request stack.
-   */
-  public function __construct(RequestStack $request_stack) {
-    $this->requestStack = $request_stack;
-  }
+class UrlCacheContext extends RequestStackCacheContextBase {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/user/src/Cache/UserCacheContext.php b/core/lib/Drupal/Core/Cache/UserCacheContext.php
similarity index 83%
rename from core/modules/user/src/Cache/UserCacheContext.php
rename to core/lib/Drupal/Core/Cache/UserCacheContext.php
index d9d880e..d5e2825 100644
--- a/core/modules/user/src/Cache/UserCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/UserCacheContext.php
@@ -2,12 +2,11 @@
 
 /**
  * @file
- * Contains \Drupal\user\Cache\UserCacheContext.
+ * Contains \Drupal\Core\Cache\UserCacheContext.
  */
 
-namespace Drupal\user\Cache;
+namespace Drupal\Core\Cache;
 
-use Drupal\Core\Cache\CacheContextInterface;
 use Drupal\Core\Session\AccountInterface;
 
 /**
diff --git a/core/modules/user/src/Cache/UserRolesCacheContext.php b/core/lib/Drupal/Core/Cache/UserRolesCacheContext.php
similarity index 30%
rename from core/modules/user/src/Cache/UserRolesCacheContext.php
rename to core/lib/Drupal/Core/Cache/UserRolesCacheContext.php
index 0f7975d..cf7b450 100644
--- a/core/modules/user/src/Cache/UserRolesCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/UserRolesCacheContext.php
@@ -2,28 +2,15 @@
 
 /**
  * @file
- * Contains \Drupal\user\Cache\UserRolesCacheContext.
+ * Contains \Drupal\Core\Cache\UserRolesCacheContext.
  */
 
-namespace Drupal\user\Cache;
-
-use Drupal\Core\Cache\CacheContextInterface;
-use Drupal\Core\Session\AccountInterface;
+namespace Drupal\Core\Cache;
 
 /**
  * Defines the UserRolesCacheContext service, for "per role" caching.
  */
-class UserRolesCacheContext implements CacheContextInterface {
-
-  /**
-   * Constructs a new UserRolesCacheContext service.
-   *
-   * @param \Drupal\Core\Session\AccountInterface $user
-   *   The current user.
-   */
-  public function __construct(AccountInterface $user) {
-    $this->user = $user;
-  }
+class UserRolesCacheContext extends UserCacheContext implements CalculatedCacheContextInterface{
 
   /**
    * {@inheritdoc}
@@ -35,8 +22,13 @@ public static function getLabel() {
   /**
    * {@inheritdoc}
    */
-  public function getContext() {
-    return 'r.' . implode(',', $this->user->getRoles());
+  public function getContext($role = NULL) {
+    if ($role === NULL) {
+      return 'r.' . implode(',', $this->user->getRoles());
+    }
+    else {
+      return 'r.' . $role . '.' . (in_array($role, $this->user->getRoles()) ? '0' : '1');
+    }
   }
 
 }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php
index 6de42ee..f7e7763 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php
@@ -34,7 +34,7 @@ public function viewElements(FieldItemListInterface $items) {
       $elements[$delta] = [
         '#cache' => [
           'contexts' => [
-            'timezone',
+            'user.timezone',
           ],
         ],
         '#markup' => format_date($item->value)
diff --git a/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php b/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php
index 49f4e99..be32e64 100644
--- a/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php
+++ b/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php
@@ -18,8 +18,9 @@
   /**
    * Gets the active trail IDs of the specified menu tree.
    *
-   * @param string $menu_name
-   *   The menu name of the requested tree.
+   * @param string|NULL $menu_name
+   *   (optional) The menu name of the requested tree. If omitted, all menu
+   *   trees will be searched.
    *
    * @return array
    *   An array containing the active trail: a list of plugin IDs.
diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml
index 440d021..179c924 100644
--- a/core/modules/book/book.services.yml
+++ b/core/modules/book/book.services.yml
@@ -23,7 +23,7 @@ services:
     arguments: ['@book.manager']
     tags:
       - { name: access_check, applies_to: _access_book_removable }
-  cache_context.book.navigation:
+  cache_context.url.route.book_navigation:
     class: Drupal\book\Cache\BookNavigationCacheContext
     arguments: ['@request_stack']
     tags:
diff --git a/core/modules/book/src/Plugin/Block/BookNavigationBlock.php b/core/modules/book/src/Plugin/Block/BookNavigationBlock.php
index 4ce4c3f..7fce677 100644
--- a/core/modules/book/src/Plugin/Block/BookNavigationBlock.php
+++ b/core/modules/book/src/Plugin/Block/BookNavigationBlock.php
@@ -186,7 +186,7 @@ protected function getRequiredCacheContexts() {
     // context.
     return [
       'user.roles',
-      'book.navigation',
+      'url.route.book_navigation',
     ];
   }
 
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 695018c..a8669a9 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -208,7 +208,7 @@ function comment_entity_build_defaults_alter(array &$build, EntityInterface $ent
     foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
       if ($definition->getType() === 'comment' && ($display_options = $display->getComponent($field_name))) {
         $pager_id = $display_options['settings']['pager_id'];
-        $build['#cache']['contexts'][] = 'pager:' . $pager_id;
+        $build['#cache']['contexts'][] = 'url.pagers:' . $pager_id;
       }
     }
   }
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php
index a7cb32a..6634592 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php
@@ -129,7 +129,7 @@ public function viewElements(FieldItemListInterface $items) {
       $elements[$delta] = array(
         '#cache' => [
           'contexts' => [
-            'timezone',
+            'user.timezone',
           ],
         ],
         '#theme' => 'time',
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
index 3f68a7f..f0c0872 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
@@ -49,7 +49,7 @@ public function viewElements(FieldItemListInterface $items) {
       $elements[$delta] = [
         '#cache' => [
           'contexts' => [
-            'timezone',
+            'user.timezone',
           ],
         ],
         '#markup' => $output,
diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml
index 64bd996..7c74628 100644
--- a/core/modules/node/node.services.yml
+++ b/core/modules/node/node.services.yml
@@ -38,8 +38,8 @@ services:
     arguments: ['@current_route_match']
     tags:
       - { name: page_cache_response_policy }
-  cache_context.node_view_grants:
-    class: Drupal\node\Cache\NodeAccessViewGrantsCacheContext
+  cache_context.user.node_grants:
+    class: Drupal\node\Cache\NodeAccessGrantsCacheContext
     arguments: ['@current_user']
     tags:
       - { name: cache.context }
diff --git a/core/modules/node/src/Cache/NodeAccessViewGrantsCacheContext.php b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
similarity index 20%
rename from core/modules/node/src/Cache/NodeAccessViewGrantsCacheContext.php
rename to core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
index 24323b9..e213c40 100644
--- a/core/modules/node/src/Cache/NodeAccessViewGrantsCacheContext.php
+++ b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
@@ -2,39 +2,22 @@
 
 /**
  * @file
- * Contains \Drupal\Core\Cache\NodeAccessViewGrantsCacheContext.
+ * Contains \Drupal\Core\Cache\NodeAccessGrantsCacheContext.
  */
 
 namespace Drupal\node\Cache;
 
-use Drupal\Core\Cache\CacheContextInterface;
-use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Cache\CalculatedCacheContextInterface;
+use Drupal\Core\Cache\UserCacheContext;
 
 /**
- * Defines the node access view grants cache context service.
+ * Defines the node access view cache context service.
  *
- * This allows for node access grants-sensitive caching when viewing nodes.
+ * This allows for node access grants-sensitive caching when listing nodes.
  *
  * @see node_query_node_access_alter()
  */
-class NodeAccessViewGrantsCacheContext implements CacheContextInterface {
-
-  /**
-   * The current user.
-   *
-   * @var \Drupal\Core\Session\AccountInterface
-   */
-  protected $user;
-
-  /**
-   * Constructs a new NodeAccessViewGrantsCacheContext service.
-   *
-   * @param \Drupal\Core\Session\AccountInterface $user
-   *   The current user.
-   */
-  public function __construct(AccountInterface $user) {
-    $this->user = $user;
-  }
+class NodeAccessGrantsCacheContext extends UserCacheContext implements CalculatedCacheContextInterface {
 
   /**
    * {@inheritdoc}
@@ -46,24 +29,52 @@ public static function getLabel() {
   /**
    * {@inheritdoc}
    */
-  public function getContext() {
-    // If the current user either:
-    // - can bypass node access
-    // - has a global view grant (such as a view grant for node ID 0) — note
-    //   that this is automatically the case if no node access modules exist (no
-    //   hook_node_grants() implementations)
-    // then we don't need to determine the exact node view grants for the
-    // current user.
-    if ($this->user->hasPermission('bypass node access') || node_access_view_all_nodes($this->user)) {
+  public function getContext($operation = NULL) {
+    // If the current user either can bypass node access then we don't need to
+    // determine the exact node grants for the current user.
+    if ($this->user->hasPermission('bypass node access')) {
       return 'all';
     }
 
-    $grants = node_access_grants('view', $this->user);
+    // When no specific operation is specified, check the grants for all three
+    // possible operations.
+    if ($operation === NULL) {
+      $result = [];
+      foreach (['view', 'update', 'delete'] as $op) {
+        $result[] = $this->checkNodeGrants($op);
+      }
+      return implode('-', $result);
+    }
+    else {
+      return $this->checkNodeGrants($operation);
+    }
+  }
+
+  /**
+   * Checks the node grants for the given operation.
+   *
+   * @param string $operation
+   *   The operation to check the node grants for.
+   *
+   * @return string
+   *   The string representation of the cache context.
+   */
+  protected function checkNodeGrants($operation) {
+    // When checking the grants for the 'view' operation and the current user
+    // has a global view grant (i.e. a view grant for node ID 0) — note that
+    // this is automatically the case if no node access modules exist (no
+    // hook_node_grants() implementations) then we don't need to determine the
+    // exact node view grants for the current user.
+    if ($operation === 'view' && node_access_view_all_nodes($this->user)) {
+      return 'view.all';
+    }
+
+    $grants = node_access_grants($operation, $this->user);
     $grants_context_parts = [];
     foreach ($grants as $realm => $gids) {
       $grants_context_parts[] = $realm . ':' . implode(',', $gids);
     }
-    return implode(';', $grants_context_parts);
+    return $operation . '.' . implode(';', $grants_context_parts);
   }
 
 }
diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php
index da6522d..5c66190 100644
--- a/core/modules/node/src/NodeGrantDatabaseStorage.php
+++ b/core/modules/node/src/NodeGrantDatabaseStorage.php
@@ -106,12 +106,11 @@ public function access(NodeInterface $node, $operation, $langcode, AccountInterf
     // theoretically cacheable, because we don't have the necessary metadata to
     // know it for a fact.
     $set_cacheability = function (AccessResult $access_result) use ($operation) {
-      if ($operation === 'view') {
-        return $access_result->addCacheContexts(['node_view_grants']);
-      }
-      else {
-        return $access_result->setCacheable(FALSE);
+      $access_result->addCacheContexts(['user.node_grants:' . $operation]);
+      if ($operation !== 'view') {
+        $access_result->setCacheable(FALSE);
       }
+      return $access_result;
     };
 
     if ($query->execute()->fetchField()) {
diff --git a/core/modules/node/src/Tests/NodeAccessViewGrantsCacheContextTest.php b/core/modules/node/src/Tests/NodeAccessGrantsCacheContextTest.php
similarity index 76%
rename from core/modules/node/src/Tests/NodeAccessViewGrantsCacheContextTest.php
rename to core/modules/node/src/Tests/NodeAccessGrantsCacheContextTest.php
index 1cd8062..d66e776 100644
--- a/core/modules/node/src/Tests/NodeAccessViewGrantsCacheContextTest.php
+++ b/core/modules/node/src/Tests/NodeAccessGrantsCacheContextTest.php
@@ -2,18 +2,18 @@
 
 /**
  * @file
- * Contains \Drupal\node\Tests\NodeAccessViewGrantsCacheContextTest.
+ * Contains \Drupal\node\Tests\NodeAccessGrantsCacheContextTest.
  */
 
 namespace Drupal\node\Tests;
 
 /**
- * Tests the node access view grants cache context service.
+ * Tests the node access grants cache context service.
  *
  * @group node
  * @group Cache
  */
-class NodeAccessViewGrantsCacheContextTest extends NodeTestBase {
+class NodeAccessGrantsCacheContextTest extends NodeTestBase {
 
   /**
    * Modules to enable.
@@ -71,20 +71,20 @@ protected function assertCacheContext(array $expected) {
         $this->drupalLogin($this->userMapping[$uid]);
       }
       $this->pass('Asserting cache context for user ' . $uid . '.');
-      $this->assertIdentical($context, $this->container->get('cache_context.node_view_grants')->getContext());
+      $this->assertIdentical($context, $this->container->get('cache_context.user.node_grants')->getContext('view'));
     }
     $this->drupalLogout();
   }
 
   /**
-   * Tests NodeAccessViewGrantsCacheContext::getContext().
+   * Tests NodeAccessGrantsCacheContext::getContext().
    */
   public function testCacheContext() {
     $this->assertCacheContext([
-      0 => 'all:0;node_access_test_author:0;node_access_all:0',
+      0 => 'view.all:0;node_access_test_author:0;node_access_all:0',
       1 => 'all',
-      2 => 'all:0;node_access_test_author:2;node_access_test:8888,8889',
-      3 => 'all:0;node_access_test_author:3',
+      2 => 'view.all:0;node_access_test_author:2;node_access_test:8888,8889',
+      3 => 'view.all:0;node_access_test_author:3',
     ]);
 
     // Grant view to all nodes (because nid = 0) for users in the
@@ -103,40 +103,40 @@ public function testCacheContext() {
     \Drupal::state()->set('node_access_test.no_access_uid', 0);
     drupal_static_reset('node_access_view_all_nodes');
     $this->assertCacheContext([
-      0 => 'all',
+      0 => 'view.all',
       1 => 'all',
-      2 => 'all:0;node_access_test_author:2;node_access_test:8888,8889',
-      3 => 'all:0;node_access_test_author:3',
+      2 => 'view.all:0;node_access_test_author:2;node_access_test:8888,8889',
+      3 => 'view.all:0;node_access_test_author:3',
     ]);
 
     // Put user accessUser (uid 2) in the realm.
     \Drupal::state()->set('node_access_test.no_access_uid', $this->accessUser->id());
     drupal_static_reset('node_access_view_all_nodes');
     $this->assertCacheContext([
-      0 => 'all:0;node_access_test_author:0',
+      0 => 'view.all:0;node_access_test_author:0',
       1 => 'all',
-      2 => 'all',
-      3 => 'all:0;node_access_test_author:3',
+      2 => 'view.all',
+      3 => 'view.all:0;node_access_test_author:3',
     ]);
 
     // Put user noAccessUser (uid 3) in the realm.
     \Drupal::state()->set('node_access_test.no_access_uid', $this->noAccessUser->id());
     drupal_static_reset('node_access_view_all_nodes');
     $this->assertCacheContext([
-      0 => 'all:0;node_access_test_author:0',
+      0 => 'view.all:0;node_access_test_author:0',
       1 => 'all',
-      2 => 'all:0;node_access_test_author:2;node_access_test:8888,8889',
-      3 => 'all',
+      2 => 'view.all:0;node_access_test_author:2;node_access_test:8888,8889',
+      3 => 'view.all',
     ]);
 
     // Uninstall the node_access_test module
     $this->container->get('module_installer')->uninstall(['node_access_test']);
     drupal_static_reset('node_access_view_all_nodes');
     $this->assertCacheContext([
-      0 => 'all',
+      0 => 'view.all',
       1 => 'all',
-      2 => 'all',
-      3 => 'all',
+      2 => 'view.all',
+      3 => 'view.all',
     ]);
   }
 
diff --git a/core/modules/node/src/Tests/NodeCacheTagsTest.php b/core/modules/node/src/Tests/NodeCacheTagsTest.php
index 6da02d0..eeaac1e 100644
--- a/core/modules/node/src/Tests/NodeCacheTagsTest.php
+++ b/core/modules/node/src/Tests/NodeCacheTagsTest.php
@@ -46,7 +46,7 @@ protected function createEntity() {
    * {@inheritdoc}
    */
   protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
-    return ['timezone'];
+    return ['user.timezone'];
   }
 
   /**
diff --git a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
index c07fba2..742966d 100644
--- a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
+++ b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
@@ -201,7 +201,7 @@ protected function getRequiredCacheContexts() {
     $menu_name = $this->getDerivativeId();
     return [
       'user.roles',
-      'menu.active_trail:' . $menu_name,
+      'url.route.menu_active_trails:' . $menu_name,
     ];
   }
 
diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
index da74be8..7b9c67a 100644
--- a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
+++ b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
@@ -69,12 +69,12 @@ function testPageCacheTags() {
 
     $cache_contexts = [
       'language',
-      'menu.active_trail:account',
-      'menu.active_trail:footer',
-      'menu.active_trail:main',
-      'menu.active_trail:tools',
+      'url.route.menu_active_trails:account',
+      'url.route.menu_active_trails:footer',
+      'url.route.menu_active_trails:main',
+      'url.route.menu_active_trails:tools',
       'theme',
-      'timezone',
+      'user.timezone',
       'user.roles',
     ];
 
diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml
index 50405b5..6021b50 100644
--- a/core/modules/user/user.services.yml
+++ b/core/modules/user/user.services.yml
@@ -19,16 +19,6 @@ services:
     class: Drupal\Core\Authentication\Provider\Cookie
     tags:
       - { name: authentication_provider, priority: 0 }
-  cache_context.user:
-    class: Drupal\user\Cache\UserCacheContext
-    arguments: ['@current_user']
-    tags:
-      - { name: cache.context}
-  cache_context.user.roles:
-    class: Drupal\user\Cache\UserRolesCacheContext
-    arguments: ['@current_user']
-    tags:
-      - { name: cache.context}
   user.data:
     class: Drupal\user\UserData
     arguments: ['@database']
diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheContextsTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheContextsTest.php
index 1563a6d..d33eaa1 100644
--- a/core/tests/Drupal/Tests/Core/Cache/CacheContextsTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/CacheContextsTest.php
@@ -146,7 +146,7 @@ public static function getLabel() {
   /**
    * {@inheritdoc}
    */
-  public function getContext($parameter) {
+  public function getContext($parameter = NULL) {
     if (!is_string($parameter) || strlen($parameter) ===  0) {
       throw new \Exception();
     }
