diff --git a/core/core.services.yml b/core/core.services.yml
index a194ead..5cd3422 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -266,6 +266,9 @@ services:
   context.handler:
     class: Drupal\Core\Plugin\Context\ContextHandler
     arguments: ['@typed_data_manager']
+  context.repository:
+    class: Drupal\Core\Plugin\Context\LazyContextRepository
+    arguments: ['@service_container']
   cron:
     class: Drupal\Core\Cron
     arguments: ['@module_handler', '@lock', '@queue', '@state', '@account_switcher', '@logger.channel.cron', '@plugin.manager.queue_worker']
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index ae0d031..8244320 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Cache\Context\CacheContextsPass;
 use Drupal\Core\Cache\ListCacheBinsPass;
 use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
+use Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass;
 use Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteEnhancers;
 use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteFilters;
@@ -88,6 +89,7 @@ public function register(ContainerBuilder $container) {
     // Add the compiler pass that will process the tagged services.
     $container->addCompilerPass(new ListCacheBinsPass());
     $container->addCompilerPass(new CacheContextsPass());
+    $container->addCompilerPass(new ContextProvidersPass());
 
     // Register plugin managers.
     $container->addCompilerPass(new PluginManagerPass());
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/ContextProvidersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/ContextProvidersPass.php
new file mode 100644
index 0000000..b724be8
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/ContextProvidersPass.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Adds the context provider service IDs to the context manager.
+ */
+class ContextProvidersPass implements CompilerPassInterface {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Passes the service IDs of all context providers to the context repository.
+   */
+  public function process(ContainerBuilder $container) {
+    $context_providers = [];
+    foreach (array_keys($container->findTaggedServiceIds('context_provider')) as $id) {
+      $context_providers[] = $id;
+    }
+
+    $definition = $container->getDefinition('context.repository');
+    $definition->addArgument($context_providers);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php
new file mode 100644
index 0000000..e956946
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\ContextProviderInterface.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+/**
+ * Defines an interface for providing plugin contexts.
+ *
+ * Implementations only need to deal with unqualified context IDs so they only
+ * need to be unique in the context of a given service provider.
+ *
+ * The fully qualified context ID then includes the service ID:
+ * @service_id:unqualified_context_id.
+ */
+interface ContextProviderInterface {
+
+  /**
+   * Provides the runtime contexts.
+   *
+   * For context-aware plugins to function correctly, all of the contexts that
+   * they require must be populated with values. So this method must set a value
+   * for each context that it adds. For example:
+   *
+   * @code
+   *   // Determine a specific node to pass as context to a block.
+   *   $node = ...
+   *
+   *   // Set that specific node as the value of the 'node' context.
+   *   $context = new Context(new ContextDefinition('entity:node'));
+   *   $context->setContextValue($node);
+   *   return ['node' => $context];
+   * @endcode
+   *
+   * @param string[] $unqualified_context_ids
+   *   The requested context IDs. The context provider must only return contexts
+   *   for those IDs.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
+   *   The determined contexts, keyed by the unqualified context_id.
+   *
+   * @see static::getAvailableContexts()
+   */
+  public function getRuntimeContexts(array $unqualified_context_ids);
+
+  /**
+   * Provides the available contexts.
+   *
+   * When a context aware plugin is being configured, the configuration UI must
+   * know which named contexts are potentially available, but does not care
+   * about the value, since the value can be different for each request, and
+   * might not be available at all during the configuration UI's request.
+   *
+   * For example:
+   * @code
+   *   // During configuration, there is no specific node to pass as context.
+   *   // However, inform the system that a context named 'node' is
+   *   // available, and provide its definition, so that context aware plugins
+   *   // can be configured to use it. When the plugin, for example a block,
+   *   // needs to evaluate the context, the value of this context will be
+   *   // supplied by getRuntimeContexts().
+   *   $context = new Context(new ContextDefinition('entity:node'));
+   *   return ['node' => $context];
+   * @endcode
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
+   *   All available contexts keyed by the unqualified context ID.
+   *
+   * @see static::getRuntimeContext()
+   */
+  public function getAvailableContexts();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextRepositoryInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextRepositoryInterface.php
new file mode 100644
index 0000000..d769aca
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextRepositoryInterface.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\ContextRepositoryInterface.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+/**
+ * Offers a global context repository.
+ *
+ * It provides a ist of all available contexts, which is mostly useful
+ * for configuration on forms, as well as a method to get the concrete
+ * contexts with their values, given a list of fully qualified context IDs.
+ */
+interface ContextRepositoryInterface {
+
+  /**
+   * Gets runtime context values for the given context IDs.
+   *
+   * @param string[] $context_ids
+   *   Fully qualified context IDs. These must be in the
+   *   @service_id:unqualified_context_id format, for example
+   *   node.node_route_context:node.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
+   *   The determined contexts.
+   */
+  public function getRuntimeContexts(array $context_ids);
+
+  /**
+   * Gets all available contexts for the purposes of configuration.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
+   *   All available contexts.
+   */
+  public function getAvailableContexts();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/LazyContextRepository.php b/core/lib/Drupal/Core/Plugin/Context/LazyContextRepository.php
new file mode 100644
index 0000000..d5a439e
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/LazyContextRepository.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\LazyContextRepository.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a context repository which uses context providers in the container.
+ */
+class LazyContextRepository implements ContextRepositoryInterface {
+
+  /**
+   * The set of available context providers service IDs.
+   *
+   * @var string[]
+   *   Context provider service IDs.
+   */
+  protected $contextProviderServiceIDs = [];
+
+  /**
+   * The service container.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
+   * The statically cached contexts.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextInterface[]
+   */
+  protected $contexts = [];
+
+  /**
+   * Constructs a LazyContextRepository object.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The current service container.
+   * @param string[] $context_provider_service_ids
+   *   The set of the available context provider service IDs.
+   */
+  public function __construct(ContainerInterface $container, array $context_provider_service_ids) {
+    $this->container = $container;
+    $this->contextProviderServiceIDs = $context_provider_service_ids;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRuntimeContexts(array $context_ids) {
+    $contexts = [];
+
+    // Create a map of context providers (service IDs) to unqualified context
+    // IDs.
+    $context_ids_by_service = [];
+    foreach ($context_ids as $id) {
+      if (isset($this->contexts[$id])) {
+        $contexts[$id] = $this->contexts[$id];
+        continue;
+      }
+      // The IDs have been passed in @{service_id}:{unqualified_context_id} format.
+      if ($id[0] === '@' && strpos($id, ':') !== FALSE) {
+        list($service_id, $unqualified_context_id) = explode(':', $id, 2);
+        // Remove the leading '@'.
+        $service_id = substr($service_id, 1);
+      }
+      else {
+        throw new \InvalidArgumentException('You must provide the context IDs in the @{service_id}:{unqualified_context_id} format.');
+      }
+      $context_ids_by_service[$service_id][] = $unqualified_context_id;
+    }
+
+    // Iterate over all missing context providers (services), gather the
+    // runtime contexts and assign them as requested.
+    foreach ($context_ids_by_service as $service_id => $unqualified_context_ids) {
+      $contexts_by_service = $this->container->get($service_id)->getRuntimeContexts($unqualified_context_ids);
+
+      $wanted_contexts = array_intersect_key($contexts_by_service, array_flip($unqualified_context_ids));
+      foreach ($wanted_contexts as $unqualified_context_id => $context) {
+        $context_id = '@' . $service_id . ':' . $unqualified_context_id;
+        $this->contexts[$context_id] = $contexts[$context_id] = $context;
+      }
+    }
+
+    return $contexts;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAvailableContexts() {
+    $contexts = [];
+    foreach ($this->contextProviderServiceIDs as $service_id) {
+      $contexts_by_service = $this->container->get($service_id)->getAvailableContexts();
+      foreach ($contexts_by_service as $unqualified_context_id => $context) {
+        $context_id = '@' . $service_id . ':' . $unqualified_context_id;
+        $contexts[$context_id] = $context;
+      }
+    }
+
+    return $contexts;
+  }
+
+}
diff --git a/core/modules/block/block.install b/core/modules/block/block.install
new file mode 100644
index 0000000..645f357
--- /dev/null
+++ b/core/modules/block/block.install
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Block module.
+ */
+
+/**
+ * @addtogroup updates-8.0.x-beta
+ * @{
+ */
+
+/**
+ * Update block visibility context mapping.
+ */
+function block_update_8001() {
+  $context_service_id_map = [
+    'language' => 'language.current_language_context',
+    'node' => 'node.node_route_context',
+    'user' => 'user.current_user_context',
+  ];
+
+  $config_factory = \Drupal::configFactory();
+  $message = NULL;
+  foreach ($config_factory->listAll('block.block.') as $block_config_name) {
+    // We deal with config objects directly to avoid the config entity API.
+    $block = $config_factory->getEditable($block_config_name);
+    $disable_block = FALSE;
+    if ($visibility = $block->get('visibility')) {
+      foreach ($visibility as &$condition) {
+        foreach ($condition['context_mapping'] as $key => $context) {
+          if (!isset($context_service_id_map[$key])) {
+            // Skip unknown entries, so the update process itself runs through
+            // and users can fix their block placements manually OR
+            // alternatively contrib modules can run their own update functions
+            // to update mappings that they provide.
+            $message = \Drupal::translation()->translate('Encountered an unexpected context mapping key, one or more mappings could not be updated. Please manually review your block placements.');
+            $disable_block = TRUE;
+            continue;
+          }
+          $new_context_id = explode('.', $context, 2);
+          $condition['context_mapping'][$key] = '@' . $context_service_id_map[$key] . ':' . $new_context_id[1];
+        }
+      }
+      if ($disable_block) {
+        // This block will have an invalid context mapping service and must be
+        // disabled to avoid the 'You must provide the context IDs in the
+        // @{service_id}:{unqualified_context_id} format' exception.
+        $block->set('status', FALSE);
+      }
+      $block->set('visibility', $visibility);
+    }
+
+    $block->save();
+  }
+  return $message;
+}
+
+/**
+ * @} End of "addtogroup updates-8.0.x-beta".
+ */
diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml
index df4d0d7..fe20e4a 100644
--- a/core/modules/block/block.services.yml
+++ b/core/modules/block/block.services.yml
@@ -7,21 +7,6 @@ services:
     class: Drupal\block\EventSubscriber\BlockPageDisplayVariantSubscriber
     tags:
       - { name: event_subscriber }
-  block.current_user_context:
-    class: Drupal\block\EventSubscriber\CurrentUserContext
-    arguments: ['@current_user', '@entity.manager']
-    tags:
-      - { name: 'event_subscriber' }
-  block.current_language_context:
-    class: Drupal\block\EventSubscriber\CurrentLanguageContext
-    arguments: ['@language_manager']
-    tags:
-      - { name: 'event_subscriber' }
-  block.node_route_context:
-    class: Drupal\block\EventSubscriber\NodeRouteContext
-    arguments: ['@current_route_match']
-    tags:
-      - { name: 'event_subscriber' }
   block.repository:
     class: Drupal\block\BlockRepository
     arguments: ['@entity.manager', '@theme.manager', '@context.handler']
diff --git a/core/modules/block/src/BlockAccessControlHandler.php b/core/modules/block/src/BlockAccessControlHandler.php
index 473e58e..b7fea60 100644
--- a/core/modules/block/src/BlockAccessControlHandler.php
+++ b/core/modules/block/src/BlockAccessControlHandler.php
@@ -18,6 +18,7 @@
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Executable\ExecutableManagerInterface;
 use Drupal\Core\Plugin\Context\ContextHandlerInterface;
+use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -46,13 +47,21 @@ class BlockAccessControlHandler extends EntityAccessControlHandler implements En
   protected $contextHandler;
 
   /**
+   * The context manager service.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
+   */
+  protected $contextRepository;
+
+  /**
    * {@inheritdoc}
    */
   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
     return new static(
       $entity_type,
       $container->get('plugin.manager.condition'),
-      $container->get('context.handler')
+      $container->get('context.handler'),
+      $container->get('context.repository')
     );
   }
 
@@ -65,11 +74,14 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
    *   The ConditionManager for checking visibility of blocks.
    * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
    *   The ContextHandler for applying contexts to conditions properly.
+   * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
+   *   The lazy context repository service.
    */
-  public function __construct(EntityTypeInterface $entity_type, ExecutableManagerInterface $manager, ContextHandlerInterface $context_handler) {
+  public function __construct(EntityTypeInterface $entity_type, ExecutableManagerInterface $manager, ContextHandlerInterface $context_handler, ContextRepositoryInterface $context_repository ) {
     parent::__construct($entity_type);
     $this->manager = $manager;
     $this->contextHandler = $context_handler;
+    $this->contextRepository = $context_repository;
   }
 
 
@@ -87,12 +99,12 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
       return AccessResult::forbidden()->cacheUntilEntityChanges($entity);
     }
     else {
-      $contexts = $entity->getContexts();
       $conditions = [];
       $missing_context = FALSE;
       foreach ($entity->getVisibilityConditions() as $condition_id => $condition) {
         if ($condition instanceof ContextAwarePluginInterface) {
           try {
+            $contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping()));
             $this->contextHandler->applyContextMapping($condition, $contexts);
           }
           catch (ContextException $e) {
diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php
index 07da65b..3c07e17 100644
--- a/core/modules/block/src/BlockForm.php
+++ b/core/modules/block/src/BlockForm.php
@@ -7,8 +7,6 @@
 
 namespace Drupal\block;
 
-use Drupal\block\Event\BlockContextEvent;
-use Drupal\block\Event\BlockEvents;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\EntityManagerInterface;
@@ -18,8 +16,8 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * Provides form for block instance forms.
@@ -69,23 +67,30 @@ class BlockForm extends EntityForm {
   protected $themeHandler;
 
   /**
+   * The context manager service.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
+   */
+  protected $contextRepository;
+
+  /**
    * Constructs a BlockForm object.
    *
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    * @param \Drupal\Core\Executable\ExecutableManagerInterface $manager
    *   The ConditionManager for building the visibility UI.
-   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
-   *   The EventDispatcher for gathering administrative contexts.
+   * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
+   *   The lazy context repository service.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language
    *   The language manager.
    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
    * The theme handler.
    */
-  public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, EventDispatcherInterface $dispatcher, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler) {
+  public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, ContextRepositoryInterface $context_repository, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler) {
     $this->storage = $entity_manager->getStorage('block');
     $this->manager = $manager;
-    $this->dispatcher = $dispatcher;
+    $this->contextRepository = $context_repository;
     $this->language = $language;
     $this->themeHandler = $theme_handler;
   }
@@ -97,7 +102,7 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity.manager'),
       $container->get('plugin.manager.condition'),
-      $container->get('event_dispatcher'),
+      $container->get('context.repository'),
       $container->get('language_manager'),
       $container->get('theme_handler')
     );
@@ -117,7 +122,7 @@ public function form(array $form, FormStateInterface $form_state) {
 
     // Store the gathered contexts in the form state for other objects to use
     // during form building.
-    $form_state->setTemporaryValue('gathered_contexts', $this->dispatcher->dispatch(BlockEvents::ADMINISTRATIVE_CONTEXT, new BlockContextEvent())->getContexts());
+    $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts());
 
     $form['#tree'] = TRUE;
     $form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state);
diff --git a/core/modules/block/src/BlockInterface.php b/core/modules/block/src/BlockInterface.php
index 39ad9a9..8955be1 100644
--- a/core/modules/block/src/BlockInterface.php
+++ b/core/modules/block/src/BlockInterface.php
@@ -96,24 +96,6 @@ public function getVisibilityCondition($instance_id);
   public function setVisibilityConfig($instance_id, array $configuration);
 
   /**
-   * Get all available contexts.
-   *
-   * @return \Drupal\Component\Plugin\Context\ContextInterface[]
-   *   An array of set contexts, keyed by context name.
-   */
-  public function getContexts();
-
-  /**
-   * Set the contexts that are available for use within the block entity.
-   *
-   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
-   *   An array of contexts to set on the block.
-   *
-   * @return $this
-   */
-  public function setContexts(array $contexts);
-
-  /**
    * Returns the weight of this block (used for sorting).
    *
    * @return int
diff --git a/core/modules/block/src/BlockRepository.php b/core/modules/block/src/BlockRepository.php
index bc5eb8c..9d56fde 100644
--- a/core/modules/block/src/BlockRepository.php
+++ b/core/modules/block/src/BlockRepository.php
@@ -50,7 +50,7 @@ public function __construct(EntityManagerInterface $entity_manager, ThemeManager
   /**
    * {@inheritdoc}
    */
-  public function getVisibleBlocksPerRegion(array $contexts, array &$cacheable_metadata = []) {
+  public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []) {
     $active_theme = $this->themeManager->getActiveTheme();
     // Build an array of the region names in the right order.
     $empty = array_fill_keys($active_theme->getRegions(), array());
@@ -58,7 +58,6 @@ public function getVisibleBlocksPerRegion(array $contexts, array &$cacheable_met
     $full = array();
     foreach ($this->blockStorage->loadByProperties(array('theme' => $active_theme->getName())) as $block_id => $block) {
       /** @var \Drupal\block\BlockInterface $block */
-      $block->setContexts($contexts);
       $access = $block->access('view', NULL, TRUE);
       $region = $block->getRegion();
       if (!isset($cacheable_metadata[$region])) {
diff --git a/core/modules/block/src/BlockRepositoryInterface.php b/core/modules/block/src/BlockRepositoryInterface.php
index 00eb5e4..59dc260 100644
--- a/core/modules/block/src/BlockRepositoryInterface.php
+++ b/core/modules/block/src/BlockRepositoryInterface.php
@@ -12,8 +12,6 @@
   /**
    * Returns an array of regions and their block entities.
    *
-   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
-   *   An array of contexts to set on the blocks.
    * @param \Drupal\Core\Cache\CacheableMetadata[] $cacheable_metadata
    *   (optional) List of CacheableMetadata objects, keyed by region. This is
    *   by reference and is used to pass this information back to the caller.
@@ -22,6 +20,6 @@
    *   The array is first keyed by region machine name, with the values
    *   containing an array keyed by block ID, with block entities as the values.
    */
-  public function getVisibleBlocksPerRegion(array $contexts, array &$cacheable_metadata = []);
+  public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []);
 
 }
diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php
index ace30b4..d2b3031 100644
--- a/core/modules/block/src/Entity/Block.php
+++ b/core/modules/block/src/Entity/Block.php
@@ -253,21 +253,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
   /**
    * {@inheritdoc}
    */
-  public function setContexts(array $contexts) {
-    $this->contexts = $contexts;
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getContexts() {
-    return $this->contexts;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getVisibility() {
     return $this->getVisibilityConditions()->getConfiguration();
   }
diff --git a/core/modules/block/src/Event/BlockContextEvent.php b/core/modules/block/src/Event/BlockContextEvent.php
deleted file mode 100644
index 99b0bbd..0000000
--- a/core/modules/block/src/Event/BlockContextEvent.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\block\Event\BlockContextEvent.
- */
-
-namespace Drupal\block\Event;
-
-use Drupal\Core\Plugin\Context\ContextInterface;
-use Symfony\Component\EventDispatcher\Event;
-
-/**
- * Event subscribers can add context to be used by the block and its conditions.
- *
- * @see \Drupal\block\Event\BlockEvents::ACTIVE_CONTEXT
- * @see \Drupal\block\Event\BlockEvents::ADMINISTRATIVE_CONTEXT
- */
-class BlockContextEvent extends Event {
-
-  /**
-   * The array of available contexts for blocks.
-   *
-   * @var array
-   */
-  protected $contexts = [];
-
-  /**
-   * Sets the context object for a given name.
-   *
-   * @param string $name
-   *   The name to store the context object under.
-   * @param \Drupal\Core\Plugin\Context\ContextInterface $context
-   *   The context object to set.
-   *
-   * @return $this
-   */
-  public function setContext($name, ContextInterface $context) {
-    $this->contexts[$name] = $context;
-    return $this;
-  }
-
-  /**
-   * Returns the context objects.
-   *
-   * @return \Drupal\Component\Plugin\Context\ContextInterface[]
-   *   An array of contexts that have been provided.
-   */
-  public function getContexts() {
-    return $this->contexts;
-  }
-
-}
diff --git a/core/modules/block/src/Event/BlockEvents.php b/core/modules/block/src/Event/BlockEvents.php
deleted file mode 100644
index 118f2f8..0000000
--- a/core/modules/block/src/Event/BlockEvents.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\block\Event\BlockEvents.
- */
-
-namespace Drupal\block\Event;
-
-/**
- * Defines events for the Block module.
- */
-final class BlockEvents {
-
-  /**
-   * Name of the event when gathering condition context for a block plugin.
-   *
-   * This event allows you to provide additional context that can be used by
-   * a condition plugin in order to determine the visibility of a block. The
-   * event listener method receives a \Drupal\block\Event\BlockContextEvent
-   * instance. Generally any new context is paired with a new condition plugin
-   * that interprets the provided context and allows the block system to
-   * determine whether or not the block should be displayed.
-   *
-   * @Event
-   *
-   * @see \Drupal\Core\Block\BlockBase::getConditionContexts()
-   * @see \Drupal\block\Event\BlockContextEvent
-   * @see \Drupal\block\EventSubscriber\NodeRouteContext::onBlockActiveContext()
-   * @see \Drupal\Core\Condition\ConditionInterface
-   */
-  const ACTIVE_CONTEXT = 'block.active_context';
-
-  /**
-   * Name of the event when gathering contexts for plugin configuration.
-   *
-   * This event allows you to provide information about your context to the
-   * administration UI without having to provide a value for the context. For
-   * example, during configuration there is no specific node to pass as context.
-   * However, we still need to inform the system that a context named 'node' is
-   * available and provide a definition so that blocks can be configured to use
-   * it.
-   *
-   * The event listener method receives a \Drupal\block\Event\BlockContextEvent
-   * instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\block\BlockForm::form()
-   * @see \Drupal\block\Event\BlockContextEvent
-   * @see \Drupal\block\EventSubscriber\NodeRouteContext::onBlockAdministrativeContext()
-   */
-  const ADMINISTRATIVE_CONTEXT = 'block.administrative_context';
-
-}
diff --git a/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php b/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php
deleted file mode 100644
index 60158db..0000000
--- a/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\block\EventSubscriber\BlockContextSubscriberBase.
- */
-
-namespace Drupal\block\EventSubscriber;
-
-use Drupal\block\Event\BlockContextEvent;
-use Drupal\block\Event\BlockEvents;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-/**
- * Provides a base class for block context subscribers.
- */
-abstract class BlockContextSubscriberBase implements EventSubscriberInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getSubscribedEvents() {
-    $events[BlockEvents::ACTIVE_CONTEXT][] = 'onBlockActiveContext';
-    $events[BlockEvents::ADMINISTRATIVE_CONTEXT][] = 'onBlockAdministrativeContext';
-    return $events;
-  }
-
-  /**
-   * Determines the available run-time contexts.
-   *
-   * For blocks to render correctly, all of the contexts that they require
-   * must be populated with values. So this method must set a value for each
-   * context that it adds. For example:
-   * @code
-   *   // Determine a specific node to pass as context to blocks.
-   *   $node = ...
-   *
-   *   // Set that specific node as the value of the 'node' context.
-   *   $context = new Context(new ContextDefinition('entity:node'));
-   *   $context->setContextValue($node);
-   *   $event->setContext('node.node', $context);
-   * @endcode
-   *
-   * @param \Drupal\block\Event\BlockContextEvent $event
-   *   The Event to which to register available contexts.
-   */
-  abstract public function onBlockActiveContext(BlockContextEvent $event);
-
-  /**
-   * Determines the available configuration-time contexts.
-   *
-   * When a block is being configured, the configuration UI must know which
-   * named contexts are potentially available, but does not care about the
-   * value, since the value can be different for each request, and might not
-   * be available at all during the configuration UI's request.
-   *
-   * For example:
-   * @code
-   *   // During configuration, there is no specific node to pass as context.
-   *   // However, inform the system that a context named 'node.node' is
-   *   // available, and provide its definition, so that blocks can be
-   *   // configured to use it. When the block is rendered, the value of this
-   *   // context will be supplied by onBlockActiveContext().
-   *   $context = new Context(new ContextDefinition('entity:node'));
-   *   $event->setContext('node.node', $context);
-   * @endcode
-   *
-   * @param \Drupal\block\Event\BlockContextEvent $event
-   *   The Event to which to register available contexts.
-   *
-   * @see static::onBlockActiveContext()
-   */
-  abstract public function onBlockAdministrativeContext(BlockContextEvent $event);
-
-}
diff --git a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php
index f5caee4..38b3dab 100644
--- a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php
+++ b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php
@@ -8,18 +8,15 @@
 namespace Drupal\block\Plugin\DisplayVariant;
 
 use Drupal\block\BlockRepositoryInterface;
-use Drupal\block\Event\BlockContextEvent;
-use Drupal\block\Event\BlockEvents;
 use Drupal\Core\Block\MainContentBlockPluginInterface;
 use Drupal\Core\Block\MessagesBlockPluginInterface;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Display\PageVariantInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityViewBuilderInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Display\VariantBase;
+use Drupal\Core\Theme\ThemeManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * Provides a page display variant that decorates the main content with blocks.
@@ -80,16 +77,13 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
    *   The block repository.
    * @param \Drupal\Core\Entity\EntityViewBuilderInterface $block_view_builder
    *   The block view builder.
-   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
-   *   The event dispatcher.
    * @param string[] $block_list_cache_tags
    *   The Block entity type list cache tags.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, EventDispatcherInterface $dispatcher, array $block_list_cache_tags) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, array $block_list_cache_tags) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->blockRepository = $block_repository;
     $this->blockViewBuilder = $block_view_builder;
-    $this->dispatcher = $dispatcher;
     $this->blockListCacheTags = $block_list_cache_tags;
   }
 
@@ -103,7 +97,6 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_definition,
       $container->get('block.repository'),
       $container->get('entity.manager')->getViewBuilder('block'),
-      $container->get('event_dispatcher'),
       $container->get('entity.manager')->getDefinition('block')->getListCacheTags()
     );
   }
@@ -129,10 +122,9 @@ public function build() {
         'tags' => $this->blockListCacheTags,
       ],
     ];
-    $contexts = $this->getActiveBlockContexts();
     // Load all region content assigned via blocks.
     $cacheable_metadata_list = [];
-    foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts, $cacheable_metadata_list) as $region => $blocks) {
+    foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata_list) as $region => $blocks) {
       /** @var $blocks \Drupal\block\BlockInterface[] */
       foreach ($blocks as $key => $block) {
         $block_plugin = $block->getPlugin();
@@ -188,14 +180,4 @@ public function build() {
     return $build;
   }
 
-  /**
-   * Returns an array of context objects to set on the blocks.
-   *
-   * @return \Drupal\Component\Plugin\Context\ContextInterface[]
-   *   An array of contexts to set on the blocks.
-   */
-  protected function getActiveBlockContexts() {
-    return $this->dispatcher->dispatch(BlockEvents::ACTIVE_CONTEXT, new BlockContextEvent())->getContexts();
-  }
-
 }
diff --git a/core/modules/block/src/Tests/BlockLanguageTest.php b/core/modules/block/src/Tests/BlockLanguageTest.php
index 56ee191..265524f 100644
--- a/core/modules/block/src/Tests/BlockLanguageTest.php
+++ b/core/modules/block/src/Tests/BlockLanguageTest.php
@@ -90,7 +90,7 @@ public function testLanguageBlockVisibilityLanguageDelete() {
           'langcodes' => array(
             'fr' => 'fr',
           ),
-          'context_mapping' => ['language' => 'language.language_interface'],
+          'context_mapping' => ['language' => '@language.current_language_context:language_interface'],
         ),
       ),
     );
