diff --git a/core/core.services.yml b/core/core.services.yml
index d0facb4..9466f95 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -115,6 +115,9 @@ services:
   config.typed:
     class: Drupal\Core\Config\TypedConfigManager
     arguments: ['@config.storage', '@config.storage.schema', '@cache.discovery']
+  context.handler:
+    class: Drupal\Core\Plugin\Context\ContextHandler
+    arguments: ['@typed_data_manager']
   cron:
     class: Drupal\Core\Cron
     arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user', '@session_manager']
diff --git a/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php b/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php
new file mode 100644
index 0000000..f5723e8
--- /dev/null
+++ b/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Condition\ConditionAccessResolverTrait.
+ */
+
+namespace Drupal\Core\Condition;
+
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Component\Plugin\ContextAwarePluginInterface;
+use Drupal\Component\Plugin\Exception\PluginException;
+
+/**
+ * Resolves a set of conditions.
+ */
+trait ConditionAccessResolverTrait {
+
+  /**
+   * Wraps the context handler.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface
+   */
+  protected function contextHandler() {
+    return \Drupal::service('context.handler');
+  }
+
+  /**
+   * Resolves the given conditions based on the condition logic ('and'/'or').
+   *
+   * @param \Drupal\Core\Condition\ConditionInterface[] $conditions
+   *   A set of conditions.
+   * @param string $condition_logic
+   *   The logic used to compute access, either 'and' or 'or'.
+   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
+   *   (optional) An array of contexts to set on the conditions.
+   *
+   * @return bool
+   *   Whether these conditions grant or deny access.
+   */
+  protected function resolveConditions($conditions, $condition_logic, $contexts = array()) {
+    foreach ($conditions as $condition) {
+      if ($condition instanceof ContextAwarePluginInterface) {
+        $assignments = array();
+        if ($condition instanceof ConfigurablePluginInterface) {
+          $configuration = $condition->getConfiguration();
+          if (isset($configuration['context_assignments'])) {
+            $assignments = array_flip($configuration['context_assignments']);
+          }
+        }
+        $this->contextHandler()->applyContextMapping($condition, $contexts, $assignments);
+      }
+
+      try {
+        $pass = $condition->execute();
+      }
+      catch (PluginException $e) {
+        // If a condition is missing context, consider that a fail.
+        $pass = FALSE;
+      }
+
+      // If a condition fails and all conditions were required, deny access.
+      if (!$pass && $condition_logic == 'and') {
+        return FALSE;
+      }
+      // If a condition passes and one condition was required, grant access.
+      elseif ($pass && $condition_logic == 'or') {
+        return TRUE;
+      }
+    }
+
+    // If no conditions passed and one condition was required, deny access.
+    return $condition_logic == 'and';
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Condition/ConditionManager.php b/core/lib/Drupal/Core/Condition/ConditionManager.php
index 4b4e659..ca24add 100644
--- a/core/lib/Drupal/Core/Condition/ConditionManager.php
+++ b/core/lib/Drupal/Core/Condition/ConditionManager.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Executable\ExecutableInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
 use Drupal\Core\Plugin\DefaultPluginManager;
 
 /**
@@ -19,6 +20,8 @@
  */
 class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface {
 
+  use ContextAwarePluginManagerTrait;
+
   /**
    * Constructs a ConditionManager object.
    *
diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginBag.php b/core/lib/Drupal/Core/Condition/ConditionPluginBag.php
new file mode 100644
index 0000000..b15f302
--- /dev/null
+++ b/core/lib/Drupal/Core/Condition/ConditionPluginBag.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Condition\ConditionPluginBag.
+ */
+
+namespace Drupal\Core\Condition;
+
+use Drupal\Core\Plugin\DefaultPluginBag;
+
+/**
+ * Provides a collection of condition plugins.
+ */
+class ConditionPluginBag extends DefaultPluginBag {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\Core\Condition\ConditionInterface
+   */
+  public function &get($instance_id) {
+    return parent::get($instance_id);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextAwarePluginManagerInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextAwarePluginManagerInterface.php
new file mode 100644
index 0000000..7bf137d
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextAwarePluginManagerInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+
+/**
+ * Provides an interface for plugin managers that support context-aware plugins.
+ */
+interface ContextAwarePluginManagerInterface extends PluginManagerInterface {
+
+  /**
+   * Determines plugins whose constraints are satisfied by a set of contexts.
+   *
+   * @todo Use context definition objects after https://drupal.org/node/2281635.
+   *
+   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
+   *   An array of contexts.
+   *
+   * @return array
+   *   An array of plugin definitions.
+   */
+  public function getDefinitionsForContexts(array $contexts = array());
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextAwarePluginManagerTrait.php b/core/lib/Drupal/Core/Plugin/Context/ContextAwarePluginManagerTrait.php
new file mode 100644
index 0000000..407b789
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextAwarePluginManagerTrait.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+/**
+ * Provides a trait for plugin managers that support context-aware plugins.
+ */
+trait ContextAwarePluginManagerTrait {
+
+  /**
+   * Wraps the context handler.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface
+   */
+  protected function contextHandler() {
+    return \Drupal::service('context.handler');
+  }
+
+  /**
+   * See \Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface::getDefinitionsForContexts().
+   */
+  public function getDefinitionsForContexts(array $contexts = array()) {
+    return $this->contextHandler()->filterPluginDefinitionsByContexts($contexts, $this->getDefinitions());
+  }
+
+  /**
+   * See \Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions().
+   */
+  abstract public function getDefinitions();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
new file mode 100644
index 0000000..68fbda3
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\ContextHandler.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+use Drupal\Component\Plugin\Context\ContextInterface;
+use Drupal\Component\Plugin\ContextAwarePluginInterface;
+use Drupal\Component\Plugin\Exception\ContextException;
+use Drupal\Component\Utility\String;
+use Drupal\Core\TypedData\DataDefinitionInterface;
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\TypedData\TypedDataManager;
+
+/**
+ * Provides methods to handle sets of contexts.
+ */
+class ContextHandler implements ContextHandlerInterface {
+
+  /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager
+   */
+  protected $typedDataManager;
+
+  /**
+   * Constructs a new ContextHandler.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataManager $typed_data
+   *   The typed data manager.
+   */
+  public function __construct(TypedDataManager $typed_data) {
+    $this->typedDataManager = $typed_data;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function filterPluginDefinitionsByContexts(array $contexts, array $definitions) {
+    return array_filter($definitions, function ($plugin_definition) use ($contexts) {
+      // If this plugin doesn't need any context, it is available to use.
+      if (!isset($plugin_definition['context'])) {
+        return TRUE;
+      }
+
+      // Build an array of requirements out of the contexts specified by the
+      // plugin definition.
+      $requirements = array();
+      foreach ($plugin_definition['context'] as $context_id => $plugin_context) {
+        $definition = $this->typedDataManager->getDefinition($plugin_context['type']);
+        $definition['type'] = $plugin_context['type'];
+
+        // If the plugin specifies additional constraints, add them to the
+        // constraints defined by the plugin type.
+        if (isset($plugin_context['constraints'])) {
+          // Ensure the array exists before adding in constraints.
+          if (!isset($definition['constraints'])) {
+            $definition['constraints'] = array();
+          }
+
+          $definition['constraints'] += $plugin_context['constraints'];
+        }
+
+        // Assume the requirement is required if unspecified.
+        if (!isset($definition['required'])) {
+          $definition['required'] = TRUE;
+        }
+
+        // @todo Use context definition objects after
+        //   https://drupal.org/node/2281635.
+        $requirements[$context_id] = new DataDefinition($definition);
+      }
+
+      // Check the set of contexts against the requirements.
+      return $this->checkRequirements($contexts, $requirements);
+    });
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkRequirements(array $contexts, array $requirements) {
+    foreach ($requirements as $requirement) {
+      if ($requirement->isRequired() && !$this->getMatchingContexts($contexts, $requirement)) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMatchingContexts(array $contexts, DataDefinitionInterface $definition) {
+    return array_filter($contexts, function (ContextInterface $context) use ($definition) {
+      // @todo getContextDefinition() should return a DataDefinitionInterface.
+      $context_definition = new DataDefinition($context->getContextDefinition());
+
+      // If the data types do not match, this context is invalid.
+      if ($definition->getDataType() != $context_definition->getDataType()) {
+        return FALSE;
+      }
+
+      // If any constraint does not match, this context is invalid.
+      // @todo This is too restrictive, consider only relying on data types.
+      foreach ($definition->getConstraints() as $constraint_name => $constraint) {
+        if ($context_definition->getConstraint($constraint_name) != $constraint) {
+          return FALSE;
+        }
+      }
+
+      // All contexts with matching data type and contexts are valid.
+      return TRUE;
+    });
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) {
+    $plugin_contexts = $plugin->getContextDefinitions();
+    // Loop through each context and set it on the plugin if it matches one of
+    // the contexts expected by the plugin.
+    foreach ($contexts as $name => $context) {
+      // If this context was given a specific name, use that.
+      $assigned_name = isset($mappings[$name]) ? $mappings[$name] : $name;
+      if (isset($plugin_contexts[$assigned_name])) {
+        // This assignment has been used, remove it.
+        unset($mappings[$name]);
+        $plugin->setContextValue($assigned_name, $context->getContextValue());
+      }
+    }
+
+    // If there are any mappings that were not satisfied, throw an exception.
+    if (!empty($mappings)) {
+      throw new ContextException(String::format('Assigned contexts were not satisfied: @mappings', array('@mappings' => implode(',', $mappings))));
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php
new file mode 100644
index 0000000..b39df2c
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\ContextHandlerInterface.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+use Drupal\Component\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\TypedData\DataDefinitionInterface;
+
+/**
+ * Provides an interface for handling sets of contexts.
+ */
+interface ContextHandlerInterface {
+
+  /**
+   * Determines plugins whose constraints are satisfied by a set of contexts.
+   *
+   * @todo Use context definition objects after https://drupal.org/node/2281635.
+   *
+   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
+   *   An array of contexts.
+   * @param array $definitions .
+   *   An array of plugin definitions.
+   *
+   * @return array
+   *   An array of plugin definitions.
+   */
+  public function filterPluginDefinitionsByContexts(array $contexts, array $definitions);
+
+  /**
+   * Checks a set of requirements against a set of contexts.
+   *
+   * @todo Use context definition objects after https://drupal.org/node/2281635.
+   *
+   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
+   *   An array of available contexts.
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $requirements
+   *   An array of requirements.
+   *
+   * @return bool
+   *   TRUE if all of the requirements are satisfied by the context, FALSE
+   *   otherwise.
+   */
+  public function checkRequirements(array $contexts, array $requirements);
+
+  /**
+   * Determines which contexts satisfy the constraints of a given definition.
+   *
+   * @todo Use context definition objects after https://drupal.org/node/2281635.
+   *
+   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
+   *   An array of contexts.
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
+   *   The definition to satisfy.
+   *
+   * @return \Drupal\Component\Plugin\Context\ContextInterface[]
+   *   An array of matching contexts.
+   */
+  public function getMatchingContexts(array $contexts, DataDefinitionInterface $definition);
+
+  /**
+   * Prepares a plugin for evaluation.
+   *
+   * @param \Drupal\Component\Plugin\ContextAwarePluginInterface $plugin
+   *   A plugin about to be evaluated.
+   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
+   *   An array of contexts to set on the plugin. They will only be set if they
+   *   match the plugin's context definitions.
+   * @param array $mappings
+   *   (optional) A mapping of the expected assignment names to their context
+   *   names. For example, if one of the $contexts is named 'entity', but the
+   *   plugin expects a context named 'node', then this map would contain
+   *   'entity' => 'node'.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\ContextException
+   *   Thrown when a context assignment was not satisfied.
+   */
+  public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array());
+
+}
diff --git a/core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php b/core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php
index 5fdc33a..4381b6d 100644
--- a/core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php
+++ b/core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php
@@ -100,7 +100,7 @@ public function defaultConfiguration() {
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     // Only grant access to users with the 'access news feeds' permission.
     return $account->hasPermission('access news feeds');
   }
diff --git a/core/modules/block/src/BlockBase.php b/core/modules/block/src/BlockBase.php
index df3507c..8810620 100644
--- a/core/modules/block/src/BlockBase.php
+++ b/core/modules/block/src/BlockBase.php
@@ -7,6 +7,9 @@
 
 namespace Drupal\block;
 
+use Drupal\Core\Condition\ConditionAccessResolverTrait;
+use Drupal\Core\Condition\ConditionPluginBag;
+use Drupal\Core\Plugin\Context\Context;
 use Drupal\Core\Plugin\ContextAwarePluginBase;
 use Drupal\block\BlockInterface;
 use Drupal\Component\Utility\Unicode;
@@ -15,6 +18,8 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\user\Entity\User;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 
 /**
  * Defines a base block implementation that most blocks plugins will extend.
@@ -27,6 +32,22 @@
  */
 abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface {
 
+  use ConditionAccessResolverTrait;
+
+  /**
+   * The condition plugin bag.
+   *
+   * @var \Drupal\Core\Condition\ConditionPluginBag
+   */
+  protected $conditionBag;
+
+  /**
+   * The condition plugin manager.
+   *
+   * @var \Drupal\Core\Executable\ExecutableManagerInterface
+   */
+  protected $conditionPluginManager;
+
   /**
    * {@inheritdoc}
    */
@@ -54,7 +75,9 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
    * {@inheritdoc}
    */
   public function getConfiguration() {
-    return $this->configuration;
+    return array(
+      'visibility' => $this->getConditions()->getConfiguration(),
+    ) + $this->configuration;
   }
 
   /**
@@ -75,6 +98,20 @@ public function setConfiguration(array $configuration) {
    *   An associative array with the default configuration.
    */
   protected function baseConfigurationDefaults() {
+    // @todo Allow list of conditions to be configured.
+    $visibility = array();
+    if (\Drupal::moduleHandler()->moduleExists('system')) {
+      $visibility['request_path'] = array('id' => 'request_path');
+    }
+    if (\Drupal::moduleHandler()->moduleExists('user')) {
+      $visibility['user_role'] = array('id' => 'user_role');
+    }
+    if (\Drupal::moduleHandler()->moduleExists('node')) {
+      $visibility['node_type'] = array('id' => 'node_type');
+    }
+    if (\Drupal::moduleHandler()->moduleExists('language')) {
+      $visibility['language'] = array('id' => 'language');
+    }
     return array(
       'id' => $this->getPluginId(),
       'label' => '',
@@ -84,6 +121,7 @@ protected function baseConfigurationDefaults() {
         'max_age' => 0,
         'contexts' => array(),
       ),
+      'visibility' => $visibility,
     );
   }
 
@@ -112,6 +150,57 @@ public function calculateDependencies() {
    * {@inheritdoc}
    */
   public function access(AccountInterface $account) {
+    if ($this->resolveConditions($this->getConditions(), 'and', $this->getConditionContexts()) === FALSE) {
+      return FALSE;
+    }
+    return $this->blockAccess($account);
+  }
+
+  /**
+   * Gets the values for all defined contexts.
+   *
+   * @return \Drupal\Component\Plugin\Context\ContextInterface[]
+   *   An array of set contexts, keyed by context name.
+   */
+  protected function getConditionContexts() {
+    // @todo Move these hardcoded contexts into an event flow.
+    $context = new Context(array(
+      'type' => 'entity:user',
+      'label' => $this->t('Current user'),
+    ));
+    $context->setContextValue(User::load(\Drupal::currentUser()->id()));
+    $contexts['user'] = $context;
+
+    // @todo Use RouteMatch after https://drupal.org/node/2238217.
+    $request = \Drupal::request();
+    if ($request->attributes->has(RouteObjectInterface::ROUTE_OBJECT) && $route_contexts = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getOption('parameters')) {
+      foreach ($route_contexts as $route_context_name => $route_context) {
+        // Skip the block itself.
+        if ($route_context_name == 'block') {
+          continue;
+        }
+        $context = new Context($route_context);
+        if ($request->attributes->has($route_context_name)) {
+          $context->setContextValue($request->attributes->get($route_context_name));
+        }
+        $contexts[$route_context_name] = $context;
+      }
+    }
+    return $contexts;
+  }
+
+  /**
+   * Indicates whether the block should be shown.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user session for which to check access.
+   *
+   * @return bool
+   *   TRUE if the block should be shown, or FALSE otherwise.
+   *
+   * @see self::access()
+   */
+  protected function blockAccess(AccountInterface $account) {
     // By default, the block is visible unless user-configured rules indicate
     // that it should be hidden.
     return TRUE;
@@ -197,12 +286,34 @@ public function buildConfigurationForm(array $form, array &$form_state) {
       $form['cache']['contexts']['#description'] .= ' ' . t('This block is <em>always</em> varied by the following contexts: %required-context-list.', array('%required-context-list' => $required_context_list));
     }
 
+    $form['visibility'] = array(
+      '#type' => 'vertical_tabs',
+      '#title' => $this->t('Visibility'),
+    );
+    foreach ($this->getConditions() as $condition_id => $condition) {
+      $form['visibility'][$condition_id] = array(
+        '#type' => 'details',
+        '#title' => $condition->getPluginDefinition()['label'],
+        '#group' => 'visibility',
+      );
+      $form['visibility'][$condition_id]['form'] = $condition->buildConfigurationForm(array(), $form_state);
+      $form['visibility'][$condition_id]['form']['#process'][] = array($this, 'processVisibilityForm');
+    }
     // Add plugin-specific settings for this block type.
     $form += $this->blockForm($form, $form_state);
     return $form;
   }
 
   /**
+   * @todo.
+   */
+  public function processVisibilityForm($element) {
+    // Remove the 'form' from parents.
+    array_pop($element['#parents']);
+    return $element;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function blockForm($form, &$form_state) {
@@ -221,6 +332,15 @@ public function validateConfigurationForm(array &$form, array &$form_state) {
     // Transform the #type = checkboxes value to a numerically indexed array.
     $form_state['values']['cache']['contexts'] = array_values(array_filter($form_state['values']['cache']['contexts']));
 
+    foreach ($this->getConditions() as $condition_id => $condition) {
+      // Allow the condition to validate the form.
+      $condition_values = array(
+        'values' => &$form_state['values']['visibility'][$condition_id],
+      );
+      $condition->validateConfigurationForm($form, $condition_values);
+
+    }
+
     $this->blockValidate($form, $form_state);
   }
 
@@ -244,6 +364,13 @@ public function submitConfigurationForm(array &$form, array &$form_state) {
       $this->configuration['label_display'] = $form_state['values']['label_display'];
       $this->configuration['provider'] = $form_state['values']['provider'];
       $this->configuration['cache'] = $form_state['values']['cache'];
+      foreach ($this->getConditions() as $condition_id => $condition) {
+        // Allow the condition to submit the form.
+        $condition_values = array(
+          'values' => &$form_state['values']['visibility'][$condition_id],
+        );
+        $condition->submitConfigurationForm($form, $condition_values);
+      }
       $this->blockSubmit($form, $form_state);
     }
   }
@@ -332,4 +459,53 @@ public function isCacheable() {
     return $max_age === Cache::PERMANENT || $max_age > 0;
   }
 
+  /**
+   * Gets conditions for this block.
+   *
+   * @return \Drupal\Core\Condition\ConditionInterface[]|\Drupal\Core\Condition\ConditionPluginBag
+   *   An array of configured condition plugins.
+   */
+  public function getConditions() {
+    if (!isset($this->conditionBag)) {
+      $this->conditionBag = new ConditionPluginBag($this->conditionPluginManager(), $this->configuration['visibility']);
+    }
+    return $this->conditionBag;
+  }
+
+  /**
+   * Gets a condition plugin instance.
+   *
+   * @param string $instance_id
+   *   The condition plugin instance ID.
+   *
+   * @return \Drupal\Core\Condition\ConditionInterface
+   *   A condition plugin.
+   */
+  public function getCondition($instance_id) {
+    return $this->getConditions()->get($instance_id);
+  }
+
+  /**
+   * Sets the condition configuration.
+   *
+   * @param string $instance_id
+   *   The condition instance ID.
+   * @param array $config
+   *   The condition configuration.
+   */
+  public function setConditionConfig($instance_id, $config) {
+    $this->getConditions()->setInstanceConfiguration($instance_id, $config);
+  }
+
+  /**
+   * Gets the condition plugin manager.
+   * @return \Drupal\Core\Executable\ExecutableManagerInterface
+   *   The condition plugin manager.
+   */
+  protected function conditionPluginManager() {
+    if (!isset($this->conditionPluginManager)) {
+      $this->conditionPluginManager = \Drupal::service('plugin.manager.condition');
+    }
+    return $this->conditionPluginManager;
+  }
 }
diff --git a/core/modules/block/src/BlockManager.php b/core/modules/block/src/BlockManager.php
index 4a1651b..1db10a6 100644
--- a/core/modules/block/src/BlockManager.php
+++ b/core/modules/block/src/BlockManager.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
 use Drupal\Core\Plugin\DefaultPluginManager;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -24,6 +25,7 @@
 class BlockManager extends DefaultPluginManager implements BlockManagerInterface {
 
   use StringTranslationTrait;
+  use ContextAwarePluginManagerTrait;
 
   /**
    * An array of all available modules and their data.
@@ -106,7 +108,7 @@ public function getCategories() {
    */
   public function getSortedDefinitions() {
     // Sort the plugins first by category, then by label.
-    $definitions = $this->getDefinitions();
+    $definitions = $this->getDefinitionsForContexts();
     uasort($definitions, function ($a, $b) {
       if ($a['category'] != $b['category']) {
         return strnatcasecmp($a['category'], $b['category']);
diff --git a/core/modules/block/src/BlockManagerInterface.php b/core/modules/block/src/BlockManagerInterface.php
index 4973f16..6656004 100644
--- a/core/modules/block/src/BlockManagerInterface.php
+++ b/core/modules/block/src/BlockManagerInterface.php
@@ -7,12 +7,12 @@
 
 namespace Drupal\block;
 
-use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface;
 
 /**
  * Provides an interface for the discovery and instantiation of block plugins.
  */
-interface BlockManagerInterface extends PluginManagerInterface {
+interface BlockManagerInterface extends ContextAwarePluginManagerInterface {
 
   /**
    * Gets the names of all block categories.
diff --git a/core/modules/block/src/Tests/BlockUiTest.php b/core/modules/block/src/Tests/BlockUiTest.php
index d842401..5c2dbfa 100644
--- a/core/modules/block/src/Tests/BlockUiTest.php
+++ b/core/modules/block/src/Tests/BlockUiTest.php
@@ -158,6 +158,24 @@ public function testCandidateBlockList() {
   }
 
   /**
+   * Tests the behavior of context-aware blocks.
+   */
+  public function testContextAwareBlocks() {
+    $arguments = array(
+      ':ul_class' => 'block-list',
+      ':li_class' => 'test-context-aware',
+      ':href' => 'admin/structure/block/add/test_context_aware/stark',
+      ':text' => 'Test context-aware block',
+    );
+
+    $this->drupalGet('admin/structure/block');
+    $elements = $this->xpath('//details[@id="edit-category-block-test"]//ul[contains(@class, :ul_class)]/li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
+    $this->assertTrue(empty($elements), 'The context-aware test block does not appear.');
+    $definition = \Drupal::service('plugin.manager.block')->getDefinition('test_context_aware');
+    $this->assertTrue(!empty($definition), 'The context-aware test block exists.');
+  }
+
+  /**
    * Tests that the BlockForm populates machine name correctly.
    */
   public function testMachineNameSuggestion() {
diff --git a/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestBlockInstantiation.php b/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestBlockInstantiation.php
index 85856a3..9a4db70 100644
--- a/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestBlockInstantiation.php
+++ b/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestBlockInstantiation.php
@@ -32,7 +32,7 @@ public function defaultConfiguration() {
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     return $account->hasPermission('access content');
   }
 
diff --git a/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestContextAwareBlock.php b/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestContextAwareBlock.php
new file mode 100644
index 0000000..ae2550b
--- /dev/null
+++ b/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestContextAwareBlock.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block_test\Plugin\Block\TestContextAwareBlock.
+ */
+
+namespace Drupal\block_test\Plugin\Block;
+
+use Drupal\block\BlockBase;
+
+/**
+ * Provides a context-aware block.
+ *
+ * @Block(
+ *   id = "test_context_aware",
+ *   admin_label = @Translation("Test context-aware block"),
+ *   context = {
+ *     "user" = {
+ *       "type" = "entity:user"
+ *     }
+ *   }
+ * )
+ */
+class TestContextAwareBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    /** @var $user \Drupal\user\UserInterface */
+    $user = $this->getContextValue('user');
+    return array(
+      '#markup' => $user->getUsername(),
+    );
+  }
+
+}
diff --git a/core/modules/block/tests/src/BlockBaseTest.php b/core/modules/block/tests/src/BlockBaseTest.php
index 913130b..682fe09 100644
--- a/core/modules/block/tests/src/BlockBaseTest.php
+++ b/core/modules/block/tests/src/BlockBaseTest.php
@@ -9,7 +9,7 @@
 
 use Drupal\block_test\Plugin\Block\TestBlockInstantiation;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Core\Transliteration\PHPTransliteration;
+use Drupal\Core\Executable\ExecutableManagerInterface;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -33,24 +33,92 @@ public static function getInfo() {
    * @see \Drupal\block\BlockBase::getMachineNameSuggestion().
    */
   public function testGetMachineNameSuggestion() {
+    $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
     $transliteraton = $this->getMockBuilder('Drupal\Core\Transliteration\PHPTransliteration')
       // @todo Inject the module handler into PHPTransliteration.
       ->setMethods(array('readLanguageOverrides'))
       ->getMock();
 
     $container = new ContainerBuilder();
+    $container->set('module_handler', $module_handler);
     $container->set('transliteration', $transliteraton);
     \Drupal::setContainer($container);
 
     $config = array();
-    $definition = array('admin_label' => 'Admin label', 'provider' => 'block_test');
+    $definition = array(
+      'admin_label' => 'Admin label',
+      'provider' => 'block_test',
+    );
     $block_base = new TestBlockInstantiation($config, 'test_block_instantiation', $definition);
     $this->assertEquals('adminlabel', $block_base->getMachineNameSuggestion());
 
     // Test with more unicodes.
-    $definition = array('admin_label' =>'über åwesome', 'provider' => 'block_test');
+    $definition = array(
+      'admin_label' => 'über åwesome',
+      'provider' => 'block_test',
+    );
     $block_base = new TestBlockInstantiation($config, 'test_block_instantiation', $definition);
     $this->assertEquals('uberawesome', $block_base->getMachineNameSuggestion());
   }
 
+  /**
+   * Tests initialising the condition plugins initialisation.
+   */
+  public function testCondtionsBagInitialisation() {
+    $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $module_handler->expects($this->exactly(4))
+      ->method('moduleExists')
+      ->will($this->returnValueMap(array(
+        array('system', TRUE),
+        array('user', TRUE),
+        array('node', TRUE),
+        array('language', TRUE),
+      )));
+    $container = new ContainerBuilder();
+    $container->set('module_handler', $module_handler);
+    \Drupal::setContainer($container);
+    $config = array();
+    $definition = array(
+      'admin_label' => 'Admin label',
+      'provider' => 'block_test',
+    );
+
+    $plugin_manager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface');
+
+    $block_base = new FakeBlock($plugin_manager, $config, 'test_block_instantiation', $definition);
+    $conditions_bag = $block_base->getConditions();
+
+    $this->assertEquals(4, $conditions_bag->count(), "There are 4 condition plugins");
+
+    $instance_id = $this->randomName();
+    $pages = 'node/1';
+    $condition_config = array('id' => 'request_path', 'pages' => $pages);
+    $block_base->setConditionConfig($instance_id, $condition_config);
+
+    $plugin_manager->expects($this->once())->method('createInstance')
+      ->withAnyParameters()->will($this->returnValue('test'));
+
+    $condition = $block_base->getCondition($instance_id);
+
+    $this->assertEquals('test', $condition, "The correct condition is returned.");
+  }
+
+}
+
+/**
+ * A fake Block for testing condition plugin config.
+ */
+class FakeBlock extends TestBlockInstantiation {
+
+  protected $mockPluginManager;
+
+  public function __construct(ExecutableManagerInterface $plugin_manager, array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->mockPluginManager = $plugin_manager;
+  }
+
+  protected function conditionPluginManager() {
+    return $this->mockPluginManager;
+  }
+
 }
diff --git a/core/modules/forum/src/Plugin/Block/ForumBlockBase.php b/core/modules/forum/src/Plugin/Block/ForumBlockBase.php
index d7dfc31..6a5b492 100644
--- a/core/modules/forum/src/Plugin/Block/ForumBlockBase.php
+++ b/core/modules/forum/src/Plugin/Block/ForumBlockBase.php
@@ -56,7 +56,7 @@ public function defaultConfiguration() {
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     return $account->hasPermission('access content');
   }
 
diff --git a/core/modules/language/src/Plugin/Block/LanguageBlock.php b/core/modules/language/src/Plugin/Block/LanguageBlock.php
index 4a6c7bc..42d5bf3 100644
--- a/core/modules/language/src/Plugin/Block/LanguageBlock.php
+++ b/core/modules/language/src/Plugin/Block/LanguageBlock.php
@@ -66,7 +66,7 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     return $this->languageManager->isMultilingual();
   }
 
diff --git a/core/modules/node/src/Plugin/Block/SyndicateBlock.php b/core/modules/node/src/Plugin/Block/SyndicateBlock.php
index efc1bdf..ace314a 100644
--- a/core/modules/node/src/Plugin/Block/SyndicateBlock.php
+++ b/core/modules/node/src/Plugin/Block/SyndicateBlock.php
@@ -33,7 +33,7 @@ public function defaultConfiguration() {
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     return $account->hasPermission('access content');
   }
 
diff --git a/core/modules/node/src/Plugin/Condition/NodeType.php b/core/modules/node/src/Plugin/Condition/NodeType.php
index 74cdf5d..f311827 100644
--- a/core/modules/node/src/Plugin/Condition/NodeType.php
+++ b/core/modules/node/src/Plugin/Condition/NodeType.php
@@ -37,7 +37,6 @@ public function buildConfigurationForm(array $form, array &$form_state) {
       '#title' => t('Node types'),
       '#type' => 'checkboxes',
       '#options' => $options,
-      '#required' => TRUE,
       '#default_value' => isset($this->configuration['bundles']) ? $this->configuration['bundles'] : array(),
     );
     return $form;
@@ -80,6 +79,9 @@ public function summary() {
    * {@inheritdoc}
    */
   public function evaluate() {
+    if (empty($this->configuration['bundles'])) {
+      return TRUE;
+    }
     $node = $this->getContextValue('node');
     return !empty($this->configuration['bundles'][$node->getType()]);
   }
diff --git a/core/modules/search/src/Plugin/Block/SearchBlock.php b/core/modules/search/src/Plugin/Block/SearchBlock.php
index c2e3e51..64b3096 100644
--- a/core/modules/search/src/Plugin/Block/SearchBlock.php
+++ b/core/modules/search/src/Plugin/Block/SearchBlock.php
@@ -26,7 +26,7 @@ class SearchBlock extends BlockBase {
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     return $account->hasPermission('search content');
   }
 
diff --git a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
index 1a19761..3bdacac 100644
--- a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
+++ b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
@@ -55,7 +55,7 @@ public function defaultConfiguration() {
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     if ($account->hasPermission('access content')) {
       $daytop = $this->configuration['top_day_num'];
       if (!$daytop || !($result = statistics_title_list('daycount', $daytop)) || !($this->day_list = node_title_list($result, t("Today's:")))) {
diff --git a/core/modules/system/src/Plugin/Block/SystemHelpBlock.php b/core/modules/system/src/Plugin/Block/SystemHelpBlock.php
index d15cfc6..e99787c 100644
--- a/core/modules/system/src/Plugin/Block/SystemHelpBlock.php
+++ b/core/modules/system/src/Plugin/Block/SystemHelpBlock.php
@@ -78,7 +78,7 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     $this->help = $this->getActiveHelp($this->request);
     return (bool) $this->help;
   }
diff --git a/core/modules/system/src/Plugin/Condition/RequestPath.php b/core/modules/system/src/Plugin/Condition/RequestPath.php
index 7923554..59936e7 100644
--- a/core/modules/system/src/Plugin/Condition/RequestPath.php
+++ b/core/modules/system/src/Plugin/Condition/RequestPath.php
@@ -134,6 +134,9 @@ public function evaluate() {
     // Convert path to lowercase. This allows comparison of the same path
     // with different case. Ex: /Page, /page, /PAGE.
     $pages = Unicode::strtolower($this->configuration['pages']);
+    if (!$pages) {
+      return TRUE;
+    }
 
     $request = $this->requestStack->getCurrentRequest();
     // Compare the lowercase path alias (if any) and internal path.
@@ -142,4 +145,5 @@ public function evaluate() {
 
     return $this->pathMatcher->matchPath($path_alias, $pages) || (($path != $path_alias) && $this->pathMatcher->matchPath($path, $pages));
   }
+
 }
diff --git a/core/modules/system/tests/modules/form_test/src/Plugin/Block/RedirectFormBlock.php b/core/modules/system/tests/modules/form_test/src/Plugin/Block/RedirectFormBlock.php
index 9703f4c..21b79b3 100644
--- a/core/modules/system/tests/modules/form_test/src/Plugin/Block/RedirectFormBlock.php
+++ b/core/modules/system/tests/modules/form_test/src/Plugin/Block/RedirectFormBlock.php
@@ -66,7 +66,7 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     return TRUE;
   }
 
diff --git a/core/modules/user/src/Plugin/Block/UserLoginBlock.php b/core/modules/user/src/Plugin/Block/UserLoginBlock.php
index 3466c21..7fca086 100644
--- a/core/modules/user/src/Plugin/Block/UserLoginBlock.php
+++ b/core/modules/user/src/Plugin/Block/UserLoginBlock.php
@@ -25,7 +25,7 @@ class UserLoginBlock extends BlockBase {
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     $route_name = \Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME);
     return ($account->isAnonymous() && !in_array($route_name, array('user.register', 'user.login', 'user.logout')));
   }
diff --git a/core/modules/user/src/Plugin/Condition/UserRole.php b/core/modules/user/src/Plugin/Condition/UserRole.php
index 8724489..1557b07 100644
--- a/core/modules/user/src/Plugin/Condition/UserRole.php
+++ b/core/modules/user/src/Plugin/Condition/UserRole.php
@@ -35,7 +35,6 @@ public function buildConfigurationForm(array $form, array &$form_state) {
       '#default_value' => $this->configuration['roles'],
       '#options' => array_map('\Drupal\Component\Utility\String::checkPlain', user_role_names()),
       '#description' => $this->t('If you select no roles, the condition will evaluate to TRUE for all users.'),
-      '#required' => TRUE,
     );
     return $form;
   }
@@ -81,8 +80,11 @@ public function summary() {
    * {@inheritdoc}
    */
   public function evaluate() {
+    if (empty($this->configuration['roles'])) {
+      return TRUE;
+    }
     $user = $this->getContextValue('user');
-    return (bool) array_intersect($this->configuration['roles'], $user->getRoles());
+    return array_intersect($this->configuration['roles'], $user->getRoles());
   }
 
 }
diff --git a/core/modules/views/src/Plugin/Block/ViewsBlockBase.php b/core/modules/views/src/Plugin/Block/ViewsBlockBase.php
index b0bfdba..5a63e76 100644
--- a/core/modules/views/src/Plugin/Block/ViewsBlockBase.php
+++ b/core/modules/views/src/Plugin/Block/ViewsBlockBase.php
@@ -91,7 +91,7 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  public function access(AccountInterface $account) {
+  protected function blockAccess(AccountInterface $account) {
     return $this->view->access($this->displayID);
   }
 
diff --git a/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php b/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php
index eb46cb3..34a0b99 100644
--- a/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php
+++ b/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests\Plugin\Block {
 
+use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\Block\ViewsBlock;
 use Drupal\block\Plugin\views\display\Block;
@@ -71,6 +72,10 @@ public static function getInfo() {
    */
   protected function setUp() {
     parent::setUp(); // TODO: Change the autogenerated stub
+    $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $container = new ContainerBuilder();
+    $container->set('module_handler', $module_handler);
+    \Drupal::setContainer($container);
 
     $this->executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
       ->disableOriginalConstructor()
@@ -128,6 +133,10 @@ public function testBuild() {
 
     $definition['provider'] = 'views';
     $plugin = new ViewsBlock($config, $block_id, $definition, $this->executableFactory, $this->storage, $this->account);
+    $reflector = new \ReflectionClass($plugin);
+    $property = $reflector->getProperty('conditionPluginManager');
+    $property->setAccessible(TRUE);
+    $property->setValue($plugin, $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface'));
 
     $this->assertEquals($build, $plugin->build());
   }
@@ -150,6 +159,10 @@ public function testBuildFailed() {
 
     $definition['provider'] = 'views';
     $plugin = new ViewsBlock($config, $block_id, $definition, $this->executableFactory, $this->storage, $this->account);
+    $reflector = new \ReflectionClass($plugin);
+    $property = $reflector->getProperty('conditionPluginManager');
+    $property->setAccessible(TRUE);
+    $property->setValue($plugin, $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface'));
 
     $this->assertEquals(array(), $plugin->build());
   }
diff --git a/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php b/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php
new file mode 100644
index 0000000..dc676c5
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Condition\ConditionAccessResolverTraitTest.
+ */
+
+namespace Drupal\Tests\Core\Condition;
+
+use Drupal\Core\Condition\ConditionAccessResolverTrait;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests resolving a set of conditions.
+ *
+ * @coversDefaultClass \Drupal\Core\Condition\ConditionAccessResolverTrait
+ *
+ * @group Drupal
+ * @group Condition
+ */
+class ConditionAccessResolverTraitTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Tests resolving a set of conditions',
+      'description' => '',
+      'group' => 'Condition',
+    );
+  }
+
+  /**
+   * Tests the resolveConditions() method.
+   *
+   * @covers ::resolveConditions
+   *
+   * @dataProvider providerTestResolveConditions
+   */
+  public function testResolveConditions($conditions, $logic, $expected) {
+    $trait_object = new TestConditionAccessResolverTrait();
+    $this->assertEquals($expected, $trait_object->resolveConditions($conditions, $logic));
+  }
+
+  public function providerTestResolveConditions() {
+    $data = array();
+
+    $condition_true = $this->getMock('Drupal\Core\Condition\ConditionInterface');
+    $condition_true->expects($this->any())
+      ->method('execute')
+      ->will($this->returnValue(TRUE));
+    $condition_false = $this->getMock('Drupal\Core\Condition\ConditionInterface');
+    $condition_false->expects($this->any())
+      ->method('execute')
+      ->will($this->returnValue(FALSE));
+    $condition_exception = $this->getMock('Drupal\Core\Condition\ConditionInterface');
+    $condition_exception->expects($this->any())
+      ->method('execute')
+      ->will($this->throwException(new PluginException()));
+
+    $conditions = array();
+    $data[] = array($conditions, 'and', TRUE);
+    $data[] = array($conditions, 'or', FALSE);
+
+    $conditions = array($condition_false);
+    $data[] = array($conditions, 'or', FALSE);
+    $data[] = array($conditions, 'and', FALSE);
+
+    $conditions = array($condition_true);
+    $data[] = array($conditions, 'or', TRUE);
+    $data[] = array($conditions, 'and', TRUE);
+
+    $conditions = array($condition_true, $condition_false);
+    $data[] = array($conditions, 'or', TRUE);
+    $data[] = array($conditions, 'and', FALSE);
+
+    $conditions = array($condition_exception);
+    $data[] = array($conditions, 'or', FALSE);
+    $data[] = array($conditions, 'and', FALSE);
+
+    $conditions = array($condition_true, $condition_exception);
+    $data[] = array($conditions, 'or', TRUE);
+    $data[] = array($conditions, 'and', FALSE);
+
+    $conditions = array($condition_exception, $condition_true);
+    $data[] = array($conditions, 'or', TRUE);
+    $data[] = array($conditions, 'and', FALSE);
+
+    $conditions = array($condition_false, $condition_exception);
+    $data[] = array($conditions, 'or', FALSE);
+    $data[] = array($conditions, 'and', FALSE);
+
+    $conditions = array($condition_exception, $condition_false);
+    $data[] = array($conditions, 'or', FALSE);
+    $data[] = array($conditions, 'and', FALSE);
+    return $data;
+  }
+
+}
+
+class TestConditionAccessResolverTrait {
+  use \Drupal\Core\Condition\ConditionAccessResolverTrait {
+    resolveConditions as public;
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
new file mode 100644
index 0000000..d162042
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
@@ -0,0 +1,403 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Plugin\ContextHandlerTest.
+ */
+
+namespace Drupal\Tests\Core\Plugin;
+
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Component\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\Plugin\Context\ContextHandler;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the ContextHandler class.
+ *
+ * @coversDefaultClass \Drupal\Core\Plugin\ContextHandler
+ *
+ * @group Drupal
+ * @group Plugin
+ * @group Context
+ */
+class ContextHandlerTest extends UnitTestCase {
+
+  /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $typedDataManager;
+
+  /**
+   * The context handler.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextHandler
+   */
+  protected $contextHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'ContextHandler',
+      'description' => 'Tests the ContextHandler',
+      'group' => 'Plugin API',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @covers ::__construct
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->typedDataManager = $this->getMockBuilder('Drupal\Core\TypedData\TypedDataManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->typedDataManager->expects($this->any())
+      ->method('getDefaultConstraints')
+      ->will($this->returnValue(array()));
+    $this->contextHandler = new ContextHandler($this->typedDataManager);
+
+    $container = new ContainerBuilder();
+    $container->set('typed_data_manager', $this->typedDataManager);
+    \Drupal::setContainer($container);
+  }
+
+  /**
+   * @covers ::checkRequirements
+   *
+   * @dataProvider providerTestCheckRequirements
+   */
+  public function testCheckRequirements($contexts, $requirements, $expected) {
+    $this->assertSame($expected, $this->contextHandler->checkRequirements($contexts, $requirements));
+  }
+
+  /**
+   * Provides data for testCheckRequirements().
+   */
+  public function providerTestCheckRequirements() {
+    $requirement_optional = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
+    $requirement_optional->expects($this->atLeastOnce())
+      ->method('isRequired')
+      ->will($this->returnValue(FALSE));
+
+    $requirement_any = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
+    $requirement_any->expects($this->atLeastOnce())
+      ->method('isRequired')
+      ->will($this->returnValue(TRUE));
+    $requirement_any->expects($this->atLeastOnce())
+      ->method('getDataType')
+      ->will($this->returnValue('any'));
+    $requirement_any->expects($this->atLeastOnce())
+      ->method('getConstraints')
+      ->will($this->returnValue(array()));
+
+    $context_any = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_any->expects($this->atLeastOnce())
+      ->method('getContextDefinition')
+      ->will($this->returnValue(array()));
+
+    $requirement_specific = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
+    $requirement_specific->expects($this->atLeastOnce())
+      ->method('isRequired')
+      ->will($this->returnValue(TRUE));
+    $requirement_specific->expects($this->atLeastOnce())
+      ->method('getDataType')
+      ->will($this->returnValue('foo'));
+    $requirement_specific->expects($this->atLeastOnce())
+      ->method('getConstraints')
+      ->will($this->returnValue(array('bar' => 'baz')));
+
+    $context_constraint_mismatch = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_constraint_mismatch->expects($this->atLeastOnce())
+      ->method('getContextDefinition')
+      ->will($this->returnValue(array('type' => 'foo')));
+    $context_datatype_mismatch = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_datatype_mismatch->expects($this->atLeastOnce())
+      ->method('getContextDefinition')
+      ->will($this->returnValue(array('type' => 'fuzzy')));
+
+    $context_specific = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_specific->expects($this->atLeastOnce())
+      ->method('getContextDefinition')
+      ->will($this->returnValue(array('type' => 'foo', 'constraints' => array('bar' => 'baz'))));
+
+    $data = array();
+    $data[] = array(array(), array(), TRUE);
+    $data[] = array(array(), array($requirement_any), FALSE);
+    $data[] = array(array(), array($requirement_optional), TRUE);
+    $data[] = array(array(), array($requirement_any, $requirement_optional), FALSE);
+    $data[] = array(array($context_any), array($requirement_any), TRUE);
+    $data[] = array(array($context_constraint_mismatch), array($requirement_specific), FALSE);
+    $data[] = array(array($context_datatype_mismatch), array($requirement_specific), FALSE);
+    $data[] = array(array($context_specific), array($requirement_specific), TRUE);
+
+    return $data;
+  }
+
+  /**
+   * @covers ::getMatchingContexts
+   *
+   * @dataProvider providerTestGetMatchingContexts
+   */
+  public function testGetMatchingContexts($contexts, $requirement, $expected = NULL) {
+    if (is_null($expected)) {
+      $expected = $contexts;
+    }
+    $this->assertSame($expected, $this->contextHandler->getMatchingContexts($contexts, $requirement));
+  }
+
+  /**
+   * Provides data for testGetMatchingContexts().
+   */
+  public function providerTestGetMatchingContexts() {
+    $requirement_any = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
+    $requirement_any->expects($this->atLeastOnce())
+      ->method('isRequired')
+      ->will($this->returnValue(TRUE));
+    $requirement_any->expects($this->atLeastOnce())
+      ->method('getDataType')
+      ->will($this->returnValue('any'));
+    $requirement_any->expects($this->atLeastOnce())
+      ->method('getConstraints')
+      ->will($this->returnValue(array()));
+    $requirement_specific = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
+    $requirement_specific->expects($this->atLeastOnce())
+      ->method('isRequired')
+      ->will($this->returnValue(TRUE));
+    $requirement_specific->expects($this->atLeastOnce())
+      ->method('getDataType')
+      ->will($this->returnValue('foo'));
+    $requirement_specific->expects($this->atLeastOnce())
+      ->method('getConstraints')
+      ->will($this->returnValue(array('bar' => 'baz')));
+
+    $context_any = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_any->expects($this->atLeastOnce())
+      ->method('getContextDefinition')
+      ->will($this->returnValue(array()));
+    $context_constraint_mismatch = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_constraint_mismatch->expects($this->atLeastOnce())
+      ->method('getContextDefinition')
+      ->will($this->returnValue(array('type' => 'foo')));
+    $context_datatype_mismatch = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_datatype_mismatch->expects($this->atLeastOnce())
+      ->method('getContextDefinition')
+      ->will($this->returnValue(array('type' => 'fuzzy')));
+    $context_specific = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_specific->expects($this->atLeastOnce())
+      ->method('getContextDefinition')
+      ->will($this->returnValue(array('type' => 'foo', 'constraints' => array('bar' => 'baz'))));
+
+    $data = array();
+    // No context will return no valid contexts.
+    $data[] = array(array(), $requirement_any);
+    // A context with a generic matching requirement is valid.
+    $data[] = array(array($context_any), $requirement_any);
+    // A context with a specific matching requirement is valid.
+    $data[] = array(array($context_specific), $requirement_specific);
+
+    // A context with a mismatched constraint is invalid.
+    $data[] = array(array($context_constraint_mismatch), $requirement_specific, array());
+    // A context with a mismatched datatype is invalid.
+    $data[] = array(array($context_datatype_mismatch), $requirement_specific, array());
+
+    return $data;
+  }
+
+  /**
+   * @covers ::filterPluginDefinitionsByContexts
+   *
+   * @dataProvider providerTestFilterPluginDefinitionsByContexts
+   */
+  public function testFilterPluginDefinitionsByContexts($contexts, $definitions, $expected, $typed_data_definition = NULL) {
+    if ($typed_data_definition) {
+      $this->typedDataManager->expects($this->atLeastOnce())
+        ->method('getDefinition')
+        ->will($this->returnValueMap($typed_data_definition));
+    }
+
+    $this->assertSame($expected, $this->contextHandler->filterPluginDefinitionsByContexts($contexts, $definitions));
+  }
+
+  /**
+   * Provides data for testFilterPluginDefinitionsByContexts().
+   */
+  public function providerTestFilterPluginDefinitionsByContexts() {
+    $context = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context->expects($this->atLeastOnce())
+      ->method('getContextDefinition')
+      ->will($this->returnValue(array('type' => 'expected_data_type', 'constraints' => array('expected_constraint_name' => 'expected_constraint_value'))));
+
+    $data = array();
+
+    $plugins = array();
+    // No context and no plugins, no plugins available.
+    $data[] = array(array(), $plugins, array());
+
+    $plugins = array('expected_plugin' => array());
+    // No context, all plugins available.
+    $data[] = array(array(), $plugins, $plugins);
+
+    $plugins = array('expected_plugin' => array('context' => array()));
+    // No context, all plugins available.
+    $data[] = array(array(), $plugins, $plugins);
+
+    $plugins = array('expected_plugin' => array('context' => array('context1' => array('type' => 'expected_data_type'))));
+    // Missing context, no plugins available.
+    $data[] = array(array(), $plugins, array());
+    // Satisfied context, all plugins available.
+    $data[] = array(array($context), $plugins, $plugins);
+
+    $plugins = array('expected_plugin' => array('context' => array('context1' => array('type' => 'expected_data_type', 'constraints' => array('mismatched_constraint_name' => 'mismatched_constraint_value')))));
+    // Mismatched constraints, no plugins available.
+    $data[] = array(array($context), $plugins, array());
+
+    $plugins = array('expected_plugin' => array('context' => array('context1' => array('type' => 'expected_data_type', 'constraints' => array('expected_constraint_name' => 'expected_constraint_value')))));
+    // Satisfied context with constraint, all plugins available.
+    $data[] = array(array($context), $plugins, $plugins);
+
+    $typed_data = array(array('expected_data_type', TRUE, array('required' => FALSE)));
+    // Optional unsatisfied context from TypedData, all plugins available.
+    $data[] = array(array(), $plugins, $plugins, $typed_data);
+
+    $typed_data = array(array('expected_data_type', TRUE, array('required' => TRUE)));
+    // Required unsatisfied context from TypedData, no plugins available.
+    $data[] = array(array(), $plugins, array(), $typed_data);
+
+    $typed_data = array(array('expected_data_type', TRUE, array('constraints' => array('mismatched_constraint_name' => 'mismatched_constraint_value'), 'required' => FALSE)));
+    // Optional mismatched constraint from TypedData, all plugins available.
+    $data[] = array(array(), $plugins, $plugins, $typed_data);
+
+    $typed_data = array(array('expected_data_type', TRUE, array('constraints' => array('mismatched_constraint_name' => 'mismatched_constraint_value'), 'required' => TRUE)));
+    // Required mismatched constraint from TypedData, no plugins available.
+    $data[] = array(array(), $plugins, array(), $typed_data);
+
+    $typed_data = array(array('expected_data_type', TRUE, array('constraints' => array('expected_constraint_name' => 'expected_constraint_value'))));
+    // Satisfied constraint from TypedData, all plugins available.
+    $data[] = array(array($context), $plugins, $plugins, $typed_data);
+
+    $plugins = array(
+      'unexpected_plugin' => array('context' => array('context1' => array('type' => 'unexpected_data_type', 'constraints' => array('mismatched_constraint_name' => 'mismatched_constraint_value')))),
+      'expected_plugin' => array('context' => array('context2' => array('type' => 'expected_data_type'))),
+    );
+    $typed_data = array(
+      array('unexpected_data_type', TRUE, array()),
+      array('expected_data_type', TRUE, array('constraints' => array('expected_constraint_name' => 'expected_constraint_value'))),
+    );
+    // Context only satisfies one plugin.
+    $data[] = array(array($context), $plugins, array('expected_plugin' => $plugins['expected_plugin']), $typed_data);
+
+    return $data;
+  }
+
+  /**
+   * @covers ::applyContextMapping
+   */
+  public function testApplyContextMapping() {
+    $context_hit = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_hit->expects($this->atLeastOnce())
+      ->method('getContextValue')
+      ->will($this->returnValue(array('foo')));
+    $context_miss = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context_miss->expects($this->never())
+      ->method('getContextValue');
+
+    $contexts = array(
+      'hit' => $context_hit,
+      'miss' => $context_miss,
+    );
+
+    $plugin = $this->getMock('Drupal\Component\Plugin\ContextAwarePluginInterface');
+    $plugin->expects($this->once())
+      ->method('getContextDefinitions')
+      ->will($this->returnValue(array('hit' => 'hit')));
+    $plugin->expects($this->once())
+      ->method('setContextValue')
+      ->with('hit', array('foo'));
+
+    $this->contextHandler->applyContextMapping($plugin, $contexts);
+  }
+
+  /**
+   * @covers ::applyContextMapping
+   */
+  public function testApplyContextMappingConfigurable() {
+    $context = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context->expects($this->never())
+      ->method('getContextValue');
+
+    $contexts = array(
+      'name' => $context,
+    );
+
+    $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
+    $plugin->expects($this->once())
+      ->method('getContextDefinitions')
+      ->will($this->returnValue(array('hit' => 'hit')));
+    $plugin->expects($this->never())
+      ->method('setContextValue');
+
+    $this->contextHandler->applyContextMapping($plugin, $contexts);
+  }
+
+  /**
+   * @covers ::applyContextMapping
+   */
+  public function testApplyContextMappingConfigurableAssigned() {
+    $context = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context->expects($this->atLeastOnce())
+      ->method('getContextValue')
+      ->will($this->returnValue(array('foo')));
+
+    $contexts = array(
+      'name' => $context,
+    );
+
+    $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
+    $plugin->expects($this->once())
+      ->method('getContextDefinitions')
+      ->will($this->returnValue(array('hit' => 'hit')));
+    $plugin->expects($this->once())
+      ->method('setContextValue')
+      ->with('hit', array('foo'));
+
+    $this->contextHandler->applyContextMapping($plugin, $contexts, array('name' => 'hit'));
+  }
+
+  /**
+   * @covers ::applyContextMapping
+   *
+   * @expectedException \Drupal\Component\Plugin\Exception\ContextException
+   * @expectedExceptionMessage Assigned contexts were not satisfied: miss
+   */
+  public function testApplyContextMappingConfigurableAssignedMiss() {
+    $context = $this->getMock('Drupal\Component\Plugin\Context\ContextInterface');
+    $context->expects($this->never())
+      ->method('getContextValue');
+
+    $contexts = array(
+      'name' => $context,
+    );
+
+    $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
+    $plugin->expects($this->once())
+      ->method('getContextDefinitions')
+      ->will($this->returnValue(array('hit' => 'hit')));
+    $plugin->expects($this->never())
+      ->method('setContextValue');
+
+    $this->contextHandler->applyContextMapping($plugin, $contexts, array('name' => 'miss'));
+  }
+
+}
+
+interface TestConfigurableContextAwarePluginInterface extends ContextAwarePluginInterface, ConfigurablePluginInterface {
+}
