 core/core.services.yml                             | 48 ++++++++++++++--------
 core/lib/Drupal/Core/Cache/CacheContextsPass.php   | 14 +++++++
 .../Core/Cache/CalculatedCacheContextInterface.php |  9 ++--
 core/lib/Drupal/Core/Cache/HostCacheContext.php    | 33 +++++++++++++++
 .../Core/Cache/MenuActiveTrailCacheContext.php     |  2 +-
 core/lib/Drupal/Core/Cache/PagerCacheContext.php   |  8 +++-
 .../Drupal/Core/Cache/QueryArgsCacheContext.php    | 40 ++++++++++++++++++
 .../Core/Cache/RequestStackCacheContextBase.php    | 38 +++++++++++++++++
 core/lib/Drupal/Core/Cache/RouteCacheContext.php   | 48 ++++++++++++++++++++++
 core/lib/Drupal/Core/Cache/UrlCacheContext.php     | 21 +---------
 .../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 +-
 .../system/src/Plugin/Block/SystemMenuBlock.php    |  2 +-
 .../Tests/Cache/PageCacheTagsIntegrationTest.php   |  8 ++--
 16 files changed, 229 insertions(+), 53 deletions(-)

diff --git a/core/core.services.yml b/core/core.services.yml
index 9b892ec..ee0d125 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -6,24 +6,46 @@ 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%' ]
   cache_context.url:
     class: Drupal\Core\Cache\UrlCacheContext
     arguments: ['@request_stack']
     tags:
-      - { name: cache.context}
-  cache_context.pager:
+      - { 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\PagerCacheContext
     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.menu_active_trail:
+    class: Drupal\Core\Cache\MenuActiveTrailCacheContext
+    calls:
+      - [setContainer, ['@service_container']]
+    tags:
+      - { name: cache.context}
+
+  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%' ]
   cache_context.language:
     class: Drupal\Core\Cache\LanguageCacheContext
     arguments: ['@language_manager']
@@ -38,12 +60,6 @@ services:
     class: Drupal\Core\Cache\TimeZoneCacheContext
     tags:
       - { name: cache.context}
-  cache_context.menu.active_trail:
-    class: Drupal\Core\Cache\MenuActiveTrailCacheContext
-    calls:
-      - [setContainer, ['@service_container']]
-    tags:
-      - { name: cache.context}
   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/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/MenuActiveTrailCacheContext.php b/core/lib/Drupal/Core/Cache/MenuActiveTrailCacheContext.php
index 7c41bee..7f0e226 100644
--- a/core/lib/Drupal/Core/Cache/MenuActiveTrailCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/MenuActiveTrailCacheContext.php
@@ -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/PagerCacheContext.php
index a4c308c..f9d6c3a 100644
--- a/core/lib/Drupal/Core/Cache/PagerCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/PagerCacheContext.php
@@ -10,7 +10,7 @@
 /**
  * Defines a cache context for "per page in a pager" caching.
  */
-class PagerCacheContext implements CalculatedCacheContextInterface {
+class PagerCacheContext 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/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/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..d4c2415 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.pager:' . $pager_id;
       }
     }
   }
diff --git a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
index c07fba2..f5713f1 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_trail:' . $menu_name,
     ];
   }
 
diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
index b56a6ba..d2e4ac9 100644
--- a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
+++ b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
@@ -72,10 +72,10 @@ 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_trail:account',
+      'url.route.menu_active_trail:footer',
+      'url.route.menu_active_trail:main',
+      'url.route.menu_active_trail:tools',
       'theme',
       'timezone',
       'user.roles',