@@ -142,7 +142,7 @@ public function testMultipleLanguageTypes() {
     // Enable a standard block and set visibility to French only.
     $block_id = strtolower($this->randomMachineName(8));
     $edit = [
-      'visibility[language][context_mapping][language]' => 'language.language_interface',
+      'visibility[language][context_mapping][language]' => '@language.current_language_context:language_interface',
       'visibility[language][langcodes][fr]' => TRUE,
       'id' => $block_id,
       'region' => 'sidebar_first',
@@ -168,7 +168,7 @@ public function testMultipleLanguageTypes() {
 
     // Change visibility to now depend on content language for this block.
     $edit = [
-      'visibility[language][context_mapping][language]' => 'language.language_content'
+      'visibility[language][context_mapping][language]' => '@language.current_language_context:language_content'
     ];
     $this->drupalPostForm('admin/structure/block/manage/' . $block_id, $edit, t('Save block'));
 
diff --git a/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php b/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php
new file mode 100644
index 0000000..1a89537
--- /dev/null
+++ b/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\Tests\Update\BlockContextMappingUpdateTest.
+ */
+
+namespace Drupal\block\Tests\Update;
+
+use Drupal\node\Entity\Node;
+use Drupal\system\Tests\Update\UpdatePathTestBase;
+
+/**
+ * Tests the upgrade path for block context mapping renames.
+ *
+ * @see https://www.drupal.org/node/2354889
+ *
+ * @group Update
+ */
+class BlockContextMappingUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+      __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php',
+    ];
+    parent::setUp();
+  }
+
+  /**
+   * Tests that block context mapping are updated properly.
+   */
+  public function testUpdateHookN() {
+    $this->runUpdates();
+    $this->assertText(t('Encountered an unexpected context mapping key, one or more mappings could not be updated. Please manually review your block placements.'));
+
+    // Disable maintenance mode.
+    \Drupal::state()->set('system.maintenance_mode', FALSE);
+
+    // The block that we are testing has the following visibility rules:
+    // - only visible on node pages
+    // - only visible to authenticated users.
+    $block_title = 'Test for 2354889';
+
+    // Create two nodes, a page and an article.
+    $page = Node::create([
+      'type' => 'page',
+      'title' => 'Page node',
+    ]);
+    $page->save();
+
+    $article = Node::create([
+      'type' => 'article',
+      'title' => 'Article node',
+    ]);
+    $article->save();
+
+    // Check that the block appears only on Page nodes for authenticated users.
+    $this->drupalGet('node/' . $page->id());
+    $this->assertRaw($block_title, 'Test block is visible on a Page node as an authenticated user.');
+
+    $this->drupalGet('node/' . $article->id());
+    $this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an authenticated user.');
+
+    $this->drupalLogout();
+
+    // Check that the block does not appear on any page for anonymous users.
+    $this->drupalGet('node/' . $page->id());
+    $this->assertNoRaw($block_title, 'Test block is not visible on a Page node as an anonymous user.');
+
+    $this->drupalGet('node/' . $article->id());
+    $this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an anonymous user.');
+  }
+
+}
diff --git a/core/modules/block/tests/src/Unit/BlockFormTest.php b/core/modules/block/tests/src/Unit/BlockFormTest.php
index 8acff20..fbb4096 100644
--- a/core/modules/block/tests/src/Unit/BlockFormTest.php
+++ b/core/modules/block/tests/src/Unit/BlockFormTest.php
@@ -31,13 +31,6 @@ class BlockFormTest extends UnitTestCase {
   protected $storage;
 
   /**
-   * The event dispatcher service.
-   *
-   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $dispatcher;
-
-  /**
    * The language manager service.
    *
    * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -60,6 +53,13 @@ class BlockFormTest extends UnitTestCase {
   protected $entityManager;
 
   /**
+   * The mocked context repository.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $contextRepository;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
@@ -67,7 +67,7 @@ protected function setUp() {
 
     $this->conditionManager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface');
     $this->language = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
-    $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
+    $this->contextRepository = $this->getMock('Drupal\Core\Plugin\Context\ContextRepositoryInterface');
 
     $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
     $this->storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface');
@@ -104,7 +104,7 @@ public function testGetUniqueMachineName() {
       ->method('getQuery')
       ->will($this->returnValue($query));
 
-    $block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->dispatcher, $this->language, $this->themeHandler);
+    $block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->contextRepository, $this->language, $this->themeHandler);
 
     // Ensure that the block with just one other instance gets the next available
     // name suggestion.
diff --git a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php
index d77d64e..d837848 100644
--- a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php
+++ b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php
@@ -60,7 +60,7 @@ protected function setUp() {
       ]);
 
     $theme_manager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface');
-    $theme_manager->expects($this->once())
+    $theme_manager->expects($this->atLeastOnce())
       ->method('getActiveTheme')
       ->will($this->returnValue($active_theme));
 
@@ -86,9 +86,6 @@ public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expec
     foreach ($blocks_config as $block_id => $block_config) {
       $block = $this->getMock('Drupal\block\BlockInterface');
       $block->expects($this->once())
-        ->method('setContexts')
-        ->willReturnSelf();
-      $block->expects($this->once())
         ->method('access')
         ->will($this->returnValue($block_config[0]));
       $block->expects($block_config[0] ? $this->atLeastOnce() : $this->never())
@@ -102,7 +99,8 @@ public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expec
       ->with(['theme' => $this->theme])
       ->willReturn($blocks);
     $result = [];
-    foreach ($this->blockRepository->getVisibleBlocksPerRegion([]) as $region => $resulting_blocks) {
+    $cacheable_metadata = [];
+    foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata) as $region => $resulting_blocks) {
       $result[$region] = [];
       foreach ($resulting_blocks as $plugin_id => $block) {
         $result[$region][] = $plugin_id;
@@ -148,9 +146,6 @@ public function providerBlocksConfig() {
   public function testGetVisibleBlocksPerRegionWithContext() {
     $block = $this->getMock('Drupal\block\BlockInterface');
     $block->expects($this->once())
-      ->method('setContexts')
-      ->willReturnSelf();
-    $block->expects($this->once())
       ->method('access')
       ->willReturn(AccessResult::allowed()->addCacheTags(['config:block.block.block_id']));
     $block->expects($this->once())
@@ -158,14 +153,13 @@ public function testGetVisibleBlocksPerRegionWithContext() {
       ->willReturn('top');
     $blocks['block_id'] = $block;
 
-    $contexts = [];
     $this->blockStorage->expects($this->once())
       ->method('loadByProperties')
       ->with(['theme' => $this->theme])
       ->willReturn($blocks);
     $result = [];
     $cacheable_metadata = [];
-    foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts, $cacheable_metadata) as $region => $resulting_blocks) {
+    foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata) as $region => $resulting_blocks) {
       $result[$region] = [];
       foreach ($resulting_blocks as $plugin_id => $block) {
         $result[$region][] = $plugin_id;
diff --git a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php
index 5438a99..3f4e3e2 100644
--- a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php
+++ b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\DependencyInjection\Container;
+use Drupal\Core\Theme\ActiveTheme;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -32,13 +33,6 @@ class BlockPageVariantTest extends UnitTestCase {
   protected $blockViewBuilder;
 
   /**
-   * The event dispatcher.
-   *
-   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $dispatcher;
-
-  /**
    * The plugin context handler.
    *
    * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -71,12 +65,9 @@ public function setUpDisplayVariant($configuration = array(), $definition = arra
 
     $this->blockRepository = $this->getMock('Drupal\block\BlockRepositoryInterface');
     $this->blockViewBuilder = $this->getMock('Drupal\Core\Entity\EntityViewBuilderInterface');
-    $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
-    $this->dispatcher->expects($this->any())
-      ->method('dispatch')
-      ->willReturnArgument(1);
+
     return $this->getMockBuilder('Drupal\block\Plugin\DisplayVariant\BlockPageVariant')
-      ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->dispatcher, ['config:block_list']))
+      ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, ['config:block_list']))
       ->setMethods(array('getRegionNames'))
       ->getMock();
   }
@@ -217,23 +208,26 @@ public function testBuild(array $blocks_config, $visible_block_count, array $exp
     $messages_block_plugin = $this->getMock('Drupal\Core\Block\MessagesBlockPluginInterface');
     foreach ($blocks_config as $block_id => $block_config) {
       $block = $this->getMock('Drupal\block\BlockInterface');
+      $block->expects($this->any())
+        ->method('getContexts')
+        ->willReturn([]);
       $block->expects($this->atLeastOnce())
         ->method('getPlugin')
         ->willReturn($block_config[1] ? $main_content_block_plugin : ($block_config[2] ? $messages_block_plugin : $block_plugin));
       $blocks[$block_config[0]][$block_id] = $block;
     }
-
     $this->blockViewBuilder->expects($this->exactly($visible_block_count))
       ->method('view')
       ->will($this->returnValue(array()));
     $this->blockRepository->expects($this->once())
       ->method('getVisibleBlocksPerRegion')
-      ->willReturnCallback(function ($contexts, &$cacheable_metadata) use ($blocks) {
+      ->willReturnCallback(function (&$cacheable_metadata) use ($blocks) {
         $cacheable_metadata['top'] = (new CacheableMetadata())->addCacheTags(['route']);
         return $blocks;
       });
 
-    $this->assertSame($expected_render_array, $display_variant->build());
+    $value = $display_variant->build();
+    $this->assertSame($expected_render_array, $value);
   }
 
   /**
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index b256da6..11e3692 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -345,7 +345,7 @@ function comment_entity_storage_load($entities, $entity_type) {
   if (!\Drupal::entityManager()->getDefinition($entity_type)->isSubclassOf('Drupal\Core\Entity\FieldableEntityInterface')) {
     return;
   }
-  if (!\Drupal::service('comment.manager')->getFields($entity_type)) {
+  if (!\Drupal::hasService('comment.manager') || !\Drupal::service('comment.manager')->getFields($entity_type)) {
     // Do not query database when entity has no comment fields.
     return;
   }
diff --git a/core/modules/language/language.services.yml b/core/modules/language/language.services.yml
index b9de79a..5a2d52c 100644
--- a/core/modules/language/language.services.yml
+++ b/core/modules/language/language.services.yml
@@ -24,3 +24,8 @@ services:
     tags:
       - { name: paramconverter }
     lazy: true
+  language.current_language_context:
+    class: Drupal\language\ContextProvider\CurrentLanguageContext
+    arguments: ['@language_manager']
+    tags:
+      - { name: 'context_provider' }
diff --git a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php b/core/modules/language/src/ContextProvider/CurrentLanguageContext.php
similarity index 66%
rename from core/modules/block/src/EventSubscriber/CurrentLanguageContext.php
rename to core/modules/language/src/ContextProvider/CurrentLanguageContext.php
index 7b1a206..bf59ad2 100644
--- a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php
+++ b/core/modules/language/src/ContextProvider/CurrentLanguageContext.php
@@ -2,22 +2,22 @@
 
 /**
  * @file
- * Contains \Drupal\block\EventSubscriber\CurrentLanguageContext.
+ * Contains \Drupal\language\ContextProvider\CurrentLanguageContext.
  */
 
-namespace Drupal\block\EventSubscriber;
+namespace Drupal\language\ContextProvider;
 
-use Drupal\block\Event\BlockContextEvent;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\Context\Context;
 use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Context\ContextProviderInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
  * Sets the current language as a context.
  */
-class CurrentLanguageContext extends BlockContextSubscriberBase {
+class CurrentLanguageContext implements ContextProviderInterface {
 
   use StringTranslationTrait;
 
@@ -41,10 +41,20 @@ public function __construct(LanguageManagerInterface $language_manager) {
   /**
    * {@inheritdoc}
    */
-  public function onBlockActiveContext(BlockContextEvent $event) {
+  public function getRuntimeContexts(array $unqualified_context_ids) {
     // Add a context for each language type.
     $language_types = $this->languageManager->getLanguageTypes();
     $info = $this->languageManager->getDefinedLanguageTypesInfo();
+
+    if ($unqualified_context_ids) {
+      foreach ($unqualified_context_ids as $unqualified_context_id) {
+        if (array_search($unqualified_context_id, $language_types) === FALSE) {
+          unset($language_types[$unqualified_context_id]);
+        }
+      }
+    }
+
+    $result = [];
     foreach ($language_types as $type_key) {
       if (isset($info[$type_key]['name'])) {
         $context = new Context(new ContextDefinition('language', $info[$type_key]['name']));
@@ -54,16 +64,18 @@ public function onBlockActiveContext(BlockContextEvent $event) {
         $cacheability->setCacheContexts(['languages:' . $type_key]);
         $context->addCacheableDependency($cacheability);
 
-        $event->setContext('language.' . $type_key, $context);
+        $result[$type_key] = $context;
       }
     }
+
+    return $result;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function onBlockAdministrativeContext(BlockContextEvent $event) {
-    $this->onBlockActiveContext($event);
+  public function getAvailableContexts() {
+    return $this->getRuntimeContexts([]);
   }
 
 }
diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml
index 637cdee..2ff32c3 100644
--- a/core/modules/node/node.services.yml
+++ b/core/modules/node/node.services.yml
@@ -45,3 +45,8 @@ services:
     arguments: ['@current_user']
     tags:
       - { name: cache.context }
+  node.node_route_context:
+    class: Drupal\node\ContextProvider\NodeRouteContext
+    arguments: ['@current_route_match']
+    tags:
+      - { name: 'context_provider' }
diff --git a/core/modules/block/src/EventSubscriber/NodeRouteContext.php b/core/modules/node/src/ContextProvider/NodeRouteContext.php
similarity index 75%
rename from core/modules/block/src/EventSubscriber/NodeRouteContext.php
rename to core/modules/node/src/ContextProvider/NodeRouteContext.php
index 89d24f5..e6f5781 100644
--- a/core/modules/block/src/EventSubscriber/NodeRouteContext.php
+++ b/core/modules/node/src/ContextProvider/NodeRouteContext.php
@@ -2,22 +2,22 @@
 
 /**
  * @file
- * Contains \Drupal\block\EventSubscriber\NodeRouteContext.
+ * Contains \Drupal\node\ContextProvider\NodeRouteContext.
  */
 
-namespace Drupal\block\EventSubscriber;
+namespace Drupal\node\ContextProvider;
 
-use Drupal\block\Event\BlockContextEvent;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Plugin\Context\Context;
 use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Context\ContextProviderInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\node\Entity\Node;
 
 /**
  * Sets the current node as a context on node routes.
  */
-class NodeRouteContext extends BlockContextSubscriberBase {
+class NodeRouteContext implements ContextProviderInterface {
 
   /**
    * The route match object.
@@ -39,13 +39,13 @@ public function __construct(RouteMatchInterface $route_match) {
   /**
    * {@inheritdoc}
    */
-  public function onBlockActiveContext(BlockContextEvent $event) {
+  public function getRuntimeContexts(array $unqualified_context_ids) {
+    $result = [];
     $context = new Context(new ContextDefinition('entity:node', NULL, FALSE));
     if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) {
       if ($node = $this->routeMatch->getParameter('node')) {
         $context->setContextValue($node);
       }
-      $event->setContext('node.node', $context);
     }
     elseif ($this->routeMatch->getRouteName() == 'node.add') {
       $node_type = $this->routeMatch->getParameter('node_type');
@@ -54,15 +54,17 @@ public function onBlockActiveContext(BlockContextEvent $event) {
     $cacheability = new CacheableMetadata();
     $cacheability->setCacheContexts(['route']);
     $context->addCacheableDependency($cacheability);
-    $event->setContext('node.node', $context);
+    $result['node'] = $context;
+
+    return $result;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function onBlockAdministrativeContext(BlockContextEvent $event) {
+  public function getAvailableContexts() {
     $context = new Context(new ContextDefinition('entity:node'));
-    $event->setContext('node.node', $context);
+    return ['node' => $context];
   }
 
 }
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php b/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php
new file mode 100644
index 0000000..0ad77c5
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @file
+ * Contains database additions to drupal-8.bare.standard.php.gz for testing the
+ * upgrade path of https://www.drupal.org/node/2354889.
+ */
+
+use Drupal\Core\Database\Database;
+
+$connection = Database::getConnection();
+
+// Structure of a custom block with visibility settings.
+$block_configs = [[
+  'uuid' => '9d204071-a923-4707-8200-c298a540fb0c',
+  'langcode' => 'en',
+  'status' => TRUE,
+  'dependencies' => [
+    'content' => [
+      'block_content:basic:c1895145-893e-460b-a24e-78cd2cefbb1f',
+    ],
+    'module' => [
+      'block_content',
+      'node',
+      'user',
+    ],
+    'theme' => [
+      'bartik',
+    ],
+  ],
+  'id' => 'testfor2354889',
+  'theme' => 'bartik',
+  'region' => 'content',
+  'weight' => -6,
+  'provider' => NULL,
+  'plugin' => 'block_content:c1895145-893e-460b-a24e-78cd2cefbb1f',
+  'settings' => [
+    'id' => 'block_content:c1895145-893e-460b-a24e-78cd2cefbb1f',
+    'label' => 'Test for 2354889',
+    'provider' => 'block_content',
+    'label_display' => 'visible',
+    'cache' => [
+      'max_age' => -1,
+    ],
+    'status' => TRUE,
+    'info' => '',
+    'view_mode' => 'full',
+  ],
+  'visibility' => [
+    'node_type' => [
+      'id' => 'node_type',
+      'bundles' => [
+        'page' => 'page',
+      ],
+      'negate' => FALSE,
+      // Context that will be tested.
+      'context_mapping' => [
+        'node' => 'node.node',
+      ],
+    ],
+    'user_role' => [
+      'id' => 'user_role',
+      'roles' => [
+        'authenticated' => 'authenticated',
+      ],
+      'negate' => FALSE,
+      // Context that will be tested.
+      'context_mapping' => [
+        'user' => 'user.current_user',
+      ],
+    ],
+  ],
+],[
+  'uuid' => 'C499B41D-035E-432E-9462-36410C43C49F',
+  'langcode' => 'en',
+  'status' => TRUE,
+  'dependencies' => [
+    'module' => [
+      'search',
+    ],
+    'theme' => [
+      'bartik',
+    ],
+  ],
+  'id' => 'secondtestfor2354889',
+  'theme' => 'bartik',
+  'region' => 'sidebar_first',
+  'weight' => -6,
+  'provider' => NULL,
+  'plugin' => 'search_form_block',
+  'settings' => [
+    'id' => 'search_form_block',
+    'label' => 'Search',
+    'provider' => 'search',
+    'label_display' => 'visible',
+    'cache' => [
+      'max_age' => -1,
+    ],
+    'status' => TRUE,
+  ],
+  'visibility' => [],
+],[
+  'uuid' => '4558907D-2918-48FE-B56F-8A007B5FBDD5',
+  'langcode' => 'en',
+  'status' => TRUE,
+  'dependencies' => [
+    'module' => [
+      'user',
+    ],
+    'theme' => [
+      'bartik',
+    ],
+  ],
+  'id' => 'thirdtestfor2354889',
+  'theme' => 'bartik',
+  'region' => 'sidebar_first',
+  'weight' => -6,
+  'provider' => NULL,
+  'plugin' => 'user_login_block',
+  'settings' => [
+    'id' => 'user_login_block',
+    'label' => 'User login',
+    'provider' => 'user',
+    'label_display' => 'visible',
+    'cache' => [
+      'max_age' => -1,
+    ],
+    'status' => TRUE,
+  ],
+  'visibility' => [
+    'node_type' => [
+      'id' => 'node_type',
+      'bundles' => [
+        'page' => 'page',
+      ],
+      'negate' => FALSE,
+      // Non-existent mapping that will be tested.
+      'context_mapping' => [
+        'baloney' => 'baloney.spam',
+      ],
+    ],
+  ],
+]];
+
+foreach ($block_configs as $block_config) {
+  $connection->insert('config')
+    ->fields(array(
+      'collection',
+      'name',
+      'data',
+    ))
+    ->values(array(
+      'collection' => '',
+      'name' => 'block.block.' . $block_config['id'],
+      'data' => serialize($block_config),
+    ))
+    ->execute();
+}
diff --git a/core/modules/block/src/EventSubscriber/CurrentUserContext.php b/core/modules/user/src/ContextProvider/CurrentUserContext.php
similarity index 76%
rename from core/modules/block/src/EventSubscriber/CurrentUserContext.php
rename to core/modules/user/src/ContextProvider/CurrentUserContext.php
index 194a252..1a0f26c 100644
--- a/core/modules/block/src/EventSubscriber/CurrentUserContext.php
+++ b/core/modules/user/src/ContextProvider/CurrentUserContext.php
@@ -2,23 +2,23 @@
 
 /**
  * @file
- * Contains \Drupal\block\EventSubscriber\CurrentUserContext.
+ * Contains \Drupal\user\ContextProvider\CurrentUserContext.
  */
 
-namespace Drupal\block\EventSubscriber;
+namespace Drupal\user\ContextProvider;
 
-use Drupal\block\Event\BlockContextEvent;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Plugin\Context\Context;
 use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Context\ContextProviderInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
  * Sets the current user as a context.
  */
-class CurrentUserContext extends BlockContextSubscriberBase {
+class CurrentUserContext implements ContextProviderInterface {
 
   use StringTranslationTrait;
 
@@ -52,7 +52,7 @@ public function __construct(AccountInterface $account, EntityManagerInterface $e
   /**
    * {@inheritdoc}
    */
-  public function onBlockActiveContext(BlockContextEvent $event) {
+  public function getRuntimeContexts(array $unqualified_context_ids) {
     $current_user = $this->userStorage->load($this->account->id());
 
     $context = new Context(new ContextDefinition('entity:user', $this->t('Current user')));
@@ -60,14 +60,19 @@ public function onBlockActiveContext(BlockContextEvent $event) {
     $cacheability = new CacheableMetadata();
     $cacheability->setCacheContexts(['user']);
     $context->addCacheableDependency($cacheability);
-    $event->setContext('user.current_user', $context);
+
+    $result = [
+      'current_user' => $context,
+    ];
+
+    return $result;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function onBlockAdministrativeContext(BlockContextEvent $event) {
-    $this->onBlockActiveContext($event);
+  public function getAvailableContexts() {
+    return $this->getRuntimeContexts([]);
   }
 
 }
diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml
index be4e679..c273cb3 100644
--- a/core/modules/user/user.services.yml
+++ b/core/modules/user/user.services.yml
@@ -61,6 +61,11 @@ services:
   user.permissions:
     class: Drupal\user\PermissionHandler
     arguments: ['@module_handler', '@string_translation', '@controller_resolver']
+  user.current_user_context:
+    class: Drupal\user\ContextProvider\CurrentUserContext
+    arguments: ['@current_user', '@entity.manager']
+    tags:
+      - { name: 'context_provider' }
 
 parameters:
   user.tempstore.expire: 604800
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Context/LazyContextRepositoryTest.php b/core/tests/Drupal/Tests/Core/Plugin/Context/LazyContextRepositoryTest.php
new file mode 100644
index 0000000..878d7b8
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Plugin/Context/LazyContextRepositoryTest.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Plugin\Context\LazyContextRepositoryTest.
+ */
+
+namespace Drupal\Tests\Core\Plugin\Context;
+
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Context\LazyContextRepository;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Plugin\Context\LazyContextRepository
+ * @group context
+ */
+class LazyContextRepositoryTest extends UnitTestCase {
+
+  /**
+   * The container.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerBuilder
+   */
+  protected $container;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->container = new ContainerBuilder();
+  }
+
+  /**
+   * @covers ::getRuntimeContexts
+   */
+  public function testGetRuntimeContextsSingle() {
+    $contexts = $this->setupContextAndProvider('test_provider', ['test_context']);
+
+    $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']);
+    $run_time_contexts = $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context']);
+    $this->assertEquals(['@test_provider:test_context' => $contexts[0]], $run_time_contexts);
+  }
+
+  /**
+   * @covers ::getRuntimeContexts
+   */
+  public function testGetRuntimeMultipleContextsPerService() {
+    $contexts = $this->setupContextAndProvider('test_provider', ['test_context0', 'test_context1']);
+
+    $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']);
+    $run_time_contexts = $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context0', '@test_provider:test_context1']);
+    $this->assertEquals(['@test_provider:test_context0' => $contexts[0], '@test_provider:test_context1' => $contexts[1]], $run_time_contexts);
+  }
+
+  /**
+   * @covers ::getRuntimeContexts
+   */
+  public function testGetRuntimeMultipleContextProviders() {
+    $contexts0 = $this->setupContextAndProvider('test_provider', ['test_context0', 'test_context1'], ['test_context0']);
+    $contexts1 = $this->setupContextAndProvider('test_provider2', ['test1_context0', 'test1_context1'], ['test1_context0']);
+
+    $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']);
+    $run_time_contexts = $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context0', '@test_provider2:test1_context0']);
+    $this->assertEquals(['@test_provider:test_context0' => $contexts0[0], '@test_provider2:test1_context0' => $contexts1[1]], $run_time_contexts);
+  }
+
+  /**
+   * @covers ::getRuntimeContexts
+   * @expectedException \InvalidArgumentException
+   * @expectedExceptionMessage You must provide the context IDs in the @{service_id}:{unqualified_context_id} format.
+   */
+  public function testInvalidContextId() {
+    $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']);
+    $lazy_context_repository->getRuntimeContexts(['test_context', '@test_provider:test_context1']);
+  }
+
+  /**
+   * @covers ::getRuntimeContexts
+   */
+  public function testGetRuntimeStaticCache() {
+    $context0 = new Context(new ContextDefinition('example'));
+    $context1 = new Context(new ContextDefinition('example'));
+
+    $context_provider = $this->prophesize('\Drupal\Core\Plugin\Context\ContextProviderInterface');
+    $context_provider->getRuntimeContexts(['test_context0', 'test_context1'])
+      ->shouldBeCalledTimes(1)
+      ->willReturn(['test_context0' => $context0, 'test_context1' => $context1]);
+    $context_provider = $context_provider->reveal();
+    $this->container->set('test_provider', $context_provider);
+
+    $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider']);
+    $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context0', '@test_provider:test_context1']);
+    $lazy_context_repository->getRuntimeContexts(['@test_provider:test_context0', '@test_provider:test_context1']);
+  }
+
+  /**
+   * @covers ::getAvailableContexts
+   */
+  public function testGetAvailableContexts() {
+    $contexts0 = $this->setupContextAndProvider('test_provider0', ['test0_context0', 'test0_context1']);
+    $contexts1 = $this->setupContextAndProvider('test_provider1', ['test1_context0', 'test1_context1']);
+
+    $lazy_context_repository = new LazyContextRepository($this->container, ['test_provider0', 'test_provider1']);
+    $contexts = $lazy_context_repository->getAvailableContexts();
+
+    $this->assertEquals([
+      '@test_provider0:test0_context0' => $contexts0[0],
+      '@test_provider0:test0_context1' => $contexts0[1],
+      '@test_provider1:test1_context0' => $contexts1[0],
+      '@test_provider1:test1_context1' => $contexts1[1],
+    ], $contexts);
+
+  }
+
+  /**
+   * Sets up contexts and context providers.
+   *
+   * @param string $service_id
+   *   The service ID of the service provider.
+   * @param string[] $unqualified_context_ids
+   *   An array of context slot names.
+   * @param string[] $expected_unqualified_context_ids
+   *   The expected context slotes passed to getRuntimeContexts.
+   *
+   * @return array
+   *   An array of set up contexts.
+   */
+  protected function setupContextAndProvider($service_id, array $unqualified_context_ids, array $expected_unqualified_context_ids = []) {
+    $contexts = [];
+    for ($i = 0; $i < count($unqualified_context_ids); $i++) {
+      $contexts[] = new Context(new ContextDefinition('example'));
+    }
+
+    $expected_unqualified_context_ids = $expected_unqualified_context_ids ?: $unqualified_context_ids;
+
+    $context_provider = $this->prophesize('\Drupal\Core\Plugin\Context\ContextProviderInterface');
+    $context_provider->getRuntimeContexts($expected_unqualified_context_ids)
+      ->willReturn(array_combine($unqualified_context_ids, $contexts));
+    $context_provider->getAvailableContexts()
+      ->willReturn(array_combine($unqualified_context_ids, $contexts));
+    $context_provider = $context_provider->reveal();
+    $this->container->set($service_id, $context_provider);
+
+    return $contexts;
+  }
+
+}
